Send data to activity with FLAG_ACTIVITY_NEW_TASK and taskAffinity - android

I have almost the same requirements as this excellent question:
Send data to activity with FLAG_ACTIVITY_REORDER_TO_FRONT
However, I'm using FLAG_ACTIVITY_NEW_TASK because each of my primary activities needs to run as a new task. In my testing onNewIntent isn't called in this case (even when activity already running). So how do I pass data to it?
I can get onNewIntent to be called, by setting launchMode to singleTask, however then I run in to this bug: Android: bug in launchMode="singleTask"? -> activity stack not preserved
I want to accomplish:
If no instance exists, create a new instance, as a new task, and pass it data. (this part already works, I can get the data in onCreate)
If an instance already exists, bring the task (entire task, not just root/one activity) it is in to the front, and:
If task currently has a child activity displayed, do nothing
If task doesn't have a child activity displayed (just root/main activity), process the passed data and update the UI
I have one task with a button, "Go to Event", when clicked it needs to open/start the Calendar task and highlight a specific event (but the Calendar will ignore that request if another activity is being displayed over the top of it).

Thanks to #qbix's comment, I found this answer:
Add Intent.FLAG_ACTIVITY_SINGLE_TOP -- which seems like it wouldn't be valid when combined with FLAG_ACTIVITY_NEW_TASK, but it works. It achieves all behaviors I listed above, and only fires onNewIntent if the activity is at the top of the stack, so I don't even have to worry about detecting that and ignoring.
More details here, even though this Q&A is related to PendingIntent and notifications:
Intent from notification does not have extras

Related

Start new activity from notification in existing task

My app receives pushes and opens different activities according to the push type.
I use TaskStackBuilder for a pending Intent to create a synthetic backstack in conjunction with android:parentActivityNamein my manifest.
So far, so easy. When the App is not started, all works as expected. But if the app is in background (task is running), the pending Intent also starts my desired activity with the defined parent from the manifest, but resets the existing task. The problem is that other activities that were started by the user in the meantime are also cleared.
So what a want to achieve is:
if the app is not started, open the desired activity with the synthetic backstack (MainActivity)
if the app is running, respect the current task order and just push the desired activity on top of it.
I can't seem to make it work with the TaskStackBuilder.
I'd be happy for some insights.
You can't really do this with TaskStackBuilder. It isn't designed for that. It always resets the task to begin with.
I would do the following:
Have the Notification start an Activity. Don't use TaskStackBuilder and don't create any artificial back-stack. This Activity will run in the application's current task if the application is currently active, and it will be put on top of the most recent Activity that is open.
In onCreate() of this new Activity, check if this Activity is the root of the task using isTaskRoot(). If this Activity is the root of the task, this means that the app was not active prior to launching this Activity. In this case, you can create an artificial back-stack using TaskStackBuilder and launch the thing again the way you want it (this will reset the task).
Try using PendingIntent.getActivities with FLAG_ONE_SHOT, this way I was able to open stack of activities with correct navigation

Intent extras contains information even when activty is destroyed and relaunched

My application is launched using a tag, and based on the information contained in tag, it further proceeds. Now my app can also be started by using touching icon, and later it asks user to touch the tag. Small flow would be as below.
So MainActivity may contains tag data(if started from TagProcessorActivity), or may not contain data (if started from icon launch). Data is passed as intent extra value from TagProcessorActivity to IconLaunchActivity then to MainActivity. After main activity, app operation proceeds. When I leave the main activity, all my previous activities gets finish. I have checked onDestroy() is called for each activity. Now if I logout after MainActivity, (Logout simply a feature that closes all existing activity), and relaunch my application from recent app, my tag details still appears in MainActivity, which I dont know why.
To make is more clear my questions are:
1) Why activity which was destroyed still contains the information from previous launch.
2) I know about removeExtra() method, but is there some better options to tackle this problem.
3) and none the less, is there some thing wrong in my code or android is keeping that instance of intent extra?
PS: Not clear which piece of code to post, so if required feel free to ask for code.
Applications never exit in Android. onDestroy only destroys the activity, not any static variables left in the app. These will keep their value the next time an Activity is launched. This can be combined with some other features (like launching from the recent tasks menu causing you to launch the same intent) and this is the behavior you will get. The answer I always used was to detect this case (by checking the intent, there's a field that says if this is a restart or fresh), and ignoring the intent extras if so.
A finished task launched from Recents (as opposed to home screen launcher icon) will receive the old Intent, including its Extras, Data, etc. There is a way to know that it was launched from Recents so you can handle the Intent appropriately.
protected boolean wasLaunchedFromRecents() {
return (getIntent().getFlags() & Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY;
}
In my humble opinion, that flag is poorly named (other flags referencing the Recents list actually use that word, e.g. FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS, FLAG_ACTIVITY_RETAIN_IN_RECENTS) and the documentation was never updated to reflect the fact that many popular Android devices have a dedicated button for Recents:
This flag is not normally set by application code, but set for you by the system if this activity is being launched from history (longpress home key).

Whats Intent lifecycle?

Say i have started activity A with new Intent(context,class) i thats has sensitive data in it, when is this intent gets destroyed? in particular, in what cases among the following would getIntent() in Activity A's code return the exact same intent i?
Press on the activity's task on android's task manager
App icon was clicked and the activity was recreated and brought to front
Ive tried it with my app, iand i get weird results... normally it doesnt get the same intent, but sometimes it seem that it does, so i am not sure whats going on, anyway If i can be returned from any of the upper options how to avoid it?
I think a glance on lifecycle of an Intent would be helpfull if any1 know of any documentation regarding this...
Say i have started activity A with new Intent(context,class) i that's has sensitive data in it, when is this intent gets destroyed?
as long as there's object/class that holds reference to your Intent object - it will not be garbage collected. the Activity (Activity A) holds reference to the intent that started it, so as long as Activity A object is not garbage collected - then i also won't be garbage collected.
important comment: onDestroy() activity callback and class distractor are to different things!!!
in what cases among the following would getIntent() in Activity A's code return the exact same intent i?
assuming you are not calling setIntent() explicitly:
1) Press on the activity's task on android's task manager:
if the activity was previously stopped in reaction to back button navigation or someone called finish() explicitly on it, then the activity passed on the onDestroy() callback. in that case - pressing on the "application" from the recent tasks manager would re-created the activity with a new intent from scratch, and thus - getIntent() would bring this new intent that don't contain your extras or other overloads.
otherwise (the activity was sent to background via home button, or other activity started on top of it) : when you'll launch it back from the recent task - it will be intent object with the original extras you passed it before...
2) App icon was clicked and the activity was recreated and brought to
front
basically the same cases I mentioned in (1) apply to (2) , but basically it depends on two more things:
the intent flags that the specific launcher you are using is overloading on the intent it creates when it launch your activity.
the launch mode and activity flags you overloaded on the intent that you used to start your own activity.
assuming you are not using any of the above, and you are using normal good functional launcher application - the behavior would be exactly as I explained in (1)

Android default launchMode of LAUNCHER activity?

does the launchMode of the launcher activity in the manifest get ignored?
The android documentation says that the default launchMode is "standard" but this isn't logic for me if this would be applied to the main activity of an app because each time you start the app, another task would be created in the instance of the app.
Well, I delved into Android sources myself and found the following thing.
The launcher starts apps using the method startActivityAsUser in LauncherAppsService. The intent is constructed using these lines:
Intent launchIntent = new Intent(Intent.ACTION_MAIN);
launchIntent.addCategory(Intent.CATEGORY_LAUNCHER);
launchIntent.setComponent(component);
launchIntent.setSourceBounds(sourceBounds);
launchIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
According to Android documentation, the flag FLAG_ACTIVITY_NEW_TASK means:
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.
This effectively and unconditionally overrides launchMode specified (or omitted to default behaviour) in the app, and ignores this attribute.
I think this demonstrates that the documentation is not clear (or complete) enough. Without such deep investigations of the core source codes everyone can get unexpected results now and then.
You are confusing two things. One is launchMode and the other is "what happens when the user selects an app icon from the HOME screen, or selects a task from the list of recent tasks". These are 2 completely different things.
launchMode
Each Activity has a specified launchMode (the default is "standard" or "multiple". This tells Android how to start this Activity, and there are many factors that can contribute to the "interpretation" of the launchMode. It depends on what other flags may have been specified in the Intent used. It depends on which task requested the launch of the Activity (or if the launch was requested from a non-activity context, like from a Service or BroadcastReceiver). It depends on whether or not an existing instance of the Activity is already active in the specified task, etc.
Behaviour on selecting an app icon from the HOME screen or list of installed applications
When the user selects an app icon, startActivity() is called with an Intent containing the following data:
ACTION=MAIN
CATEGORY=LAUNCHER
Component is set to the package name and the class name of the Activity that is defined in the manifest with ACTION=MAIN and CATEGORY=LAUNCHER
Flag FLAG_ACTIVITY_NEW_TASK and FLAG_ACTIVITY_RESET_TASK_IF_NEEDED are set.
Regardless of the launchMode definition of the Activity to be launched, calling startActivity() with an Intent like this causes the following behaviour:
If there is already an existing task whose task affinity matches the Activity being started (in simple terms, if the app is already running), Android will simply bring the existing task to the foreground. That's it. It doesn't create an instance of any Activity. It doesn't call onNewIntent() on any Activity. It does nothing other than bringing the existing task to the foreground. This is why, even if you specify launchMode="standard" for your launcher Activity, Android doesn't create a new instance every time you click on your app icon.
If there isn't already an existing task whose task affinity matches the Activity being started (in simple terms, if the app isn't already running), Android will create a new task and launch the Activity into that task. launchMode doesn't play a role here, since there is absolutely no difference between the launch modes when launching a single Activity into a new task. Android always creates a new task and always creates a new instance of the Activity as the root of that task.
This behaviour is also the same when the user selectsa task from the list of recent tasks. If the task is still running, Android just brings the task to the foreground, does not start any new Activity instances and does not call onNewIntent(). If the task is no longer running, Android creates a new task and launches the launcher Activity into that task. The only difference here is that the flag FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY is also set in the Intent if the user selected a task from the list of recent tasks.
I hope this answers your question.
See this answer for a very detailed explanation of FLAG_ACTIVITY_RESET_TASK_IF_NEEDED and task reparenting in general.
Think of everything except the opening activity as an abstract implementation. Declaring an activity as
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
Will cause it to open first. Subsequent activities are Overriden at the time an Intent is formed to navigate between activities. The overrides are represented as intent flags.
A list of intent extras:
http://developer.android.com/reference/android/content/Intent.html
With flags being commands you'd otherwise have written in the Manifest.
You are right.The default mode is "standard".
According to android documentation
*In standard mode ,Every time there's a new intent for a "standard" activity, a new instance of the class is created to respond to that intent. Each instance handles a single intent.
*.If the parent activity has launch mode standard (and the up intent does not contain FLAG_ACTIVITY_CLEAR_TOP), the current activity and its parent are both popped off the stack, and a new instance of the parent activity is created to receive the navigation intent.
The behavior of Activity set to standard mode is a new Activity will always be created to work separately with each Intent sent. Imagine, if there are 10 Intents sent to compose an email, there should be 10 Activities launch to serve each Intent separately. As a result, there could be an unlimited number of this kind of Activity launched in a device.
Behavior on Android pre-Lollipop
standard Activity would be created and placed on top of stack in the same task as one that sent an Intent.
For example, when we share an image from gallery to a standard Activity, It will be stacked in the same task as described although they are from the different application.
If we switch the application to the another one and then switch back to Gallery, we will still see that standard launchMode place on top of Gallery's task. As a result, if we need to do anything with Gallery, we have to finish our job in that additional Activity first.
Behavior on Android Lollipop
If the Activities are from the same application, it will work just like on pre-Lollipop, stacked on top of the task.
But in case that an Intent is sent from a different application. New task will be created and the newly created Activity will be placed as a root Activity like below.
Source from here

Will android:alwaysRetainTaskState flag save the activity intent?

Suppose the current task stack of my app P contains activities: A B. A started B with some intent i.
A is defined with android:alwaysRetainTaskState flag.
Then user switched to other app, and after a while the process of P is killed by OS.
Then user started P from home screen. Since A has android:alwaysRetainTaskState flag, the stack will be restored to A B, and B is visible. My understanding is that, only B.onCreate() will be called and A.onCreate() will not be called. Am I right?
Besides, at the moment, does B still have the intent i? That is, when B calls getIntent(), will getIntent() returns null or an intent object, i?
Thanks!
My understanding is that, only B.onCreate() will be called and A.onCreate() will not be called. Am I right?
Yes, you are.
Besides, at the moment, does B still have the intent i? That is, when B calls getIntent(), will getIntent() returns null or an intent object, i?
getIntent() will work as always - it will return Intent object equal to one you have passed from activity A (not the same instance, but deserialized one)
This answer comes from my experiments (with android 2.3), but it fits neatly into Android's ideology of independent activities (IMHO it's the only reason why Intent can contain only serializable data even when it's used within one application), so I believe it's true for all android versions.
And "alwaysRetainTaskState" isn't even necessary - system will lead you to activity B even with default "false" value if you return to you task quickly (according to http://developer.android.com/guide/topics/manifest/activity-element.html#always, system will only reset task state "if the user hasn't visited the task for a certain amount of time, such as 30 minutes").

Categories

Resources