How to pass custom Serializable object to BroadcastReceiver via PendingIntent - java

I am trying to pass a custom Serialized object from my IntentService to a BroadcastReceiver using PendingIntent.
Here is my custom object:
Row.java
public class Row implements Serializable {
private String name;
private String address;
public Row(BluetoothDevice device) {
this.name = device.getName();
this.address = device.getAddress();
}
}
Here is my IntentService
MyIntentService.java
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService");
}
#Override
public void onCreate() {
super.onCreate();
}
#Override
protected void onHandleIntent(Intent workIntent) {
AlarmManager alarmMgr;
PendingIntent alarmPendingIntent;
Intent alarmIntent;
// The object "RowsList" is passed from my MainActivity and is correctly received by my IntentService.
// NO PROBLEMS HERE
Row[] arrRows = (Row[])workIntent.getSerializableExtra("RowsList");
Log.i(TAG, "Inside Intent Service...");
int interval = 2;
try{
if(interval != 0) {
alarmMgr = (AlarmManager)getSystemService(Context.ALARM_SERVICE);
alarmIntent = new Intent(this, AlarmReceiver.class);
alarmIntent.putExtra("IntentReason", "Reason"); // THIS GETS PASSED
alarmIntent.putExtra("RowsList", arrRows); // THIS DOES NOT GET PASSED
alarmPendingIntent = PendingIntent.getBroadcast(this, 0, alarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + (interval * 1000), alarmPendingIntent);
}
}
catch (Exception ignored){}
}
}
MainActivity.java
// NO PROBLEMS HERE
private Intent myIntent;
myIntent = new Intent(getApplicationContext(), MyIntentService.class);
Log.i(TAG, "Starting Intent Service...");
myIntent.putExtra("RowsList", arrRows);
getApplicationContext().startService(intentListenBT);
Here is my BroadcastReceiver:
AlarmReceiver.java
public class AlarmReceiver extends BroadcastReceiver {
Row[] arrRows;
#Override
public void onReceive(Context context, Intent intent) {
this.context = context;
String str = intent.getStringExtra("IntentReason"); // RECEIVED AS "Reason"
arrRows = (Row[])intent.getSerializableExtra("RowsList"); // HERE IT IS null
}
}
The most annoying part is that this code was working previously on my Nexus 6P (Lollipop 6.0 API23). It stopped working once I updated the same phone to Android 7.0(Nougat). Is there anything that has changed in Nougat that is causing this problem?
NOTE:
I ran my code using an emulator on Nexus 6P with API 23 and it works fine.

Most likely, you are running into the same sort of problem that you see with custom Parcelable implementations. Paraphrasing myself from that blog post: basically, if a core OS process needs to modify the Intent extras, that process winds up trying to recreate your Serializable objects as part of setting up the extras Bundle for modification. That process does not have your class and so it gets a runtime exception.
The most annoying part is that this code was working previously on my Nexus 6P (Lollipop 6.0 API23).
The behavior will vary by Android version, by how you are using the PendingIntent, and possibly by firmware/ROM. Do not assume that your current implementation will be reliable on any Android version.
Your only option is to not put the Serializable directly in an Intent extra. Use something other than Serializable (e.g., a nested Bundle), convert the Serializable into a byte[], etc.
This sample app demonstrates the latter approach, applied to a Parcelable object. The same basic technique should work for Serializable. (hat tip to AyeVeeKay for the link in the comments).

Related

unable to start activity from service class

Please be informed, we are trying to start activity from service class, which is fired on clicking push notification addaction intent. The service class contains two actions, one to stop playing ringtone and another to startactivity. But unfortunately the start activity just does not boot in our service class.
Service Class page is as given below:
public class RingtonePlayingService extends Service {
private static final String TAG = RingtonePlayingService.class.getSimpleName();
private static final String URI_BASE = RingtonePlayingService.class.getName() + ".";
public static final String ACTION_DISMISS = URI_BASE + "ACTION_DISMISS";
public static final String ACTION_START = URI_BASE + "ACTION_START";
private MediaPlayer mp;
private Ringtone ringtone;
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "onStartCommand");
int notificationId = intent.getIntExtra("notificationId", 0);
if (intent == null) {
Log.d(TAG, "The intent is null.");
return START_REDELIVER_INTENT;
}
String action = intent.getAction();
if (ACTION_START.equals(action)) {
String uri = String.valueOf(intent.getIntExtra("uri", 0));
Intent intents = new Intent(this, MainActivity.class);
intents.putExtra("uri", uri);
intents.putExtra("notification_id", notificationId);
intents.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
this.startActivity(intents);
}
return START_STICKY;
}
#Override
public void onDestroy() {
super.onDestroy();
mp.reset ();
}}
Firebase (from where service class's intent is fired)
Intent startIntent = new Intent(this, RingtonePlayingService.class);
startIntent.setAction(RingtonePlayingService.ACTION_START);
startIntent.putExtra("uri", uri);
startIntent.putExtra("notification_id", notification_id);
PendingIntent pt = PendingIntent.getService(this,123, startIntent, PendingIntent.FLAG_UPDATE_CURRENT);
NotificationCompat.Action action_n = new NotificationCompat.Action.Builder(R.mipmap.ic_launcher, tag, pt).build();
The objective here is on firing of pending Intent 'pt'. We want to start the activity (that opens the app's url) as well as run the service class (which stops the service and the ringtone).
Please help us find a solution on this never ending issue.
The service class contains two actions, one to stop playing ringtone and another to startactivity. But unfortunately the start activity just does not boot in our service class.
You cannot start an activity from the background on modern versions of Android.
Firebase (from where service class's intent is fired)
That code has problems:
Your Intent is for RingtonePlayingService, which according to your first code snippet is a Service. Yet, you try using it with PendingIntent.getActivity(), rather than PendingIntent.getService().
You are using addFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK), which are irrelevant for a Service.

Encapsulating alarm creation code in a different class than the Activity

I'm creating my first Android Notifications app, so I'm very much a beginner. I have a class, Notification.java, that asks the user for the time and date. Using these data, it creates an alarm that is triggered at the specified date and time.
Here is my code for Notification.java
public class Notification extends Activity {
private PendingIntent pendingIntent;
private SetAlarm alarm;
private Date date;
private Time time;
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_alarm);
findViewById(R.id.setTime).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setAlarmTime();
}
});
findViewById(R.id.setDate).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
setAlarmDate();
}
});
findViewById(R.id.checkBox).setOnClickListener(new View.OnClickListener() {
#Override
public void onClick(View v) {
createAlarm();
}
});
}
private void setAlarmTime() {
}
private void setAlarmDate() {
}
private void createAlarm() {
alarm = new SetAlarm();
}
}
The createAlarm() method is supposed to actually create the alarm using the information that the user has provided (i.e. time and date). However, I understand that I need the following code block to create the alarm?
private void setTheAlarm() {
Intent alarmIntent = new Intent(SetAlarm.this, AlarmReceiver.class);
pendingIntent = PendingIntent.getBroadcast(SetAlarm.this, 0, alarmIntent, 0);
AlarmManager manager = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
int interval;
/* Set the alarm to the date specified by user */
/* Repeating on every x minutes interval */
}
However, the Notification.java is where I am extending 'Activity'. It is also where I have the 'pendingIntent; code.
So essentially, how can I move the alarm creation code into a separate class when the code dealing with the Activity is in an entirely different class?
Thanks for the help. I hope my question is clear enough.
Not exactly clear if that is what you want, but if I understand you correct, you need the alarmManager inside an extra class to reach it from everywhere? You could make a static one like this:
public class MyAlarmManager{
private static AlarmManager mAlarmManager;
private static PendingIntent mPendingIntent;
//start alarm
public static void setAlarm(Context context, int alarmId, long alarmTime) {
if (mAlarmManager== null) {
mAlarmManager= (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
Intent startAlarmIntent = new Intent(context, YouReceiver.class);
if(mPendingIntent==null){
mPendingIntent = PendingIntent.getBroadcast(context, alarmId,
startAlarmIntent, PendingIntent.FLAG_UPDATE_CURRENT);
}
//check the version because of doze mode since MM
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
mAlarmManager.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP,
alarmTime, mPendingIntent);
} else {
mAlarmManager.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, alarmTime, mPendingIntent);
}
}
//stop alarm
public static void stopAlarm(Context context, int id) {
if (mAlarmManager == null) {
mAlarmManager = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);
}
Intent stopAlarmIntent = new Intent(context, YourReceiver.class);
if(mPendingIntent==null){
mPendingIntent= PendingIntent.getBroadcast(context, id, stopAlarmIntent, 0);
}
mAlarmManager.cancel(mPendingIntent);
mPendingIntent.cancel();
}
}
Then you can call it like:
MyAlarmManager.setAlarm(this, id, interval);
and stop it:
MyAlarmManager.stopAlarm(this, id);
You can do this from every class by passing the context and the identical id . The alarm id must be the same as you passed by starting the alarm, otherwise it will not work. Notice that above MarshMallow, there are some changes for AlarmManager and it´s possible that it does not work in every case. If your app get´s killed or goes into idle mode, the alarm won´t be triggered in every circumstance. To handle doze mode, see this:https://developer.android.com/training/monitoring-device-state/doze-standby.html
And be aware of any third party app and battery managers, that could kill your app. Also, Huawei devices have their own battery management besides the doze mode.
If this is not what you wanted, come back. Can´t guarantee that there is no error because I have overseen something, it´s from scratch.

Can I attach and then retrieve my own class descending from android.os.ResultReceiver to an Intent?

I'm trying to make a class that extends Android's ResultReceiver for use with a custom IntentService. The problem is that when I try to attach my receiver to an Intent using putExtra and then retrieve it in onHandleIntent that I am unable to cast it to the child class. A simple code example can be seen here.
public class MyReceiver extends ResultReceiver {
public JSONReceiver(Handler handler) {
super(handler);
}
}
public class MyService extends IntentService {
private static final String EXTRA_RECEIVER = "com.myname.extra.receiver";
protected static void sendIntent(Context context, MyReceiver receiver) {
Intent intent = new Intent(context, MyService.class);
intent.putExtra(EXTRA_RECEIVER, receiver);
//outputs com.myname.MyReceiver
Log.i("type", receiver.getClass().getName());
//outputs com.myname.MyReceiver
Log.i("type", intent.getParcelableExtra(EXTRA_RECEIVER).getClass().getName());
context.startService(intent);
}
#Override
protected void onHandleIntent(Intent intent) {
if (intent != null) {
final Parcelable parcelable = intent.getParcelableExtra(EXTRA_RECEIVER);
//outputs android.os.ResultReceiver
Log.i("type", parcelable.getClass().getName());
//results in a casting error
final MyReceiver receiver = (MyReceiver) parcelable;
}
}
}
It seems that the retrieval works as expected before calling startService so something must be happening between that and onHandleIntent being called that changes what the receiver is an instance of. I'm curious why/how this is happening and also if it is possible to achieve what I'm trying to accomplish here. Thanks!

How to set two intents in a pending intent

Is possible to set more than one intent for be launched when, in example, the user clicks an notification.
Let me explain my concrete problem:
I have an app with notifications. Each notification open a different Activity (with different extras too).
Now I want to extract info about the notifications usage. So, every time a notification gets open I'd like to launch a Service with some extras.
I'd like to implement that without modifying the existing activities, since they are not "guilty" of the change.
Ideally the pseudocode could be something like that:
Intent originalActivityIntent=...;
Intent notificationsAnalyticsIntent=getRegisterNotificationClick(notificationId,username);
PendingIntent pi= PendingIntent.multiple(
context,
originalActivityIntent,
notificationsAnalyticsIntent)
Having both intents launched when the notification is clicked.
Writting some kind of service/broadcast receiver could be pretty complex since I would need to handle the different params for each Activity.
Any ideas of how to keep this clean?
No.
The way we solved it is that every Activity in out app extends from some base activity and in each notification we pass an extra in the intent that the base activity handle it.
Reached a pretty clean solution. Not perfect but it works and keeps the code clean, It only requires to create a single adapter activity and its transparent for the rest of your app.
In the manifest:
<activity
android:name="com.tests.AnalyticsActivity"
android:noHistory="true">
</activity>
And the java code:
public class AnalyticsActivity extends Activity {
private static final String KEY_INTERNAL_COMPONENT = "com.tests.AnalyticsReceiver.COMPONENT";
private static final String KEY_NOTIFICATION_TYPE = "com.tests.AnalyticsReceiver.NOTIFICATION_TYPE";
private static final String KEY_USERNAME = "com.tests.AnalyticsReceiver.USERNAME";
public static Intent getLaunchIntent(
Context context, Intent intent, long notificationType, String username) {
intent.putExtra(KEY_INTERNAL_COMPONENT, intent.getComponent());
intent.setComponent(new ComponentName(context, AnalyticsActivity.class));
intent.putExtra(KEY_NOTIFICATION_TYPE, notificationType);
intent.putExtra(KEY_USERNAME,username);
return intent;
}
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if(savedInstanceState==null){
Intent intent=getIntent();
long notifType=intent.getLongExtra(KEY_NOTIFICATION_TYPE, 0);
String username=intent.getStringExtra(KEY_USERNAME);
Log.e("RegisterEvent", "notif=" + notifType + ",username=" + username);
ComponentName componentName=intent.getParcelableExtra(KEY_INTERNAL_COMPONENT);
intent.setComponent(componentName);
startActivity(intent);
}
}
}
I was able to solve this by essentially chaining intents. Have the PendingIntent start your NotificationsAnalyticsIntent; and then have NotificationsAnalyticsIntent start the activity intent. There are several ways to "tell" the notification intent which activity to start. One way is to pass it as a string.
Intent intent = NotificationsAnalyticsIntent.newIntent(applicationContext, SomeActivity.class);
PendingIntent pendingIntent = PendingIntent.getService(applicationContext, 0, intent, 0);
...
public class NotificationsAnalyticsIntent extends IntentService {
private static final String EXTRA_ACTIVITY_TO_START = "extra_activity_to_start";
public static Intent newIntent(final Context context, final Class activityToStart) {
Intent intent = new Intent(context, NotificationsAnalyticsIntent.class);
intent.putExtra(EXTRA_ACTIVITY_TO_START, activityToStart.getSimpleName());
return intent;
}
public NotificationsAnalyticsIntent() {
super(NotificationsAnalyticsIntent.class.getSimpleName());
}
#Override
protected void onHandleIntent(#Nullable final Intent intent) {
// .... do notification stuff
// TODO
// .... then start activity
String activityToStart = intent.getStringExtra(EXTRA_ACTIVITY_TO_START);
try {
Class<?> aClass = Class.forName(activityToStart);
Method method = aClass.getMethod("newIntent", Context.class);
Context params = getApplicationContext();
Intent activityIntent = (Intent) method.invoke(null, (Object) params);
startActivity(activityIntent);
} catch (ClassNotFoundException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) {
// TODO
}
}
}
Several limitations:
activity class has to be fully qualified. make sure activityToStart.getName() returns com.example.app.SomeActivity
reflection :barf:
can't start SomeActivity with it's own intent extras without some extra uglieness

Get Activity's widget from service

The main activity has an AlarmManager which calls a Service every X minutes. I need a way that the methods inside the service's class can update a TextView in the main activity, but I dont know how to get the TextView's object in the service. Is there any way?
Here is part of the code:
Intent myIntent = new Intent(MainActivity.this, MyAlarmService.class);
pintent = PendingIntent.getService(MainActivity.this, 0, myIntent, 0);
AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
alarmManager.setRepeating(AlarmManager.RTC_WAKEUP, calendar.getTimeInMillis(), 60000, pintent);
In your alarm service you have a onReceive method
public void onReceive(Context context, Intent arg1) {
String data = "haha";
if (data.isEmpty() == false && data.contentEquals("") == false) {
nm = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
CharSequence from = "sing";
CharSequence message = data;
//get the activity
PendingIntent contentIntent = PendingIntent.getActivity(context, 0,
new Intent(), 0);
Notification notif = new Notification(R.drawable.icon,
data, System.currentTimeMillis());
notif.defaults |= Notification.DEFAULT_SOUND;
notif.setLatestEventInfo(context, from, message, contentIntent);
nm.notify(1, notif);
}
Method call:
AlarmManager am = (AlarmManager) getSystemService(Context.ALARM_SERVICE);
Intent intent = new Intent(this, ReminderReceiverActivity.class);
PendingIntent pendingIntent = PendingIntent.getBroadcast(this, 0,
intent, PendingIntent.FLAG_CANCEL_CURRENT);
am.setRepeating(AlarmManager.RTC_WAKEUP, System.currentTimeMillis(),
100000, pendingIntent);
}
I had the same need, in an accelerometer-reading service and an app that would start/stop the service and display the current average accelerometer magnitude.
I used Service Binding to allow the Service to send a Message to the Activity, containing the accelerometer value.
See http://developer.android.com/guide/components/bound-services.html#Messenger for background on Binding. For a good example, see the API Samples' MessengerServiceActivities and MessengerService classes.
I did the following. I've left out the mundane details (such as Synchronization to avoid races) for clarity. Notice that I use bind() as well as StartService(). The bind() is for sending messages between the Activity and Service; the StartService() is so the Service keeps running after the Activity exits.
Basically, the Activity and Service exchange Messengers that will allow each to send Messages to the other. The Activity sends custom Messages to the Service in order to Subscribe to Service Messages or Unsubscribe from Service Messsages. When the Service wants to send data to the Activity, it sends a custom Message to the Activity's Messenger. The Activity, on receiving such a message, displays the new value to the user.
The answer that suggested using Notifications uses a simple way to get data onto the screen. This more complex Message-passing answer is needed if you want to display data in your Activity (vs. the Notification bar).
I apologize for the length of the example code below, but there are a lot of necessary details to convey.
In my Activity, named AccelServiceControl:
private FromServiceHandler handler; // My custom class for Handling Messages from my Service.
private Messenger fromService; // For receiving messages from our Service
...
public void onCreate(Bundle savedInstanceState) {
...
handler = new FromServiceHandler(this);
fromService = new Messenger(handler);
...
}
protected void onResume() {
...
// While we're in the foreground, we want to be bound to our service.
// onServiceConnected() will return the IBinder we'll use to communicate back and forth.
bindService(new Intent(this, AccelService.class), this, Context.BIND_AUTO_CREATE);
}
protected void onPause() {
...
// Note: By itself, this doesn't stop the Service from sending messages to us.
// We need to send a custom Unsubscribe Message to stop getting messages from the Service.
unbindService(this);
}
public void onClick(View v) {
// Send a custom intent to start or stop our Service.
if (buttonStart == v) {
startService(new Intent(AccelService.ACTION_START));
} else if (buttonStop == v) {
startService(new Intent(AccelService.ACTION_STOP));
}
...
}
public void onServiceConnected(ComponentName name, IBinder service) {
...
// Ask our Service to send us updates.
toService = new Messenger(service);
Message msg = Message.obtain(null, FromClientHandler.MSG_SUBSCRIBE); // our custom Subscribe message
msg.replyTo = fromService;
try {
toService.send(msg);
} catch (RemoteException e) {
// Failed because the Service has died.
// We handle this in onServiceDisconnected().
}
}
In the Activity's custom Message Handler:
....
public void handleMessage(Message msg) {
switch (msg.what) {
case MSG_ACCEL_UPDATE:
...
Bundle bundle = msg.getData();
double accelValue = bundle.getDouble(ACCEL_UPDATE_VALUE);
...then display the new accelValue in, for example, a TextView.
...
}
}
In the Service, named AccelService:
...
private Messenger fromClient; // For receiving messages from our Client(s).
private FromClientHandler handler; // needed just for unlinking at in onDestroy().
// Since we have only one Client, we store only one Activity's Messenger
private Messenger subscribedMessenger;
public void onCreate() {
...
handler = new FromClientHandler(this);
fromClient = new Messenger(handler);
}
public void onDestroy() {
// Unlink ourselves from our Handler, so the Garbage Collector can get rid of us. That's a topic in itself.
handler.unlink();
....
}
public int onStartCommand(Intent intent, int flags, int startId) {
...
int returnValue = START_NOT_STICKY;
String action = intent.getAction();
if (ACTION_START.equals(action)) {
doActionStart();
returnValue = START_STICKY;
} else if (ACTION_STOP.equals(action)) {
...
// Our Service is done
stopSelf();
}
...
return returnValue;
}
public IBinder onBind(Intent intent) {
// Hand back a way to send messages to us.
return fromClient.getBinder();
}
...when we want to send data to the Activity:
Message msg = Message.obtain(null, FromServiceHandler.MSG_ACCEL_UPDATE);
Bundle bundle = new Bundle();
bundle.putDouble(FromServiceHandler.ACCEL_UPDATE_VALUE, avgAccel);
msg.setData(bundle);
try {
subscribedMessenger.send(msg);
} catch (RemoteException e) {
// Failed because the Client has unbound.
subscribedMessenger = null;
}

Categories