Backstack and task management for picture-in-picture on android O

Chuang Xie
ProAndroidDev
Published in
4 min readMar 26, 2018

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.

PIP demo from google developer guide

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’sandroid:launchMode to singleTask 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

  1. you need to provide custom back/up navigation behavior
  2. 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.

Code snippet to bring up launcher task

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

  1. 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 the player_2 Activity, it will clear the backstack of player task (created when launching player_1 Activity) to make sure it’s at the root of the task.
  2. 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
App task info with singleTask as default launch mode

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.

App task info with unique taskAffinity for player

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

Free

Distraction-free reading. No ads.

Organize your knowledge with lists and highlights.

Tell your story. Find your audience.

Membership

Read member-only stories

Support writers you read most

Earn money for your writing

Listen to audio narrations

Read offline with the Medium app

--

--

Responses (1)

Write a response