Incorrect PendingIntent being launched after home key press - android

I have two (or more) widgets, e.g. A and B, which should launch the same activity passing their appWidgetId in the Intent extras to the activity. This works fine after application install on the first launch from any of the widgets (the activity receives the correct appWidgetId). It also works fine if I press the back button after activity launch and launch the activity from a different widget. However, if I launch the activity from widget A, hit the home button and then launch the activity from widget B (or C or D...) it launches with A's appWidgetId. I'm baffled as to how to fix this. Here is how I am declaring my PendingIntent:
Intent intent = new Intent(context, WidgetActivity.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setAction(this.getClass().getName() + System.currentTimeMillis());
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent,
PendingIntent.FLAG_UPDATE_CURRENT);
If I understand correctly, I am successfully setting up unique PendingIntents for each widget. Any help most appreciated!
EDIT:
The accepted answer to this question suggests that:
I believe the problem is that you have a PendingIntent that only differs by extra. PendingIntents are cached, so if you use two with the same action and data, they'll overwrite each other.
However, my code creates randomized actions to perform and so I think my PendingIntents differ by more than just extras

Looks like I need to brush up on my activity lifecycle events. The answer was the following code put into my Activity:
#Override
protected void onNewIntent(Intent intent) {
setIntent(intent);
super.onNewIntent(intent);
}
The problem was that, on Home button press, the Activity was holding onto the original Intent which launched it. Then, when the other widget launched the Activity, the original intent was being reused. The above code simply replaces the original intent with the new one and carries on with the remaining lifecycle events.

Related

Avoid caching Intent Extras after App is closed with back press [duplicate]

I have issue in intent of my launcher activity.Scenerio is:
1. Send intents form notification service to my launcher activity
PendingIntent contentIntent = PendingIntent.getActivity(this, TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID, new Intent(this, MainActivity.class).putExtra("is_log", true), Intent.FLAG_ACTIVITY_CLEAR_TOP);
2. In my MainActivity i getting this intent. code is:
if(this.getIntent().getExtras()!=null){
boolean isLogNewTripScreen = (boolean)this.getIntent().getExtras().getBoolean("is_log");
}
}
3. this work fine but when i come from notification service,but when i launch from not notification service ,that data in intentis still there.How can i remove that data from intent.
EDIT: I've created a sample application to test this problem and possible solutions. Here are my findings:
If you launch your app from a notification with extras and then later return to your app by selecting it from the list of recent tasks, Android will launch the app again the same way it was launched from the notification (ie: with the extras). This is either a bug or a feature, depending on who you ask.
You'll need to add additional code to deal with this situation. I can offer 2 suggestions:
1. Use FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS
When you create your notification, set the flag Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS in the Intent. Then, when the user selects the notification and launches the app from the notification, this will not create an entry for this task in the list of recent tasks. Also, if there was an entry in the list of recent tasks for this application, that entry will also be removed. In this case, it will not be possible for the user to return to this task from the list of recent tasks. This solves your problem by removing the possibility that the user launches the app from the list of recent tasks (but only when the app has been launched from the notification).
2. Detect FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
When the user launches your app from the list of recent tasks, Android sets the flag Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY in the Intent that is passed to onCreate() of your launch activity. You can detect the presence of this flag in onCreate() and then you know that the app has been launched from the recent tasks list and not from the notification. In this case, you can just ignore the fact that the extras in the Intent still contain data.
Choose the solution that best suits the workflow for your application. And thanks for the question, this was an interesting challenge to solve :-)
Additional information:
You are creating the PendingIntent incorrectly. You are calling
PendingIntent contentIntent = PendingIntent.getActivity(this,
TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID,
new Intent(this, MainActivity.class).putExtra("is_log", true),
Intent.FLAG_ACTIVITY_CLEAR_TOP);
You are passing Intent.FLAG_ACTIVITY_CLEAR_TOP as the 4th parameter to getActivity(). However, that parameter should be PendingIntent flags. If you want to set FLAG_ACTIVITY_CLEAR_TOP on the Intent, you need to do it this way:
PendingIntent contentIntent = PendingIntent.getActivity(this,
TripLoggerConstants.PENDING_TRIPS_NOTIFICATION_ID,
new Intent(this, MainActivity.class).putExtra("is_log", true)
.addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP), 0);
I noticed that using fragments. I read a QR Code in Activity A that opens fragment 1, send its content to a webservice and if goes right, replace it with fragment 2. When user press back, the onBackPressed in Activity A call finish. If user select the app again in the list, it was opening fragment 1 instead of fragment 2.
I solved that checking in onBackPressed if extra contains a field indicating that fragment 2 was already opened. If true, moveTaskToBack(true) is called instead of finish()
Activity A
#Override
public void onBackPressed() {
Bundle extras = getIntent().getExtras();
if(extras.containsKey(Constants.TICKET_DONT_SHOW_QRCODE_SCREEN)){
moveTaskToBack(true);
}else {
finish();
}
}
Fragment 2
Intent mainIntent = getActivity().getIntent();
mainIntent.putExtra(Constants.TICKET_DONT_SHOW_QRCODE_SCREEN, true);
getActivity().setIntent(mainIntent);
I've tested all the answers of stackoverflow with no luck, what worked for me was this. Create a helper class to check the activity flags. Or a function, it does not matter.
object FlagHelper {
fun notLaunchedFromNotification(activity: AppCompatActivity): Boolean {
return activity.intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY
}
}
Then use as the following code. It returns a boolean so you can check the intent extras when it's false
val notLaunchedFromNotification = FlagHelper.notLaunchedFromNotification(this)
Add android:launchMode="singleInstance" to your launcher activity
and then Use flag Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS while starting your activity

Resume activity from a android tv recommendation

I have some troubles with the management of recommendations (notifications) on an Android TV with the Leanback Launcher.
I use the google sample here to implement my own : https://github.com/googlesamples/androidtv-Leanback
Expected result :
I'm on my activity "MainActivity" (with a webview)
I press HOME, so I'm on the Leanback launcher with recommendation include mines.
I press on one of them
Resume activity "MainActivity" without recreate it with a new Intent with a new extra.
Actually, The resume of the activity without reload the activity works fine, below the creation of the PendingIntent :
private PendingIntent buildPendingIntent(Parcelable media, int id) {
Intent detailsIntent = new Intent(this, MainActivity.class);
detailsIntent.putExtra("media", media);
detailsIntent.putExtra("id", id);
TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
stackBuilder.addParentStack(MainActivity.class);
stackBuilder.addNextIntent(detailsIntent);
return stackBuilder.getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
}
Current result :
I'm on my activity "MainActivity" (with a webview)
I press HOME, so I'm on the Leanback launcher with recommendation include mines.
I press on one of them
My activity receive "onNewIntent" event by in the extrat intent values I have always the same media.
This solution doesn't work, because Google said in a comment of the sample code :
// Ensure a unique PendingIntents, otherwise all recommendations end up with the same
// PendingIntent
detailsIntent.setAction(movie.getId());
So to differentiate all recommandations, I have to set Action with an ID, else, it will be always the last pendingintent sent to my activity. It's my current behavior, I receive in "onNewIntent" always the same "media" and "id" in the intent (the last one), whatever on which recommendation I click, I always get the same Intent.
But if I set the action with an "id", the activity "MainActivity" is recreated, so the resume failed, and the context of my webview is cleared my webview is reloaded :( but I get the good intent with the good media in the extra intent values.
Have you a solution to help me to have the behavior I want ?
To simplify my question, how can I resume my Activity B from a recommendation with the good pendingintent without reload my activity ?
Thank you in advance for your help.
I found a solution. I don't think it's the good one but it works for my case.
PendingIntent pIntent = PendingIntent.getActivity(this, id, detailsIntent, PendingIntent.FLAG_UPDATE_CURRENT);
return pIntent;
So, If I set the "resquetCode" with the id, so Android think it's a different Activity and resume it without recreate a new one.

Multiple widgets: Launching configuration activity on widget click

following scenario:
I have 3 of the same widgets on my home screen. If one gets clicked, the widget configuration activity gets launched.
This was implemented by following code:
Intent intent = new Intent(context, WidgetConfigurator.class);
intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, appWidgetId);
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent pendIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteView.setOnClickPendingIntent(R.id.widget_linearlayout, pendIntent);
The launching is working, but there is one problem:
1. Widget A gets clicked, configuration activity of Widget A is opened
2. User hits "back" key, configuration activity disappears
3. Widget B gets clicked, configuration activity of Widget B is opened
4. User hits "back" key
=> Now the configuration activity of Widget A is shown
I always only want the "actual" configuration activity (fitting to the widget that was clicked) to be shown. Which settings do i have to use for the Intent / PendingIntent?
thx for any help
Seems after trying around a lot i now found a working solution for all android versions:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP);
intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME)));
PendingIntent pendIntent = PendingIntent.getActivity(context, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT);
remoteView.setOnClickPendingIntent(R.id.widget_linearlayout, pendIntent);
Change your pending intent code to include the widget id as android reuses intents. I wasted many hours on this with the wrong intent being sent on click.
PendingIntent pendIntent = PendingIntent.getActivity(context, WIDGETID, intent, PendingIntent.FLAG_UPDATE_CURRENT);
A way to get sure your current activity gets closed is to call finish(). An android app is build like stack, the activity A starts, if the user hits back, this activity isn´t closing. It´s just lying under the new started activity B. If user hits back in activity B, then activity A will be shown. I never tried this, but if You call finish in your onBackPressed method, the current activity will be closed. To do this, override onBackPressed in your Activitys.

Widget launching a second instance of application

I am having problems launching my application from my widget when it's clicked upon.
Here is a cut of some code
// Create an Intent to launch activity
Intent intent = new Intent(context, Entry_MainTabView.class);
PendingIntent pendingIntent = PendingIntent.getActivity(context, 0, intent, 0);
updateViews.setOnClickPendingIntent(R.id.Widget, pendingIntent);
Now take the scenario.
I launch my app, exit to the home page , then click on my widget.
It launches a second instance of the application, so when I hit "back" , the application goes away and reveals the other copy. I then hit back again and finally go back to the homescreen.
How can I make the Intent only create a new instance if none already exists in ram ?
Have you looked at singleInstance and singleTask launch modes of an activity? This extract from Pro Android 2 indicates that there is no documented way of ensuring that there is only one instance of the widget running.

Android - When launch the same activity from widget with different extras, how to prevent the same instance show up after returned from HOME button?

I have a widget that contains 4 buttons to show 4 stock prices, each of them will launch into the same activity Quote.class to show stock details. In onUpdate(), it will set up the pendingIntent with extras with stock symbol. After I hit button A, it goes to Quote activity that shows stock A. Then I hit the BACK button to the homescreen, Quote activity calls onDestroy() and when I hit button B, stock B will show properly. However, when i hit HOME button after it shows stock A, the Quote activity only calls onStop without calling onDestroy(), then as i hit button B, it will call onStart() and it shows the same instance that shows stock A.
RemoteViews views = new RemoteViews(context.getPackageName(), R.layout.etappwidget);
setQuoteIntent(context, views, R.id.view1, "BAC", 1);
setQuoteIntent(context, views, R.id.view2, "C", 2);
setQuoteIntent(context, views, R.id.view3, "GOOG", 3);
setQuoteIntent(context, views, R.id.view4, "AAPL", 4);
private static void setQuoteIntent(Context context, RemoteViews views, int viewId, String symbol, int requestCode) {
Intent i = new Intent(context, Quote.class);
i.putExtra("SYMBOL", symbol);
i.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK);
PendingIntent pi = PendingIntent.getActivity(context, requestCode, i, PendingIntent.FLAG_UPDATE_CURRENT);
views.setOnClickPendingIntent(viewId, pi);
}
Originally I thought adding a flag in the Intent should solve this problem. But I have tried
i.setFlags(Intent.FLAG_ACTIVITY_MULTIPLE_TASK or FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_NO_HISTORY), none of them makes any difference.
So is there any ways to make it work? How can i remove the history stack from HOME button? How can I call onCreate in Quote activity and get new extras when i hit button B? Any help is appreciated. Thanks
It is normal for onDestroy to possibly not get called if you do simple task switching (like holding the HOME button). If you need to do clean-up, it needs to go in onPause.
I believe the problem is that you have a PendingIntent that only differs by extra. PendingIntents are cached, so if you use two with the same action and data, they'll overwrite each other. You can circumvent that by giving each some random data. Try passing the symbol in the data rather than through the extra (which is preferred anyway).
You can try this:
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
intent.setFlags(Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS);
Your activity will not be seen in the history stack
clickIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
Since: API Level 1 If set, the new activity is not kept in the
history stack. As soon as the user navigates away from it, the
activity is finished. This may also be set with the noHistory
attribute.
This solved the same issue of mine while implementing widget activities.
When you press home, most probably the activity will not be destroyed. It is put to pause state in such case. So, I guess your code to initiate the stock view would stationed in onCreate rather than onResume. So, moving those to onResume should solve the problem.
If you are using a different Action in your Intent and use the SingleTop or similar flag and override onNewIntent to detect the correct action that should do the trick. You need to be prepared to handle the intent in onCreate and onNewIntent.

Categories

Resources