How to programmatically resume an Activity from a notification? - android

I've been reading a lot of answers on the topic but none of them resolve this issue.
The app is running a "foreground service" so a notification is required.
This is how the Intent is created.
Intent intent = new Intent(context, notificationClass);
return PendingIntent.getActivity(context, 0, intent, 0);
Note that the Activity class to launch from the notification is known at runtime (notificationClass). For extra context, there's a library that exposes a View which when inflated creates the Service which creates the Notification and because any Activity could include the View, the Class is requested so that when the user clicks the notification resumes the correct Activity.
Then, the intent is added to the notificationBuilder (NotificationCompat.Builder).
notificationBuilder.setContentIntent(intent);
Implementing this when the app goes to the background and then the notification is clicked, it creates a fresh copy of the Activity instead of resuming it.
For testing purposes, I got the expected behavior (the Activity is resumed after clicking the notification) by adding the launchMode of the Activity (know beforehand) to singleTop in the AndroidManifest.xml file. But I wasn't able to get it working in any other way.
With these constraints I'm wondering if it's possible to get the same behavior programmatically when the Notification is created. I've tried a bunch of Intent flags combinations (also addFlags vs setFlags) without luck.
Is there any possibility of creating an Intent to behave as explained?
Thanks a lot!
cc'ing https://stackoverflow.com/users/769265/david-wasser that has answered a lot of similar Intent-related questions

Not sure if I completely understand, but if you just want to bring the existing task to the foreground (which is the same as selecting a task from the list of recent tasks), then try this:
PackageManager pm = getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(packageName);
return PendingIntent.getActivity(context, 0, intent, 0);
the packageName is the name of the package in the AndroidManifest.xml.
This "launches" the root Activity for the task, which will not actually launch anything but just brings the existing task to the foreground.

After some trial and error testing, I got it working using the following combination of methods
PackageManager pm = context.getPackageManager();
Intent intent = pm.getLaunchIntentForPackage(context.getPackageName());
intent.setPackage(null);
return PendingIntent.getActivity(context, 0, intent, 0);enter code here
The key part here was intent.setPackage(null);
/**
* (Usually optional) Set an explicit application package name that limits
* the components this Intent will resolve to. If left to the default
* value of null, all components in all applications will considered.
* If non-null, the Intent can only match the components in the given
* application package.
*
* #param packageName The name of the application package to handle the
* intent, or null to allow any application package.
*
* #return Returns the same Intent object, for chaining multiple calls
* into a single statement.
*
* #see #getPackage
* #see #resolveActivity
*/
So apparently, pm.getLaunchIntentForPackage(context.getPackageName()); returns an Intent limited to the components in context.getPackageName() and setting the package explicitly to null is needed afterwards so that all components are considered.
I'm not completely sure about ^ though. FWIW adding it, solved the issue ¯\_(ツ)_/¯

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

setOnClickFillInIntent Error Handling

I have made an appWidget with a collection view. And in order to handle a click event on each view of the collection view, I used 'setPendingIntentTemplate' and 'setOnClickFillInIntent'. It is working fine in a normal case. It opens up the target activity by sending the intent upon click. But if an invalid uri (it is just not a valid uri for the target activity, but in syntax, it is a valid uri.) is injected to the intent for it, the widget just simply doesn't handle the click event. It doesn't throw any exception or return any error.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse(link);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
view.setOnClickFillInIntent(R.id.image, intent);
Is there any way to catch this error (the click event is not being handled due to the intent with an invalid uri)? - So that, I can switch to different code to handle the situation.
I was able to get a solution for this problem from another forum I also posted the same question.
Basically, exception handling for an Intent which is already executed is not possible.
But, there is a way to check if a target component (activity, service or receiver) exists for the intent before adding a PendingIntent
getPackageManager().queryIntentActivities( intent, flags )
If this returns an empty array, that means there is no activity that can be executed by the intent. With this method, you can determine whether you add a PendingIntent or not.

Prompt for default activity without actually opening activity

I need to set the default app for a specific mime type. I know how to clear the default but I need to then prompt the user without actually opening the app.
PackageManager p = mContext.getPackageManager();
ComponentName cN = new ComponentName(mContext, FakeDownloadActivity.class);
p.setComponentEnabledSetting(cN, PackageManager.COMPONENT_ENABLED_STATE_ENABLED, PackageManager.DONT_KILL_APP);
Intent selector = new Intent(Intent.ACTION_DEFAULT);
selector.addCategory(Intent.CATEGORY_DEFAULT);
selector.setType(mimeType);
mContext.startActivity(selector);
p.setComponentEnabledSetting(cN, PackageManager.COMPONENT_ENABLED_STATE_DISABLED, PackageManager.DONT_KILL_APP);
The code above launches the activity rather than ONLY selecting the default activity. It works be enabling a fake activity then disabling it. This causes the Select Default App dialog to show the next time it is called. I simply want to ONLY select the default activity.
What you are looking for is an ACTION_PICK_ACTIVITY intent.
First, you create an intent that defines the apps that should be eligible to choose, for instance:
Intent mainIntent = new Intent(Intent.ACTION_DEFAULT, null);
mainIntent.addCategory(Intent.CATEGORY_DEFAULT);
Then, you create the ACTION_PICK_ACTIVITY intent, and as an Extra, you pass the main intent you created before
Intent pickIntent = new Intent(Intent.ACTION_PICK_ACTIVITY);
pickIntent.putExtra(Intent.EXTRA_INTENT, mainIntent);
Now, you just start an activity for result with this intent:
startActivityForResult(pickIntent, 0);
And a dialog will be created where the used can pick an application, but when clicked, the activity is not launched, instead, it will stay in your activity, and the function onActivityResult will be called with the results. So you need to create that function:
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
//In data, you have all the information about the selected application
if (data != null) {
//You can launch the application that we just picked with startActivity(data);
//or explore the variable to get all the information than you want
}
}
Take a look at the Intent class. There you have information about the package name, and the class that would be launched.
From now, what you need is to set that package and class as the default to the intent, or whatever else you need. The bad side, is that you only can save that information for your own internal purposes, for example to decide what app to launch next time that the users performs some action. What you cannot do is to modify the system settings to set a default activity for a given intent. Actually, the package manager has the addPreferredActivity method, that was supposed to do this, but it is deprecated since API level 8, giving this reasons:
This is a protected API that should not have been available to third
party applications. It is the platform's responsibility for assigning
preferred activities and this cannot be directly modified. Add a new
preferred activity mapping to the system. This will be used to
automatically select the given activity component when
Context.startActivity() finds multiple matching activities and also
matches the given filter.

How to use an Android SearchView without singleTop?

I have an activity that I normally want to exist in multiple tasks, so that the [Back] button restores a previous state; however, I also want to use a SearchView with an existing activity, without pushing a new one onto the task stack (since I want to search what's currently displayed). Here's my problem:
If I set the activity's launch mode to "singleTop", the SearchView works with the current activity, but Context.startActivity does not add new activities to the stack.
If I set the activity's launch mode to "standard", startActivity adds new activities to the stack, but SearchView also creates a new (empty) activity, instead of searching the existing one.
I can't find any way make SearchView use intent flags (e.g. FLAG_ACTIVITY_SINGLE_TOP. I tried going the other way around, setting launch mode to "singleTop" then adding FLAG_ACTIVITY_NEW_TASK to every intent, but that worked only when launching from a different activity class; when I try to launch a new instance of an activity from its own class, Android didn't respect FLAG_ACTIVITY_NEW_TASK and didn't push a new task onto the stack.
Perhaps there's something I can do in Activity.onNewIntent to force the activity to clone itself then.
I'm on the verge of giving up on SearchView and just writing my own custom widget, despite that fact that "Beginning in Android 3.0, using the SearchView widget as an item in the action bar is the preferred way to provide search in your app" (Source) — so far, it seems just too inflexible and non-configurable to be useful in non-trivial cases.
Has anyone found a solution to this problem?
After a lot of reflection and false starts, I realized that SearchView was simply the wrong thing for this task. Google designed SearchView with a very specific type of search activity in mind:
The user types in a search query.
The app pushes a new activity showing a list of results.
That's not the search model I was using; instead, I wanted to refine a list currently shown on the screen using the query — it was more of a filtering search than a synchronous query and response model. SearchView is not designed for that, so I went ahead and wrote my own widget (and bound it to the search menu item and the search button).
I'm surprised that no one had any comments on this question — usually Android questions generate a lot of discussion. I guess that this was was just too far out there.
I had the same problem as the OP, but have a solution that doesn't mean giving up on using SearchView. It's not the perfect solution, but it's fairly clean and seems to work well.
As the OP stated...
when I try to launch a new instance of an activity from its own class, Android didn't respect FLAG_ACTIVITY_NEW_TASK and didn't push a new task onto the stack
...so my solution is to go via an intermediary Activity. For this, I created a DummyActivity class which basically takes the original intent and forwards it on to your destination activity.
So, from my original activity, instead of calling...
Intent destinationIntent = new Intent(getActivity(), DestinationActivity.class);
startActivity(destinationIntent);
...I call...
/*
* Create the intent as normal, but replace our destination Activity with DummyActivity
*/
Intent dummyIntent = new Intent(getActivity(), DummyActivity.class);
/*
* This will make it so that if user clicks back from the next activity, they won't be shown DummyActivity
*/
dummyIntent.setFlags(Intent.FLAG_ACTIVITY_NO_HISTORY);
/*
* Tell DummyActivity about the destination Activity
*/
dummyIntent.putExtra(DummyActivity.EXTRA_DESTINATION_ACTIVITY, YourDestinationActivity.class); //IMPORTANT - the destination Activity must implement Serializable
/*
* If you want, you can add any other extras to dummyIntent (which will be passed through to the destination activity). Launch DummyActivity.
*/
startActivity(dummyIntent);
In my manifest, DesinationActivity is defined as singleTop.
And the only other thing you should need is the DummyActivity class which shouldn't need any major customising...
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
public class DummyActivity extends Activity {
public static final String EXTRA_DESTINATION_ACTIVITY = "uk.co.mobilewebexpert.EXTRA_DESTINATION_ACTIVITY";
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//setContentView(R.layout.activity_dummy);
Intent intent = getIntent();
/*
* Get destination activity info
*/
#SuppressWarnings("unchecked")
Class<Activity> destinationActivity = (Class<Activity>) intent.getSerializableExtra(EXTRA_DESTINATION_ACTIVITY);
Bundle destinationActivityExtras = intent.getExtras();
/*
* Launch destination activity
*/
Intent destinationActivityIntent = new Intent(this, destinationActivity);
destinationActivityIntent.putExtras(destinationActivityExtras);
destinationActivityIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
startActivity(destinationActivityIntent);
}
/**
* Override super.onNewIntent() so that calls to getIntent() will return the latest
* intent, rather than the first intent.
*/
#Override
public void onNewIntent(Intent intent){
super.onNewIntent(intent);
setIntent(intent);
}
}
Hopefully this will work for you, too. Suggestions welcomed. Cheers.

How to get to another application's specific page

I have a widget application, which I have made clickable to reach to another application. However, I am stuck on how to get to a specific fragment in my application. My code is making me open the application, but I want to open a specific page. For that, I tried to put the package of the class I want to be displayed but I got a null pointer exception on the category launcher line. Is it possible? This is my code so far. TIA
Intent in = new Intent(Intent.ACTION_MAIN);
PackageManager manager = context.getPackageManager();
in = manager.getLaunchIntentForPackage("com.playup.android");
in.addCategory(Intent.CATEGORY_LAUNCHER);
PendingIntent pendingIntent = PendingIntent.getActivity(context,0 /* no requestCode */, in, 0 /* no flags */);

Categories

Resources