Notification PendingIntent extras always empty - android

I read about this all day long with different kind of solutions but non worked so far.
I have an upload service (IntentService) that sends updates to a ResultReceiver. The receiver creates and manages the Notification.
After clicking on the notification (error or success) the MainActivity is loaded. But the bundle is always null. How can I change my code to access/get the bundle?
This happens if I'am in another Activity, if the App is in background and if the app is stopped.
CustomResultReceiver:
private Context mContext;
private NotificationManager mNotificationManager;
private NotificationCompat.Builder mBuilder;
public static final String RETURN_MESSAGE = "RETURN_MESSAGE";
public static final String RETURN_STATUS = "RETURN_STATUS";
private final int id = 1;
public CustomResultReceiver(Handler handler, Context context) {
super(handler);
mContext = context;
mNotificationManager = (NotificationManager) context.getSystemService(Context.NOTIFICATION_SERVICE);
mBuilder = new NotificationCompat.Builder(context);
mBuilder.setContentTitle("Upload data")
.setContentText("uploading...")
.setSmallIcon(R.mipmap.ic_launcher);
}
Creates the Notification. In onReceiveResult I notify the notification and if an error occured or the upload is successful I add a ContentIntent.
protected void onReceiveResult(int resultCode, Bundle resultData) {
super.onReceiveResult(resultCode, resultData);
String message;
boolean success;
switch (resultCode) {
case RESULT_ERROR:
message = resultData.getString(BROADCAST_MESSAGE, null);
boolean failed = resultData.getBoolean(EXTENDED_ACTION_FAILED, false);
if (failed) {
mBuilder.setProgress(0, 0, false);
mBuilder.setContentText("Aborted! Error while uploading.");
mBuilder.setContentIntent(createContentIntent(message, false));
mNotificationManager.notify(id, mBuilder.build());
}
break;
case RESULT_FINISHED:
message = resultData.getString(UPLOAD_MESSAGE, null);
success = resultData.getBoolean(UPLOAD_STATUS, false);
if (success) {
mBuilder.setProgress(0, 0, false);
mBuilder.setContentText("Upload successful");
mBuilder.setContentIntent(createContentIntent(message, true));
mNotificationManager.notify(id, mBuilder.build());
}
break;
}
}
createContentIntent() provides a PendingIntent with a result message and a success boolean:
private PendingIntent createContentIntent(String message, boolean success) {
Intent intent = new Intent(mContext, MainActivity.class);
intent.putExtra(RETURN_MESSAGE, message);
intent.putExtra(RETURN_STATUS, success);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP);
intent.setAction("NOTIFIY RESPONSE");
return PendingIntent.getActivity(mContext, 1, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
In the receiver I'am using:
android.app.NotificationManager;
android.app.PendingIntent;
android.content.Context;
android.content.Intent;
android.os.Bundle;
android.os.Handler;
android.os.ResultReceiver;
android.support.v4.app.NotificationCompat;
The MainActivity is an android.support.v7.app.AppCompatActivity.
For the creation of the PendingIntent I already tried several different Flag combinations.
Here are some snippets from the manifest:
My permissions:
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.CAMERA"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-feature android:name="android.hardware.camera"/>
The Activity that I open via the Notification:
<activity
android:name=".views.activities.MainActivity"
android:label="#string/title_activity_main"
android:screenOrientation="landscape"
android:theme="#style/AppTheme.NoActionBar"
android:windowSoftInputMode="stateHidden">
<intent-filter>
<action android:name="android.intent.action.RUN"/>
</intent-filter>
</activity>
Here the Service:
<service android:name=".services.UploadService"/>
For when I launch the activity from the ResultReceiver following happens:
The activity starts and onCreate is called where i check for the bundle like this:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.act_main);
...
Intent old = getIntent();
if (old != null) {
Bundle extras = old.getExtras();
if (extras != null) {
Log.d(MainActivity.class.getSimpleName(), "Message: " + extras.getString(CustomResultReceiver.RETURN_MESSAGE, "empty"));
Log.d(MainActivity.class.getSimpleName(), "Status: " + extras.getBoolean(CustomResultReceiver.RETURN_STATUS, false));
}
}
...
}

So I finally found the problem. After debugging several flag options and changing request codes. The request code here is irrelevant to the problem.
Responsible for the empty (not null) bundle is the flag ACTIVITY_NEW_TASK.
If I went with all other tested flags for the Intent or the PendingIntent everything works fine.
The android doc says following for ACTIVITY_NEW_TASK:
When using this flag, if a task is already running for the activity
you are now starting, then a new activity will not be started;
instead, the current task will simply be brought to the front of the
screen with the state it was last in. See FLAG_ACTIVITY_MULTIPLE_TASK
for a flag to disable this behavior.
This seems to indicate that the old Intent is still present and not the new one which I build in the receiver. Funny fact: even if I closed the app and then started the PendingIntent the bundle was empty. The doc as for my understanding would state it otherwise (activity not in a task? -> new task with new activity).

Related

BroadcastReciever.onReceive startActivity doesn't display activity

None of the comments below worked for me.
However, I have found a fix. I needed to change my AlarmActivity and it seems the keyguardManager.requestDismissKeyguard was very important.
Code:
public class AlarmActivity extends BaseActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
this.setTurnScreenOn(true);
this.setShowWhenLocked(true);
KeyguardManager keyguardManager = (KeyguardManager) getSystemService(Activity.KEYGUARD_SERVICE);
keyguardManager.requestDismissKeyguard(this, null);
}
}
Initial problem was..
I am trying to display an activity (startActivity) from a BroadcastReciever.onReceive method but it isn't working consistently. The code I have seems to work well if the device is active but has issues when the device is locked. The following code should be minSdkVersion 28 compliant.
The existing code uses an AlarmManager.setExactAndAllowWhileIdle to trigger a BroadcastReciever. Depending on some values inside intent.getExtras the code should display either: AssessmentAlarmActivity or MATActivity. AssessmentAlarmActivity needs to ALWAYS display even when the device is snoozing and on the locked screen. MATActivity should display only if device is active (doesn't have to wake device or display on lock screen).
The following code seems to work on a lot of devices. Looking at the logcat data, I see the BroadcaseReceiver.onReceive is getting triggered but sometimes the activity can take a minute before it will display. On some devices the activity will never displays at all. I am assuming the following code has incorrect values in the intent.addFlags code, but I am not sure.
AlarmManager.setExactAndAllowWhileIdle code:
private static void setAlarm(Context context, ResponseInfo responseInfo, int requestCode) {
// Create a new PendingIntent and add it to the AlarmManager
Intent intent = new Intent(context, ResponseAlarmReceiver.class);
responseInfo.setRequestCode(requestCode);
intent.putExtras(responseInfo.createBundle());
// Create a new PendingIntent and add it to the AlarmManager
PendingIntent pendingIntent = PendingIntent.getBroadcast(context, requestCode, intent, PendingIntent.FLAG_UPDATE_CURRENT);
AlarmManager am = (AlarmManager) context.getSystemService(Activity.ALARM_SERVICE);
am.setExactAndAllowWhileIdle(AlarmManager.RTC_WAKEUP, responseInfo.getAlarmTime(), pendingIntent);
pendingIntents.put(requestCode, pendingIntent);
}
BroadcastReceiver.onRecieve code:
public class ResponseAlarmReceiver extends BroadcastReceiver {
#Override
public void onReceive(Context context, Intent intent) {
// read the ResponseInfo from the extras
ResponseInfo responseInfo = new ResponseInfo();
responseInfo.readBundle(intent.getExtras());
// log requestInfo data
Log.i("ALARM", String.format("Now: %d, responseInfo: %s",
System.currentTimeMillis(), responseInfo.toString()) );
if (responseInfo.getAlarmType() == AlarmType.CANCEL) {
cancelAlarm(context);
}
else {
showAssessmentAlarm(context);
}
}
private static void showAssessmentAlarm(Context context) {
Log.i("ALARM", "showAssessmentAlarm top");
Intent nextIntent = new Intent(context, AssessmentAlarmActivity.class);
nextIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(nextIntent);
Log.i("ALARM", "showAssessmentAlarm bottom");
}
private void cancelAlarm(Context context) {
Log.i("ALARM", "cancelAlarm top");
Intent intent = new Intent(context, MATActivity.class);
intent.addFlags(Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED | Intent.FLAG_ACTIVITY_CLEAR_TOP | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
AndroidManifest.xml definition of the activities:
<activity
android:name=".MATActivity"
android:label="#string/app_name"
android:screenOrientation="portrait" >
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<activity android:name=".AssessmentAlarmActivity" android:theme="#android:style/Theme.Dialog" android:screenOrientation="portrait" ></activity>
MATActivity is a pretty standard activity, so I will skip that code. AssessmentAlarmActivity does have a special settings and extends AlarmActivity:
public class AlarmActivity extends BaseActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
// not minSdkVersion 28 complient, but this is what I have tested most
super.onCreate(savedInstanceState);
getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON |
WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED |
WindowManager.LayoutParams.FLAG_TURN_SCREEN_ON);
}
}
public class AssessmentAlarmActivity extends AlarmActivity {
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.assessment_alarm);
..
}
...
}
I think it's because of background process limits.
try testing it again with phone plugged in (charging). if the issue solved so it's because of background process limits.
if you are making a alarm clock or a very important thing to do you can use alarmManager.setAlarmClock(). it will bypass the restriction.
don't forget to acquire a wakeLock immediately in onReceive. else android system may go to idle mode very soon.
note: you may just need the second part (acquiring a WakeLock). but implementing both is a stronger way.

Android: NotificationCompat.MediaStyle action buttons don't do anything

I've got a simple Android app containing one Activity and a Service that derives from MediaBrowserServiceCompat. I've successfully gotten it set up to play audio from my main activity by using MediaBrowserCompat and MediaControllerCompat. It can even play and pause the audio from my Bluetooth headphones. All good.
My challenge concerns the NotificationCompat.MediaStyle notification that appears on the lock screen and in the notifications tray. The notification appears properly. However, when I add buttons using addAction() and MediaButtonReceiver.buildMediaButtonPendingIntent, they don't do anything. If I instead add a dummy PendingIntent that just launches my main activity, that works fine.
Here's my code to generate the notification (apologies, this is C# running in Xamarin, so the casing and names will be slightly different from what you might expect). This is inside my service class.
var builder = new NotificationCompat.Builder(this, CHANNEL_ID)
.SetVisibility(NotificationCompat.VisibilityPublic)
.SetSmallIcon(Resource.Drawable.ic_launcher)
.SetContentTitle("Title")
.SetContentText("Content")
.SetSubText("Subtext")
.SetLargeIcon(icon)
.SetColor(Android.Graphics.Color.DarkOrange)
.SetContentIntent(intent)
.SetDeleteIntent(MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionStop))
.AddAction(new NotificationCompat.Action(
Resource.Drawable.ic_pause, "Pause",
MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionPause)))
.SetStyle(new Android.Support.V4.Media.App.NotificationCompat.MediaStyle()
.SetShowActionsInCompactView(0)
.SetMediaSession(this.mediaSession.SessionToken)
.SetShowCancelButton(true)
.SetCancelButtonIntent(MediaButtonReceiver.BuildMediaButtonPendingIntent(this, PlaybackStateCompat.ActionStop))
);
this.StartForeground(NOTIFICATION_ID, builder.Build());
Here's what I have looked at so far to try to solve this:
When I start playback, I use MediaSession.setActive(true)
Each time I start and stop playback, I set the appropriate actions in PlaybackStateCompat
I have the session token set correctly.
I do not have anything set up as a MediaButtonReceiver in my manifest, nor have I set anything up to handle android.intent.action.MEDIA_BUTTON, because I am targeting Android 5.0 and higher and using the *Compat classes, and my understanding is that that is no longer necessary.
I know that media button events are being routed properly to my app, since my Bluetooth headphone buttons work. I tried it in my car and it works there too. It's just the buttons in the notification that won't work. I'm expecting them to generate calls to the appropriate methods of MediaSessionCompat.Callback. Is this incorrect? What am I doing wrong here?
I would be grateful for any pointers.
UPDATE:
I got it working. I needed to add the following inside the <application> node of the manifest:
<receiver android:name="android.support.v4.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
...and the following inside the node of the Service that implements MediaBrowserServiceCompat:
<intent-filter>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
I'm still a little confused about why this was necessary, since button presses from my Bluetooth headphones and car infotainment system were routed to the app just fine. More importantly, Google says:
If you already have a MediaBrowserServiceCompat in your app,
MediaButtonReceiver will deliver the received key events to the
MediaBrowserServiceCompat by default. You can handle them in your
MediaSessionCompat.Callback.
They gave this as an alternative to the option "Service Handling ACTION_MEDIA_BUTTON," so I took that to mean I didn't need to do anything more with my manifest. If anyone could enlighten me here, I would appreciate it.
But, for what it's worth, this worked for me.
Probably you have not set up the actions. Look the code below, it show how to bind the buttons with an intent. Please modify it for android O devices which require a channel.
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.media.MediaDescriptionCompat;
import android.support.v4.media.MediaMetadataCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
import android.support.v7.app.NotificationCompat;
/**
* Keeps track of a notification and updates it automatically for a given MediaSession. This is
* required so that the music service don't get killed during playback.
*/
public class MediaNotificationManager extends BroadcastReceiver {
private static final int NOTIFICATION_ID = 412;
private static final int REQUEST_CODE = 100;
private static final String ACTION_PAUSE = "com.example.android.musicplayercodelab.pause";
private static final String ACTION_PLAY = "com.example.android.musicplayercodelab.play";
private static final String ACTION_NEXT = "com.example.android.musicplayercodelab.next";
private static final String ACTION_PREV = "com.example.android.musicplayercodelab.prev";
private final MusicService mService;
private final NotificationManager mNotificationManager;
private final NotificationCompat.Action mPlayAction;
private final NotificationCompat.Action mPauseAction;
private final NotificationCompat.Action mNextAction;
private final NotificationCompat.Action mPrevAction;
private boolean mStarted;
public MediaNotificationManager(MusicService service) {
mService = service;
String pkg = mService.getPackageName();
PendingIntent playIntent =
PendingIntent.getBroadcast(
mService,
REQUEST_CODE,
new Intent(ACTION_PLAY).setPackage(pkg),
PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent pauseIntent =
PendingIntent.getBroadcast(
mService,
REQUEST_CODE,
new Intent(ACTION_PAUSE).setPackage(pkg),
PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent nextIntent =
PendingIntent.getBroadcast(
mService,
REQUEST_CODE,
new Intent(ACTION_NEXT).setPackage(pkg),
PendingIntent.FLAG_CANCEL_CURRENT);
PendingIntent prevIntent =
PendingIntent.getBroadcast(
mService,
REQUEST_CODE,
new Intent(ACTION_PREV).setPackage(pkg),
PendingIntent.FLAG_CANCEL_CURRENT);
mPlayAction =
new NotificationCompat.Action(
R.drawable.ic_play_arrow_white_24dp,
mService.getString(R.string.label_play),
playIntent);
mPauseAction =
new NotificationCompat.Action(
R.drawable.ic_pause_white_24dp,
mService.getString(R.string.label_pause),
pauseIntent);
mNextAction =
new NotificationCompat.Action(
R.drawable.ic_skip_next_white_24dp,
mService.getString(R.string.label_next),
nextIntent);
mPrevAction =
new NotificationCompat.Action(
R.drawable.ic_skip_previous_white_24dp,
mService.getString(R.string.label_previous),
prevIntent);
IntentFilter filter = new IntentFilter();
filter.addAction(ACTION_NEXT);
filter.addAction(ACTION_PAUSE);
filter.addAction(ACTION_PLAY);
filter.addAction(ACTION_PREV);
mService.registerReceiver(this, filter);
mNotificationManager =
(NotificationManager) mService.getSystemService(Context.NOTIFICATION_SERVICE);
// Cancel all notifications to handle the case where the Service was killed and
// restarted by the system.
mNotificationManager.cancelAll();
}
#Override
public void onReceive(Context context, Intent intent) {
final String action = intent.getAction();
switch (action) {
case ACTION_PAUSE:
mService.mCallback.onPause();
break;
case ACTION_PLAY:
mService.mCallback.onPlay();
break;
case ACTION_NEXT:
mService.mCallback.onSkipToNext();
break;
case ACTION_PREV:
mService.mCallback.onSkipToPrevious();
break;
}
}
public void update(
MediaMetadataCompat metadata,
PlaybackStateCompat state,
MediaSessionCompat.Token token) {
if (state == null
|| state.getState() == PlaybackStateCompat.STATE_STOPPED
|| state.getState() == PlaybackStateCompat.STATE_NONE) {
mService.stopForeground(true);
try {
mService.unregisterReceiver(this);
} catch (IllegalArgumentException ex) {
// ignore receiver not registered
}
mService.stopSelf();
return;
}
if (metadata == null) {
return;
}
boolean isPlaying = state.getState() == PlaybackStateCompat.STATE_PLAYING;
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(mService);
MediaDescriptionCompat description = metadata.getDescription();
notificationBuilder
.setStyle(
new NotificationCompat.MediaStyle()
.setMediaSession(token)
.setShowActionsInCompactView(0, 1, 2))
.setColor(
mService.getApplication().getResources().getColor(R.color.notification_bg))
.setSmallIcon(R.drawable.ic_notification)
.setVisibility(Notification.VISIBILITY_PUBLIC)
.setContentIntent(createContentIntent())
.setContentTitle(description.getTitle())
.setContentText(description.getSubtitle())
.setLargeIcon(MusicLibrary.getAlbumBitmap(mService, description.getMediaId()))
.setOngoing(isPlaying)
.setWhen(isPlaying ? System.currentTimeMillis() - state.getPosition() : 0)
.setShowWhen(isPlaying)
.setUsesChronometer(isPlaying);
// If skip to next action is enabled
if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS) != 0) {
notificationBuilder.addAction(mPrevAction);
}
notificationBuilder.addAction(isPlaying ? mPauseAction : mPlayAction);
// If skip to prev action is enabled
if ((state.getActions() & PlaybackStateCompat.ACTION_SKIP_TO_NEXT) != 0) {
notificationBuilder.addAction(mNextAction);
}
Notification notification = notificationBuilder.build();
if (isPlaying && !mStarted) {
mService.startService(new Intent(mService.getApplicationContext(), MusicService.class));
mService.startForeground(NOTIFICATION_ID, notification);
mStarted = true;
} else {
if (!isPlaying) {
mService.stopForeground(false);
mStarted = false;
}
mNotificationManager.notify(NOTIFICATION_ID, notification);
}
}
private PendingIntent createContentIntent() {
Intent openUI = new Intent(mService, MusicPlayerActivity.class);
openUI.setFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
return PendingIntent.getActivity(
mService, REQUEST_CODE, openUI, PendingIntent.FLAG_CANCEL_CURRENT);
}
}

Clicking on notification doesn't open Android app

I am using onesignal and firebase to push notifications from wordpress blog to Android app and when I click on notification that just arrived, application will open only if it run in the background. If it is completely closed, clicking on notification will do nothing. How do I achieve clicking on notification opens app even if app is not in the background opened?
Below is the code that handles notifications:
class nyonNotificationOpenedHandler implements OneSignal.NotificationOpenedHandler {
// This fires when a notification is opened by tapping on it.
#Override
public void notificationOpened(OSNotificationOpenResult result) {
OSNotificationAction.ActionType actionType = result.action.type;
JSONObject data = result.notification.payload.additionalData;
String customKey;
if (data != null) {
customKey = data.optString("customkey", null);
if (customKey != null)
Log.i("OneSignalnyon", "customkey set with value: " + customKey);
}
if (actionType == OSNotificationAction.ActionType.ActionTaken)
Log.i("OneSignalnyon", "Button pressed with id: " + result.action.actionID);
// The following can be used to open an Activity of your choice.
// Replace - getApplicationContext() - with any Android Context.
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
The code which I am using for opening app on the click of notification, which is working perfectly fine :
Intent resultIntent = new Intent(getApplicationContext(), YourActivity.class);
resultIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);
//if you want to send some data
resultIntent.putExtra(AppConstants.NOTIFICATION, data);
Now you have to create PendingIntent
PendingIntent: As per docs by creating pending intent means you are granting it the right to perform the operation you have specified as if the other application was yourself. https://developer.android.com/reference/android/app/PendingIntent
PendingIntent resultPendingIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_CANCEL_CURRENT
);
Now when you are creating your Notification set this pending intent setContentIntent(resultPendingIntent) to that notification.
Notification notification;
notification = mBuilder.setSmallIcon(icon).setTicker(title).setWhen(0)
.setAutoCancel(true)
.setContentTitle(title)
.setContentIntent(resultPendingIntent)
.setStyle(inboxStyle)
.setWhen(getTimeMilliSec(System.currentTimeMillis() + ""))
.setSmallIcon(R.drawable.ic_app_icon)
.setLargeIcon(BitmapFactory.decodeResource(context.getResources(), icon))
.setContentText(message)
.setChannelId(CHANNEL_ID)
.build();
Well to solve this problem, first thing is to read documentation more clearly (what I didn't do) so here it is:
By default OneSignal will open or resume your launcher Activity when a notification is tapped on. You can disable this behavior by adding the meta-data tag com.onesignal.NotificationOpened.DEFAULT set to DISABLE inside your application tag in your AndroidManifest.xml.
Make sure that you register it inside android manifest, e.g.:
<application ...>
<meta-data android:name="com.onesignal.NotificationOpened.DEFAULT" android:value="DISABLE" />
</application>
Create handler for opened notifications, e.g.:
public class MyNotificationOpenedHandler implements OneSignal.NotificationOpenedHandler {
private final Context context;
public MyNotificationOpenedHandler(Context context) {
this.context = context;
}
#Override
public void notificationOpened(OSNotificationOpenResult result) {
if (result.action.type == OSNotificationAction.ActionType.Opened) {
JSONObject data = result.notification.payload.additionalData;
if (data == null) {
return;
}
String category = data.optString("category", null);
if (category == null) {
return;
}
if (category.equals("global")) {
Intent intent = new Intent(context, NotificationDetailsActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(intent);
}
}
}
}
Next point is:
Make sure you are initializing OneSignal with setNotificationOpenedHandler in the onCreate method in your Application class. You will need to call startActivity from this callback.
You'll need to extend Application class, e.g.:
public class MyApplication extends Application {
#Override
public void onCreate() {
super.onCreate();
OneSignal.startInit(this)
.inFocusDisplaying(OneSignal.OSInFocusDisplayOption.Notification)
.setNotificationOpenedHandler(new MyNotificationOpenedHandler(getApplicationContext()))
.unsubscribeWhenNotificationsAreDisabled(true)
.init();
}
}
Set application name inside android manifest, e.g.:
<application
android:name=".MyApplication"
android:icon="#mipmap/ic_launcher"
android:roundIcon="#mipmap/ic_launcher_round"
android:theme="#style/AppTheme">
And you're ready to handle notifications when app is closed.
Set "setNotificationOpenedHandler"
OneSignal.startInit(this)
.inFocusDisplaying(OneSignal.OSInFocusDisplayOption.Notification)
.setNotificationOpenedHandler(new NotificationOpenedHandler())
.init();
Add this class to your launcher activity ( make sure notification have "additionalData" )
public class NotificationOpenedHandler implements OneSignal.NotificationOpenedHandler {
// This fires when a notification is opened by tapping on it.
#Override
public void notificationOpened(OSNotificationOpenResult result) {
//OSNotificationAction.ActionType actionType = result.action.type;
JSONObject data = result.notification.payload.additionalData;
String customKey;
if (data != null) {
customKey = data.optString("Data", null);
if (customKey != null)
{
Log.d("LOGGED", "notificationOpened: " + customKey);
if(customKey.equals("Notification"))
{
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT | Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
else
{
Intent intent = new Intent(getApplicationContext(), MainActivity.class);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(intent);
}
//Toast.makeText(MainActivity.this, "Value is : " + customKey, Toast.LENGTH_SHORT).show();
}
}
More Info
https://documentation.onesignal.com/docs/android-native-sdk#section--notificationopenedhandler-
Add the following to your AndroidManifest.xml to prevent the launching of your main Activity
<application ...>
<meta-data android:name="com.onesignal.NotificationOpened.DEFAULT" android:value="DISABLE" />
</application>

Android, Display alertDialog instead of notification when app is open

I followed this developer tutorial, and have Geofencing working within my app, as expected.
A notification is sent when a Geofence Transition occurs, from within an IntentService:
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
...
sendNotification(geofenceTransitionDetails);
}
private void sendNotification(String notificationDetails) {
// Create an explicit content Intent that starts the main Activity.
Intent notificationIntent = new Intent(getApplicationContext(), MainActivity.class);
// Construct a task stack.
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
// Add the main Activity to the task stack as the parent.
stackBuilder.addParentStack(MainActivity.class);
// Push the content Intent onto the stack.
stackBuilder.addNextIntent(notificationIntent);
// Get a PendingIntent containing the entire back stack.
PendingIntent notificationPendingIntent =
stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
// Get a notification builder that's compatible with platform versions >= 4
NotificationCompat.Builder builder = new NotificationCompat.Builder(this);
// Define the notification settings.
builder.setSmallIcon(R.mipmap.ic_launcher)
// In a real app, you may want to use a library like Volley
// to decode the Bitmap.
.setLargeIcon(BitmapFactory.decodeResource(getResources(),
R.mipmap.ic_launcher))
.setColor(Color.RED)
.setContentTitle(notificationDetails)
.setContentText("Return to app")
.setContentIntent(notificationPendingIntent);
// Dismiss notification once the user touches it.
builder.setAutoCancel(true);
// Get an instance of the Notification manager
NotificationManager mNotificationManager =
(NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
// Issue the notification
mNotificationManager.notify(0, builder.build());
}
This is cookie-cutter from the tutorial. The intent is set-up in the Main activity:
private PendingIntent getGeofencePendingIntent() {
// Reuse the PendingIntent if we already have it.
if (mGeofencePendingIntent != null) {
return mGeofencePendingIntent;
}
Intent intent = new Intent(this, GeofenceTransitionsIntentService.class);
// We use FLAG_UPDATE_CURRENT so that we get the same pending intent back when calling
// addGeofences() and removeGeofences().
return PendingIntent.getService(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
}
How can I add functionality that suppresses the notifications if the app is open, and instead displays an AlertDialog to the user? Ideally, I'd like to be able to execute different tasks, depending on which view the user is currently in when the Geofence Transition occurs. Can I monitor/intercept the transition from within each view, or somehow globally?
Thanks in advance.
Some of the answers were incomplete, and so here is the complete solution to what I was looking for.
First off, set up MyApplication class, that implements ActivityLifecycleCallbacks:
public class MyApplication extends Application implements Application.ActivityLifecycleCallbacks {
private static boolean isActive;
#Override
public void onCreate() {
super.onCreate();
registerActivityLifecycleCallbacks(this);
}
public static boolean isActivityVisible(){
return isActive;
}
#Override
public void onActivityResumed(Activity activity) {
isActive = true;
}
#Override
public void onActivityPaused(Activity activity) {
isActive = false;
}
... no other methods need to be used, but there are more that
... must be included for the ActivityLifecycleCallbacks
}
Be sure to name this in your manifest (only name line was added, rest is default):
<application
android:name=".MyApplication"
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:theme="#style/AppTheme"
android:hardwareAccelerated="true">
What was done above is used to track the lifecycle of your app. You can use this to check if your app is currently in the foreground or not.
Next is to set up a BroadcastReceiver, wherever you would like code to run (in the event that the app is open when the trigger occurs). In this case, it is in my MainActivity:
protected BroadcastReceiver mNotificationReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
... Do whatever you want here
Toast.makeText(...).show();
}
};
Register the receiver in your onCreate of the same activity:
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
LocalBroadcastManager.getInstance(this).registerReceiver(mNotificationReceiver, new IntentFilter("some_custom_id"));
}
And don't forget to unregister it:
#Override
protected void onDestroy() {
LocalBroadcastManager.getInstance(this).unregisterReceiver(mNotificationReceiver);
super.onDestroy();
}
When a broadcast is received, the code within the receiver is executed.
Now, to check if the app is in the foreground, and send a broadcast if it is. Inside of the IntentService:
#Override
protected void onHandleIntent(Intent intent) {
GeofencingEvent geofencingEvent = GeofencingEvent.fromIntent(intent);
if (geofencingEvent.hasError()) {
String errorMessage = getErrorString(this,
geofencingEvent.getErrorCode());
return;
}
int geofenceTransition = geofencingEvent.getGeofenceTransition();
// Test that the reported transition was of interest.
if (geofenceTransition == Geofence.GEOFENCE_TRANSITION_ENTER ||
geofenceTransition == Geofence.GEOFENCE_TRANSITION_EXIT) {
...
if(MyApplication.isActivityVisible()){
Intent intnt = new Intent("some_custom_id");
intnt.putExtra("message", geofenceTransitionDetails);
LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
}else{
sendNotification(geofenceTransitionDetails);
}
} else {
// Log the error.
}
}
The important bit is the last nested if-statement:
if(MyApplication.isActivityVisible()){
Intent intnt = new Intent("some_custom_id");
intnt.putExtra("message", geofenceTransitionDetails);
LocalBroadcastManager.getInstance(this).sendBroadcast(intnt);
}else{
sendNotification(geofenceTransitionDetails);
}
Check if the app is in the foreground using MyApplication.isActivityVisible(), as defined above, and then either send the notification, or send a broadcast. Just make sure that your intent code (i.e. "some_custom_id") matches on your sender and receiver.
And that's about it. If the app is in the foreground (specifically the MainActivity), I execute some code. If the app is not in the foreground, I send a notification.
The easiest way would be to use LocalBroadcastManager or some event bus.
So when transition happens you should send local broadcast from IntentService and catch it with some component X in between IntentService and any of your Activity's. Component X must track if any of your Activity's is in foreground and
if yes - pass other local broadcast up (to the foreground Activity),
if not - show notification.
Please note that in Android you cannot track easily if your app is in foreground or not (and if you have more than 1 Activity, you cannot do it properly in my opinion) but you can try.
a) You can notify your service of the activity's lifecycle events.
b) You can keep the current state of your UI in a static field in the activity and check it from the service before showing the notification.

Notification Action button to pass info to activity

I have created a big view style notification in a service
I intend to put a button that will pass some info back to the activity but it seems the activity just can't get the extras I set before.
Here's the code that I used to show the notification:
public class TestService extends Service {
...
#Override
public void onCreate() {
showNotification();
}
private void showNotification() {
PendingIntent contentIntent = PendingIntent.getActivity(this, 0,
new Intent(this, TestActivity.class), PendingIntent.FLAG_UPDATE_CURRENT);
Intent discardIntent = new Intent(this, TestActivity.class);
discardIntent.putExtra("piAction", "discard");
PendingIntent piDiscard = PendingIntent.getActivity(this, 0, discardIntent, PendingIntent.FLAG_CANCEL_CURRENT);
NotificationCompat.Builder mBuilder = new NotificationCompat.Builder(this);
mBuilder.setSmallIcon(R.drawable.ic_launcher);
mBuilder.setContentTitle("Test Notification");
mBuilder.addAction(R.drawable.content_discard, "Discard", piDiscard);
mBuilder.setStyle(new NotificationCompat.BigTextStyle().bigText("Test service is running"));
mBuilder.setContentIntent(contentIntent);
Notification notification = mBuilder.build();
notification.flags |= Notification.FLAG_AUTO_CANCEL;
mNotificationManager.notify(0, notification);
}
...
}
And here's the activity that will catch the info sent by the button in notification
public class TestActivity extends Activity {
...
#Override
protected void onResume() {
super.onResume();
Log.i("Activity Resume", "onResume");
Bundle extras = getIntent().getExtras();
if (extras != null) {
Log.i(TAG, "extras not null");
if (extras.containsKey("piAction")) {
Log.i("Intent Received", "piAction");
}
}
}
...
}
Please note, when launching TestActivity, it will also start TestService. What I intend to do is when the discard button inside the notification is clicked, it will pass the previously put extra back to TestActivity. However, after a few tests, I found TestActivity can be launched successfully, but it can't get the extras I set before.
So where's the possible problems in my code?
If you require any other details, please state in the comment, I'll update my question with those details accordingly.
I had face same type of problem when I was passing string from my notification to my launching activity to solve that
1) take a one String e.g. public String temp field in your application extended class
now instead of this
discardIntent.putExtra("piAction", "discard");
use this
YourApplication app = (YourApplication)getApplicationContext();
app.temp = "discard";
in your activity
instead of this
Bundle extras = getIntent().getExtras();
if (extras != null) {
Log.i(TAG, "extras not null");
if (extras.containsKey("piAction")) {
Log.i("Intent Received", "piAction");
}
}
get your piAction status from YourApplication
YourApplication app = (YourApplication)getApplicationContext();
String stringFromNotification = app.temp;

Categories

Resources