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
diff --git a/core/res/res/values/config.xml b/core/res/res/values/config.xml
index 5a35ca7..239747a 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 8fb7c9d..96ac86a 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 b67bc62..7e5a080 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 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 @@
     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 @@
     /**
      * 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 @@
 
         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 @@
             }
         }
 
-        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 @@
                 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 @@
                 return this;
             }
 
-            ScheduleAnimationBuilder setOpeningSnapshot(TaskSnapshot snapshot) {
-                mOpenSnapshot = snapshot;
-                return this;
-            }
-
             ScheduleAnimationBuilder setIsLaunchBehind(boolean launchBehind) {
                 mIsLaunchBehind = launchBehind;
                 return this;
@@ -1017,17 +1017,32 @@
                         || 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 @@
                 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 @@
         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 b80c3e8..d0628f1 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.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.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.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 @@
                 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 @@
         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);