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

Commit d6518e5a authored by Vadim Caen's avatar Vadim Caen Committed by Automerger Merge Worker
Browse files

Add developer option for back animation DO NOT MERGE am: 94f506aa am: d67d6753

parents 25f03fe9 d67d6753
Loading
Loading
Loading
Loading
+8 −0
Original line number Diff line number Diff line
@@ -15108,6 +15108,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";
+50 −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,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<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 @@ public class BackAnimationController implements RemoteCallable<BackAnimationCont
    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 @@ 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
@@ -55,6 +55,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;
@@ -735,11 +736,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
@@ -43,6 +43,7 @@ android_test {
        "truth-prebuilt",
        "testables",
        "platform-test-annotations",
        "frameworks-base-testutils",
    ],

    libs: [
+80 −43
Original line number Diff line number Diff line
@@ -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<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
        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, 0, MotionEvent.ACTION_UP, 0, 0, 0),
                MotionEvent.ACTION_UP,
                MotionEvent.obtain(0, mEventTime, actionDown, coordinate, coordinate, 0),
                actionDown,
                BackEvent.EDGE_LEFT);
        verify(mIOnBackInvokedCallback).onBackInvoked();
        mEventTime += 10;
    }
}
Loading