Add developer option for back animation DO NOT MERGE

Bug: 228936326
Test: BackAnimationControllerTest#animationDisabledFromSettings
Change-Id: I857a480e7375cada9171712d737162c7876c087e
Merged-In: Ie7a3acc5674080a22ccdcd0fe31a158a30ee9040
diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java
index 731b531..2255bf5 100644
--- a/core/java/android/provider/Settings.java
+++ b/core/java/android/provider/Settings.java
@@ -15020,6 +15020,14 @@
          */
         public static final String DEVICE_CONFIG_SYNC_DISABLED = "device_config_sync_disabled";
 
+
+        /**
+         * Whether back preview animations are played when user does a back gesture or presses
+         * the back button.
+         * @hide
+         */
+        public static final String ENABLE_BACK_ANIMATION = "enable_back_animation";
+
         /** @hide */ public static String zenModeToString(int mode) {
             if (mode == ZEN_MODE_IMPORTANT_INTERRUPTIONS) return "ZEN_MODE_IMPORTANT_INTERRUPTIONS";
             if (mode == ZEN_MODE_ALARMS) return "ZEN_MODE_ALARMS";
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
index ced36a7..c3fbe55 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/back/BackAnimationController.java
@@ -24,12 +24,18 @@
 import android.app.ActivityTaskManager;
 import android.app.IActivityTaskManager;
 import android.app.WindowConfiguration;
+import android.content.ContentResolver;
 import android.content.Context;
+import android.database.ContentObserver;
 import android.graphics.Point;
 import android.graphics.PointF;
 import android.hardware.HardwareBuffer;
+import android.net.Uri;
+import android.os.Handler;
 import android.os.RemoteException;
 import android.os.SystemProperties;
+import android.os.UserHandle;
+import android.provider.Settings.Global;
 import android.util.Log;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
@@ -42,22 +48,27 @@
 import com.android.internal.protolog.common.ProtoLog;
 import com.android.wm.shell.common.RemoteCallable;
 import com.android.wm.shell.common.ShellExecutor;
+import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 
+import java.util.concurrent.atomic.AtomicBoolean;
+
 /**
  * Controls the window animation run when a user initiates a back gesture.
  */
 public class BackAnimationController implements RemoteCallable<BackAnimationController> {
     private static final String TAG = "BackAnimationController";
+    private static final int SETTING_VALUE_OFF = 0;
+    private static final int SETTING_VALUE_ON = 1;
     private static final String PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP =
             "persist.wm.debug.predictive_back_progress_threshold";
     public static final boolean IS_ENABLED =
-            SystemProperties.getInt("persist.wm.debug.predictive_back", 1) != 0;
+            SystemProperties.getInt("persist.wm.debug.predictive_back",
+                    SETTING_VALUE_ON) != SETTING_VALUE_OFF;
     private static final int PROGRESS_THRESHOLD = SystemProperties
             .getInt(PREDICTIVE_BACK_PROGRESS_THRESHOLD_PROP, -1);
-    @VisibleForTesting
-    boolean mEnableAnimations = SystemProperties.getInt(
-            "persist.wm.debug.predictive_back_anim", 0) != 0;
+
+    private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);
 
     /**
      * Location of the initial touch event of the back gesture.
@@ -87,21 +98,50 @@
     private float mProgressThreshold;
 
     public BackAnimationController(
-            @ShellMainThread ShellExecutor shellExecutor,
+            @NonNull @ShellMainThread ShellExecutor shellExecutor,
+            @NonNull @ShellBackgroundThread Handler backgroundHandler,
             Context context) {
-        this(shellExecutor, new SurfaceControl.Transaction(), ActivityTaskManager.getService(),
-                context);
+        this(shellExecutor, backgroundHandler, new SurfaceControl.Transaction(),
+                ActivityTaskManager.getService(), context, context.getContentResolver());
     }
 
     @VisibleForTesting
-    BackAnimationController(@NonNull ShellExecutor shellExecutor,
+    BackAnimationController(@NonNull @ShellMainThread ShellExecutor shellExecutor,
+            @NonNull @ShellBackgroundThread Handler handler,
             @NonNull SurfaceControl.Transaction transaction,
             @NonNull IActivityTaskManager activityTaskManager,
-            Context context) {
+            Context context, ContentResolver contentResolver) {
         mShellExecutor = shellExecutor;
         mTransaction = transaction;
         mActivityTaskManager = activityTaskManager;
         mContext = context;
+        setupAnimationDeveloperSettingsObserver(contentResolver, handler);
+    }
+
+    private void setupAnimationDeveloperSettingsObserver(
+            @NonNull ContentResolver contentResolver,
+            @NonNull @ShellBackgroundThread final Handler backgroundHandler) {
+        ContentObserver settingsObserver = new ContentObserver(backgroundHandler) {
+            @Override
+            public void onChange(boolean selfChange, Uri uri) {
+                updateEnableAnimationFromSetting();
+            }
+        };
+        contentResolver.registerContentObserver(
+                Global.getUriFor(Global.ENABLE_BACK_ANIMATION),
+                false, settingsObserver, UserHandle.USER_SYSTEM
+        );
+        updateEnableAnimationFromSetting();
+    }
+
+    @ShellBackgroundThread
+    private void updateEnableAnimationFromSetting() {
+        int settingValue = Global.getInt(mContext.getContentResolver(),
+                Global.ENABLE_BACK_ANIMATION, SETTING_VALUE_OFF);
+        boolean isEnabled = settingValue == SETTING_VALUE_ON;
+        mEnableAnimations.set(isEnabled);
+        ProtoLog.d(WM_SHELL_BACK_PREVIEW, "Back animation enabled=%s",
+                isEnabled);
     }
 
     public BackAnimation getBackAnimationImpl() {
@@ -340,12 +380,7 @@
     private boolean shouldDispatchToLauncher(int backType) {
         return backType == BackNavigationInfo.TYPE_RETURN_TO_HOME
                 && mBackToLauncherCallback != null
-                && mEnableAnimations;
-    }
-
-    @VisibleForTesting
-    void setEnableAnimations(boolean shouldEnable) {
-        mEnableAnimations = shouldEnable;
+                && mEnableAnimations.get();
     }
 
     private static void dispatchOnBackStarted(IOnBackInvokedCallback callback) {
diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
index 4ad0868..3335673 100644
--- a/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
+++ b/libs/WindowManager/Shell/src/com/android/wm/shell/dagger/WMShellBaseModule.java
@@ -55,6 +55,7 @@
 import com.android.wm.shell.common.TaskStackListenerImpl;
 import com.android.wm.shell.common.TransactionPool;
 import com.android.wm.shell.common.annotations.ShellAnimationThread;
+import com.android.wm.shell.common.annotations.ShellBackgroundThread;
 import com.android.wm.shell.common.annotations.ShellMainThread;
 import com.android.wm.shell.common.annotations.ShellSplashscreenThread;
 import com.android.wm.shell.compatui.CompatUI;
@@ -734,11 +735,12 @@
     @Provides
     static Optional<BackAnimationController> provideBackAnimationController(
             Context context,
-            @ShellMainThread ShellExecutor shellExecutor
+            @ShellMainThread ShellExecutor shellExecutor,
+            @ShellBackgroundThread Handler backgroundHandler
     ) {
         if (BackAnimationController.IS_ENABLED) {
             return Optional.of(
-                    new BackAnimationController(shellExecutor, context));
+                    new BackAnimationController(shellExecutor, backgroundHandler, context));
         }
         return Optional.empty();
     }
diff --git a/libs/WindowManager/Shell/tests/unittest/Android.bp b/libs/WindowManager/Shell/tests/unittest/Android.bp
index fb53e535..a899709 100644
--- a/libs/WindowManager/Shell/tests/unittest/Android.bp
+++ b/libs/WindowManager/Shell/tests/unittest/Android.bp
@@ -43,6 +43,7 @@
         "truth-prebuilt",
         "testables",
         "platform-test-annotations",
+        "frameworks-base-testutils",
     ],
 
     libs: [
diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
index a905dca..6cf8829 100644
--- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
+++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/back/BackAnimationControllerTest.java
@@ -26,17 +26,23 @@
 import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.never;
+import static org.mockito.Mockito.times;
 import static org.mockito.Mockito.verify;
 
 import android.app.IActivityTaskManager;
 import android.app.WindowConfiguration;
-import android.content.Context;
+import android.content.pm.ApplicationInfo;
 import android.graphics.Point;
 import android.graphics.Rect;
 import android.hardware.HardwareBuffer;
+import android.os.Handler;
 import android.os.RemoteCallback;
 import android.os.RemoteException;
+import android.provider.Settings;
 import android.testing.AndroidTestingRunner;
+import android.testing.TestableContentResolver;
+import android.testing.TestableContext;
+import android.testing.TestableLooper;
 import android.view.MotionEvent;
 import android.view.RemoteAnimationTarget;
 import android.view.SurfaceControl;
@@ -45,12 +51,14 @@
 import android.window.IOnBackInvokedCallback;
 
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
+import com.android.internal.util.test.FakeSettingsProvider;
 import com.android.wm.shell.TestShellExecutor;
-import com.android.wm.shell.common.ShellExecutor;
 
 import org.junit.Before;
 import org.junit.Ignore;
+import org.junit.Rule;
 import org.junit.Test;
 import org.junit.runner.RunWith;
 import org.mockito.ArgumentCaptor;
@@ -60,14 +68,17 @@
 /**
  * atest WMShellUnitTests:BackAnimationControllerTest
  */
+@TestableLooper.RunWithLooper
 @SmallTest
 @RunWith(AndroidTestingRunner.class)
 public class BackAnimationControllerTest {
 
-    private final ShellExecutor mShellExecutor = new TestShellExecutor();
+    private static final String ANIMATION_ENABLED = "1";
+    private final TestShellExecutor mShellExecutor = new TestShellExecutor();
 
-    @Mock
-    private Context mContext;
+    @Rule
+    public TestableContext mContext =
+            new TestableContext(InstrumentationRegistry.getInstrumentation().getContext());
 
     @Mock
     private SurfaceControl.Transaction mTransaction;
@@ -80,18 +91,32 @@
 
     private BackAnimationController mController;
 
+    private int mEventTime = 0;
+    private TestableContentResolver mContentResolver;
+    private TestableLooper mTestableLooper;
+
     @Before
     public void setUp() throws Exception {
         MockitoAnnotations.initMocks(this);
+        mContext.getApplicationInfo().privateFlags |= ApplicationInfo.PRIVATE_FLAG_PRIVILEGED;
+        mContentResolver = new TestableContentResolver(mContext);
+        mContentResolver.addProvider(Settings.AUTHORITY, new FakeSettingsProvider());
+        Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION,
+                ANIMATION_ENABLED);
+        mTestableLooper = TestableLooper.get(this);
         mController = new BackAnimationController(
-                mShellExecutor, mTransaction, mActivityTaskManager, mContext);
-        mController.setEnableAnimations(true);
+                mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+                mActivityTaskManager, mContext,
+                mContentResolver);
+        mEventTime = 0;
+        mShellExecutor.flushAll();
     }
 
     private void createNavigationInfo(RemoteAnimationTarget topAnimationTarget,
             SurfaceControl screenshotSurface,
             HardwareBuffer hardwareBuffer,
-            int backType) {
+            int backType,
+            IOnBackInvokedCallback onBackInvokedCallback) {
         BackNavigationInfo navigationInfo = new BackNavigationInfo(
                 backType,
                 topAnimationTarget,
@@ -99,7 +124,7 @@
                 hardwareBuffer,
                 new WindowConfiguration(),
                 new RemoteCallback((bundle) -> {}),
-                null);
+                onBackInvokedCallback);
         try {
             doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation();
         } catch (RemoteException ex) {
@@ -124,15 +149,10 @@
     }
 
     private void triggerBackGesture() {
-        MotionEvent event = MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0);
-        mController.onMotionEvent(event, event.getAction(), BackEvent.EDGE_LEFT);
-
-        event = MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0);
-        mController.onMotionEvent(event, event.getAction(), BackEvent.EDGE_LEFT);
-
+        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+        doMotionEvent(MotionEvent.ACTION_MOVE, 0);
         mController.setTriggerBack(true);
-        event = MotionEvent.obtain(10, 0, MotionEvent.ACTION_UP, 100, 100, 0);
-        mController.onMotionEvent(event, event.getAction(), BackEvent.EDGE_LEFT);
+        doMotionEvent(MotionEvent.ACTION_UP, 0);
     }
 
     @Test
@@ -141,11 +161,8 @@
         SurfaceControl screenshotSurface = new SurfaceControl();
         HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
         createNavigationInfo(createAnimationTarget(), screenshotSurface, hardwareBuffer,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY);
-        mController.onMotionEvent(
-                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
-                MotionEvent.ACTION_DOWN,
-                BackEvent.EDGE_LEFT);
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
+        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         verify(mTransaction).setBuffer(screenshotSurface, hardwareBuffer);
         verify(mTransaction).setVisibility(screenshotSurface, true);
         verify(mTransaction).apply();
@@ -157,15 +174,9 @@
         HardwareBuffer hardwareBuffer = mock(HardwareBuffer.class);
         RemoteAnimationTarget animationTarget = createAnimationTarget();
         createNavigationInfo(animationTarget, screenshotSurface, hardwareBuffer,
-                BackNavigationInfo.TYPE_CROSS_ACTIVITY);
-        mController.onMotionEvent(
-                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
-                MotionEvent.ACTION_DOWN,
-                BackEvent.EDGE_LEFT);
-        mController.onMotionEvent(
-                MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
-                MotionEvent.ACTION_MOVE,
-                BackEvent.EDGE_LEFT);
+                BackNavigationInfo.TYPE_CROSS_ACTIVITY, null);
+        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
+        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         // b/207481538, we check that the surface is not moved for now, we can re-enable this once
         // we implement the animation
         verify(mTransaction, never()).setScale(eq(screenshotSurface), anyInt(), anyInt());
@@ -196,30 +207,56 @@
         mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
         RemoteAnimationTarget animationTarget = createAnimationTarget();
         createNavigationInfo(animationTarget, null, null,
-                BackNavigationInfo.TYPE_RETURN_TO_HOME);
+                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);
 
         // Check that back start is dispatched.
-        mController.onMotionEvent(
-                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
-                MotionEvent.ACTION_DOWN,
-                BackEvent.EDGE_LEFT);
+        doMotionEvent(MotionEvent.ACTION_DOWN, 0);
         verify(mIOnBackInvokedCallback).onBackStarted();
 
         // Check that back progress is dispatched.
-        mController.onMotionEvent(
-                MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
-                MotionEvent.ACTION_MOVE,
-                BackEvent.EDGE_LEFT);
+        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
         ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
         verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
         assertEquals(animationTarget, backEventCaptor.getValue().getDepartingAnimationTarget());
 
         // Check that back invocation is dispatched.
         mController.setTriggerBack(true);   // Fake trigger back
-        mController.onMotionEvent(
-                MotionEvent.obtain(0, 0, MotionEvent.ACTION_UP, 0, 0, 0),
-                MotionEvent.ACTION_UP,
-                BackEvent.EDGE_LEFT);
+        doMotionEvent(MotionEvent.ACTION_UP, 0);
         verify(mIOnBackInvokedCallback).onBackInvoked();
     }
+
+    @Test
+    public void animationDisabledFromSettings() throws RemoteException {
+        // Toggle the setting off
+        Settings.Global.putString(mContentResolver, Settings.Global.ENABLE_BACK_ANIMATION, "0");
+        mController = new BackAnimationController(
+                mShellExecutor, new Handler(mTestableLooper.getLooper()), mTransaction,
+                mActivityTaskManager, mContext,
+                mContentResolver);
+        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
+
+        RemoteAnimationTarget animationTarget = createAnimationTarget();
+        IOnBackInvokedCallback appCallback = mock(IOnBackInvokedCallback.class);
+        ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
+        createNavigationInfo(animationTarget, null, null,
+                BackNavigationInfo.TYPE_RETURN_TO_HOME, appCallback);
+
+        triggerBackGesture();
+
+        verify(appCallback, never()).onBackStarted();
+        verify(appCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(appCallback, times(1)).onBackInvoked();
+
+        verify(mIOnBackInvokedCallback, never()).onBackStarted();
+        verify(mIOnBackInvokedCallback, never()).onBackProgressed(backEventCaptor.capture());
+        verify(mIOnBackInvokedCallback, never()).onBackInvoked();
+    }
+
+    private void doMotionEvent(int actionDown, int coordinate) {
+        mController.onMotionEvent(
+                MotionEvent.obtain(0, mEventTime, actionDown, coordinate, coordinate, 0),
+                actionDown,
+                BackEvent.EDGE_LEFT);
+        mEventTime += 10;
+    }
 }
diff --git a/packages/SettingsLib/res/values/strings.xml b/packages/SettingsLib/res/values/strings.xml
index df2685d..a171f86 100644
--- a/packages/SettingsLib/res/values/strings.xml
+++ b/packages/SettingsLib/res/values/strings.xml
@@ -1604,4 +1604,11 @@
     <string name="bt_le_audio_broadcast_dialog_switch_app">Broadcast <xliff:g id="switchApp" example="App Name 2">%1$s</xliff:g></string>
     <!-- [CHAR LIMIT=NONE] Le audio broadcast dialog, different output. -->
     <string name="bt_le_audio_broadcast_dialog_different_output">Change output</string>
+
+    <!-- Developer setting: enable animations when a back gesture is executed [CHAR LIMIT=50] -->
+    <string name="back_navigation_animation">Predictive back animations</string>
+    <!-- Developer setting: enable animations when a back gesture is executed [CHAR LIMIT=150] -->
+    <string name="back_navigation_animation_summary">Enable system animations for predictive back.</string>
+    <!-- Developer setting: enable animations when a back gesture is executed, full explanation[CHAR LIMIT=NONE] -->
+    <string name="back_navigation_animation_dialog">This setting enables system animations for predictive gesture animation. It requires setting per-app "enableOnBackInvokedCallback" to true in the manifest file.</string>
 </resources>
diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
index 4b7d0d2..cce5154 100644
--- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
+++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java
@@ -598,6 +598,7 @@
                     Settings.Global.WATCHDOG_TIMEOUT_MILLIS,
                     Settings.Global.MANAGED_PROVISIONING_DEFER_PROVISIONING_TO_ROLE_HOLDER,
                     Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
+                    Settings.Global.ENABLE_BACK_ANIMATION, // Temporary for T, dev option only
                     Settings.Global.Wearable.BATTERY_SAVER_MODE,
                     Settings.Global.Wearable.COMBINED_LOCATION_ENABLED,
                     Settings.Global.Wearable.HAS_PAY_TOKENS,