From 1b7ef7b04a30d7ab2633806a8c82e86e1ea5ea2f Mon Sep 17 00:00:00 2001 From: wilsonshih Date: Fri, 24 Feb 2023 07:41:58 +0000 Subject: [PATCH] Provide config for customize the showing strategy of back target. Provide config config_predictShowStartingSurface with default = true. If config_predictShowStartingSurface is true, system will always create a snapshot or splash screen surface for the predice animation target. When set config_predictShowStartingSurface to false, system will always make the back animation target alive. OEMs can choose the preview strategy for the opening target by override the config based on the device, but in general, it should only be disabled on low-ram device. There will be a CTS to verify this behavior. Bug: 268563842 Test: atest BackNavigationControllerTests Change-Id: Iaa3a01b9852756d7096bba9a356f723b0c53f7cd --- core/res/res/values/config.xml | 5 ++ core/res/res/values/symbols.xml | 1 + .../server/wm/BackNavigationController.java | 77 +++++++++++-------- .../wm/BackNavigationControllerTests.java | 77 +++++++++++++++++++ 4 files changed, 126 insertions(+), 34 deletions(-) diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5a35ca74e5dc7..239747a37ec67 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3345,6 +3345,11 @@ 6 + + true + false diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8fb7c9d161705..96ac86ad9c374 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -393,6 +393,7 @@ + diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b67bc62e52f1c..7e5a080810269 100644 --- a/services/core/java/com/android/server/wm/BackNavigationController.java +++ b/services/core/java/com/android/server/wm/BackNavigationController.java @@ -32,6 +32,7 @@ import static com.android.server.wm.SurfaceAnimator.ANIMATION_TYPE_PREDICT_BACK; import android.annotation.NonNull; import android.annotation.Nullable; +import android.content.Context; import android.content.res.ResourceId; import android.graphics.Point; import android.graphics.Rect; @@ -75,7 +76,7 @@ class BackNavigationController { private Runnable mPendingAnimation; private final NavigationMonitor mNavigationMonitor = new NavigationMonitor(); - private AnimationHandler mAnimationHandler; + AnimationHandler mAnimationHandler; private final ArrayList mTmpOpenApps = new ArrayList<>(); private final ArrayList mTmpCloseApps = new ArrayList<>(); @@ -642,7 +643,8 @@ class BackNavigationController { /** * Create and handling animations status for an open/close animation targets. */ - private static class AnimationHandler { + static class AnimationHandler { + private final boolean mShowWindowlessSurface; private final WindowManagerService mWindowManagerService; private BackWindowAnimationAdaptor mCloseAdaptor; private BackWindowAnimationAdaptor mOpenAdaptor; @@ -661,6 +663,9 @@ class BackNavigationController { AnimationHandler(WindowManagerService wms) { mWindowManagerService = wms; + final Context context = wms.mContext; + mShowWindowlessSurface = context.getResources().getBoolean( + com.android.internal.R.bool.config_predictShowStartingSurface); } private static final int UNKNOWN = 0; private static final int TASK_SWITCH = 1; @@ -690,7 +695,8 @@ class BackNavigationController { } } - boolean composeAnimations(@NonNull WindowContainer close, @NonNull WindowContainer open) { + private boolean composeAnimations(@NonNull WindowContainer close, + @NonNull WindowContainer open) { clearBackAnimateTarget(null /* cleanupTransaction */); if (close == null || open == null) { Slog.e(TAG, "reset animation with null target close: " @@ -974,21 +980,20 @@ class BackNavigationController { case BackNavigationInfo.TYPE_CROSS_ACTIVITY: return new ScheduleAnimationBuilder(backType, adapter) .setComposeTarget(currentActivity, previousActivity) - .setOpeningSnapshot(getActivitySnapshot(previousActivity)); + .setIsLaunchBehind(false); case BackNavigationInfo.TYPE_CROSS_TASK: return new ScheduleAnimationBuilder(backType, adapter) .setComposeTarget(currentTask, previousTask) - .setOpeningSnapshot(getTaskSnapshot(previousTask)); + .setIsLaunchBehind(false); } return null; } - private class ScheduleAnimationBuilder { + class ScheduleAnimationBuilder { final int mType; final BackAnimationAdapter mBackAnimationAdapter; WindowContainer mCloseTarget; WindowContainer mOpenTarget; - TaskSnapshot mOpenSnapshot; boolean mIsLaunchBehind; ScheduleAnimationBuilder(int type, BackAnimationAdapter backAnimationAdapter) { @@ -1002,11 +1007,6 @@ class BackNavigationController { return this; } - ScheduleAnimationBuilder setOpeningSnapshot(TaskSnapshot snapshot) { - mOpenSnapshot = snapshot; - return this; - } - ScheduleAnimationBuilder setIsLaunchBehind(boolean launchBehind) { mIsLaunchBehind = launchBehind; return this; @@ -1017,17 +1017,32 @@ class BackNavigationController { || wc.hasChild(mOpenTarget) || wc.hasChild(mCloseTarget); } + /** + * Apply preview strategy on the opening target + * @param open The opening target. + * @param visibleOpenActivity The visible activity in opening target. + * @return If the preview strategy is launch behind, returns the Activity that has + * launchBehind set, or null otherwise. + */ + private ActivityRecord applyPreviewStrategy(WindowContainer open, + ActivityRecord visibleOpenActivity) { + if (isSupportWindowlessSurface() && mShowWindowlessSurface && !mIsLaunchBehind) { + createStartingSurface(getSnapshot(open)); + return null; + } + setLaunchBehind(visibleOpenActivity); + return visibleOpenActivity; + } + Runnable build() { if (mOpenTarget == null || mCloseTarget == null) { return null; } - final boolean shouldLaunchBehind = mIsLaunchBehind || !isSupportWindowlessSurface(); - final ActivityRecord launchBehindActivity = !shouldLaunchBehind ? null - : mOpenTarget.asTask() != null + final ActivityRecord openActivity = mOpenTarget.asTask() != null ? mOpenTarget.asTask().getTopNonFinishingActivity() : mOpenTarget.asActivityRecord() != null ? mOpenTarget.asActivityRecord() : null; - if (shouldLaunchBehind && launchBehindActivity == null) { + if (openActivity == null) { Slog.e(TAG, "No opening activity"); return null; } @@ -1035,11 +1050,8 @@ class BackNavigationController { if (!composeAnimations(mCloseTarget, mOpenTarget)) { return null; } - if (launchBehindActivity != null) { - setLaunchBehind(launchBehindActivity); - } else { - createStartingSurface(mOpenSnapshot); - } + final ActivityRecord launchBehindActivity = + applyPreviewStrategy(mOpenTarget, openActivity); final IBackAnimationFinishedCallback callback = makeAnimationFinishedCallback( launchBehindActivity != null ? triggerBack -> { @@ -1162,25 +1174,22 @@ class BackNavigationController { mPendingAnimationBuilder = null; } - private static TaskSnapshot getActivitySnapshot(@NonNull ActivityRecord r) { + static TaskSnapshot getSnapshot(@NonNull WindowContainer w) { if (!isScreenshotEnabled()) { return null; } - // Check if we have a screenshot of the previous activity, indexed by its - // component name. - // TODO return TaskSnapshot when feature complete. -// final HardwareBuffer hw = r.getTask().getSnapshotForActivityRecord(r); - return null; - } + if (w.asTask() != null) { + final Task task = w.asTask(); + return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot( + task.mTaskId, task.mUserId, false /* restoreFromDisk */, + false /* isLowResolution */); + } - private static TaskSnapshot getTaskSnapshot(Task task) { - if (!isScreenshotEnabled()) { + if (w.asActivityRecord() != null) { + // TODO (b/259497289) return TaskSnapshot when feature complete. return null; } - // Don't read from disk!! - return task.mRootWindowContainer.mWindowManager.mTaskSnapshotController.getSnapshot( - task.mTaskId, task.mUserId, false /* restoreFromDisk */, - false /* isLowResolution */); + return null; } void setWindowManager(WindowManagerService wm) { 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 b80c3e84198be..d0628f12336cd 100644 --- a/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java +++ b/services/tests/wmtests/src/com/android/server/wm/BackNavigationControllerTests.java @@ -26,25 +26,31 @@ import static android.view.WindowManager.LayoutParams.TYPE_BASE_APPLICATION; import static android.window.BackNavigationInfo.typeToString; import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; import static com.android.dx.mockito.inline.extended.ExtendedMockito.spyOn; +import static com.android.dx.mockito.inline.extended.ExtendedMockito.verify; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doAnswer; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.never; import static org.mockito.Mockito.when; import android.annotation.NonNull; import android.annotation.Nullable; import android.app.ActivityOptions; import android.content.Context; +import android.content.ContextWrapper; import android.content.pm.ApplicationInfo; +import android.content.res.Resources; import android.os.Bundle; import android.os.RemoteCallback; import android.os.RemoteException; @@ -58,6 +64,7 @@ import android.window.IOnBackInvokedCallback; import android.window.OnBackInvokedCallback; import android.window.OnBackInvokedCallbackInfo; import android.window.OnBackInvokedDispatcher; +import android.window.TaskSnapshot; import android.window.WindowOnBackInvokedDispatcher; import com.android.server.LocalServices; @@ -66,6 +73,8 @@ import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; import org.mockito.Mockito; +import org.mockito.MockitoSession; +import org.mockito.quality.Strictness; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; @@ -408,6 +417,25 @@ public class BackNavigationControllerTests extends WindowTestsBase { 0, navigationObserver.getCount()); } + + /** + * Test with + * config_predictShowStartingSurface = true + */ + @Test + public void testEnableWindowlessSurface() { + testPrepareAnimation(true); + } + + /** + * Test with + * config_predictShowStartingSurface = false + */ + @Test + public void testDisableWindowlessSurface() { + testPrepareAnimation(false); + } + private IOnBackInvokedCallback withSystemCallback(Task task) { IOnBackInvokedCallback callback = createOnBackInvokedCallback(); task.getTopMostActivity().getTopChild().setOnBackInvokedCallbackInfo( @@ -492,6 +520,55 @@ public class BackNavigationControllerTests extends WindowTestsBase { doReturn(true).when(kc).isDisplayOccluded(anyInt()); } + private void testPrepareAnimation(boolean preferWindowlessSurface) { + final TaskSnapshot taskSnapshot = mock(TaskSnapshot.class); + final ContextWrapper contextSpy = Mockito.spy(new ContextWrapper(mWm.mContext)); + final Resources resourcesSpy = Mockito.spy(contextSpy.getResources()); + + when(contextSpy.getResources()).thenReturn(resourcesSpy); + + MockitoSession mockitoSession = mockitoSession().mockStatic(BackNavigationController.class) + .strictness(Strictness.LENIENT).startMocking(); + doReturn(taskSnapshot).when(() -> BackNavigationController.getSnapshot(any())); + when(resourcesSpy.getBoolean( + com.android.internal.R.bool.config_predictShowStartingSurface)) + .thenReturn(preferWindowlessSurface); + + final BackNavigationController.AnimationHandler animationHandler = + Mockito.spy(new BackNavigationController.AnimationHandler(mWm)); + doReturn(true).when(animationHandler).isSupportWindowlessSurface(); + testWithConfig(animationHandler, preferWindowlessSurface); + mockitoSession.finishMocking(); + } + + private void testWithConfig(BackNavigationController.AnimationHandler animationHandler, + boolean preferWindowlessSurface) { + final Task task = createTask(mDefaultDisplay); + final ActivityRecord bottomActivity = createActivityRecord(task); + final ActivityRecord homeActivity = mRootHomeTask.getTopNonFinishingActivity(); + + final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toHomeBuilder = + animationHandler.prepareAnimation(BackNavigationInfo.TYPE_RETURN_TO_HOME, + mBackAnimationAdapter, task, mRootHomeTask, bottomActivity, homeActivity); + assertTrue(toHomeBuilder.mIsLaunchBehind); + toHomeBuilder.build(); + verify(animationHandler, never()).createStartingSurface(any()); + + // Back to ACTIVITY and TASK have the same logic, just with different target. + final ActivityRecord topActivity = createActivityRecord(task); + final BackNavigationController.AnimationHandler.ScheduleAnimationBuilder toActivityBuilder = + animationHandler.prepareAnimation( + BackNavigationInfo.TYPE_CROSS_ACTIVITY, mBackAnimationAdapter, task, task, + topActivity, bottomActivity); + assertFalse(toActivityBuilder.mIsLaunchBehind); + toActivityBuilder.build(); + if (preferWindowlessSurface) { + verify(animationHandler).createStartingSurface(any()); + } else { + verify(animationHandler, never()).createStartingSurface(any()); + } + } + @NonNull private Task createTopTaskWithActivity() { Task task = createTask(mDefaultDisplay);