Android Picture-in-Picture Mode

Introduction

Intended primarily for video playback, PiP mode allows an activity screen to be reduced in size and positioned at any location on the screen. While in this state, the activity continues to run and the window remains visible regardless of any other activities running on the device. This allows the user to, for example, continue watching video playback while performing tasks such as checking email or working on a spreadsheet.

When activity placed into PiP mode, configuration options may be specified that control the aspect ratio of the PiP window and also to define the area of the activity screen that is to be included in the window. Following screenshot, for example, shows a image activity in PiP mode:

android_pip_mode.png

Following screenshot shows a PiP mode window after it has been tapped by the user. When in this mode, the window appears larger and includes a full screen action in the center which, when tapped, restores the window to full screen mode and an exit button in the top right-hand corner to close the window and place the app in the background. Any custom actions added to the PiP window will also appear on the screen when it is displayed in this mode.

android_pip_mode_tapped.png

PiP mode is currently only supported on devices running Android 8.0 (API 26) or newer.

To modify the SDK setting, locate the Gradle Scripts –> build.gradle (Module: app) file and increase the minSdkVersion setting from 14 to 26:

android {
    compileSdkVersion 26
    buildToolsVersion "26.0.0"
    defaultConfig {
        ...
        minSdkVersion 26
    }
}

The second step in implementing PiP mode is to enable it within the project’s manifest file.

PiP mode is configured on a per activity basis by adding the following lines to each activity element for which PiP support is required:

<activity android:name=".MainActivity"
    android:supportsPictureInPicture="true"
    android:configChanges=
        "screenSize|smallestScreenSize|screenLayout|orientation">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>

The android:supportsPictureInPicture entry enables PiP for the activity while the android:configChanges property notifies Android that the activity is able to handle layout configuration changes. Without this setting, each time the activity moves in and out of PiP mode the activity will be restarted resulting in playback restarting from the beginning of the video during the transition.

PiP behavior is defined through the use of the PictureInPictureParams class, instances of which can be created using the Builder class as follows:

PictureInPictureParams params = new PictureInPictureParams.Builder().build();

The above code creates a default PictureInPictureParams instance with special parameters defined. The following optional method calls may also be used to customize the parameters:

  • setActions() – Used to define actions that can be performed from within the PiP window while the activity is in PiP mode.
  • setAspectRatio() – Declares the preferred aspect ratio for appearance of the PiP window. This method takes as an argument a Rational object containing the height width / height ratio.
  • setSourceRectHint() – Takes as an argument a Rect object defining the area of the activity screen to be displayed within the PiP window.

The following code, for example, configures aspect ratio and action parameters within a PictureInPictureParams object. In the case of the aspect ratio, this is defined using the width and height dimensions of a VideoView instance:

Rational rational = new Rational(videoView.getWidth(),
    videoView.getHeight());
PictureInPictureParams params = new PictureInPictureParams.Builder()
    .setAspectRatio(rational)
    .setActions(actions)
    .build();

Once defined, PiP parameters may be set at any time using the setPictureInPictureParams

setPictureInPictureParams(params);

Parameters may also be specified when entering PiP mode.

An activity is placed into Picture-in-Picture mode via a call to the enterPictureInPictureMode() method, passing through a PictureInPictureParams object:

enterPictureInPictureMode(params);

If no parameters are required, simply create a default PictureInPictureParams object as outlined above. If parameters have previously been set using the setPictureInPictureParams() method, these parameters are combined with those specified during the enterPictureInPictureMode() method call.

When an activity enters PiP mode, it is important to hide any unnecessary views so that only the video playback is visible within the PiP window. When the activity re-enters full screen mode, any hidden user interface components need to be re-instated. These and any other app specific tasks can be performed by overriding the onPictureInPictureModeChanged() method. When added to the activity, this method is called each time the activity transitions between PiP and full screen modes and is passed a Boolean value indicating whether the activity is currently in PiP mode:

@Override
public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
    super.onPictureInPictureModeChanged(isInPictureInPictureMode);
    if (isInPictureInPictureMode) {
        // acitivity entered Picture-in-Picture mode
    } else {
        // activity entered full screen mode
    }
}

Adding Picture-in-Picture Actions

Picture-in-Picture actions appear as icons within the PiP window when it is tapped by the user. Implementation of PiP actions is a multi-step process that begins with implementing a way for the PiP window to notify the activity that an action has been selected. This is achieved by setting up a broadcast receiver within the activity, and then creating a pending intent within the PiP action which, in turn, is configured to broadcast an intent for which the broadcast receiver is listening. When the broadcast receiver is triggered by the intent, the data stored in the intent can be used to identify the action performed and to take the necessary action within the activity.

PiP actions are declared using the RemoteAction instances which are initialized with an icon, a title, a description and the PendingIntent object. Once one or more actions have been created, they are added to an ArrayList and passed through to the setActions() method while building a PictureInPictureParams object.

The following code fragment demonstrates the creation of the Intent, PendingIntent and RemoteAction objects together with a PictureInPictureParams instance which is then applied to the activity’s PiP settings:

ArrayList<RemoteAction> actions = new ArrayList<>();
Intent actionIntent = new Intent("MY_PIP_ACTION");
PendingIntent pendingIntent = PendingIntent.getBroadcast(activity,
    REQUEST_CODE, actionIntent, 0);
Icon icon = Icon.createWithResource(activity, android.R.drawable.ic_dialog_info);

RemoteAction remoteAction = new RemoteAction(icon,
    "My Action Title",
    "My Action Description",
    pendingIntent);

actions.add(remoteAction);

PictureInPictureParams params = new PictureInPictureParams.Builder()
    .setActions(actions)
    .build();
setPictureInPictureParams(params);

Example

Following is the layout

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical">

    <ImageView
        android:id="@+id/iv"
        android:layout_width="match_parent"
        android:layout_height="200dp"
        android:layout_alignParentTop="true"
        android:src="@drawable/pic"
        android:scaleType="centerCrop"/>

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:visibility="gone"
        android:text="PiP mode"
        android:textSize="30sp"
        android:textColor="@android:color/white"
        android:layout_centerHorizontal="true"
        android:layout_alignParentTop="true"
        android:layout_marginTop="10dp" />

    <Button
        android:id="@+id/btn"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="PiP"
        android:layout_centerHorizontal="true"
        android:layout_below="@id/iv"
        android:onClick="gotoPip"/>

</RelativeLayout>

Following is the MainActivity

public class MainActivity extends AppCompatActivity {
    public static final String BROADCAST_ACTION = "me.proft.PIP_ACTION";
    private Button btn;
    private ImageView iv;
    private TextView tv;
    private BroadcastReceiver receiver;
    private static final int REQUEST_CODE = 101;
    private ArrayList<RemoteAction> actions = new ArrayList<>();
    private Activity activity = MainActivity.this;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        btn = findViewById(R.id.btn);
        iv = findViewById(R.id.iv);
        tv = findViewById(R.id.tv);
    }

    public void gotoPip(View v) {
        btn.setVisibility(View.INVISIBLE);
        tv.setVisibility(View.VISIBLE);

        Rational rational = new Rational(iv.getWidth(), iv.getHeight());
        PictureInPictureParams params = new PictureInPictureParams.Builder()
            .setAspectRatio(rational)
            .build();
        enterPictureInPictureMode(params);
    }

    private void createPipAction() {
        Intent actionIntent = new Intent(BROADCAST_ACTION);
        final PendingIntent pendingIntent = PendingIntent.getBroadcast(activity, REQUEST_CODE, actionIntent, 0);

        final Icon icon = Icon.createWithResource(activity, android.R.drawable.ic_dialog_info);
        RemoteAction remoteAction = new RemoteAction(icon, "Info", "Some info", pendingIntent);
        actions.add(remoteAction);

        PictureInPictureParams params =
                new PictureInPictureParams.Builder()
                        .setActions(actions)
                        .build();
        setPictureInPictureParams(params);
    }

    @Override
    public void onPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
        super.onPictureInPictureModeChanged(isInPictureInPictureMode);
        if (isInPictureInPictureMode) {
            // action
            IntentFilter filter = new IntentFilter();
            filter.addAction(BROADCAST_ACTION);
            receiver = new BroadcastReceiver() {
                @Override
                public void onReceive(Context context, Intent intent) {
                    Toast.makeText(context, "Some action", Toast.LENGTH_LONG).show();
                }
            };
            registerReceiver(receiver, filter);

            createPipAction();
        } else {
            btn.setVisibility(View.VISIBLE);
            tv.setVisibility(View.GONE);

            if (receiver != null) {
                unregisterReceiver(receiver);
            }
        }
    }
}

Result

android_pip_mode.png

So, Picture-in-Picture mode is a multitasking feature introduced with Android 8.0 designed specifically to allow video playback to continue in a small window while the user performs tasks in other apps and activities. Before PiP mode can be used, it must first be enabled within the manifest file for those activities that require PiP support.