Adding support for PIP actions.

- Introduced generic RemoteAction to represents an action
  that can be made across processes with an icon and text
  description based on a Notification action.
- Modified PinnedStackController to ensure that it notifies
  the listeners from the source of truth, this ensures that
  SysUI is in the right state if killed and re-registers
  itself.

Test: Enable menu & minimize in SystemUI tuner.
Test: android.server.cts.ActivityManagerPinnedStackTests
Test: #testNumPipActions

Change-Id: I5b5d0cf9de3f06b5687337d59cfb91e17355bdb1
Signed-off-by: Winson Chung <winsonc@google.com>
diff --git a/api/current.txt b/api/current.txt
index 67bc116..dfe0340 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3657,6 +3657,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
     method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
@@ -3737,6 +3738,7 @@
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
     method public int getLockTaskModeState();
+    method public static int getMaxNumPictureInPictureActions();
     method public int getMemoryClass();
     method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo);
     method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo);
@@ -5547,6 +5549,22 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
+  public final class RemoteAction implements android.os.Parcelable {
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    method public android.app.RemoteAction clone();
+    method public int describeContents();
+    method public void dump(java.lang.String, java.io.PrintWriter);
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getTitle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
+  }
+
+  public static abstract interface RemoteAction.OnActionListener {
+    method public abstract void onAction(android.app.RemoteAction);
+  }
+
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
     method public int describeContents();
diff --git a/api/system-current.txt b/api/system-current.txt
index 4f6e921..076b0ba 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3776,6 +3776,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
     method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
@@ -3862,6 +3863,7 @@
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
     method public int getLockTaskModeState();
+    method public static int getMaxNumPictureInPictureActions();
     method public int getMemoryClass();
     method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo);
     method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo);
@@ -5714,6 +5716,22 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
+  public final class RemoteAction implements android.os.Parcelable {
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    method public android.app.RemoteAction clone();
+    method public int describeContents();
+    method public void dump(java.lang.String, java.io.PrintWriter);
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getTitle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
+  }
+
+  public static abstract interface RemoteAction.OnActionListener {
+    method public abstract void onAction(android.app.RemoteAction);
+  }
+
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
     method public int describeContents();
diff --git a/api/test-current.txt b/api/test-current.txt
index c8c0790..5290306 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3659,6 +3659,7 @@
     method public void setIntent(android.content.Intent);
     method public final void setMediaController(android.media.session.MediaController);
     method public void setOverlayWithDecorCaptionEnabled(boolean);
+    method public void setPictureInPictureActions(java.util.List<android.app.RemoteAction>);
     method public void setPictureInPictureAspectRatio(float);
     method public final deprecated void setProgress(int);
     method public final deprecated void setProgressBarIndeterminate(boolean);
@@ -3740,6 +3741,7 @@
     method public int getLauncherLargeIconDensity();
     method public int getLauncherLargeIconSize();
     method public int getLockTaskModeState();
+    method public static int getMaxNumPictureInPictureActions();
     method public int getMemoryClass();
     method public void getMemoryInfo(android.app.ActivityManager.MemoryInfo);
     method public static void getMyMemoryState(android.app.ActivityManager.RunningAppProcessInfo);
@@ -5558,6 +5560,22 @@
     field public static final int STYLE_SPINNER = 0; // 0x0
   }
 
+  public final class RemoteAction implements android.os.Parcelable {
+    ctor public RemoteAction(android.graphics.drawable.Icon, java.lang.CharSequence, java.lang.CharSequence, android.app.RemoteAction.OnActionListener);
+    method public android.app.RemoteAction clone();
+    method public int describeContents();
+    method public void dump(java.lang.String, java.io.PrintWriter);
+    method public java.lang.CharSequence getContentDescription();
+    method public android.graphics.drawable.Icon getIcon();
+    method public java.lang.CharSequence getTitle();
+    method public void writeToParcel(android.os.Parcel, int);
+    field public static final android.os.Parcelable.Creator<android.app.RemoteAction> CREATOR;
+  }
+
+  public static abstract interface RemoteAction.OnActionListener {
+    method public abstract void onAction(android.app.RemoteAction);
+  }
+
   public final class RemoteInput implements android.os.Parcelable {
     method public static void addResultsToIntent(android.app.RemoteInput[], android.content.Intent, android.os.Bundle);
     method public int describeContents();
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 2cdda3d..2ccfe0e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -45,6 +45,7 @@
 import android.content.pm.ApplicationInfo;
 import android.content.pm.PackageManager;
 import android.content.pm.PackageManager.NameNotFoundException;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Configuration;
 import android.content.res.Resources;
 import android.content.res.TypedArray;
@@ -2019,7 +2020,29 @@
     }
 
     /**
-     * Updates the aspect ratio of the current picture-in-picture activity.
+     * Requests to the system that the activity can be automatically put into picture-in-picture
+     * mode when the user leaves the activity causing it normally to be hidden.  Generally, this
+     * happens when another task is brought to the forground or the task containing this activity
+     * is moved to the background.  This is a *not* a guarantee that the activity will actually be
+     * put in picture-in-picture mode, and depends on a number of factors, including whether there
+     * is already something in picture-in-picture.
+     *
+     * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter
+     *                                        picture-in-picture
+     */
+    public void enterPictureInPictureModeOnMoveToBackground(
+            boolean enterPictureInPictureOnMoveToBg) {
+        try {
+            ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken,
+                    enterPictureInPictureOnMoveToBg);
+        } catch (RemoteException e) {
+        }
+    }
+
+    /**
+     * Updates the aspect ratio of the current picture-in-picture activity if this activity is
+     * already in picture-in-picture mode, or sets it to be used later if
+     * {@link #enterPictureInPictureModeOnMoveToBackground(boolean)} is requested.
      *
      * @param aspectRatio the new aspect ratio of the picture-in-picture.
      */
@@ -2031,23 +2054,19 @@
     }
 
     /**
-     * Requests to the system that the activity can be automatically put into picture-in-picture
-     * mode when the user leaves the activity causing it normally to be hidden.  This is a *not*
-     * a guarantee that the activity will actually be put in picture-in-picture mode, and depends
-     * on a number of factors, including whether there is already something in picture-in-picture.
+     * Updates the set of user actions associated with the picture-in-picture activity.
      *
-     * If {@param enterPictureInPictureOnMoveToBg} is true, then you may also call
-     * {@link #setPictureInPictureAspectRatio(float)} to specify the aspect ratio to automatically
-     * enter picture-in-picture with.
-     *
-     * @param enterPictureInPictureOnMoveToBg whether or not this activity can automatically enter
-     *                                     picture-in-picture
+     * @param actions the new actions for picture-in-picture (can be null to reset the set of
+     *                actions).  The maximum number of actions that will be displayed on this device
+     *                is defined by {@link ActivityManager#getMaxNumPictureInPictureActions()}.
      */
-    public void enterPictureInPictureModeOnMoveToBackground(
-            boolean enterPictureInPictureOnMoveToBg) {
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
         try {
-            ActivityManagerNative.getDefault().enterPictureInPictureModeOnMoveToBackground(mToken,
-                    enterPictureInPictureOnMoveToBg);
+            if (actions == null) {
+                actions = new ArrayList<>();
+            }
+            ActivityManagerNative.getDefault().setPictureInPictureActions(mToken,
+                    new ParceledListSlice<RemoteAction>(actions));
         } catch (RemoteException e) {
         }
     }
diff --git a/core/java/android/app/ActivityManager.java b/core/java/android/app/ActivityManager.java
index f04455b..761da35 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -87,8 +87,9 @@
 
     private static int gMaxRecentTasks = -1;
 
+    private static final int NUM_ALLOWED_PIP_ACTIONS = 3;
+
     private final Context mContext;
-    private final Handler mHandler;
 
     private static volatile boolean sSystemReady = false;
 
@@ -491,7 +492,6 @@
 
     /*package*/ ActivityManager(Context context, Handler handler) {
         mContext = context;
-        mHandler = handler;
     }
 
     /**
@@ -1012,6 +1012,14 @@
     }
 
     /**
+     * Return the maximum number of actions that will be displayed in the picture-in-picture UI when
+     * the user interacts with the activity currently in picture-in-picture mode.
+     */
+    public static int getMaxNumPictureInPictureActions() {
+        return NUM_ALLOWED_PIP_ACTIONS;
+    }
+
+    /**
      * Information you can set and retrieve about the current activity within the recent task list.
      */
     public static class TaskDescription implements Parcelable {
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 913edfd8..87700dc 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -28,6 +28,8 @@
 
 import com.android.internal.app.IVoiceInteractor;
 
+import java.io.PrintWriter;
+import java.util.ArrayList;
 import java.util.List;
 
 /**
@@ -62,6 +64,36 @@
     public static final int APP_TRANSITION_TIMEOUT = 3;
 
     /**
+     * Class to hold deferred properties to apply for picture-in-picture for a given activity.
+     */
+    public static class PictureInPictureArguments {
+        /**
+         * The expected aspect ratio of the picture-in-picture.
+         */
+        public float aspectRatio;
+
+        /**
+         * The set of actions that are associated with this activity when in picture in picture.
+         */
+        public List<RemoteAction> userActions = new ArrayList<>();
+
+        public void dump(PrintWriter pw, String prefix) {
+            pw.println(prefix + "aspectRatio=" + aspectRatio);
+            if (userActions.isEmpty()) {
+                pw.println(prefix + "  userActions=[]");
+            } else {
+                pw.println(prefix + "  userActions=[");
+                for (int i = 0; i < userActions.size(); i++) {
+                    RemoteAction action = userActions.get(i);
+                    pw.print(prefix + "    Action[" + i + "]: ");
+                    action.dump("", pw);
+                }
+                pw.println(prefix + "  ]");
+            }
+        }
+    }
+
+    /**
      * Grant Uri permissions from one app to another. This method only extends
      * permission grants if {@code callingUid} has permission to them.
      */
diff --git a/core/java/android/app/IActivityManager.aidl b/core/java/android/app/IActivityManager.aidl
index a10fffe..fcc6e3d 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -474,6 +474,7 @@
     void enterPictureInPictureModeOnMoveToBackground(in IBinder token,
             boolean enterPictureInPictureOnMoveToBg);
     void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
+    void setPictureInPictureActions(in IBinder token, in ParceledListSlice actions);
     void activityRelaunched(in IBinder token);
     IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
     /**
diff --git a/core/java/android/app/RemoteAction.aidl b/core/java/android/app/RemoteAction.aidl
new file mode 100644
index 0000000..2f9f1cc
--- /dev/null
+++ b/core/java/android/app/RemoteAction.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2016, The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *     http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+parcelable RemoteAction;
diff --git a/core/java/android/app/RemoteAction.java b/core/java/android/app/RemoteAction.java
new file mode 100644
index 0000000..a37680f
--- /dev/null
+++ b/core/java/android/app/RemoteAction.java
@@ -0,0 +1,159 @@
+/*
+ * Copyright (C) 2016 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+import android.annotation.NonNull;
+import android.graphics.drawable.Icon;
+import android.os.Handler;
+import android.os.Message;
+import android.os.Messenger;
+import android.os.Parcel;
+import android.os.Parcelable;
+import android.os.RemoteException;
+import android.text.TextUtils;
+import android.util.Log;
+
+import java.io.PrintWriter;
+
+/**
+ * Represents a remote action that can be called from another process.  The action can have an
+ * associated visualization including metadata like an icon or title.
+ */
+public final class RemoteAction implements Parcelable {
+
+    /**
+     * Interface definition for a callback to be invoked when an action is invoked.
+     */
+    public interface OnActionListener {
+        /**
+         * Called when the associated action is invoked.
+         *
+         * @param action The action that was invoked.
+         */
+        void onAction(RemoteAction action);
+    }
+
+    private static final String TAG = "RemoteAction";
+
+    private static final int MESSAGE_ACTION_INVOKED = 1;
+
+    private final Icon mIcon;
+    private final CharSequence mTitle;
+    private final CharSequence mContentDescription;
+    private OnActionListener mActionCallback;
+    private final Messenger mMessenger;
+
+    RemoteAction(Parcel in) {
+        mIcon = Icon.CREATOR.createFromParcel(in);
+        mTitle = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mContentDescription = TextUtils.CHAR_SEQUENCE_CREATOR.createFromParcel(in);
+        mMessenger = in.readParcelable(Messenger.class.getClassLoader());
+    }
+
+    public RemoteAction(@NonNull Icon icon, @NonNull CharSequence title,
+            @NonNull CharSequence contentDescription, @NonNull OnActionListener callback) {
+        if (icon == null || title == null || contentDescription == null || callback == null) {
+            throw new IllegalArgumentException("Expected icon, title, content description and " +
+                    "action callback");
+        }
+        mIcon = icon;
+        mTitle = title;
+        mContentDescription = contentDescription;
+        mActionCallback = callback;
+        mMessenger = new Messenger(new Handler() {
+            @Override
+            public void handleMessage(Message msg) {
+                switch (msg.what) {
+                    case MESSAGE_ACTION_INVOKED:
+                        mActionCallback.onAction(RemoteAction.this);
+                        break;
+                }
+            }
+        });
+    }
+
+    /**
+     * Return an icon representing the action.
+     */
+    public @NonNull Icon getIcon() {
+        return mIcon;
+    }
+
+    /**
+     * Return an title representing the action.
+     */
+    public @NonNull CharSequence getTitle() {
+        return mTitle;
+    }
+
+    /**
+     * Return a content description representing the action.
+     */
+    public @NonNull CharSequence getContentDescription() {
+        return mContentDescription;
+    }
+
+    /**
+     * Sends a message that the action was invoked.
+     * @hide
+     */
+    public void sendActionInvoked() {
+        Message m = Message.obtain();
+        m.what = MESSAGE_ACTION_INVOKED;
+        try {
+            mMessenger.send(m);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Could not send action-invoked", e);
+        }
+    }
+
+    @Override
+    public RemoteAction clone() {
+        return new RemoteAction(mIcon, mTitle, mContentDescription, mActionCallback);
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @Override
+    public void writeToParcel(Parcel out, int flags) {
+        mIcon.writeToParcel(out, 0);
+        TextUtils.writeToParcel(mTitle, out, flags);
+        TextUtils.writeToParcel(mContentDescription, out, flags);
+        out.writeParcelable(mMessenger, flags);
+    }
+
+    public void dump(String prefix, PrintWriter pw) {
+        pw.print(prefix);
+        pw.print("title=" + mTitle);
+        pw.print(" contentDescription=" + mContentDescription);
+        pw.print(" icon=" + mIcon);
+        pw.println();
+    }
+
+    public static final Parcelable.Creator<RemoteAction> CREATOR =
+            new Parcelable.Creator<RemoteAction>() {
+                public RemoteAction createFromParcel(Parcel in) {
+                    return new RemoteAction(in);
+                }
+                public RemoteAction[] newArray(int size) {
+                    return new RemoteAction[size];
+                }
+            };
+}
\ No newline at end of file
diff --git a/core/java/android/view/IPinnedStackListener.aidl b/core/java/android/view/IPinnedStackListener.aidl
index 3050dbb..3c348c5 100644
--- a/core/java/android/view/IPinnedStackListener.aidl
+++ b/core/java/android/view/IPinnedStackListener.aidl
@@ -16,13 +16,14 @@
 
 package android.view;
 
+import android.content.pm.ParceledListSlice;
 import android.view.IPinnedStackController;
 
 /**
-  * Listener for changes to the pinned stack made by the WindowManager.
-  *
-  * @hide
-  */
+ * Listener for changes to the pinned stack made by the WindowManager.
+ *
+ * @hide
+ */
 oneway interface IPinnedStackListener {
 
     /**
@@ -36,4 +37,24 @@
      * is first registered to allow the listener to synchronized its state with the controller.
      */
     void onBoundsChanged(boolean adjustedForIme);
+
+    /**
+     * Called when window manager decides to adjust the minimized state, or when the listener
+     * is first registered to allow the listener to synchronized its state with the controller.
+     */
+    void onMinimizedStateChanged(boolean isMinimized);
+
+    /**
+     * Called when window manager decides to adjust the snap-to-edge state, which determines whether
+     * to snap only to the corners of the screen or to the closest edge.  It is called when the
+     * listener is first registered to allow the listener to synchronized its state with the
+     * controller.
+     */
+    void onSnapToEdgeStateChanged(boolean isSnapToEdge);
+
+    /**
+     * Called when the set of actions for the current PiP activity changes, or when the listener
+     * is first registered to allow the listener to synchronized its state with the controller.
+     */
+    void onActionsChanged(in ParceledListSlice actions);
 }
diff --git a/packages/SystemUI/res/layout/pip_menu_action.xml b/packages/SystemUI/res/layout/pip_menu_action.xml
new file mode 100644
index 0000000..db6ae19a
--- /dev/null
+++ b/packages/SystemUI/res/layout/pip_menu_action.xml
@@ -0,0 +1,44 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2016 The Android Open Source Project
+
+     Licensed under the Apache License, Version 2.0 (the "License");
+     you may not use this file except in compliance with the License.
+     You may obtain a copy of the License at
+
+          http://www.apache.org/licenses/LICENSE-2.0
+
+     Unless required by applicable law or agreed to in writing, software
+     distributed under the License is distributed on an "AS IS" BASIS,
+     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+     See the License for the specific language governing permissions and
+     limitations under the License.
+-->
+<FrameLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:layout_width="0dp"
+    android:layout_height="match_parent"
+    android:layout_weight="1"
+    android:background="?android:selectableItemBackground">
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center_vertical"
+        android:orientation="vertical">
+        <ImageView
+            android:id="@+id/icon"
+            android:layout_width="@dimen/pip_menu_action_icon_size"
+            android:layout_height="@dimen/pip_menu_action_icon_size"
+            android:layout_gravity="center_horizontal"
+            android:layout_marginBottom="4dp" />
+        <TextView
+            android:id="@+id/title"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:includeFontPadding="false"
+            android:gravity="center"
+            android:textSize="12sp"
+            android:textColor="#ffffffff"
+            android:textAllCaps="true"
+            android:fontFamily="sans-serif" />
+    </LinearLayout>
+</FrameLayout>
\ No newline at end of file
diff --git a/packages/SystemUI/res/layout/pip_menu_activity.xml b/packages/SystemUI/res/layout/pip_menu_activity.xml
index 88e6e72..054bfab 100644
--- a/packages/SystemUI/res/layout/pip_menu_activity.xml
+++ b/packages/SystemUI/res/layout/pip_menu_activity.xml
@@ -1,5 +1,5 @@
 <?xml version="1.0" encoding="utf-8"?>
-<!-- Copyright (C) 2014 The Android Open Source Project
+<!-- Copyright (C) 2016 The Android Open Source Project
 
      Licensed under the Apache License, Version 2.0 (the "License");
      you may not use this file except in compliance with the License.
@@ -16,21 +16,44 @@
 
 <FrameLayout
     xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/menu"
     android:layout_width="match_parent"
     android:layout_height="match_parent"
     android:background="#33000000">
+
     <LinearLayout
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_gravity="center">
-        <Button
-            android:id="@+id/expand_pip"
-            android:layout_width="wrap_content"
-            android:layout_height="wrap_content"
-            android:layout_gravity="center"
-            android:textSize="14sp"
+        android:id="@+id/actions"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:layout_marginBottom="48dp">
+    </LinearLayout>
+
+    <LinearLayout
+        android:layout_width="match_parent"
+        android:layout_height="48dp"
+        android:layout_gravity="bottom"
+        android:orientation="horizontal">
+        <TextView
+            android:id="@+id/dismiss"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="12sp"
             android:textColor="#ffffffff"
-            android:text="@string/pip_phone_expand"
-            android:fontFamily="sans-serif" />
+            android:fontFamily="sans-serif"
+            android:text="@string/pip_phone_dismiss"
+            android:background="?android:selectableItemBackground" />
+        <TextView
+            android:id="@+id/minimize"
+            android:layout_width="0dp"
+            android:layout_height="match_parent"
+            android:layout_weight="1"
+            android:gravity="center"
+            android:textSize="12sp"
+            android:textColor="#ffffffff"
+            android:fontFamily="sans-serif"
+            android:text="@string/pip_phone_minimize"
+            android:background="?android:selectableItemBackground" />
     </LinearLayout>
 </FrameLayout>
diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml
index a7d4aa0..fe5606e 100644
--- a/packages/SystemUI/res/values/dimens.xml
+++ b/packages/SystemUI/res/values/dimens.xml
@@ -717,6 +717,9 @@
     <!-- The size of the PIP dismiss target. -->
     <dimen name="pip_dismiss_target_size">48dp</dimen>
 
+    <!-- The size of a PIP menu action icon. -->
+    <dimen name="pip_menu_action_icon_size">32dp</dimen>
+
     <!-- Values specific to grid-based recents. -->
     <!-- Margins around recent tasks. -->
     <dimen name="recents_grid_margin_left">15dp</dimen>
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index c8b3b69d..6af2fc6 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1688,9 +1688,15 @@
         not appear on production builds ever. -->
     <string name="tuner_doze_always_on" translatable="false">Always on</string>
 
-    <!-- Making the PIP fullscreen -->
+    <!-- Making the PIP fullscreen [CHAR LIMIT=25] -->
     <string name="pip_phone_expand">Expand</string>
 
+    <!-- Label for PIP action to Minimize the PIP [CHAR LIMIT=25] -->
+    <string name="pip_phone_minimize">Minimize</string>
+
+    <!-- Label for PIP action to Dismiss the PIP -->
+    <string name="pip_phone_dismiss">Dismiss</string>
+
     <!-- PIP section of the tuner. Non-translatable since it should
         not appear on production builds ever. -->
     <string name="picture_in_picture" translatable="false">Picture-in-Picture</string>
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
index 43cfa32..7e275d8 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java
@@ -16,9 +16,17 @@
 
 package com.android.systemui.pip.phone;
 
+import static android.view.Display.DEFAULT_DISPLAY;
+
 import android.app.ActivityManager;
 import android.app.IActivityManager;
 import android.content.Context;
+import android.content.pm.ParceledListSlice;
+import android.os.Handler;
+import android.os.RemoteException;
+import android.util.Log;
+import android.view.IPinnedStackController;
+import android.view.IPinnedStackListener;
 import android.view.IWindowManager;
 import android.view.WindowManagerGlobal;
 
@@ -33,10 +41,52 @@
     private Context mContext;
     private IActivityManager mActivityManager;
     private IWindowManager mWindowManager;
+    private Handler mHandler = new Handler();
+
+    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
 
     private PipMenuActivityController mMenuController;
     private PipTouchHandler mTouchHandler;
 
+    /**
+     * Handler for messages from the PIP controller.
+     */
+    private class PinnedStackListener extends IPinnedStackListener.Stub {
+
+        @Override
+        public void onListenerRegistered(IPinnedStackController controller) {
+            mHandler.post(() -> {
+                mTouchHandler.setPinnedStackController(controller);
+            });
+        }
+
+        @Override
+        public void onBoundsChanged(boolean adjustedForIme) {
+            // Do nothing
+        }
+
+        @Override
+        public void onActionsChanged(ParceledListSlice actions) {
+            mHandler.post(() -> {
+                mMenuController.setActions(actions);
+            });
+        }
+
+        @Override
+        public void onMinimizedStateChanged(boolean isMinimized) {
+            mHandler.post(() -> {
+                mTouchHandler.onMinimizedStateChanged(isMinimized);
+            });
+        }
+
+        @Override
+        public void onSnapToEdgeStateChanged(boolean isSnapToEdge) {
+            mHandler.post(() -> {
+                mTouchHandler.onSnapToEdgeStateChanged(isSnapToEdge);
+            });
+        }
+    }
+
     private PipManager() {}
 
     /**
@@ -47,6 +97,12 @@
         mActivityManager = ActivityManager.getService();
         mWindowManager = WindowManagerGlobal.getWindowManagerService();
 
+        try {
+            mWindowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
+        } catch (RemoteException e) {
+            Log.e(TAG, "Failed to register pinned stack listener", e);
+        }
+
         mMenuController = new PipMenuActivityController(context, mActivityManager, mWindowManager);
         mTouchHandler = new PipTouchHandler(context, mMenuController, mActivityManager,
                 mWindowManager);
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
index bfe5cff..fe8ee6f 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java
@@ -19,16 +19,27 @@
 import android.annotation.Nullable;
 import android.app.Activity;
 import android.app.ActivityManager;
+import android.app.PendingIntent.CanceledException;
+import android.app.RemoteAction;
 import android.content.Intent;
+import android.content.pm.ParceledListSlice;
 import android.os.Bundle;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
 import android.os.RemoteException;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
+import android.view.ViewGroup;
+import android.widget.ImageView;
+import android.widget.TextView;
+
 import com.android.systemui.R;
 
+import java.util.ArrayList;
+import java.util.List;
+
 /**
  * Translucent activity that gets started on top of a task in PIP to allow the user to control it.
  */
@@ -36,16 +47,25 @@
 
     private static final String TAG = "PipMenuActivity";
 
-    public static final int MESSAGE_FINISH_SELF = 2;
+    public static final int MESSAGE_FINISH_SELF = 1;
+    public static final int MESSAGE_UPDATE_ACTIONS = 2;
 
     private static final long INITIAL_DISMISS_DELAY = 2000;
     private static final long POST_INTERACTION_DISMISS_DELAY = 1500;
 
+    private List<RemoteAction> mActions = new ArrayList<>();
+    private View mDismissButton;
+    private View mMinimizeButton;
+
+    private Handler mHandler = new Handler();
     private Messenger mToControllerMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
         @Override
         public void handleMessage(Message msg) {
             switch (msg.what) {
+                case MESSAGE_UPDATE_ACTIONS:
+                    setActions(((ParceledListSlice) msg.obj).getList());
+                    break;
                 case MESSAGE_FINISH_SELF:
                     finish();
                     break;
@@ -63,15 +83,27 @@
     @Override
     protected void onCreate(@Nullable Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
-
-        Intent startingIntet = getIntent();
-        mToControllerMessenger = startingIntet.getParcelableExtra(
-                PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER);
-
         setContentView(R.layout.pip_menu_activity);
-        findViewById(R.id.expand_pip).setOnClickListener((view) -> {
-            finish();
-            notifyExpandPip();
+
+        Intent startingIntent = getIntent();
+        mToControllerMessenger = startingIntent.getParcelableExtra(
+                PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER);
+        ParceledListSlice actions = startingIntent.getParcelableExtra(
+                PipMenuActivityController.EXTRA_ACTIONS);
+        if (actions != null) {
+            setActions(actions.getList());
+        }
+
+        findViewById(R.id.menu).setOnClickListener((v) -> {
+            expandPip();
+        });
+        mDismissButton = findViewById(R.id.dismiss);
+        mDismissButton.setOnClickListener((v) -> {
+            dismissPip();
+        });
+        mMinimizeButton = findViewById(R.id.minimize);
+        mMinimizeButton.setOnClickListener((v) -> {
+            minimizePip();
         });
     }
 
@@ -107,25 +139,74 @@
         // Do nothing
     }
 
+    private void setActions(List<RemoteAction> actions) {
+        mActions.clear();
+        mActions.addAll(actions);
+        updateActionViews();
+    }
+
+    private void updateActionViews() {
+        ViewGroup actionsContainer = (ViewGroup) findViewById(R.id.actions);
+        if (actionsContainer != null) {
+            actionsContainer.removeAllViews();
+
+            // Recreate the layout
+            final LayoutInflater inflater = LayoutInflater.from(this);
+            for (int i = 0; i < mActions.size(); i++) {
+                final RemoteAction action = mActions.get(i);
+                final ViewGroup actionContainer = (ViewGroup) inflater.inflate(
+                        R.layout.pip_menu_action, actionsContainer, false);
+                actionContainer.setOnClickListener((v) -> {
+                    action.sendActionInvoked();
+                });
+
+                final TextView title = (TextView) actionContainer.findViewById(R.id.title);
+                title.setText(action.getTitle());
+                title.setContentDescription(action.getContentDescription());
+
+                final ImageView icon = (ImageView) actionContainer.findViewById(R.id.icon);
+                action.getIcon().loadDrawableAsync(this, (d) -> {
+                    icon.setImageDrawable(d);
+                }, mHandler);
+                actionsContainer.addView(actionContainer);
+            }
+        }
+    }
+
     private void notifyActivityVisibility(boolean visible) {
         Message m = Message.obtain();
         m.what = PipMenuActivityController.MESSAGE_ACTIVITY_VISIBILITY_CHANGED;
         m.arg1 = visible ? 1 : 0;
         m.replyTo = visible ? mMessenger : null;
-        try {
-            mToControllerMessenger.send(m);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Could not notify controller of PIP menu visibility", e);
-        }
+        sendMessage(m, "Could not notify controller of PIP menu visibility");
     }
 
-    private void notifyExpandPip() {
+    private void expandPip() {
+        sendEmptyMessage(PipMenuActivityController.MESSAGE_EXPAND_PIP,
+                "Could not notify controller to expand PIP");
+    }
+
+    private void minimizePip() {
+        sendEmptyMessage(PipMenuActivityController.MESSAGE_MINIMIZE_PIP,
+                "Could not notify controller to minimize PIP");
+    }
+
+    private void dismissPip() {
+        sendEmptyMessage(PipMenuActivityController.MESSAGE_DISMISS_PIP,
+                "Could not notify controller to dismiss PIP");
+    }
+
+    private void sendEmptyMessage(int what, String errorMsg) {
         Message m = Message.obtain();
-        m.what = PipMenuActivityController.MESSAGE_EXPAND_PIP;
+        m.what = what;
+        sendMessage(m, errorMsg);
+    }
+
+    private void sendMessage(Message m, String errorMsg) {
         try {
             mToControllerMessenger.send(m);
         } catch (RemoteException e) {
-            Log.e(TAG, "Could not notify controller to expand PIP", e);
+            Log.e(TAG, errorMsg, e);
         }
     }
 
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
index d1bce0c..64e2d1a 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java
@@ -1,14 +1,13 @@
 package com.android.systemui.pip.phone;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
 import android.app.ActivityManager.StackInfo;
 import android.app.ActivityOptions;
 import android.app.IActivityManager;
 import android.content.Context;
 import android.content.Intent;
-import android.graphics.Rect;
+import android.content.pm.ParceledListSlice;
 import android.os.Handler;
 import android.os.Message;
 import android.os.Messenger;
@@ -24,8 +23,12 @@
     private static final String TAG = "PipMenuActivityController";
 
     public static final String EXTRA_CONTROLLER_MESSENGER = "messenger";
-    public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 1;
-    public static final int MESSAGE_EXPAND_PIP = 3;
+    public static final String EXTRA_ACTIONS = "actions";
+
+    public static final int MESSAGE_ACTIVITY_VISIBILITY_CHANGED = 100;
+    public static final int MESSAGE_EXPAND_PIP = 101;
+    public static final int MESSAGE_MINIMIZE_PIP = 102;
+    public static final int MESSAGE_DISMISS_PIP = 103;
 
     /**
      * A listener interface to receive notification on changes in PIP.
@@ -35,12 +38,29 @@
          * Called when the PIP menu visibility changes.
          */
         void onPipMenuVisibilityChanged(boolean visible);
+
+        /**
+         * Called when the PIP requested to be expanded.
+         */
+        void onPipExpand();
+
+        /**
+         * Called when the PIP requested to be minimized.
+         */
+        void onPipMinimize();
+
+        /**
+         * Called when the PIP requested to be expanded.
+         */
+        void onPipDismiss();
     }
 
     private Context mContext;
     private IActivityManager mActivityManager;
     private IWindowManager mWindowManager;
+
     private ArrayList<Listener> mListeners = new ArrayList<>();
+    private ParceledListSlice mActions;
 
     private Messenger mToActivityMessenger;
     private Messenger mMessenger = new Messenger(new Handler() {
@@ -57,10 +77,23 @@
                     break;
                 }
                 case MESSAGE_EXPAND_PIP: {
-                    try {
-                        mActivityManager.resizeStack(PINNED_STACK_ID, null, true, true, true, 225);
-                    } catch (RemoteException e) {
-                        Log.e(TAG, "Error showing PIP menu activity", e);
+                    int listenerCount = mListeners.size();
+                    for (int i = 0; i < listenerCount; i++) {
+                        mListeners.get(i).onPipExpand();
+                    }
+                    break;
+                }
+                case MESSAGE_MINIMIZE_PIP: {
+                    int listenerCount = mListeners.size();
+                    for (int i = 0; i < listenerCount; i++) {
+                        mListeners.get(i).onPipMinimize();
+                    }
+                    break;
+                }
+                case MESSAGE_DISMISS_PIP: {
+                    int listenerCount = mListeners.size();
+                    for (int i = 0; i < listenerCount; i++) {
+                        mListeners.get(i).onPipDismiss();
                     }
                     break;
                 }
@@ -95,6 +128,7 @@
                     pinnedStackInfo.taskIds.length > 0) {
                 Intent intent = new Intent(mContext, PipMenuActivity.class);
                 intent.putExtra(EXTRA_CONTROLLER_MESSENGER, mMessenger);
+                intent.putExtra(EXTRA_ACTIONS, mActions);
                 ActivityOptions options = ActivityOptions.makeBasic();
                 options.setLaunchTaskId(
                         pinnedStackInfo.taskIds[pinnedStackInfo.taskIds.length - 1]);
@@ -123,4 +157,22 @@
             mToActivityMessenger = null;
         }
     }
+
+    /**
+     * Sets the {@param actions} associated with the PiP.
+     */
+    public void setActions(ParceledListSlice actions) {
+        mActions = actions;
+
+        if (mToActivityMessenger != null) {
+            Message m = Message.obtain();
+            m.what = PipMenuActivity.MESSAGE_UPDATE_ACTIONS;
+            m.obj = actions;
+            try {
+                mToActivityMessenger.send(m);
+            } catch (RemoteException e) {
+                Log.e(TAG, "Could not notify menu activity to update actions", e);
+            }
+        }
+    }
 }
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
index 09671e7..ff3cc79 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java
@@ -17,7 +17,6 @@
 package com.android.systemui.pip.phone;
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
-import static android.view.Display.DEFAULT_DISPLAY;
 import static android.view.WindowManager.INPUT_CONSUMER_PIP;
 
 import static com.android.systemui.Interpolators.FAST_OUT_LINEAR_IN;
@@ -38,7 +37,6 @@
 import android.os.RemoteException;
 import android.util.Log;
 import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
 import android.view.IWindowManager;
 import android.view.InputChannel;
 import android.view.InputEvent;
@@ -80,7 +78,6 @@
     private final IActivityManager mActivityManager;
     private final IWindowManager mWindowManager;
     private final ViewConfiguration mViewConfig;
-    private final PinnedStackListener mPinnedStackListener = new PinnedStackListener();
     private final PipMenuListener mMenuListener = new PipMenuListener();
     private IPinnedStackController mPinnedStackController;
 
@@ -149,26 +146,6 @@
     }
 
     /**
-     * Handler for messages from the PIP controller.
-     */
-    private class PinnedStackListener extends IPinnedStackListener.Stub {
-
-        @Override
-        public void onListenerRegistered(IPinnedStackController controller) {
-            mPinnedStackController = controller;
-
-            // Update the controller with the current tuner state
-            setMinimizedState(mIsMinimized);
-            setSnapToEdge(mEnableSnapToEdge);
-        }
-
-        @Override
-        public void onBoundsChanged(boolean adjustedForIme) {
-            // Do nothing
-        }
-    }
-
-    /**
      * A listener for the PIP menu activity.
      */
     private class PipMenuListener implements PipMenuActivityController.Listener {
@@ -181,17 +158,30 @@
                 unregisterInputConsumer();
             }
         }
+
+        @Override
+        public void onPipExpand() {
+            if (!mIsMinimized) {
+                expandPinnedStackToFullscreen();
+            }
+        }
+
+        @Override
+        public void onPipMinimize() {
+            setMinimizedState(true);
+            animateToClosestMinimizedTarget();
+        }
+
+        @Override
+        public void onPipDismiss() {
+            animateDismissPinnedStack(mPinnedStackBounds);
+        }
     }
 
     public PipTouchHandler(Context context, PipMenuActivityController menuController,
             IActivityManager activityManager, IWindowManager windowManager) {
 
         // Initialize the Pip input consumer
-        try {
-            windowManager.registerPinnedStackListener(DEFAULT_DISPLAY, mPinnedStackListener);
-        } catch (RemoteException e) {
-            Log.e(TAG, "Failed to create PIP input consumer", e);
-        }
         mContext = context;
         mActivityManager = activityManager;
         mWindowManager = windowManager;
@@ -255,6 +245,14 @@
         updateBoundedPinnedStackBounds(false /* updatePinnedStackBounds */);
     }
 
+    public void onMinimizedStateChanged(boolean isMinimized) {
+        mIsMinimized = isMinimized;
+    }
+
+    public void onSnapToEdgeStateChanged(boolean isSnapToEdge) {
+        mSnapAlgorithm.setSnapToEdge(isSnapToEdge);
+    }
+
     private boolean handleTouchEvent(MotionEvent ev) {
         // Skip touch handling until we are bound to the controller
         if (mPinnedStackController == null) {
@@ -353,10 +351,17 @@
     }
 
     /**
-     * Sets the snap-to-edge state.
+     * Sets the controller to update the system of changes from user interaction.
+     */
+    void setPinnedStackController(IPinnedStackController controller) {
+        mPinnedStackController = controller;
+    }
+
+    /**
+     * Sets the snap-to-edge state and notifies the controller.
      */
     private void setSnapToEdge(boolean snapToEdge) {
-        mSnapAlgorithm.setSnapToEdge(snapToEdge);
+        onSnapToEdgeStateChanged(snapToEdge);
 
         if (mPinnedStackController != null) {
             try {
@@ -371,7 +376,7 @@
      * Sets the minimized state and notifies the controller.
      */
     private void setMinimizedState(boolean isMinimized) {
-        mIsMinimized = isMinimized;
+        onMinimizedStateChanged(isMinimized);
 
         if (mPinnedStackController != null) {
             try {
@@ -432,6 +437,12 @@
         mPinnedStackBoundsAnimator = mMotionHelper.createAnimationToBounds(mPinnedStackBounds,
                 toBounds, MINIMIZE_STACK_MAX_DURATION, LINEAR_OUT_SLOW_IN,
                 mUpdatePinnedStackBoundsListener);
+        mPinnedStackBoundsAnimator.addListener(new AnimatorListenerAdapter() {
+            @Override
+            public void onAnimationEnd(Animator animation) {
+                mMenuController.hideMenu();
+            }
+        });
         mPinnedStackBoundsAnimator.start();
     }
 
@@ -759,7 +770,8 @@
 
         @Override
         public boolean onUp(PipTouchState touchState) {
-            if (mEnableTapThrough && !touchState.isDragging() && !mIsTappingThrough) {
+            if (mEnableTapThrough && !touchState.isDragging() && !mIsMinimized &&
+                    !mIsTappingThrough) {
                 mMenuController.showMenu();
                 mIsTappingThrough = true;
                 return true;
diff --git a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
index 17d9864..2e84ced 100644
--- a/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
+++ b/packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java
@@ -16,11 +16,7 @@
 
 package com.android.systemui.pip.phone;
 
-import android.app.IActivityManager;
 import android.graphics.PointF;
-import android.view.IPinnedStackController;
-import android.view.IPinnedStackListener;
-import android.view.IWindowManager;
 import android.view.MotionEvent;
 import android.view.VelocityTracker;
 import android.view.ViewConfiguration;
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 4ea73e7..1b68b24 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -17,9 +17,11 @@
 package com.android.server.am;
 
 import android.annotation.Nullable;
+import android.app.ActivityManagerInternal.PictureInPictureArguments;
 import android.app.ApplicationThreadConstants;
 import android.app.ContentProviderHolder;
 import android.app.IActivityManager;
+import android.app.RemoteAction;
 import android.app.WaitResult;
 import android.os.IDeviceIdentifiersPolicyService;
 
@@ -7514,6 +7516,23 @@
         enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */);
     }
 
+    @Override
+    public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
+            boolean enterPictureInPictureOnMoveToBg) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+                        "enterPictureInPictureModeOnMoveToBackground", token, -1f /* aspectRatio */,
+                        false /* checkAspectRatio */, false /* checkActivityVisibility */);
+
+                r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
     private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio,
             boolean checkAspectRatio) {
         final long origId = Binder.clearCallingIdentity();
@@ -7523,7 +7542,8 @@
                         "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
                         true /* checkActivityVisibility */);
 
-                enterPictureInPictureModeLocked(r, displayId, aspectRatio,
+                r.pictureInPictureArgs.aspectRatio = aspectRatio;
+                enterPictureInPictureModeLocked(r, displayId, r.pictureInPictureArgs,
                         true /* moveHomeStackToFront */, "enterPictureInPictureMode");
             }
         } finally {
@@ -7531,29 +7551,13 @@
         }
     }
 
-    void enterPictureInPictureModeLocked(ActivityRecord r, int displayId, float aspectRatio,
-            boolean moveHomeStackToFront, String reason) {
-        final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
-                ? mWindowManager.getPictureInPictureBounds(displayId, aspectRatio)
+    void enterPictureInPictureModeLocked(ActivityRecord r, int displayId,
+            PictureInPictureArguments pipArgs, boolean moveHomeStackToFront, String reason) {
+        final Rect bounds = isValidPictureInPictureAspectRatio(pipArgs.aspectRatio)
+                ? mWindowManager.getPictureInPictureBounds(displayId, pipArgs.aspectRatio)
                 : mWindowManager.getPictureInPictureDefaultBounds(displayId);
         mStackSupervisor.moveActivityToPinnedStackLocked(r, reason, bounds, moveHomeStackToFront);
-    }
-
-    @Override
-    public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
-            boolean enterPictureInPictureOnMoveToBg) {
-        final long origId = Binder.clearCallingIdentity();
-        try {
-            synchronized(this) {
-                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
-                        "requestAutoEnterPictureInPicture", token, -1f /* aspectRatio */,
-                        false /* checkAspectRatio */, false /* checkActivityVisibility */);
-
-                r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
-            }
-        } finally {
-            Binder.restoreCallingIdentity(origId);
-        }
+        mWindowManager.setPictureInPictureActions(pipArgs.userActions);
     }
 
     @Override
@@ -7565,11 +7569,39 @@
                         "setPictureInPictureAspectRatio", token, aspectRatio,
                         true /* checkAspectRatio */, false /* checkActivityVisibility */);
 
+                r.pictureInPictureArgs.aspectRatio = aspectRatio;
                 if (r.getStack().getStackId() == PINNED_STACK_ID) {
                     // If the activity is already in picture-in-picture, update the pinned stack now
                     mWindowManager.setPictureInPictureAspectRatio(aspectRatio);
                 }
-                r.pictureInPictureAspectRatio = aspectRatio;
+            }
+        } finally {
+            Binder.restoreCallingIdentity(origId);
+        }
+    }
+
+    @Override
+    public void setPictureInPictureActions(IBinder token, ParceledListSlice actionsList) {
+        final long origId = Binder.clearCallingIdentity();
+        try {
+            synchronized(this) {
+                final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
+                        "setPictureInPictureActions", token, -1 /* aspectRatio */,
+                        false /* checkAspectRatio */, false /* checkActivityVisibility */);
+
+                final List<RemoteAction> actions = actionsList.getList();
+                if (actions.size() > ActivityManager.getMaxNumPictureInPictureActions()) {
+                    throw new IllegalArgumentException("setPictureInPictureActions: Invalid number"
+                            + " of picture-in-picture actions.  Only a maximum of "
+                            + ActivityManager.getMaxNumPictureInPictureActions()
+                            + " actions allowed");
+                }
+
+                r.pictureInPictureArgs.userActions = actions;
+                if (r.getStack().getStackId() == PINNED_STACK_ID) {
+                    // If the activity is already in picture-in-picture, update the pinned stack now
+                    mWindowManager.setPictureInPictureActions(actions);
+                }
             }
         } finally {
             Binder.restoreCallingIdentity(origId);
@@ -7605,7 +7637,7 @@
 
         if (!r.canEnterPictureInPicture(checkActivityVisibility)) {
             throw new IllegalArgumentException(caller
-                    + "Current activity does not support picture-in-picture or is not "
+                    + ": Current activity does not support picture-in-picture or is not "
                     + "visible r=" + r);
         }
 
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index 7dd2e84..ef19700 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -54,6 +54,7 @@
 
 import android.annotation.NonNull;
 import android.app.ActivityManager.TaskDescription;
+import android.app.ActivityManagerInternal.PictureInPictureArguments;
 import android.app.ActivityOptions;
 import android.app.PendingIntent;
 import android.app.ResultInfo;
@@ -217,13 +218,13 @@
     boolean immersive;      // immersive mode (don't interrupt if possible)
     boolean forceNewConfig; // force re-create with new config next time
     boolean supportsPipOnMoveToBackground;   // Supports automatically entering picture-in-picture
-            // when this activity is hidden. This flag is requested by the activity.
+        // when this activity is hidden. This flag is requested by the activity.
     private boolean enterPipOnMoveToBackground; // Flag to enter picture in picture when this
-            // activity is made invisible. This flag is set specifically when another task is being
-            // launched or moved to the front which may cause this activity to try and enter PiP
-            // when it is next made invisible.
-    float pictureInPictureAspectRatio; // The aspect ratio to use when auto-entering
-            // picture-in-picture
+        // activity is made invisible. This flag is set specifically when another task is being
+        // launched or moved to the front which may cause this activity to try and enter PiP
+        // when it is next made invisible.
+    PictureInPictureArguments pictureInPictureArgs = new PictureInPictureArguments();  // The PiP
+        // arguments used when deferring the entering of picture-in-picture.
     int launchCount;        // count of launches since last state
     long lastLaunchTime;    // time of last launch of this activity
     ComponentName requestedVrComponent; // the requested component for handling VR mode.
@@ -441,9 +442,10 @@
             pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
         }
         if (supportsPipOnMoveToBackground) {
-            pw.println(prefix + "supportsPipOnMoveToBackground=1 "
-                    + "enterPipOnMoveToBackground="
-                            + (enterPipOnMoveToBackground ? 1 : 0));
+            pw.println(prefix + "supportsPipOnMoveToBackground=1");
+            pw.println(prefix + "enterPipOnMoveToBackground=" +
+                    (enterPipOnMoveToBackground ? 1 : 0));
+            pictureInPictureArgs.dump(pw, prefix);
         }
     }
 
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 671c84e..5bdae57 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1895,7 +1895,7 @@
             // since it will affect the focused stack's visibility and occlude
             // starting activities
             mService.enterPictureInPictureModeLocked(r, r.getDisplayId(),
-                    r.pictureInPictureAspectRatio, false /* moveHomeStackToFront */,
+                    r.pictureInPictureArgs, false /* moveHomeStackToFront */,
                     "ensureActivitiesVisibleLocked");
             return true;
         }
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index 0ad4e0a..34633c2 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -18,12 +18,13 @@
 
 import static android.app.ActivityManager.StackId.PINNED_STACK_ID;
 import static android.util.TypedValue.COMPLEX_UNIT_DIP;
-import static android.view.Display.DEFAULT_DISPLAY;
 
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WITH_CLASS_NAME;
 import static com.android.server.wm.WindowManagerDebugConfig.TAG_WM;
 
 import android.animation.ValueAnimator;
+import android.app.RemoteAction;
+import android.content.pm.ParceledListSlice;
 import android.content.res.Resources;
 import android.graphics.Point;
 import android.graphics.PointF;
@@ -41,12 +42,13 @@
 import android.view.IPinnedStackController;
 import android.view.IPinnedStackListener;
 
-import com.android.internal.os.BackgroundThread;
 import com.android.internal.policy.PipMotionHelper;
 import com.android.internal.policy.PipSnapAlgorithm;
 import com.android.server.UiThread;
 
 import java.io.PrintWriter;
+import java.util.ArrayList;
+import java.util.List;
 
 /**
  * Holds the common state of the pinned stack between the system and SystemUI.
@@ -70,10 +72,14 @@
     // States that affect how the PIP can be manipulated
     private boolean mInInteractiveMode;
     private boolean mIsMinimized;
+    private boolean mIsSnappingToEdge;
     private boolean mIsImeShowing;
     private int mImeHeight;
     private ValueAnimator mBoundsAnimator = null;
 
+    // The set of actions that are currently allowed on the PiP activity
+    private ArrayList<RemoteAction> mActions = new ArrayList<>();
+
     // Used to calculate stack bounds across rotations
     private final DisplayInfo mDisplayInfo = new DisplayInfo();
 
@@ -113,6 +119,7 @@
         @Override
         public void setSnapToEdge(final boolean snapToEdge) {
             mHandler.post(() -> {
+                mIsSnappingToEdge = snapToEdge;
                 mSnapAlgorithm.setSnapToEdge(snapToEdge);
             });
         }
@@ -171,6 +178,9 @@
             listener.onListenerRegistered(mCallbacks);
             mPinnedStackListener = listener;
             notifyBoundsChanged(mIsImeShowing);
+            notifyMinimizeChanged(mIsMinimized);
+            notifySnapToEdgeChanged(mIsSnappingToEdge);
+            notifyActionsChanged(mActions);
         } catch (RemoteException e) {
             Log.e(TAG, "Failed to register pinned stack listener", e);
         }
@@ -315,7 +325,16 @@
     }
 
     /**
-     * Sends a broadcast that the PIP movement bounds have changed.
+     * Sets the current set of actions.
+     */
+    void setActions(List<RemoteAction> actions) {
+        mActions.clear();
+        mActions.addAll(actions);
+        notifyActionsChanged(mActions);
+    }
+
+    /**
+     * Notifies listeners that the PIP movement bounds have changed.
      */
     private void notifyBoundsChanged(boolean adjustedForIme) {
         if (mPinnedStackListener != null) {
@@ -328,6 +347,45 @@
     }
 
     /**
+     * Notifies listeners that the PIP minimized state has changed.
+     */
+    private void notifyMinimizeChanged(boolean isMinimized) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onMinimizedStateChanged(isMinimized);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering minimize changed event.", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP snap-to-edge state has changed.
+     */
+    private void notifySnapToEdgeChanged(boolean isSnappingToEdge) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onSnapToEdgeStateChanged(isSnappingToEdge);
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering snap-to-edge changed event.", e);
+            }
+        }
+    }
+
+    /**
+     * Notifies listeners that the PIP actions have changed.
+     */
+    private void notifyActionsChanged(List<RemoteAction> actions) {
+        if (mPinnedStackListener != null) {
+            try {
+                mPinnedStackListener.onActionsChanged(new ParceledListSlice(actions));
+            } catch (RemoteException e) {
+                Slog.e(TAG_WM, "Error delivering actions changed event.", e);
+            }
+        }
+    }
+
+    /**
      * @return the bounds on the screen that the PIP can be visible in.
      */
     private void getInsetBounds(Rect outRect) {
@@ -355,5 +413,16 @@
         pw.println(prefix + "  mIsImeShowing=" + mIsImeShowing);
         pw.println(prefix + "  mInInteractiveMode=" + mInInteractiveMode);
         pw.println(prefix + "  mIsMinimized=" + mIsMinimized);
+        if (mActions.isEmpty()) {
+            pw.println(prefix + "  mActions=[]");
+        } else {
+            pw.println(prefix + "  mActions=[");
+            for (int i = 0; i < mActions.size(); i++) {
+                RemoteAction action = mActions.get(i);
+                pw.print(prefix + "    Action[" + i + "]: ");
+                action.dump("", pw);
+            }
+            pw.println(prefix + "  ]");
+        }
     }
 }
diff --git a/services/core/java/com/android/server/wm/WindowManagerService.java b/services/core/java/com/android/server/wm/WindowManagerService.java
index 158fd2b..577e5a0 100644
--- a/services/core/java/com/android/server/wm/WindowManagerService.java
+++ b/services/core/java/com/android/server/wm/WindowManagerService.java
@@ -26,6 +26,7 @@
 import android.app.ActivityManagerInternal;
 import android.app.AppOpsManager;
 import android.app.IActivityManager;
+import android.app.RemoteAction;
 import android.app.admin.DevicePolicyManager;
 import android.content.BroadcastReceiver;
 import android.content.ContentResolver;
@@ -3405,22 +3406,6 @@
         }
     }
 
-    public void setPictureInPictureAspectRatio(float aspectRatio) {
-        synchronized (mWindowMap) {
-            if (!mSupportsPictureInPicture) {
-                return;
-            }
-
-            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
-            if (stack == null) {
-                return;
-            }
-
-            animateResizePinnedStack(getPictureInPictureBounds(
-                    stack.getDisplayContent().getDisplayId(), aspectRatio), -1);
-        }
-    }
-
     public Rect getPictureInPictureBounds(int displayId, float aspectRatio) {
         synchronized (mWindowMap) {
             if (!mSupportsPictureInPicture) {
@@ -3447,6 +3432,36 @@
     }
 
     /**
+     * Sets the current picture-in-picture aspect ratio.
+     */
+    public void setPictureInPictureAspectRatio(float aspectRatio) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            if (!mSupportsPictureInPicture || stack == null) {
+                return;
+            }
+
+            final int displayId = stack.getDisplayContent().getDisplayId();
+            final Rect toBounds = getPictureInPictureBounds(displayId, aspectRatio);
+            animateResizePinnedStack(toBounds, -1 /* duration */);
+        }
+    }
+
+    /**
+     * Sets the current picture-in-picture actions.
+     */
+    public void setPictureInPictureActions(List<RemoteAction> actions) {
+        synchronized (mWindowMap) {
+            final TaskStack stack = mStackIdToStack.get(PINNED_STACK_ID);
+            if (!mSupportsPictureInPicture || stack == null) {
+                return;
+            }
+
+            stack.getDisplayContent().getPinnedStackController().setActions(actions);
+        }
+    }
+
+    /**
      * Place a TaskStack on a DisplayContent. Will create a new TaskStack if none is found with
      * specified stackId.
      * @param stackId The unique identifier of the new stack.