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:
@@ -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);
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user