A comprehensive developer's guide to implementing Android Picture-in-Picture (PiP) mode. Learn to handle manifest setup, transitions, lifecycle events, and custom controls.
Modern mobile users expect seamless multitasking, such as watching videos or following navigation routes while using other apps. Android's Picture-in-Picture (PiP) mode addresses this by shrinking the active interface into a compact, floating window that overlays other applications. For developers, implementing a robust PiP experience is key to driving user retention, minimizing app abandonment during interruptions, and aligning with Android's platform standards.
Understanding Android's PiP Architecture
Android's Picture-in-Picture mode leverages the platform's multi-window API. When an activity enters PiP mode, the system places it in a special multi-window stack positioned at the absolute top of the window hierarchy. The system window manager handles user gestures for dragging, resizing, or closing the window, shielding developers from low-level window manipulation.
Because a PiP window remains visible, the system keeps the activity running rather than pausing all execution threads. However, because it occupies a tiny, non-interactive overlay, the system manages resources with extreme strictness. If your app does not handle resource allocation, rendering threads, and lifecycle shifts correctly, the OS will flag the activity as background-heavy and terminate it to reclaim memory for foreground tasks.
Use Cases and Design Choices
Before writing code, evaluate whether PiP is appropriate for your application. Only certain interfaces translate well to a miniature overlay, and placing non-essential apps in PiP ruins user experience.
From a product perspective, video platforms that support PiP see higher session duration because users don't close the app when they need to check a text message or browse a webpage.
Primary Use Cases
- Video Playback: Watching live streams, news broadcasts, or video tutorials while taking notes or messaging.
- Turn-by-Turn Navigation: Keeping maps visible while users toggle logistics or delivery tools.
- Video Calls: Keeping the active speaker visible while the user checks emails or documents.
When to Avoid PiP
- Text-Heavy Layouts: Feeds, article readers, or dashboards that rely on text readability.
- Interactive Forms: Apps requiring extensive keyboard inputs or complex interactions.
- Standard Games: Gameplay is impossible within a small, non-interactive window.
| App Category | Optimal Candidate | Sub-optimal Candidate | Key Reason |
|---|---|---|---|
| Media | Video Playback, Live Streaming | Audio Podcast, Text Articles | Visual continuity is key; audio doesn't need screen space. |
| Communication | Video Calls, Webinar Views | Chat Lists, Message Compose | Video calls need persistent feeds; typing requires a keyboard. |
| Utilities | GPS Navigation | Spreadsheets, Task Managers | GPS relies on glanceable updates; editing requires precision. |
Table: Assessing app compatibility with Android PiP mode.
Step-by-Step Implementation Guide
Implementing PiP requires manifest configurations, activity lifecycle management, and dynamic UI styling.
1. Configure the Android Manifest
In AndroidManifest.xml, declare support and specify configuration changes for the activity that will run in PiP mode:
<activity
android:name=".ui.VideoPlaybackActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation">
</activity>Specifying these configuration changes is mandatory. Each flag represents a viewport change that occurs when the window transitions.
2. Enter PiP Mode Programmatically
To trigger PiP, call enterPictureInPictureMode() using PictureInPictureParams to set the window's visual parameters.
Here is the entry method in Kotlin:
import android.app.PictureInPictureParams
import android.util.Rational
fun enterPlayerPiP() {
val aspectRatio = Rational(16, 9)
val pipParams = PictureInPictureParams.Builder()
.setAspectRatio(aspectRatio)
.build()
enterPictureInPictureMode(pipParams)
}The aspect ratio must fall between 1:2.39 and 2.39:1. Using ratios outside these bounds throws an IllegalArgumentException and crashes the app.
3. Implement Seamless Auto-Enter (Android 12+)
Instead of requiring users to click a button, transition to PiP automatically when they swipe home.
In Android 12 (API level 31) and higher, use the setAutoEnterEnabled(true) flag for an animation-smooth transition:
fun updatePiPParams() {
val pipParams = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.setAutoEnterEnabled(true)
.build()
setPictureInPictureParams(pipParams)
}For backward compatibility, override the onUserLeaveHint() method:
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)) {
val params = PictureInPictureParams.Builder()
.setAspectRatio(Rational(16, 9))
.build()
enterPictureInPictureMode(params)
}
}Using setAutoEnterEnabled is preferred because it utilizes system-level transitions, avoiding the flickering that occurs with onUserLeaveHint.
4. Optimize the UI for PiP
When entering PiP, your viewport shrinks. Hide all UI elements except the main video player to avoid clutter.
Override onPictureInPictureModeChanged to hide non-essential views:
import android.content.res.Configuration
override fun onPictureInPictureModeChanged(
isInPiP: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPiP, newConfig)
if (isInPiP) {
videoControlsLayout.visibility = View.GONE
channelDescriptionView.visibility = View.GONE
} else {
videoControlsLayout.visibility = View.VISIBLE
channelDescriptionView.visibility = View.VISIBLE
}
}Managing the Lifecycle and Playback States
Managing the activity lifecycle is crucial. When your activity transitions to PiP mode, the system places it in the Paused state. Under normal conditions, developers stop playback inside onPause(). In PiP, the app must keep playing.
To handle this correctly, modify your lifecycle callbacks to verify if the activity is in PiP mode before pausing:
override fun onPause() {
super.onPause()
if (isInPictureInPictureMode) {
// Keep playing video, pause non-essential background tasks
} else {
videoPlayer.pause()
}
}
override fun onStop() {
super.onStop()
// Release resources when the activity is fully invisible
videoPlayer.stop()
}In Android 10 and higher, the system supports Multi-Resume, allowing multiple visible activities to remain in the resumed state. However, manual lifecycle handling inside onPause() is still required to support older Android versions that treat PiP as a paused state.
Custom Media Controls with RemoteAction
By default, the PiP window only shows close and settings buttons. For media apps, implement play and pause controls using RemoteAction:
import android.app.PendingIntent
import android.app.RemoteAction
import android.content.Intent
import android.graphics.drawable.Icon
fun updatePiPControls(isPlaying: Boolean) {
val actionIntent = Intent("ACTION_MEDIA_CONTROL").apply {
putExtra("CONTROL_TYPE", if (isPlaying) "PAUSE" else "PLAY")
}
val pendingIntent = PendingIntent.getBroadcast(
this, 0, actionIntent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
val icon = Icon.createWithResource(this, if (isPlaying) R.drawable.ic_pause else R.drawable.ic_play)
val title = if (isPlaying) "Pause" else "Play"
val action = RemoteAction(icon, title, title, pendingIntent)
val params = PictureInPictureParams.Builder()
.setActions(listOf(action))
.build()
setPictureInPictureParams(params)
}Register a BroadcastReceiver in your activity to listen for "ACTION_MEDIA_CONTROL" and trigger play or pause on your player.
Performance and Resource Management
Because PiP mode runs alongside other apps, keep its resource footprint minimal to prevent the system from terminating your activity.
- Verify System Support: Query the package manager before executing PiP code:
val supportsPiP = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)- Minimize Layout Complexity: Use a flat layout hierarchy to prevent rendering lag and frame drops during window resizing animations.
- Handle Audio Focus: Pause or duck your video audio if another app takes audio focus, ensuring your app does not clash with phone calls or system alerts.
- Prevent Memory Leaks: Clear static references and release heavy assets (like media decoders) in
onStop()to avoid memory pressure issues.
FAQs
Frequently Asked Questions
Picture-in-Picture mode, introduced in Android 8.0 (API level 26), is a specialized multi-window mode designed for activities that play video or display navigation routes. It allows an app to shrink its UI into a small, floating window that remains pinned to a corner of the screen while the user navigates elsewhere on the device.
