diff options
8 files changed, 275 insertions, 26 deletions
diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_tooltip.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_tooltip.xml index 601198bb7e09..5e7b7e16dbdc 100644 --- a/packages/SystemUI/res/layout/accessibility_floating_menu_tooltip.xml +++ b/packages/SystemUI/res/layout/accessibility_floating_menu_tooltip.xml @@ -22,6 +22,14 @@ android:layout_height="wrap_content" android:orientation="horizontal"> + <View + android:id="@+id/arrow_left" + android:layout_width="@dimen/accessibility_floating_tooltip_arrow_width" + android:layout_height="@dimen/accessibility_floating_tooltip_arrow_height" + android:layout_marginRight="@dimen/accessibility_floating_tooltip_arrow_margin" + android:visibility="gone" + android:layout_gravity="center_vertical"/> + <TextView android:id="@+id/text" android:layout_width="wrap_content" @@ -35,9 +43,10 @@ android:textSize="@dimen/accessibility_floating_tooltip_font_size"/> <View - android:id="@+id/arrow" + android:id="@+id/arrow_right" android:layout_width="@dimen/accessibility_floating_tooltip_arrow_width" android:layout_height="@dimen/accessibility_floating_tooltip_arrow_height" android:layout_marginLeft="@dimen/accessibility_floating_tooltip_arrow_margin" + android:visibility="gone" android:layout_gravity="center_vertical"/> </LinearLayout> diff --git a/packages/SystemUI/src/com/android/systemui/Prefs.java b/packages/SystemUI/src/com/android/systemui/Prefs.java index 7f183798709c..bf09975bb034 100644 --- a/packages/SystemUI/src/com/android/systemui/Prefs.java +++ b/packages/SystemUI/src/com/android/systemui/Prefs.java @@ -73,7 +73,8 @@ public final class Prefs { Key.TOUCHED_RINGER_TOGGLE, Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP, 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 }) // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them public @interface Key { @@ -122,6 +123,8 @@ public final class Prefs { String HAS_SEEN_ODI_CAPTIONS_TOOLTIP = "HasSeenODICaptionsTooltip"; String HAS_SEEN_REVERSE_BOTTOM_SHEET = "HasSeenReverseBottomSheet"; String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount"; + String HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP = + "HasSeenAccessibilityFloatingMenuDockTooltip"; } public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java index fd10684e6fca..ee6276813512 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java @@ -24,6 +24,7 @@ import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE; import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON; import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets; +import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType; import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType; @@ -35,6 +36,7 @@ import android.os.UserHandle; import android.provider.Settings; import com.android.internal.annotations.VisibleForTesting; +import com.android.systemui.Prefs; /** * Contains logic for an accessibility floating menu view. @@ -46,6 +48,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { private final Context mContext; private final AccessibilityFloatingMenuView mMenuView; private final MigrationTooltipView mMigrationTooltipView; + private final DockTooltipView mDockTooltipView; private final Handler mHandler = new Handler(Looper.getMainLooper()); private final ContentObserver mContentObserver = @@ -90,6 +93,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { mContext = context; mMenuView = menuView; mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView); + mDockTooltipView = new DockTooltipView(mContext, mMenuView); } @Override @@ -109,6 +113,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { getOpacityValue(mContext)); mMenuView.setSizeType(getSizeType(mContext)); mMenuView.setShapeType(getShapeType(mContext)); + mMenuView.setOnDragEndListener(this::showDockTooltipIfNecessary); showMigrationTooltipIfNecessary(); @@ -123,6 +128,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { mMenuView.hide(); mMigrationTooltipView.hide(); + mDockTooltipView.hide(); unregisterContentObservers(); } @@ -144,6 +150,21 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1; } + /** + * Shows tooltip when user drags accessibility floating menu for the first time. + */ + private void showDockTooltipIfNecessary() { + if (!Prefs.get(mContext).getBoolean( + 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 (mMenuView.isOvalShape()) { + mDockTooltipView.show(); + } + + Prefs.putBoolean(mContext, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true); + } + } + private static boolean isFadeEffectEnabled(Context context) { return Settings.Secure.getInt( context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED, diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java index 96a4a2aa91ba..49e6b47b3b79 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -19,6 +19,8 @@ package com.android.systemui.accessibility.floatingmenu; import static android.util.MathUtils.constrain; import static android.util.MathUtils.sq; +import static java.util.Objects.requireNonNull; + import android.animation.Animator; import android.animation.AnimatorListenerAdapter; import android.animation.ValueAnimator; @@ -43,7 +45,9 @@ import android.view.ViewGroup; import android.view.WindowManager; import android.view.accessibility.AccessibilityNodeInfo; import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction; +import android.view.animation.Animation; import android.view.animation.OvershootInterpolator; +import android.view.animation.TranslateAnimation; import android.widget.FrameLayout; import androidx.annotation.DimenRes; @@ -60,6 +64,7 @@ import java.lang.annotation.RetentionPolicy; import java.util.ArrayList; import java.util.Collections; import java.util.List; +import java.util.Optional; /** * Accessibility floating menu is used for the actions of accessibility features, it's also the @@ -78,6 +83,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout 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_DURATION_MS = 600; + private static final float ANIMATION_TO_X_VALUE = 0.5f; + private boolean mIsFadeEffectEnabled; private boolean mIsShowing; private boolean mIsDownInEnlargedTouchArea; @@ -107,6 +116,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout private float mPercentageY = LOCATION_Y_PERCENTAGE; private float mSquareScaledTouchSlop; private final Configuration mLastConfiguration; + private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty(); private final RecyclerView mListView; private final AccessibilityTargetAdapter mAdapter; private float mFadeOutValue; @@ -161,6 +171,17 @@ public class AccessibilityFloatingMenuView extends FrameLayout int RIGHT = 1; } + /** + * Interface for a callback to be invoked when the floating menu was dragging. + */ + interface OnDragEndListener { + + /** + * Invoked when the floating menu has dragged end. + */ + void onDragEnd(); + } + public AccessibilityFloatingMenuView(Context context) { this(context, new RecyclerView(context)); } @@ -201,6 +222,8 @@ public class AccessibilityFloatingMenuView extends FrameLayout updateRadiusWith(mSizeType, mRadiusType, mTargets.size()); fadeOut(); + + mOnDragEndListener.ifPresent(OnDragEndListener::onDragEnd); } }); @@ -266,7 +289,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout // Must switch the oval shape type before tapping the corresponding item in the // list view, otherwise it can't work on it. - if (mShapeType == ShapeType.HALF_OVAL) { + if (!isOvalShape()) { setShapeType(ShapeType.OVAL); return true; @@ -363,6 +386,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout return mIsShowing; } + boolean isOvalShape() { + return mShapeType == ShapeType.OVAL; + } + void onTargetsChanged(List<AccessibilityTarget> newTargets) { fadeIn(); @@ -407,6 +434,35 @@ public class AccessibilityFloatingMenuView extends FrameLayout fadeOut(); } + public void setOnDragEndListener(OnDragEndListener onDragListener) { + mOnDragEndListener = Optional.ofNullable(onDragListener); + } + + void startTranslateXAnimation() { + fadeIn(); + + final float toXValue = mAlignment == Alignment.RIGHT + ? ANIMATION_TO_X_VALUE + : -ANIMATION_TO_X_VALUE; + final TranslateAnimation animation = + new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, toXValue, + Animation.RELATIVE_TO_SELF, 0, + Animation.RELATIVE_TO_SELF, 0); + animation.setDuration(ANIMATION_DURATION_MS); + animation.setRepeatMode(Animation.REVERSE); + animation.setInterpolator(new OvershootInterpolator()); + animation.setRepeatCount(Animation.INFINITE); + animation.setStartOffset(ANIMATION_START_OFFSET); + mListView.startAnimation(animation); + } + + void stopTranslateXAnimation() { + mListView.clearAnimation(); + + fadeOut(); + } + Rect getWindowLocationOnScreen() { final int left = mCurrentLayoutParams.x; final int top = mCurrentLayoutParams.y; @@ -536,11 +592,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout } private Handler createUiHandler() { - final Looper looper = Looper.myLooper(); - if (looper == null) { - throw new IllegalArgumentException("looper must not be null"); - } - return new Handler(looper); + return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null")); } private void updateDimensions() { diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java index a5d5f1267eaa..1abf559d6846 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/BaseTooltipView.java @@ -66,7 +66,6 @@ class BaseTooltipView extends FrameLayout { private int mArrowCornerRadius; private int mScreenWidth; private boolean mIsShowing; - private View mArrowView; private TextView mTextView; private final WindowManager.LayoutParams mCurrentLayoutParams; private final WindowManager mWindowManager; @@ -87,12 +86,11 @@ class BaseTooltipView extends FrameLayout { super.onConfigurationChanged(newConfig); updateDimensions(); - updateTextView(); - updateArrow(); mAnchorView.onConfigurationChanged(newConfig); final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen(); + updateArrowWith(anchorViewLocation); updateWidthWith(anchorViewLocation); updateLocationWith(anchorViewLocation); @@ -132,6 +130,7 @@ class BaseTooltipView extends FrameLayout { mIsShowing = true; final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen(); + updateArrowWith(anchorViewLocation); updateWidthWith(anchorViewLocation); updateLocationWith(anchorViewLocation); @@ -164,9 +163,6 @@ class BaseTooltipView extends FrameLayout { LayoutInflater.from(getContext()).inflate( R.layout.accessibility_floating_menu_tooltip, this, false); - mArrowView = contentView.findViewById(R.id.arrow); - drawArrow(mArrowView); - mTextView = contentView.findViewById(R.id.text); addView(contentView); @@ -220,17 +216,23 @@ class BaseTooltipView extends FrameLayout { gradientDrawable.setCornerRadius(mTextViewCornerRadius); } - private void updateArrow() { - final ShapeDrawable shapeDrawable = (ShapeDrawable) mArrowView.getBackground(); - final Paint paint = shapeDrawable.getPaint(); - paint.setPathEffect(new CornerPathEffect(mArrowCornerRadius)); + private void updateArrowWith(Rect anchorViewLocation) { + final boolean isAnchorViewOnLeft = isAnchorViewOnLeft(anchorViewLocation); + final View arrowView = findViewById(isAnchorViewOnLeft + ? R.id.arrow_left + : R.id.arrow_right); + arrowView.setVisibility(VISIBLE); + drawArrow(arrowView, isAnchorViewOnLeft); final LinearLayout.LayoutParams layoutParams = - (LinearLayout.LayoutParams) mArrowView.getLayoutParams(); + (LinearLayout.LayoutParams) arrowView.getLayoutParams(); layoutParams.width = mArrowWidth; layoutParams.height = mArrowHeight; - layoutParams.setMargins(mArrowMargin, 0, 0, 0); - mArrowView.setLayoutParams(layoutParams); + + final int leftMargin = isAnchorViewOnLeft ? 0 : mArrowMargin; + final int rightMargin = isAnchorViewOnLeft ? mArrowMargin : 0; + layoutParams.setMargins(leftMargin, 0, rightMargin, 0); + arrowView.setLayoutParams(layoutParams); } private void updateWidthWith(Rect anchorViewLocation) { @@ -240,17 +242,19 @@ class BaseTooltipView extends FrameLayout { } private void updateLocationWith(Rect anchorViewLocation) { - mCurrentLayoutParams.x = - mScreenWidth - getWindowWidthWith(anchorViewLocation) - anchorViewLocation.width(); + mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation) + ? anchorViewLocation.width() + : mScreenWidth - getWindowWidthWith(anchorViewLocation) + - anchorViewLocation.width(); mCurrentLayoutParams.y = anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2); } - private void drawArrow(View view) { + private void drawArrow(View view, boolean isPointingLeft) { final ViewGroup.LayoutParams layoutParams = view.getLayoutParams(); final TriangleShape triangleShape = - TriangleShape.createHorizontal(layoutParams.width, layoutParams.height, - false); + TriangleShape.createHorizontal(layoutParams.width, layoutParams.height, + isPointingLeft); final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape); final Paint arrowPaint = arrowDrawable.getPaint(); arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(), @@ -260,6 +264,10 @@ class BaseTooltipView extends FrameLayout { view.setBackground(arrowDrawable); } + private boolean isAnchorViewOnLeft(Rect anchorViewLocation) { + return anchorViewLocation.left < (mScreenWidth / 2); + } + private int getTextWidthWith(Rect anchorViewLocation) { final int widthSpec = MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java new file mode 100644 index 000000000000..49056a6039ec --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DockTooltipView.java @@ -0,0 +1,50 @@ +/* + * 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.content.Context; + +import com.android.systemui.R; + +/** + * Dock tooltip view that shows the info about moving the Accessibility button to the edge to hide. + */ +class DockTooltipView extends BaseTooltipView { + private final AccessibilityFloatingMenuView mAnchorView; + + DockTooltipView(Context context, AccessibilityFloatingMenuView anchorView) { + super(context, anchorView); + mAnchorView = anchorView; + + setDescription( + getContext().getText(R.string.accessibility_floating_button_docking_tooltip)); + } + + @Override + void hide() { + super.hide(); + + mAnchorView.stopTranslateXAnimation(); + } + + @Override + void show() { + super.show(); + + mAnchorView.startTranslateXAnimation(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java index 3829330f5235..e4f3e3145998 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MigrationTooltipView.java @@ -37,6 +37,7 @@ class MigrationTooltipView extends BaseTooltipView { intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Intent.EXTRA_COMPONENT_NAME, ACCESSIBILITY_BUTTON_COMPONENT_NAME.flattenToShortString()); + final AnnotationLinkSpan.LinkInfo linkInfo = new AnnotationLinkSpan.LinkInfo( AnnotationLinkSpan.LinkInfo.DEFAULT_ANNOTATION, v -> { diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java new file mode 100644 index 000000000000..41b948fb80b0 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/DockTooltipViewTest.java @@ -0,0 +1,105 @@ +/* + * 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 org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.MotionEvent; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; +import com.android.systemui.accessibility.MotionEventHelper; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +/** Tests for {@link DockTooltipView}. */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class DockTooltipViewTest extends SysuiTestCase { + + @Mock + private WindowManager mWindowManager; + + private AccessibilityFloatingMenuView mMenuView; + private DockTooltipView mDockTooltipView; + private final MotionEventHelper mMotionEventHelper = new MotionEventHelper(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final WindowManager wm = mContext.getSystemService(WindowManager.class); + doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( + mWindowManager).getMaximumWindowMetrics(); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + + mMenuView = spy(new AccessibilityFloatingMenuView(mContext)); + mDockTooltipView = new DockTooltipView(mContext, mMenuView); + } + + @Test + public void showTooltip_success() { + mDockTooltipView.show(); + + verify(mMenuView).startTranslateXAnimation(); + verify(mWindowManager).addView(eq(mDockTooltipView), any(WindowManager.LayoutParams.class)); + } + + @Test + public void hideTooltip_success() { + mDockTooltipView.show(); + mDockTooltipView.hide(); + + verify(mMenuView).stopTranslateXAnimation(); + verify(mWindowManager).removeView(mDockTooltipView); + } + + @Test + public void touchOutsideWhenToolTipViewShown_stopAnimation() { + final MotionEvent outsideEvent = + mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, + /* eventTime= */ 1, + MotionEvent.ACTION_OUTSIDE, + /* x= */ 0, + /* y= */ 0); + + mDockTooltipView.show(); + mDockTooltipView.dispatchTouchEvent(outsideEvent); + + verify(mMenuView).stopTranslateXAnimation(); + } + + @After + public void tearDown() { + mMotionEventHelper.recycleEvents(); + } +} |