Android , broadcasting parcelable data - android

I've implemented a class that extends NotificationListenerService which works fine for picking up on notifications posted.
I'm then wanting to take the statusBarNotification object received and broadcast it.
I'd doing the following:
#Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
Intent intent = new Intent();
intent.putExtra("STATUS_BAR_NOTIFICATION",statusBarNotification);
intent.setAction("com.example.NotificationPosted");
sendBroadcast(intent);
}
But when I do this I get the following error:
01-05 01:50:14.333 19574-19673/com.example W/NotificationListenerService[NotificationListener]﹕ Error running onNotificationPosted
java.lang.RuntimeException: Not allowed to write file descriptors here
at android.os.Parcel.nativeAppendFrom(Native Method)
at android.os.Parcel.appendFrom(Parcel.java:431)
at android.os.Bundle.writeToParcel(Bundle.java:1679)
at android.os.Parcel.writeBundle(Parcel.java:636)
at android.app.Notification.writeToParcel(Notification.java:962)
at android.service.notification.StatusBarNotification.writeToParcel(StatusBarNotification.java:106)
at android.os.Parcel.writeParcelable(Parcel.java:1285)
at android.os.Parcel.writeValue(Parcel.java:1204)
at android.os.Parcel.writeArrayMapInternal(Parcel.java:618)
at android.os.Bundle.writeToParcel(Bundle.java:1692)
at android.os.Parcel.writeBundle(Parcel.java:636)
at android.content.Intent.writeToParcel(Intent.java:7013)
at android.app.ActivityManagerProxy.broadcastIntent(ActivityManagerNative.java:2361)
at android.app.ContextImpl.sendBroadcast(ContextImpl.java:1127)
at android.content.ContextWrapper.sendBroadcast(ContextWrapper.java:365)
at com.example.NotificationListener.onNotificationPosted(NotificationListener.java:113)
at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:168)
at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:56)
at android.os.Binder.execTransact(Binder.java:404)
at dalvik.system.NativeStart.run(Native Method)
Can anyone see what I'm doing wrong, or is this not possible. StatusBarNotification implements Parcelable

I've had the same problem with notifications from Twitter.
I successfully solved it setting the notification's extras to null.
Try this:
#Override
#SuppressLint("NewApi") // Notification.extras is only available in API Level 19+
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
// Apprarently, the bug is caused by the extras when they're written to the parcel
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
statusBarNotification.getNotification().extras = null;
}
Intent intent = new Intent();
intent.putExtra("STATUS_BAR_NOTIFICATION",statusBarNotification);
intent.setAction("com.example.NotificationPosted");
sendBroadcast(intent);
}
Please note that this solution could break the functionality of notification's app if you send the contentIntent (the app could think the extras are there without checking).

This is probably some kind of android bug as some users noted above. If you want to get around it and still use as much bundle as possible consider implementing custom bundle serializer/deserializer. I already answered a question how to build such thing in How to serialize a Bundle?
and what is missing is how to actually use it when packing/unpacking the parcel. This is described here:
#Override public void writeToParcel(Parcel dest, int flags) {
byte[] bytes = serializeBundle(yourBundle);
dest.writeInt(bytes.length);
dest.writeByteArray(bytes);
}
and then
YourConstructor(Parcel in) {
byte[] bytes = new byte[in.readInt()]; // read the length of the array
in.readByteArray(bytes); // read bytes to array
yourBundle = deserializeBundle(bytes); // unpack the bundle
}

I have crash only kitkat(api 19). Му error(show all error scrooling to right):
Error running onNotificationPosted
java.lang.AbstractMethodError: abstract method not implemented
at android.service.notification.NotificationListenerService.onNotificationPosted(NotificationListenerService.java)
at service.CustomNotificationListenerService.onNotificationPosted(CustomNotificationListenerService.kt:46)
at android.service.notification.NotificationListenerService$INotificationListenerWrapper.onNotificationPosted(NotificationListenerService.java:168)
at android.service.notification.INotificationListener$Stub.onTransact(INotificationListener.java:56)
at android.os.Binder.execTransact(Binder.java:404) at dalvik.system.NativeStart.run(Native Method)
Simple Solve: i removed method: super()

This solution worked for me:
#Override
public void onNotificationPosted(StatusBarNotification statusBarNotification) {
Bundle bundle = new Bundle(); statusBarNotification.getNotification().extras.putBundle("android.car.EXTENSIONS", bundle);
Intent intent = new Intent();
intent.putExtra("STATUS_BAR_NOTIFICATION",statusBarNotification);
intent.setAction("com.example.NotificationPosted");
sendBroadcast(intent);
}

Related

Implicit Internal Intent vulnerability showing up when Android app is deployed to the Playstore

Recently I have uploaded my android apk on the app store and its been told that the next upload to Google play store will get rejected and we need to check and resolve it. Below is the screenshot of the message:
They are referring to package name also. Below is the code:
#Override
public void onDestroy() {
cleanup();
super.onDestroy();
Intent intent = new Intent("com.test.dummyapp");
sendBroadcast(intent);
}
Please assist me how to resolve this.
Below is the code where the component is triggered:
IntentFilter restartFilter = new IntentFilter("com.test.dummyapp");
registerReceiver(restartBroadcastReciver, restartFilter);
private BroadcastReceiver restartBroadcastReciver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
doBindService();
}
};
When you do this, you are broadcasting an "implicit Intent". This is dangerous because any app can register to get this (potential leak of information) and any app can also broadcast this Intent (triggering your app).
Intent intent = new Intent("com.test.dummyapp");
sendBroadcast(intent);
To fix this you can use LocalBroadcastManager (it is deprecated, but still works). Using a local broadcast ensures that other apps cannot see your broadcast Intent and other apps cannot trigger your app this way.
See https://developer.android.com/reference/androidx/localbroadcastmanager/content/LocalBroadcastManager
As an alternative, you should be able to make the Intent explicit by setting the package name:
Intent intent = new Intent("com.test.dummyapp");
intent.setPackage("my.package.name");
sendBroadcast(intent);
It seems really weird to send a Broadcast in onDestroy. I can't possibly see a use for that, and I can see a lot of problems due to onDestroy being called unexpectedly (rotation, screen size change, etc).
But if you have to do it, use new Intent(getPackageName()). What they're looking for is a hardcoded package name like that. The problem is that if you run 'com.facebook.whateveritscalled' and a piece of malware is installed that named itself that, you would be sending the intent to it. Which if you have extras in the intent could be leaking information to it.
Thanks for the information.
I made some changes to the posted code. Let me know if this works fine.
#Override
public void onDestroy() {
cleanup();
super.onDestroy();
openApp((Context) context,"com.test.dummyapp");
}
public static boolean openApp(Context context, String packageName) {
PackageManager manager = context.getPackageManager();
try {
Intent i = manager.getLaunchIntentForPackage(packageName);
if (i == null) {
return false;
}
i.addCategory(Intent.CATEGORY_LAUNCHER);
context.sendBroadcast(i);
return true;
} catch (ActivityNotFoundException e) {
return false;
}
}

Accessing MainActivity once BOOT_COMPLETE is received

I have a reminder app that will notify me at a given time when the next reminder from an array of objects it due.
I am trying to make it set the notification again on boot.
I have my boot receiver all set in the Manifest, but how do I access any information from MainActivity once the phone has booted, given that the app hasn't been opened yet?
I was hoping to use this -
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("RegularReminders","onReceive");
new MainActivity().setNotifications();
}
}
But it returns a null error from within that notification once it tries to run the method in MainActivity, the app crashes as the emulator boots up and I see this in the logcat -
java.lang.RuntimeException: Unable to start receiver com.androidandyuk.regularreminders.BootReceiver: java.lang.NullPointerException: Attempt to invoke virtual method 'int java.util.ArrayList.size()' on a null object reference
This points to a line -
if (reminders.size() >= 0) {
I did wonder if I could save the notification message to SharedPrefs and call it back in the receiver, but I got errors of null object reference doing that too.
I tried sending back another broadcast adding extra info, but I guess the receiver set up in MainActivity isn't listening as the app hasn't been run?
I know Google is protecting us from Malware, not letting them do much after book, but is there any way round this so I can set my notification after a reboot?
Thanks.
Although you can't access variables from the MainActivity, you can access SharedPreferences and the SQLite Database used in the app, so in this case I made a new array and read the database into it and worked on that.
The problem is that your activity is not created when you receive ACTION_BOOT_COMPLETED.
What you could do is to start your activity after the boot is completed and pass it arguments to tell it do some work.
public class BootReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
Log.i("RegularReminders","onReceive");
if (intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)) {
Intent i = new Intent(context, YourActivity.class);
i.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
Bundle args = new Bundle();
//put some args inside the bundle
//attach the bundle
i.putExtras(args);
//start the activity
context.startActivity(i);
}
}
}
If you don't what to start and show your activity. You can use Android Services.

BroadcastReceiver in IntentService in WakefulBroadcastReceiver does not always work

So, I have this problem.
I'm using a dependency project that is some kind of GCM notification parser. It's a bit poorly written, however I'm forced to use it, becase of work related reasons. Anyways:
The main service (that extends IntentService) is launched with WakefulBroadcastReceiver.
After it receives message from GCM I does some magic and sends it to the main App using broadcast.
In main app I'm constantly running service with another BroadcastReceiver that catches messages and saves everything in database etc.
Why is it so complicated? Firstly - originally it was someone else's project and now I'm trying to fix bugs. Secondly - I have no access from dependency to the main application project so I pass messages with broadcasts.
And now, the fun part. I need to filter whether I want to show notification or not. While sending a message to my main AppService I check it with the history of previous messages and then I decide if I need to show this message to User or not. However, no matter what my decision is, my dependency still shows my notification.
So I added yet another broadcast, when after successful validation I launch in my dependency notification building method.
Here is the code:
My WakefulBroadcastReceiver:
public class GcmBroadcastReceiver extends WakefulBroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
ComponentName comp = new ComponentName(context.getPackageName(), PushService.class.getName());
startWakefulService(context, (intent.setComponent(comp)));
setResultCode(Activity.RESULT_OK);
}
}
Here is my Depencency service
public NotificationCheckerReceiver notificationCheckerReceiver;
...
#Override
protected void onHandleIntent(Intent intent) {
Bundle extras = intent.getExtras();
GoogleCloudMessaging gcm = GoogleCloudMessaging.getInstance(this);
String messageType = gcm.getMessageType(intent);
if (!extras.isEmpty()) {
if (GoogleCloudMessaging.MESSAGE_TYPE_MESSAGE.equals(messageType)) {
//Launch my "approval" receiving broadcast
launchBroadcastReceiver(extras, intent);
//send broadcast to main app with the message we will parse etc.
sendSmsBroadcast(...));
}
}
}
#Override
public void onDestroy() {
unregisterReceiver(notificationCheckerReceiver);
super.onDestroy();
}
//Launch to build notification
public void showNotification(Bundle extras){
...
//Basic notification builder
}
//Receive broadcast from DB if notification was already in the DB
private void launchBroadcastReceiver(Bundle extras, Intent intent){
Log.d(TAG, "Broadcast receiver loaded");
notificationCheckerReceiver = new NotificationCheckerReceiver(new NotiFlag() {
#Override
public void onReceiveApproval(Boolean flag, Intent intent, Bundle extras) {
Log.d(TAG, "Approved notification show");
showNotification(extras);
JustPushGcmBroadcastReceiver.completeWakefulIntent(intent);
}
}, intent, extras);
registerReceiver(notificationCheckerReceiver, new IntentFilter(notificationCheckerReceiver.INTENT_EVENT_NAME));
}
public void sendSmsBroadcast(String message, boolean isAppOnScreen){
...
//This works
}
}
and my "faulty" receiver:
public class NotificationCheckerReceiver extends BroadcastReceiver{
private final String TAG = getClass().getSimpleName();
public static final String INTENT_EVENT_NAME = "NOTIFLAG";
public static final String INTENT_FLAG_KEY = "FLAG";
Intent intent;
Bundle extras;
NotiFlag nofiFlag;
public NotificationCheckerReceiver(NotiFlag nofiFlag, Intent intent, Bundle extras){
Log.d(TAG, "Launched constructor NotificationChecker");
this.nofiFlag = nofiFlag;
this.intent = intent;
this.extras = extras;
}
#Override
public void onReceive(Context context, Intent intent) {
Log.d(TAG, "Launched onReceive");
Boolean bool = intent.getExtras().getBoolean(INTENT_FLAG_KEY);
Log.d(TAG, "___________Broadcast receiver got something and it is intent: "+bool);
if (bool != false) {
nofiFlag.onReceiveApproval(bool, this.intent, this.extras);
}
}
}
and lastly, what I'm sending from my main service:
public void sendNotificationCheckerBroadcast(Boolean message){
Intent flag = new Intent(NotificationCheckerReceiver.INTENT_EVENT_NAME);
flag.putExtra(NotificationCheckerReceiver.INTENT_FLAG_KEY, message);
DvLogs.d(TAG, "__________Sending intent: "+message);
sendBroadcast(flag);
}
What happens is that eveything to the point where I launch "sendNotificationCheckerBroadcast()". I get that I'm sending some kind of boolean... and that's it.
The funny part is: it SOMETIMES works.
I don't know why, but when for some reason it launches - everything is awesome.
EDIT:
When it works, because sometimes it does, I have this error:
01-15 11:20:22.204 3234-3234/pl.digitalvirgo.lafarge E/ActivityThread﹕ Service com.example.name.PushService has leaked IntentReceiver com.example.name.NotificationCheckerReceiver#43042b50 that was originally registered here. Are you missing a call to unregisterReceiver()?
android.app.IntentReceiverLeaked: Service com.example.name.PushService has leaked IntentReceiver com.example.name.NotificationCheckerReceiver#43042b50 that was originally registered here. Are you missing a call to unregisterReceiver()?
at android.app.LoadedApk$ReceiverDispatcher.<init>(LoadedApk.java:814)
at android.app.LoadedApk.getReceiverDispatcher(LoadedApk.java:610)
at android.app.ContextImpl.registerReceiverInternal(ContextImpl.java:1772)
at android.app.ContextImpl.registerReceiver(ContextImpl.java:1752)
at android.app.ContextImpl.registerReceiver(ContextImpl.java:1746)
at android.content.ContextWrapper.registerReceiver(ContextWrapper.java:479)
at com.example.name.PushService.launchBroadcastReceiver(Unknown Source)
at com.example.name.PushService.onHandleIntent(Unknown Source)
at android.app.IntentService$ServiceHandler.handleMessage(IntentService.java:65)
at android.os.Handler.dispatchMessage(Handler.java:102)
at android.os.Looper.loop(Looper.java:157)
at android.os.HandlerThread.run(HandlerThread.java:61)
Maybe it's somehow related?
I know that I should unRegister this Receiver ... somewhere. Tried onStop, but as we can see - no success.
Edit2:
Weird.
I believe, that the problem is in onStop() method. Probably it's called too early (?) so my Receiver has no chance to work. When I launch app without unRegister everything works. Of course I get bug above, but still... it's something.
Any ideas guys?
Well. The problem was inside the idea of IntentService.
intentService kills itself after onHandleIntent() method.
So the solution for this problem is to change IntentService into Service remembering to handle stopping this thing.

How can I start the MusicPlaybackService from an external app?

I'm trying to extend the functionality of the Wired Mic Remote Button through a Service and Receiver that sits on top of the Google Play Music App and communicates with it through broadcasts. I'm on ICS with a Google Nexus S.
The issue I have is my code works great once the MusicPlaybackService is running, which is not always the case. How do I start this service without showing any UI (without the user knowing). My code has the following definitions:
public static final String SERVICECMD = "com.android.music.musicservicecommand";
public static final String CMDNAME = "command";
public static final String CMDTOGGLEPAUSE = "togglepause";
public static final String TOGGLEPAUSE_ACTION = "com.android.music.musicservicecommand.togglepause";
I have tried the following options:
1) Based off this SO question. I'm not sure whether about SERVICECMD, as the question states I should use "com.android.music.musicservicecommand" though for ICS I believe the package names have changed.
Intent i = new Intent(SERVICECMD);
i.putExtra(CMDNAME, CMDTOGGLEPAUSE);
sendBroadcast(i);
I have also tried replacing SERVICECMD with TOGGLEPAUSE_ACTION.
2) From this android project issue.
Intent musicIntent = new Intent();
musicIntent.setClassName("com.google.android.music", "com.google.android.music.MusicPlaybackService");
musicIntent.setAction(TOGGLEPAUSE_ACTION);
musicIntent.putExtra(CMDNAME, CMDTOGGLEPAUSE); // or "next" or "previous"
sendBroadcast(musicIntent);
3) This code crashes with the error below. I'm not sure how to go further with this option.
Caused by: android.content.ActivityNotFoundException: Unable to find explicit activity class {com.google.android.music/com.google.android.music.MusicPlaybackService}; have you declared this activity in your AndroidManifest.xml?
Intent intent = new Intent();
intent.putExtra(CMDNAME, CMDTOGGLEPAUSE);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setClassName("com.google.android.music", "com.google.android.music.MusicPlaybackService");
startActivity(intent);
4) I've noticed that when the headset is removed from my phone, the MusicPlaybackService is started because it is responding to the Intent.ACTION_HEADSET_PLUG broadcast that is picked up by WiredAccessoryObserver. I tried duplicating that broadcast in my code using the values shown in LogCat.
Intent intent = new Intent(Intent.ACTION_HEADSET_PLUG);
intent.addFlags(Intent.FLAG_RECEIVER_REGISTERED_ONLY);
intent.putExtra("state", 0);
intent.putExtra("name", "h2w");
intent.putExtra("microphone", 1);
sendBroadcast(intent);
I'm really looking for a method that works all the time that just starts up the Service. I'm open to anything. I still have to try binding to the service that uses IMediaPlaybackService.aidl which is supposed to break randomly, and is not ideal. Thanks in advance for helping out.
EDIT
I tried binding to the service using the code below which threw an error:
Caused by: java.lang.SecurityException: Not allowed to bind to service Intent { cmp=com.google.android.music/.MusicPlaybackService }
In my OnCreate function
if (mPlaybackService == null) {
Intent i = new Intent();
i.setClassName("com.google.android.music","com.google.android.music.MusicPlaybackService" );
bindService(i, connection, Context.BIND_AUTO_CREATE);
}
In my class
IMediaPlaybackService mPlaybackService = null;
ServiceConnection connection = new ServiceConnection() {
public void onServiceConnected(ComponentName name, IBinder service) {
mPlaybackService = IMediaPlaybackService.Stub.asInterface(service);
}
public void onServiceDisconnected(ComponentName name) {
mPlaybackService = null;
}
};
I don't think this is possible. My workaround is to let the apps own broadcastreceiver wake up the service when the button is pressed. I just request audiofocus for my app and wait til it gets lost.

BroadcastReceiver on new app installs

I want to receive a notification when a new application is installed.
IntentFilter newAppFilter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
newAppFilter.addAction(Intent.ACTION_PACKAGE_CHANGED);
newAppFilter.addAction(Intent.ACTION_PACKAGE_INSTALL);
newAppFilter.addAction(Intent.ACTION_PACKAGE_REPLACED);
getApplicationContext().registerReceiver(newAppReceiver, newAppFilter);
public static BroadcastReceiver newAppReceiver = new BroadcastReceiver() {
public void onReceive(Context context, Intent intent) {
Log.e("Broadcast","Received");
}
};
But I am not able to get any log. Anyone can help me?
Try to add data scheme to your IntentFilter.
newAppFilter.addDataScheme("package");
Reference: IntentFilter.addDataScheme() documentation
If no schemes are included, then an
Intent will match only if it includes
no data.
If anyone runs across this, the intent documentation now says:
ACTION_PACKAGE_INSTALL - This constant is deprecated. This constant has never been used.

Categories

Resources