Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c26ac8c6 authored by Peter_Liang's avatar Peter_Liang Committed by PETER LIANG
Browse files

Add new floating action menu for Accessibility targets. (2/n)

Patch action:
1. Update the style depends on the settings icon and size key.
2. Under half circle shape mode, the menu will enlarge touch area.

Bug: 173958541
Test: atest AccessibilityFloatingMenuTest AccessibilityFloatingMenuViewTest AccessibilityTargetAdapterTest
Change-Id: Iec53ef71d7e0abe5c7619d4e5f67862ca56dfd1f
parent 2be49c9b
Loading
Loading
Loading
Loading
+2 −2
Original line number Diff line number Diff line
@@ -21,9 +21,9 @@
    <item android:id="@+id/menu_background_item">
        <shape android:shape="rectangle">
            <corners
                android:bottomLeftRadius="@dimen/accessibility_floating_menu_single_radius"
                android:bottomLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
                android:bottomRightRadius="0dp"
                android:topLeftRadius="@dimen/accessibility_floating_menu_single_radius"
                android:topLeftRadius="@dimen/accessibility_floating_menu_small_single_radius"
                android:topRightRadius="0dp"/>
            <solid
                android:color="@color/accessibility_floating_menu_background"/>
+4 −3
Original line number Diff line number Diff line
@@ -19,14 +19,15 @@
    xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="@dimen/accessibility_floating_menu_padding"
    android:paddingStart="@dimen/accessibility_floating_menu_padding"
    android:paddingEnd="@dimen/accessibility_floating_menu_padding"
    android:orientation="vertical"
    android:gravity="center">

    <View
        android:id="@+id/icon_view"
        android:layout_width="@dimen/accessibility_floating_menu_width_height"
        android:layout_height="@dimen/accessibility_floating_menu_width_height"/>
        android:layout_width="@dimen/accessibility_floating_menu_small_width_height"
        android:layout_height="@dimen/accessibility_floating_menu_small_width_height"/>

    <View
        android:id="@+id/transparent_divider"
+6 −3
Original line number Diff line number Diff line
@@ -1358,9 +1358,12 @@
    <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen>
    <dimen name="accessibility_floating_menu_margin">16dp</dimen>
    <dimen name="accessibility_floating_menu_padding">6dp</dimen>
    <dimen name="accessibility_floating_menu_width_height">36dp</dimen>
    <dimen name="accessibility_floating_menu_single_radius">25dp</dimen>
    <dimen name="accessibility_floating_menu_multiple_radius">20dp</dimen>
    <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen>
    <dimen name="accessibility_floating_menu_small_single_radius">25dp</dimen>
    <dimen name="accessibility_floating_menu_small_multiple_radius">20dp</dimen>
    <dimen name="accessibility_floating_menu_large_width_height">56dp</dimen>
    <dimen name="accessibility_floating_menu_large_single_radius">33dp</dimen>
    <dimen name="accessibility_floating_menu_large_multiple_radius">35dp</dimen>

    <dimen name="rounded_slider_height">48dp</dimen>
    <!-- rounded_slider_height / 2 -->
+56 −5
Original line number Diff line number Diff line
@@ -16,9 +16,13 @@

package com.android.systemui.accessibility.floatingmenu;

import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE;
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.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;

import android.content.Context;
import android.database.ContentObserver;
@@ -44,6 +48,22 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
                }
            };

    private final ContentObserver mSizeContentObserver =
            new ContentObserver(new Handler(Looper.getMainLooper())) {
                @Override
                public void onChange(boolean selfChange) {
                    mMenuView.setSizeType(getSizeType(mContext));
                }
            };

    private final ContentObserver mShapeContentObserver =
            new ContentObserver(new Handler(Looper.getMainLooper())) {
                @Override
                public void onChange(boolean selfChange) {
                    mMenuView.setShapeType(getShapeType(mContext));
                }
            };

    public AccessibilityFloatingMenu(Context context) {
        mContext = context;
        mMenuView = new AccessibilityFloatingMenuView(context);
@@ -66,13 +86,12 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
            return;
        }

        mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
        mMenuView.show();
        mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
        mMenuView.setSizeType(getSizeType(mContext));
        mMenuView.setShapeType(getShapeType(mContext));

        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                /* notifyForDescendants */ false, mContentObserver,
                UserHandle.USER_CURRENT);
        registerContentObservers();
    }

    @Override
@@ -83,6 +102,38 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {

        mMenuView.hide();

        unregisterContentObservers();
    }

    private static int getSizeType(Context context) {
        return Settings.Secure.getInt(
                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
    }

    private static int getShapeType(Context context) {
        return Settings.Secure.getInt(
                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
                ShapeType.CIRCLE);
    }

    private void registerContentObservers() {
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS),
                /* notifyForDescendants */ false, mContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
                /* notifyForDescendants */ false, mSizeContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE),
                /* notifyForDescendants */ false, mShapeContentObserver,
                UserHandle.USER_CURRENT);
    }

    private void unregisterContentObservers() {
        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mShapeContentObserver);
    }
}
+170 −32
Original line number Diff line number Diff line
@@ -16,15 +16,18 @@

package com.android.systemui.accessibility.floatingmenu;

import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.FrameLayout;
@@ -34,8 +37,11 @@ import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.R;

import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.List;

@@ -50,21 +56,59 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
    private static final float DEFAULT_LOCATION_Y_PERCENTAGE = 0.8f;
    private static final int INDEX_MENU_ITEM = 0;
    private boolean mIsShowing;
    private RecyclerView mListView;
    @SizeType
    private int mSizeType = SizeType.SMALL;
    private int mMargin;
    private int mPadding;
    private int mScreenHeight;
    private int mScreenWidth;
    private int mIconWidth;
    private int mIconHeight;
    private final RecyclerView mListView;
    private final AccessibilityTargetAdapter mAdapter;
    private final WindowManager.LayoutParams mLayoutParams;
    private final WindowManager mWindowManager;
    private final AccessibilityTargetAdapter mAdapter;
    private final List<AccessibilityTarget> mTargets = new ArrayList<>();

    @IntDef({
            SizeType.SMALL,
            SizeType.LARGE
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface SizeType {
        int SMALL = 0;
        int LARGE = 1;
    }

    @IntDef({
            ShapeType.CIRCLE,
            ShapeType.HALF_CIRCLE
    })
    @Retention(RetentionPolicy.SOURCE)
    @interface ShapeType {
        int CIRCLE = 0;
        int HALF_CIRCLE = 1;
    }

    public AccessibilityFloatingMenuView(Context context) {
        this(context, new RecyclerView(context));
    }

    @VisibleForTesting
    AccessibilityFloatingMenuView(Context context,
            RecyclerView listView) {
        super(context);

        mListView = listView;
        mWindowManager = context.getSystemService(WindowManager.class);
        mLayoutParams = createDefaultLayoutParams();
        mAdapter = new AccessibilityTargetAdapter(mTargets);

        initListView(context, mAdapter);
        updateStroke();
        updateDimensions();
        initListView();

        final int uiMode = context.getResources().getConfiguration().uiMode;
        updateStrokeWith(uiMode);
    }

    void show() {
@@ -94,41 +138,100 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        mTargets.addAll(newTargets);
        mAdapter.notifyDataSetChanged();

        final boolean hasMoreItems = mAdapter.getItemCount() > 1;
        final int resId = hasMoreItems
                ? R.dimen.accessibility_floating_menu_multiple_radius
                : R.dimen.accessibility_floating_menu_single_radius;
        setRadius(resId);
        updateRadiusWith(mSizeType, mTargets.size());
    }

    private void setRadius(@DimenRes int radiusResId) {
        final float radius = getResources().getDimension(radiusResId);
    void setSizeType(@SizeType int newSizeType) {
        mSizeType = newSizeType;

        updateIconSizeWith(newSizeType);
        updateRadiusWith(newSizeType, mTargets.size());

        // When the icon sized changed, the menu size and location will be impacted.
        updateLocation();
    }

    void setShapeType(@ShapeType int newShapeType) {
        final boolean isCircleShape =
                newShapeType == ShapeType.CIRCLE;
        final float offset =
                isCircleShape
                        ? 0
                        : getLayoutWidth() / 2.0f;
        mListView.animate().translationX(offset);

        setOnTouchListener(
                isCircleShape
                        ? null
                        : (view, event) -> onTouched(event));
    }

    private boolean onTouched(MotionEvent event) {
        final int currentX = (int) event.getX();
        final int currentY = (int) event.getY();

        final int menuHalfWidth = getLayoutWidth() / 2;
        final Rect touchDelegateBounds =
                new Rect(mMargin, mMargin, mMargin + menuHalfWidth, mMargin + getLayoutHeight());
        if (touchDelegateBounds.contains(currentX, currentY)) {
            // In order to correspond to the correct item of list view.
            event.setLocation(currentX - mMargin, currentY - mMargin);
            return mListView.dispatchTouchEvent(event);
        }

        return false;
    }

    private void setRadius(float radius) {
        final float[] radii = new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius};
        getMenuGradientDrawable().setCornerRadii(radii);
    }

    private void initListView(Context context, AccessibilityTargetAdapter adapter) {
        final Resources res = context.getResources();
        final int margin =
    private void updateDimensions() {
        final Resources res = getResources();
        final DisplayMetrics dm = res.getDisplayMetrics();
        mScreenWidth = dm.widthPixels;
        mScreenHeight = dm.heightPixels;
        mMargin =
                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin);
        final int elevation =
                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
        mPadding =
                res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_padding);
    }

    private void updateIconSizeWith(@SizeType int sizeType) {
        final Resources res = getResources();
        final int iconResId =
                sizeType == SizeType.SMALL
                        ? R.dimen.accessibility_floating_menu_small_width_height
                        : R.dimen.accessibility_floating_menu_large_width_height;
        mIconWidth = res.getDimensionPixelSize(iconResId);
        mIconHeight = mIconWidth;

        mAdapter.setIconWidthHeight(mIconWidth);
        mAdapter.notifyDataSetChanged();
    }

    private void initListView() {
        final Drawable listViewBackground =
                context.getDrawable(R.drawable.accessibility_floating_menu_background);
                getContext().getDrawable(R.drawable.accessibility_floating_menu_background);
        final LinearLayoutManager layoutManager = new LinearLayoutManager(getContext());
        mListView.setBackground(listViewBackground);
        mListView.setAdapter(mAdapter);
        mListView.setLayoutManager(layoutManager);
        updateListView();

        mListView = new RecyclerView(context);
        final LinearLayoutManager layoutManager = new LinearLayoutManager(context);
        addView(mListView);
    }

    private void updateListView() {
        final int elevation =
                getResources().getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation);
        final LayoutParams layoutParams =
                new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT,
                        ViewGroup.LayoutParams.WRAP_CONTENT);
        layoutParams.setMarginsRelative(margin, margin, /* end= */ 0, margin);
        layoutParams.setMarginsRelative(mMargin, mMargin, /* end= */ 0, mMargin);
        mListView.setLayoutParams(layoutParams);
        mListView.setElevation(elevation);
        mListView.setBackground(listViewBackground);
        mListView.setAdapter(adapter);
        mListView.setLayoutManager(layoutManager);

        addView(mListView);
    }

    private WindowManager.LayoutParams createDefaultLayoutParams() {
@@ -150,9 +253,12 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        updateLocation();
        updateDimensions();
        updateListView();
        updateIconSizeWith(mSizeType);
        updateColor();
        updateStroke();
        updateStrokeWith(newConfig.uiMode);
        updateLocation();
    }

    private LayerDrawable getMenuLayerDrawable() {
@@ -163,10 +269,12 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM);
    }

    /**
     * Updates the floating menu to be fixed at the side of the screen.
     */
    private void updateLocation() {
        final DisplayMetrics dm = getResources().getDisplayMetrics();
        mLayoutParams.x = dm.widthPixels;
        mLayoutParams.y = (int) (dm.heightPixels * DEFAULT_LOCATION_Y_PERCENTAGE);
        mLayoutParams.x = mScreenWidth - mMargin - getLayoutWidth();
        mLayoutParams.y = (int) (mScreenHeight * DEFAULT_LOCATION_Y_PERCENTAGE);
        mWindowManager.updateViewLayout(this, mLayoutParams);
    }

@@ -175,10 +283,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId));
    }

    private void updateStroke() {
    private void updateStrokeWith(int uiMode) {
        final Resources res = getResources();
        final boolean isNightMode =
                (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK)
                (uiMode & Configuration.UI_MODE_NIGHT_MASK)
                        == Configuration.UI_MODE_NIGHT_YES;

        final int inset =
@@ -192,4 +300,34 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark);
        getMenuGradientDrawable().setStroke(strokeWidth, strokeColor);
    }

    private void updateRadiusWith(@SizeType int sizeType, int itemCount) {
        setRadius(getResources().getDimensionPixelSize(getRadiusResId(sizeType, itemCount)));
    }

    private @DimenRes int getRadiusResId(@SizeType int sizeType, int itemCount) {
        return sizeType == SizeType.SMALL
                ? getSmallSizeResIdWith(itemCount)
                : getLargeSizeResIdWith(itemCount);
    }

    private int getSmallSizeResIdWith(int itemCount) {
        return itemCount > 1
                ? R.dimen.accessibility_floating_menu_small_multiple_radius
                : R.dimen.accessibility_floating_menu_small_single_radius;
    }

    private int getLargeSizeResIdWith(int itemCount) {
        return itemCount > 1
                ? R.dimen.accessibility_floating_menu_large_multiple_radius
                : R.dimen.accessibility_floating_menu_large_single_radius;
    }

    private int getLayoutWidth() {
        return mPadding * 2 + mIconWidth;
    }

    private int getLayoutHeight() {
        return (mPadding + mIconHeight) * mTargets.size() + mPadding;
    }
}
Loading