Implementing Android's Picture-in-Picture (PiP) Mode
Mobility

Implementing Android's Picture-in-Picture (PiP) Mode

Published May 16, 2019Updated June 3, 20264 min read

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 CategoryOptimal CandidateSub-optimal CandidateKey Reason
MediaVideo Playback, Live StreamingAudio Podcast, Text ArticlesVisual continuity is key; audio doesn't need screen space.
CommunicationVideo Calls, Webinar ViewsChat Lists, Message ComposeVideo calls need persistent feeds; typing requires a keyboard.
UtilitiesGPS NavigationSpreadsheets, Task ManagersGPS 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:

html
<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:

text
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:

text
fun updatePiPParams() {
    val pipParams = PictureInPictureParams.Builder()
        .setAspectRatio(Rational(16, 9))
        .setAutoEnterEnabled(true)
        .build()
        
    setPictureInPictureParams(pipParams)
}

For backward compatibility, override the onUserLeaveHint() method:

text
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:

text
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:

text
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:

text
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.

  1. Verify System Support: Query the package manager before executing PiP code:
text
   val supportsPiP = packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
  1. Minimize Layout Complexity: Use a flat layout hierarchy to prevent rendering lag and frame drops during window resizing animations.
  2. 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.
  3. 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.