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

Commit f57262c3 authored by Jason Hsu's avatar Jason Hsu Committed by Android (Google) Code Review
Browse files

Merge "Maintain the position of accessibility floating menu" into sc-dev

parents e77df8f5 d2f7022a
Loading
Loading
Loading
Loading
+3 −1
Original line number Diff line number Diff line
@@ -74,7 +74,8 @@ public final class Prefs {
            Key.HAS_SEEN_ODI_CAPTIONS_TOOLTIP,
            Key.HAS_SEEN_REVERSE_BOTTOM_SHEET,
            Key.CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT,
            Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP
            Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP,
            Key.ACCESSIBILITY_FLOATING_MENU_POSITION
    })
    // TODO: annotate these with their types so {@link PrefsCommandLine} can know how to set them
    public @interface Key {
@@ -125,6 +126,7 @@ public final class Prefs {
        String CONTROLS_STRUCTURE_SWIPE_TOOLTIP_COUNT = "ControlsStructureSwipeTooltipCount";
        String HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP =
                "HasSeenAccessibilityFloatingMenuDockTooltip";
        String ACCESSIBILITY_FLOATING_MENU_POSITION = "AccessibilityFloatingMenuPosition";
    }

    public static boolean getBoolean(Context context, @Key String key, boolean defaultValue) {
+41 −5
Original line number Diff line number Diff line
@@ -28,12 +28,16 @@ import static com.android.systemui.Prefs.Key.HAS_SEEN_ACCESSIBILITY_FLOATING_MEN
import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.ShapeType;
import static com.android.systemui.accessibility.floatingmenu.AccessibilityFloatingMenuView.SizeType;

import android.annotation.FloatRange;
import android.content.Context;
import android.database.ContentObserver;
import android.os.Handler;
import android.os.Looper;
import android.os.UserHandle;
import android.provider.Settings;
import android.text.TextUtils;

import androidx.annotation.NonNull;

import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
@@ -44,7 +48,13 @@ import com.android.systemui.Prefs;
public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
    private static final int DEFAULT_FADE_EFFECT_IS_ENABLED = 1;
    private static final int DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED = 0;
    @FloatRange(from = 0.0, to = 1.0)
    private static final float DEFAULT_OPACITY_VALUE = 0.55f;
    @FloatRange(from = 0.0, to = 1.0)
    private static final float DEFAULT_POSITION_X_PERCENT = 1.0f;
    @FloatRange(from = 0.0, to = 1.0)
    private static final float DEFAULT_POSITION_Y_PERCENT = 0.8f;

    private final Context mContext;
    private final AccessibilityFloatingMenuView mMenuView;
    private final MigrationTooltipView mMigrationTooltipView;
@@ -85,7 +95,10 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
            };

    public AccessibilityFloatingMenu(Context context) {
        this(context, new AccessibilityFloatingMenuView(context));
        mContext = context;
        mMenuView = new AccessibilityFloatingMenuView(context, getPosition(context));
        mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
        mDockTooltipView = new DockTooltipView(mContext, mMenuView);
    }

    @VisibleForTesting
@@ -113,7 +126,7 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
                getOpacityValue(mContext));
        mMenuView.setSizeType(getSizeType(mContext));
        mMenuView.setShapeType(getShapeType(mContext));
        mMenuView.setOnDragEndListener(this::showDockTooltipIfNecessary);
        mMenuView.setOnDragEndListener(this::onDragEnd);

        showMigrationTooltipIfNecessary();

@@ -127,12 +140,25 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
        }

        mMenuView.hide();
        mMenuView.setOnDragEndListener(null);
        mMigrationTooltipView.hide();
        mDockTooltipView.hide();

        unregisterContentObservers();
    }

    @NonNull
    private Position getPosition(Context context) {
        final String absolutePositionString = Prefs.getString(context,
                Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION, /* defaultValue= */ null);

        if (TextUtils.isEmpty(absolutePositionString)) {
            return new Position(DEFAULT_POSITION_X_PERCENT, DEFAULT_POSITION_Y_PERCENT);
        } else {
            return Position.fromString(absolutePositionString);
        }
    }

    // Migration tooltip was the android S feature. It's just used on the Android version from R
    // to S. In addition, it only shows once.
    private void showMigrationTooltipIfNecessary() {
@@ -150,18 +176,28 @@ public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu {
                DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
    }

    private void onDragEnd(Position position) {
        savePosition(mContext, position);
        showDockTooltipIfNecessary(mContext);
    }

    private void savePosition(Context context, Position position) {
        Prefs.putString(context, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
                position.toString());
    }

    /**
     * Shows tooltip when user drags accessibility floating menu for the first time.
     */
    private void showDockTooltipIfNecessary() {
        if (!Prefs.get(mContext).getBoolean(
    private void showDockTooltipIfNecessary(Context context) {
        if (!Prefs.get(context).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);
            Prefs.putBoolean(context, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true);
        }
    }

+51 −30
Original line number Diff line number Diff line
@@ -26,6 +26,7 @@ import static java.util.Objects.requireNonNull;
import android.animation.Animator;
import android.animation.AnimatorListenerAdapter;
import android.animation.ValueAnimator;
import android.annotation.FloatRange;
import android.annotation.IntDef;
import android.content.Context;
import android.content.pm.ActivityInfo;
@@ -85,7 +86,6 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    private static final int FADE_EFFECT_DURATION_MS = 3000;
    private static final int SNAP_TO_LOCATION_DURATION_MS = 150;
    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;
@@ -97,7 +97,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    private boolean mIsDragging = false;
    private boolean mImeVisibility;
    @Alignment
    private int mAlignment = Alignment.RIGHT;
    private int mAlignment;
    @SizeType
    private int mSizeType = SizeType.SMALL;
    @VisibleForTesting
@@ -105,7 +105,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    int mShapeType = ShapeType.OVAL;
    private int mTemporaryShapeType;
    @RadiusType
    private int mRadiusType = RadiusType.LEFT_HALF_OVAL;
    private int mRadiusType;
    private int mMargin;
    private int mPadding;
    private int mScreenHeight;
@@ -118,7 +118,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    private int mRelativeToPointerDownX;
    private int mRelativeToPointerDownY;
    private float mRadius;
    private float mPercentageY = LOCATION_Y_PERCENTAGE;
    private final Position mPosition;
    private float mSquareScaledTouchSlop;
    private final Configuration mLastConfiguration;
    private Optional<OnDragEndListener> mOnDragEndListener = Optional.empty();
@@ -182,25 +182,35 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    interface OnDragEndListener {

        /**
         * Invoked when the floating menu has dragged end.
         * Called when a drag is completed.
         *
         * @param position Stores information about the position
         */
        void onDragEnd();
        void onDragEnd(Position position);
    }

    public AccessibilityFloatingMenuView(Context context) {
        this(context, new RecyclerView(context));
    public AccessibilityFloatingMenuView(Context context, @NonNull Position position) {
        this(context, position, new RecyclerView(context));
    }

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

        mListView = listView;
        mWindowManager = context.getSystemService(WindowManager.class);
        mCurrentLayoutParams = createDefaultLayoutParams();
        mAdapter = new AccessibilityTargetAdapter(mTargets);
        mUiHandler = createUiHandler();
        mPosition = position;
        mAlignment = transformToAlignment(mPosition.getPercentageX());
        mRadiusType = (mAlignment == Alignment.RIGHT)
                ? RadiusType.LEFT_HALF_OVAL
                : RadiusType.RIGHT_HALF_OVAL;

        updateDimensions();

        mCurrentLayoutParams = createDefaultLayoutParams();

        mFadeOutAnimator = ValueAnimator.ofFloat(1.0f, mFadeOutValue);
        mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
@@ -213,10 +223,11 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        mDragAnimator.addListener(new AnimatorListenerAdapter() {
            @Override
            public void onAnimationEnd(Animator animation) {
                mAlignment = calculateCurrentAlignment();
                mPercentageY = calculateCurrentPercentageY();
                mPosition.update(transformCurrentPercentageXToEdge(),
                        calculateCurrentPercentageY());
                mAlignment = transformToAlignment(mPosition.getPercentageX());

                updateLocationWith(mAlignment, mPercentageY);
                updateLocationWith(mPosition);

                updateInsetWith(getResources().getConfiguration().uiMode, mAlignment);

@@ -227,13 +238,13 @@ public class AccessibilityFloatingMenuView extends FrameLayout

                fadeOut();

                mOnDragEndListener.ifPresent(OnDragEndListener::onDragEnd);
                mOnDragEndListener.ifPresent(
                        onDragEndListener -> onDragEndListener.onDragEnd(mPosition));
            }
        });

        mLastConfiguration = new Configuration(getResources().getConfiguration());

        updateDimensions();
        initListView();
        updateStrokeWith(getResources().getConfiguration().uiMode, mAlignment);
    }
@@ -423,7 +434,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        updateRadiusWith(newSizeType, mRadiusType, mTargets.size());

        // When the icon sized changed, the menu size and location will be impacted.
        updateLocationWith(mAlignment, mPercentageY);
        updateLocationWith(mPosition);
        updateScrollModeWith(hasExceededMaxLayoutHeight());
        updateOffsetWith(mShapeType, mAlignment);
        setSystemGestureExclusion();
@@ -446,14 +457,14 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        fadeOut();
    }

    public void setOnDragEndListener(OnDragEndListener onDragListener) {
        mOnDragEndListener = Optional.ofNullable(onDragListener);
    public void setOnDragEndListener(OnDragEndListener onDragEndListener) {
        mOnDragEndListener = Optional.ofNullable(onDragEndListener);
    }

    void startTranslateXAnimation() {
        fadeIn();

        final float toXValue = mAlignment == Alignment.RIGHT
        final float toXValue = (mAlignment == Alignment.RIGHT)
                ? ANIMATION_TO_X_VALUE
                : -ANIMATION_TO_X_VALUE;
        final TranslateAnimation animation =
@@ -581,7 +592,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        final boolean currentImeVisibility = insets.isVisible(ime());
        if (currentImeVisibility != mImeVisibility) {
            mImeVisibility = currentImeVisibility;
            updateLocationWith(mAlignment, mPercentageY);
            updateLocationWith(mPosition);
        }

        return insets;
@@ -697,8 +708,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        params.receiveInsetsIgnoringZOrder = true;
        params.windowAnimations = android.R.style.Animation_Translucent;
        params.gravity = Gravity.START | Gravity.TOP;
        params.x = getMaxWindowX();
        params.y = (int) (getMaxWindowY() * mPercentageY);
        params.x = (mAlignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
//        params.y = (int) (mPosition.getPercentageY() * getMaxWindowY());
        final int currentLayoutY = (int) (mPosition.getPercentageY() * getMaxWindowY());
        params.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
        updateAccessibilityTitle(params);
        return params;
    }
@@ -716,7 +729,7 @@ public class AccessibilityFloatingMenuView extends FrameLayout
        updateItemViewWith(mSizeType);
        updateColor();
        updateStrokeWith(newConfig.uiMode, mAlignment);
        updateLocationWith(mAlignment, mPercentageY);
        updateLocationWith(mPosition);
        updateRadiusWith(mSizeType, mRadiusType, mTargets.size());
        updateScrollModeWith(hasExceededMaxLayoutHeight());
        setSystemGestureExclusion();
@@ -765,9 +778,10 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    /**
     * Updates the floating menu to be fixed at the side of the screen.
     */
    private void updateLocationWith(@Alignment int side, float percentageCurrentY) {
        mCurrentLayoutParams.x = (side == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
        final int currentLayoutY = (int) (percentageCurrentY * getMaxWindowY());
    private void updateLocationWith(Position position) {
        final @Alignment int alignment = transformToAlignment(position.getPercentageX());
        mCurrentLayoutParams.x = (alignment == Alignment.RIGHT) ? getMaxWindowX() : getMinWindowX();
        final int currentLayoutY = (int) (position.getPercentageY() * getMaxWindowY());
        mCurrentLayoutParams.y = Math.max(MIN_WINDOW_Y, currentLayoutY - getInterval());
        mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
    }
@@ -861,10 +875,17 @@ public class AccessibilityFloatingMenuView extends FrameLayout
    }

    @Alignment
    private int calculateCurrentAlignment() {
        return mCurrentLayoutParams.x >= ((getMinWindowX() + getMaxWindowX()) / 2)
                ? Alignment.RIGHT
                : Alignment.LEFT;
    private int transformToAlignment(@FloatRange(from = 0.0, to = 1.0) float percentageX) {
        return (percentageX < 0.5f) ? Alignment.LEFT : Alignment.RIGHT;
    }

    private float transformCurrentPercentageXToEdge() {
        final float percentageX = calculateCurrentPercentageX();
        return (percentageX < 0.5) ? 0.0f : 1.0f;
    }

    private float calculateCurrentPercentageX() {
        return mCurrentLayoutParams.x / (float) getMaxWindowX();
    }

    private float calculateCurrentPercentageY() {
+83 −0
Original line number Diff line number Diff line
/*
 * 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.annotation.FloatRange;
import android.text.TextUtils;

/**
 * Stores information about the position, which includes percentage of X-axis of the screen,
 * percentage of Y-axis of the screen.
 */
public class Position {

    private static final char STRING_SEPARATOR = ',';
    private static final TextUtils.SimpleStringSplitter sStringCommaSplitter =
            new TextUtils.SimpleStringSplitter(STRING_SEPARATOR);

    private float mPercentageX;
    private float mPercentageY;

    /**
     * Creates a {@link Position} from a encoded string described in {@link #toString()}.
     *
     * @param positionString A string conform to the format described in {@link #toString()}
     * @return A {@link Position} with the given value retrieved from {@code absolutePositionString}
     * @throws IllegalArgumentException If {@code positionString} does not conform to the format
     *                                  described in {@link #toString()}
     */
    public static Position fromString(String positionString) {
        sStringCommaSplitter.setString(positionString);
        if (sStringCommaSplitter.hasNext()) {
            final float percentageX = Float.parseFloat(sStringCommaSplitter.next());
            final float percentageY = Float.parseFloat(sStringCommaSplitter.next());
            return new Position(percentageX, percentageY);
        }

        throw new IllegalArgumentException(
                "Invalid Position string: " + positionString);
    }

    Position(float percentageX, float percentageY) {
        update(percentageX, percentageY);
    }

    @Override
    public String toString() {
        return mPercentageX + ", " + mPercentageY;
    }

    /**
     * Updates the position with {@code percentageX} and {@code percentageY}.
     *
     * @param percentageX the new percentage of X-axis of the screen, from 0.0 to 1.0.
     * @param percentageY the new percentage of Y-axis of the screen, from 0.0 to 1.0.
     */
    public void update(@FloatRange(from = 0.0, to = 1.0) float percentageX,
            @FloatRange(from = 0.0, to = 1.0) float percentageY) {
        mPercentageX = percentageX;
        mPercentageY = percentageY;
    }

    public float getPercentageX() {
        return mPercentageX;
    }

    public float getPercentageY() {
        return mPercentageY;
    }
}
+8 −9
Original line number Diff line number Diff line
@@ -22,7 +22,6 @@ import static com.google.common.truth.Truth.assertThat;

import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;

import android.content.Context;
import android.testing.AndroidTestingRunner;
@@ -31,15 +30,16 @@ import android.view.accessibility.AccessibilityManager;

import androidx.test.filters.SmallTest;

import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.systemui.SysuiTestCase;

import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;

import java.util.ArrayList;
import java.util.List;
@@ -50,6 +50,9 @@ import java.util.List;
@TestableLooper.RunWithLooper
public class AccessibilityFloatingMenuTest extends SysuiTestCase {

    @Rule
    public MockitoRule mockito = MockitoJUnit.rule();

    @Mock
    private AccessibilityManager mAccessibilityManager;

@@ -58,18 +61,14 @@ public class AccessibilityFloatingMenuTest extends SysuiTestCase {

    @Before
    public void initMenu() {
        MockitoAnnotations.initMocks(this);

        final List<AccessibilityTarget> mTargets = new ArrayList<>();
        mTargets.add(mock(AccessibilityTarget.class));

        final List<String> assignedTargets = new ArrayList<>();
        mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager);
        assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME);
        doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets(
                anyInt());

        mMenuView = new AccessibilityFloatingMenuView(mContext);
        final Position position = new Position(0, 0);
        mMenuView = new AccessibilityFloatingMenuView(mContext, position);
        mMenu = new AccessibilityFloatingMenu(mContext, mMenuView);
    }

Loading