
Key takeaways
• Auto-enter is the 2026 default. On Android 12+ you set setAutoEnterEnabled(true) once and the system runs the home-gesture transition; onUserLeaveHint is now the fallback for API 26–30.
• Android 16 can break PiP silently. The predictive-back gesture finishes your activity before it can enter PiP. Set android:enableOnBackInvokedCallback="false" on the PiP activity, and it is fixed.
• Source rect hint kills the black flash. Without it the OS animates a black rectangle; with it the player zooms from its real bounds — the single biggest perceived-quality fix.
• Manifest configChanges is non-negotiable. Miss orientation|screenSize|smallestScreenSize|screenLayout and the OS recreates your activity on every toggle, resetting the video to 0:00.
• The work is small, the payoff is large. A clean Picture-in-Picture integration on a healthy Kotlin codebase is usually 3–5 engineering days, cheap insurance against a feature your competitors already ship.
Why Fora Soft wrote this playbook
We have built calling, streaming and video-collaboration products since 2005 — 250+ projects with a 50-engineer in-house team. Picture-in-Picture is the feature clients ask for the moment one user complains that “the call dies when I open my notes.” It looks trivial in the official sample (one line, enterPictureInPictureMode()), but the production version that survives Samsung gestures, foldable hinges, the home-button race condition and Android 16’s predictive back is a few hundred lines of careful Kotlin.
This guide consolidates what we learned shipping PiP across BrainCert (a WebRTC-first LMS), ProVideoMeeting (an enterprise video-conferencing platform) and Sprii (a live-video shopping platform). Every snippet below is compiled against Android 16 (API 36) and verified from Android 8.0 (API 26) up, cross-checked against Google’s official Picture-in-Picture guide. Skim the key takeaways and jump to auto-enter for the modern pattern, or to source rect hint for the smooth-zoom trick. Building the same feature on iPhone or iPad? Our iOS Picture-in-Picture guide walks through the AVKit and WebRTC paths.
Building an Android video app and want PiP done right?
Book a 30-minute scoping call with our Android lead. We will sanity-check your PiP stack, source-rect handling and lifecycle hooks against your specific player.
Why Picture-in-Picture is now table stakes
Every competitor your users compare you to has PiP. Zoom, Google Meet, WhatsApp, Telegram, YouTube, Netflix, Spotify, Google Maps, Twitch and TikTok all treat the floating window as a default. Shipping a calling product without it in 2026 reads as outdated the moment someone tries to check their calendar mid-meeting and watches the call die.
The business case sits on three pillars. Session length: users stay in your app longer when they can multitask. Conversion to paid: PiP is a recognizable premium signal that lifts upgrade rates on subscription tiers. Churn defense: a one-star review saying “app closes when I switch” hurts store ranking for weeks. The work to ship it is small, usually 3–5 engineering days on a healthy codebase, which makes it one of the highest-return Android features you can ship per dev-day.
The PiP API matrix — what each Android version added
Picture-in-Picture on Android shipped on Android 8.0 (API 26) in 2017 and has been refined on every release since. The timeline below collapses that history into the four inflection points that change your code path; the table underneath is the same story in detail.

Figure 1. Four API levels change how you enter PiP — and Android 16 adds a trap, not a method.
| API / Version | Capability | Why it matters |
|---|---|---|
| API 26 — Android 8.0 (2017) | PiP launched. enterPictureInPictureMode(), setSourceRectHint, setActions. |
Manual transition via onUserLeaveHint. The baseline for every calling app. |
| API 31 — Android 12 (2021) | setAutoEnterEnabled for auto-enter, plus a resize-mode flag for non-video content. |
No more onUserLeaveHint dance; smoother resize for non-video content. |
| API 33 — Android 13 (2022) | setTitle, setSubtitle, setExpandedAspectRatio, setCloseAction. |
Richer PiP UI for foldables and tablets; confirmation dialogs prevent accidental close. |
| API 34–35 — Android 14–15 (2023–24) | No new PiP API surface. Multi-window and foldable polish. | The API is stable; invest now without rework risk. |
| API 36 — Android 16 (2025) | No new PiP methods, but predictive back changes behavior. Guard with enableOnBackInvokedCallback. |
Play Store requires targeting API 36 for new apps and updates from 31 Aug 2026 — the guard is mandatory. |
Picture-in-Picture has shipped on every Android release since 8.0, so in 2026 it covers essentially the entire active device base. The only branch that matters for your code is whether you target API 31+ (auto-enter) or still support API 26–30 (the leave-hint fallback).
Manifest setup that prevents activity restarts
The single most common production bug we catch in PiP code review is a missing configChanges attribute on the call activity. When PiP toggles on or off, Android delivers a configuration change. If your activity does not declare that it handles those changes, the OS recreates it: the video resets to 0:00, the WebRTC peer connection tears down, and the UX is ruined. Add the lines below before you touch anything else.
<uses-feature
android:name="android.software.picture_in_picture"
android:required="false" />
<activity
android:name=".CallActivity"
android:supportsPictureInPicture="true"
android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden"
android:launchMode="singleTask"
android:resizeableActivity="true" />
Reach for android:launchMode="singleTask" when: the call activity must return to the foreground from PiP without being recreated. The default standard launch mode produces duplicate activity instances and breaks lifecycle assumptions.
Detecting device support before you call PiP
Android-Go devices, in-vehicle infotainment systems and a handful of low-end OEMs ship without PiP. Calling enterPictureInPictureMode() on those devices throws or silently no-ops. Always feature-check first.
val isPipSupported: Boolean
get() = Build.VERSION.SDK_INT >= Build.VERSION_CODES.O
&& packageManager.hasSystemFeature(PackageManager.FEATURE_PICTURE_IN_PICTURE)
When isPipSupported is false, fall back gracefully: hide the PiP affordance in the UI, and on the home gesture end the call cleanly with a “return to call” notification rather than letting the activity die.
Auto-enter PiP on Android 12+ — the modern default
Before Android 12 you had to listen for onUserLeaveHint — a callback the OS fires roughly when the user leaves your activity — and call enterPictureInPictureMode() from there. The race conditions were ugly: the gesture fires too late on some devices, the system kills the activity on others, and Compose apps had to wire it through an awkward side effect.
API 31 introduced setAutoEnterEnabled(true). The OS now handles the home-gesture transition for you. The only rule: the parameters must be fresh at the moment the user leaves. Set them once in onResume and never update, and an aspect-ratio change halfway through the call will not be reflected. The diagram below maps both entry paths and where the Android 16 guard sits.

Figure 2. The two code paths into PiP — and the one line that keeps Android 16 from finishing your activity first.
private fun updatePipParams(call: ActiveCall) {
if (!isPipSupported) return
val builder = PictureInPictureParams.Builder()
.setAspectRatio(Rational(call.videoWidth, call.videoHeight))
.setSourceRectHint(playerView.getGlobalRect())
.setActions(listOf(muteAction(call), endCallAction(call)))
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
builder.setAutoEnterEnabled(call.isInProgress)
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
builder
.setTitle(call.peerName)
.setSubtitle(call.elapsedFormatted())
.setCloseAction(closeWithConfirmAction(call))
}
setPictureInPictureParams(builder.build())
}
Call updatePipParams from onResume and from anywhere that mutates the call — aspect-ratio change after a remote camera flip, mute toggle, hold and resume. The OS reads the latest snapshot when the gesture fires.
Reach for setAutoEnterEnabled when: you target API 31+ and want the smoothest transition with the least code. Keep the params fresh on every layout and call-state change, and let the OS decide the exact moment to enter.
The Android 16 predictive-back trap
Here is the catch that surprises teams migrating in 2026. Android 16 enables predictive back by default for apps targeting API 36, and the system treats a back gesture as “finish this activity.” The result: instead of entering PiP, your activity is finished and the user is dropped to the previous screen — auto-enter never gets its chance. The fix is one manifest attribute on the PiP activity, confirmed in Google’s Android 16 behavior changes.
<activity
android:name=".CallActivity"
android:supportsPictureInPicture="true"
android:enableOnBackInvokedCallback="false" /> <!-- opt this activity out of predictive back -->
Setting enableOnBackInvokedCallback="false" on just the PiP activity restores classic back behavior there, so your activity decides whether to enter PiP instead of being finished outright. Keep predictive back enabled everywhere else in the app.
Legacy fallback for API 26–30
override fun onUserLeaveHint() {
super.onUserLeaveHint()
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.S
&& isPipSupported
&& callManager.isInCall
) {
enterPictureInPictureMode(buildPipParams(callManager.activeCall))
}
}
Lifecycle hooks: hiding the right UI in PiP
A 320 × 180 PiP window cannot fit your full call UI. Hide everything except the remote video. Action buttons live in the system PiP overlay; chat threads, mute toggles, participant lists and any sensitive surfaces should disappear entirely. onPictureInPictureModeChanged is your hook.

Figure 3. In PiP, everything but the remote stream goes isGone = true; the three actions render in the system overlay.
override fun onPictureInPictureModeChanged(
isInPictureInPictureMode: Boolean,
newConfig: Configuration
) {
super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig)
binding.controlsScrim.isGone = isInPictureInPictureMode
binding.chatPanel.isGone = isInPictureInPictureMode
binding.localPreview.isGone = isInPictureInPictureMode
binding.bottomSheet.isGone = isInPictureInPictureMode
binding.toolbar.isGone = isInPictureInPictureMode
// Make the player fill the window
binding.remoteVideo.layoutParams = binding.remoteVideo.layoutParams.apply {
width = ViewGroup.LayoutParams.MATCH_PARENT
height = ViewGroup.LayoutParams.MATCH_PARENT
}
}
In Compose, expose the same state through a snapshot — PiP works the same way because Compose hosts inside a regular Activity:
@Composable
fun rememberIsInPip(): Boolean {
val activity = LocalContext.current as Activity
var isInPip by remember { mutableStateOf(activity.isInPictureInPictureMode) }
DisposableEffect(activity) {
val listener = Consumer<PictureInPictureModeChangedInfo> { info ->
isInPip = info.isInPictureInPictureMode
}
activity.addOnPictureInPictureModeChangedListener(listener)
onDispose { activity.removeOnPictureInPictureModeChangedListener(listener) }
}
return isInPip
}
Source rect hint — the smooth-zoom secret
Without a source rect hint, the OS animates a black rectangle from the centre of the screen into the PiP corner. With it, the player smoothly zooms from its real position. The fix is mechanical: hand the OS the bounds of the video view at the moment of the transition.

Figure 4. Same transition, two experiences: a black-block teleport without the hint, a real zoom with it.
private fun View.getGlobalRect(): Rect {
val rect = Rect()
getGlobalVisibleRect(rect)
return rect
}
playerView.addOnLayoutChangeListener { _, _, _, _, _, _, _, _, _ ->
updatePipParams(callManager.activeCall)
}
Refresh the rect on every layout change — orientation flips, soft-keyboard appearance and split-screen handoff all move the player’s real bounds. When the source rect is missing you also lose the reverse animation on exit; the user gets a teleport instead of a zoom.
PiP entering with a black flash on your app?
We have shipped this exact integration on WebRTC, ExoPlayer and LiveKit-based stacks. A 30-minute call usually narrows the diagnosis to one of four root causes.
Action buttons (RemoteAction): mute, hang up, switch camera
PiP buttons are RemoteAction objects. The system renders icons in its own colour palette — never assume your tint survives. Cap your action list at three; getMaxNumPictureInPictureActions() returns the device-specific maximum, but three works everywhere.
private fun endCallAction(call: ActiveCall): RemoteAction {
val intent = Intent(this, CallControlsReceiver::class.java)
.setAction(CallControlsReceiver.ACTION_END_CALL)
.putExtra(CallControlsReceiver.EXTRA_CALL_ID, call.id)
val pendingIntent = PendingIntent.getBroadcast(
this, REQ_END_CALL, intent,
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE
)
return RemoteAction(
Icon.createWithResource(this, R.drawable.ic_call_end),
getString(R.string.end_call),
getString(R.string.end_call_a11y),
pendingIntent
)
}
Route the action through a BroadcastReceiver rather than re-launching the activity — the activity is already up, and starting it again confuses the lifecycle. Two more details: every PendingIntent needs FLAG_IMMUTABLE on API 31+, and unique request codes per action stop the OS from collapsing your intents into one.
Aspect ratio, expanded ratio, and foldables
The system clamps PiP windows between roughly 2.39:1 and 1:2.39 — anything more extreme is clipped. For a video call use Rational(16, 9) when the remote video is horizontal, Rational(9, 16) when it is portrait, and update on every camera flip.
Android 13 added setExpandedAspectRatio for foldables and tablets. When the user unfolds a Pixel Fold or rotates a Galaxy Tab sideways, your PiP can expand to a wider ratio without a reset. Wire both:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
builder
.setAspectRatio(Rational(9, 16)) // default portrait
.setExpandedAspectRatio(Rational(16, 9)) // expanded when user pulls
}
Reach for setExpandedAspectRatio when: foldables and tablets are a non-trivial slice of your install base. On a phone-only audience it is a day-two nicety, not a launch blocker.
Title, subtitle and the Android 13 close action
Three Android 13 additions take a stock PiP from functional to polished. setTitle and setSubtitle populate the system PiP UI when the user expands the window, and setCloseAction intercepts the system close affordance to surface a confirmation step.
Reach for setCloseAction when: a stray swipe to dismiss the PiP window would drop a live call, end a paid live stream, or hang up on a doctor mid-consultation. A confirmation step preserves session integrity without adding friction.
Title and subtitle render on Android Auto and on the lock-screen PiP overlay too — populating them is free metadata polish for a stock system surface.
PiP for media playback (ExoPlayer / Media3)
If your product is a streaming app rather than a calling app, the integration looks slightly different. ExoPlayer — now Media3, stable at 1.10 since March 2026 — gives you the player and, through a MediaSession, the default play / pause / seek controls in the PiP window. You still trigger PiP entry yourself with setAutoEnterEnabled / setPictureInPictureParams; without a MediaSession, no system controls render. (New to the video stack? Our digital-video foundations primer covers the terms.)
val player = ExoPlayer.Builder(this).build()
val mediaSession = MediaSession.Builder(this, player).build()
playerView.player = player
playerView.useController = true
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
setPictureInPictureParams(
PictureInPictureParams.Builder()
.setAutoEnterEnabled(true)
.setSeamlessResizeEnabled(true) // direct scaling for video (the default)
.setSourceRectHint(playerView.getGlobalRect())
.setAspectRatio(Rational(player.videoSize.width, player.videoSize.height))
.build()
)
}
The flag above defaults to direct scaling, which is the right choice for any moving video, call or playback: the surface just scales. Pass false to get the cross-fade instead, and reserve that for non-video PiP content such as a map or a dashboard, where a stretched UI looks worse than a fade.
Six pitfalls we catch in code review
1. Forgotten configChanges. The activity restarts on every PiP toggle. Symptom: video resets to 0:00, WebRTC peer connection rebuilt. Fix in the manifest, single line.
2. Stale params with auto-enter. Setting setAutoEnterEnabled(true) once in onCreate and never updating means the OS uses the initial aspect ratio for the whole call. Refresh on every layout change and call-state change.
3. Android 16 predictive back finishing the activity. New in 2026: without enableOnBackInvokedCallback="false" on the PiP activity, the back gesture finishes it before PiP can fire. Symptom: PiP that worked on Android 15 silently stops on 16.
4. PiP from a background activity. enterPictureInPictureMode() from a background activity returns false silently. Always call from a resumed foreground activity, ideally through the auto-enter path.
5. RemoteAction PendingIntent without FLAG_IMMUTABLE. Crashes the app on API 31+ the first time a button renders. Audit every PendingIntent.get* call.
6. Sensitive UI leaking into PiP. Chat threads, password fields and payment forms must be hidden in onPictureInPictureModeChanged. We have seen apps expose user data in PiP recordings — treat the PiP window as a public surface.
OEM quirks: Samsung, Xiaomi, foldables
Samsung One UI uses an edge-swipe gesture instead of the stock home gesture on certain models. The auto-enter path still fires because the OS sends the same lifecycle events, but Samsung’s “reduce app screen size” option intercepts before PiP. Test on a Galaxy S device with stock gestures disabled.
Xiaomi HyperOS (and older MIUI) sometimes blocks PiP entirely under aggressive battery profiles. Mitigate by surfacing a one-time onboarding tip that deep-links to autostart settings, similar to how we ship our custom call notification stack.
Foldables (Pixel Fold, Galaxy Z Fold, Honor Magic V) trigger an onConfigurationChanged on hinge-state change. With the manifest configChanges declared correctly the activity stays alive and you can update PiP params on the fly.
Mini case: PiP rollout that lifted call duration 18%
A European video-conferencing client on our roster, similar in profile to ProVideoMeeting, came to us with a flat call-duration metric. Average call length was 9 minutes; competitor benchmarks were closer to 13. The feedback was consistent: people answered but ended quickly because they could not multitask without dropping.
Our two-week plan: full PiP support with auto-enter on Android 12+, source rect hint for smooth zoom, three action buttons (mute, switch camera, end call), Android 13 title and subtitle, and Compose lifecycle wiring. We tested across 12 devices including a Galaxy S24, a Pixel 8 Pro, a Xiaomi 14 and a Pixel Fold.
Outcome over the next 30 days: average call duration moved from 9 to 10.6 minutes (+18%), home-gesture-during-call abandonment dropped from 14% to 3%, and the Play Store rating climbed from 4.2 to 4.5 with “multitasking” appearing positively in a fifth of new reviews. Engineering effort was four Android-engineer days across two calendar weeks; our agent-assisted engineering let us reuse most of the test scaffolding from earlier projects. Want a similar assessment? Grab a 30-minute call.
Want PiP shipped without the OEM surprises?
Our team has a device lab and the OEM scars to match. We will scope your integration — auto-enter, Android 16 guard, Samsung and Xiaomi survival — and hand back a punch list.
KPIs: what to measure once PiP ships
Quality KPIs. PiP-enter success rate over 99% on supported devices; black-flash rate (frames where the PiP window renders solid black for >100 ms) under 5%; activity-restart-on-PiP rate at 0%.
Business KPIs. Average call-duration delta (target +10% within 30 days), home-gesture abandonment rate during an active call (target under 5%), and Play Store rating with “multitasking” sentiment.
Reliability KPIs. PiP-related crash-free sessions over 99.5%; IllegalStateException on enter under 0.1%; correct aspect ratio in 100% of camera-flip scenarios.
A decision framework in five questions
The single biggest fork is calling versus playback: it flips the control source, aspect-ratio source, action set and close behavior. The matrix below is the whole answer to question one; the four questions after it scope the rest.

Figure 5. One API surface, two tuning profiles — match the column to what your app actually plays.
Q1. Is the primary use case live video (calling) or playback (streaming)? Calling → custom RemoteAction buttons (mute, switch, end) and a dynamic aspect ratio. Playback → a MediaSession for the default play / pause / seek controls, aspect ratio from the player.
Q2. Do you target API 31+? Yes → auto-enter is the default. No → onUserLeaveHint with an explicit feature check.
Q3. How many actions do users actually need? Two or three. Anything more is invisible on most devices and clutters the system overlay.
Q4. Are foldables a non-trivial portion of your install base? Yes → ship setExpandedAspectRatio on day one. No → defer to a later release.
Q5. Is an accidental call drop a paid-product problem? Yes → ship setCloseAction with confirmation. No → the default close behavior is fine.
When NOT to ship PiP at all
Three cases where PiP is the wrong investment. First, if the product has no time-sensitive visual content — an audio-only podcast app, a chat app without media — PiP adds nothing. Use the system media notification instead.
Second, if your minSdk is below API 26, or your DAU on sub-API-26 devices is significant, the cost of supporting two paths plus a graceful fallback usually outweighs the benefit. Ship a notification-driven “return to call” instead.
Reach for FLAG_SECURE when: you run end-to-end-encrypted video under a strict HIPAA posture. PiP then renders as a black rectangle in screenshots and recordings — correct behavior, but flag it to your PM so QA does not file it as a bug.
The broader Android calling stack PiP fits into
Picture-in-Picture is one piece of a calling product. Three adjacent surfaces deserve the same care, and if you are scoping the whole thing, our video-conferencing development team owns all four.
Custom call notifications wake the device when the user is not in your app — see our custom Android call notifications guide for the CallStyle and full-screen-intent patterns.
Foreground services keep the call alive when the activity goes to PiP and then to background — see our foreground services and deep links on Android guide.
Audio output switching between speaker, earpiece and Bluetooth headset is the most reported post-launch bug; the audio routing deep dive covers the edge cases.
Screen sharing for products that double as collaboration tools layers cleanly on top of a PiP-aware app — see implementing Android screen sharing on Kotlin + WebRTC.
FAQ
How do I add Picture-in-Picture on Android?
Declare supportsPictureInPicture="true" and a wide configChanges on the activity, feature-check with FEATURE_PICTURE_IN_PICTURE, then on API 31+ call setAutoEnterEnabled(true) with a fresh PictureInPictureParams. On API 26–30 call enterPictureInPictureMode() from onUserLeaveHint.
Why did Picture-in-Picture stop working after I targeted Android 16?
Android 16’s predictive-back gesture finishes the activity before it can enter PiP. Set android:enableOnBackInvokedCallback="false" on the PiP activity in the manifest to restore classic back behavior there. Leave predictive back enabled everywhere else.
What minSdk should a calling app target if it needs PiP?
API 26 (Android 8.0) is the floor for PiP itself. Auto-enter requires API 31; below that you fall back to onUserLeaveHint. For the Play Store, new apps and updates must target API 36 (Android 16) from 31 August 2026.
Why does my activity recreate every time PiP toggles?
The manifest configChanges attribute is missing or incomplete. Add screenSize|smallestScreenSize|screenLayout|orientation|keyboardHidden to the activity declaration so the OS delivers a config change instead of recreating the activity.
Why does the PiP transition flash black?
You did not set setSourceRectHint, or the rect is stale. Refresh it on every layout change of the player view and pass the latest rect into setPictureInPictureParams so the player zooms instead of teleporting.
How many action buttons can a PiP window display?
The system guarantees three. Query getMaxNumPictureInPictureActions() for a device’s maximum, but three renders everywhere, so cap at three for portability.
Does Picture-in-Picture work in Jetpack Compose?
Yes. Compose hosts inside a regular Activity, so the auto-enter pattern, source rect hint and lifecycle hooks all apply. Use addOnPictureInPictureModeChangedListener on the activity to drive Compose state through a remembered flag.
Can I show PiP from a background activity?
Not reliably. enterPictureInPictureMode() from a background activity returns false silently. Always trigger PiP from a resumed activity, ideally via the auto-enter path.
How much does adding PiP to a calling app usually cost?
On a healthy Kotlin codebase, three to five engineering days for the core integration and another two for OEM device-lab QA. Our agent-assisted engineering compresses that timeline. For an estimate against your codebase, a 30-minute scoping call is usually enough.
What to read next
Notifications
Custom Android call notifications in 2026
CallStyle, full-screen intents, typed foreground services — the wake half of the calling stack.
Android services
Foreground services and deep links on Android
The background-service scaffolding that keeps PiP calls alive.
Audio engineering
Audio output switching on Android during a call
Speaker, earpiece, Bluetooth — the routing problem behind most post-launch bug reports.
Collaboration
Implementing Android screen sharing on Kotlin + WebRTC
MediaProjection, foreground service types, and the OEM quirks for collaboration apps.
Architecture
Custom video conferencing architecture in 2026
P2P, SFU, MCU — the scaling trade-offs behind every calling product.
Ready to ship PiP that feels native?
A correct Picture-in-Picture integration in 2026 is auto-enter guarded by feature detection, a fresh source rect hint on every layout change, manifest configChanges wide enough to absorb the transition, three immutable-PendingIntent action buttons, Android 13 metadata for foldable polish, and the one-line Android 16 predictive-back guard. The work is small and the payoff is large: longer sessions, lower abandonment, higher reviews.
If you want a second pair of eyes on your PiP stack, or a team that has shipped this pattern across 250+ projects since 2005, we are a 30-minute call away.
Need PiP that survives every Android device?
Tell us about your stack. We will return a punch list of PiP, lifecycle and OEM survival fixes within a week.


