Merge changes I1f676efc,I9428c3a3
* changes: Support the feature of Drag to remove for the Accessibility Floating Menu(2/n). Support the feature of Drag to remove for the Accessibility Floating Menu(1/n).
This commit is contained in:
@@ -0,0 +1,22 @@
|
||||
<!--
|
||||
~ Copyright (C) 2022 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.
|
||||
-->
|
||||
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:androidprv="http://schemas.android.com/apk/prv/res/android"
|
||||
android:shape="rectangle">
|
||||
<solid android:color="@color/accessibility_floating_menu_message_background"/>
|
||||
<corners android:radius="28dp"/>
|
||||
</shape>
|
||||
@@ -99,6 +99,8 @@
|
||||
|
||||
<!-- Accessibility floating menu -->
|
||||
<color name="accessibility_floating_menu_background">#B3000000</color> <!-- 70% -->
|
||||
<color name="accessibility_floating_menu_message_background">@*android:color/background_material_dark</color>
|
||||
<color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_dark</color>
|
||||
|
||||
<color name="people_tile_background">@color/material_dynamic_secondary20</color>
|
||||
</resources>
|
||||
|
||||
@@ -219,6 +219,8 @@
|
||||
<!-- Accessibility floating menu -->
|
||||
<color name="accessibility_floating_menu_background">#CCFFFFFF</color> <!-- 80% -->
|
||||
<color name="accessibility_floating_menu_stroke_dark">#26FFFFFF</color> <!-- 15% -->
|
||||
<color name="accessibility_floating_menu_message_background">@*android:color/background_material_light</color>
|
||||
<color name="accessibility_floating_menu_message_text">@*android:color/primary_text_default_material_light</color>
|
||||
|
||||
<!-- Wallet screen -->
|
||||
<color name="wallet_card_border">#33FFFFFF</color>
|
||||
|
||||
@@ -1336,6 +1336,14 @@
|
||||
<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_message_container_horizontal_padding">15dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_text_vertical_padding">8dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_margin">8dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_elevation">5dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_text_size">14sp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_min_width">312dp</dimen>
|
||||
<dimen name="accessibility_floating_menu_message_min_height">48dp</dimen>
|
||||
|
||||
<dimen name="accessibility_floating_tooltip_arrow_width">8dp</dimen>
|
||||
<dimen name="accessibility_floating_tooltip_arrow_height">16dp</dimen>
|
||||
<dimen name="accessibility_floating_tooltip_arrow_margin">-2dp</dimen>
|
||||
|
||||
@@ -2197,6 +2197,15 @@
|
||||
<string name="accessibility_floating_button_migration_tooltip">Tap to open accessibility features. Customize or replace this button in Settings.\n\n<annotation id="link">View settings</annotation></string>
|
||||
<!-- Message for the accessibility floating button docking tooltip. It shows when the user first time drag the button. It will tell the user about docking behavior. [CHAR LIMIT=70] -->
|
||||
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
|
||||
<!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
|
||||
<string name="accessibility_floating_button_undo">Undo</string>
|
||||
|
||||
<!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
|
||||
<string name="accessibility_floating_button_undo_message_text">{count, plural,
|
||||
=1 {{label} shortcut removed}
|
||||
other {# shortcuts removed}
|
||||
}</string>
|
||||
|
||||
<!-- Action in accessibility menu to move the accessibility floating button to the top left of the screen. [CHAR LIMIT=30] -->
|
||||
<string name="accessibility_floating_button_action_move_top_left">Move top left</string>
|
||||
<!-- Action in accessibility menu to move the accessibility floating button to the top right of the screen. [CHAR LIMIT=30] -->
|
||||
|
||||
@@ -28,6 +28,7 @@ import android.os.UserHandle;
|
||||
import android.text.TextUtils;
|
||||
import android.view.Display;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.annotation.MainThread;
|
||||
|
||||
@@ -56,6 +57,7 @@ public class AccessibilityFloatingMenuController implements
|
||||
private Context mContext;
|
||||
private final WindowManager mWindowManager;
|
||||
private final DisplayManager mDisplayManager;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final FeatureFlags mFeatureFlags;
|
||||
@VisibleForTesting
|
||||
IAccessibilityFloatingMenu mFloatingMenu;
|
||||
@@ -96,6 +98,7 @@ public class AccessibilityFloatingMenuController implements
|
||||
public AccessibilityFloatingMenuController(Context context,
|
||||
WindowManager windowManager,
|
||||
DisplayManager displayManager,
|
||||
AccessibilityManager accessibilityManager,
|
||||
AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver,
|
||||
AccessibilityButtonModeObserver accessibilityButtonModeObserver,
|
||||
KeyguardUpdateMonitor keyguardUpdateMonitor,
|
||||
@@ -103,6 +106,7 @@ public class AccessibilityFloatingMenuController implements
|
||||
mContext = context;
|
||||
mWindowManager = windowManager;
|
||||
mDisplayManager = displayManager;
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver;
|
||||
mAccessibilityButtonModeObserver = accessibilityButtonModeObserver;
|
||||
mKeyguardUpdateMonitor = keyguardUpdateMonitor;
|
||||
@@ -180,7 +184,8 @@ public class AccessibilityFloatingMenuController implements
|
||||
final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY);
|
||||
mFloatingMenu = new MenuViewLayerController(
|
||||
mContext.createWindowContext(defaultDisplay,
|
||||
TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager);
|
||||
TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager,
|
||||
mAccessibilityManager);
|
||||
} else {
|
||||
mFloatingMenu = new AccessibilityFloatingMenu(mContext);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,175 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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.animation.Animator;
|
||||
import android.animation.AnimatorListenerAdapter;
|
||||
import android.animation.ValueAnimator;
|
||||
import android.content.ComponentCallbacks;
|
||||
import android.content.res.Configuration;
|
||||
import android.view.MotionEvent;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.dynamicanimation.animation.DynamicAnimation;
|
||||
|
||||
import com.android.systemui.R;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
/**
|
||||
* Controls the interaction between {@link MagnetizedObject} and
|
||||
* {@link MagnetizedObject.MagneticTarget}.
|
||||
*/
|
||||
class DismissAnimationController implements ComponentCallbacks {
|
||||
private static final float COMPLETELY_OPAQUE = 1.0f;
|
||||
private static final float COMPLETELY_TRANSPARENT = 0.0f;
|
||||
private static final float CIRCLE_VIEW_DEFAULT_SCALE = 1.0f;
|
||||
private static final float ANIMATING_MAX_ALPHA = 0.7f;
|
||||
|
||||
private final DismissView mDismissView;
|
||||
private final MenuView mMenuView;
|
||||
private final ValueAnimator mDismissAnimator;
|
||||
private final MagnetizedObject<?> mMagnetizedObject;
|
||||
private float mMinDismissSize;
|
||||
private float mSizePercent;
|
||||
|
||||
DismissAnimationController(DismissView dismissView, MenuView menuView) {
|
||||
mDismissView = dismissView;
|
||||
mDismissView.setPivotX(dismissView.getWidth() / 2.0f);
|
||||
mDismissView.setPivotY(dismissView.getHeight() / 2.0f);
|
||||
mMenuView = menuView;
|
||||
|
||||
updateResources();
|
||||
|
||||
mDismissAnimator = ValueAnimator.ofFloat(COMPLETELY_OPAQUE, COMPLETELY_TRANSPARENT);
|
||||
mDismissAnimator.addUpdateListener(dismissAnimation -> {
|
||||
final float animatedValue = (float) dismissAnimation.getAnimatedValue();
|
||||
final float scaleValue = Math.max(animatedValue, mSizePercent);
|
||||
dismissView.getCircle().setScaleX(scaleValue);
|
||||
dismissView.getCircle().setScaleY(scaleValue);
|
||||
|
||||
menuView.setAlpha(Math.max(animatedValue, ANIMATING_MAX_ALPHA));
|
||||
});
|
||||
|
||||
mDismissAnimator.addListener(new AnimatorListenerAdapter() {
|
||||
@Override
|
||||
public void onAnimationEnd(@NonNull Animator animation, boolean isReverse) {
|
||||
super.onAnimationEnd(animation, isReverse);
|
||||
|
||||
if (isReverse) {
|
||||
mDismissView.getCircle().setScaleX(CIRCLE_VIEW_DEFAULT_SCALE);
|
||||
mDismissView.getCircle().setScaleY(CIRCLE_VIEW_DEFAULT_SCALE);
|
||||
mMenuView.setAlpha(COMPLETELY_OPAQUE);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
mMagnetizedObject =
|
||||
new MagnetizedObject<MenuView>(mMenuView.getContext(), mMenuView,
|
||||
new MenuAnimationController.MenuPositionProperty(
|
||||
DynamicAnimation.TRANSLATION_X),
|
||||
new MenuAnimationController.MenuPositionProperty(
|
||||
DynamicAnimation.TRANSLATION_Y)) {
|
||||
@Override
|
||||
public void getLocationOnScreen(MenuView underlyingObject, int[] loc) {
|
||||
underlyingObject.getLocationOnScreen(loc);
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getHeight(MenuView underlyingObject) {
|
||||
return underlyingObject.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public float getWidth(MenuView underlyingObject) {
|
||||
return underlyingObject.getWidth();
|
||||
}
|
||||
};
|
||||
|
||||
final MagnetizedObject.MagneticTarget magneticTarget = new MagnetizedObject.MagneticTarget(
|
||||
dismissView.getCircle(), (int) mMinDismissSize);
|
||||
mMagnetizedObject.addTarget(magneticTarget);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onConfigurationChanged(@NonNull Configuration newConfig) {
|
||||
updateResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLowMemory() {
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
void showDismissView(boolean show) {
|
||||
if (show) {
|
||||
mDismissView.show();
|
||||
} else {
|
||||
mDismissView.hide();
|
||||
}
|
||||
}
|
||||
|
||||
void setMagnetListener(MagnetizedObject.MagnetListener magnetListener) {
|
||||
mMagnetizedObject.setMagnetListener(magnetListener);
|
||||
}
|
||||
|
||||
void maybeConsumeDownMotionEvent(MotionEvent event) {
|
||||
mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This used to pass {@link MotionEvent#ACTION_DOWN} to the magnetized object to check if it was
|
||||
* within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
|
||||
*
|
||||
* @param event that move the magnetized object which is also the menu list view.
|
||||
* @return true if the location of the motion events moves within the magnetic field of a
|
||||
* target, but false if didn't set
|
||||
* {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
|
||||
*/
|
||||
boolean maybeConsumeMoveMotionEvent(MotionEvent event) {
|
||||
return mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
/**
|
||||
* This used to pass {@link MotionEvent#ACTION_UP} to the magnetized object to check if it was
|
||||
* within the magnetic field. It should be used in the {@link MenuListViewTouchHandler}.
|
||||
*
|
||||
* @param event that move the magnetized object which is also the menu list view.
|
||||
* @return true if the location of the motion events moves within the magnetic field of a
|
||||
* target, but false if didn't set
|
||||
* {@link DismissAnimationController#setMagnetListener(MagnetizedObject.MagnetListener)}.
|
||||
*/
|
||||
boolean maybeConsumeUpMotionEvent(MotionEvent event) {
|
||||
return mMagnetizedObject.maybeConsumeMotionEvent(event);
|
||||
}
|
||||
|
||||
void animateDismissMenu(boolean scaleUp) {
|
||||
if (scaleUp) {
|
||||
mDismissAnimator.start();
|
||||
} else {
|
||||
mDismissAnimator.reverse();
|
||||
}
|
||||
}
|
||||
|
||||
private void updateResources() {
|
||||
final float maxDismissSize = mDismissView.getResources().getDimensionPixelSize(
|
||||
R.dimen.dismiss_circle_size);
|
||||
mMinDismissSize = mDismissView.getResources().getDimensionPixelSize(
|
||||
R.dimen.dismiss_circle_small);
|
||||
mSizePercent = mMinDismissSize / maxDismissSize;
|
||||
}
|
||||
}
|
||||
@@ -47,6 +47,9 @@ class MenuAnimationController {
|
||||
private static final float MIN_PERCENT = 0.0f;
|
||||
private static final float MAX_PERCENT = 1.0f;
|
||||
private static final float COMPLETELY_OPAQUE = 1.0f;
|
||||
private static final float COMPLETELY_TRANSPARENT = 0.0f;
|
||||
private static final float SCALE_SHRINK = 0.0f;
|
||||
private static final float SCALE_GROW = 1.0f;
|
||||
private static final float FLING_FRICTION_SCALAR = 1.9f;
|
||||
private static final float DEFAULT_FRICTION = 4.2f;
|
||||
private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
|
||||
@@ -297,6 +300,28 @@ class MenuAnimationController {
|
||||
mMenuView.onDraggingStart();
|
||||
}
|
||||
|
||||
void startShrinkAnimation(Runnable endAction) {
|
||||
mMenuView.animate().cancel();
|
||||
|
||||
mMenuView.animate()
|
||||
.scaleX(SCALE_SHRINK)
|
||||
.scaleY(SCALE_SHRINK)
|
||||
.alpha(COMPLETELY_TRANSPARENT)
|
||||
.translationY(mMenuView.getTranslationY())
|
||||
.withEndAction(endAction).start();
|
||||
}
|
||||
|
||||
void startGrowAnimation() {
|
||||
mMenuView.animate().cancel();
|
||||
|
||||
mMenuView.animate()
|
||||
.scaleX(SCALE_GROW)
|
||||
.scaleY(SCALE_GROW)
|
||||
.alpha(COMPLETELY_OPAQUE)
|
||||
.translationY(mMenuView.getTranslationY())
|
||||
.start();
|
||||
}
|
||||
|
||||
private void onSpringAnimationEnd(PointF position) {
|
||||
mMenuView.onBoundsInParentChanged((int) position.x, (int) position.y);
|
||||
constrainPositionAndUpdate(position);
|
||||
|
||||
@@ -38,9 +38,12 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
|
||||
private final PointF mMenuTranslationDown = new PointF();
|
||||
private boolean mIsDragging = false;
|
||||
private float mTouchSlop;
|
||||
private final DismissAnimationController mDismissAnimationController;
|
||||
|
||||
MenuListViewTouchHandler(MenuAnimationController menuAnimationController) {
|
||||
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
|
||||
DismissAnimationController dismissAnimationController) {
|
||||
mMenuAnimationController = menuAnimationController;
|
||||
mDismissAnimationController = dismissAnimationController;
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -61,6 +64,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
|
||||
mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
|
||||
|
||||
mMenuAnimationController.cancelAnimations();
|
||||
mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
|
||||
break;
|
||||
case MotionEvent.ACTION_MOVE:
|
||||
if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
|
||||
@@ -69,8 +73,13 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
|
||||
mMenuAnimationController.onDraggingStart();
|
||||
}
|
||||
|
||||
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
|
||||
mMenuAnimationController.moveToPositionYIfNeeded(mMenuTranslationDown.y + dy);
|
||||
mDismissAnimationController.showDismissView(/* show= */ true);
|
||||
|
||||
if (!mDismissAnimationController.maybeConsumeMoveMotionEvent(motionEvent)) {
|
||||
mMenuAnimationController.moveToPositionX(mMenuTranslationDown.x + dx);
|
||||
mMenuAnimationController.moveToPositionYIfNeeded(
|
||||
mMenuTranslationDown.y + dy);
|
||||
}
|
||||
}
|
||||
break;
|
||||
case MotionEvent.ACTION_UP:
|
||||
@@ -79,10 +88,18 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
|
||||
final float endX = mMenuTranslationDown.x + dx;
|
||||
mIsDragging = false;
|
||||
|
||||
if (!mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
|
||||
if (mMenuAnimationController.maybeMoveToEdgeAndHide(endX)) {
|
||||
mDismissAnimationController.showDismissView(/* show= */ false);
|
||||
mMenuAnimationController.fadeOutIfEnabled();
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!mDismissAnimationController.maybeConsumeUpMotionEvent(motionEvent)) {
|
||||
mVelocityTracker.computeCurrentVelocity(VELOCITY_UNIT_SECONDS);
|
||||
mMenuAnimationController.flingMenuThenSpringToEdge(endX,
|
||||
mVelocityTracker.getXVelocity(), mVelocityTracker.getYVelocity());
|
||||
mDismissAnimationController.showDismissView(/* show= */ false);
|
||||
}
|
||||
|
||||
// Avoid triggering the listener of the item.
|
||||
|
||||
@@ -0,0 +1,162 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 android.util.TypedValue.COMPLEX_UNIT_PX;
|
||||
import static android.view.ViewGroup.LayoutParams.WRAP_CONTENT;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.content.Context;
|
||||
import android.content.res.ColorStateList;
|
||||
import android.content.res.Configuration;
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Rect;
|
||||
import android.view.Gravity;
|
||||
import android.view.View;
|
||||
import android.view.ViewTreeObserver;
|
||||
import android.widget.Button;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.LinearLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.android.settingslib.Utils;
|
||||
import com.android.systemui.R;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
|
||||
/**
|
||||
* The message view with the action prompt to whether to undo operation for users when removing
|
||||
* the {@link MenuView}.
|
||||
*/
|
||||
class MenuMessageView extends LinearLayout implements
|
||||
ViewTreeObserver.OnComputeInternalInsetsListener {
|
||||
private final TextView mTextView;
|
||||
private final Button mUndoButton;
|
||||
|
||||
@IntDef({
|
||||
Index.TEXT_VIEW,
|
||||
Index.UNDO_BUTTON
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface Index {
|
||||
int TEXT_VIEW = 0;
|
||||
int UNDO_BUTTON = 1;
|
||||
}
|
||||
|
||||
MenuMessageView(Context context) {
|
||||
super(context);
|
||||
|
||||
setVisibility(GONE);
|
||||
|
||||
mTextView = new TextView(context);
|
||||
mUndoButton = new Button(context);
|
||||
|
||||
addView(mTextView, Index.TEXT_VIEW,
|
||||
new LayoutParams(/* width= */ 0, WRAP_CONTENT, /* weight= */ 1));
|
||||
addView(mUndoButton, Index.UNDO_BUTTON, new LayoutParams(WRAP_CONTENT, WRAP_CONTENT));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
|
||||
updateResources();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onAttachedToWindow() {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
final FrameLayout.LayoutParams containerParams = new FrameLayout.LayoutParams(WRAP_CONTENT,
|
||||
WRAP_CONTENT);
|
||||
containerParams.gravity = Gravity.BOTTOM | Gravity.CENTER_HORIZONTAL;
|
||||
setLayoutParams(containerParams);
|
||||
setGravity(Gravity.CENTER_VERTICAL);
|
||||
|
||||
mUndoButton.setBackground(null);
|
||||
|
||||
updateResources();
|
||||
|
||||
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||
|
||||
if (getVisibility() == VISIBLE) {
|
||||
final int x = (int) getX();
|
||||
final int y = (int) getY();
|
||||
inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Registers a listener to be invoked when this undo action button is clicked. It should be
|
||||
* called after {@link View#onAttachedToWindow()}.
|
||||
*
|
||||
* @param listener The listener that will run
|
||||
*/
|
||||
void setUndoListener(OnClickListener listener) {
|
||||
mUndoButton.setOnClickListener(listener);
|
||||
}
|
||||
|
||||
private void updateResources() {
|
||||
final Resources res = getResources();
|
||||
|
||||
final int containerPadding =
|
||||
res.getDimensionPixelSize(
|
||||
R.dimen.accessibility_floating_menu_message_container_horizontal_padding);
|
||||
final int margin = res.getDimensionPixelSize(
|
||||
R.dimen.accessibility_floating_menu_message_margin);
|
||||
final FrameLayout.LayoutParams containerParams =
|
||||
(FrameLayout.LayoutParams) getLayoutParams();
|
||||
containerParams.setMargins(margin, margin, margin, margin);
|
||||
setLayoutParams(containerParams);
|
||||
setBackground(res.getDrawable(R.drawable.accessibility_floating_message_background));
|
||||
setPadding(containerPadding, /* top= */ 0, containerPadding, /* bottom= */ 0);
|
||||
setMinimumWidth(
|
||||
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_width));
|
||||
setMinimumHeight(
|
||||
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_min_height));
|
||||
setElevation(
|
||||
res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_message_elevation));
|
||||
|
||||
final int textPadding =
|
||||
res.getDimensionPixelSize(
|
||||
R.dimen.accessibility_floating_menu_message_text_vertical_padding);
|
||||
final int textColor = res.getColor(R.color.accessibility_floating_menu_message_text);
|
||||
final int textSize = res.getDimensionPixelSize(
|
||||
R.dimen.accessibility_floating_menu_message_text_size);
|
||||
mTextView.setPadding(/* left= */ 0, textPadding, /* right= */ 0, textPadding);
|
||||
mTextView.setTextSize(COMPLEX_UNIT_PX, textSize);
|
||||
mTextView.setTextColor(textColor);
|
||||
|
||||
final ColorStateList colorAccent = Utils.getColorAccent(getContext());
|
||||
mUndoButton.setText(res.getString(R.string.accessibility_floating_button_undo));
|
||||
mUndoButton.setTextSize(COMPLEX_UNIT_PX, textSize);
|
||||
mUndoButton.setTextColor(colorAccent);
|
||||
}
|
||||
}
|
||||
@@ -42,7 +42,7 @@ import java.util.Collections;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* The menu view displays the accessibility features.
|
||||
* The container view displays the accessibility features.
|
||||
*/
|
||||
@SuppressLint("ViewConstructor")
|
||||
class MenuView extends FrameLayout implements
|
||||
@@ -64,13 +64,14 @@ class MenuView extends FrameLayout implements
|
||||
this::onTargetFeaturesChanged;
|
||||
private final MenuViewAppearance mMenuViewAppearance;
|
||||
|
||||
private OnTargetFeaturesChangeListener mFeaturesChangeListener;
|
||||
|
||||
MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) {
|
||||
super(context);
|
||||
|
||||
mMenuViewModel = menuViewModel;
|
||||
mMenuViewAppearance = menuViewAppearance;
|
||||
mMenuAnimationController = new MenuAnimationController(this);
|
||||
|
||||
mAdapter = new AccessibilityTargetAdapter(mTargetFeatures);
|
||||
mTargetFeaturesView = new RecyclerView(context);
|
||||
mTargetFeaturesView.setAdapter(mAdapter);
|
||||
@@ -96,7 +97,9 @@ class MenuView extends FrameLayout implements
|
||||
@Override
|
||||
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
|
||||
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
|
||||
inoutInfo.touchableRegion.set(mBoundsInParent);
|
||||
if (getVisibility() == VISIBLE) {
|
||||
inoutInfo.touchableRegion.union(mBoundsInParent);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -108,10 +111,18 @@ class MenuView extends FrameLayout implements
|
||||
mTargetFeaturesView.setOverScrollMode(mMenuViewAppearance.getMenuScrollMode());
|
||||
}
|
||||
|
||||
void setOnTargetFeaturesChangeListener(OnTargetFeaturesChangeListener listener) {
|
||||
mFeaturesChangeListener = listener;
|
||||
}
|
||||
|
||||
void addOnItemTouchListenerToList(RecyclerView.OnItemTouchListener listener) {
|
||||
mTargetFeaturesView.addOnItemTouchListener(listener);
|
||||
}
|
||||
|
||||
MenuAnimationController getMenuAnimationController() {
|
||||
return mMenuAnimationController;
|
||||
}
|
||||
|
||||
@SuppressLint("NotifyDataSetChanged")
|
||||
private void onItemSizeChanged() {
|
||||
mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
|
||||
@@ -139,7 +150,7 @@ class MenuView extends FrameLayout implements
|
||||
onEdgeChanged();
|
||||
}
|
||||
|
||||
private void onEdgeChanged() {
|
||||
void onEdgeChanged() {
|
||||
final int[] insets = mMenuViewAppearance.getMenuInsets();
|
||||
getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2],
|
||||
insets[3]);
|
||||
@@ -193,6 +204,9 @@ class MenuView extends FrameLayout implements
|
||||
onEdgeChanged();
|
||||
onPositionChanged();
|
||||
|
||||
if (mFeaturesChangeListener != null) {
|
||||
mFeaturesChangeListener.onChange(newTargetFeatures);
|
||||
}
|
||||
mMenuAnimationController.fadeOutIfEnabled();
|
||||
}
|
||||
|
||||
@@ -299,4 +313,17 @@ class MenuView extends FrameLayout implements
|
||||
final ViewGroup parentView = (ViewGroup) getParent();
|
||||
parentView.setSystemGestureExclusionRects(Collections.singletonList(mBoundsInParent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface definition for the {@link AccessibilityTarget} list changes.
|
||||
*/
|
||||
interface OnTargetFeaturesChangeListener {
|
||||
/**
|
||||
* Called when the list of accessibility target features was updated. This will be
|
||||
* invoked when the end of {@code onTargetFeaturesChanged}.
|
||||
*
|
||||
* @param newTargetFeatures the list related to the current accessibility features.
|
||||
*/
|
||||
void onChange(List<AccessibilityTarget> newTargetFeatures);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -16,42 +16,154 @@
|
||||
|
||||
package com.android.systemui.accessibility.floatingmenu;
|
||||
|
||||
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
|
||||
|
||||
import android.annotation.IntDef;
|
||||
import android.annotation.SuppressLint;
|
||||
import android.content.Context;
|
||||
import android.content.res.Configuration;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.provider.Settings;
|
||||
import android.util.PluralsMessageFormatter;
|
||||
import android.view.MotionEvent;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
import android.widget.FrameLayout;
|
||||
import android.widget.TextView;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
|
||||
import com.android.internal.accessibility.dialog.AccessibilityTarget;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.internal.util.Preconditions;
|
||||
import com.android.systemui.R;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
|
||||
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* The basic interactions with the child view {@link MenuView}.
|
||||
* The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
|
||||
* {@link MenuMessageView}. When dragging the menu view, the dismissed view would be shown at the
|
||||
* same time. If the menu view overlaps on the dismissed circle view and drops out, the menu
|
||||
* message view would be shown and allowed users to undo it.
|
||||
*/
|
||||
@SuppressLint("ViewConstructor")
|
||||
class MenuViewLayer extends FrameLayout {
|
||||
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
|
||||
|
||||
private final MenuView mMenuView;
|
||||
private final MenuMessageView mMessageView;
|
||||
private final DismissView mDismissView;
|
||||
private final MenuAnimationController mMenuAnimationController;
|
||||
private final AccessibilityManager mAccessibilityManager;
|
||||
private final Handler mHandler = new Handler(Looper.getMainLooper());
|
||||
private final IAccessibilityFloatingMenu mFloatingMenu;
|
||||
private final DismissAnimationController mDismissAnimationController;
|
||||
|
||||
@IntDef({
|
||||
LayerIndex.MENU_VIEW
|
||||
LayerIndex.MENU_VIEW,
|
||||
LayerIndex.DISMISS_VIEW,
|
||||
LayerIndex.MESSAGE_VIEW,
|
||||
})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface LayerIndex {
|
||||
int MENU_VIEW = 0;
|
||||
int DISMISS_VIEW = 1;
|
||||
int MESSAGE_VIEW = 2;
|
||||
}
|
||||
|
||||
MenuViewLayer(@NonNull Context context, WindowManager windowManager) {
|
||||
@VisibleForTesting
|
||||
final Runnable mDismissMenuAction = new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
Settings.Secure.putString(getContext().getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, /* value= */ "");
|
||||
mFloatingMenu.hide();
|
||||
}
|
||||
};
|
||||
|
||||
MenuViewLayer(@NonNull Context context, WindowManager windowManager,
|
||||
AccessibilityManager accessibilityManager, IAccessibilityFloatingMenu floatingMenu) {
|
||||
super(context);
|
||||
|
||||
mAccessibilityManager = accessibilityManager;
|
||||
mFloatingMenu = floatingMenu;
|
||||
|
||||
final MenuViewModel menuViewModel = new MenuViewModel(context);
|
||||
final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context,
|
||||
windowManager);
|
||||
mMenuView = new MenuView(context, menuViewModel, menuViewAppearance);
|
||||
mMenuAnimationController = mMenuView.getMenuAnimationController();
|
||||
|
||||
mDismissView = new DismissView(context);
|
||||
mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
|
||||
mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
|
||||
@Override
|
||||
public void onStuckToTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
mDismissAnimationController.animateDismissMenu(/* scaleUp= */ true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onUnstuckFromTarget(@NonNull MagnetizedObject.MagneticTarget target,
|
||||
float velocityX, float velocityY, boolean wasFlungOut) {
|
||||
mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
|
||||
hideMenuAndShowMessage();
|
||||
mDismissView.hide();
|
||||
mDismissAnimationController.animateDismissMenu(/* scaleUp= */ false);
|
||||
}
|
||||
});
|
||||
|
||||
final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
|
||||
mMenuAnimationController, mDismissAnimationController);
|
||||
mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
|
||||
|
||||
mMessageView = new MenuMessageView(context);
|
||||
|
||||
mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
|
||||
if (newTargetFeatures.size() < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
// During the undo action period, the pending action will be canceled and undo back
|
||||
// to the previous state if users did any action related to the accessibility features.
|
||||
if (mMessageView.getVisibility() == VISIBLE) {
|
||||
undo();
|
||||
}
|
||||
|
||||
final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
|
||||
messageText.setText(getMessageText(newTargetFeatures));
|
||||
});
|
||||
|
||||
addView(mMenuView, LayerIndex.MENU_VIEW);
|
||||
addView(mDismissView, LayerIndex.DISMISS_VIEW);
|
||||
addView(mMessageView, LayerIndex.MESSAGE_VIEW);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onConfigurationChanged(Configuration newConfig) {
|
||||
super.onConfigurationChanged(newConfig);
|
||||
mDismissView.updateResources();
|
||||
}
|
||||
|
||||
private String getMessageText(List<AccessibilityTarget> newTargetFeatures) {
|
||||
Preconditions.checkArgument(newTargetFeatures.size() > 0,
|
||||
"The list should at least have one feature.");
|
||||
|
||||
final Map<String, Object> arguments = new HashMap<>();
|
||||
arguments.put("count", newTargetFeatures.size());
|
||||
arguments.put("label", newTargetFeatures.get(0).getLabel());
|
||||
return PluralsMessageFormatter.format(getResources(), arguments,
|
||||
R.string.accessibility_floating_button_undo_message_text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -68,6 +180,8 @@ class MenuViewLayer extends FrameLayout {
|
||||
super.onAttachedToWindow();
|
||||
|
||||
mMenuView.show();
|
||||
mMessageView.setUndoListener(view -> undo());
|
||||
mContext.registerComponentCallbacks(mDismissAnimationController);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -75,5 +189,26 @@ class MenuViewLayer extends FrameLayout {
|
||||
super.onDetachedFromWindow();
|
||||
|
||||
mMenuView.hide();
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
mContext.unregisterComponentCallbacks(mDismissAnimationController);
|
||||
}
|
||||
|
||||
private void hideMenuAndShowMessage() {
|
||||
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
|
||||
SHOW_MESSAGE_DELAY_MS,
|
||||
AccessibilityManager.FLAG_CONTENT_TEXT
|
||||
| AccessibilityManager.FLAG_CONTENT_CONTROLS);
|
||||
mHandler.postDelayed(mDismissMenuAction, delayTime);
|
||||
mMessageView.setVisibility(VISIBLE);
|
||||
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
|
||||
}
|
||||
|
||||
private void undo() {
|
||||
mHandler.removeCallbacksAndMessages(/* token= */ null);
|
||||
mMessageView.setVisibility(GONE);
|
||||
mMenuView.onEdgeChanged();
|
||||
mMenuView.onPositionChanged();
|
||||
mMenuView.setVisibility(VISIBLE);
|
||||
mMenuAnimationController.startGrowAnimation();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -22,6 +22,7 @@ import android.content.Context;
|
||||
import android.graphics.PixelFormat;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
/**
|
||||
* Controls the {@link MenuViewLayer} whether to be attached to the window via the interface
|
||||
@@ -32,9 +33,10 @@ class MenuViewLayerController implements IAccessibilityFloatingMenu {
|
||||
private final MenuViewLayer mMenuViewLayer;
|
||||
private boolean mIsShowing;
|
||||
|
||||
MenuViewLayerController(Context context, WindowManager windowManager) {
|
||||
MenuViewLayerController(Context context, WindowManager windowManager,
|
||||
AccessibilityManager accessibilityManager) {
|
||||
mWindowManager = windowManager;
|
||||
mMenuViewLayer = new MenuViewLayer(context, windowManager);
|
||||
mMenuViewLayer = new MenuViewLayer(context, windowManager, accessibilityManager, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
@@ -35,6 +35,7 @@ import android.provider.Settings;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -68,6 +69,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
|
||||
public MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
private Context mContextWrapper;
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
private KeyguardUpdateMonitor mKeyguardUpdateMonitor;
|
||||
private AccessibilityFloatingMenuController mController;
|
||||
private AccessibilityButtonTargetsObserver mTargetsObserver;
|
||||
@@ -87,6 +89,7 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
|
||||
}
|
||||
};
|
||||
|
||||
mAccessibilityManager = mContext.getSystemService(AccessibilityManager.class);
|
||||
mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(),
|
||||
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
|
||||
mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(),
|
||||
@@ -348,8 +351,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase {
|
||||
mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class);
|
||||
final AccessibilityFloatingMenuController controller =
|
||||
new AccessibilityFloatingMenuController(mContextWrapper, windowManager,
|
||||
displayManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor,
|
||||
featureFlags);
|
||||
displayManager, mAccessibilityManager, mTargetsObserver, mModeObserver,
|
||||
mKeyguardUpdateMonitor, featureFlags);
|
||||
controller.init();
|
||||
|
||||
return controller;
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2022 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 org.mockito.Mockito.verify;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/** Tests for {@link DismissAnimationController}. */
|
||||
@SmallTest
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@TestableLooper.RunWithLooper
|
||||
public class DismissAnimationControllerTest extends SysuiTestCase {
|
||||
private DismissAnimationController mDismissAnimationController;
|
||||
private DismissView mDismissView;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
|
||||
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
|
||||
stubWindowManager);
|
||||
final MenuView stubMenuView = new MenuView(mContext, stubMenuViewModel,
|
||||
stubMenuViewAppearance);
|
||||
mDismissView = new DismissView(mContext);
|
||||
mDismissAnimationController = new DismissAnimationController(mDismissView, stubMenuView);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void showDismissView_success() {
|
||||
mDismissAnimationController.showDismissView(true);
|
||||
|
||||
verify(mDismissView).show();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void hideDismissView_success() {
|
||||
mDismissAnimationController.showDismissView(false);
|
||||
|
||||
verify(mDismissView).hide();
|
||||
}
|
||||
}
|
||||
@@ -18,9 +18,16 @@ package com.android.systemui.accessibility.floatingmenu;
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.graphics.PointF;
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.View;
|
||||
import android.view.ViewPropertyAnimator;
|
||||
import android.view.WindowManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
@@ -36,6 +43,8 @@ import org.junit.runner.RunWith;
|
||||
@TestableLooper.RunWithLooper(setAsMainLooper = true)
|
||||
@SmallTest
|
||||
public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
|
||||
private ViewPropertyAnimator mViewPropertyAnimator;
|
||||
private MenuView mMenuView;
|
||||
private MenuAnimationController mMenuAnimationController;
|
||||
|
||||
@@ -45,7 +54,11 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext,
|
||||
stubWindowManager);
|
||||
final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext);
|
||||
mMenuView = new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance);
|
||||
|
||||
mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance));
|
||||
mViewPropertyAnimator = spy(mMenuView.animate());
|
||||
doReturn(mViewPropertyAnimator).when(mMenuView).animate();
|
||||
|
||||
mMenuAnimationController = new MenuAnimationController(mMenuView);
|
||||
}
|
||||
|
||||
@@ -58,4 +71,20 @@ public class MenuAnimationControllerTest extends SysuiTestCase {
|
||||
assertThat(mMenuView.getTranslationX()).isEqualTo(50);
|
||||
assertThat(mMenuView.getTranslationY()).isEqualTo(60);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startShrinkAnimation_verifyAnimationEndAction() {
|
||||
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(View.VISIBLE));
|
||||
|
||||
verify(mViewPropertyAnimator).withEndAction(any(Runnable.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startGrowAnimation_menuCompletelyOpaque() {
|
||||
mMenuAnimationController.startShrinkAnimation(null);
|
||||
|
||||
mMenuAnimationController.startGrowAnimation();
|
||||
|
||||
assertThat(mMenuView.getAlpha()).isEqualTo(/* completelyOpaque */ 1.0f);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -21,6 +21,8 @@ import static android.view.View.OVER_SCROLL_NEVER;
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.anyFloat;
|
||||
import static org.mockito.Mockito.any;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
@@ -36,6 +38,7 @@ import androidx.test.filters.SmallTest;
|
||||
import com.android.internal.accessibility.dialog.AccessibilityTarget;
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
import com.android.systemui.accessibility.MotionEventHelper;
|
||||
import com.android.wm.shell.bubbles.DismissView;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
@@ -57,7 +60,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
|
||||
private MenuView mStubMenuView;
|
||||
private MenuListViewTouchHandler mTouchHandler;
|
||||
private MenuAnimationController mMenuAnimationController;
|
||||
private DismissAnimationController mDismissAnimationController;
|
||||
private RecyclerView mStubListView;
|
||||
private DismissView mDismissView;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
@@ -69,7 +74,11 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
|
||||
mStubMenuView.setTranslationX(0);
|
||||
mStubMenuView.setTranslationY(0);
|
||||
mMenuAnimationController = spy(new MenuAnimationController(mStubMenuView));
|
||||
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController);
|
||||
mDismissView = spy(new DismissView(mContext));
|
||||
mDismissAnimationController =
|
||||
spy(new DismissAnimationController(mDismissView, mStubMenuView));
|
||||
mTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
|
||||
mDismissAnimationController);
|
||||
final AccessibilityTargetAdapter stubAdapter = new AccessibilityTargetAdapter(mStubTargets);
|
||||
mStubListView = (RecyclerView) mStubMenuView.getChildAt(0);
|
||||
mStubListView.setAdapter(stubAdapter);
|
||||
@@ -88,7 +97,9 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActionMoveEvent_shouldMoveToPosition() {
|
||||
public void onActionMoveEvent_notConsumedEvent_shouldMoveToPosition() {
|
||||
doReturn(false).when(mDismissAnimationController).maybeConsumeMoveMotionEvent(
|
||||
any(MotionEvent.class));
|
||||
final int offset = 100;
|
||||
final MotionEvent stubDownEvent =
|
||||
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
|
||||
@@ -107,6 +118,24 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
|
||||
assertThat(mStubMenuView.getTranslationY()).isEqualTo(offset);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void onActionMoveEvent_shouldShowDismissView() {
|
||||
final int offset = 100;
|
||||
final MotionEvent stubDownEvent =
|
||||
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
|
||||
MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
|
||||
mStubMenuView.getTranslationY());
|
||||
final MotionEvent stubMoveEvent =
|
||||
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 3,
|
||||
MotionEvent.ACTION_MOVE, mStubMenuView.getTranslationX() + offset,
|
||||
mStubMenuView.getTranslationY() + offset);
|
||||
|
||||
mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
|
||||
mTouchHandler.onInterceptTouchEvent(mStubListView, stubMoveEvent);
|
||||
|
||||
verify(mDismissView).show();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void dragAndDrop_shouldFlingMenuThenSpringToEdge() {
|
||||
final int offset = 100;
|
||||
|
||||
@@ -34,6 +34,7 @@ import android.view.ViewGroup;
|
||||
import android.view.WindowInsets;
|
||||
import android.view.WindowManager;
|
||||
import android.view.WindowMetrics;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
@@ -58,6 +59,9 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
|
||||
@Mock
|
||||
private WindowManager mWindowManager;
|
||||
|
||||
@Mock
|
||||
private AccessibilityManager mAccessibilityManager;
|
||||
|
||||
@Mock
|
||||
private WindowMetrics mWindowMetrics;
|
||||
|
||||
@@ -72,7 +76,8 @@ public class MenuViewLayerControllerTest extends SysuiTestCase {
|
||||
when(mWindowManager.getCurrentWindowMetrics()).thenReturn(mWindowMetrics);
|
||||
when(mWindowMetrics.getBounds()).thenReturn(new Rect(0, 0, 1080, 2340));
|
||||
when(mWindowMetrics.getWindowInsets()).thenReturn(stubDisplayInsets());
|
||||
mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager);
|
||||
mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager,
|
||||
mAccessibilityManager);
|
||||
}
|
||||
|
||||
@Test
|
||||
|
||||
@@ -23,18 +23,25 @@ import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.Laye
|
||||
|
||||
import static com.google.common.truth.Truth.assertThat;
|
||||
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
||||
import android.testing.AndroidTestingRunner;
|
||||
import android.testing.TestableLooper;
|
||||
import android.view.View;
|
||||
import android.view.WindowManager;
|
||||
import android.view.accessibility.AccessibilityManager;
|
||||
|
||||
import androidx.test.filters.SmallTest;
|
||||
|
||||
import com.android.systemui.SysuiTestCase;
|
||||
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
/** Tests for {@link MenuViewLayer}. */
|
||||
@RunWith(AndroidTestingRunner.class)
|
||||
@@ -43,10 +50,19 @@ import org.junit.runner.RunWith;
|
||||
public class MenuViewLayerTest extends SysuiTestCase {
|
||||
private MenuViewLayer mMenuViewLayer;
|
||||
|
||||
@Rule
|
||||
public MockitoRule mockito = MockitoJUnit.rule();
|
||||
|
||||
@Mock
|
||||
private IAccessibilityFloatingMenu mFloatingMenu;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
final WindowManager stubWindowManager = mContext.getSystemService(WindowManager.class);
|
||||
mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager);
|
||||
final AccessibilityManager stubAccessibilityManager = mContext.getSystemService(
|
||||
AccessibilityManager.class);
|
||||
mMenuViewLayer = new MenuViewLayer(mContext, stubWindowManager, stubAccessibilityManager,
|
||||
mFloatingMenu);
|
||||
}
|
||||
|
||||
@Test
|
||||
@@ -64,4 +80,11 @@ public class MenuViewLayerTest extends SysuiTestCase {
|
||||
|
||||
assertThat(menuView.getVisibility()).isEqualTo(GONE);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void tiggerDismissMenuAction_hideFloatingMenu() {
|
||||
mMenuViewLayer.mDismissMenuAction.run();
|
||||
|
||||
verify(mFloatingMenu).hide();
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user