Merge changes from topic "ig"
* changes: Fix that accessibility floating menu did not remember tucked state when lock/unlock the phone. Refactor the design and improve the animations of Accessibility Floating Menu(9/n).
This commit is contained in:
@@ -1341,6 +1341,7 @@
|
||||
<dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_large_single_radius">35dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_ime_shifting_space">48dp</dimen>
|
||||
|
||||
<dimen name="accessibility_floating_menu_message_container_horizontal_padding">15dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
|
||||
|
||||
@@ -72,7 +72,8 @@ public final class Prefs {
|
||||
Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
|
||||
Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
|
||||
Key.HAS_CLICKED_NUDGE_TO_SETUP_DREAM,
|
||||
Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM
|
||||
Key.HAS_DISMISSED_NUDGE_TO_SETUP_DREAM,
|
||||
Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED
|
||||
})
|
||||
// TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
|
||||
public @interface Key {
|
||||
@@ -117,6 +118,7 @@ public final class Prefs {
|
||||
String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition";
|
||||
String HAS_CLICKED_NUDGE_TO_SETUP_DREAM = "HasClickedNudgeToSetupDream";
|
||||
String HAS_DISMISSED_NUDGE_TO_SETUP_DREAM = "HasDismissedNudgeToSetupDream";
|
||||
String HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED = "HasAccessibilityFloatingMenuTucked";
|
||||
}
|
||||
|
||||
public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
|
||||
|
||||
@@ -16,8 +16,6 @@
|
||||
|
||||
package com.android.systemui.accessibility.floatingmenu;
|
||||
|
||||
import static android.util.MathUtils.constrain;
|
||||
|
||||
import static java.util.Objects.requireNonNull;
|
||||
|
||||
import android.animation.ValueAnimator;
|
||||
@@ -64,7 +62,6 @@ class MenuAnimationController {
|
||||
private final MenuView mMenuView;
|
||||
private final ValueAnimator mFadeOutAnimator;
|
||||
private final Handler mHandler;
|
||||
private boolean mIsMovedToEdge;
|
||||
private boolean mIsFadeEffectEnabled;
|
||||
private DismissAnimationController.DismissCallback mDismissCallback;
|
||||
|
||||
@@ -111,25 +108,25 @@ class MenuAnimationController {
|
||||
}
|
||||
|
||||
void moveToTopLeftPosition() {
|
||||
mIsMovedToEdge = false;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.top));
|
||||
}
|
||||
|
||||
void moveToTopRightPosition() {
|
||||
mIsMovedToEdge = false;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.top));
|
||||
}
|
||||
|
||||
void moveToBottomLeftPosition() {
|
||||
mIsMovedToEdge = false;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
moveAndPersistPosition(new PointF(draggableBounds.left, draggableBounds.bottom));
|
||||
}
|
||||
|
||||
void moveToBottomRightPosition() {
|
||||
mIsMovedToEdge = false;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
moveAndPersistPosition(new PointF(draggableBounds.right, draggableBounds.bottom));
|
||||
}
|
||||
@@ -254,6 +251,8 @@ class MenuAnimationController {
|
||||
// If the translation x is zero, it should be at the left of the bound.
|
||||
if (currentXTranslation < draggableBounds.left
|
||||
|| currentXTranslation > draggableBounds.right) {
|
||||
constrainPositionAndUpdate(
|
||||
new PointF(mMenuView.getTranslationX(), mMenuView.getTranslationY()));
|
||||
moveToEdgeAndHide();
|
||||
return true;
|
||||
}
|
||||
@@ -262,37 +261,33 @@ class MenuAnimationController {
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean isOnLeftSide() {
|
||||
boolean isOnLeftSide() {
|
||||
return mMenuView.getTranslationX() < mMenuView.getMenuDraggableBounds().centerX();
|
||||
}
|
||||
|
||||
boolean isMovedToEdge() {
|
||||
return mIsMovedToEdge;
|
||||
boolean isMoveToTucked() {
|
||||
return mMenuView.isMoveToTucked();
|
||||
}
|
||||
|
||||
void moveToEdgeAndHide() {
|
||||
mIsMovedToEdge = true;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ true);
|
||||
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
final float endY = constrain(mMenuView.getTranslationY(), draggableBounds.top,
|
||||
draggableBounds.bottom);
|
||||
final float menuHalfWidth = mMenuView.getWidth() / 2.0f;
|
||||
final PointF position = mMenuView.getMenuPosition();
|
||||
final float menuHalfWidth = mMenuView.getMenuWidth() / 2.0f;
|
||||
final float endX = isOnLeftSide()
|
||||
? draggableBounds.left - menuHalfWidth
|
||||
: draggableBounds.right + menuHalfWidth;
|
||||
moveAndPersistPosition(new PointF(endX, endY));
|
||||
? position.x - menuHalfWidth
|
||||
: position.x + menuHalfWidth;
|
||||
moveToPosition(new PointF(endX, position.y));
|
||||
|
||||
// Keep the touch region let users could click extra space to pop up the menu view
|
||||
// from the screen edge
|
||||
mMenuView.onBoundsInParentChanged(isOnLeftSide()
|
||||
? draggableBounds.left
|
||||
: draggableBounds.right, (int) mMenuView.getTranslationY());
|
||||
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
|
||||
|
||||
fadeOutIfEnabled();
|
||||
}
|
||||
|
||||
void moveOutEdgeAndShow() {
|
||||
mIsMovedToEdge = false;
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
|
||||
mMenuView.onPositionChanged();
|
||||
mMenuView.onEdgeChangedIfNeeded();
|
||||
@@ -345,7 +340,7 @@ class MenuAnimationController {
|
||||
}
|
||||
|
||||
private void constrainPositionAndUpdate(PointF position) {
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBounds();
|
||||
final Rect draggableBounds = mMenuView.getMenuDraggableBoundsExcludeIme();
|
||||
// Have the space gap margin between the top bound and the menu view, so actually the
|
||||
// position y range needs to cut the margin.
|
||||
position.offset(-draggableBounds.left, -draggableBounds.top);
|
||||
|
||||
@@ -51,6 +51,7 @@ class MenuInfoRepository {
|
||||
|
||||
@FloatRange(from = 0.0, to = 1.0)
|
||||
private static final float DEFAULT_MENU_POSITION_Y_PERCENT = 0.77f;
|
||||
private static final boolean DEFAULT_MOVE_TO_TUCKED_VALUE = false;
|
||||
|
||||
private final Context mContext;
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
@@ -92,6 +93,12 @@ class MenuInfoRepository {
|
||||
mPercentagePosition = getStartPosition();
|
||||
}
|
||||
|
||||
void loadMenuMoveToTucked(OnInfoReady<Boolean> callback) {
|
||||
callback.onReady(
|
||||
Prefs.getBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
|
||||
DEFAULT_MOVE_TO_TUCKED_VALUE));
|
||||
}
|
||||
|
||||
void loadMenuPosition(OnInfoReady<Position> callback) {
|
||||
callback.onReady(mPercentagePosition);
|
||||
}
|
||||
@@ -113,6 +120,11 @@ class MenuInfoRepository {
|
||||
getMenuOpacityFromSettings(mContext));
|
||||
}
|
||||
|
||||
void updateMoveToTucked(boolean isMoveToTucked) {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
|
||||
isMoveToTucked);
|
||||
}
|
||||
|
||||
void updateMenuSavingPosition(Position percentagePosition) {
|
||||
mPercentagePosition = percentagePosition;
|
||||
Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
|
||||
|
||||
@@ -74,10 +74,10 @@ class MenuItemAccessibilityDelegate extends RecyclerViewAccessibilityDelegate.It
|
||||
R.string.accessibility_floating_button_action_move_bottom_right));
|
||||
info.addAction(moveBottomRight);
|
||||
|
||||
final int moveEdgeId = mAnimationController.isMovedToEdge()
|
||||
final int moveEdgeId = mAnimationController.isMoveToTucked()
|
||||
? R.id.action_move_out_edge_and_show
|
||||
: R.id.action_move_to_edge_and_hide;
|
||||
final int moveEdgeTextResId = mAnimationController.isMovedToEdge()
|
||||
final int moveEdgeTextResId = mAnimationController.isMoveToTucked()
|
||||
? R.string.accessibility_floating_button_action_move_out_edge_and_show
|
||||
: R.string.accessibility_floating_button_action_move_to_edge_and_hide_to_half;
|
||||
final AccessibilityNodeInfoCompat.AccessibilityActionCompat moveToOrOutEdge =
|
||||
|
||||
@@ -58,12 +58,15 @@ class MenuView extends FrameLayout implements
|
||||
this::updateSystemGestureExcludeRects;
|
||||
private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver =
|
||||
this::onMenuFadeEffectInfoChanged;
|
||||
private final Observer<Boolean> mMoveToTuckedObserver = this::onMoveToTucked;
|
||||
private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition;
|
||||
private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged;
|
||||
private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver =
|
||||
this::onTargetFeaturesChanged;
|
||||
private final MenuViewAppearance mMenuViewAppearance;
|
||||
|
||||
private boolean mIsMoveToTucked;
|
||||
|
||||
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
|
||||
|
||||
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
|
||||
@@ -161,6 +164,12 @@ class MenuView extends FrameLayout implements
|
||||
mMenuViewAppearance.getMenuStrokeColor());
|
||||
}
|
||||
|
||||
private void onMoveToTucked(boolean isMoveToTucked) {
|
||||
mIsMoveToTucked = isMoveToTucked;
|
||||
|
||||
onPositionChanged();
|
||||
}
|
||||
|
||||
private void onPercentagePosition(Position percentagePosition) {
|
||||
mMenuViewAppearance.setPercentagePosition(percentagePosition);
|
||||
|
||||
@@ -171,6 +180,10 @@ class MenuView extends FrameLayout implements
|
||||
final PointF position = mMenuViewAppearance.getMenuPosition();
|
||||
mMenuAnimationController.moveToPosition(position);
|
||||
onBoundsInParentChanged((int) position.x, (int) position.y);
|
||||
|
||||
if (isMoveToTucked()) {
|
||||
mMenuAnimationController.moveToEdgeAndHide();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
@@ -219,6 +232,22 @@ class MenuView extends FrameLayout implements
|
||||
return mMenuViewAppearance.getMenuDraggableBounds();
|
||||
}
|
||||
|
||||
Rect getMenuDraggableBoundsExcludeIme() {
|
||||
return mMenuViewAppearance.getMenuDraggableBoundsExcludeIme();
|
||||
}
|
||||
|
||||
int getMenuHeight() {
|
||||
return mMenuViewAppearance.getMenuHeight();
|
||||
}
|
||||
|
||||
int getMenuWidth() {
|
||||
return mMenuViewAppearance.getMenuWidth();
|
||||
}
|
||||
|
||||
PointF getMenuPosition() {
|
||||
return mMenuViewAppearance.getMenuPosition();
|
||||
}
|
||||
|
||||
void persistPositionAndUpdateEdge(Position percentagePosition) {
|
||||
mMenuViewModel.updateMenuSavingPosition(percentagePosition);
|
||||
mMenuViewAppearance.setPercentagePosition(percentagePosition);
|
||||
@@ -226,6 +255,16 @@ class MenuView extends FrameLayout implements
|
||||
onEdgeChangedIfNeeded();
|
||||
}
|
||||
|
||||
boolean isMoveToTucked() {
|
||||
return mIsMoveToTucked;
|
||||
}
|
||||
|
||||
void updateMenuMoveToTucked(boolean isMoveToTucked) {
|
||||
mIsMoveToTucked = isMoveToTucked;
|
||||
mMenuViewModel.updateMenuMoveToTucked(isMoveToTucked);
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Uses the touch events from the parent view to identify if users clicked the extra
|
||||
* space of the menu view. If yes, will use the percentage position and update the
|
||||
@@ -241,7 +280,7 @@ class MenuView extends FrameLayout implements
|
||||
boolean maybeMoveOutEdgeAndShow(int x, int y) {
|
||||
// Utilizes the touch region of the parent view to implement that users could tap extra
|
||||
// the space region to show the menu from the edge.
|
||||
if (!mMenuAnimationController.isMovedToEdge() || !mBoundsInParent.contains(x, y)) {
|
||||
if (!isMoveToTucked() || !mBoundsInParent.contains(x, y)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -258,6 +297,7 @@ class MenuView extends FrameLayout implements
|
||||
mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
|
||||
mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver);
|
||||
mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
|
||||
mMenuViewModel.getMoveToTuckedData().observeForever(mMoveToTuckedObserver);
|
||||
setVisibility(VISIBLE);
|
||||
mMenuViewModel.registerContentObservers();
|
||||
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
||||
@@ -271,6 +311,7 @@ class MenuView extends FrameLayout implements
|
||||
mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver);
|
||||
mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
|
||||
mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
|
||||
mMenuViewModel.getMoveToTuckedData().removeObserver(mMoveToTuckedObserver);
|
||||
mMenuViewModel.unregisterContentObservers();
|
||||
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
||||
getViewTreeObserver().removeOnDrawListener(mSystemGestureExcludeUpdater);
|
||||
|
||||
@@ -47,6 +47,9 @@ class MenuViewAppearance {
|
||||
private final Resources mRes;
|
||||
private final Position mPercentagePosition = new Position(/* percentageX= */
|
||||
0f, /* percentageY= */ 0f);
|
||||
private boolean mIsImeShowing;
|
||||
// Avoid the menu view overlapping on the primary action button under the bottom as possible.
|
||||
private int mImeShiftingSpace;
|
||||
private int mTargetFeaturesSize;
|
||||
private int mSizeType;
|
||||
private int mMargin;
|
||||
@@ -62,6 +65,7 @@ class MenuViewAppearance {
|
||||
private int mStrokeColor;
|
||||
private int mInset;
|
||||
private int mElevation;
|
||||
private float mImeTop;
|
||||
private float[] mRadii;
|
||||
private Drawable mBackgroundDrawable;
|
||||
private String mContentDescription;
|
||||
@@ -106,6 +110,8 @@ class MenuViewAppearance {
|
||||
mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark);
|
||||
mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset);
|
||||
mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
|
||||
mImeShiftingSpace = mRes.getDimensionPixelSize(
|
||||
R.dimen.accessibility_floating_menu_ime_shifting_space);
|
||||
final Drawable drawable =
|
||||
mRes.getDrawable(R.drawable.accessibility_floating_menu_background);
|
||||
mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable});
|
||||
@@ -131,29 +137,56 @@ class MenuViewAppearance {
|
||||
mRadii = createRadii(isMenuOnLeftSide(), getMenuRadius(mTargetFeaturesSize));
|
||||
}
|
||||
|
||||
void onImeVisibilityChanged(boolean imeShowing, float imeTop) {
|
||||
mIsImeShowing = imeShowing;
|
||||
mImeTop = imeTop;
|
||||
}
|
||||
|
||||
Rect getMenuDraggableBounds() {
|
||||
return getMenuDraggableBoundsWith(/* includeIme= */ true);
|
||||
}
|
||||
|
||||
Rect getMenuDraggableBoundsExcludeIme() {
|
||||
return getMenuDraggableBoundsWith(/* includeIme= */ false);
|
||||
}
|
||||
|
||||
private Rect getMenuDraggableBoundsWith(boolean includeIme) {
|
||||
final int margin = getMenuMargin();
|
||||
final Rect draggableBounds = getWindowAvailableBounds();
|
||||
final Rect draggableBounds = new Rect(getWindowAvailableBounds());
|
||||
|
||||
// Initializes start position for mapping the translation of the menu view.
|
||||
draggableBounds.offsetTo(/* newLeft= */ 0, /* newTop= */ 0);
|
||||
|
||||
draggableBounds.top += margin;
|
||||
draggableBounds.right -= getMenuWidth();
|
||||
draggableBounds.bottom -= Math.min(
|
||||
getWindowAvailableBounds().height() - draggableBounds.top,
|
||||
calculateActualMenuHeight() + margin);
|
||||
|
||||
if (includeIme && mIsImeShowing) {
|
||||
final int imeHeight = (int) (draggableBounds.bottom - mImeTop);
|
||||
draggableBounds.bottom -= (imeHeight + mImeShiftingSpace);
|
||||
}
|
||||
draggableBounds.bottom -= (calculateActualMenuHeight() + margin);
|
||||
draggableBounds.bottom = Math.max(draggableBounds.top, draggableBounds.bottom);
|
||||
|
||||
return draggableBounds;
|
||||
}
|
||||
|
||||
PointF getMenuPosition() {
|
||||
final Rect draggableBounds = getMenuDraggableBounds();
|
||||
final Rect draggableBounds = getMenuDraggableBoundsExcludeIme();
|
||||
final float x = draggableBounds.left
|
||||
+ draggableBounds.width() * mPercentagePosition.getPercentageX();
|
||||
|
||||
return new PointF(
|
||||
draggableBounds.left
|
||||
+ draggableBounds.width() * mPercentagePosition.getPercentageX(),
|
||||
draggableBounds.top
|
||||
+ draggableBounds.height() * mPercentagePosition.getPercentageY());
|
||||
float y = draggableBounds.top
|
||||
+ draggableBounds.height() * mPercentagePosition.getPercentageY();
|
||||
|
||||
// If the bottom of the menu view and overlap on the ime, its position y will be
|
||||
// overridden with new y.
|
||||
final float menuBottom = y + getMenuHeight() + mMargin;
|
||||
if (mIsImeShowing && (menuBottom >= mImeTop)) {
|
||||
y = Math.max(draggableBounds.top,
|
||||
mImeTop - getMenuHeight() - mMargin - mImeShiftingSpace);
|
||||
}
|
||||
|
||||
return new PointF(x, y);
|
||||
}
|
||||
|
||||
String getContentDescription() {
|
||||
|
||||
@@ -16,6 +16,10 @@
|
||||
|
||||
package com.android.systemui.accessibility.floatingmenu;
|
||||
|
||||
import static android.view.WindowInsets.Type.ime;
|
||||
|
||||
import static androidx.core.view.WindowInsetsCompat.Type;
|
||||
|
||||
import static com.android.internal.accessibility.common.ShortcutConstants.AccessibilityFragmentType.INVISIBLE_TOGGLE;
|
||||
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
|
||||
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
|
||||
@@ -26,13 +30,16 @@ import android.annotation.IntDef;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.util.PluralsMessageFormatter;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
@@ -62,14 +69,17 @@ import java.util.Map;
|
||||
class MenuViewLayer extends FrameLayout {
|
||||
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
|
||||
|
||||
private final WindowManager mWindowManager;
|
||||
private final MenuView mMenuView;
|
||||
private final MenuMessageView mMessageView;
|
||||
private final DismissView mDismissView;
|
||||
private final MenuViewAppearance mMenuViewAppearance;
|
||||
private final MenuAnimationController mMenuAnimationController;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final IAccessibilityFloatingMenu mFloatingMenu;
|
||||
private final DismissAnimationController mDismissAnimationController;
|
||||
private final Rect mImeInsetsRect = new Rect();
|
||||
|
||||
@IntDef({
|
||||
LayerIndex.MENU_VIEW,
|
||||
@@ -111,13 +121,13 @@ class MenuViewLayer extends FrameLayout {
|
||||
AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
|
||||
super(context);
|
||||
|
||||
mWindowManager = windowManager;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
mFloatingMenu = floatingMenu;
|
||||
|
||||
final MenuViewModel menuViewModel = new MenuViewModel(context);
|
||||
final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
|
||||
windowManager);
|
||||
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
|
||||
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
|
||||
mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance);
|
||||
mMenuAnimationController = mMenuView.getMenuAnimationController();
|
||||
mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
|
||||
|
||||
@@ -200,6 +210,7 @@ class MenuViewLayer extends FrameLayout {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
mMenuView.show();
|
||||
setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
|
||||
mMessageView.setUndoListener(view -> undo());
|
||||
mContext.registerComponentCallbacks(mDismissAnimationController);
|
||||
}
|
||||
@@ -209,10 +220,35 @@ class MenuViewLayer extends FrameLayout {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mMenuView.hide();
|
||||
setOnApplyWindowInsetsListener(null);
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
mContext.unregisterComponentCallbacks(mDismissAnimationController);
|
||||
}
|
||||
|
||||
private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
|
||||
final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
|
||||
final WindowInsets windowInsets = windowMetrics.getWindowInsets();
|
||||
final Rect imeInsetsRect = windowInsets.getInsets(ime()).toRect();
|
||||
if (!imeInsetsRect.equals(mImeInsetsRect)) {
|
||||
final Rect windowBounds = new Rect(windowMetrics.getBounds());
|
||||
final Rect systemBarsAndDisplayCutoutInsetsRect =
|
||||
windowInsets.getInsetsIgnoringVisibility(
|
||||
Type.systemBars() | Type.displayCutout()).toRect();
|
||||
final float imeTop =
|
||||
windowBounds.height() - systemBarsAndDisplayCutoutInsetsRect.top
|
||||
- imeInsetsRect.bottom;
|
||||
|
||||
mMenuViewAppearance.onImeVisibilityChanged(windowInsets.isVisible(ime()), imeTop);
|
||||
|
||||
mMenuView.onEdgeChanged();
|
||||
mMenuView.onPositionChanged();
|
||||
|
||||
mImeInsetsRect.set(imeInsetsRect);
|
||||
}
|
||||
|
||||
return insets;
|
||||
}
|
||||
|
||||
private void hideMenuAndShowMessage() {
|
||||
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
|
||||
SHOW_MESSAGE_DELAY_MS,
|
||||
|
||||
@@ -35,6 +35,7 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
|
||||
private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>();
|
||||
private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
|
||||
new MutableLiveData<>();
|
||||
private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
|
||||
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
|
||||
private final MenuInfoRepository mInfoRepository;
|
||||
|
||||
@@ -57,10 +58,19 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
|
||||
mFadeEffectInfoData.setValue(fadeEffectInfo);
|
||||
}
|
||||
|
||||
void updateMenuMoveToTucked(boolean isMoveToTucked) {
|
||||
mInfoRepository.updateMoveToTucked(isMoveToTucked);
|
||||
}
|
||||
|
||||
void updateMenuSavingPosition(Position percentagePosition) {
|
||||
mInfoRepository.updateMenuSavingPosition(percentagePosition);
|
||||
}
|
||||
|
||||
LiveData<Boolean> getMoveToTuckedData() {
|
||||
mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue);
|
||||
return mMoveToTuckedData;
|
||||
}
|
||||
|
||||
LiveData<Position> getPercentagePositionData() {
|
||||
mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
|
||||
return mPercentagePositionData;
|
||||
|
||||
@@ -32,8 +32,10 @@ import android.view.WindowManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.Prefs;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
@@ -44,6 +46,7 @@ import org.junit.runner.RunWith;
|
||||
@SmallTest
|
||||
public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
|
||||
private boolean mLastIsMoveToTucked;
|
||||
private ViewPropertyAnimator mViewPropertyAnimator;
|
||||
private MenuView mMenuView;
|
||||
private MenuAnimationController mMenuAnimationController;
|
||||
@@ -60,6 +63,14 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
|
||||
|
||||
mMenuAnimationController = new MenuAnimationController(mMenuView);
|
||||
mLastIsMoveToTucked = Prefs.getBoolean(mContext,
|
||||
Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED,
|
||||
mLastIsMoveToTucked);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -81,10 +92,34 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
|
||||
@Test
|
||||
public void startGrowAnimation_menuCompletelyOpaque() {
|
||||
mMenuAnimationController.startShrinkAnimation(null);
|
||||
mMenuAnimationController.startShrinkAnimation(/* endAction= */ null);
|
||||
|
||||
mMenuAnimationController.startGrowAnimation();
|
||||
|
||||
assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moveToEdgeAndHide_untucked_expectedSharedPreferenceValue() {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
|
||||
false);
|
||||
|
||||
mMenuAnimationController.moveToEdgeAndHide();
|
||||
final boolean isMoveToTucked = Prefs.getBoolean(mContext,
|
||||
Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ false);
|
||||
|
||||
assertThat(isMoveToTucked).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void moveOutEdgeAndShow_tucked_expectedSharedPreferenceValue() {
|
||||
Prefs.putBoolean(mContext, Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* value= */
|
||||
true);
|
||||
|
||||
mMenuAnimationController.moveOutEdgeAndShow();
|
||||
final boolean isMoveToTucked = Prefs.getBoolean(mContext,
|
||||
Prefs.Key.HAS_ACCESSIBILITY_FLOATING_MENU_TUCKED, /* defaultValue= */ true);
|
||||
|
||||
assertThat(isMoveToTucked).isFalse();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -18,12 +18,17 @@ package com.android.systemui.accessibility.floatingmenu;
|
||||
|
||||
import static android.view.View.GONE;
|
||||
import static android.view.View.VISIBLE;
|
||||
import static android.view.WindowInsets.Type.displayCutout;
|
||||
import static android.view.WindowInsets.Type.ime;
|
||||
import static android.view.WindowInsets.Type.systemBars;
|
||||
|
||||
import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_COMPONENT_NAME;
|
||||
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
@@ -32,13 +37,18 @@ import android.content.ComponentName;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.ResolveInfo;
|
||||
import android.content.pm.ServiceInfo;
|
||||
import android.graphics.Insets;
|
||||
import android.graphics.PointF;
|
||||
import android.graphics.Rect;
|
||||
import android.os.Build;
|
||||
import android.os.UserHandle;
|
||||
import android.provider.Settings;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.View;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -68,9 +78,20 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
private static final ComponentName TEST_SELECT_TO_SPEAK_COMPONENT_NAME = new ComponentName(
|
||||
SELECT_TO_SPEAK_PACKAGE_NAME, SELECT_TO_SPEAK_SERVICE_NAME);
|
||||
|
||||
private static final int DISPLAY_WINDOW_WIDTH = 1080;
|
||||
private static final int DISPLAY_WINDOW_HEIGHT = 2340;
|
||||
private static final int STATUS_BAR_HEIGHT = 75;
|
||||
private static final int NAVIGATION_BAR_HEIGHT = 125;
|
||||
private static final int IME_HEIGHT = 350;
|
||||
private static final int IME_TOP =
|
||||
DISPLAY_WINDOW_HEIGHT - STATUS_BAR_HEIGHT - NAVIGATION_BAR_HEIGHT - IME_HEIGHT;
|
||||
|
||||
private MenuViewLayer mMenuViewLayer;
|
||||
private String mLastAccessibilityButtonTargets;
|
||||
private String mLastEnabledAccessibilityServices;
|
||||
private WindowMetrics mWindowMetrics;
|
||||
private MenuView mMenuView;
|
||||
private MenuAnimationController mMenuAnimationController;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockito = MockitoJUnit.rule();
|
||||
@@ -78,14 +99,24 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private IAccessibilityFloatingMenu mFloatingMenu;
|
||||
|
||||
@Mock
|
||||
private WindowManager mStubWindowManager;
|
||||
|
||||
@Mock
|
||||
private AccessibilityManager mStubAccessibilityManager;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, mStubAccessibilityManager,
|
||||
final Rect mDisplayBounds = new Rect();
|
||||
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
|
||||
DISPLAY_WINDOW_HEIGHT);
|
||||
mWindowMetrics = spy(new WindowMetrics(mDisplayBounds, fakeDisplayInsets()));
|
||||
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
|
||||
|
||||
mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
|
||||
mFloatingMenu);
|
||||
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
|
||||
mMenuAnimationController = mMenuView.getMenuAnimationController();
|
||||
|
||||
mLastAccessibilityButtonTargets =
|
||||
Settings.Secure.getStringForUser(mContext.getContentResolver(),
|
||||
@@ -93,6 +124,12 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
mLastEnabledAccessibilityServices =
|
||||
Settings.Secure.getStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
|
||||
|
||||
mMenuViewLayer.onAttachedToWindow();
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
|
||||
}
|
||||
|
||||
@After
|
||||
@@ -103,6 +140,9 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
Settings.Secure.putStringForUser(mContext.getContentResolver(),
|
||||
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
|
||||
UserHandle.USER_CURRENT);
|
||||
|
||||
mMenuView.updateMenuMoveToTucked(/* isMoveToTucked= */ false);
|
||||
mMenuViewLayer.onDetachedFromWindow();
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -168,4 +208,69 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
|
||||
assertThat(value).isEqualTo("");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showingImeInsetsChange_notOverlapOnIme_menuKeepOriginalPosition() {
|
||||
final float menuTop = STATUS_BAR_HEIGHT + 100;
|
||||
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
|
||||
|
||||
dispatchShowingImeInsets();
|
||||
|
||||
assertThat(mMenuView.getTranslationX()).isEqualTo(0);
|
||||
assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showingImeInsetsChange_overlapOnIme_menuShownAboveIme() {
|
||||
final float menuTop = IME_TOP + 100;
|
||||
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
|
||||
|
||||
dispatchShowingImeInsets();
|
||||
|
||||
final float menuBottom = mMenuView.getTranslationY() + mMenuView.getMenuHeight();
|
||||
assertThat(mMenuView.getTranslationX()).isEqualTo(0);
|
||||
assertThat(menuBottom).isLessThan(IME_TOP);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hidingImeInsetsChange_overlapOnIme_menuBackToOriginalPosition() {
|
||||
final float menuTop = IME_TOP + 200;
|
||||
mMenuAnimationController.moveAndPersistPosition(new PointF(0, menuTop));
|
||||
dispatchShowingImeInsets();
|
||||
|
||||
dispatchHidingImeInsets();
|
||||
|
||||
assertThat(mMenuView.getTranslationX()).isEqualTo(0);
|
||||
assertThat(mMenuView.getTranslationY()).isEqualTo(menuTop);
|
||||
}
|
||||
|
||||
private void dispatchShowingImeInsets() {
|
||||
final WindowInsets fakeShowingImeInsets = fakeImeInsets(/* isImeVisible= */ true);
|
||||
doReturn(fakeShowingImeInsets).when(mWindowMetrics).getWindowInsets();
|
||||
mMenuViewLayer.dispatchApplyWindowInsets(fakeShowingImeInsets);
|
||||
}
|
||||
|
||||
private void dispatchHidingImeInsets() {
|
||||
final WindowInsets fakeHidingImeInsets = fakeImeInsets(/* isImeVisible= */ false);
|
||||
doReturn(fakeHidingImeInsets).when(mWindowMetrics).getWindowInsets();
|
||||
mMenuViewLayer.dispatchApplyWindowInsets(fakeHidingImeInsets);
|
||||
}
|
||||
|
||||
private WindowInsets fakeDisplayInsets() {
|
||||
return new WindowInsets.Builder()
|
||||
.setVisible(systemBars() | displayCutout(), /* visible= */ true)
|
||||
.setInsets(systemBars() | displayCutout(),
|
||||
Insets.of(/* left= */ 0, STATUS_BAR_HEIGHT, /* right= */ 0,
|
||||
NAVIGATION_BAR_HEIGHT))
|
||||
.build();
|
||||
}
|
||||
|
||||
private WindowInsets fakeImeInsets(boolean isImeVisible) {
|
||||
final int bottom = isImeVisible ? (IME_HEIGHT + NAVIGATION_BAR_HEIGHT) : 0;
|
||||
return new WindowInsets.Builder()
|
||||
.setVisible(ime(), isImeVisible)
|
||||
.setInsets(ime(),
|
||||
Insets.of(/* left= */ 0, /* top= */ 0, /* right= */ 0, bottom))
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user