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

Commit 5d7e9fab authored by mincheli's avatar mincheli
Browse files

Magnification button applies fade-in and fade-out animation

We use runnable task to handle the UI animation to prevent the case
that "Disable animation" settings disables all the animation showing.
The default showing duration of magnification button is 3 secs and
the fade-in & fade-out animation durations are both 300ms.
The showing step: 1.fade-in > 2.show magnification button > 3.fade-out
And the demo video is attached in the bug

Bug: 168867867
Test: atest MagnificationModeSwitchTest
Change-Id: If2d9a93987555b947e36e09b5345aaf588c02927
parent 700fd875
Loading
Loading
Loading
Loading
+32 −14
Original line number Diff line number Diff line
@@ -46,9 +46,15 @@ import com.android.systemui.R;
 */
class MagnificationModeSwitch {

    private static final int DURATION_MS = 5000;
    private static final int START_DELAY_MS = 3000;
    private final Runnable mAnimationTask;
    @VisibleForTesting
    static final long FADING_ANIMATION_DURATION_MS = 300;
    private static final int DEFAULT_FADE_OUT_ANIMATION_DELAY_MS = 3000;
    // The button visible duration starting from the last showButton() called.
    private int mVisibleDuration = DEFAULT_FADE_OUT_ANIMATION_DELAY_MS;
    private final Runnable mFadeInAnimationTask;
    private final Runnable mFadeOutAnimationTask;
    @VisibleForTesting
    boolean mIsFadeOutAnimating = false;

    private final Context mContext;
    private final WindowManager mWindowManager;
@@ -100,12 +106,19 @@ class MagnificationModeSwitch {
            }
        });

        mAnimationTask = () -> {
        mFadeInAnimationTask = () -> {
            mImageView.animate()
                    .alpha(1f)
                    .setDuration(FADING_ANIMATION_DURATION_MS)
                    .start();
        };
        mFadeOutAnimationTask = () -> {
            mImageView.animate()
                    .alpha(0f)
                    .setDuration(DURATION_MS)
                    .setDuration(FADING_ANIMATION_DURATION_MS)
                    .withEndAction(() -> removeButton())
                    .start();
            mIsFadeOutAnimating = true;
        };
    }

@@ -128,7 +141,6 @@ class MagnificationModeSwitch {
        }
        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mImageView.setAlpha(1.0f);
                mImageView.animate().cancel();
                mLastDown.set(event.getRawX(), event.getRawY());
                mLastDrag.set(event.getRawX(), event.getRawY());
@@ -169,9 +181,13 @@ class MagnificationModeSwitch {
        if (!mIsVisible) {
            return;
        }
        // Reset button status.
        mImageView.removeCallbacks(mFadeInAnimationTask);
        mImageView.removeCallbacks(mFadeOutAnimationTask);
        mImageView.animate().cancel();
        mIsFadeOutAnimating = false;
        mImageView.setAlpha(0f);
        mWindowManager.removeView(mImageView);
        // Reset button status.
        mIsVisible = false;
        mParams.x = 0;
        mParams.y = 0;
@@ -185,14 +201,15 @@ class MagnificationModeSwitch {
        if (!mIsVisible) {
            mWindowManager.addView(mImageView, mParams);
            mIsVisible = true;
            mImageView.postOnAnimation(mFadeInAnimationTask);
        }
        mImageView.setAlpha(1.0f);
        // TODO(b/143852371): use accessibility timeout as a delay.
        // Dismiss the magnification switch button after the button is displayed for a period of
        // time.
        if (mIsFadeOutAnimating) {
            mImageView.animate().cancel();
        mImageView.removeCallbacks(mAnimationTask);
        mImageView.postDelayed(mAnimationTask, START_DELAY_MS);
            mImageView.setAlpha(1f);
        }
        // Refresh the time slot of the fade-out task whenever this method is called.
        mImageView.removeCallbacks(mFadeOutAnimationTask);
        mImageView.postOnAnimationDelayed(mFadeOutAnimationTask, mVisibleDuration);
    }

    void onConfigurationChanged(int configDiff) {
@@ -222,6 +239,7 @@ class MagnificationModeSwitch {
        imageView.setClickable(true);
        imageView.setFocusable(true);
        imageView.setScaleType(ImageView.ScaleType.CENTER_INSIDE);
        imageView.setAlpha(0f);
        return imageView;
    }

+53 −38
Original line number Diff line number Diff line
@@ -24,6 +24,7 @@ import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;
import static android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction.ACTION_CLICK;

import static com.android.systemui.accessibility.MagnificationModeSwitch.FADING_ANIMATION_DURATION_MS;
import static com.android.systemui.accessibility.MagnificationModeSwitch.getIconResId;

import static junit.framework.Assert.assertEquals;
@@ -35,7 +36,6 @@ import static org.mockito.ArgumentMatchers.anyFloat;
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.Mockito.doAnswer;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;

@@ -69,6 +69,9 @@ import org.mockito.MockitoAnnotations;
@RunWith(AndroidTestingRunner.class)
public class MagnificationModeSwitchTest extends SysuiTestCase {

    private static final float FADE_IN_ALPHA = 1f;
    private static final float FADE_OUT_ALPHA = 0f;

    private ImageView mSpyImageView;
    @Mock
    private WindowManager mWindowManager;
@@ -87,49 +90,47 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        ).when(mWindowManager).getMaximumWindowMetrics();
        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
        mSpyImageView = Mockito.spy(new ImageView(mContext));
        doAnswer(invocation -> null).when(mSpyImageView).setOnTouchListener(
                mTouchListenerCaptor.capture());
        initMockImageViewAndAnimator();
        resetMockImageViewAndAnimator();

        mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView);
    }

    @Test
    public void removeButton_removeView() {
    public void removeButton_buttonIsShowing_removeView() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);

        mMagnificationModeSwitch.removeButton();

        verify(mWindowManager).removeView(mSpyImageView);
        // First invocation is in showButton.
        verify(mViewPropertyAnimator, times(2)).cancel();
        verify(mViewPropertyAnimator).cancel();
    }

    @Test
    public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);

        verify(mSpyImageView).setAlpha(1.0f);
        verify(mSpyImageView).setImageResource(
                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
        assertShowButtonAnimation();
        assertShowFadingAnimation(FADE_IN_ALPHA);
        assertShowFadingAnimation(FADE_OUT_ALPHA);

        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
        verify(mViewPropertyAnimator).withEndAction(captor.capture());
        verify(mWindowManager).addView(eq(mSpyImageView), any(WindowManager.LayoutParams.class));

        captor.getValue().run();

        // First invocation is in showButton.
        verify(mViewPropertyAnimator, times(2)).cancel();
        verify(mViewPropertyAnimator).cancel();
        verify(mWindowManager).removeView(mSpyImageView);
    }

    @Test
    public void onConfigurationChanged_setImageResource() {
    public void onConfigurationChanged_buttonIsShowing_setImageResource() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
        resetMockImageViewAndAnimator();

        mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);

        verify(mSpyImageView, times(2)).setImageResource(
        verify(mSpyImageView).setImageResource(
                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN));
    }

@@ -162,7 +163,6 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, 0, ACTION_DOWN, 100, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        verify(mViewPropertyAnimator).cancel();

        listener.onTouch(mSpyImageView, MotionEvent.obtain(
@@ -173,9 +173,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout() + 10, ACTION_UP, 100 + offset, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
        assertShowFadingAnimation(FADE_OUT_ALPHA);
    }

    @Test
@@ -193,9 +192,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
        assertShowFadingAnimation(FADE_OUT_ALPHA);
    }

    @Test
@@ -216,9 +214,8 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100 + offset, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
        assertShowFadingAnimation(FADE_OUT_ALPHA);
    }

    @Test
@@ -249,37 +246,55 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        verifyTapAction(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
    }

    @Test
    public void showButton_showFadeOutAnimation_fadeOutAnimationCanceled() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
        assertShowFadingAnimation(FADE_OUT_ALPHA);
        resetMockImageViewAndAnimator();

        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);

        verify(mViewPropertyAnimator).cancel();
        assertEquals(1f, mSpyImageView.getAlpha());
        assertShowFadingAnimation(FADE_OUT_ALPHA);
    }

    private void assertModeUnchanged(int expectedMode) {
        final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
        assertEquals(expectedMode, actualMode);
    }

    private void assertShowButtonAnimation() {
        verify(mViewPropertyAnimator).cancel();
        verify(mViewPropertyAnimator).setDuration(anyLong());
        verify(mViewPropertyAnimator).alpha(anyFloat());
    private void assertShowFadingAnimation(float alpha) {
        ArgumentCaptor<Runnable> runnableCaptor = ArgumentCaptor.forClass(Runnable.class);
        if (alpha == FADE_IN_ALPHA) { // Fade-in
            verify(mSpyImageView).postOnAnimation(runnableCaptor.capture());
        } else { // Fade-out
            verify(mSpyImageView).postOnAnimationDelayed(runnableCaptor.capture(), anyLong());
        }
        resetMockAnimator();

        runnableCaptor.getValue().run();

        verify(mViewPropertyAnimator).setDuration(eq(FADING_ANIMATION_DURATION_MS));
        verify(mViewPropertyAnimator).alpha(alpha);
        verify(mViewPropertyAnimator).start();
    }

    private void initMockImageViewAndAnimator() {
    private void resetMockImageViewAndAnimator() {
        Mockito.reset(mSpyImageView);
        doAnswer(invocation -> null).when(mSpyImageView).setOnTouchListener(
                mTouchListenerCaptor.capture());
        resetMockAnimator();
    }

    private void resetMockAnimator() {
        Mockito.reset(mViewPropertyAnimator);
        when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
                mViewPropertyAnimator);

        when(mSpyImageView.animate()).thenReturn(mViewPropertyAnimator);
        doAnswer(invocation -> {
            Runnable run = invocation.getArgument(0);
            run.run();
            return null;
        }).when(mSpyImageView).postDelayed(any(), anyLong());
    }

    private void resetMockImageViewAndAnimator() {
        Mockito.reset(mViewPropertyAnimator);
        Mockito.reset(mSpyImageView);
        initMockImageViewAndAnimator();
    }

    /**