Merge "Maintain the position of accessibility floating menu" into sc-dev am: f57262c38d am: 7ae0698c58

Original change: https://googleplex-android-review.googlesource.com/c/platform/frameworks/base/+/14427396

Change-Id: Id3d4d1c31a5f3b3431bcec97e96b05ee29592bdc
This commit is contained in:
Jason Hsu
2021-06-03 16:19:36 +00:00
committed by Automerger Merge Worker
10 changed files with 442 additions and 218 deletions

View File

@@ -74,7 +74,8 @@ public final class Prefs {
Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
Key.HAS_SEEN_REVERSE_BOTTOM_SHEET, Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT, Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
Key.ACCESSIBILITY_FLOATING_MENU_POSITION
}) })
// TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
public @interface Key { public @interface Key {
@@ -125,6 +126,7 @@ public final class Prefs {
String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
String HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP = String HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP =
"HasSeenAccessibilityFloatingMenuDockTooltip"; "HasSeenAccessibilityFloatingMenuDockTooltip";
String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition";
} }
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {

View File

@@ -28,12 +28,16 @@ import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MEN
import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;
import android.annotation.FloatRange;
import android.content.Context; import android.content.Context;
import android.database.ContentObserver; import android.database.ContentObserver;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.UserHandle; import android.os.UserHandle;
import android.provider.Settings; import android.provider.Settings;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import com.android.internal.annotations.VisibleForTesting; import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs; import com.android.systemui.Prefs;
@@ -44,7 +48,13 @@ import com.android.systemui.Prefs;
public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1; private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1;
private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0; private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0;
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_OPACITY_VALUE = 0.55f; private static final float DEFAULT_OPACITY_VALUE = 0.55f;
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
@FloatRange(from = 0.0, to = 1.0)
private static final float DEFAULT_POSITION_Y_PERCENT = 0.8f;
private final Context mContext; private final Context mContext;
private final AccessibilityFloatingMenuView mMenuView; private final AccessibilityFloatingMenuView mMenuView;
private final MigrationTooltipView mMigrationTooltipView; private final MigrationTooltipView mMigrationTooltipView;
@@ -85,7 +95,10 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
}; };
public AccessibilityFloatingMenu(Context context) { public AccessibilityFloatingMenu(Context context) {
this(context, new AccessibilityFloatingMenuView(context)); mContext = context;
mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
mDockTooltipView = new DockTooltipView(mContext, mMenuView);
} }
@VisibleForTesting @VisibleForTesting
@@ -113,7 +126,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
getOpacityValue(mContext)); getOpacityValue(mContext));
mMenuView.setSizeType(getSizeType(mContext)); mMenuView.setSizeType(getSizeType(mContext));
mMenuView.setShapeType(getShapeType(mContext)); mMenuView.setShapeType(getShapeType(mContext));
mMenuView.setOnDragEndListener(this::showDockTooltipIfNecessary); mMenuView.setOnDragEndListener(this::onDragEnd);
showMigrationTooltipIfNecessary(); showMigrationTooltipIfNecessary();
@@ -127,12 +140,25 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
} }
mMenuView.hide(); mMenuView.hide();
mMenuView.setOnDragEndListener(null);
mMigrationTooltipView.hide(); mMigrationTooltipView.hide();
mDockTooltipView.hide(); mDockTooltipView.hide();
unregisterContentObservers(); unregisterContentObservers();
} }
@NonNull
private Position getPosition(Context context) {
final String absolutePositionString = Prefs.getString(context,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);
if (TextUtils.isEmpty(absolutePositionString)) {
return new Position(DEFAULT_POSITION_X_PERCENT, DEFAULT_POSITION_Y_PERCENT);
} else {
return Position.fromString(absolutePositionString);
}
}
// Migration tooltip was the android S feature. It's just used on the Android version from R // Migration tooltip was the android S feature. It's just used on the Android version from R
// to S. In addition, it only shows once. // to S. In addition, it only shows once.
private void showMigrationTooltipIfNecessary() { private void showMigrationTooltipIfNecessary() {
@@ -150,18 +176,28 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1; DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
} }
private void onDragEnd(Position position) {
savePosition(mContext, position);
showDockTooltipIfNecessary(mContext);
}
private void savePosition(Context context, Position position) {
Prefs.putString(context, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
position.toString());
}
/** /**
* Shows tooltip when user drags accessibility floating menu for the first time. * Shows tooltip when user drags accessibility floating menu for the first time.
*/ */
private void showDockTooltipIfNecessary() { private void showDockTooltipIfNecessary(Context context) {
if (!Prefs.get(mContext).getBoolean( if (!Prefs.get(context).getBoolean(
HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, false)) { HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, false)) {
// if the menu is an oval, the user has already dragged it out, so show the tooltip. // if the menu is an oval, the user has already dragged it out, so show the tooltip.
if (mMenuView.isOvalShape()) { if (mMenuView.isOvalShape()) {
mDockTooltipView.show(); mDockTooltipView.show();
} }
Prefs.putBoolean(mContext, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true); Prefs.putBoolean(context, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true);
} }
} }

View File

@@ -26,6 +26,7 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator; import android.animation.Animator;
import android.animation.AnimatorListenerAdapter; import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator; import android.animation.ValueAnimator;
import android.annotation.FloatRange;
import android.annotation.IntDef; import android.annotation.IntDef;
import android.content.Context; import android.content.Context;
import android.content.pm.ActivityInfo; import android.content.pm.ActivityInfo;
@@ -85,7 +86,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private static final int FADE_EFFECT_DURATION_MS = 3000; private static final int FADE_EFFECT_DURATION_MS = 3000;
private static final int SNAP_TO_LOCATION_DURATION_MS = 150; private static final int SNAP_TO_LOCATION_DURATION_MS = 150;
private static final int MIN_WINDOW_Y = 0; private static final int MIN_WINDOW_Y = 0;
private static final float LOCATION_Y_PERCENTAGE = 0.8f;
private static final int ANIMATION_START_OFFSET = 600; private static final int ANIMATION_START_OFFSET = 600;
private static final int ANIMATION_DURATION_MS = 600; private static final int ANIMATION_DURATION_MS = 600;
@@ -97,7 +97,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private boolean mIsDragging = false; private boolean mIsDragging = false;
private boolean mImeVisibility; private boolean mImeVisibility;
@Alignment @Alignment
private int mAlignment = Alignment.RIGHT; private int mAlignment;
@SizeType @SizeType
private int mSizeType = SizeType.SMALL; private int mSizeType = SizeType.SMALL;
@VisibleForTesting @VisibleForTesting
@@ -105,7 +105,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
int mShapeType = ShapeType.OVAL; int mShapeType = ShapeType.OVAL;
private int mTemporaryShapeType; private int mTemporaryShapeType;
@RadiusType @RadiusType
private int mRadiusType = RadiusType.LEFT_HALF_OVAL; private int mRadiusType;
private int mMargin; private int mMargin;
private int mPadding; private int mPadding;
private int mScreenHeight; private int mScreenHeight;
@@ -118,7 +118,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
private int mRelativeToPointerDownX; private int mRelativeToPointerDownX;
private int mRelativeToPointerDownY; private int mRelativeToPointerDownY;
private float mRadius; private float mRadius;
private float mPercentageY = LOCATION_Y_PERCENTAGE; private final Position mPosition;
private float mSquareScaledTouchSlop; private float mSquareScaledTouchSlop;
private final Configuration mLastConfiguration; private final Configuration mLastConfiguration;
private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty(); private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty();
@@ -182,25 +182,35 @@ public class AccessibilityFloatingMenuView extends FrameLayout
interface OnDragEndListener { interface OnDragEndListener {
/** /**
* Invoked when the floating menu has dragged end. * Called when a drag is completed.
*
* @param position Stores information about the position
*/ */
void onDragEnd(); void onDragEnd(Position position);
} }
public AccessibilityFloatingMenuView(Context context) { public AccessibilityFloatingMenuView(Context context, @NonNull Position position) {
this(context, new RecyclerView(context)); this(context, position, new RecyclerView(context));
} }
@VisibleForTesting @VisibleForTesting
AccessibilityFloatingMenuView(Context context, AccessibilityFloatingMenuView(Context context, @NonNull Position position,
RecyclerView listView) { RecyclerView listView) {
super(context); super(context);
mListView = listView; mListView = listView;
mWindowManager = context.getSystemService(WindowManager.class); mWindowManager = context.getSystemService(WindowManager.class);
mCurrentLayoutParams = createDefaultLayoutParams();
mAdapter = new AccessibilityTargetAdapter(mTargets); mAdapter = new AccessibilityTargetAdapter(mTargets);
mUiHandler = createUiHandler(); mUiHandler = createUiHandler();
mPosition = position;
mAlignment = transformToAlignment(mPosition.getPercentageX());
mRadiusType = (mAlignment == Alignment.RIGHT)
? RadiusType.LEFT_HALF_OVAL
: RadiusType.RIGHT_HALF_OVAL;
updateDimensions();
mCurrentLayoutParams = createDefaultLayoutParams();
mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue); mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS); mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
@@ -213,10 +223,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
mDragAnimator.addListener(new AnimatorListenerAdapter() { mDragAnimator.addListener(new AnimatorListenerAdapter() {
@Override @Override
public void onAnimationEnd(Animator animation) { public void onAnimationEnd(Animator animation) {
mAlignment = calculateCurrentAlignment(); mPosition.update(transformCurrentPercentageXToEdge(),
mPercentageY = calculateCurrentPercentageY(); calculateCurrentPercentageY());
mAlignment = transformToAlignment(mPosition.getPercentageX());
updateLocationWith(mAlignment, mPercentageY); updateLocationWith(mPosition);
updateInsetWith(getResources().getConfiguration().uiMode, mAlignment); updateInsetWith(getResources().getConfiguration().uiMode, mAlignment);
@@ -227,13 +238,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout
fadeOut(); fadeOut();
mOnDragEndListener.ifPresent(OnDragEndListener::onDragEnd); mOnDragEndListener.ifPresent(
onDragEndListener -> onDragEndListener.onDragEnd(mPosition));
} }
}); });
mLastConfiguration = new Configuration(getResources().getConfiguration()); mLastConfiguration = new Configuration(getResources().getConfiguration());
updateDimensions();
initListView(); initListView();
updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment); updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
} }
@@ -423,7 +434,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateRadiusWith(newSizeType, mRadiusType, mTargets.size()); updateRadiusWith(newSizeType, mRadiusType, mTargets.size());
// When the icon sized changed, the menu size and location will be impacted. // When the icon sized changed, the menu size and location will be impacted.
updateLocationWith(mAlignment, mPercentageY); updateLocationWith(mPosition);
updateScrollModeWith(hasExceededMaxLayoutHeight()); updateScrollModeWith(hasExceededMaxLayoutHeight());
updateOffsetWith(mShapeType, mAlignment); updateOffsetWith(mShapeType, mAlignment);
setSystemGestureExclusion(); setSystemGestureExclusion();
@@ -446,14 +457,14 @@ public class AccessibilityFloatingMenuView extends FrameLayout
fadeOut(); fadeOut();
} }
public void setOnDragEndListener(OnDragEndListener onDragListener) { public void setOnDragEndListener(OnDragEndListener onDragEndListener) {
mOnDragEndListener = Optional.ofNullable(onDragListener); mOnDragEndListener = Optional.ofNullable(onDragEndListener);
} }
void startTranslateXAnimation() { void startTranslateXAnimation() {
fadeIn(); fadeIn();
final float toXValue = mAlignment == Alignment.RIGHT final float toXValue = (mAlignment == Alignment.RIGHT)
? ANIMATION_TO_X_VALUE ? ANIMATION_TO_X_VALUE
: -ANIMATION_TO_X_VALUE; : -ANIMATION_TO_X_VALUE;
final TranslateAnimation animation = final TranslateAnimation animation =
@@ -581,7 +592,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
final boolean currentImeVisibility = insets.isVisible(ime()); final boolean currentImeVisibility = insets.isVisible(ime());
if (currentImeVisibility != mImeVisibility) { if (currentImeVisibility != mImeVisibility) {
mImeVisibility = currentImeVisibility; mImeVisibility = currentImeVisibility;
updateLocationWith(mAlignment, mPercentageY); updateLocationWith(mPosition);
} }
return insets; return insets;
@@ -697,8 +708,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
params.receiveInsetsIgnoringZOrder = true; params.receiveInsetsIgnoringZOrder = true;
params.windowAnimations = android.R.style.Animation_Translucent; params.windowAnimations = android.R.style.Animation_Translucent;
params.gravity = Gravity.START | Gravity.TOP; params.gravity = Gravity.START | Gravity.TOP;
params.x = getMaxWindowX(); params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
params.y = (int) (getMaxWindowY() * mPercentageY); // params.y = (int) (mPosition.getPercentageY() * getMaxWindowY());
final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
params.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
updateAccessibilityTitle(params); updateAccessibilityTitle(params);
return params; return params;
} }
@@ -716,7 +729,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
updateItemViewWith(mSizeType); updateItemViewWith(mSizeType);
updateColor(); updateColor();
updateStrokeWith(newConfig.uiMode, mAlignment); updateStrokeWith(newConfig.uiMode, mAlignment);
updateLocationWith(mAlignment, mPercentageY); updateLocationWith(mPosition);
updateRadiusWith(mSizeType, mRadiusType, mTargets.size()); updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
updateScrollModeWith(hasExceededMaxLayoutHeight()); updateScrollModeWith(hasExceededMaxLayoutHeight());
setSystemGestureExclusion(); setSystemGestureExclusion();
@@ -765,9 +778,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
/** /**
* Updates the floating menu to be fixed at the side of the screen. * Updates the floating menu to be fixed at the side of the screen.
*/ */
private void updateLocationWith(@Alignment int side, float percentageCurrentY) { private void updateLocationWith(Position position) {
mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX(); final @Alignment int alignment = transformToAlignment(position.getPercentageX());
final int currentLayoutY = (int) (percentageCurrentY * getMaxWindowY()); mCurrentLayoutParams.x = (alignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
final int currentLayoutY = (int) (position.getPercentageY() * getMaxWindowY());
mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval()); mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
mWindowManager.updateViewLayout(this, mCurrentLayoutParams); mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
} }
@@ -861,10 +875,17 @@ public class AccessibilityFloatingMenuView extends FrameLayout
} }
@Alignment @Alignment
private int calculateCurrentAlignment() { private int transformToAlignment(@FloatRange(from = 0.0, to = 1.0) float percentageX) {
return mCurrentLayoutParams.x >= ((getMinWindowX() + getMaxWindowX()) / 2) return (percentageX < 0.5f) ? Alignment.LEFT : Alignment.RIGHT;
? Alignment.RIGHT }
: Alignment.LEFT;
private float transformCurrentPercentageXToEdge() {
final float percentageX = calculateCurrentPercentageX();
return (percentageX < 0.5) ? 0.0f : 1.0f;
}
private float calculateCurrentPercentageX() {
return mCurrentLayoutParams.x / (float) getMaxWindowX();
} }
private float calculateCurrentPercentageY() { private float calculateCurrentPercentageY() {

View File

@@ -0,0 +1,83 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.accessibility.floatingmenu;
import android.annotation.FloatRange;
import android.text.TextUtils;
/**
* Stores information about the position, which includes percentage of X-axis of the screen,
* percentage of Y-axis of the screen.
*/
public class Position {
private static final char STRING_SEPARATOR = ',';
private static final TextUtils.SimpleStringSplitter sStringCommaSplitter =
new TextUtils.SimpleStringSplitter(STRING_SEPARATOR);
private float mPercentageX;
private float mPercentageY;
/**
* Creates a {@link Position} from a encoded string described in {@link #toString()}.
*
* @param positionString A string conform to the format described in {@link #toString()}
* @return A {@link Position} with the given value retrieved from {@code absolutePositionString}
* @throws IllegalArgumentException If {@code positionString} does not conform to the format
* described in {@link #toString()}
*/
public static Position fromString(String positionString) {
sStringCommaSplitter.setString(positionString);
if (sStringCommaSplitter.hasNext()) {
final float percentageX = Float.parseFloat(sStringCommaSplitter.next());
final float percentageY = Float.parseFloat(sStringCommaSplitter.next());
return new Position(percentageX, percentageY);
}
throw new IllegalArgumentException(
"Invalid Position string: " + positionString);
}
Position(float percentageX, float percentageY) {
update(percentageX, percentageY);
}
@Override
public String toString() {
return mPercentageX + ", " + mPercentageY;
}
/**
* Updates the position with {@code percentageX} and {@code percentageY}.
*
* @param percentageX the new percentage of X-axis of the screen, from 0.0 to 1.0.
* @param percentageY the new percentage of Y-axis of the screen, from 0.0 to 1.0.
*/
public void update(@FloatRange(from = 0.0, to = 1.0) float percentageX,
@FloatRange(from = 0.0, to = 1.0) float percentageY) {
mPercentageX = percentageX;
mPercentageY = percentageY;
}
public float getPercentageX() {
return mPercentageX;
}
public float getPercentageY() {
return mPercentageY;
}
}

View File

@@ -22,7 +22,6 @@ import static com.google.common.truth.Truth.assertThat;
import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn; import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import android.content.Context; import android.content.Context;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
@@ -31,15 +30,16 @@ import android.view.accessibility.AccessibilityManager;
import androidx.test.filters.SmallTest; import androidx.test.filters.SmallTest;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase; import com.android.systemui.SysuiTestCase;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -50,6 +50,9 @@ import java.util.List;
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
public class AccessibilityFloatingMenuTest extends SysuiTestCase { public class AccessibilityFloatingMenuTest extends SysuiTestCase {
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
@Mock @Mock
private AccessibilityManager mAccessibilityManager; private AccessibilityManager mAccessibilityManager;
@@ -58,18 +61,14 @@ public class AccessibilityFloatingMenuTest extends SysuiTestCase {
@Before @Before
public void initMenu() { public void initMenu() {
MockitoAnnotations.initMocks(this);
final List<AccessibilityTarget> mTargets = new ArrayList<>();
mTargets.add(mock(AccessibilityTarget.class));
final List<String> assignedTargets = new ArrayList<>(); final List<String> assignedTargets = new ArrayList<>();
mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME); assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets( doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
anyInt()); anyInt());
mMenuView = new AccessibilityFloatingMenuView(mContext); final Position position = new Position(0, 0);
mMenuView = new AccessibilityFloatingMenuView(mContext, position);
mMenu = new AccessibilityFloatingMenu(mContext, mMenuView); mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
} }

View File

@@ -39,7 +39,6 @@ import android.content.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Insets; import android.graphics.Insets;
import android.graphics.Rect; import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable; import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable; import android.graphics.drawable.LayerDrawable;
import android.testing.AndroidTestingRunner; import android.testing.AndroidTestingRunner;
@@ -63,13 +62,16 @@ import com.android.systemui.accessibility.MotionEventHelper;
import org.junit.After; import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Rule;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.InOrder; import org.mockito.InOrder;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.MockitoAnnotations; import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections;
import java.util.List; import java.util.List;
/** Tests for {@link AccessibilityFloatingMenuView}. */ /** Tests for {@link AccessibilityFloatingMenuView}. */
@@ -77,22 +79,26 @@ import java.util.List;
@RunWith(AndroidTestingRunner.class) @RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper @TestableLooper.RunWithLooper
public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
private AccessibilityFloatingMenuView mMenuView;
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
private final List<AccessibilityTarget> mTargets = new ArrayList<>(
Collections.singletonList(mock(AccessibilityTarget.class)));
private final Rect mAvailableBounds = new Rect(100, 200, 300, 400);
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
@Mock @Mock
private WindowManager mWindowManager; private WindowManager mWindowManager;
@Mock @Mock
private ViewPropertyAnimator mAnimator; private ViewPropertyAnimator mAnimator;
@Mock @Mock
private WindowMetrics mWindowMetrics; private WindowMetrics mWindowMetrics;
private MotionEvent mInterceptMotionEvent; private MotionEvent mInterceptMotionEvent;
private AccessibilityFloatingMenuView mMenuView;
private RecyclerView mListView; private RecyclerView mListView = new RecyclerView(mContext);
private Rect mAvailableBounds = new Rect(100, 200, 300, 400);
private int mScreenHeight; private int mScreenHeight;
private int mMenuWindowHeight; private int mMenuWindowHeight;
@@ -101,23 +107,21 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
private int mScreenHalfWidth; private int mScreenHalfWidth;
private int mScreenHalfHeight; private int mScreenHalfHeight;
private int mMaxWindowX; private int mMaxWindowX;
private int mMaxWindowY;
private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@Before @Before
public void setUp() { public void initMenuView() {
MockitoAnnotations.initMocks(this);
final WindowManager wm = mContext.getSystemService(WindowManager.class); final WindowManager wm = mContext.getSystemService(WindowManager.class);
doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( doAnswer(invocation -> wm.getMaximumWindowMetrics()).when(
mWindowManager).getMaximumWindowMetrics(); mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
mTargets.add(mock(AccessibilityTarget.class)); mMenuView = spy(
mListView = new RecyclerView(mContext); new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, mListView));
mMenuView = new AccessibilityFloatingMenuView(mContext, mListView); }
@Before
public void setUpMatrices() {
final Resources res = mContext.getResources(); final Resources res = mContext.getResources();
final int margin = final int margin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
@@ -135,6 +139,7 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
mScreenHalfHeight = mScreenHeight / 2; mScreenHalfHeight = mScreenHeight / 2;
mMaxWindowX = screenWidth - margin - menuWidth; mMaxWindowX = screenWidth - margin - menuWidth;
mMenuWindowHeight = menuHeight + margin * 2; mMenuWindowHeight = menuHeight + margin * 2;
mMaxWindowY = mScreenHeight - mMenuWindowHeight;
} }
@Test @Test
@@ -180,42 +185,46 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
} }
@Test @Test
public void updateListViewRadius_singleTarget_matchResult() { public void onTargetsChanged_singleTarget_expectedRadii() {
final float radius = final Position alignRightPosition = new Position(1.0f, 0.0f);
getContext().getResources().getDimensionPixelSize( final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
R.dimen.accessibility_floating_menu_small_single_radius); alignRightPosition);
final float[] expectedRadii = setupBasicMenuView(menuView);
new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
mMenuView.onTargetsChanged(mTargets); menuView.onTargetsChanged(mTargets);
final View view = mMenuView.getChildAt(0);
final View view = menuView.getChildAt(0);
final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground(); final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground();
final GradientDrawable gradientDrawable = final GradientDrawable gradientDrawable =
(GradientDrawable) layerDrawable.getDrawable(0); (GradientDrawable) layerDrawable.getDrawable(0);
final float[] actualRadii = gradientDrawable.getCornerRadii(); final float smallRadius =
getContext().getResources().getDimensionPixelSize(
assertThat(actualRadii).isEqualTo(expectedRadii); R.dimen.accessibility_floating_menu_small_single_radius);
final float[] expectedRadii =
new float[]{smallRadius, smallRadius, 0.0f, 0.0f, 0.0f, 0.0f, smallRadius,
smallRadius};
assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
} }
@Test @Test
public void setSizeType_largeSize_matchResult() { public void setSizeType_alignRightAndLargeSize_expectedRadii() {
final int shapeType = 2; final RecyclerView listView = spy(new RecyclerView(mContext));
final float radius = getContext().getResources().getDimensionPixelSize( final Position alignRightPosition = new Position(1.0f, 0.0f);
R.dimen.accessibility_floating_menu_large_single_radius); final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
final float[] expectedRadii = alignRightPosition, listView);
new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius}; setupBasicMenuView(menuView);
final Drawable listViewBackground =
mContext.getDrawable(R.drawable.accessibility_floating_menu_background); menuView.setSizeType(/* largeSize */ 1);
mListView = spy(new RecyclerView(mContext));
mListView.setBackground(listViewBackground);
mMenuView = new AccessibilityFloatingMenuView(mContext, mListView);
mMenuView.setSizeType(shapeType);
final LayerDrawable layerDrawable = final LayerDrawable layerDrawable =
(LayerDrawable) mListView.getBackground(); (LayerDrawable) listView.getBackground();
final GradientDrawable gradientDrawable = final GradientDrawable gradientDrawable =
(GradientDrawable) layerDrawable.getDrawable(0); (GradientDrawable) layerDrawable.getDrawable(0);
final float largeRadius = getContext().getResources().getDimensionPixelSize(
R.dimen.accessibility_floating_menu_large_single_radius);
final float[] expectedRadii =
new float[] {largeRadius, largeRadius, 0.0f, 0.0f, 0.0f, 0.0f, largeRadius,
largeRadius};
assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii); assertThat(gradientDrawable.getCornerRadii()).isEqualTo(expectedRadii);
} }
@@ -223,49 +232,43 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
public void setShapeType_halfCircle_translationX() { public void setShapeType_halfCircle_translationX() {
final RecyclerView listView = spy(new RecyclerView(mContext)); final RecyclerView listView = spy(new RecyclerView(mContext));
final AccessibilityFloatingMenuView menuView = final AccessibilityFloatingMenuView menuView =
new AccessibilityFloatingMenuView(mContext, listView); new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition, listView);
final int shapeType = 2; setupBasicMenuView(menuView);
doReturn(mAnimator).when(listView).animate(); doReturn(mAnimator).when(listView).animate();
menuView.setShapeType(shapeType); menuView.setShapeType(/* halfOvalShape */ 1);
verify(mAnimator).translationX(anyFloat()); verify(mAnimator).translationX(anyFloat());
} }
@Test @Test
public void onTargetsChanged_fadeInOut() { public void onTargetsChanged_fadeInOut() {
final AccessibilityFloatingMenuView menuView = spy(mMenuView); final InOrder inOrderMenuView = inOrder(mMenuView);
final InOrder inOrderMenuView = inOrder(menuView);
menuView.onTargetsChanged(mTargets); mMenuView.onTargetsChanged(mTargets);
inOrderMenuView.verify(menuView).fadeIn(); inOrderMenuView.verify(mMenuView).fadeIn();
inOrderMenuView.verify(menuView).fadeOut(); inOrderMenuView.verify(mMenuView).fadeOut();
} }
@Test @Test
public void setSizeType_fadeInOut() { public void setSizeType_fadeInOut() {
final AccessibilityFloatingMenuView menuView = spy(mMenuView); final InOrder inOrderMenuView = inOrder(mMenuView);
final InOrder inOrderMenuView = inOrder(menuView);
final int smallSize = 0;
menuView.setSizeType(smallSize);
inOrderMenuView.verify(menuView).fadeIn(); mMenuView.setSizeType(/* smallSize */ 0);
inOrderMenuView.verify(menuView).fadeOut();
inOrderMenuView.verify(mMenuView).fadeIn();
inOrderMenuView.verify(mMenuView).fadeOut();
} }
@Test @Test
public void tapOnAndDragMenu_interceptUpEvent() { public void tapOnAndDragMenu_interceptUpEvent() {
final RecyclerView listView = new RecyclerView(mContext); final RecyclerView listView = new RecyclerView(mContext);
final TestAccessibilityFloatingMenu menuView = final TestAccessibilityFloatingMenu menuView =
new TestAccessibilityFloatingMenu(mContext, listView); new TestAccessibilityFloatingMenu(mContext, mPlaceholderPosition, listView);
setupBasicMenuView(menuView);
menuView.show(); final int currentWindowX = menuView.mCurrentLayoutParams.x;
menuView.onTargetsChanged(mTargets); final int currentWindowY = menuView.mCurrentLayoutParams.y;
menuView.setSizeType(0);
menuView.setShapeType(0);
final int currentWindowX = mMenuView.mCurrentLayoutParams.x;
final int currentWindowY = mMenuView.mCurrentLayoutParams.y;
final MotionEvent downEvent = final MotionEvent downEvent =
mMotionEventHelper.obtainMotionEvent(0, 1, mMotionEventHelper.obtainMotionEvent(0, 1,
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_DOWN,
@@ -283,6 +286,7 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
/* screenCenterX */ mScreenHalfWidth /* screenCenterX */ mScreenHalfWidth
- /* offsetXToScreenLeftHalfRegion */ 10, - /* offsetXToScreenLeftHalfRegion */ 10,
/* screenCenterY */ mScreenHalfHeight); /* screenCenterY */ mScreenHalfHeight);
listView.dispatchTouchEvent(downEvent); listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent); listView.dispatchTouchEvent(moveEvent);
listView.dispatchTouchEvent(upEvent); listView.dispatchTouchEvent(upEvent);
@@ -292,12 +296,15 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
@Test @Test
public void tapOnAndDragMenu_matchLocation() { public void tapOnAndDragMenu_matchLocation() {
mMenuView.show(); final float expectedX = 1.0f;
mMenuView.onTargetsChanged(mTargets); final float expectedY = 0.7f;
mMenuView.setSizeType(0); final Position position = new Position(expectedX, expectedY);
mMenuView.setShapeType(0); final RecyclerView listView = new RecyclerView(mContext);
final int currentWindowX = mMenuView.mCurrentLayoutParams.x; final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
final int currentWindowY = mMenuView.mCurrentLayoutParams.y; position, listView);
setupBasicMenuView(menuView);
final int currentWindowX = menuView.mCurrentLayoutParams.x;
final int currentWindowY = menuView.mCurrentLayoutParams.y;
final MotionEvent downEvent = final MotionEvent downEvent =
mMotionEventHelper.obtainMotionEvent(0, 1, mMotionEventHelper.obtainMotionEvent(0, 1,
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_DOWN,
@@ -315,25 +322,28 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
/* screenCenterX */ mScreenHalfWidth /* screenCenterX */ mScreenHalfWidth
+ /* offsetXToScreenRightHalfRegion */ 10, + /* offsetXToScreenRightHalfRegion */ 10,
/* screenCenterY */ mScreenHalfHeight); /* screenCenterY */ mScreenHalfHeight);
mListView.dispatchTouchEvent(downEvent);
mListView.dispatchTouchEvent(moveEvent);
mListView.dispatchTouchEvent(upEvent);
mMenuView.mDragAnimator.end();
assertThat(mMenuView.mCurrentLayoutParams.x).isEqualTo(mMaxWindowX); listView.dispatchTouchEvent(downEvent);
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo( listView.dispatchTouchEvent(moveEvent);
listView.dispatchTouchEvent(upEvent);
menuView.mDragAnimator.end();
assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
/* newWindowY = screenCenterY - offsetY */ mScreenHalfHeight - mMenuHalfHeight); /* newWindowY = screenCenterY - offsetY */ mScreenHalfHeight - mMenuHalfHeight);
} }
@Test @Test
public void tapOnAndDragMenuToScreenSide_transformShapeHalfOval() { public void tapOnAndDragMenuToScreenSide_transformShapeHalfOval() {
mMenuView.show(); final Position alignRightPosition = new Position(1.0f, 0.8f);
mMenuView.onTargetsChanged(mTargets); final RecyclerView listView = new RecyclerView(mContext);
mMenuView.setSizeType(0); final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
mMenuView.setShapeType(/* oval */ 0); alignRightPosition, listView);
final int currentWindowX = mMenuView.mCurrentLayoutParams.x; setupBasicMenuView(menuView);
final int currentWindowY = mMenuView.mCurrentLayoutParams.y;
final int currentWindowX = menuView.mCurrentLayoutParams.x;
final int currentWindowY = menuView.mCurrentLayoutParams.y;
final MotionEvent downEvent = final MotionEvent downEvent =
mMotionEventHelper.obtainMotionEvent(0, 1, mMotionEventHelper.obtainMotionEvent(0, 1,
MotionEvent.ACTION_DOWN, MotionEvent.ACTION_DOWN,
@@ -351,125 +361,110 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
/* downX */(currentWindowX + mMenuHalfWidth) /* downX */(currentWindowX + mMenuHalfWidth)
+ /* offsetXToScreenRightSide */ mMenuHalfWidth, + /* offsetXToScreenRightSide */ mMenuHalfWidth,
/* downY */ (currentWindowY + mMenuHalfHeight)); /* downY */ (currentWindowY + mMenuHalfHeight));
mListView.dispatchTouchEvent(downEvent);
mListView.dispatchTouchEvent(moveEvent);
mListView.dispatchTouchEvent(upEvent);
assertThat(mMenuView.mShapeType).isEqualTo(/* halfOval */ 1); listView.dispatchTouchEvent(downEvent);
listView.dispatchTouchEvent(moveEvent);
listView.dispatchTouchEvent(upEvent);
assertThat(menuView.mShapeType).isEqualTo(/* halfOval */ 1);
} }
@Test @Test
public void getAccessibilityActionList_matchResult() { public void getAccessibilityActionList_matchResult() {
final AccessibilityNodeInfo infos = new AccessibilityNodeInfo(); final AccessibilityNodeInfo info = new AccessibilityNodeInfo();
mMenuView.onInitializeAccessibilityNodeInfo(infos);
assertThat(infos.getActionList().size()).isEqualTo(5); mMenuView.onInitializeAccessibilityNodeInfo(info);
assertThat(info.getActionList().size()).isEqualTo(5);
} }
@Test @Test
public void accessibilityActionMove_halfOval_moveTopLeft_success() { public void accessibilityActionMove_halfOval_moveTopLeft_success() {
final AccessibilityFloatingMenuView menuView = doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
spy(new AccessibilityFloatingMenuView(mContext)); mMenuView.setShapeType(/* halfOvalShape */ 1);
doReturn(mAvailableBounds).when(menuView).getAvailableBounds();
menuView.setShapeType(/* halfOvalShape */ 1);
final boolean isActionPerformed = final boolean moveTopLeftAction =
menuView.performAccessibilityAction(R.id.action_move_top_left, null); mMenuView.performAccessibilityAction(R.id.action_move_top_left, null);
assertThat(isActionPerformed).isTrue(); assertThat(moveTopLeftAction).isTrue();
assertThat(menuView.mShapeType).isEqualTo(/* ovalShape */ 0); assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
verify(menuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.top); verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.top);
} }
@Test @Test
public void accessibilityActionMove_halfOval_moveTopRight_success() { public void accessibilityActionMove_halfOval_moveTopRight_success() {
final AccessibilityFloatingMenuView menuView = doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
spy(new AccessibilityFloatingMenuView(mContext)); mMenuView.setShapeType(/* halfOvalShape */ 1);
doReturn(mAvailableBounds).when(menuView).getAvailableBounds();
menuView.setShapeType(/* halfOvalShape */ 1);
final boolean isActionPerformed = final boolean moveTopRightAction =
menuView.performAccessibilityAction(R.id.action_move_top_right, null); mMenuView.performAccessibilityAction(R.id.action_move_top_right, null);
assertThat(isActionPerformed).isTrue(); assertThat(moveTopRightAction).isTrue();
assertThat(menuView.mShapeType).isEqualTo(/* ovalShape */ 0); assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
verify(menuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.top); verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.top);
} }
@Test @Test
public void accessibilityActionMove_halfOval_moveBottomLeft_success() { public void accessibilityActionMove_halfOval_moveBottomLeft_success() {
final AccessibilityFloatingMenuView menuView = doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
spy(new AccessibilityFloatingMenuView(mContext)); mMenuView.setShapeType(/* halfOvalShape */ 1);
doReturn(mAvailableBounds).when(menuView).getAvailableBounds();
menuView.setShapeType(/* halfOvalShape */ 1);
final boolean isActionPerformed = final boolean moveBottomLeftAction =
menuView.performAccessibilityAction(R.id.action_move_bottom_left, null); mMenuView.performAccessibilityAction(R.id.action_move_bottom_left, null);
assertThat(isActionPerformed).isTrue(); assertThat(moveBottomLeftAction).isTrue();
assertThat(menuView.mShapeType).isEqualTo(/* ovalShape */ 0); assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
verify(menuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.bottom); verify(mMenuView).snapToLocation(mAvailableBounds.left, mAvailableBounds.bottom);
} }
@Test @Test
public void accessibilityActionMove_halfOval_moveBottomRight_success() { public void accessibilityActionMove_halfOval_moveBottomRight_success() {
final AccessibilityFloatingMenuView menuView = doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
spy(new AccessibilityFloatingMenuView(mContext)); mMenuView.setShapeType(/* halfOvalShape */ 1);
doReturn(mAvailableBounds).when(menuView).getAvailableBounds();
menuView.setShapeType(/* halfOvalShape */ 1);
final boolean isActionPerformed = final boolean moveBottomRightAction =
menuView.performAccessibilityAction(R.id.action_move_bottom_right, null); mMenuView.performAccessibilityAction(R.id.action_move_bottom_right, null);
assertThat(isActionPerformed).isTrue(); assertThat(moveBottomRightAction).isTrue();
assertThat(menuView.mShapeType).isEqualTo(/* ovalShape */ 0); assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
verify(menuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.bottom); verify(mMenuView).snapToLocation(mAvailableBounds.right, mAvailableBounds.bottom);
} }
@Test @Test
public void accessibilityActionMove_halfOval_moveOutEdgeAndShow_success() { public void accessibilityActionMove_halfOval_moveOutEdgeAndShow_success() {
final AccessibilityFloatingMenuView menuView = doReturn(mAvailableBounds).when(mMenuView).getAvailableBounds();
spy(new AccessibilityFloatingMenuView(mContext)); mMenuView.setShapeType(/* halfOvalShape */ 1);
doReturn(mAvailableBounds).when(menuView).getAvailableBounds();
menuView.setShapeType(/* halfOvalShape */ 1);
final boolean isActionPerformed = final boolean moveOutEdgeAndShowAction =
menuView.performAccessibilityAction(R.id.action_move_out_edge_and_show, null); mMenuView.performAccessibilityAction(R.id.action_move_out_edge_and_show, null);
assertThat(isActionPerformed).isTrue(); assertThat(moveOutEdgeAndShowAction).isTrue();
assertThat(menuView.mShapeType).isEqualTo(/* ovalShape */ 0); assertThat(mMenuView.mShapeType).isEqualTo(/* ovalShape */ 0);
} }
@Test @Test
public void setupAccessibilityActions_oval_hasActionMoveToEdgeAndHide() { public void setupAccessibilityActions_oval_hasActionMoveToEdgeAndHide() {
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext); final AccessibilityNodeInfo info = new AccessibilityNodeInfo();
menuView.setShapeType(/* ovalShape */ 0); mMenuView.setShapeType(/* ovalShape */ 0);
final AccessibilityNodeInfo infos = new AccessibilityNodeInfo(); mMenuView.onInitializeAccessibilityNodeInfo(info);
menuView.onInitializeAccessibilityNodeInfo(infos);
assertThat(infos.getActionList().stream().anyMatch( assertThat(info.getActionList().stream().anyMatch(
action -> action.getId() == R.id.action_move_to_edge_and_hide)).isTrue(); action -> action.getId() == R.id.action_move_to_edge_and_hide)).isTrue();
} }
@Test @Test
public void onTargetsChanged_exceedAvailableHeight_overScrollAlways() { public void onTargetsChanged_exceedAvailableHeight_overScrollAlways() {
final RecyclerView listView = new RecyclerView(mContext); doReturn(true).when(mMenuView).hasExceededMaxLayoutHeight();
final AccessibilityFloatingMenuView menuView =
spy(new AccessibilityFloatingMenuView(mContext, listView));
doReturn(true).when(menuView).hasExceededMaxLayoutHeight();
menuView.onTargetsChanged(mTargets); mMenuView.onTargetsChanged(mTargets);
assertThat(listView.getOverScrollMode()).isEqualTo(OVER_SCROLL_ALWAYS); assertThat(mListView.getOverScrollMode()).isEqualTo(OVER_SCROLL_ALWAYS);
} }
@Test @Test
public void onTargetsChanged_notExceedAvailableHeight_overScrollNever() { public void onTargetsChanged_notExceedAvailableHeight_overScrollNever() {
final RecyclerView listView = new RecyclerView(mContext); doReturn(false).when(mMenuView).hasExceededMaxLayoutHeight();
final AccessibilityFloatingMenuView menuView =
spy(new AccessibilityFloatingMenuView(mContext, listView));
doReturn(false).when(menuView).hasExceededMaxLayoutHeight();
mMenuView.onTargetsChanged(mTargets); mMenuView.onTargetsChanged(mTargets);
@@ -480,21 +475,24 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() { public void showMenuView_insetsListener_overlapWithIme_menuViewShifted() {
final int offset = 200; final int offset = 200;
showMenuWithLatestStatus(); final Position alignRightPosition = new Position(1.0f, 0.8f);
final WindowInsets imeInset = fakeImeInsetWith(offset); final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
alignRightPosition);
setupBasicMenuView(menuView);
final WindowInsets imeInset = fakeImeInsetWith(menuView, offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y - offset; final int expectedLayoutY = menuView.mCurrentLayoutParams.y - offset;
mMenuView.dispatchApplyWindowInsets(imeInset); menuView.dispatchApplyWindowInsets(imeInset);
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY); assertThat(menuView.mCurrentLayoutParams.y).isEqualTo(expectedLayoutY);
} }
@Test @Test
public void hideIme_onMenuViewShifted_menuViewMovedBack() { public void hideIme_onMenuViewShifted_menuViewMovedBack() {
final int offset = 200; final int offset = 200;
showMenuWithLatestStatus(); setupBasicMenuView(mMenuView);
final WindowInsets imeInset = fakeImeInsetWith(offset); final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y; final int expectedLayoutY = mMenuView.mCurrentLayoutParams.y;
@@ -510,8 +508,8 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
public void showMenuAndIme_withHigherIme_alignScreenTopEdge() { public void showMenuAndIme_withHigherIme_alignScreenTopEdge() {
final int offset = 99999; final int offset = 99999;
showMenuWithLatestStatus(); setupBasicMenuView(mMenuView);
final WindowInsets imeInset = fakeImeInsetWith(offset); final WindowInsets imeInset = fakeImeInsetWith(mMenuView, offset);
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics); when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset); when(mWindowMetrics.getWindowInsets()).thenReturn(imeInset);
mMenuView.dispatchApplyWindowInsets(imeInset); mMenuView.dispatchApplyWindowInsets(imeInset);
@@ -519,31 +517,47 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0); assertThat(mMenuView.mCurrentLayoutParams.y).isEqualTo(0);
} }
@Test
public void testConstructor_withPosition_expectedPosition() {
final float expectedX = 1.0f;
final float expectedY = 0.7f;
final Position position = new Position(expectedX, expectedY);
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
position);
setupBasicMenuView(menuView);
assertThat((float) menuView.mCurrentLayoutParams.x).isWithin(1.0f).of(mMaxWindowX);
assertThat((float) menuView.mCurrentLayoutParams.y).isWithin(1.0f).of(
expectedY * mMaxWindowY);
}
@After @After
public void tearDown() { public void tearDown() {
mInterceptMotionEvent = null; mInterceptMotionEvent = null;
mMotionEventHelper.recycleEvents(); mMotionEventHelper.recycleEvents();
mListView = null;
} }
private void showMenuWithLatestStatus() { private void setupBasicMenuView(AccessibilityFloatingMenuView menuView) {
mMenuView.show(); menuView.show();
mMenuView.onTargetsChanged(mTargets); menuView.onTargetsChanged(mTargets);
mMenuView.setSizeType(0); menuView.setSizeType(0);
mMenuView.setShapeType(0); menuView.setShapeType(0);
} }
/** /**
* Based on the current menu status, fake the ime inset component {@link WindowInsets} used * Based on the current menu status, fake the ime inset component {@link WindowInsets} used
* for testing. * for testing.
* *
* @param offset is used for the y-axis position of ime higher than the y-axis position of menu. * @param menuView {@link AccessibilityFloatingMenuView} that needs to be changed
* @param offset is used for the y-axis position of ime higher than the y-axis position of menu
* @return the ime inset * @return the ime inset
*/ */
private WindowInsets fakeImeInsetWith(int offset) { private WindowInsets fakeImeInsetWith(AccessibilityFloatingMenuView menuView, int offset) {
// Ensure the keyboard has overlapped on the menu view. // Ensure the keyboard has overlapped on the menu view.
final int fakeImeHeight = final int fakeImeHeight =
mScreenHeight - (mMenuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset; mScreenHeight - (menuView.mCurrentLayoutParams.y + mMenuWindowHeight) + offset;
return new WindowInsets.Builder() return new WindowInsets.Builder()
.setVisible(ime() | navigationBars(), true) .setVisible(ime() | navigationBars(), true)
.setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight)) .setInsets(ime() | navigationBars(), Insets.of(0, 0, 0, fakeImeHeight))
@@ -551,8 +565,8 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {
} }
private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView { private class TestAccessibilityFloatingMenu extends AccessibilityFloatingMenuView {
TestAccessibilityFloatingMenu(Context context, RecyclerView listView) { TestAccessibilityFloatingMenu(Context context, Position position, RecyclerView listView) {
super(context, listView); super(context, position, listView);
} }
@Override @Override

View File

@@ -55,6 +55,7 @@ public class BaseTooltipViewTest extends SysuiTestCase {
private AccessibilityFloatingMenuView mMenuView; private AccessibilityFloatingMenuView mMenuView;
private BaseTooltipView mToolTipView; private BaseTooltipView mToolTipView;
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
private final MotionEventHelper mMotionEventHelper = new MotionEventHelper(); private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@Before @Before
@@ -66,7 +67,7 @@ public class BaseTooltipViewTest extends SysuiTestCase {
mWindowManager).getMaximumWindowMetrics(); mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
mMenuView = new AccessibilityFloatingMenuView(mContext); mMenuView = new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition);
mToolTipView = new BaseTooltipView(mContext, mMenuView); mToolTipView = new BaseTooltipView(mContext, mMenuView);
} }

View File

@@ -51,6 +51,7 @@ public class DockTooltipViewTest extends SysuiTestCase {
private AccessibilityFloatingMenuView mMenuView; private AccessibilityFloatingMenuView mMenuView;
private DockTooltipView mDockTooltipView; private DockTooltipView mDockTooltipView;
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
private final MotionEventHelper mMotionEventHelper = new MotionEventHelper(); private final MotionEventHelper mMotionEventHelper = new MotionEventHelper();
@Before @Before
@@ -62,7 +63,7 @@ public class DockTooltipViewTest extends SysuiTestCase {
mWindowManager).getMaximumWindowMetrics(); mWindowManager).getMaximumWindowMetrics();
mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager);
mMenuView = spy(new AccessibilityFloatingMenuView(mContext)); mMenuView = spy(new AccessibilityFloatingMenuView(mContext, mPlaceholderPosition));
mDockTooltipView = new DockTooltipView(mContext, mMenuView); mDockTooltipView = new DockTooltipView(mContext, mMenuView);
} }

View File

@@ -40,10 +40,12 @@ import org.junit.runner.RunWith;
public class MigrationTooltipViewTest extends SysuiTestCase { public class MigrationTooltipViewTest extends SysuiTestCase {
private TextView mTextView; private TextView mTextView;
private final Position mPlaceholderPosition = new Position(0.0f, 0.0f);
@Before @Before
public void setUp() { public void setUp() {
final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext); final AccessibilityFloatingMenuView menuView = new AccessibilityFloatingMenuView(mContext,
mPlaceholderPosition);
final MigrationTooltipView toolTipView = new MigrationTooltipView(mContext, menuView); final MigrationTooltipView toolTipView = new MigrationTooltipView(mContext, menuView);
mTextView = toolTipView.findViewById(R.id.text); mTextView = toolTipView.findViewById(R.id.text);
} }

View File

@@ -0,0 +1,65 @@
/*
* Copyright (C) 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.systemui.accessibility.floatingmenu;
import static com.google.common.truth.Truth.assertThat;
import android.testing.AndroidTestingRunner;
import androidx.test.filters.SmallTest;
import com.android.systemui.SysuiTestCase;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link Position}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
public class PositionTest extends SysuiTestCase {
@Test
public void fromString_correctFormat_expectedValues() {
final float expectedX = 0.0f;
final float expectedY = 0.7f;
final String correctStringFormat = expectedX + ", " + expectedY;
final Position position = Position.fromString(correctStringFormat);
assertThat(position.getPercentageX()).isEqualTo(expectedX);
assertThat(position.getPercentageY()).isEqualTo(expectedY);
}
@Test(expected = IllegalArgumentException.class)
public void fromString_incorrectFormat_throwsException() {
final String incorrectStringFormat = "0.0: 1.0";
// expect to throw IllegalArgumentException for the incorrect separator ":"
Position.fromString(incorrectStringFormat);
}
@Test
public void constructor() {
final float expectedX = 0.5f;
final float expectedY = 0.9f;
final Position position = new Position(expectedX, expectedY);
assertThat(position.getPercentageX()).isEqualTo(expectedX);
assertThat(position.getPercentageY()).isEqualTo(expectedY);
}
}