diff options
12 files changed, 295 insertions, 1 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 04207145673f..d3214c10decc 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -4022,6 +4022,7 @@ package android.app { method public void onPictureInPictureModeChanged(boolean, android.content.res.Configuration); method @Deprecated public void onPictureInPictureModeChanged(boolean); method public boolean onPictureInPictureRequested(); + method public void onPictureInPictureUiStateChanged(@NonNull android.app.PictureInPictureUiState); method @CallSuper protected void onPostCreate(@Nullable android.os.Bundle); method public void onPostCreate(@Nullable android.os.Bundle, @Nullable android.os.PersistableBundle); method @CallSuper protected void onPostResume(); @@ -6414,6 +6415,13 @@ package android.app { method public android.app.PictureInPictureParams.Builder setSourceRectHint(android.graphics.Rect); } + public final class PictureInPictureUiState implements android.os.Parcelable { + method public int describeContents(); + method public boolean isStashed(); + method public void writeToParcel(@NonNull android.os.Parcel, int); + field @NonNull public static final android.os.Parcelable.Creator<android.app.PictureInPictureUiState> 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/api/test-current.txt b/core/api/test-current.txt index a77bf32544e4..8f90296b8c97 100644 --- a/core/api/test-current.txt +++ b/core/api/test-current.txt @@ -347,6 +347,10 @@ package android.app { method public boolean isSeamlessResizeEnabled(); } + public final class PictureInPictureUiState implements android.os.Parcelable { + ctor public PictureInPictureUiState(boolean); + } + public class StatusBarManager { method public void clickNotification(@Nullable String, int, int, boolean); method @RequiresPermission(android.Manifest.permission.STATUS_BAR) public void collapsePanels(); diff --git a/core/java/android/app/Activity.java b/core/java/android/app/Activity.java index 45120b694b62..c5cafb828253 100644 --- a/core/java/android/app/Activity.java +++ b/core/java/android/app/Activity.java @@ -2813,6 +2813,29 @@ public class Activity extends ContextThemeWrapper } /** + * Called by the system when the activity is in PiP and has state changes. + * + * Compare to {@link #onPictureInPictureModeChanged(boolean, Configuration)}, which is only + * called when PiP mode changes (meaning, enters or exits PiP), this can be called at any time + * while the activity is in PiP mode. Therefore, all invocation can only happen after + * {@link #onPictureInPictureModeChanged(boolean, Configuration)} is called with true, and + * before {@link #onPictureInPictureModeChanged(boolean, Configuration)} is called with false. + * You would not need to worry about cases where this is called and the activity is not in + * Picture-In-Picture mode. For managing cases where the activity enters/exits + * Picture-in-Picture (e.g. resources clean-up on exit), use + * {@link #onPictureInPictureModeChanged(boolean, Configuration)}. + * + * The default state is everything declared in {@link PictureInPictureUiState} is false, such as + * {@link PictureInPictureUiState#isStashed()}. + * + * @param pipState the new Picture-in-Picture state. + */ + public void onPictureInPictureUiStateChanged(@NonNull PictureInPictureUiState pipState) { + // Left deliberately empty. There should be no side effects if a direct + // subclass of Activity does not call super. + } + + /** * Called by the system when the activity changes to and from picture-in-picture mode. * * @see android.R.attr#supportsPictureInPicture diff --git a/core/java/android/app/ActivityThread.java b/core/java/android/app/ActivityThread.java index 98fee9cf90cf..f56dfcccfe88 100644 --- a/core/java/android/app/ActivityThread.java +++ b/core/java/android/app/ActivityThread.java @@ -39,7 +39,6 @@ import android.annotation.Nullable; import android.app.assist.AssistContent; import android.app.assist.AssistStructure; import android.app.backup.BackupAgent; -import android.app.backup.BackupManager; import android.app.servertransaction.ActivityLifecycleItem; import android.app.servertransaction.ActivityLifecycleItem.LifecycleState; import android.app.servertransaction.ActivityRelaunchItem; @@ -3987,6 +3986,12 @@ public final class ActivityThread extends ClientTransactionHandler } } + @Override + public void handlePictureInPictureStateChanged(@NonNull ActivityClientRecord r, + PictureInPictureUiState pipState) { + r.activity.onPictureInPictureUiStateChanged(pipState); + } + /** * Register a splash screen manager to this process. */ diff --git a/core/java/android/app/ClientTransactionHandler.java b/core/java/android/app/ClientTransactionHandler.java index cf5fd148c030..c752f34ab0bb 100644 --- a/core/java/android/app/ClientTransactionHandler.java +++ b/core/java/android/app/ClientTransactionHandler.java @@ -159,6 +159,10 @@ public abstract class ClientTransactionHandler { /** Request that an activity enter picture-in-picture. */ public abstract void handlePictureInPictureRequested(@NonNull ActivityClientRecord r); + /** Signal to an activity (that is currently in PiP) of PiP state changes. */ + public abstract void handlePictureInPictureStateChanged(@NonNull ActivityClientRecord r, + PictureInPictureUiState pipState); + /** Whether the activity want to handle splash screen exit animation */ public abstract boolean isHandleSplashScreenExit(@NonNull IBinder token); diff --git a/core/java/android/app/IActivityTaskManager.aidl b/core/java/android/app/IActivityTaskManager.aidl index 346882ee7298..2f1f14e186db 100644 --- a/core/java/android/app/IActivityTaskManager.aidl +++ b/core/java/android/app/IActivityTaskManager.aidl @@ -36,6 +36,7 @@ import android.app.IUidObserver; import android.app.IUserSwitchObserver; import android.app.Notification; import android.app.PendingIntent; +import android.app.PictureInPictureUiState; import android.app.ProfilerInfo; import android.app.WaitResult; import android.app.assist.AssistContent; @@ -330,4 +331,9 @@ interface IActivityTaskManager { */ void onSplashScreenViewCopyFinished(int taskId, in SplashScreenView.SplashScreenViewParcelable material); + + /** + * When the Picture-in-picture state has changed. + */ + void onPictureInPictureStateChanged(in PictureInPictureUiState pipState); } diff --git a/core/java/android/app/PictureInPictureUiState.aidl b/core/java/android/app/PictureInPictureUiState.aidl new file mode 100644 index 000000000000..ca81fb6b99d1 --- /dev/null +++ b/core/java/android/app/PictureInPictureUiState.aidl @@ -0,0 +1,19 @@ +/** + * Copyright (c) 2021, 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 PictureInPictureUiState;
\ No newline at end of file diff --git a/core/java/android/app/PictureInPictureUiState.java b/core/java/android/app/PictureInPictureUiState.java new file mode 100644 index 000000000000..3d2cb3ffa58d --- /dev/null +++ b/core/java/android/app/PictureInPictureUiState.java @@ -0,0 +1,83 @@ +/* + * Copyright (C) 2021 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.annotation.TestApi; +import android.os.Parcel; +import android.os.Parcelable; + +import java.util.Objects; + +/** + * Used by {@link Activity#onPictureInPictureUiStateChanged(PictureInPictureUiState)}. + */ +public final class PictureInPictureUiState implements Parcelable { + + private boolean mIsStashed; + + /** {@hide} */ + PictureInPictureUiState(Parcel in) { + mIsStashed = in.readBoolean(); + } + + /** {@hide} */ + @TestApi + public PictureInPictureUiState(boolean isStashed) { + mIsStashed = isStashed; + } + + /** + * Returns whether Picture-in-Picture is stashed or not. + */ + public boolean isStashed() { + return mIsStashed; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (!(o instanceof PictureInPictureUiState)) return false; + PictureInPictureUiState that = (PictureInPictureUiState) o; + return Objects.equals(mIsStashed, that.mIsStashed); + } + + @Override + public int hashCode() { + return Objects.hash(mIsStashed); + } + + @Override + public int describeContents() { + return 0; + } + + @Override + public void writeToParcel(@NonNull Parcel out, int flags) { + out.writeBoolean(mIsStashed); + } + + public static final @android.annotation.NonNull Creator<PictureInPictureUiState> CREATOR = + new Creator<PictureInPictureUiState>() { + public PictureInPictureUiState createFromParcel(Parcel in) { + return new PictureInPictureUiState(in); + } + public PictureInPictureUiState[] newArray(int size) { + return new PictureInPictureUiState[size]; + } + }; +} diff --git a/core/java/android/app/servertransaction/PipStateTransactionItem.java b/core/java/android/app/servertransaction/PipStateTransactionItem.java new file mode 100644 index 000000000000..167f5a43b1b1 --- /dev/null +++ b/core/java/android/app/servertransaction/PipStateTransactionItem.java @@ -0,0 +1,93 @@ +/* + * Copyright 2021 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.servertransaction; + +import android.annotation.Nullable; +import android.app.ActivityThread.ActivityClientRecord; +import android.app.ClientTransactionHandler; +import android.app.PictureInPictureUiState; +import android.os.Parcel; + +/** + * Request an activity to enter picture-in-picture mode. + * @hide + */ +public final class PipStateTransactionItem extends ActivityTransactionItem { + + private PictureInPictureUiState mPipState; + + @Override + public void execute(ClientTransactionHandler client, ActivityClientRecord r, + PendingTransactionActions pendingActions) { + client.handlePictureInPictureStateChanged(r, mPipState); + } + + // ObjectPoolItem implementation + + private PipStateTransactionItem() {} + + /** Obtain an instance initialized with provided params. */ + public static PipStateTransactionItem obtain(PictureInPictureUiState pipState) { + PipStateTransactionItem instance = ObjectPool.obtain(PipStateTransactionItem.class); + if (instance == null) { + instance = new PipStateTransactionItem(); + } + instance.mPipState = pipState; + + return instance; + } + + @Override + public void recycle() { + mPipState = null; + ObjectPool.recycle(this); + } + + // Parcelable implementation + + /** Write to Parcel. */ + @Override + public void writeToParcel(Parcel dest, int flags) { + mPipState.writeToParcel(dest, flags); + } + + /** Read from Parcel. */ + private PipStateTransactionItem(Parcel in) { + mPipState = PictureInPictureUiState.CREATOR.createFromParcel(in); + } + + public static final @android.annotation.NonNull Creator<PipStateTransactionItem> CREATOR = + new Creator<PipStateTransactionItem>() { + public PipStateTransactionItem createFromParcel(Parcel in) { + return new PipStateTransactionItem(in); + } + + public PipStateTransactionItem[] newArray(int size) { + return new PipStateTransactionItem[size]; + } + }; + + @Override + public boolean equals(@Nullable Object o) { + return this == o; + } + + @Override + public String toString() { + return "PipStateTransactionItem{}"; + } +} diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java index e3594d0cd367..561dff0da6ca 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/pip/PipBoundsState.java @@ -19,10 +19,14 @@ package com.android.wm.shell.pip; import android.annotation.IntDef; import android.annotation.NonNull; import android.annotation.Nullable; +import android.app.ActivityTaskManager; +import android.app.PictureInPictureUiState; import android.content.ComponentName; import android.content.Context; import android.graphics.Point; import android.graphics.Rect; +import android.os.RemoteException; +import android.util.Log; import android.util.Size; import android.view.Display; @@ -185,7 +189,18 @@ public final class PipBoundsState { /** Dictate where PiP currently should be stashed, if at all. */ public void setStashed(@StashType int stashedState) { + if (mStashedState == stashedState) { + return; + } + mStashedState = stashedState; + try { + ActivityTaskManager.getService().onPictureInPictureStateChanged( + new PictureInPictureUiState(stashedState != STASH_TYPE_NONE /* isStashed */) + ); + } catch (RemoteException e) { + Log.e(TAG, "Unable to set alert PiP state change."); + } } /** diff --git a/services/core/java/com/android/server/wm/ActivityClientController.java b/services/core/java/com/android/server/wm/ActivityClientController.java index c5115b283f0a..efee0a1133b6 100644 --- a/services/core/java/com/android/server/wm/ActivityClientController.java +++ b/services/core/java/com/android/server/wm/ActivityClientController.java @@ -45,8 +45,10 @@ import android.app.ActivityTaskManager; import android.app.IActivityClientController; import android.app.IRequestFinishCallback; import android.app.PictureInPictureParams; +import android.app.PictureInPictureUiState; import android.app.servertransaction.ClientTransaction; import android.app.servertransaction.EnterPipRequestedItem; +import android.app.servertransaction.PipStateTransactionItem; import android.content.ComponentName; import android.content.Context; import android.content.Intent; @@ -779,6 +781,26 @@ class ActivityClientController extends IActivityClientController.Stub { } } + /** + * Alert the client that the Picture-in-Picture state has changed. + */ + void onPictureInPictureStateChanged(@NonNull ActivityRecord r, + PictureInPictureUiState pipState) { + if (!r.inPinnedWindowingMode()) { + throw new IllegalStateException("Activity is not in PIP mode"); + } + + try { + final ClientTransaction transaction = ClientTransaction.obtain( + r.app.getThread(), r.token); + transaction.addCallback(PipStateTransactionItem.obtain(pipState)); + mService.getLifecycleManager().scheduleTransaction(transaction); + } catch (Exception e) { + Slog.w(TAG, "Failed to send pip state transaction item: " + + r.intent.getComponent(), e); + } + } + @Override public void toggleFreeformWindowingMode(IBinder token) { final long ident = Binder.clearCallingIdentity(); diff --git a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java index a0beee4afcb9..6c417795359f 100644 --- a/services/core/java/com/android/server/wm/ActivityTaskManagerService.java +++ b/services/core/java/com/android/server/wm/ActivityTaskManagerService.java @@ -141,6 +141,7 @@ import android.app.Notification; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.PictureInPictureParams; +import android.app.PictureInPictureUiState; import android.app.ProfilerInfo; import android.app.RemoteAction; import android.app.WaitResult; @@ -3644,6 +3645,17 @@ public class ActivityTaskManagerService extends IActivityTaskManager.Stub { } } + @Override + public void onPictureInPictureStateChanged(PictureInPictureUiState pipState) { + enforceTaskPermission("onPictureInPictureStateChanged"); + final Task rootPinnedStask = mRootWindowContainer.getDefaultTaskDisplayArea() + .getRootPinnedTask(); + if (rootPinnedStask != null && rootPinnedStask.getTopMostActivity() != null) { + mWindowManager.mAtmService.mActivityClientController.onPictureInPictureStateChanged( + rootPinnedStask.getTopMostActivity(), pipState); + } + } + void dumpLastANRLocked(PrintWriter pw) { pw.println("ACTIVITY MANAGER LAST ANR (dumpsys activity lastanr)"); if (mLastANRState == null) { |