Move PiP state check exception to the client side
- When calling enterPictureInPictureMode(), the state of the activity in
the client may be out of sync with the state of the activity in the
system, causing an exception to be thrown erroneously. Instead, fail
silently in the system if this occurs, and throw the exception in the
client when it attempts to enter PiP from an invalid state.
Bug: 63753007
Test: android.server.cts.ActivityManagerPinnedStackTests
Change-Id: Ia99cc086805edc31f997d4325f7a5ccd7c85a77e
diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java
index 3fa8927..757795e 100644
--- a/core/java/android/app/Activity.java
+++ b/core/java/android/app/Activity.java
@@ -762,6 +762,10 @@
private boolean mDestroyed;
private boolean mDoReportFullyDrawn = true;
private boolean mRestoredFromBundle;
+
+ /** {@code true} if the activity lifecycle is in a state which supports picture-in-picture.
+ * This only affects the client-side exception, the actual state check still happens in AMS. */
+ private boolean mCanEnterPictureInPicture = false;
/** true if the activity is going through a transient pause */
/*package*/ boolean mTemporaryPause = false;
/** true if the activity is being destroyed in order to recreate it with a new configuration */
@@ -2091,6 +2095,10 @@
if (params == null) {
throw new IllegalArgumentException("Expected non-null picture-in-picture params");
}
+ if (!mCanEnterPictureInPicture) {
+ throw new IllegalStateException("Activity must be resumed to enter"
+ + " picture-in-picture");
+ }
return ActivityManagerNative.getDefault().enterPictureInPictureMode(mToken, params);
} catch (RemoteException e) {
return false;
@@ -6957,25 +6965,29 @@
return mParent != null ? mParent.getActivityToken() : mToken;
}
- final void performCreateCommon() {
+ final void performCreate(Bundle icicle) {
+ performCreate(icicle, null);
+ }
+
+ final void performCreate(Bundle icicle, PersistableBundle persistentState) {
+ mCanEnterPictureInPicture = true;
+ restoreHasCurrentPermissionRequest(icicle);
+ if (persistentState != null) {
+ onCreate(icicle, persistentState);
+ } else {
+ onCreate(icicle);
+ }
+ mActivityTransitionState.readState(icicle);
+
mVisibleFromClient = !mWindow.getWindowStyle().getBoolean(
com.android.internal.R.styleable.Window_windowNoDisplay, false);
mFragments.dispatchActivityCreated();
mActivityTransitionState.setEnterActivityOptions(this, getActivityOptions());
}
- final void performCreate(Bundle icicle) {
- restoreHasCurrentPermissionRequest(icicle);
- onCreate(icicle);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
- }
-
- final void performCreate(Bundle icicle, PersistableBundle persistentState) {
- restoreHasCurrentPermissionRequest(icicle);
- onCreate(icicle, persistentState);
- mActivityTransitionState.readState(icicle);
- performCreateCommon();
+ final void performNewIntent(Intent intent) {
+ mCanEnterPictureInPicture = true;
+ onNewIntent(intent);
}
final void performStart() {
@@ -7126,6 +7138,9 @@
mDoReportFullyDrawn = false;
mFragments.doLoaderStop(mChangingConfigurations /*retain*/);
+ // Disallow entering picture-in-picture after the activity has been stopped
+ mCanEnterPictureInPicture = false;
+
if (!mStopped) {
if (mWindow != null) {
mWindow.closeAllPanels();
diff --git a/core/java/android/app/Instrumentation.java b/core/java/android/app/Instrumentation.java
index 467fc95..e260967 100644
--- a/core/java/android/app/Instrumentation.java
+++ b/core/java/android/app/Instrumentation.java
@@ -48,6 +48,7 @@
import android.view.MotionEvent;
import android.view.ViewConfiguration;
import android.view.Window;
+
import com.android.internal.content.ReferrerIntent;
import java.io.File;
@@ -1305,7 +1306,7 @@
* @param intent The new intent being received.
*/
public void callActivityOnNewIntent(Activity activity, Intent intent) {
- activity.onNewIntent(intent);
+ activity.performNewIntent(intent);
}
/**
diff --git a/services/core/java/com/android/server/am/ActivityManagerService.java b/services/core/java/com/android/server/am/ActivityManagerService.java
index 32d3445..5c5a6b7 100644
--- a/services/core/java/com/android/server/am/ActivityManagerService.java
+++ b/services/core/java/com/android/server/am/ActivityManagerService.java
@@ -8045,7 +8045,7 @@
// Activity supports picture-in-picture, now check that we can enter PiP at this
// point, if it is
if (!r.checkEnterPictureInPictureState("enterPictureInPictureMode",
- false /* noThrow */, false /* beforeStopping */)) {
+ false /* beforeStopping */)) {
return false;
}
diff --git a/services/core/java/com/android/server/am/ActivityRecord.java b/services/core/java/com/android/server/am/ActivityRecord.java
index e5985c5..874bd1e 100644
--- a/services/core/java/com/android/server/am/ActivityRecord.java
+++ b/services/core/java/com/android/server/am/ActivityRecord.java
@@ -1186,10 +1186,9 @@
* @param beforeStopping Whether this check is for an auto-enter-pip operation, that is to say
* the activity has requested to enter PiP when it would otherwise be stopped.
*
- * @return whether this activity is currently allowed to enter PIP, throwing an exception if
- * the activity is not currently visible and {@param noThrow} is not set.
+ * @return whether this activity is currently allowed to enter PIP.
*/
- boolean checkEnterPictureInPictureState(String caller, boolean noThrow, boolean beforeStopping) {
+ boolean checkEnterPictureInPictureState(String caller, boolean beforeStopping) {
if (!supportsPictureInPicture()) {
return false;
}
@@ -1237,13 +1236,7 @@
return isNotLockedOrOnKeyguard && !hasPinnedStack;
}
default:
- if (noThrow) {
- return false;
- } else {
- throw new IllegalStateException(caller
- + ": Current activity is not visible (state=" + state.name() + ") "
- + "r=" + this);
- }
+ return false;
}
}
diff --git a/services/core/java/com/android/server/am/ActivityStack.java b/services/core/java/com/android/server/am/ActivityStack.java
index c2656a1..eb3177a 100644
--- a/services/core/java/com/android/server/am/ActivityStack.java
+++ b/services/core/java/com/android/server/am/ActivityStack.java
@@ -2076,7 +2076,7 @@
if (DEBUG_VISIBILITY) Slog.v(TAG_VISIBILITY, "Making invisible: " + r + " " + r.state);
try {
final boolean canEnterPictureInPicture = r.checkEnterPictureInPictureState(
- "makeInvisible", true /* noThrow */, true /* beforeStopping */);
+ "makeInvisible", true /* beforeStopping */);
// Defer telling the client it is hidden if it can enter Pip and isn't current stopped
// or stopping. This gives it a chance to enter Pip in onPause().
final boolean deferHidingClient = canEnterPictureInPicture
@@ -2390,7 +2390,7 @@
// represent the last resumed activity. However, the last focus stack does if it isn't null.
final ActivityRecord lastResumed = lastFocusedStack.mResumedActivity;
lastResumedCanPip = lastResumed != null && lastResumed.checkEnterPictureInPictureState(
- "resumeTopActivity", true /* noThrow */, userLeaving /* beforeStopping */);
+ "resumeTopActivity", userLeaving /* beforeStopping */);
}
// 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 only if the