diff options
4 files changed, 126 insertions, 34 deletions
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml index 5a35ca74e5dc..239747a37ec6 100644 --- a/core/res/res/values/config.xml +++ b/core/res/res/values/config.xml @@ -3345,6 +3345,11 @@ <!-- The duration in which a recent task is considered in session and should be visible. --> <integer name="config_activeTaskDurationHours">6</integer> + <!-- Whether this device prefers to show snapshot or splash screen on back predict target. + When set true, there will create windowless starting surface for the preview target, so it + won't affect activity's lifecycle. This should only be disabled on low-ram device. --> + <bool name="config_predictShowStartingSurface">true</bool> + <!-- default window ShowCircularMask property --> <bool name="config_windowShowCircularMask">false</bool> diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 8fb7c9d16170..96ac86ad9c37 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -393,6 +393,7 @@ <java-symbol type="integer" name="config_maxNumVisibleRecentTasks" /> <java-symbol type="integer" name="config_activeTaskDurationHours" /> <java-symbol type="bool" name="config_windowShowCircularMask" /> + <java-symbol type="bool" name="config_predictShowStartingSurface" /> <java-symbol type="bool" name="config_windowEnableCircularEmulatorDisplayOverlay" /> <java-symbol type="bool" name="config_supportMicNearUltrasound" /> <java-symbol type="bool" name="config_supportSpeakerNearUltrasound" /> diff --git a/services/core/java/com/android/server/wm/BackNavigationController.java b/services/core/java/com/android/server/wm/BackNavigationController.java index b67bc62e52f1..7e5a08081026 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<WindowContainer> mTmpOpenApps = new ArrayList<>(); private final ArrayList<WindowContainer> 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 b80c3e84198b..d0628f12336c 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); |