diff --git a/core/java/android/provider/Settings.java b/core/java/android/provider/Settings.java index 731b5315c7fbb..2255bf5775d96 100644 --- a/core/java/android/provider/Settings.java +++ b/core/java/android/provider/Settings.java @@ -15020,6 +15020,14 @@ public final class Settings { */ 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 ced36a705df27..c3fbe55436300 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.annotation.Nullable; 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.annotations.VisibleForTesting; 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 { 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 @@ public class BackAnimationController implements RemoteCallable 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 fb53e5355c4ac..a899709c1405b 100644 --- a/libs/WindowManager/Shell/tests/unittest/Android.bp +++ b/libs/WindowManager/Shell/tests/unittest/Android.bp @@ -43,6 +43,7 @@ android_test { "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 a905dcaebc6b8..6cf88297f3b24 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.atLeastOnce; 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.BackNavigationInfo; 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 @@ import org.mockito.MockitoAnnotations; /** * 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 @@ public class BackAnimationControllerTest { 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 @@ public class BackAnimationControllerTest { hardwareBuffer, new WindowConfiguration(), new RemoteCallback((bundle) -> {}), - null); + onBackInvokedCallback); try { doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation(); } catch (RemoteException ex) { @@ -124,15 +149,10 @@ public class BackAnimationControllerTest { } 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 @@ public class BackAnimationControllerTest { 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 @@ public class BackAnimationControllerTest { 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 @@ public class BackAnimationControllerTest { 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 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 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 df2685db17f54..a171f86dd2176 100644 --- a/packages/SettingsLib/res/values/strings.xml +++ b/packages/SettingsLib/res/values/strings.xml @@ -1604,4 +1604,11 @@ Broadcast %1$s Change output + + + Predictive back animations + + Enable system animations for predictive back. + + This setting enables system animations for predictive gesture animation. It requires setting per-app "enableOnBackInvokedCallback" to true in the manifest file. diff --git a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java index 4b7d0d2a3c742..cce515444c1f3 100644 --- a/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java +++ b/packages/SettingsProvider/test/src/android/provider/SettingsBackupTest.java @@ -598,6 +598,7 @@ public class SettingsBackupTest { 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,