Backstack and task management for picture-in-picture on android O
Picture-in-picture (will be referenced as PIP below in this article) is a new feature available in android O, allowing user to watch a video in a small window floating on the screen.

Google has provided us a simple guidance on how to support PIP mode in your app and some best practices. In the guidance, it briefly mentions about using singleTask launch mode for your Activity supporting PIP.
In your app, a user might select a new video when browsing for content on the main screen, while a video playback activity is in PIP mode. Play the new video in the existing playback activity in full screen mode, instead of launching a new activity that might confuse the user.
To ensure a single activity is used for video playback requests and switched into or out of PIP mode as needed, set the activity’s
android:launchMode
tosingleTask
in your manifest.
If this doesn’t suffice your needs depends on your use cases, here we’re going to explore a bit more.
So, why task management?
When you successfully put Activity into PIP mode by calling enterPictureInPictureMode
(this could fail depends on current system state, e.g. another app is already in PIP mode), the Activity will be detached from its current task into a new one (it’s not specifically called out in the documentation though). And this might affect you if
- you need to provide custom back/up navigation behavior
- user can navigate to other activities from the Activity supporting PIP
Customize back/up navigation behavior
By default, the android system will navigate back to previous task user was in when back button was pressed (or up navigation as well if you didn’t have any customized rules), you can look up more here. That being said, if user enters PIP mode and then expanded into fullscreen mode from home screen for example, pressing back/up will take them back to home screen or other apps where they came from.
But what if you want user to stay in your app?
ActivityManager and AppTask provides us some convenient apis to interact with tasks in the app. For above use cases, what you want to do would be essentially to bring up your launcher task to foreground.
Navigate to other Activities from PIP-supported Activity
This is why singleTask
as default launch mode starts to break.
The system creates the activity at the root of a new task and routes the intent to it. However, if an instance of the activity already exists, the system routes the intent to existing instance through a call to its
onNewIntent()
method, rather than creating a new one.
Take a video app as an example, PlayerActivity
is the Activity for video playback supporting PIP, and we use singleTask
as its default launch mode, from where user can go to details page (represented as DetailsPageActivity
) to browse content and possibly starts new playback.
For a user flow like home -> player_1 -> details_1 -> details_2 -> player_2
, we might run into two problems here
- Backwards compatible on devices without PIP feature available. On pressing back button, user will land on home page instead of
details_2
page as what user would expected. This is because when you launch theplayer_2
Activity, it will clear the backstack of player task (created when launchingplayer_1
Activity) to make sure it’s at the root of the task. - With PIP available, when user goes to
details_1
page and we try to put player into PIP mode, it will end up put details page into PIP mode as well since they’re in the same task. App task will be something like below

So how can we fix this?
taskAffinity
comes to save. The documentation here explains it pretty well, in nutshell, you can think this as an identifier to conceptually group activities under different tasks. By default, if this is not set for activities, it will inherit the affinity set from the application. Also note that this will only work with allowTaskReparenting
attributes or activities launched with FLAG_ACTIVITY_NEW_TASK
flag.
After understanding that, what we want to do here is to keep standard
launch mode as unchanged (no side-affects for devices under android Oreo), provide a different taskAffinity
for player Activity above, and when launching details Activity on top of player, make sure it’s launched with new_task flag. Now, the app task will be something like below graph when user navigates to details page from playback while player enters PIP mode.

Hooray, now we have our details page properly placed over home page in launcher task while player is separated into its own task and put into PIP mode successfully. With this approach, it will also fail gracefully when enter PIP mode fails, in that case, no player task will be generated and backstack in launcher task would be home -> details_1 -> player_1
.
#happycoding