Merge "Add the new dock tooltip for accessibility floating menu." into sc-dev

This commit is contained in:
PETER LIANG
2021-05-12 03:31:01 +00:00
committed by Android (Google) Code Review
8 changed files with 275 additions and 26 deletions

View File

@@ -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>

View File

@@ -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) {

View File

@@ -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,

View File

@@ -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() {

View File

@@ -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);

View File

@@ -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();
}
}

View File

@@ -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 -> {

View File

@@ -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();
}
}