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
This commit is contained in:
mincheli
2020-07-29 14:24:37 +08:00
parent 7d3bff08a8
commit bd3af96cb7
2 changed files with 206 additions and 36 deletions

View File

@@ -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);

View File

@@ -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();
}
}