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

Commit bd3af96c authored by mincheli's avatar mincheli
Browse files

Magnification button is movable by dragging the icon

1.The magnification button is movable by dragging the icon.
2.Switching magnification mode by single-tapping the icon.

Bug: 158104770
Test: atest WindowMagnificationManagerTest, atest MagnificationModeSwitchTest
Change-Id: Id0725c2a6f3fce24f1063141f203e73de6db7592
parent 7d3bff08
Loading
Loading
Loading
Loading
+64 −6
Original line number Diff line number Diff line
@@ -20,8 +20,13 @@ import android.annotation.NonNull;
import android.content.Context;
import android.content.pm.ActivityInfo;
import android.graphics.PixelFormat;
import android.graphics.PointF;
import android.provider.Settings;
import android.util.MathUtils;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.WindowManager;
import android.widget.ImageView;

@@ -31,7 +36,8 @@ import com.android.systemui.R;
/**
 * Shows/hides a {@link android.widget.ImageView} on the screen and changes the values of
 * {@link Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE} when the UI is toggled.
 * The button UI would automatically be dismissed after displaying for a period of time.
 * The button icon is movable by dragging. And the button UI would automatically be dismissed after
 * displaying for a period of time.
 */
class MagnificationModeSwitch {

@@ -41,6 +47,10 @@ class MagnificationModeSwitch {
    private final Context mContext;
    private final WindowManager mWindowManager;
    private final ImageView mImageView;
    private final PointF mLastDown = new PointF();
    private final PointF mLastDrag = new PointF();
    private final int mTapTimeout = ViewConfiguration.getTapTimeout();
    private final int mTouchSlop;
    private int mMagnificationMode = Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
    private final WindowManager.LayoutParams mParams;
    private boolean mIsVisible = false;
@@ -56,13 +66,10 @@ class MagnificationModeSwitch {
                Context.WINDOW_SERVICE);
        mParams = createLayoutParams();
        mImageView = imageView;
        mTouchSlop = ViewConfiguration.get(mContext).getScaledTouchSlop();
        applyResourcesValues();
        mImageView.setOnClickListener(
                view -> {
                    removeButton();
                    toggleMagnificationMode();
                });
        mImageView.setImageResource(getIconResId(mMagnificationMode));
        mImageView.setOnTouchListener(this::onTouch);
    }

    private void applyResourcesValues() {
@@ -71,13 +78,59 @@ class MagnificationModeSwitch {
        mImageView.setPadding(padding, padding, padding, padding);
    }

    private boolean onTouch(View v, MotionEvent event) {
        if (!mIsVisible || mImageView == null) {
            return false;
        }
        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());
                return true;
            case MotionEvent.ACTION_MOVE:
                // Move the button position.
                moveButton(event.getRawX() - mLastDrag.x,
                        event.getRawY() - mLastDrag.y);
                mLastDrag.set(event.getRawX(), event.getRawY());
                return true;
            case MotionEvent.ACTION_UP:
                // Single tap to toggle magnification mode and the button position will be reset
                // after the action is performed.
                final float distance = MathUtils.dist(mLastDown.x, mLastDown.y,
                        event.getRawX(), event.getRawY());
                if ((event.getEventTime() - event.getDownTime()) <= mTapTimeout
                        && distance <= mTouchSlop) {
                    handleSingleTap();
                } else {
                    showButton(mMagnificationMode);
                }
                return true;
            case MotionEvent.ACTION_CANCEL:
                showButton(mMagnificationMode);
                return true;
            default:
                return false;
        }
    }

    private void moveButton(float offsetX, float offsetY) {
        mParams.x -= offsetX;
        mParams.y -= offsetY;
        mWindowManager.updateViewLayout(mImageView, mParams);
    }

    void removeButton() {
        if (!mIsVisible) {
            return;
        }
        mImageView.animate().cancel();
        mWindowManager.removeView(mImageView);
        // Reset button status.
        mIsVisible = false;
        mParams.x = 0;
        mParams.y = 0;
    }

    void showButton(int mode) {
@@ -120,6 +173,11 @@ class MagnificationModeSwitch {
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, newMode);
    }

    private void handleSingleTap() {
        removeButton();
        toggleMagnificationMode();
    }

    private static ImageView createView(Context context) {
        ImageView imageView = new ImageView(context);
        imageView.setClickable(true);
+142 −30
Original line number Diff line number Diff line
@@ -18,6 +18,10 @@ package com.android.systemui.accessibility;

import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN;
import static android.provider.Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW;
import static android.view.MotionEvent.ACTION_CANCEL;
import static android.view.MotionEvent.ACTION_DOWN;
import static android.view.MotionEvent.ACTION_MOVE;
import static android.view.MotionEvent.ACTION_UP;

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

@@ -27,6 +31,7 @@ import static org.mockito.ArgumentMatchers.any;
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;
@@ -35,7 +40,9 @@ import android.content.Context;
import android.content.pm.ActivityInfo;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewConfiguration;
import android.view.ViewPropertyAnimator;
import android.view.WindowManager;
import android.widget.ImageView;
@@ -48,35 +55,38 @@ import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.ArgumentCaptor;
import org.mockito.Captor;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.mockito.MockitoAnnotations;

@SmallTest
@RunWith(AndroidTestingRunner.class)
public class MagnificationModeSwitchTest extends SysuiTestCase {

    @Mock
    private ImageView mMockImageView;
    private ImageView mSpyImageView;
    @Mock
    private WindowManager mWindowManager;
    @Mock
    private ViewPropertyAnimator mViewPropertyAnimator;
    private MagnificationModeSwitch mMagnificationModeSwitch;
    @Captor
    private ArgumentCaptor<View.OnTouchListener> mTouchListenerCaptor;

    @Before
    public void setUp() throws Exception {
        MockitoAnnotations.initMocks(this);
        WindowManager wm = mContext.getSystemService(WindowManager.class);
        doAnswer(invocation ->
                wm.getMaximumWindowMetrics()
        ).when(mWindowManager).getMaximumWindowMetrics();
        mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
        mSpyImageView = Mockito.spy(new ImageView(mContext));
        doAnswer(invocation -> null).when(mSpyImageView).setOnTouchListener(
                mTouchListenerCaptor.capture());
        initMockImageViewAndAnimator();

        when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
                mViewPropertyAnimator);

        when(mMockImageView.animate()).thenReturn(mViewPropertyAnimator);

        mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mMockImageView);
        mMagnificationModeSwitch = new MagnificationModeSwitch(mContext, mSpyImageView);
    }

    @Test
@@ -85,7 +95,7 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {

        mMagnificationModeSwitch.removeButton();

        verify(mWindowManager).removeView(mMockImageView);
        verify(mWindowManager).removeView(mSpyImageView);
        // First invocation is in showButton.
        verify(mViewPropertyAnimator, times(2)).cancel();
    }
@@ -94,22 +104,19 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
    public void showWindowModeButton_fullscreenMode_addViewAndSetImageResource() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW);

        verify(mMockImageView).setAlpha(1.0f);
        verify(mMockImageView).setImageResource(
        verify(mSpyImageView).setAlpha(1.0f);
        verify(mSpyImageView).setImageResource(
                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
        verify(mViewPropertyAnimator).cancel();
        verify(mViewPropertyAnimator).setDuration(anyLong());
        verify(mViewPropertyAnimator).setStartDelay(anyLong());
        verify(mViewPropertyAnimator).alpha(anyFloat());
        assertShowButtonAnimation();
        ArgumentCaptor<Runnable> captor = ArgumentCaptor.forClass(Runnable.class);
        verify(mViewPropertyAnimator).withEndAction(captor.capture());
        verify(mWindowManager).addView(eq(mMockImageView), any(WindowManager.LayoutParams.class));
        verify(mWindowManager).addView(eq(mSpyImageView), any(WindowManager.LayoutParams.class));

        captor.getValue().run();

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

    @Test
@@ -117,26 +124,131 @@ public class MagnificationModeSwitchTest extends SysuiTestCase {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
        mMagnificationModeSwitch.onConfigurationChanged(ActivityInfo.CONFIG_DENSITY);

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

    @Test
    public void performClick_fullscreenMode_removeViewAndChangeSettingsValue() {
        ArgumentCaptor<View.OnClickListener> captor = ArgumentCaptor.forClass(
                View.OnClickListener.class);
        verify(mMockImageView).setOnClickListener(captor.capture());
    public void performSingleTap_fullscreenMode_removeViewAndChangeSettingsValue() {
        mMagnificationModeSwitch.showButton(ACCESSIBILITY_MAGNIFICATION_MODE_FULLSCREEN);
        resetMockImageViewAndAnimator();

        captor.getValue().onClick(mMockImageView);
        // Perform a single-tap
        final View.OnTouchListener listener = mTouchListenerCaptor.getValue();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, 0, ACTION_DOWN, 100, 100, 0));
        verify(mViewPropertyAnimator).cancel();

        // First invocation is in showButton.
        verify(mViewPropertyAnimator, times(2)).cancel();
        verify(mMockImageView).setImageResource(
        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_UP, 100, 100, 0));
        verify(mViewPropertyAnimator).cancel();
        verify(mSpyImageView).setImageResource(
                getIconResId(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW));
        verify(mWindowManager).removeView(mMockImageView);
        verify(mWindowManager).removeView(mSpyImageView);
        final int actualMode = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
        assertEquals(ACCESSIBILITY_MAGNIFICATION_MODE_WINDOW, actualMode);
    }

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

        // Perform dragging
        final View.OnTouchListener listener = mTouchListenerCaptor.getValue();
        final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop();
        final int previousMode = Settings.Secure.getInt(mContext.getContentResolver(),
                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(
                0, ViewConfiguration.getTapTimeout(), ACTION_MOVE, 100 + offset, 100, 0));
        verify(mWindowManager).updateViewLayout(eq(mSpyImageView),
                any(WindowManager.LayoutParams.class));

        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout() + 10, ACTION_UP, 100 + offset, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
    }

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

        // Perform single tap
        final View.OnTouchListener listener = mTouchListenerCaptor.getValue();
        final int previousMode = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, 0, ACTION_DOWN, 100, 100, 0));

        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
    }

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

        // Perform dragging
        final View.OnTouchListener listener = mTouchListenerCaptor.getValue();
        final int offset = ViewConfiguration.get(mContext).getScaledTouchSlop();
        final int previousMode = Settings.Secure.getInt(mContext.getContentResolver(),
                Settings.Secure.ACCESSIBILITY_MAGNIFICATION_MODE, 0);
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, 0, ACTION_DOWN, 100, 100, 0));
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_MOVE, 100 + offset, 100, 0));

        resetMockImageViewAndAnimator();
        listener.onTouch(mSpyImageView, MotionEvent.obtain(
                0, ViewConfiguration.getTapTimeout(), ACTION_CANCEL, 100 + offset, 100, 0));
        verify(mSpyImageView).setAlpha(1.0f);
        assertModeUnchanged(previousMode);
        assertShowButtonAnimation();
    }

    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).setStartDelay(anyLong());
        verify(mViewPropertyAnimator).alpha(anyFloat());
        verify(mViewPropertyAnimator).start();
    }

    private void initMockImageViewAndAnimator() {
        when(mViewPropertyAnimator.setDuration(anyLong())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.alpha(anyFloat())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.setStartDelay(anyLong())).thenReturn(mViewPropertyAnimator);
        when(mViewPropertyAnimator.withEndAction(any(Runnable.class))).thenReturn(
                mViewPropertyAnimator);

        when(mSpyImageView.animate()).thenReturn(mViewPropertyAnimator);
    }

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