Refactor the design and improve the animations of Accessibility Floating Menu(10/n).

Actions:
1) Add the migration tooltip into to indicate users where they could switch the floating menu mode.

Bug: 227715451
Test: atest MenuEduTooltipViewTest MenuListViewTouchHandlerTest
Change-Id: I53ea56b86c38bfcf71431c03c7af6f1d4900a232
This commit is contained in:
Peter Liang
2022-11-22 15:26:30 +08:00
parent 615213dd93
commit cb6558813e
8 changed files with 495 additions and 8 deletions

View File

@@ -0,0 +1,222 @@
/*
* 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.View.MeasureSpec.AT_MOST;
import static android.view.View.MeasureSpec.UNSPECIFIED;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.CornerPathEffect;
import android.graphics.Paint;
import android.graphics.PointF;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.text.method.LinkMovementMethod;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;
/**
* The tooltip view shows the information about the operation of the anchor view {@link MenuView}
* . It's just shown on the left or right of the anchor view.
*/
@SuppressLint("ViewConstructor")
class MenuEduTooltipView extends FrameLayout {
private int mFontSize;
private int mTextViewMargin;
private int mTextViewPadding;
private int mTextViewCornerRadius;
private int mArrowMargin;
private int mArrowWidth;
private int mArrowHeight;
private int mArrowCornerRadius;
private int mColorAccentPrimary;
private View mArrowLeftView;
private View mArrowRightView;
private TextView mMessageView;
private final MenuViewAppearance mMenuViewAppearance;
MenuEduTooltipView(@NonNull Context context, MenuViewAppearance menuViewAppearance) {
super(context);
mMenuViewAppearance = menuViewAppearance;
updateResources();
initViews();
}
@Override
protected void onConfigurationChanged(Configuration newConfig) {
super.onConfigurationChanged(newConfig);
updateResources();
updateMessageView();
updateArrowView();
updateLocationAndVisibility();
}
void show(CharSequence message) {
mMessageView.setText(message);
updateLocationAndVisibility();
}
void updateLocationAndVisibility() {
final boolean isTooltipOnRightOfAnchor = mMenuViewAppearance.isMenuOnLeftSide();
updateArrowVisibilityWith(isTooltipOnRightOfAnchor);
updateLocationWith(getMenuBoundsInParent(), isTooltipOnRightOfAnchor);
}
/**
* Gets the bounds of the {@link MenuView}. Besides, its parent view {@link MenuViewLayer} is
* also the root view of the tooltip view.
*
* @return The menu bounds based on its parent view.
*/
private Rect getMenuBoundsInParent() {
final Rect bounds = new Rect();
final PointF position = mMenuViewAppearance.getMenuPosition();
bounds.set((int) position.x, (int) position.y,
(int) position.x + mMenuViewAppearance.getMenuWidth(),
(int) position.y + mMenuViewAppearance.getMenuHeight());
return bounds;
}
private void updateResources() {
final Resources res = getResources();
mArrowWidth =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
mArrowHeight =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_height);
mArrowMargin =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_arrow_margin);
mArrowCornerRadius =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_arrow_corner_radius);
mFontSize =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_font_size);
mTextViewMargin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_margin);
mTextViewPadding =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_padding);
mTextViewCornerRadius =
res.getDimensionPixelSize(
R.dimen.accessibility_floating_tooltip_text_corner_radius);
mColorAccentPrimary = Utils.getColorAttrDefaultColor(getContext(),
com.android.internal.R.attr.colorAccentPrimary);
}
private void updateLocationWith(Rect anchorBoundsInParent, boolean isTooltipOnRightOfAnchor) {
final int widthSpec = MeasureSpec.makeMeasureSpec(
getAvailableTextViewWidth(isTooltipOnRightOfAnchor), AT_MOST);
final int heightSpec = MeasureSpec.makeMeasureSpec(/* size= */ 0, UNSPECIFIED);
mMessageView.measure(widthSpec, heightSpec);
final LinearLayout.LayoutParams textViewParams =
(LinearLayout.LayoutParams) mMessageView.getLayoutParams();
textViewParams.width = mMessageView.getMeasuredWidth();
mMessageView.setLayoutParams(textViewParams);
final int layoutWidth = mMessageView.getMeasuredWidth() + mArrowWidth + mArrowMargin;
setTranslationX(isTooltipOnRightOfAnchor
? anchorBoundsInParent.right
: anchorBoundsInParent.left - layoutWidth);
setTranslationY(anchorBoundsInParent.centerY() - (mMessageView.getMeasuredHeight() / 2.0f));
}
private void updateMessageView() {
mMessageView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
mMessageView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
mTextViewPadding);
final GradientDrawable gradientDrawable = (GradientDrawable) mMessageView.getBackground();
gradientDrawable.setCornerRadius(mTextViewCornerRadius);
gradientDrawable.setColor(mColorAccentPrimary);
}
private void updateArrowView() {
drawArrow(mArrowLeftView, /* isPointingLeft= */ true);
drawArrow(mArrowRightView, /* isPointingLeft= */ false);
}
private void updateArrowVisibilityWith(boolean isTooltipOnRightOfAnchor) {
if (isTooltipOnRightOfAnchor) {
mArrowLeftView.setVisibility(VISIBLE);
mArrowRightView.setVisibility(GONE);
} else {
mArrowLeftView.setVisibility(GONE);
mArrowRightView.setVisibility(VISIBLE);
}
}
private void drawArrow(View arrowView, boolean isPointingLeft) {
final TriangleShape triangleShape =
TriangleShape.createHorizontal(mArrowWidth, mArrowHeight, isPointingLeft);
final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
final Paint arrowPaint = arrowDrawable.getPaint();
arrowPaint.setColor(mColorAccentPrimary);
final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
arrowPaint.setPathEffect(effect);
arrowView.setBackground(arrowDrawable);
}
private void initViews() {
final View contentView = LayoutInflater.from(getContext()).inflate(
R.layout.accessibility_floating_menu_tooltip, /* root= */ this, /* attachToRoot= */
false);
mMessageView = contentView.findViewById(R.id.text);
mMessageView.setMovementMethod(LinkMovementMethod.getInstance());
mArrowLeftView = contentView.findViewById(R.id.arrow_left);
mArrowRightView = contentView.findViewById(R.id.arrow_right);
updateMessageView();
updateArrowView();
addView(contentView);
}
private int getAvailableTextViewWidth(boolean isOnRightOfAnchor) {
final PointF position = mMenuViewAppearance.getMenuPosition();
final int availableWidth = isOnRightOfAnchor
? mMenuViewAppearance.getMenuDraggableBounds().width() - (int) position.x
: (int) position.x;
return availableWidth - mArrowWidth - mArrowMargin - mTextViewMargin;
}
}

View File

@@ -17,6 +17,7 @@
package com.android.systemui.accessibility.floatingmenu;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
import static android.provider.Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES;
@@ -28,6 +29,7 @@ import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfo
import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
@@ -40,6 +42,8 @@ import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.List;
/**
@@ -52,12 +56,23 @@ 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 static final int DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT = MigrationPrompt.DISABLED;
private final Context mContext;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final OnSettingsContentsChanged mSettingsContentsCallback;
private Position mPercentagePosition;
@IntDef({
MigrationPrompt.DISABLED,
MigrationPrompt.ENABLED,
})
@Retention(RetentionPolicy.SOURCE)
@interface MigrationPrompt {
int DISABLED = 0;
int ENABLED = 1;
}
private final ContentObserver mMenuTargetFeaturesContentObserver =
new ContentObserver(mHandler) {
@Override
@@ -99,6 +114,13 @@ class MenuInfoRepository {
DEFAULT_MOVE_TO_TUCKED_VALUE));
}
void loadMigrationTooltipVisibility(OnInfoReady<Boolean> callback) {
callback.onReady(Settings.Secure.getIntForUser(mContext.getContentResolver(),
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
DEFAULT_MIGRATION_TOOLTIP_VALUE_PROMPT, UserHandle.USER_CURRENT)
== MigrationPrompt.ENABLED);
}
void loadMenuPosition(OnInfoReady<Position> callback) {
callback.onReady(mPercentagePosition);
}
@@ -131,6 +153,13 @@ class MenuInfoRepository {
percentagePosition.toString());
}
void updateMigrationTooltipVisibility(boolean visible) {
Settings.Secure.putIntForUser(mContext.getContentResolver(),
ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
visible ? MigrationPrompt.ENABLED : MigrationPrompt.DISABLED,
UserHandle.USER_CURRENT);
}
private Position getStartPosition() {
final String absolutePositionString = Prefs.getString(mContext,
Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);

View File

@@ -25,6 +25,8 @@ import android.view.ViewConfiguration;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import java.util.Optional;
/**
* Controls the all touch events of the accessibility target features view{@link RecyclerView} in
* the {@link MenuView}. And then compute the gestures' velocity for fling and spring
@@ -39,6 +41,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
private boolean mIsDragging = false;
private float mTouchSlop;
private final DismissAnimationController mDismissAnimationController;
private Optional<Runnable> mOnActionDownEnd = Optional.empty();
MenuListViewTouchHandler(MenuAnimationController menuAnimationController,
DismissAnimationController dismissAnimationController) {
@@ -65,6 +68,8 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
mMenuAnimationController.cancelAnimations();
mDismissAnimationController.maybeConsumeDownMotionEvent(motionEvent);
mOnActionDownEnd.ifPresent(Runnable::run);
break;
case MotionEvent.ACTION_MOVE:
if (mIsDragging || Math.hypot(dx, dy) > mTouchSlop) {
@@ -125,6 +130,10 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {
// Do nothing
}
void setOnActionDownEndListener(Runnable onActionDownEndListener) {
mOnActionDownEnd = Optional.ofNullable(onActionDownEndListener);
}
/**
* Adds a movement to the velocity tracker using raw screen coordinates.
*/

View File

@@ -293,7 +293,7 @@ class MenuViewAppearance {
return bounds;
}
private boolean isMenuOnLeftSide() {
boolean isMenuOnLeftSide() {
return mPercentagePosition.getPercentageX() < 0.5f;
}

View File

@@ -20,6 +20,7 @@ import static android.view.WindowInsets.Type.ime;
import static androidx.core.view.WindowInsetsCompat.Type;
import static com.android.internal.accessibility.AccessibilityShortcutController.ACCESSIBILITY_BUTTON_COMPONENT_NAME;
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;
@@ -27,8 +28,10 @@ import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.In
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
import android.graphics.Rect;
import android.os.Handler;
@@ -37,6 +40,8 @@ import android.os.UserHandle;
import android.provider.Settings;
import android.util.PluralsMessageFormatter;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewTreeObserver;
import android.view.WindowInsets;
import android.view.WindowManager;
import android.view.WindowMetrics;
@@ -45,6 +50,7 @@ import android.widget.FrameLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.lifecycle.Observer;
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
@@ -58,6 +64,7 @@ import java.lang.annotation.RetentionPolicy;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* The basic interactions with the child views {@link MenuView}, {@link DismissView}, and
@@ -66,11 +73,13 @@ import java.util.Map;
* message view would be shown and allowed users to undo it.
*/
@SuppressLint("ViewConstructor")
class MenuViewLayer extends FrameLayout {
class MenuViewLayer extends FrameLayout implements
ViewTreeObserver.OnComputeInternalInsetsListener, View.OnClickListener {
private static final int SHOW_MESSAGE_DELAY_MS = 3000;
private final WindowManager mWindowManager;
private final MenuView mMenuView;
private final MenuListViewTouchHandler mMenuListViewTouchHandler;
private final MenuMessageView mMessageView;
private final DismissView mDismissView;
private final MenuViewAppearance mMenuViewAppearance;
@@ -79,18 +88,33 @@ class MenuViewLayer extends FrameLayout {
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final DismissAnimationController mDismissAnimationController;
private final MenuViewModel mMenuViewModel;
private final Observer<Boolean> mMigrationTooltipObserver =
this::onMigrationTooltipVisibilityChanged;
private final Rect mImeInsetsRect = new Rect();
private boolean mIsMigrationTooltipShowing;
private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
@IntDef({
LayerIndex.MENU_VIEW,
LayerIndex.DISMISS_VIEW,
LayerIndex.MESSAGE_VIEW,
LayerIndex.TOOLTIP_VIEW,
})
@Retention(RetentionPolicy.SOURCE)
@interface LayerIndex {
int MENU_VIEW = 0;
int DISMISS_VIEW = 1;
int MESSAGE_VIEW = 2;
int TOOLTIP_VIEW = 3;
}
@StringDef({
TooltipType.MIGRATION,
})
@Retention(RetentionPolicy.SOURCE)
@interface TooltipType {
String MIGRATION = "migration";
}
@VisibleForTesting
@@ -125,12 +149,11 @@ class MenuViewLayer extends FrameLayout {
mAccessibilityManager = accessibilityManager;
mFloatingMenu = floatingMenu;
final MenuViewModel menuViewModel = new MenuViewModel(context);
mMenuViewModel = new MenuViewModel(context);
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, menuViewModel, mMenuViewAppearance);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
mDismissView = new DismissView(context);
mDismissAnimationController = new DismissAnimationController(mDismissView, mMenuView);
mDismissAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@@ -153,9 +176,9 @@ class MenuViewLayer extends FrameLayout {
}
});
final MenuListViewTouchHandler menuListViewTouchHandler = new MenuListViewTouchHandler(
mMenuAnimationController, mDismissAnimationController);
mMenuView.addOnItemTouchListenerToList(menuListViewTouchHandler);
mMenuListViewTouchHandler = new MenuListViewTouchHandler(mMenuAnimationController,
mDismissAnimationController);
mMenuView.addOnItemTouchListenerToList(mMenuListViewTouchHandler);
mMessageView = new MenuMessageView(context);
@@ -210,7 +233,11 @@ class MenuViewLayer extends FrameLayout {
super.onAttachedToWindow();
mMenuView.show();
setOnClickListener(this);
setOnApplyWindowInsetsListener((view, insets) -> onWindowInsetsApplied(insets));
getViewTreeObserver().addOnComputeInternalInsetsListener(this);
mMenuViewModel.getMigrationTooltipVisibilityData().observeForever(
mMigrationTooltipObserver);
mMessageView.setUndoListener(view -> undo());
mContext.registerComponentCallbacks(mDismissAnimationController);
}
@@ -220,11 +247,31 @@ class MenuViewLayer extends FrameLayout {
super.onDetachedFromWindow();
mMenuView.hide();
setOnClickListener(null);
setOnApplyWindowInsetsListener(null);
getViewTreeObserver().removeOnComputeInternalInsetsListener(this);
mMenuViewModel.getMigrationTooltipVisibilityData().removeObserver(
mMigrationTooltipObserver);
mHandler.removeCallbacksAndMessages(/* token= */ null);
mContext.unregisterComponentCallbacks(mDismissAnimationController);
}
@Override
public void onComputeInternalInsets(ViewTreeObserver.InternalInsetsInfo inoutInfo) {
inoutInfo.setTouchableInsets(ViewTreeObserver.InternalInsetsInfo.TOUCHABLE_INSETS_REGION);
if (mEduTooltipView.isPresent()) {
final int x = (int) getX();
final int y = (int) getY();
inoutInfo.touchableRegion.union(new Rect(x, y, x + getWidth(), y + getHeight()));
}
}
@Override
public void onClick(View v) {
mEduTooltipView.ifPresent(this::removeTooltip);
}
private WindowInsets onWindowInsetsApplied(WindowInsets insets) {
final WindowMetrics windowMetrics = mWindowManager.getCurrentWindowMetrics();
final WindowInsets windowInsets = windowMetrics.getWindowInsets();
@@ -249,6 +296,57 @@ class MenuViewLayer extends FrameLayout {
return insets;
}
private void onMigrationTooltipVisibilityChanged(boolean visible) {
mIsMigrationTooltipShowing = visible;
if (mIsMigrationTooltipShowing) {
mEduTooltipView = Optional.of(new MenuEduTooltipView(mContext, mMenuViewAppearance));
mEduTooltipView.ifPresent(
view -> addTooltipView(view, getMigrationMessage(), TooltipType.MIGRATION));
}
}
private CharSequence getMigrationMessage() {
final Intent intent = new Intent(Settings.ACTION_ACCESSIBILITY_DETAILS_SETTINGS);
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 -> {
getContext().startActivity(intent);
mEduTooltipView.ifPresent(this::removeTooltip);
});
final int textResId = R.string.accessibility_floating_button_migration_tooltip;
return AnnotationLinkSpan.linkify(getContext().getText(textResId), linkInfo);
}
private void addTooltipView(MenuEduTooltipView tooltipView, CharSequence message,
CharSequence tag) {
addView(tooltipView, LayerIndex.TOOLTIP_VIEW);
tooltipView.show(message);
tooltipView.setTag(tag);
mMenuListViewTouchHandler.setOnActionDownEndListener(
() -> mEduTooltipView.ifPresent(this::removeTooltip));
}
private void removeTooltip(View tooltipView) {
if (tooltipView.getTag().equals(TooltipType.MIGRATION)) {
mMenuViewModel.updateMigrationTooltipVisibility(/* visible= */ false);
mIsMigrationTooltipShowing = false;
}
removeView(tooltipView);
mMenuListViewTouchHandler.setOnActionDownEndListener(null);
mEduTooltipView = Optional.empty();
}
private void hideMenuAndShowMessage() {
final int delayTime = mAccessibilityManager.getRecommendedTimeoutMillis(
SHOW_MESSAGE_DELAY_MS,

View File

@@ -36,6 +36,7 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
new MutableLiveData<>();
private final MutableLiveData<Boolean> mMoveToTuckedData = new MutableLiveData<>();
private final MutableLiveData<Boolean> mMigrationTooltipData = new MutableLiveData<>();
private final MutableLiveData<Position> mPercentagePositionData = new MutableLiveData<>();
private final MenuInfoRepository mInfoRepository;
@@ -66,11 +67,20 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
mInfoRepository.updateMenuSavingPosition(percentagePosition);
}
void updateMigrationTooltipVisibility(boolean visible) {
mInfoRepository.updateMigrationTooltipVisibility(visible);
}
LiveData<Boolean> getMoveToTuckedData() {
mInfoRepository.loadMenuMoveToTucked(mMoveToTuckedData::setValue);
return mMoveToTuckedData;
}
LiveData<Boolean> getMigrationTooltipVisibilityData() {
mInfoRepository.loadMigrationTooltipVisibility(mMigrationTooltipData::setValue);
return mMigrationTooltipData;
}
LiveData<Position> getPercentagePositionData() {
mInfoRepository.loadMenuPosition(mPercentagePositionData::setValue);
return mPercentagePositionData;

View File

@@ -0,0 +1,105 @@
/*
* 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 com.google.common.truth.Truth.assertThat;
import android.content.res.Resources;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
import android.view.WindowManager;
import android.widget.TextView;
import androidx.test.filters.SmallTest;
import com.android.systemui.R;
import com.android.systemui.SysuiTestCase;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Tests for {@link MenuEduTooltipView}. */
@SmallTest
@RunWith(AndroidTestingRunner.class)
@TestableLooper.RunWithLooper
public class MenuEduTooltipViewTest extends SysuiTestCase {
private MenuViewAppearance mMenuViewAppearance;
private MenuEduTooltipView mMenuEduTooltipView;
@Before
public void setUp() throws Exception {
final WindowManager windowManager = mContext.getSystemService(WindowManager.class);
mMenuViewAppearance = new MenuViewAppearance(mContext, windowManager);
mMenuEduTooltipView = new MenuEduTooltipView(mContext, mMenuViewAppearance);
}
@Test
public void show_matchMessageText() {
final CharSequence text = "Message";
mMenuEduTooltipView.show(text);
final TextView messageView = mMenuEduTooltipView.findViewById(R.id.text);
assertThat(messageView.getText().toString().contentEquals(text)).isTrue();
}
@Test
public void show_menuOnLeft_onRightOfAnchor() {
mMenuViewAppearance.setPercentagePosition(
new Position(/* percentageX= */ 0.0f, /* percentageY= */ 0.0f));
final CharSequence text = "Message";
mMenuEduTooltipView.show(text);
final int tooltipViewX = (int) (mMenuViewAppearance.getMenuPosition().x
+ mMenuViewAppearance.getMenuWidth());
assertThat(mMenuEduTooltipView.getX()).isEqualTo(tooltipViewX);
}
@Test
public void show_menuCloseToLeftOfCenter_onLeftOfAnchor() {
mMenuViewAppearance.setPercentagePosition(
new Position(/* percentageX= */ 0.4f, /* percentageY= */ 0.0f));
final CharSequence text = "Message";
mMenuEduTooltipView.show(text);
final int tooltipViewX = (int) (mMenuViewAppearance.getMenuPosition().x
+ mMenuViewAppearance.getMenuWidth());
assertThat(mMenuEduTooltipView.getX()).isEqualTo(tooltipViewX);
}
@Test
public void show_menuOnRight_onLeftOfAnchor() {
mMenuViewAppearance.setPercentagePosition(
new Position(/* percentageX= */ 1.0f, /* percentageY= */ 0.0f));
final Resources res = getContext().getResources();
final int arrowWidth =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_width);
final int arrowMargin =
res.getDimensionPixelSize(R.dimen.accessibility_floating_tooltip_arrow_margin);
final CharSequence text = "Message";
mMenuEduTooltipView.show(text);
final TextView messageView = mMenuEduTooltipView.findViewById(R.id.text);
final int layoutWidth = messageView.getMeasuredWidth() + arrowWidth + arrowMargin;
assertThat(mMenuEduTooltipView.getX()).isEqualTo(
mMenuViewAppearance.getMenuPosition().x - layoutWidth);
}
}

View File

@@ -181,6 +181,20 @@ public class MenuListViewTouchHandlerTest extends SysuiTestCase {
verify(mMenuAnimationController).moveToEdgeAndHide();
}
@Test
public void receiveActionDownMotionEvent_verifyOnActionDownEnd() {
final Runnable onActionDownEnd = mock(Runnable.class);
mTouchHandler.setOnActionDownEndListener(onActionDownEnd);
final MotionEvent stubDownEvent =
mMotionEventHelper.obtainMotionEvent(/* downTime= */ 0, /* eventTime= */ 1,
MotionEvent.ACTION_DOWN, mStubMenuView.getTranslationX(),
mStubMenuView.getTranslationY());
mTouchHandler.onInterceptTouchEvent(mStubListView, stubDownEvent);
verify(onActionDownEnd).run();
}
@After
public void tearDown() {
mMotionEventHelper.recycleEvents();