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

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

Merge changes I723b58a0,I536b0f86 into main

* changes:
  fix(fullscreen magnification): Fix action in configuration changes
  fix(fullscreen magnification): Add animation to the orange border
parents 23efadc2 dc1335dc
Loading
Loading
Loading
Loading
+157 −20
Original line number Diff line number Diff line
@@ -18,8 +18,15 @@ package com.android.systemui.accessibility;

import static android.view.WindowManager.LayoutParams;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.annotation.UiContext;
import android.content.ComponentCallbacks;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.content.res.Configuration;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.Region;
@@ -30,56 +37,123 @@ import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.AccelerateDecelerateInterpolator;
import android.view.animation.Interpolator;

import androidx.annotation.NonNull;
import androidx.annotation.UiThread;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.dagger.qualifiers.Main;
import com.android.systemui.res.R;

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

class FullscreenMagnificationController {
class FullscreenMagnificationController implements ComponentCallbacks {

    private final Context mContext;
    private final AccessibilityManager mAccessibilityManager;
    private final WindowManager mWindowManager;
    private Supplier<SurfaceControlViewHost> mScvhSupplier;
    private SurfaceControlViewHost mSurfaceControlViewHost;
    private SurfaceControlViewHost mSurfaceControlViewHost = null;
    private SurfaceControl mBorderSurfaceControl = null;
    private Rect mWindowBounds;
    private SurfaceControl.Transaction mTransaction;
    private View mFullscreenBorder = null;
    private int mBorderOffset;
    private final int mDisplayId;
    private static final Region sEmptyRegion = new Region();
    private ValueAnimator mShowHideBorderAnimator;
    private Executor mExecutor;
    private boolean mFullscreenMagnificationActivated = false;
    private final Configuration mConfiguration;

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

    @VisibleForTesting
    FullscreenMagnificationController(
            @UiContext Context context,
            @Main Executor executor,
            AccessibilityManager accessibilityManager,
            WindowManager windowManager,
            Supplier<SurfaceControlViewHost> scvhSupplier,
            SurfaceControl.Transaction transaction,
            ValueAnimator valueAnimator) {
        mContext = context;
        mExecutor = executor;
        mAccessibilityManager = accessibilityManager;
        mWindowManager = windowManager;
        mWindowBounds = mWindowManager.getCurrentWindowMetrics().getBounds();
        mTransaction = new SurfaceControl.Transaction();
        mTransaction = transaction;
        mScvhSupplier = scvhSupplier;
        mBorderOffset = mContext.getResources().getDimensionPixelSize(
                R.dimen.magnifier_border_width_fullscreen_with_offset)
                - mContext.getResources().getDimensionPixelSize(
                R.dimen.magnifier_border_width_fullscreen);
        mDisplayId = mContext.getDisplayId();
        mConfiguration = new Configuration(context.getResources().getConfiguration());
        mShowHideBorderAnimator = valueAnimator;
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
                if (isReverse) {
                    // The animation was played in reverse, which means we are hiding the border.
                    // We would like to perform clean up after the border is fully hidden.
                    cleanUpBorder();
                }
            }
        });
    }

    private static ValueAnimator createNullTargetObjectAnimator(Context context) {
        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);
        return valueAnimator;
    }

    /**
     * Check the fullscreen magnification activation status, and proceed corresponding actions when
     * there is an activation change.
     */
    @UiThread
    void onFullscreenMagnificationActivationChanged(boolean activated) {
        final boolean changed = (mFullscreenMagnificationActivated != activated);
        if (changed) {
            mFullscreenMagnificationActivated = activated;
            if (activated) {
                createFullscreenMagnificationBorder();
            } else {
                removeFullscreenMagnificationBorder();
            }
        }
    }

    /**
     * This method should only be called when fullscreen magnification is changed from activated
     * to inactivated.
     */
    @UiThread
    private void removeFullscreenMagnificationBorder() {
        mContext.unregisterComponentCallbacks(this);
        mShowHideBorderAnimator.reverse();
    }

    private void cleanUpBorder() {
        if (mSurfaceControlViewHost != null) {
            mSurfaceControlViewHost.release();
            mSurfaceControlViewHost = null;
@@ -91,31 +165,57 @@ class FullscreenMagnificationController {
    }

    /**
     * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
     * and set negative offset to the border view to fill up the spaces between the border and the
     * device corners.
     * This method should only be called when fullscreen magnification is changed from inactivated
     * to activated.
     */
    @UiThread
    private void createFullscreenMagnificationBorder() {
        onConfigurationChanged(mContext.getResources().getConfiguration());
        mContext.registerComponentCallbacks(this);

        if (mSurfaceControlViewHost == null) {
            // Create the view only if it does not exist yet. If we are trying to enable fullscreen
            // magnification before it was fully disabled, we use the previous view instead of
            // creating a new one.
            mFullscreenBorder = LayoutInflater.from(mContext)
                    .inflate(R.layout.fullscreen_magnification_border, null);
            // Set the initial border view alpha manually so we won't show the border accidentally
            // after we apply show() to the SurfaceControl and before the animation starts to run.
            mFullscreenBorder.setAlpha(0f);
            mShowHideBorderAnimator.setTarget(mFullscreenBorder);
            mSurfaceControlViewHost = mScvhSupplier.get();
            mSurfaceControlViewHost.setView(mFullscreenBorder, getBorderLayoutParams());

        SurfaceControl surfaceControl = mSurfaceControlViewHost
                .getSurfacePackage().getSurfaceControl();
            mBorderSurfaceControl = mSurfaceControlViewHost.getSurfacePackage().getSurfaceControl();
        }

        mTransaction
                .setPosition(surfaceControl, -mBorderOffset, -mBorderOffset)
                .setLayer(surfaceControl, Integer.MAX_VALUE)
                .show(surfaceControl)
                .addTransactionCommittedListener(
                        mExecutor,
                        () -> {
                            if (mShowHideBorderAnimator.isRunning()) {
                                // Since the method is only called when there is an activation
                                // status change, the running animator is hiding the border.
                                mShowHideBorderAnimator.reverse();
                            } else {
                                mShowHideBorderAnimator.start();
                            }
                        })
                .setPosition(mBorderSurfaceControl, -mBorderOffset, -mBorderOffset)
                .setLayer(mBorderSurfaceControl, Integer.MAX_VALUE)
                .show(mBorderSurfaceControl)
                .apply();

        mAccessibilityManager.attachAccessibilityOverlayToDisplay(mDisplayId, surfaceControl);
        mAccessibilityManager.attachAccessibilityOverlayToDisplay(
                mDisplayId, mBorderSurfaceControl);

        applyTouchableRegion();
    }

    /**
     * Since the device corners are not perfectly rounded, we would like to create a thick stroke,
     * and set negative offset to the border view to fill up the spaces between the border and the
     * device corners.
     */
    private LayoutParams getBorderLayoutParams() {
        LayoutParams params =  new LayoutParams(
                mWindowBounds.width() + 2 * mBorderOffset,
@@ -137,4 +237,41 @@ class FullscreenMagnificationController {
        // all touch events to go through this view.
        surfaceControl.setTouchableRegion(sEmptyRegion);
    }

    @Override
    public void onConfigurationChanged(@NonNull Configuration newConfig) {
        final int configDiff = newConfig.diff(mConfiguration);
        mConfiguration.setTo(newConfig);
        onConfigurationChanged(configDiff);
    }

    @VisibleForTesting
    void onConfigurationChanged(int configDiff) {
        boolean reCreateWindow = false;
        if ((configDiff & ActivityInfo.CONFIG_DENSITY) != 0
                || (configDiff & ActivityInfo.CONFIG_SCREEN_SIZE) != 0
                || (configDiff & ActivityInfo.CONFIG_ORIENTATION) != 0) {
            updateDimensions();
            mWindowBounds.set(mWindowManager.getCurrentWindowMetrics().getBounds());
            reCreateWindow = true;
        }

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

    private void updateDimensions() {
        mBorderOffset = mContext.getResources().getDimensionPixelSize(
                R.dimen.magnifier_border_width_fullscreen_with_offset)
                - mContext.getResources().getDimensionPixelSize(
                        R.dimen.magnifier_border_width_fullscreen);
    }

    @Override
    public void onLowMemory() {

    }
}
+10 −5
Original line number Diff line number Diff line
@@ -54,6 +54,7 @@ import com.android.systemui.statusbar.CommandQueue;
import com.android.systemui.util.settings.SecureSettings;

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

import javax.inject.Inject;
@@ -71,6 +72,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
    private final ModeSwitchesController mModeSwitchesController;
    private final Context mContext;
    private final Handler mHandler;
    private final Executor mExecutor;
    private final AccessibilityManager mAccessibilityManager;
    private final CommandQueue mCommandQueue;
    private final OverviewProxyService mOverviewProxyService;
@@ -139,12 +141,13 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
            DisplayIdIndexSupplier<FullscreenMagnificationController> {

        private final Context mContext;
        private final Executor mExecutor;

        FullscreenMagnificationControllerSupplier(Context context, Handler handler,
                DisplayManager displayManager, SysUiState sysUiState,
                SecureSettings secureSettings) {
        FullscreenMagnificationControllerSupplier(Context context, DisplayManager displayManager,
                Executor executor) {
            super(displayManager);
            mContext = context;
            mExecutor = executor;
        }

        @Override
@@ -156,6 +159,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
            windowContext.setTheme(com.android.systemui.res.R.style.Theme_SystemUI);
            return new FullscreenMagnificationController(
                    windowContext,
                    mExecutor,
                    windowContext.getSystemService(AccessibilityManager.class),
                    windowContext.getSystemService(WindowManager.class),
                    scvhSupplier);
@@ -200,13 +204,14 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
    DisplayIdIndexSupplier<MagnificationSettingsController> mMagnificationSettingsSupplier;

    @Inject
    public Magnification(Context context, @Main Handler mainHandler,
    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) {
        mContext = context;
        mHandler = mainHandler;
        mExecutor = executor;
        mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
        mCommandQueue = commandQueue;
        mModeSwitchesController = modeSwitchesController;
@@ -218,7 +223,7 @@ public class Magnification implements CoreStartable, CommandQueue.Callbacks {
                mHandler, mWindowMagnifierCallback,
                displayManager, sysUiState, secureSettings);
        mFullscreenMagnificationControllerSupplier = new FullscreenMagnificationControllerSupplier(
                context, mHandler, displayManager, sysUiState, secureSettings);
                context, displayManager, mExecutor);
        mMagnificationSettingsSupplier = new SettingsSupplier(context,
                mMagnificationSettingsControllerCallback, displayManager, secureSettings);

+171 −22
Original line number Diff line number Diff line
@@ -16,49 +16,84 @@

package com.android.systemui.accessibility;

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 org.junit.Assert.assertTrue;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ObjectAnimator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.Rect;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.SurfaceControl;
import android.view.SurfaceControlViewHost;
import android.view.View;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityManager;
import android.view.animation.DecelerateInterpolator;
import android.view.animation.Interpolator;
import android.window.InputTransferToken;

import androidx.annotation.NonNull;
import androidx.test.filters.SmallTest;

import com.android.systemui.SysuiTestCase;
import com.android.systemui.res.R;

import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

@SmallTest
@TestableLooper.RunWithLooper
@RunWith(AndroidTestingRunner.class)
public class FullscreenMagnificationControllerTest extends SysuiTestCase {

    private static final long ANIMATION_DURATION_MS = 100L;
    private static final long WAIT_TIMEOUT_S = 5L * HW_TIMEOUT_MULTIPLIER;
    private static final long ANIMATION_TIMEOUT_MS =
            5L * ANIMATION_DURATION_MS * HW_TIMEOUT_MULTIPLIER;
    private FullscreenMagnificationController mFullscreenMagnificationController;
    private SurfaceControlViewHost mSurfaceControlViewHost;
    private ValueAnimator mShowHideBorderAnimator;
    private SurfaceControl.Transaction mTransaction;
    private TestableWindowManager mWindowManager;

    @Before
    public void setUp() {
        getInstrumentation().runOnMainSync(() -> mSurfaceControlViewHost =
                new SurfaceControlViewHost(mContext, mContext.getDisplay(),
                        new InputTransferToken(), "FullscreenMagnification"));

                spy(new SurfaceControlViewHost(mContext, mContext.getDisplay(),
                        new InputTransferToken(), "FullscreenMagnification")));
        Supplier<SurfaceControlViewHost> scvhSupplier = () -> mSurfaceControlViewHost;
        final WindowManager wm = mContext.getSystemService(WindowManager.class);
        mWindowManager = new TestableWindowManager(wm);
        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);

        mTransaction = new SurfaceControl.Transaction();
        mShowHideBorderAnimator = spy(newNullTargetObjectAnimator());
        mFullscreenMagnificationController = new FullscreenMagnificationController(
                mContext,
                mContext.getMainExecutor(),
                mContext.getSystemService(AccessibilityManager.class),
                mContext.getSystemService(WindowManager.class),
                scvhSupplier);
                scvhSupplier,
                mTransaction,
                mShowHideBorderAnimator);
    }

    @After
@@ -69,29 +104,143 @@ public class FullscreenMagnificationControllerTest extends SysuiTestCase {
    }

    @Test
    public void onFullscreenMagnificationActivationChange_activated_visibleBorder() {
        getInstrumentation().runOnMainSync(
                () -> mFullscreenMagnificationController
                        .onFullscreenMagnificationActivationChanged(true)
        );

        // Wait for Rects updated.
        waitForIdleSync();
    public void enableFullscreenMagnification_visibleBorder() throws InterruptedException {
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch animationEndLatch = new CountDownLatch(1);
        mTransaction.addTransactionCommittedListener(
                Runnable::run, transactionCommittedLatch::countDown);
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animationEndLatch.countDown();
            }
        });
        getInstrumentation().runOnMainSync(() ->
                //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));
        verify(mShowHideBorderAnimator).start();
        assertThat(mSurfaceControlViewHost.getView().isVisibleToUser()).isTrue();
    }

    @Test
    public void onFullscreenMagnificationActivationChange_deactivated_invisibleBorder() {
        getInstrumentation().runOnMainSync(
                () -> {
    public void disableFullscreenMagnification_reverseAnimationAndReleaseScvh()
            throws InterruptedException {
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch enableAnimationEndLatch = new CountDownLatch(1);
        CountDownLatch disableAnimationEndLatch = new CountDownLatch(1);
        mTransaction.addTransactionCommittedListener(
                Runnable::run, transactionCommittedLatch::countDown);
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
                if (isReverse) {
                    disableAnimationEndLatch.countDown();
                } else {
                    enableAnimationEndLatch.countDown();
                }
            }
        });
        getInstrumentation().runOnMainSync(() ->
                //Enable fullscreen magnification
                mFullscreenMagnificationController
                            .onFullscreenMagnificationActivationChanged(true);
                        .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));
        verify(mShowHideBorderAnimator).start();

        getInstrumentation().runOnMainSync(() ->
                // Disable fullscreen magnification
                mFullscreenMagnificationController
                            .onFullscreenMagnificationActivationChanged(false);
                        .onFullscreenMagnificationActivationChanged(false));

        assertTrue("Failed to wait for disabling animation to be finished",
                disableAnimationEndLatch.await(
                        ANIMATION_TIMEOUT_MS, TimeUnit.MILLISECONDS));
        verify(mShowHideBorderAnimator).reverse();
        verify(mSurfaceControlViewHost).release();
    }
        );

        assertThat(mSurfaceControlViewHost.getView()).isNull();
    @Test
    public void onFullscreenMagnificationActivationChangeTrue_deactivating_reverseAnimator()
            throws InterruptedException {
        // Simulate the hiding border animation is running
        when(mShowHideBorderAnimator.isRunning()).thenReturn(true);
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch animationEndLatch = new CountDownLatch(1);
        mTransaction.addTransactionCommittedListener(
                Runnable::run, transactionCommittedLatch::countDown);
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animationEndLatch.countDown();
            }
        });

        getInstrumentation().runOnMainSync(
                () -> 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));
        verify(mShowHideBorderAnimator).reverse();
    }

    @Test
    public void onScreenSizeChanged_activated_borderChangedToExpectedSize()
            throws InterruptedException {
        CountDownLatch transactionCommittedLatch = new CountDownLatch(1);
        CountDownLatch animationEndLatch = new CountDownLatch(1);
        mTransaction.addTransactionCommittedListener(
                Runnable::run, transactionCommittedLatch::countDown);
        mShowHideBorderAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                animationEndLatch.countDown();
            }
        });
        getInstrumentation().runOnMainSync(() ->
                //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));
        final Rect testWindowBounds = new Rect(
                mWindowManager.getCurrentWindowMetrics().getBounds());
        testWindowBounds.set(testWindowBounds.left, testWindowBounds.top,
                testWindowBounds.right + 100, testWindowBounds.bottom + 100);
        mWindowManager.setWindowBounds(testWindowBounds);

        getInstrumentation().runOnMainSync(() ->
                mFullscreenMagnificationController.onConfigurationChanged(
                        ActivityInfo.CONFIG_SCREEN_SIZE));

        int borderOffset = mContext.getResources().getDimensionPixelSize(
                R.dimen.magnifier_border_width_fullscreen_with_offset)
                - mContext.getResources().getDimensionPixelSize(
                R.dimen.magnifier_border_width_fullscreen);
        final int newWidth = testWindowBounds.width() + 2 * borderOffset;
        final int newHeight = testWindowBounds.height() + 2 * borderOffset;
        verify(mSurfaceControlViewHost).relayout(newWidth, newHeight);
    }

    private ValueAnimator newNullTargetObjectAnimator() {
        final ValueAnimator animator =
                ObjectAnimator.ofFloat(/* target= */ null, View.ALPHA, 0f, 1f);
        Interpolator interpolator = new DecelerateInterpolator(2.5f);
        animator.setInterpolator(interpolator);
        animator.setDuration(ANIMATION_DURATION_MS);
        return animator;
    }
}
+1 −1

File changed.

Preview size limit exceeded, changes collapsed.

+2 −1

File changed.

Preview size limit exceeded, changes collapsed.