Upgrade to Pro — share decks privately, control downloads, hide ads and more …

Task & Document API

Task & Document API

Tasks are the plumbing of Android apps: you use them every day when developing. But the Task API in Android is one of the most difficult API to apprehend. The behaviour fragmentation between the Android versions does not help us either: taskAffinity, launchMode, flags, etc. In addition to the Task API, Android 5.0 introduced the Document Task API. This new feature is a big upheaval on how we manage tasks as Android developers and users.

In this presentation, we will have an in depth look at these APIs. We will see how to use them in typical use cases.

Video available here: https://www.youtube.com/watch?v=NTWmxp8dzBs

Mathieu Calba

November 10, 2015
Tweet

More Decks by Mathieu Calba

Other Decks in Programming

Transcript

  1. What is a Task? An application is often composed of

    A task is an ordered queue of activities called “back stack”
  2. What is a Task? • Since 1.0: icon and name

    • Screenshot added in 4.3 • Icon and name customisation added in 5.0 Overview screen
  3. How does it work? 2 main ways: • Attributes on

    the <activity> tag in the AndroidManifest.xml • Flags on the Intent Task API
  4. How does it work? android:allowTaskReparenting android:alwaysRetainTaskState android:autoRemoveFromRecents android:clearTaskOnLaunch android:documentLaunchMode android:excludeFromRecents

    android:finishOnTaskLaunch Task API android:launchMode android:maxRecents android:noHistory android:relinquishTaskIdentity android:stateNotNeeded android:taskAffinity
  5. How does it work? Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT Intent.FLAG_ACTIVITY_CLEAR_TASK Intent.FLAG_ACTIVITY_CLEAR_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS Intent.FLAG_ACTIVITY_FORWARD_RESULT

    Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY Intent.FLAG_ACTIVITY_MULTIPLE_TASK Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NEW_TASK Task API Intent.FLAG_ACTIVITY_NO_ANIMATION Intent.FLAG_ACTIVITY_NO_HISTORY Intent.FLAG_ACTIVITY_NO_USER_ACTION Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_REORDER_TO_FRONT Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS Intent.FLAG_ACTIVITY_SINGLE_TOP Intent.FLAG_ACTIVITY_TASK_ON_HOME Intent.FLAG_ACTIVITY_HELL
  6. Standard behaviour Start from launcher Action: android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags:

    • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  7. My awesome app Standard behaviour Start from launcher Action: android.intent.action.MAIN

    Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  8. My awesome app Standard behaviour Start from launcher Action: android.intent.action.MAIN

    Category: android.intent.category.LAUNCHER Flags: • Intent.FLAG_ACTIVITY_NEW_TASK • Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED
  9. Intent.FLAG_ACTIVITY_NEW_TASK Start the activity in a new Task depending on

    the task’s affinity Standard behaviour Start from launcher
  10. android:taskAffinity Used to determine in which task this activity will

    be launched when started with Intent.FLAG_ACTIVITY_NEW_TASK Application’s package name by default Standard behaviour Start from launcher
  11. Action: null Category: null Flags: null My awesome app Standard

    behaviour Start from within the app My awesome screen
  12. My awesome app My awesome screen Standard behaviour Start from

    within the app Action: null Category: null Flags: null
  13. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  14. My awesome app My awesome screen Standard behaviour Start from

    overview Action: null Category: null Flags: null
  15. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  16. Standard behaviour Start from overview My awesome app My awesome

    screen Action: null Category: null Flags: null
  17. My awesome app Same Intent that launched this activity Action:

    android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Standard behaviour Restart from back My awesome app My awesome screen
  18. My awesome app Same Intent that launched this activity Action:

    android.intent.action.MAIN Category: android.intent.category.LAUNCHER Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED Standard behaviour Restart from back My awesome app My awesome screen
  19. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  20. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  21. But there is more to it USE ANOTHER APP TO

    DO A JOB MANY ENTRY POINTS INTO OUR APP MULTITASKING INSIDE AN APP
  22. Using other applications Opening an Url It depends on the

    opened application! startActivity(new Intent().setAction(Intent.ACTION_VIEW) .setData(Uri.parse("http://captaintrain.com"));
 What behaviour does occur? New task or not?
  23. Using other applications • With the old Android browser: new

    task • With Firefox: new task • With Chrome: same task Opening an Url
  24. Using other applications Same task Action: android.intent.action.VIEW Category: null Flags:

    Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Opening an Url
  25. Using other applications Opening an Url New task Action: android.intent.action.VIEW

    Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP
  26. Using other applications Opening an Url New task Action: android.intent.action.VIEW

    Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP WHY DID IT APPEAR?
  27. Using other applications In the old Android browser’s manifest: !

    <activity
 android:label="@string/app_name"
 android:launchMode="singleTask"
 android:name=".MainActivity"
 android:theme="@style/AppTheme.NoActionBar"> Opening an Url
  28. Using other applications In the old Android browser’s manifest: !

    <activity
 android:label="@string/app_name"
 android:launchMode="singleTask"
 android:name=".MainActivity"
 android:theme="@style/AppTheme.NoActionBar"> Opening an Url
  29. android:launchMode • New instance of the activity all the time

    • Can be anywhere in the stack • Which task is determined by the presence of FLAG_ACTIVITY_NEW_TASK and the android:taskAffinity attribute android:launchMode=“standard”
  30. android:launchMode • New instance of the activity most of the

    time • Can be anywhere in the stack • Which task is determined by the presence of FLAG_ACTIVITY_NEW_TASK and the android:taskAffinity attribute • If the targeted task already has this activity at the top of it with this android:launchMode, instead of creating a new instance, it just send a new Intent to this instance, which will receive it in the onNewIntent() method android:launchMode=“singleTop”
  31. android:launchMode • Only one instance of the activity • Always

    the root activity of the task • Other activities can be in its task (only standard and singleTop activities) android:launchMode=“singleTask”
  32. android:launchMode • Only one instance of the activity • Always

    the root activity of the task • Alone in its task • Every startActivity() behave just like if the Intent has the FLAG_ACTIVITY_NEW_TASK flag android:launchMode=“singleInstance”
  33. Using other applications What is the correct behaviour? This way

    (app -> browser), it’s better to have them in the same task, because the browser is a utility Nothing can be done to prevent a new task to be launched here Opening an Url
  34. Using other applications Taking a picture final Intent intent =

    new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
  35. Using other applications Taking a picture Only works when activity

    started in the same task Otherwise, an Activity.RESULT_CANCELED is received directly final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
 startActivityForResult(intent, REQUEST_CODE_IMAGE_CAPTURE);
  36. Using other applications Sharing via email But it launch in

    the same task, which is not the expected behaviour on 5.0+ final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "[email protected]", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  37. Using other applications Sharing via email Action: android.intent.action.SENDTO Category: null

    Data: mailto:[email protected] Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "[email protected]", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  38. Using other applications Sharing via email Action: android.intent.action.SENDTO Category: null

    Data: mailto:[email protected] Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "[email protected]", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 startActivity(Intent.createChooser(target, "My chooser"));
  39. Using other applications Intent.FLAG_ACTIVITY_NEW_TASK? No, because we don’t want to

    be launched in the targeted application’s task Sharing via email
  40. Using other applications Intent.FLAG_ACTIVITY_NEW_DOCUMENT (5.0+) • Start a new task

    dedicated to this job • New mail and original application are accessible via the overview screen • Restarting the original task from overview will not bring the mail activity Sharing via email
  41. Using other applications Compatibility: Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET • Start a the job

    in the same task • Restarting the original task from overview will not bring the mail activity as it will be finished Sharing via email
  42. Using other applications final Intent target = new Intent()
 .setAction(Intent.ACTION_SENDTO)


    .setData(Uri.fromParts(“mailto", "[email protected]", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 target.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 } else {
 target.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
 }
 startActivity(Intent.createChooser(target, "My chooser")); Sharing via email
  43. Using other applications Sharing via email final Intent target =

    new Intent()
 .setAction(Intent.ACTION_SENDTO)
 .setData(Uri.fromParts(“mailto", "[email protected]", null))
 .putExtra(Intent.EXTRA_TEXT, "Hello Droidcon!");
 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
 target.addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 } else {
 target.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
 }
 startActivity(Intent.createChooser(target, "My chooser"));
  44. Multiple entry points Internally: • From the notification • From

    a widget • From a wear app Externally: • Multiple icons in the launcher • Responding to a public Intent (URL, sharing, etc)
  45. Multiple entry points Internally final PendingIntent pendingIntent = TaskStackBuilder.create(this)
 .addNextIntentWithParentStack(DetailsActivity.

    newFocusOnBarcodeIntent(this, id)) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT); Action: null Category: null Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT
  46. Multiple entry points Allow us to create a stack of

    activities Internally final PendingIntent pendingIntent = TaskStackBuilder.create(this)
 .addNextIntentWithParentStack(DetailsActivity. newFocusOnBarcodeIntent(this, id)) .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
  47. Multiple entry points Add the targeted activity’s Intent Internally TaskStackBuilder.create(this)


    .addParentStack(ParentActivity.class)
 .addNextIntent(detailIntent)
 .startActivities();
  48. Multiple entry points Internally Do both at the same time

    TaskStackBuilder.create(this)
 .addNextIntentWithParentTask(detailIntent)
 .startActivities();
  49. Multiple entry points Internally Create a pending Intent to be

    launched TaskStackBuilder.create(this)
 .addNextIntentWithParentTask(detailIntent)
 .getPendingIntent(0, PendingIntent.FLAG_UPDATE_CURRENT);
  50. Multiple entry points Multiple icons in the app drawer Use

    cases very uncommon: app with multiple specific jobs Multiple activities declared with intent-filter: ! <intent-filter>
 <action android:name="android.intent.action.MAIN" />
 <category android:name="android.intent.category.LAUNCHER" />
 </intent-filter> Become interesting with non-standard android:launchMode Externally
  51. Multiple entry points Intercepting Urls: ! <intent-filter>
 <action android:name="android.intent.action.VIEW" />


    
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 
 <data android:scheme="http" />
 <data android:scheme="https" />
 <data android:host="captaintrain.com" />
 <data android:host="www.captaintrain.com" />
 <data android:path="/search" />
 <data android:pathPrefix="/search/" />
 </intent-filter> Externally
  52. Multiple entry points From the old Android browser: Action: android.intent.action.VIEW

    Category: android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally S AM E TASK
  53. Multiple entry points From the email app: Action: android.intent.action.VIEW Category:

    android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NO_ANIMATION Externally N EW TASK
  54. Multiple entry points From the email app: Action: android.intent.action.VIEW Category:

    android.intent.category.BROWSABLE Data: https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET Intent.FLAG_ACTIVITY_NEW_DOCUMENT Intent.FLAG_ACTIVITY_NO_ANIMATION Externally N EW TASK
  55. Multiple entry points From Chrome: Action: android.intent.action.VIEW Category: android.intent.category.BROWSABLE Data:

    https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally N EW TASK
  56. Multiple entry points From Chrome: Action: android.intent.action.VIEW Category: android.intent.category.BROWSABLE Data:

    https://www.captaintrain.com/search Flags: Intent.FLAG_ACTIVITY_NEW_TASK Intent.FLAG_ACTIVITY_FORWARD_RESULT Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP Externally N EW TASK
  57. Multiple entry points 2 solutions: • let the current behaviour

    and let some user have some bad experience • force the creation of a new task for better user experience at the expense of clean code Externally
  58. Multiple entry points 2 solutions: • let the current behaviour

    and let some user have some bad experience • force the creation of a new task for better user experience at the expense of clean code Externally Here come android:launchMode=“singleTask” to the rescue!
  59. Multiple entry points Create a special activity that will catch

    all Urls you need to catch: ! <activity
 android:launchMode="singleTask"
 android:name=".UrlCatcherActivity"
 android:theme="@android:style/Theme.NoDisplay">
 <intent-filter> <action android:name="android.intent.action.VIEW" />
 
 <category android:name="android.intent.category.DEFAULT" />
 <category android:name="android.intent.category.BROWSABLE" />
 
 <data android:scheme="http" />
 <data android:scheme="https" />
 <data android:host="mydomain.com" />
 </intent-filter>
 </activity> And then it will redirect to the correct activity and finish itself Externally
  60. Multitasking inside an app Why? • multi-document opened at the

    same time • writing an email and looking at other emails as references • multi-tasking at the system level is quicker than at the app level
  61. Introducing Document API (5.0+) An extension of the Tasks API

    Move from app-centric to document-centric system
  62. Multitasking inside an app Only one instance of this document,

    one task Same as setting FLAG_ACTIVITY_NEW_DOCUMENT alone on the Intent android:launchMode=“standard” mandatory Create new document <activity
 android:label="NewDocument"
 android:documentLaunchMode="intoExisting"
 android:name=".NewDocumentActivity" />
  63. Multitasking inside an app Multiple instance of this document, multiple

    tasks Same as setting FLAG_ACTIVITY_NEW_DOCUMENT and FLAG_ACTIVITY_MULTIPLE_TASK on the Intent android:launchMode must be standard Create new document <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:name=".NewDocumentActivity" />
  64. Multitasking inside an app Create new document Default value of

    the attribute New task only if FLAG_ACTIVITY_NEW_TASK or specific android:launchMode <activity
 android:label="NewDocument"
 android:documentLaunchMode="none"
 android:name=".NewDocumentActivity" />
  65. Multitasking inside an app Create new document Even with FLAG_ACTIVITY_NEW_DOCUMENT,

    no new document task <activity
 android:label="NewDocument"
 android:documentLaunchMode="never"
 android:name=".NewDocumentActivity" />
  66. Multitasking inside an app Limiting the history Define the max

    number of recent documents for one activity Between 1 and 50, default to 16 <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:maxRecents="50"
 android:name=".NewDocumentActivity" />
  67. Multitasking inside an app Starting in background Force the grouping

    of documents with the original activity Force the behaviour of the android:documentLaunchMode=“always” final ActivityOptions options = ActivityOptions.makeTaskLaunchBehind();
 final Intent intent = new Intent(this, NewDocumentActivity.class)
 .addFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
 startActivity(intent, options.toBundle());
  68. Multitasking inside an app ActivityManager.AppTask Contains a lot of information

    on a task, you can: ! public RecentTaskInfo getTaskInfo() ! public void moveToFront() ! public void finishAndRemoveTask() Managing documents
  69. Multitasking inside an app In a document activity: • finish()

    will terminate the activity but let the task remain in overview • finishAndRemoveTask() will terminate the activity and remove the task in overview Removing a document
  70. Multitasking inside an app Use FLAG_ACTIVITY_NEW_DOCUMENT when launching an external

    activity on 5.0+ On pre-5.0, use FLAG_ACTIVITY_CLEAR_TASK_WHEN_RESET to get roughly the same behaviour Compatibility
  71. Multitasking inside an app ActivityManager.TaskDescription Customising the overview entry of

    your activity: ! setTaskDescription(new ActivityManager.TaskDescription( "New label”, bitmap, getColor(R.color.red))); Overview screen customisation (5.0+)
  72. Multitasking inside an app Persistance behaviours (5.0+) Default value Persist

    the activity only if it’s the root of the task <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistRootOnly"
 android:name=".NewDocumentActivity" />
  73. Multitasking inside an app Persistance behaviours (5.0+) If the activity

    is the root of the task, the task is not persisted <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistNever"
 android:name=".NewDocumentActivity" />
  74. Multitasking inside an app Persistance behaviours (5.0+) The activity is

    persisted across reboot Uses a PersistableBundle in onCreate/onSavedInstanceState to restore/ persist some data across reboot <activity
 android:label="NewDocument"
 android:documentLaunchMode="always"
 android:persistableMode="persistAcrossReboots"
 android:name=".NewDocumentActivity" />