summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--api/current.txt18
-rw-r--r--api/system-current.txt18
-rw-r--r--api/test-current.txt18
-rw-r--r--core/java/android/app/Activity.java49
-rw-r--r--core/java/android/app/ActivityManager.java12
-rw-r--r--core/java/android/app/ActivityManagerInternal.java32
-rw-r--r--core/java/android/app/IActivityManager.aidl1
-rw-r--r--core/java/android/app/RemoteAction.aidl19
-rw-r--r--core/java/android/app/RemoteAction.java159
-rw-r--r--core/java/android/view/IPinnedStackListener.aidl29
-rw-r--r--packages/SystemUI/res/layout/pip_menu_action.xml44
-rw-r--r--packages/SystemUI/res/layout/pip_menu_activity.xml47
-rw-r--r--packages/SystemUI/res/values/dimens.xml3
-rw-r--r--packages/SystemUI/res/values/strings.xml8
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipManager.java56
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivity.java111
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipMenuActivityController.java68
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchHandler.java76
-rw-r--r--packages/SystemUI/src/com/android/systemui/pip/phone/PipTouchState.java4
-rw-r--r--services/core/java/com/android/server/am/ActivityManagerService.java64
-rw-r--r--services/core/java/com/android/server/am/ActivityRecord.java20
-rw-r--r--services/core/java/com/android/server/am/ActivityStack.java2
-rw-r--r--services/core/java/com/android/server/wm/PinnedStackController.java75
-rw-r--r--services/core/java/com/android/server/wm/WindowManagerService.java47
24 files changed, 842 insertions, 138 deletions
diff --git a/api/current.txt b/api/current.txt
index dddb58b04ea9..e9952629f5e9 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3657,6 +3657,7 @@ package android.app {
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 @@ package android.app {
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 @@ package android.app {
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 cfb20ee93309..6cbe2d0e1d6f 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3776,6 +3776,7 @@ package android.app {
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 @@ package android.app {
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);
@@ -5715,6 +5717,22 @@ package android.app {
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 5e1c8abe47f5..c98f49ca8ffc 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3659,6 +3659,7 @@ package android.app {
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 @@ package android.app {
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 @@ package android.app {
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 2cdda3d06c0e..2ccfe0e3b72d 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -45,6 +45,7 @@ import android.content.pm.ActivityInfo;
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 @@ public class Activity extends ContextThemeWrapper
}
/**
- * 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 @@ public class Activity extends ContextThemeWrapper
}
/**
- * 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.
- *
- * 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.
+ * Updates the set of user actions associated with the picture-in-picture activity.
*
- * @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 f04455b7306d..761da35f3607 100644
--- a/core/java/android/app/ActivityManager.java
+++ b/core/java/android/app/ActivityManager.java
@@ -87,8 +87,9 @@ public class ActivityManager {
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 @@ public class ActivityManager {
/*package*/ ActivityManager(Context context, Handler handler) {
mContext = context;
- mHandler = handler;
}
/**
@@ -1012,6 +1012,14 @@ public class ActivityManager {
}
/**
+ * 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 913edfd81081..87700dc7aa35 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -28,6 +28,8 @@ import android.service.voice.IVoiceInteractionSession;
import com.android.internal.app.IVoiceInteractor;
+import java.io.PrintWriter;
+import java.util.ArrayList;
import java.util.List;
/**
@@ -62,6 +64,36 @@ public abstract class ActivityManagerInternal {
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 a10fffe063cb..fcc6e3d1e913 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -474,6 +474,7 @@ interface IActivityManager {
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 000000000000..2f9f1cce7389
--- /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 000000000000..a37680f7a8f8
--- /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 3050dbba062f..3c348c50369f 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 @@ oneway interface IPinnedStackListener {
* 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 000000000000..db6ae19a9c34
--- /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 88e6e725c976..054bfabb2f84 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: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:text="@string/pip_phone_expand"
- android:fontFamily="sans-serif" />
+ 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 a7d4aa094cdd..fe5606e408f2 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 967a84cdc5ec..31b83cc8d8bb 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -1687,9 +1687,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 43cfa3249a90..7e275d81031d 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 @@ public class PipManager {
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 @@ public class PipManager {
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 bfe5cff90a24..fe8ee6f6b27f 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 @@ package com.android.systemui.pip.phone;
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 @@ public class PipMenuActivity extends Activity {
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 @@ public class PipMenuActivity extends Activity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+ setContentView(R.layout.pip_menu_activity);
- Intent startingIntet = getIntent();
- mToControllerMessenger = startingIntet.getParcelableExtra(
+ Intent startingIntent = getIntent();
+ mToControllerMessenger = startingIntent.getParcelableExtra(
PipMenuActivityController.EXTRA_CONTROLLER_MESSENGER);
+ ParceledListSlice actions = startingIntent.getParcelableExtra(
+ PipMenuActivityController.EXTRA_ACTIONS);
+ if (actions != null) {
+ setActions(actions.getList());
+ }
- setContentView(R.layout.pip_menu_activity);
- findViewById(R.id.expand_pip).setOnClickListener((view) -> {
- finish();
- notifyExpandPip();
+ 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 @@ public class PipMenuActivity extends Activity {
// 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 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 notifyExpandPip() {
+ 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 d1bce0c74ccd..64e2d1a7ff07 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 @@ public class PipMenuActivityController {
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 @@ public class PipMenuActivityController {
* 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 @@ public class PipMenuActivityController {
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 @@ public class PipMenuActivityController {
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 @@ public class PipMenuActivityController {
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 09671e7ac0e6..ff3cc7981bd9 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.Looper;
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 @@ public class PipTouchHandler implements TunerService.Tunable {
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 @@ public class PipTouchHandler implements TunerService.Tunable {
}
/**
- * 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 @@ public class PipTouchHandler implements TunerService.Tunable {
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 @@ public class PipTouchHandler implements TunerService.Tunable {
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 @@ public class PipTouchHandler implements TunerService.Tunable {
}
/**
- * 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 @@ public class PipTouchHandler implements TunerService.Tunable {
* 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 @@ public class PipTouchHandler implements TunerService.Tunable {
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 @@ public class PipTouchHandler implements TunerService.Tunable {
@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 17d9864e4d25..2e84ceda8ae0 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 c73c48636748..bc446287b6af 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;
@@ -7522,6 +7524,23 @@ public class ActivityManagerService extends IActivityManager.Stub
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();
@@ -7531,7 +7550,8 @@ public class ActivityManagerService extends IActivityManager.Stub
"enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
true /* checkActivityVisibility */);
- enterPictureInPictureModeLocked(r, displayId, aspectRatio,
+ r.pictureInPictureArgs.aspectRatio = aspectRatio;
+ enterPictureInPictureModeLocked(r, displayId, r.pictureInPictureArgs,
true /* moveHomeStackToFront */, "enterPictureInPictureMode");
}
} finally {
@@ -7539,25 +7559,29 @@ public class ActivityManagerService extends IActivityManager.Stub
}
}
- 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);
+ mWindowManager.setPictureInPictureActions(pipArgs.userActions);
}
@Override
- public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
- boolean enterPictureInPictureOnMoveToBg) {
+ public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "requestAutoEnterPictureInPicture", token, -1f /* aspectRatio */,
- false /* checkAspectRatio */, false /* checkActivityVisibility */);
+ "setPictureInPictureAspectRatio", token, aspectRatio,
+ true /* checkAspectRatio */, false /* checkActivityVisibility */);
- r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
+ 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);
+ }
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7565,19 +7589,27 @@ public class ActivityManagerService extends IActivityManager.Stub
}
@Override
- public void setPictureInPictureAspectRatio(IBinder token, float aspectRatio) {
+ public void setPictureInPictureActions(IBinder token, ParceledListSlice actionsList) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "setPictureInPictureAspectRatio", token, aspectRatio,
- true /* checkAspectRatio */, false /* checkActivityVisibility */);
+ "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.setPictureInPictureAspectRatio(aspectRatio);
+ mWindowManager.setPictureInPictureActions(actions);
}
- r.pictureInPictureAspectRatio = aspectRatio;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7613,7 +7645,7 @@ public class ActivityManagerService extends IActivityManager.Stub
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 7dd2e84c90d6..ef197002f0a7 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 static com.android.server.am.TaskRecord.INVALID_TASK_ID;
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 @@ final class ActivityRecord {
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 @@ final class ActivityRecord {
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 671c84e57968..5bdae574421b 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 @@ final class ActivityStack extends ConfigurationContainer {
// 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 0ad4e0a64791..34633c243d65 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 @@ package com.android.server.wm;
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.Gravity;
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 @@ class PinnedStackController {
// 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 @@ class PinnedStackController {
@Override
public void setSnapToEdge(final boolean snapToEdge) {
mHandler.post(() -> {
+ mIsSnappingToEdge = snapToEdge;
mSnapAlgorithm.setSnapToEdge(snapToEdge);
});
}
@@ -171,6 +178,9 @@ class PinnedStackController {
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 @@ class PinnedStackController {
}
/**
- * 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 @@ class PinnedStackController {
}
/**
+ * 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 @@ class PinnedStackController {
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 158fd2bfa9e5..577e5a0c2e20 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.ActivityManager;
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 class WindowManagerService extends IWindowManager.Stub
}
}
- 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 @@ public class WindowManagerService extends IWindowManager.Stub
}
/**
+ * 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.