Updating picture-in-picture API.
- Consolidating to enterPictureInPictureMode(), the new method will
attempt to put the activity into picture-in-picture mode if the
activity is visible or pausing in a state that would allow us to
pip it. Also consolidate the setting of the PiP aspect ratio and
actions into setPictureInPictureArgs().
- Fixing issue with onPause not completing when moving the
paused activity between stacks while dispatching onPause
Bug: 33692987
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: I3af2365f31a9b95de4a92eae46b77108947b2a49
diff --git a/api/current.txt b/api/current.txt
index 21ff81e..ec27f7c 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -3500,8 +3500,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3674,8 +3673,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 void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -5533,6 +5531,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
diff --git a/api/system-current.txt b/api/system-current.txt
index 1f590dd..962ab3e 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -3619,8 +3619,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3795,8 +3794,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 void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -5718,6 +5716,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
diff --git a/api/test-current.txt b/api/test-current.txt
index f4e71e3..2daf663 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -3502,8 +3502,7 @@
method public boolean dispatchTrackballEvent(android.view.MotionEvent);
method public void dump(java.lang.String, java.io.FileDescriptor, java.io.PrintWriter, java.lang.String[]);
method public void enterPictureInPictureMode();
- method public void enterPictureInPictureMode(float);
- method public void enterPictureInPictureModeOnMoveToBackground(boolean);
+ method public boolean enterPictureInPictureMode(android.app.PictureInPictureArgs);
method public android.view.View findViewById(int);
method public void finish();
method public void finishActivity(int);
@@ -3676,8 +3675,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 void setPictureInPictureArgs(android.app.PictureInPictureArgs);
method public final deprecated void setProgress(int);
method public final deprecated void setProgressBarIndeterminate(boolean);
method public final deprecated void setProgressBarIndeterminateVisibility(boolean);
@@ -5544,6 +5542,17 @@
method public abstract void onSendFinished(android.app.PendingIntent, android.content.Intent, int, java.lang.String, android.os.Bundle);
}
+ public final class PictureInPictureArgs implements android.os.Parcelable {
+ ctor public PictureInPictureArgs();
+ ctor public PictureInPictureArgs(float, java.util.List<android.app.RemoteAction>);
+ method public android.app.PictureInPictureArgs clone();
+ method public int describeContents();
+ method public void setActions(java.util.List<android.app.RemoteAction>);
+ method public void setAspectRatio(float);
+ method public void writeToParcel(android.os.Parcel, int);
+ field public static final android.os.Parcelable.Creator<android.app.PictureInPictureArgs> CREATOR;
+ }
+
public class Presentation extends android.app.Dialog {
ctor public Presentation(android.content.Context, android.view.Display);
ctor public Presentation(android.content.Context, android.view.Display, int);
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index f6771857..556d7ad 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -2029,78 +2029,53 @@
}
/**
- * Puts the activity in picture-in-picture mode.
+ * Puts the activity in picture-in-picture mode if possible in the current system state. Any
+ * prior calls to {@link #setPictureInPictureArgs(PictureInPictureArgs)} will still apply when
+ * entering picture-in-picture through this call.
+ *
+ * @see #enterPictureInPictureMode(PictureInPictureArgs)
* @see android.R.attr#supportsPictureInPicture
*/
public void enterPictureInPictureMode() {
- try {
- ActivityManager.getService().enterPictureInPictureMode(mToken);
- } catch (RemoteException e) {
- }
+ enterPictureInPictureMode(new PictureInPictureArgs());
}
/**
- * Puts the activity in picture-in-picture mode with a given aspect ratio.
+ * Puts the activity in picture-in-picture mode if possible in the current system state with
+ * explicit given arguments. Only the set parameters in {@param args} will override prior calls
+ * {@link #setPictureInPictureArgs(PictureInPictureArgs)}.
+ *
+ * The system may disallow entering picture-in-picture in various cases, including when the
+ * activity is not visible.
+ *
* @see android.R.attr#supportsPictureInPicture
*
- * @param aspectRatio the new aspect ratio of the picture-in-picture.
+ * @param args the explicit non-null arguments to use when entering picture-in-picture.
+ * @return whether the system successfully entered picture-in-picture.
*/
- public void enterPictureInPictureMode(float aspectRatio) {
+ public boolean enterPictureInPictureMode(@NonNull PictureInPictureArgs args) {
try {
- ActivityManagerNative.getDefault().enterPictureInPictureModeWithAspectRatio(mToken,
- aspectRatio);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * 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.
- */
- public void setPictureInPictureAspectRatio(float aspectRatio) {
- try {
- ActivityManagerNative.getDefault().setPictureInPictureAspectRatio(mToken, aspectRatio);
- } catch (RemoteException e) {
- }
- }
-
- /**
- * Updates the set of user actions associated with the picture-in-picture activity.
- *
- * @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 setPictureInPictureActions(List<RemoteAction> actions) {
- try {
- if (actions == null) {
- actions = new ArrayList<>();
+ if (args == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture args");
}
- ActivityManagerNative.getDefault().setPictureInPictureActions(mToken,
- new ParceledListSlice<RemoteAction>(actions));
+ return ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken, args);
+ } catch (RemoteException e) {
+ return false;
+ }
+ }
+
+ /**
+ * Updates the properties of the picture-in-picture activity, or sets it to be used later when
+ * {@link #enterPictureInPictureMode()} is called.
+ *
+ * @param args the new properties of the picture-in-picture.
+ */
+ public void setPictureInPictureArgs(@NonNull PictureInPictureArgs args) {
+ try {
+ if (args == null) {
+ throw new IllegalArgumentException("Expected non-null picture-in-picture args");
+ }
+ ActivityManagerNative.getDefault().setPictureInPictureArgs(mToken, args);
} catch (RemoteException e) {
}
}
diff --git a/core/java/android/app/ActivityManagerInternal.java b/core/java/android/app/ActivityManagerInternal.java
index 87700dc..4ff843d 100644
--- a/core/java/android/app/ActivityManagerInternal.java
+++ b/core/java/android/app/ActivityManagerInternal.java
@@ -64,36 +64,6 @@
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 e143255..2c1e7c5 100644
--- a/core/java/android/app/IActivityManager.aidl
+++ b/core/java/android/app/IActivityManager.aidl
@@ -34,6 +34,7 @@
import android.app.IUserSwitchObserver;
import android.app.Notification;
import android.app.PendingIntent;
+import android.app.PictureInPictureArgs;
import android.app.ProfilerInfo;
import android.app.WaitResult;
import android.app.assist.AssistContent;
@@ -479,12 +480,8 @@
boolean isInMultiWindowMode(in IBinder token);
boolean isInPictureInPictureMode(in IBinder token);
void killPackageDependents(in String packageName, int userId);
- void enterPictureInPictureMode(in IBinder token);
- void enterPictureInPictureModeWithAspectRatio(in IBinder token, float aspectRatio);
- void enterPictureInPictureModeOnMoveToBackground(in IBinder token,
- boolean enterPictureInPictureOnMoveToBg);
- void setPictureInPictureAspectRatio(in IBinder token, float aspectRatio);
- void setPictureInPictureActions(in IBinder token, in ParceledListSlice actions);
+ boolean enterPictureInPictureMode(in IBinder token, in PictureInPictureArgs args);
+ void setPictureInPictureArgs(in IBinder token, in PictureInPictureArgs args);
void activityRelaunched(in IBinder token);
IBinder getUriPermissionOwnerForActivity(in IBinder activityToken);
/**
diff --git a/core/java/android/app/PictureInPictureArgs.aidl b/core/java/android/app/PictureInPictureArgs.aidl
new file mode 100644
index 0000000..49df39a
--- /dev/null
+++ b/core/java/android/app/PictureInPictureArgs.aidl
@@ -0,0 +1,19 @@
+/**
+ * Copyright (c) 2017, 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 PictureInPictureArgs;
diff --git a/core/java/android/app/PictureInPictureArgs.java b/core/java/android/app/PictureInPictureArgs.java
new file mode 100644
index 0000000..fbdcbf4
--- /dev/null
+++ b/core/java/android/app/PictureInPictureArgs.java
@@ -0,0 +1,175 @@
+/*
+ * Copyright (C) 2017 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.Nullable;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents a set of arguments used to initialize the picture-in-picture mode.
+ */
+public final class PictureInPictureArgs implements Parcelable {
+
+ /**
+ * The expected aspect ratio of the picture-in-picture.
+ */
+ @Nullable
+ private Float mAspectRatio;
+
+ /**
+ * The set of actions that are associated with this activity when in picture in picture.
+ */
+ @Nullable
+ private List<RemoteAction> mUserActions;
+
+ PictureInPictureArgs(Parcel in) {
+ if (in.readInt() != 0) {
+ mAspectRatio = in.readFloat();
+ }
+ if (in.readInt() != 0) {
+ mUserActions = new ArrayList<>();
+ in.readParcelableList(mUserActions, RemoteAction.class.getClassLoader());
+ }
+ }
+
+ /**
+ * Creates a new set of picture-in-picture arguments.
+ */
+ public PictureInPictureArgs() {
+ // Empty constructor
+ }
+
+ /**
+ * Creates a new set of picture-in-picture arguments from the given {@param aspectRatio} and
+ * {@param actions}.
+ */
+ public PictureInPictureArgs(float aspectRatio, List<RemoteAction> actions) {
+ mAspectRatio = aspectRatio;
+ if (actions != null) {
+ mUserActions = new ArrayList<>(actions);
+ }
+ }
+
+ /**
+ * Copies the set parameters from the other picture-in-picture args.
+ * @hide
+ */
+ public void copyOnlySet(PictureInPictureArgs otherArgs) {
+ if (otherArgs.hasSetAspectRatio()) {
+ mAspectRatio = otherArgs.mAspectRatio;
+ }
+ if (otherArgs.hasSetActions()) {
+ mUserActions = otherArgs.mUserActions;
+ }
+ }
+
+ /**
+ * Sets the aspect ratio.
+ * @param aspectRatio the new aspect ratio for picture-in-picture.
+ */
+ public void setAspectRatio(float aspectRatio) {
+ mAspectRatio = aspectRatio;
+ }
+
+ /**
+ * @return the aspect ratio. If none is set, return 0.
+ * @hide
+ */
+ public float getAspectRatio() {
+ if (mAspectRatio != null) {
+ return mAspectRatio;
+ }
+ return 0f;
+ }
+
+ /**
+ * @return whether the aspect ratio is set.
+ * @hide
+ */
+ public boolean hasSetAspectRatio() {
+ return mAspectRatio != null;
+ }
+
+ /**
+ * Sets the user actions.
+ * @param actions the new actions to show in the picture-in-picture menu.
+ */
+ public void setActions(List<RemoteAction> actions) {
+ if (mUserActions != null) {
+ mUserActions = null;
+ }
+ if (actions != null) {
+ mUserActions = new ArrayList<>(actions);
+ }
+ }
+
+ /**
+ * @return the set of user actions.
+ * @hide
+ */
+ public List<RemoteAction> getActions() {
+ return mUserActions;
+ }
+
+ /**
+ * @return whether the user actions are set.
+ * @hide
+ */
+ public boolean hasSetActions() {
+ return mUserActions != null;
+ }
+
+ @Override
+ public PictureInPictureArgs clone() {
+ return new PictureInPictureArgs(mAspectRatio, mUserActions);
+ }
+
+ @Override
+ public int describeContents() {
+ return 0;
+ }
+
+ @Override
+ public void writeToParcel(Parcel out, int flags) {
+ if (mAspectRatio != null) {
+ out.writeInt(1);
+ out.writeFloat(mAspectRatio);
+ } else {
+ out.writeInt(0);
+ }
+ if (mUserActions != null) {
+ out.writeInt(1);
+ out.writeParcelableList(mUserActions, 0);
+ } else {
+ out.writeInt(0);
+ }
+ }
+
+ public static final Creator<PictureInPictureArgs> CREATOR =
+ new Creator<PictureInPictureArgs>() {
+ public PictureInPictureArgs createFromParcel(Parcel in) {
+ return new PictureInPictureArgs(in);
+ }
+ public PictureInPictureArgs[] newArray(int size) {
+ return new PictureInPictureArgs[size];
+ }
+ };
+}
\ No newline at end of file
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 2a324eb..db7d505 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -151,7 +151,6 @@
import android.app.ActivityManager.TaskSnapshot;
import android.app.ActivityManager.TaskThumbnailInfo;
import android.app.ActivityManagerInternal;
-import android.app.ActivityManagerInternal.PictureInPictureArguments;
import android.app.ActivityManagerInternal.SleepToken;
import android.app.ActivityOptions;
import android.app.ActivityThread;
@@ -182,6 +181,7 @@
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
+import android.app.PictureInPictureArgs;
import android.app.ProfilerInfo;
import android.app.RemoteAction;
import android.app.WaitResult;
@@ -229,7 +229,6 @@
import android.content.res.Resources;
import android.database.ContentObserver;
import android.graphics.Bitmap;
-import android.graphics.GraphicBuffer;
import android.graphics.Point;
import android.graphics.Rect;
import android.location.LocationManager;
@@ -7556,45 +7555,31 @@
}
@Override
- public void enterPictureInPictureMode(IBinder token) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, -1f /* aspectRatio */,
- false /* checkAspectRatio */);
- }
-
- @Override
- public void enterPictureInPictureModeWithAspectRatio(IBinder token, float aspectRatio) {
- enterPictureInPictureMode(token, DEFAULT_DISPLAY, aspectRatio, true /* checkAspectRatio */);
- }
-
- @Override
- public void enterPictureInPictureModeOnMoveToBackground(IBinder token,
- boolean enterPictureInPictureOnMoveToBg) {
+ public boolean enterPictureInPictureMode(IBinder token, final PictureInPictureArgs args) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "enterPictureInPictureModeOnMoveToBackground", token, -1f /* aspectRatio */,
- false /* checkAspectRatio */, false /* checkActivityVisibility */);
+ final ActivityRecord r = ensureValidPictureInPictureActivityArgsLocked(
+ "enterPictureInPictureMode", token, args);
- r.supportsPipOnMoveToBackground = enterPictureInPictureOnMoveToBg;
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
+ // Activity supports picture-in-picture, now check that we can enter PiP at this
+ // point, if it is
+ if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode")) {
+ return false;
+ }
- private void enterPictureInPictureMode(IBinder token, int displayId, float aspectRatio,
- boolean checkAspectRatio) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "enterPictureInPictureMode", token, aspectRatio, checkAspectRatio,
- true /* checkActivityVisibility */);
final Runnable enterPipRunnable = () -> {
- r.pictureInPictureArgs.aspectRatio = aspectRatio;
- enterPictureInPictureModeLocked(r, displayId, r.pictureInPictureArgs,
- true /* moveHomeStackToFront */, "enterPictureInPictureMode");
+ // Only update the saved args from the args that are set
+ r.pictureInPictureArgs.copyOnlySet(args);
+ final float aspectRatio = r.pictureInPictureArgs.getAspectRatio();
+ final List<RemoteAction> actions = r.pictureInPictureArgs.getActions();
+ final Rect bounds = isValidPictureInPictureAspectRatio(aspectRatio)
+ ? mWindowManager.getPictureInPictureBounds(DEFAULT_DISPLAY,
+ aspectRatio)
+ : mWindowManager.getPictureInPictureDefaultBounds(DEFAULT_DISPLAY);
+ mStackSupervisor.moveActivityToPinnedStackLocked(r, "enterPictureInPictureMode",
+ bounds, true /* moveHomeStackToFront */);
+ mWindowManager.setPictureInPictureActions(actions);
};
if (isKeyguardLocked()) {
@@ -7625,35 +7610,7 @@
// Enter picture in picture immediately otherwise
enterPipRunnable.run();
}
- }
- } finally {
- Binder.restoreCallingIdentity(origId);
- }
- }
-
- 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 setPictureInPictureAspectRatio(IBinder token, float aspectRatio) {
- final long origId = Binder.clearCallingIdentity();
- try {
- synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "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);
- }
+ return true;
}
} finally {
Binder.restoreCallingIdentity(origId);
@@ -7661,26 +7618,21 @@
}
@Override
- public void setPictureInPictureActions(IBinder token, ParceledListSlice actionsList) {
+ public void setPictureInPictureArgs(IBinder token, final PictureInPictureArgs args) {
final long origId = Binder.clearCallingIdentity();
try {
synchronized(this) {
- final ActivityRecord r = ensureValidPictureInPictureActivityLocked(
- "setPictureInPictureActions", token, -1 /* aspectRatio */,
- false /* checkAspectRatio */, false /* checkActivityVisibility */);
+ final ActivityRecord r = ensureValidPictureInPictureActivityArgsLocked(
+ "setPictureInPictureArgs", token, args);
- 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;
+ // Only update the saved args from the args that are set
+ r.pictureInPictureArgs.copyOnlySet(args);
if (r.getStack().getStackId() == PINNED_STACK_ID) {
// If the activity is already in picture-in-picture, update the pinned stack now
- mWindowManager.setPictureInPictureActions(actions);
+ mWindowManager.setPictureInPictureAspectRatio(
+ r.pictureInPictureArgs.getAspectRatio());
+ mWindowManager.setPictureInPictureActions(
+ r.pictureInPictureArgs.getActions());
}
}
} finally {
@@ -7696,14 +7648,10 @@
* Checks the state of the system and the activity associated with the given {@param token} to
* verify that picture-in-picture is supported for that activity.
*
- * @param checkAspectRatio whether or not to check {@param aspectRatio} is within a valid range
- * @param checkActivityVisibility whether or not to enforce that the activity is currently
- * visible
- *
* @return the activity record for the given {@param token} if all the checks pass.
*/
- private ActivityRecord ensureValidPictureInPictureActivityLocked(String caller, IBinder token,
- float aspectRatio, boolean checkAspectRatio, boolean checkActivityVisibility) {
+ private ActivityRecord ensureValidPictureInPictureActivityArgsLocked(String caller,
+ IBinder token, PictureInPictureArgs args) {
if (!mSupportsPictureInPicture) {
throw new IllegalStateException(caller
+ ": Device doesn't support picture-in-picture mode.");
@@ -7715,10 +7663,9 @@
+ ": Can't find activity for token=" + token);
}
- if (!r.canEnterPictureInPicture(checkActivityVisibility)) {
- throw new IllegalArgumentException(caller
- + ": Current activity does not support picture-in-picture or is not "
- + "visible r=" + r);
+ if (!r.supportsPictureInPicture()) {
+ throw new IllegalStateException(caller
+ + ": Current activity does not support picture-in-picture.");
}
if (r.getStack().isHomeStack()) {
@@ -7726,12 +7673,20 @@
+ ": Activities on the home stack not supported");
}
- if (checkAspectRatio && !isValidPictureInPictureAspectRatio(aspectRatio)) {
+ if (args.hasSetAspectRatio()
+ && !isValidPictureInPictureAspectRatio(args.getAspectRatio())) {
throw new IllegalArgumentException(String.format(caller
+ ": Aspect ratio is too extreme (must be between %f and %f).",
mMinPipAspectRatio, mMaxPipAspectRatio));
}
+ if (args.hasSetActions()
+ && args.getActions().size() > ActivityManager.getMaxNumPictureInPictureActions()) {
+ throw new IllegalArgumentException(String.format(caller + ": Invalid number of"
+ + "picture-in-picture actions. Only a maximum of %d actions allowed",
+ ActivityManager.getMaxNumPictureInPictureActions()));
+ }
+
return r;
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index a2fb9f9..6134d75 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -63,9 +63,9 @@
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.PictureInPictureArgs;
import android.app.ResultInfo;
import android.content.ComponentName;
import android.content.Intent;
@@ -229,13 +229,10 @@
boolean frozenBeforeDestroy;// has been frozen but not yet destroyed.
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.
- 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.
- PictureInPictureArguments pictureInPictureArgs = new PictureInPictureArguments(); // The PiP
+ boolean supportsPictureInPictureWhilePausing; // This flag is set by the system to indicate
+ // that the activity can enter picture in picture while pausing (ie. only when another
+ // task is brought to front or started)
+ PictureInPictureArgs pictureInPictureArgs = new PictureInPictureArgs(); // 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
@@ -453,12 +450,8 @@
if (info != null) {
pw.println(prefix + "resizeMode=" + ActivityInfo.resizeModeToString(info.resizeMode));
}
- if (supportsPipOnMoveToBackground) {
- pw.println(prefix + "supportsPipOnMoveToBackground=1");
- pw.println(prefix + "enterPipOnMoveToBackground=" +
- (enterPipOnMoveToBackground ? 1 : 0));
- pictureInPictureArgs.dump(pw, prefix);
- }
+ pw.println(prefix + "supportsPictureInPictureWhilePausing: "
+ + supportsPictureInPictureWhilePausing);
}
private boolean crossesHorizontalSizeThreshold(int firstDp, int secondDp) {
@@ -819,23 +812,6 @@
}
/**
- * If this activity has requested that it auto-enter picture-in-picture and we can actually do
- * this, then mark it to enter picture in picture at that point.
- */
- void setEnterPipOnMoveToBackground(boolean enterPipOnInvisible) {
- if (supportsPipOnMoveToBackground) {
- enterPipOnMoveToBackground = enterPipOnInvisible;
- }
- }
-
- /**
- * @return whether to enter PiP when this activity is made invisible.
- */
- public boolean shouldEnterPictureInPictureOnInvisible() {
- return enterPipOnMoveToBackground;
- }
-
- /**
* @return Stack value from current task, null if there is no task.
*/
ActivityStack getStack() {
@@ -918,24 +894,34 @@
}
/**
- * @return whether this activity is currently allowed to enter PIP, if
- * {@param checkActivityVisibility} is set, then the current activity visibility is taken into
- * account.
+ * @return whether this activity is currently allowed to enter PIP, throwing an exception if
+ * the activity is not currently visible.
*/
- boolean canEnterPictureInPicture(boolean checkActivityVisibility) {
- if (!checkActivityVisibility) {
- return supportsPictureInPicture();
+ boolean checkEnterPictureInPictureState(String caller) {
+ boolean isKeyguardLocked = service.isKeyguardLocked();
+ boolean hasPinnedStack = mStackSupervisor.getStack(PINNED_STACK_ID) != null;
+ switch (state) {
+ case RESUMED:
+ // When visible, allow entering PiP if not on the lockscreen. If there is another
+ // PiP activity, the logic to handle that comes later in enterPictureInPictureMode()
+ return !isKeyguardLocked;
+ case PAUSING:
+ case PAUSED:
+ // When pausing, only allow enter PiP if not on the lockscreen and there is not
+ // already an existing PiP activity
+ return !isKeyguardLocked && !hasPinnedStack && supportsPictureInPictureWhilePausing;
+ case STOPPING:
+ // When stopping in a valid state, then only allow enter PiP as in the pause state.
+ // Otherwise, fall through to throw an exception if the caller is trying to enter
+ // PiP in an invalid stopping state.
+ if (supportsPictureInPictureWhilePausing) {
+ return !isKeyguardLocked && !hasPinnedStack;
+ }
+ default:
+ throw new IllegalStateException(caller
+ + ": Current activity is not visible (state=" + state.name() + ") "
+ + "r=" + this);
}
-
- if (supportsPictureInPicture()) {
- switch (state) {
- case RESUMED:
- case PAUSING:
- case PAUSED:
- return true;
- }
- }
- return false;
}
boolean canGoInDockedStack() {
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index 4df0cb1..cd11d21 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -1698,9 +1698,7 @@
+ stackInvisible + " behindFullscreenActivity="
+ behindFullscreenActivity + " mLaunchTaskBehind="
+ r.mLaunchTaskBehind);
- if (!enterPictureInPictureOnActivityInvisible(r)) {
- makeInvisible(r, visibleBehind);
- }
+ makeInvisible(r, visibleBehind);
}
}
if (mStackId == FREEFORM_WORKSPACE_STACK_ID) {
@@ -1859,35 +1857,6 @@
return false;
}
- /**
- * Attempts to enter picture-in-picture if the activity that is being made invisible supports
- * it. If not, then
- *
- * @return whether or not picture-in-picture mode was entered.
- */
- private boolean enterPictureInPictureOnActivityInvisible(ActivityRecord r) {
- final boolean hasPinnedStack =
- mStackSupervisor.getStack(PINNED_STACK_ID) != null;
- final boolean isKeyguardLocked = mService.isKeyguardLocked();
- if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, " enterPictureInPictureOnInvisible="
- + r.shouldEnterPictureInPictureOnInvisible()
- + " hasPinnedStack=" + hasPinnedStack
- + " isKeyguardLocked=" + isKeyguardLocked);
- if (!hasPinnedStack && !isKeyguardLocked && r.visible &&
- r.shouldEnterPictureInPictureOnInvisible()) {
- r.setEnterPipOnMoveToBackground(false);
-
- // Enter picture in picture, but don't move the home stack to the front
- // since it will affect the focused stack's visibility and occlude
- // starting activities
- mService.enterPictureInPictureModeLocked(r, r.getDisplayId(),
- r.pictureInPictureArgs, false /* moveHomeStackToFront */,
- "ensureActivitiesVisibleLocked");
- return true;
- }
- return false;
- }
-
private void makeInvisible(ActivityRecord r, ActivityRecord visibleBehind) {
if (!r.visible) {
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Already invisible: " + r);
@@ -1906,6 +1875,10 @@
"Scheduling invisibility: " + r);
r.app.thread.scheduleWindowVisibility(r.appToken, false);
}
+
+ // Reset the flag indicating that an app can enter picture-in-picture once the
+ // activity is hidden
+ r.supportsPictureInPictureWhilePausing = false;
break;
case INITIALIZING:
@@ -2187,15 +2160,16 @@
mStackSupervisor.setLaunchSource(next.info.applicationInfo.uid);
- // We need to start pausing the current activity so the top one can be resumed...
- final boolean dontWaitForPause = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
- boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, dontWaitForPause);
+ // If the flag RESUME_WHILE_PAUSING is set, then continue to schedule the previous activity
+ // to be paused, while at the same time resuming the new resume activity
+ final boolean resumeWhilePausing = (next.info.flags & FLAG_RESUME_WHILE_PAUSING) != 0;
+ boolean pausing = mStackSupervisor.pauseBackStacks(userLeaving, next, false);
if (mResumedActivity != null) {
if (DEBUG_STATES) Slog.d(TAG_STATES,
"resumeTopActivityLocked: Pausing " + mResumedActivity);
- pausing |= startPausingLocked(userLeaving, false, next, dontWaitForPause);
+ pausing |= startPausingLocked(userLeaving, false, next, false);
}
- if (pausing) {
+ if (pausing && !resumeWhilePausing) {
if (DEBUG_SWITCH || DEBUG_STATES) Slog.v(TAG_STATES,
"resumeTopActivityLocked: Skip resume: need to start pausing");
// At this point we want to put the upcoming activity's process
@@ -2696,10 +2670,10 @@
if (r.mLaunchTaskBehind) {
transit = TRANSIT_TASK_OPEN_BEHIND;
} else {
- // If a new task is being launched, then mark the existing top activity to
- // enter picture-in-picture if it supports auto-entering PiP
+ // If a new task is being launched, then mark the existing top activity as
+ // supporting picture-in-picture while pausing
if (focusedTopActivity != null) {
- focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ focusedTopActivity.supportsPictureInPictureWhilePausing = true;
}
transit = TRANSIT_TASK_OPEN;
}
@@ -4245,10 +4219,10 @@
} else {
updateTransitLocked(TRANSIT_TASK_TO_FRONT, options);
}
- // If a new task is moved to the front, then mark the existing top activity to enter
- // picture-in-picture if it supports auto-entering PiP
+ // If a new task is moved to the front, then mark the existing top activity as supporting
+ // picture-in-picture while paused
if (focusedTopActivity != null) {
- focusedTopActivity.setEnterPipOnMoveToBackground(true);
+ focusedTopActivity.supportsPictureInPictureWhilePausing = true;
}
mStackSupervisor.resumeFocusedStackTopActivityLocked();
@@ -4966,8 +4940,8 @@
}
}
- void moveToFrontAndResumeStateIfNeeded(
- ActivityRecord r, boolean moveToFront, boolean setResume, String reason) {
+ void moveToFrontAndResumeStateIfNeeded(ActivityRecord r, boolean moveToFront, boolean setResume,
+ boolean setPause, String reason) {
if (!moveToFront) {
return;
}
@@ -4978,6 +4952,10 @@
if (setResume) {
mResumedActivity = r;
}
+ // If the activity was previously pausing, then ensure we transfer that as well
+ if (setPause) {
+ mPausingActivity = r;
+ }
// Move the stack in which we are placing the activity to the front. The call will also
// make sure the activity focus is set.
moveToFront(reason);
@@ -4998,6 +4976,7 @@
final boolean wasFocused = mStackSupervisor.isFocusedStack(prevStack)
&& (mStackSupervisor.topRunningActivityLocked() == r);
final boolean wasResumed = wasFocused && (prevStack.mResumedActivity == r);
+ final boolean wasPaused = prevStack.mPausingActivity == r;
final TaskRecord task = createTaskRecord(
mStackSupervisor.getNextTaskIdForUserLocked(r.userId),
@@ -5005,10 +4984,14 @@
r.setTask(task, null);
task.addActivityToTop(r);
mStackSupervisor.scheduleReportPictureInPictureModeChangedIfNeeded(task, prevStack);
- moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, "moveActivityToStack");
+ moveToFrontAndResumeStateIfNeeded(r, wasFocused, wasResumed, wasPaused,
+ "moveActivityToStack");
if (wasResumed) {
prevStack.mResumedActivity = null;
}
+ if (wasPaused) {
+ prevStack.mPausingActivity = null;
+ }
}
public int getStackId() {
diff --git a/services/core/java/com/android/server/am/ActivityStackSupervisor.java b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
index 4fe8939..1224abd 100644
--- a/services/core/java/com/android/server/am/ActivityStackSupervisor.java
+++ b/services/core/java/com/android/server/am/ActivityStackSupervisor.java
@@ -2617,6 +2617,7 @@
final ActivityStack prevStack = task.getStack();
final boolean wasFocused = isFocusedStack(prevStack) && (topRunningActivityLocked() == r);
final boolean wasResumed = prevStack.mResumedActivity == r;
+ final boolean wasPaused = prevStack.mPausingActivity == r;
// In some cases the focused stack isn't the front stack. E.g. pinned stack.
// Whenever we are moving the top activity from the front stack we want to make sure to move
// the stack to the front.
@@ -2641,10 +2642,19 @@
task.mTemporarilyUnresizable = false;
task.reparent(stack.mStackId, toTop ? MAX_VALUE : 0, reason);
+ // Reset the resumed activity on the previous stack
+ if (wasResumed) {
+ prevStack.mResumedActivity = null;
+ }
+ // Reset the paused activity on the previous stack
+ if (wasPaused) {
+ prevStack.mPausingActivity = null;
+ }
+
// If the task had focus before (or we're requested to move focus),
// move focus to the new stack by moving the stack to the front.
- stack.moveToFrontAndResumeStateIfNeeded(
- r, forceFocus || wasFocused || wasFront, wasResumed, reason);
+ stack.moveToFrontAndResumeStateIfNeeded(r, forceFocus || wasFocused || wasFront, wasResumed,
+ wasPaused, reason);
return stack;
}
@@ -2795,8 +2805,12 @@
if (task.mActivities.size() == 1) {
// There is only one activity in the task. So, we can just move the task over to
- // the stack without re-parenting the activity in a different task.
- if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE) {
+ // the stack without re-parenting the activity in a different task. We don't
+ // move the home stack forward if we are currently entering picture-in-picture
+ // while pausing because that changes the focused stack and may prevent the new
+ // starting activity from resuming.
+ if (moveHomeStackToFront && task.getTaskToReturnTo() == HOME_ACTIVITY_TYPE
+ && !r.supportsPictureInPictureWhilePausing) {
// Move the home stack forward if the task we just moved to the pinned stack
// was launched from home so home should be visible behind it.
moveHomeStackToFront(reason);
@@ -2808,6 +2822,10 @@
// reveal/leave the other activities in their original task
stack.moveActivityToStack(r);
}
+
+ // Reset the state that indicates it can enter PiP while pausing after we've moved it
+ // to the pinned stack
+ r.supportsPictureInPictureWhilePausing = false;
} finally {
mWindowManager.continueSurfaceLayout();
}
diff --git a/services/core/java/com/android/server/wm/PinnedStackController.java b/services/core/java/com/android/server/wm/PinnedStackController.java
index bfb4269..08f9b45 100644
--- a/services/core/java/com/android/server/wm/PinnedStackController.java
+++ b/services/core/java/com/android/server/wm/PinnedStackController.java
@@ -333,7 +333,9 @@
*/
void setActions(List<RemoteAction> actions) {
mActions.clear();
- mActions.addAll(actions);
+ if (actions != null) {
+ mActions.addAll(actions);
+ }
notifyActionsChanged(mActions);
}