diff options
| author | 2024-11-04 05:07:01 +0000 | |
|---|---|---|
| committer | 2024-11-04 05:07:01 +0000 | |
| commit | 9f7247f5de71fbb6ffccb366faa45895afb6d502 (patch) | |
| tree | cb418b3fe495c45291929cc8ad0ae5e92287c5c3 | |
| parent | 126c0f3323a322f0cf15cca0cb1b9e96a52ad574 (diff) | |
| parent | 1f38d0f044f0468a67f66fd730e45d0d0e7cb380 (diff) | |
Merge "Supporting more non-default close activity callback for PB" into main
8 files changed, 303 insertions, 12 deletions
diff --git a/core/api/current.txt b/core/api/current.txt index 2cb7a30eb28c..015321352145 100644 --- a/core/api/current.txt +++ b/core/api/current.txt @@ -62048,6 +62048,11 @@ package android.window { method public void markSyncReady(); } + @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") public final class SystemOnBackInvokedCallbacks { + method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull android.app.Activity); + method @FlaggedApi("com.android.window.flags.predictive_back_system_override_callback") @NonNull public static android.window.OnBackInvokedCallback moveTaskToBackCallback(@NonNull android.app.Activity); + } + @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public final class TrustedPresentationThresholds implements android.os.Parcelable { ctor @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public TrustedPresentationThresholds(@FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @FloatRange(from=0.0f, fromInclusive=false, to=1.0f) float, @IntRange(from=1) int); method @FlaggedApi("com.android.window.flags.trusted_presentation_listener_for_window") public int describeContents(); diff --git a/core/java/android/window/OnBackInvokedCallbackInfo.java b/core/java/android/window/OnBackInvokedCallbackInfo.java index bb5fe96fdec1..44c7bd9ec612 100644 --- a/core/java/android/window/OnBackInvokedCallbackInfo.java +++ b/core/java/android/window/OnBackInvokedCallbackInfo.java @@ -16,6 +16,8 @@ package android.window; +import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; + import android.annotation.NonNull; import android.os.Parcel; import android.os.Parcelable; @@ -29,19 +31,23 @@ public final class OnBackInvokedCallbackInfo implements Parcelable { private final IOnBackInvokedCallback mCallback; private @OnBackInvokedDispatcher.Priority int mPriority; private final boolean mIsAnimationCallback; + private final @SystemOverrideOnBackInvokedCallback.OverrideBehavior int mOverrideBehavior; public OnBackInvokedCallbackInfo(@NonNull IOnBackInvokedCallback callback, int priority, - boolean isAnimationCallback) { + boolean isAnimationCallback, + int overrideBehavior) { mCallback = callback; mPriority = priority; mIsAnimationCallback = isAnimationCallback; + mOverrideBehavior = overrideBehavior; } private OnBackInvokedCallbackInfo(@NonNull Parcel in) { mCallback = IOnBackInvokedCallback.Stub.asInterface(in.readStrongBinder()); mPriority = in.readInt(); mIsAnimationCallback = in.readBoolean(); + mOverrideBehavior = in.readInt(); } @Override @@ -54,6 +60,7 @@ public final class OnBackInvokedCallbackInfo implements Parcelable { dest.writeStrongInterface(mCallback); dest.writeInt(mPriority); dest.writeBoolean(mIsAnimationCallback); + dest.writeInt(mOverrideBehavior); } public static final Creator<OnBackInvokedCallbackInfo> CREATOR = @@ -70,7 +77,8 @@ public final class OnBackInvokedCallbackInfo implements Parcelable { }; public boolean isSystemCallback() { - return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM; + return mPriority == OnBackInvokedDispatcher.PRIORITY_SYSTEM + || mOverrideBehavior != OVERRIDE_UNDEFINED; } @NonNull @@ -87,12 +95,18 @@ public final class OnBackInvokedCallbackInfo implements Parcelable { return mIsAnimationCallback; } + @SystemOverrideOnBackInvokedCallback.OverrideBehavior + public int getOverrideBehavior() { + return mOverrideBehavior; + } + @Override public String toString() { return "OnBackInvokedCallbackInfo{" + "mCallback=" + mCallback + ", mPriority=" + mPriority + ", mIsAnimationCallback=" + mIsAnimationCallback + + ", mOverrideBehavior=" + mOverrideBehavior + '}'; } } diff --git a/core/java/android/window/SystemOnBackInvokedCallbacks.java b/core/java/android/window/SystemOnBackInvokedCallbacks.java new file mode 100644 index 000000000000..f67520b1de96 --- /dev/null +++ b/core/java/android/window/SystemOnBackInvokedCallbacks.java @@ -0,0 +1,168 @@ +/* + * Copyright (C) 2024 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.window; + +import android.annotation.FlaggedApi; +import android.annotation.NonNull; +import android.app.Activity; +import android.util.ArrayMap; + +import com.android.window.flags.Flags; + +import java.lang.ref.WeakReference; + +/** + * Utility class providing {@link OnBackInvokedCallback}s to override the default behavior when + * system back is invoked. e.g. {@link Activity#finish} + * + * <p>By registering these callbacks with the {@link OnBackInvokedDispatcher}, the system can + * trigger specific behaviors and play corresponding ahead-of-time animations when the back + * gesture is invoked. + * + * <p>For example, to trigger the {@link Activity#moveTaskToBack} behavior: + * <pre> + * OnBackInvokedDispatcher dispatcher = activity.getOnBackInvokedDispatcher(); + * dispatcher.registerOnBackInvokedCallback( + * OnBackInvokedDispatcher.PRIORITY_DEFAULT, + * SystemOnBackInvokedCallbacks.moveTaskToBackCallback(activity)); + * </pre> + */ +@SuppressWarnings("SingularCallback") +@FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK) +public final class SystemOnBackInvokedCallbacks { + private static final OverrideCallbackFactory<Activity> sMoveTaskToBackFactory = new + MoveTaskToBackCallbackFactory(); + private static final OverrideCallbackFactory<Activity> sFinishAndRemoveTaskFactory = new + FinishAndRemoveTaskCallbackFactory(); + + private SystemOnBackInvokedCallbacks() { + throw new UnsupportedOperationException("This is a utility class and cannot be " + + "instantiated"); + } + + /** + * <p>Get a callback to triggers {@link Activity#moveTaskToBack(boolean)} on the associated + * {@link Activity}, moving the task containing the activity to the background. The system + * will play the corresponding transition animation, regardless of whether the activity + * is the root activity of the task.</p> + * + * @param activity The associated {@link Activity} + * @see Activity#moveTaskToBack(boolean) + */ + @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK) + @NonNull + public static OnBackInvokedCallback moveTaskToBackCallback(@NonNull Activity activity) { + return sMoveTaskToBackFactory.getOverrideCallback(activity); + } + + /** + * <p>Get a callback to triggers {@link Activity#finishAndRemoveTask()} on the associated + * {@link Activity}. If the activity is the root activity of its task, the entire task + * will be removed from the recents task. The activity will be finished in all cases. + * The system will play the corresponding transition animation.</p> + * + * @param activity The associated {@link Activity} + * @see Activity#finishAndRemoveTask() + */ + @FlaggedApi(Flags.FLAG_PREDICTIVE_BACK_SYSTEM_OVERRIDE_CALLBACK) + @NonNull + public static OnBackInvokedCallback finishAndRemoveTaskCallback(@NonNull Activity activity) { + return sFinishAndRemoveTaskFactory.getOverrideCallback(activity); + } + + /** + * Abstract factory for creating system override {@link SystemOverrideOnBackInvokedCallback} + * instances. + * + * <p>Concrete implementations of this factory are responsible for creating callbacks that + * override the default system back navigation behavior. These callbacks should be used + * exclusively for system overrides and should never be invoked directly.</p> + */ + private abstract static class OverrideCallbackFactory<TYPE> { + private final ArrayMap<WeakReference<TYPE>, + WeakReference<SystemOverrideOnBackInvokedCallback>> mObjectMap = new ArrayMap<>(); + + protected abstract SystemOverrideOnBackInvokedCallback createCallback( + @NonNull TYPE context); + + @NonNull SystemOverrideOnBackInvokedCallback getOverrideCallback(@NonNull TYPE object) { + if (object == null) { + throw new NullPointerException("Input object cannot be null"); + } + synchronized (mObjectMap) { + WeakReference<SystemOverrideOnBackInvokedCallback> callback = null; + for (int i = mObjectMap.size() - 1; i >= 0; --i) { + final WeakReference<TYPE> next = mObjectMap.keyAt(i); + if (next.get() == object) { + callback = mObjectMap.get(next); + break; + } + } + if (callback != null) { + return callback.get(); + } + final SystemOverrideOnBackInvokedCallback contextCallback = createCallback(object); + if (contextCallback != null) { + mObjectMap.put(new WeakReference<>(object), + new WeakReference<>(contextCallback)); + } + return contextCallback; + } + } + } + + private static class MoveTaskToBackCallbackFactory extends OverrideCallbackFactory<Activity> { + @Override + protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) { + final WeakReference<Activity> activityRef = new WeakReference<>(activity); + return new SystemOverrideOnBackInvokedCallback() { + @Override + public void onBackInvoked() { + if (activityRef.get() != null) { + activityRef.get().moveTaskToBack(true /* nonRoot */); + } + } + + @Override + public int overrideBehavior() { + return OVERRIDE_MOVE_TASK_TO_BACK; + } + }; + } + } + + private static class FinishAndRemoveTaskCallbackFactory extends + OverrideCallbackFactory<Activity> { + @Override + protected SystemOverrideOnBackInvokedCallback createCallback(Activity activity) { + final WeakReference<Activity> activityRef = new WeakReference<>(activity); + return new SystemOverrideOnBackInvokedCallback() { + @Override + public void onBackInvoked() { + if (activityRef.get() != null) { + activityRef.get().finishAndRemoveTask(); + } + } + + @Override + public int overrideBehavior() { + return OVERRIDE_FINISH_AND_REMOVE_TASK; + } + }; + } + } +} diff --git a/core/java/android/window/SystemOverrideOnBackInvokedCallback.java b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java new file mode 100644 index 000000000000..3360a199682e --- /dev/null +++ b/core/java/android/window/SystemOverrideOnBackInvokedCallback.java @@ -0,0 +1,61 @@ +/* + * Copyright (C) 2024 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.window; + +import android.annotation.IntDef; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * Non-default ahead-of-time system OnBackInvokedCallback. + * @hide + */ +public interface SystemOverrideOnBackInvokedCallback extends OnBackInvokedCallback { + /** + * No override request + */ + int OVERRIDE_UNDEFINED = 0; + + /** + * Navigating back will bring the task to back + */ + int OVERRIDE_MOVE_TASK_TO_BACK = 1; + + /** + * Navigating back will finish activity, and remove the task if this activity is root activity. + */ + int OVERRIDE_FINISH_AND_REMOVE_TASK = 2; + + /** @hide */ + @IntDef({ + OVERRIDE_UNDEFINED, + OVERRIDE_MOVE_TASK_TO_BACK, + OVERRIDE_FINISH_AND_REMOVE_TASK, + }) + @Retention(RetentionPolicy.SOURCE) + @interface OverrideBehavior { + } + + /** + * @return Override type of this callback. + */ + @OverrideBehavior + default int overrideBehavior() { + return OVERRIDE_UNDEFINED; + } +} diff --git a/core/java/android/window/WindowOnBackInvokedDispatcher.java b/core/java/android/window/WindowOnBackInvokedDispatcher.java index c9d458f22463..0ea4bb41d3a4 100644 --- a/core/java/android/window/WindowOnBackInvokedDispatcher.java +++ b/core/java/android/window/WindowOnBackInvokedDispatcher.java @@ -16,6 +16,9 @@ package android.window; +import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; + +import static com.android.window.flags.Flags.predictiveBackSystemOverrideCallback; import static com.android.window.flags.Flags.predictiveBackPrioritySystemNavigationObserver; import static com.android.window.flags.Flags.predictiveBackTimestampApi; @@ -201,6 +204,15 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { mImeDispatcher.registerOnBackInvokedCallback(priority, callback); return; } + if (predictiveBackPrioritySystemNavigationObserver() + && predictiveBackSystemOverrideCallback()) { + if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER + && callback instanceof SystemOverrideOnBackInvokedCallback) { + Log.e(TAG, "System override callbacks cannot be registered to " + + "NAVIGATION_OBSERVER"); + return; + } + } if (predictiveBackPrioritySystemNavigationObserver()) { if (priority == PRIORITY_SYSTEM_NAVIGATION_OBSERVER) { registerSystemNavigationObserverCallback(callback); @@ -365,7 +377,8 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { public void tryInvokeSystemNavigationObserverCallback() { OnBackInvokedCallback topCallback = getTopCallback(); Integer callbackPriority = mAllCallbacks.getOrDefault(topCallback, null); - if (callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) { + final boolean isSystemOverride = topCallback instanceof SystemOverrideOnBackInvokedCallback; + if ((callbackPriority != null && callbackPriority == PRIORITY_SYSTEM) || isSystemOverride) { invokeSystemNavigationObserverCallback(); } } @@ -384,14 +397,22 @@ public class WindowOnBackInvokedDispatcher implements OnBackInvokedDispatcher { OnBackInvokedCallbackInfo callbackInfo = null; if (callback != null) { int priority = mAllCallbacks.get(callback); + int overrideAnimation = OVERRIDE_UNDEFINED; + if (callback instanceof SystemOverrideOnBackInvokedCallback) { + overrideAnimation = ((SystemOverrideOnBackInvokedCallback) callback) + .overrideBehavior(); + } + final boolean isSystemCallback = priority == PRIORITY_SYSTEM + || overrideAnimation != OVERRIDE_UNDEFINED; final IOnBackInvokedCallback iCallback = new OnBackInvokedCallbackWrapper(callback, mTouchTracker, mProgressAnimator, mHandler, this::callOnKeyPreIme, this::invokeSystemNavigationObserverCallback, - /*isSystemCallback*/ priority == PRIORITY_SYSTEM); + isSystemCallback /*isSystemCallback*/); callbackInfo = new OnBackInvokedCallbackInfo( iCallback, priority, - callback instanceof OnBackAnimationCallback); + callback instanceof OnBackAnimationCallback, + overrideAnimation); } mWindowSession.setOnBackInvokedCallbackInfo(mWindow, callbackInfo); } catch (RemoteException e) { diff --git a/core/java/android/window/flags/windowing_frontend.aconfig b/core/java/android/window/flags/windowing_frontend.aconfig index e9a5ffd10d92..015614e4a734 100644 --- a/core/java/android/window/flags/windowing_frontend.aconfig +++ b/core/java/android/window/flags/windowing_frontend.aconfig @@ -369,4 +369,12 @@ flag { metadata { purpose: PURPOSE_BUGFIX } +} + +flag { + name: "predictive_back_system_override_callback" + namespace: "windowing_frontend" + description: "Provide pre-make predictive back API extension" + is_fixed_read_only: true + bug: "362938401" }
\ No newline at end of file diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index de6a2cf64a61..1a70e6896d18 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -26,6 +26,8 @@ import static android.view.WindowManager.TRANSIT_CLOSE; import static android.view.WindowManager.TRANSIT_OLD_NONE; import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; import static android.view.WindowManager.TRANSIT_TO_BACK; +import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_FINISH_AND_REMOVE_TASK; +import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; import static com.android.internal.protolog.WmProtoLogGroups.WM_DEBUG_BACK_PREVIEW; import static com.android.server.wm.BackNavigationProto.ANIMATION_IN_PROGRESS; @@ -60,6 +62,7 @@ import android.window.BackNavigationInfo; import android.window.IBackAnimationFinishedCallback; import android.window.IWindowlessStartingSurfaceCallback; import android.window.OnBackInvokedCallbackInfo; +import android.window.SystemOverrideOnBackInvokedCallback; import android.window.TaskSnapshot; import com.android.internal.annotations.VisibleForTesting; @@ -226,10 +229,19 @@ class BackNavigationController { && callbackInfo.isAnimationCallback()); mNavigationMonitor.startMonitor(window, navigationObserver); + int requestOverride = callbackInfo.getOverrideBehavior(); ProtoLog.d(WM_DEBUG_BACK_PREVIEW, "startBackNavigation currentTask=%s, " + "topRunningActivity=%s, callbackInfo=%s, currentFocus=%s", currentTask, currentActivity, callbackInfo, window); - + if (requestOverride == OVERRIDE_FINISH_AND_REMOVE_TASK) { + final ActivityRecord rootR = currentTask != null ? currentTask.getRootActivity() + : null; + if (currentActivity != null && rootR != currentActivity) { + // The top activity is not root activity, the activity cannot remove task when + // finishAndRemoveTask called. + requestOverride = OVERRIDE_UNDEFINED; + } + } // Clear the pointer down outside focus if any. mWindowManagerService.clearPointerDownOutsideFocusRunnable(); @@ -274,7 +286,8 @@ class BackNavigationController { } else if (hasTranslucentActivity(currentActivity, prevActivities)) { // skip if one of participant activity is translucent backType = BackNavigationInfo.TYPE_CALLBACK; - } else if (prevActivities.size() > 0) { + } else if (prevActivities.size() > 0 + && requestOverride == SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED) { if ((!isOccluded || isAllActivitiesCanShowWhenLocked(prevActivities)) && isAllActivitiesCreated(prevActivities)) { // We have another Activity in the same currentTask to go to diff --git a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java index 93b23190a23c..d6be9159694b 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -26,6 +26,7 @@ import static android.view.WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY; import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.view.WindowManager.TRANSIT_PREPARE_BACK_NAVIGATION; import static android.window.BackNavigationInfo.typeToString; +import static android.window.SystemOverrideOnBackInvokedCallback.OVERRIDE_UNDEFINED; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; @@ -455,7 +456,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { new OnBackInvokedCallbackInfo( callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT, - /* isAnimationCallback = */ false)); + /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); BackNavigationInfo backNavigationInfo = startBackNavigation(); assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); @@ -476,7 +477,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { new OnBackInvokedCallbackInfo( callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT, - /* isAnimationCallback = */ true)); + /* isAnimationCallback = */ true, OVERRIDE_UNDEFINED)); BackNavigationInfo backNavigationInfo = startBackNavigation(); assertWithMessage("BackNavigationInfo").that(backNavigationInfo).isNotNull(); @@ -617,7 +618,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { new OnBackInvokedCallbackInfo( callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT, - /* isAnimationCallback = */ false)); + /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); BackNavigationInfo backNavigationInfo = startBackNavigation(); assertThat(backNavigationInfo).isNull(); @@ -731,7 +732,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { new OnBackInvokedCallbackInfo( callback, OnBackInvokedDispatcher.PRIORITY_SYSTEM, - /* isAnimationCallback = */ false)); + /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); return callback; } @@ -741,7 +742,7 @@ public class BackNavigationControllerTests extends WindowTestsBase { new OnBackInvokedCallbackInfo( callback, OnBackInvokedDispatcher.PRIORITY_DEFAULT, - /* isAnimationCallback = */ false)); + /* isAnimationCallback = */ false, OVERRIDE_UNDEFINED)); return callback; } |