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

Commit 8bfb5c74 authored by PETER LIANG's avatar PETER LIANG Committed by Android (Google) Code Review
Browse files

Merge "Add new floating action menu for Accessibility targets. (3/n)"

parents ab486513 ff5466d3
Loading
Loading
Loading
Loading
+36 −0
Original line number Diff line number Diff line
@@ -16,7 +16,9 @@

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_ICON_TYPE;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY;
import static android.provider.Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE;
import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTTON;

@@ -37,6 +39,8 @@ import com.android.internal.annotations.VisibleForTesting;
 * Contains logic for an accessibility floating menu view.
 */
public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
    private static final int DEFAULT_FADE_EFFECT_ENABLED = 1;
    private static final float DEFAULT_OPACITY_VALUE = 0.55f;
    private final Context mContext;
    private final AccessibilityFloatingMenuView mMenuView;

@@ -64,6 +68,15 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
                }
            };

    private final ContentObserver mFadeOutContentObserver =
            new ContentObserver(new Handler(Looper.getMainLooper())) {
                @Override
                public void onChange(boolean selfChange) {
                    mMenuView.updateOpacityWith(isFadeEffectEnabled(mContext),
                            getOpacityValue(mContext));
                }
            };

    public AccessibilityFloatingMenu(Context context) {
        mContext = context;
        mMenuView = new AccessibilityFloatingMenuView(context);
@@ -88,6 +101,8 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {

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

@@ -105,6 +120,18 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
        unregisterContentObservers();
    }

    private static boolean isFadeEffectEnabled(Context context) {
        return Settings.Secure.getInt(
                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                DEFAULT_FADE_EFFECT_ENABLED) == /* enable */ 1;
    }

    private static float getOpacityValue(Context context) {
        return Settings.Secure.getFloat(
                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_OPACITY,
                DEFAULT_OPACITY_VALUE);
    }

    private static int getSizeType(Context context) {
        return Settings.Secure.getInt(
                context.getContentResolver(), ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
@@ -129,11 +156,20 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_ICON_TYPE),
                /* notifyForDescendants */ false, mShapeContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
                /* notifyForDescendants */ false, mFadeOutContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY),
                /* notifyForDescendants */ false, mFadeOutContentObserver,
                UserHandle.USER_CURRENT);
    }

    private void unregisterContentObservers() {
        mContext.getContentResolver().unregisterContentObserver(mContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mSizeContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mShapeContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mFadeOutContentObserver);
    }
}
+98 −1
Original line number Diff line number Diff line
@@ -16,6 +16,7 @@

package com.android.systemui.accessibility.floatingmenu;

import android.animation.ValueAnimator;
import android.annotation.IntDef;
import android.content.Context;
import android.content.res.Configuration;
@@ -25,6 +26,8 @@ import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.LayerDrawable;
import android.os.Handler;
import android.os.Looper;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.MotionEvent;
@@ -33,6 +36,7 @@ import android.view.WindowManager;
import android.widget.FrameLayout;

import androidx.annotation.DimenRes;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;

@@ -52,9 +56,14 @@ import java.util.List;
 * <p>The number of items would depend on strings key
 * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}.
 */
public class AccessibilityFloatingMenuView extends FrameLayout {
public class AccessibilityFloatingMenuView extends FrameLayout
        implements RecyclerView.OnItemTouchListener {
    private static final float DEFAULT_LOCATION_Y_PERCENTAGE = 0.8f;
    private static final int INDEX_MENU_ITEM = 0;
    private static final int FADE_OUT_DURATION_MS = 1000;
    private static final int FADE_EFFECT_DURATION_MS = 3000;

    private boolean mIsFadeEffectEnabled;
    private boolean mIsShowing;
    @SizeType
    private int mSizeType = SizeType.SMALL;
@@ -66,6 +75,9 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
    private int mIconHeight;
    private final RecyclerView mListView;
    private final AccessibilityTargetAdapter mAdapter;
    private float mFadeOutValue;
    private final ValueAnimator mFadeOutAnimator;
    private final Handler mUiHandler;
    private final WindowManager.LayoutParams mLayoutParams;
    private final WindowManager mWindowManager;
    private final List<AccessibilityTarget> mTargets = new ArrayList<>();
@@ -103,6 +115,12 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        mWindowManager = context.getSystemService(WindowManager.class);
        mLayoutParams = createDefaultLayoutParams();
        mAdapter = new AccessibilityTargetAdapter(mTargets);
        mUiHandler = createUiHandler();

        mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
        mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
        mFadeOutAnimator.addUpdateListener(
                (animation) -> setAlpha((float) animation.getAnimatedValue()));

        updateDimensions();
        initListView();
@@ -111,6 +129,36 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        updateStrokeWith(uiMode);
    }

    @Override
    public boolean onInterceptTouchEvent(@NonNull RecyclerView recyclerView,
            @NonNull MotionEvent event) {

        switch (event.getAction()) {
            case MotionEvent.ACTION_DOWN:
                fadeIn();
                break;
            case MotionEvent.ACTION_MOVE:
                // Do nothing
                break;
            case MotionEvent.ACTION_UP:
            case MotionEvent.ACTION_CANCEL:
                fadeOut();
                break;
            default: // Do nothing
        }
        return false;
    }

    @Override
    public void onTouchEvent(@NonNull RecyclerView recyclerView, @NonNull MotionEvent motionEvent) {
        // Do Nothing
    }

    @Override
    public void onRequestDisallowInterceptTouchEvent(boolean b) {
        // Do Nothing
    }

    void show() {
        if (isShowing()) {
            return;
@@ -134,14 +182,20 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
    }

    void onTargetsChanged(List<AccessibilityTarget> newTargets) {
        fadeIn();

        mTargets.clear();
        mTargets.addAll(newTargets);
        mAdapter.notifyDataSetChanged();

        updateRadiusWith(mSizeType, mTargets.size());

        fadeOut();
    }

    void setSizeType(@SizeType int newSizeType) {
        fadeIn();

        mSizeType = newSizeType;

        updateIconSizeWith(newSizeType);
@@ -149,9 +203,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout {

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

        fadeOut();
    }

    void setShapeType(@ShapeType int newShapeType) {
        fadeIn();

        final boolean isCircleShape =
                newShapeType == ShapeType.CIRCLE;
        final float offset =
@@ -164,6 +222,36 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
                isCircleShape
                        ? null
                        : (view, event) -> onTouched(event));

        fadeOut();
    }

    void updateOpacityWith(boolean isFadeEffectEnabled, float newOpacityValue) {
        mIsFadeEffectEnabled = isFadeEffectEnabled;
        mFadeOutValue = newOpacityValue;

        mFadeOutAnimator.cancel();
        mFadeOutAnimator.setFloatValues(1.0f, mFadeOutValue);
        setAlpha(mIsFadeEffectEnabled ? mFadeOutValue : /* completely opaque */ 1.0f);
    }

    @VisibleForTesting
    void fadeIn() {
        if (!mIsFadeEffectEnabled) {
            return;
        }

        mUiHandler.removeCallbacksAndMessages(null);
        mUiHandler.post(() -> setAlpha(/* completely opaque */ 1.0f));
    }

    @VisibleForTesting
    void fadeOut() {
        if (!mIsFadeEffectEnabled) {
            return;
        }

        mUiHandler.postDelayed(() -> mFadeOutAnimator.start(), FADE_EFFECT_DURATION_MS);
    }

    private boolean onTouched(MotionEvent event) {
@@ -187,6 +275,14 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        getMenuGradientDrawable().setCornerRadii(radii);
    }

    private Handler createUiHandler() {
        final Looper looper = Looper.myLooper();
        if (looper == null) {
            throw new IllegalArgumentException("looper must not be null");
        }
        return new Handler(looper);
    }

    private void updateDimensions() {
        final Resources res = getResources();
        final DisplayMetrics dm = res.getDisplayMetrics();
@@ -218,6 +314,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout {
        mListView.setBackground(listViewBackground);
        mListView.setAdapter(mAdapter);
        mListView.setLayoutManager(layoutManager);
        mListView.addOnItemTouchListener(this);
        updateListView();

        addView(mListView);
+18 −1
Original line number Diff line number Diff line
@@ -82,7 +82,7 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {

        mTargets.add(mock(AccessibilityTarget.class));
        mListView = spy(new RecyclerView(mContext));
        mMenuView = new AccessibilityFloatingMenuView(mContext);
        mMenuView = spy(new AccessibilityFloatingMenuView(mContext));
    }

    @Test
@@ -177,4 +177,21 @@ public class AccessibilityFloatingMenuViewTest extends SysuiTestCase {

        verify(mAnimator).translationX(anyFloat());
    }

    @Test
    public void onTargetsChanged_fadeInOut() {
        mMenuView.onTargetsChanged(mTargets);

        verify(mMenuView).fadeIn();
        verify(mMenuView).fadeOut();
    }

    @Test
    public void setSizeType_fadeInOut() {
        final int smallSize = 0;
        mMenuView.setSizeType(smallSize);

        verify(mMenuView).fadeIn();
        verify(mMenuView).fadeOut();
    }
}