Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 0cfb9315 authored by Vadim Caen's avatar Vadim Caen
Browse files

Add developer option for back animation

Bug: 228936326
Test: BackAnimationControllerTest#animationDisabledFromSettings
Change-Id: I857a480e7375cada9171712d737162c7876c087e
Merged-In: I857a480e7375cada9171712d737162c7876c087e
parent 6957e3ff
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -15181,6 +15181,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";
+51 −15
Original line number Diff line number Diff line
@@ -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,28 +48,34 @@ 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<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;

    /**
     * Max duration to wait for a transition to finish before accepting another gesture start
     * request.
     */
    private static final long MAX_TRANSITION_DURATION = 2000;

    private final AtomicBoolean mEnableAnimations = new AtomicBoolean(false);

    /**
     * Location of the initial touch event of the back gesture.
     */
@@ -100,21 +112,50 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    };

    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() {
@@ -364,12 +405,7 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    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) {
+4 −2
Original line number Diff line number Diff line
@@ -53,6 +53,7 @@ import com.android.wm.shell.common.SystemWindows;
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;
@@ -695,11 +696,12 @@ public abstract class WMShellBaseModule {
    @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();
    }
+1 −0
Original line number Diff line number Diff line
@@ -44,6 +44,7 @@ android_test {
        "truth-prebuilt",
        "testables",
        "platform-test-annotations",
        "frameworks-base-testutils",
    ],

    libs: [
+83 −44
Original line number Diff line number Diff line
@@ -27,18 +27,24 @@ import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.never;
import static org.mockito.Mockito.reset;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;

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;
@@ -47,11 +53,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 org.junit.Before;
import org.junit.Ignore;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
@@ -61,14 +70,18 @@ import org.mockito.MockitoAnnotations;
/**
 * atest WMShellUnitTests:BackAnimationControllerTest
 */
@TestableLooper.RunWithLooper
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class BackAnimationControllerTest {

    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;
@@ -81,18 +94,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,
@@ -100,7 +127,7 @@ public class BackAnimationControllerTest {
                hardwareBuffer,
                new WindowConfiguration(),
                new RemoteCallback((bundle) -> {}),
                null);
                onBackInvokedCallback);
        try {
            doReturn(navigationInfo).when(mActivityTaskManager).startBackNavigation();
        } catch (RemoteException ex) {
@@ -125,15 +152,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
@@ -142,11 +164,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();
@@ -158,15 +177,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());
@@ -197,18 +210,12 @@ public class BackAnimationControllerTest {
        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
        RemoteAnimationTarget animationTarget = createAnimationTarget();
        createNavigationInfo(animationTarget, null, null,
                BackNavigationInfo.TYPE_RETURN_TO_HOME);
                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);

        mController.onMotionEvent(
                MotionEvent.obtain(0, 0, MotionEvent.ACTION_DOWN, 0, 0, 0),
                MotionEvent.ACTION_DOWN,
                BackEvent.EDGE_LEFT);
        doMotionEvent(MotionEvent.ACTION_DOWN, 0);

        // Check that back start and progress is dispatched when first move.
        mController.onMotionEvent(
                MotionEvent.obtain(10, 0, MotionEvent.ACTION_MOVE, 100, 100, 0),
                MotionEvent.ACTION_MOVE,
                BackEvent.EDGE_LEFT);
        doMotionEvent(MotionEvent.ACTION_MOVE, 100);
        verify(mIOnBackInvokedCallback).onBackStarted();
        ArgumentCaptor<BackEvent> backEventCaptor = ArgumentCaptor.forClass(BackEvent.class);
        verify(mIOnBackInvokedCallback).onBackProgressed(backEventCaptor.capture());
@@ -216,10 +223,7 @@ public class BackAnimationControllerTest {

        // 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();
    }

@@ -228,7 +232,7 @@ public class BackAnimationControllerTest {
        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
        RemoteAnimationTarget animationTarget = createAnimationTarget();
        createNavigationInfo(animationTarget, null, null,
                BackNavigationInfo.TYPE_RETURN_TO_HOME);
                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);

        triggerBackGesture();
        // Check that back invocation is dispatched.
@@ -261,7 +265,7 @@ public class BackAnimationControllerTest {
        mController.setBackToLauncherCallback(mIOnBackInvokedCallback);
        RemoteAnimationTarget animationTarget = createAnimationTarget();
        createNavigationInfo(animationTarget, null, null,
                BackNavigationInfo.TYPE_RETURN_TO_HOME);
                BackNavigationInfo.TYPE_RETURN_TO_HOME, null);

        triggerBackGesture();
        reset(mIOnBackInvokedCallback);
@@ -278,4 +282,39 @@ public class BackAnimationControllerTest {
                BackEvent.EDGE_LEFT);
        verify(mIOnBackInvokedCallback).onBackStarted();
    }

    @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;
    }
}
Loading