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

Commit 82954473 authored by Candice Lo's avatar Candice Lo Committed by Android (Google) Code Review
Browse files

Merge "fix(fullscreen border): Watch rotation and hide the border before rotation" into main

parents 254568d7 18293a96
Loading
Loading
Loading
Loading
+93 −9
Original line number Diff line number Diff line
@@ -30,7 +30,12 @@ import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
import android.os.Handler;
import android.util.Log;
import android.view.AttachedSurfaceControl;
import android.view.Display;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.LayoutInflater;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
@@ -46,15 +51,18 @@ import androidx.annotation.UiThread;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;
import com.android.systemui.util.leak.RotationUtils;

import java.util.concurrent.Executor;
import java.util.function.Supplier;

class FullscreenMagnificationController implements ComponentCallbacks {

    private static final String TAG = "FullscreenMagnificationController";
    private final Context mContext;
    private final AccessibilityManager mAccessibilityManager;
    private final WindowManager mWindowManager;
    private final IWindowManager mIWindowManager;
    private Supplier<SurfaceControlViewHost> mScvhSupplier;
    private SurfaceControlViewHost mSurfaceControlViewHost = null;
    private SurfaceControl mBorderSurfaceControl = null;
@@ -65,33 +73,50 @@ class FullscreenMagnificationController implements ComponentCallbacks {
    private final int mDisplayId;
    private static final Region sEmptyRegion = new Region();
    private ValueAnimator mShowHideBorderAnimator;
    private Handler mHandler;
    private Executor mExecutor;
    private boolean mFullscreenMagnificationActivated = false;
    private final Configuration mConfiguration;
    private final Runnable mShowBorderRunnable = this::showBorderWithNullCheck;
    private int mRotation;
    private final IRotationWatcher mRotationWatcher = new IRotationWatcher.Stub() {
        @Override
        public void onRotationChanged(final int rotation) {
            handleScreenRotation();
        }
    };
    private final long mLongAnimationTimeMs;

    FullscreenMagnificationController(
            @UiContext Context context,
            Executor executor,
            @Main Handler handler,
            @Main Executor executor,
            AccessibilityManager accessibilityManager,
            WindowManager windowManager,
            IWindowManager iWindowManager,
            Supplier<SurfaceControlViewHost> scvhSupplier) {
        this(context, executor, accessibilityManager, windowManager, scvhSupplier,
                new SurfaceControl.Transaction(), createNullTargetObjectAnimator(context));
        this(context, handler, executor, accessibilityManager,
                windowManager, iWindowManager, scvhSupplier,
                new SurfaceControl.Transaction(), null);
    }

    @VisibleForTesting
    FullscreenMagnificationController(
            @UiContext Context context,
            @Main Handler handler,
            @Main Executor executor,
            AccessibilityManager accessibilityManager,
            WindowManager windowManager,
            IWindowManager iWindowManager,
            Supplier<SurfaceControlViewHost> scvhSupplier,
            SurfaceControl.Transaction transaction,
            ValueAnimator valueAnimator) {
        mContext = context;
        mHandler = handler;
        mExecutor = executor;
        mAccessibilityManager = accessibilityManager;
        mWindowManager = windowManager;
        mIWindowManager = iWindowManager;
        mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
        mTransaction = transaction;
        mScvhSupplier = scvhSupplier;
@@ -101,7 +126,10 @@ class FullscreenMagnificationController implements ComponentCallbacks {
                R.dimen.magnifier_border_width_fullscreen);
        mDisplayId = mContext.getDisplayId();
        mConfiguration = new Configuration(context.getResources().getConfiguration());
        mShowHideBorderAnimator = valueAnimator;
        mLongAnimationTimeMs = mContext.getResources().getInteger(
                com.android.internal.R.integer.config_longAnimTime);
        mShowHideBorderAnimator = (valueAnimator == null)
                ? createNullTargetObjectAnimator() : valueAnimator;
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
@@ -114,15 +142,13 @@ class FullscreenMagnificationController implements ComponentCallbacks {
        });
    }

    private static ValueAnimator createNullTargetObjectAnimator(Context context) {
    private ValueAnimator createNullTargetObjectAnimator() {
        final ValueAnimator valueAnimator =
                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
        Interpolator interpolator = new AccelerateDecelerateInterpolator();
        final long longAnimationDuration = context.getResources().getInteger(
                com.android.internal.R.integer.config_longAnimTime);

        valueAnimator.setInterpolator(interpolator);
        valueAnimator.setDuration(longAnimationDuration);
        valueAnimator.setDuration(mLongAnimationTimeMs);
        return valueAnimator;
    }

@@ -149,7 +175,11 @@ class FullscreenMagnificationController implements ComponentCallbacks {
     */
    @UiThread
    private void removeFullscreenMagnificationBorder() {
        if (mHandler.hasCallbacks(mShowBorderRunnable)) {
            mHandler.removeCallbacks(mShowBorderRunnable);
        }
        mContext.unregisterComponentCallbacks(this);

        mShowHideBorderAnimator.reverse();
    }

@@ -161,6 +191,11 @@ class FullscreenMagnificationController implements ComponentCallbacks {

        if (mFullscreenBorder != null) {
            mFullscreenBorder = null;
            try {
                mIWindowManager.removeRotationWatcher(mRotationWatcher);
            } catch (Exception e) {
                Log.w(TAG, "Failed to remove rotation watcher", e);
            }
        }
    }

@@ -186,6 +221,11 @@ class FullscreenMagnificationController implements ComponentCallbacks {
            mSurfaceControlViewHost = mScvhSupplier.get();
            mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());
            mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
            try {
                mIWindowManager.watchRotation(mRotationWatcher, Display.DEFAULT_DISPLAY);
            } catch (Exception e) {
                Log.w(TAG, "Failed to register rotation watcher", e);
            }
        }

        mTransaction
@@ -256,11 +296,55 @@ class FullscreenMagnificationController implements ComponentCallbacks {
            reCreateWindow = true;
        }

        if (mFullscreenBorder != null && reCreateWindow) {
        if (mFullscreenBorder == null) {
            return;
        }

        if (reCreateWindow) {
            final int newWidth = mWindowBounds.width() + 2 * mBorderOffset;
            final int newHeight = mWindowBounds.height() + 2 * mBorderOffset;
            mSurfaceControlViewHost.relayout(newWidth, newHeight);
        }

        // Rotating from Landscape to ReverseLandscape will not trigger the config changes in
        // CONFIG_SCREEN_SIZE and CONFIG_ORIENTATION. Therefore, we would like to check the device
        // rotation separately.
        // Since there's a possibility that {@link onConfigurationChanged} comes before
        // {@link onRotationChanged}, we would like to handle screen rotation in either case that
        // happens earlier.
        int newRotation = RotationUtils.getRotation(mContext);
        if (newRotation != mRotation) {
            mRotation = newRotation;
            handleScreenRotation();
        }
    }

    private boolean isActivated() {
        return mFullscreenBorder != null;
    }

    private void handleScreenRotation() {
        if (!isActivated()) {
            return;
        }

        if (mHandler.hasCallbacks(mShowBorderRunnable)) {
            mHandler.removeCallbacks(mShowBorderRunnable);
        }

        // We hide the border immediately as early as possible to beat the redrawing of window
        // in response to the orientation change so users won't see a weird shape border.
        mHandler.postAtFrontOfQueue(() -> {
            mFullscreenBorder.setAlpha(0f);
        });

        mHandler.postDelayed(mShowBorderRunnable, mLongAnimationTimeMs);
    }

    private void showBorderWithNullCheck() {
        if (mShowHideBorderAnimator != null) {
            mShowHideBorderAnimator.start();
        }
    }

    private void updateDimensions() {
+19 −7
Original line number Diff line number Diff line
@@ -34,6 +34,7 @@ import android.os.Looper;
import android.os.Message;
import android.util.SparseArray;
import android.view.Display;
import android.view.IWindowManager;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.WindowManager;
@@ -148,13 +149,19 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
            DisplayIdIndexSupplier<FullscreenMagnificationController> {

        private final Context mContext;
        private final Handler mHandler;
        private final Executor mExecutor;
        private final IWindowManager mIWindowManager;

        FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
                Executor executor) {
        FullscreenMagnificationControllerSupplier(Context context,
                DisplayManager displayManager,
                Handler handler,
                Executor executor, IWindowManager iWindowManager) {
            super(displayManager);
            mContext = context;
            mHandler = handler;
            mExecutor = executor;
            mIWindowManager = iWindowManager;
        }

        @Override
@@ -166,9 +173,11 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
            return new FullscreenMagnificationController(
                    windowContext,
                    mHandler,
                    mExecutor,
                    windowContext.getSystemService(AccessibilityManager.class),
                    windowContext.getSystemService(WindowManager.class),
                    mIWindowManager,
                    scvhSupplier);
        }
    }
@@ -211,14 +220,16 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
    DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;

    @Inject
    public Magnification(Context context, @Main Handler mainHandler, @Main Executor executor,
    public Magnification(Context context,
            @Main Handler mainHandler, @Main Executor executor,
            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
            SysUiState sysUiState, OverviewProxyService overviewProxyService,
            SecureSettings secureSettings, DisplayTracker displayTracker,
            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
            DisplayManager displayManager, AccessibilityLogger a11yLogger,
            IWindowManager iWindowManager) {
        this(context, mainHandler.getLooper(), executor, commandQueue,
                modeSwitchesController, sysUiState, overviewProxyService, secureSettings,
                displayTracker, displayManager, a11yLogger);
                displayTracker, displayManager, a11yLogger, iWindowManager);
    }

    @VisibleForTesting
@@ -226,7 +237,8 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
            CommandQueue commandQueue, ModeSwitchesController modeSwitchesController,
            SysUiState sysUiState, OverviewProxyService overviewProxyService,
            SecureSettings secureSettings, DisplayTracker displayTracker,
            DisplayManager displayManager, AccessibilityLogger a11yLogger) {
            DisplayManager displayManager, AccessibilityLogger a11yLogger,
            IWindowManager iWindowManager) {
        mContext = context;
        mHandler = new Handler(looper) {
            @Override
@@ -248,7 +260,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
                mHandler, mWindowMagnifierCallback,
                displayManager, sysUiState, secureSettings);
        mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
                context, displayManager, mExecutor);
                context, displayManager, mHandler, mExecutor, iWindowManager);
        mMagnificationSettingsSupplier = new SettingsSupplier(context,
                mMagnificationSettingsControllerCallback, displayManager, secureSettings);

+47 −23
Original line number Diff line number Diff line
@@ -21,8 +21,10 @@ import static android.os.Build.HW_TIMEOUT_MULTIPLIER;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;

import static com.google.common.truth.Truth.assertThat;
import static com.google.common.truth.Truth.assertWithMessage;

import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
@@ -34,8 +36,12 @@ import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.os.RemoteException;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.IRotationWatcher;
import android.view.IWindowManager;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
@@ -55,6 +61,8 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
@@ -73,9 +81,12 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
    private ValueAnimator mShowHideBorderAnimator;
    private SurfaceControl.Transaction mTransaction;
    private TestableWindowManager mWindowManager;
    @Mock
    private IWindowManager mIWindowManager;

    @Before
    public void setUp() {
        MockitoAnnotations.initMocks(this);
        getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
                spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
                        new InputTransferToken(), "FullscreenMagnification")));
@@ -88,9 +99,11 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
        mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
        mFullscreenMagnificationController = new FullscreenMagnificationController(
                mContext,
                mContext.getMainThreadHandler(),
                mContext.getMainExecutor(),
                mContext.getSystemService(AccessibilityManager.class),
                mContext.getSystemService(WindowManager.class),
                mIWindowManager,
                scvhSupplier,
                mTransaction,
                mShowHideBorderAnimator);
@@ -104,7 +117,8 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
    }

    @Test
    public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
    public void enableFullscreenMagnification_visibleBorder()
            throws InterruptedException, RemoteException {
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch animationEndLatch = new CountDownLatch(1);
        mTransaction.addTransactionCommittedListener(
@@ -119,17 +133,21 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
                //Enable fullscreen magnification
                mFullscreenMagnificationController
                        .onFullscreenMagnificationActivationChanged(true));
        assertTrue("Failed to wait for transaction committed",
                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
        assertTrue("Failed to wait for animation to be finished",
                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        assertWithMessage("Failed to wait for transaction committed")
                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
                .isTrue();
        assertWithMessage("Failed to wait for animation to be finished")
                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
                .isTrue();
        verify(mShowHideBorderAnimator).start();
        verify(mIWindowManager)
                .watchRotation(any(IRotationWatcher.class), eq(Display.DEFAULT_DISPLAY));
        assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
    }

    @Test
    public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
            throws InterruptedException {
            throws InterruptedException, RemoteException {
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
        CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
@@ -149,11 +167,12 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
                //Enable fullscreen magnification
                mFullscreenMagnificationController
                        .onFullscreenMagnificationActivationChanged(true));
        assertTrue("Failed to wait for transaction committed",
                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
        assertTrue("Failed to wait for enabling animation to be finished",
                enableAnimationEndLatch.await(
                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        assertWithMessage("Failed to wait for transaction committed")
                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
                .isTrue();
        assertWithMessage("Failed to wait for enabling animation to be finished")
                .that(enableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
                .isTrue();
        verify(mShowHideBorderAnimator).start();

        getInstrumentation().runOnMainSync(() ->
@@ -161,11 +180,12 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
                mFullscreenMagnificationController
                        .onFullscreenMagnificationActivationChanged(false));

        assertTrue("Failed to wait for disabling animation to be finished",
                disableAnimationEndLatch.await(
                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        assertWithMessage("Failed to wait for disabling animation to be finished")
                .that(disableAnimationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
                .isTrue();
        verify(mShowHideBorderAnimator).reverse();
        verify(mSurfaceControlViewHost).release();
        verify(mIWindowManager).removeRotationWatcher(any(IRotationWatcher.class));
    }

    @Test
@@ -188,10 +208,12 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
                () -> mFullscreenMagnificationController
                            .onFullscreenMagnificationActivationChanged(true));

        assertTrue("Failed to wait for transaction committed",
                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
        assertTrue("Failed to wait for animation to be finished",
                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        assertWithMessage("Failed to wait for transaction committed")
                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
                .isTrue();
        assertWithMessage("Failed to wait for animation to be finished")
                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
                        .isTrue();
        verify(mShowHideBorderAnimator).reverse();
    }

@@ -212,10 +234,12 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
                //Enable fullscreen magnification
                mFullscreenMagnificationController
                        .onFullscreenMagnificationActivationChanged(true));
        assertTrue("Failed to wait for transaction committed",
                transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS));
        assertTrue("Failed to wait for animation to be finished",
                animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        assertWithMessage("Failed to wait for transaction committed")
                .that(transactionCommittedLatch.await(WAIT_TIMEOUT_S, TimeUnit.SECONDS))
                .isTrue();
        assertWithMessage("Failed to wait for animation to be finished")
                .that(animationEndLatch.await(ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS))
                .isTrue();
        final Rect testWindowBounds = new Rect(
                mWindowManager.getCurrentWindowMetrics().getBounds());
        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
+6 −2
Original line number Diff line number Diff line
@@ -39,6 +39,7 @@ import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IMagnificationConnection;
import android.view.accessibility.IMagnificationConnectionCallback;
@@ -99,6 +100,8 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
    private SecureSettings mSecureSettings;
    @Mock
    private AccessibilityLogger mA11yLogger;
    @Mock
    private IWindowManager mIWindowManager;

    private IMagnificationConnection mIMagnificationConnection;
    private Magnification mMagnification;
@@ -117,9 +120,10 @@ public class IMagnificationConnectionTest extends SysuiTestCase {
        mTestableLooper = TestableLooper.get(this);
        assertNotNull(mTestableLooper);
        mMagnification = new Magnification(getContext(),
                mTestableLooper.getLooper(), getContext().getMainExecutor(), mCommandQueue,
                mTestableLooper.getLooper(), mContext.getMainExecutor(), mCommandQueue,
                mModeSwitchesController, mSysUiState, mOverviewProxyService, mSecureSettings,
                mDisplayTracker, getContext().getSystemService(DisplayManager.class), mA11yLogger);
                mDisplayTracker, getContext().getSystemService(DisplayManager.class),
                mA11yLogger, mIWindowManager);
        mMagnification.mWindowMagnificationControllerSupplier =
                new FakeWindowMagnificationControllerSupplier(
                        mContext.getSystemService(DisplayManager.class));
+5 −2
Original line number Diff line number Diff line
@@ -42,6 +42,7 @@ import android.hardware.display.DisplayManager;
import android.os.RemoteException;
import android.testing.TestableLooper;
import android.view.Display;
import android.view.IWindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.accessibility.IMagnificationConnection;
import android.view.accessibility.IMagnificationConnectionCallback;
@@ -93,6 +94,8 @@ public class MagnificationTest extends SysuiTestCase {
    private MagnificationSettingsController mMagnificationSettingsController;
    @Mock
    private AccessibilityLogger mA11yLogger;
    @Mock
    private IWindowManager mIWindowManager;

    @Before
    public void setUp() throws Exception {
@@ -122,10 +125,10 @@ public class MagnificationTest extends SysuiTestCase {

        mCommandQueue = new CommandQueue(getContext(), mDisplayTracker);
        mMagnification = new Magnification(getContext(),
                getContext().getMainThreadHandler(), getContext().getMainExecutor(),
                getContext().getMainThreadHandler(), mContext.getMainExecutor(),
                mCommandQueue, mModeSwitchesController,
                mSysUiState, mOverviewProxyService, mSecureSettings, mDisplayTracker,
                getContext().getSystemService(DisplayManager.class), mA11yLogger);
                getContext().getSystemService(DisplayManager.class), mA11yLogger, mIWindowManager);
        mMagnification.mWindowMagnificationControllerSupplier = new FakeControllerSupplier(
                mContext.getSystemService(DisplayManager.class), mWindowMagnificationController);
        mMagnification.mMagnificationSettingsSupplier = new FakeSettingsSupplier(