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

Commit 3fe54225 authored by Peter Liang's avatar Peter Liang Committed by PETER LIANG
Browse files

Remove the legacy implementation of the accessibility floating menu.

Goal:
Replace the legacy implementation with a new one.

Bug: 262224538
Test: manual test
Change-Id: I92149aedc57a2dbeec4e0dcba331b025fd24bf61
parent c6ee1e9e
Loading
Loading
Loading
Loading
+0 −277
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 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_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.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;

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.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
import com.android.systemui.Prefs;
import com.android.systemui.shared.system.SysUiStatsLog;
import com.android.systemui.util.settings.SecureSettings;

import java.util.List;

/**
 * Contains logic for an accessibility floating menu view.
 */
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.77f;

    private final Context mContext;
    private final SecureSettings mSecureSettings;
    private final AccessibilityFloatingMenuView mMenuView;
    private final MigrationTooltipView mMigrationTooltipView;
    private final DockTooltipView mDockTooltipView;
    private final Handler mHandler = new Handler(Looper.getMainLooper());

    private final ContentObserver mContentObserver =
            new ContentObserver(mHandler) {
                @Override
                public void onChange(boolean selfChange) {
                    mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON));
                }
            };

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

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

    private final ContentObserver mEnabledA11yServicesContentObserver =
            new ContentObserver(mHandler) {
                @Override
                public void onChange(boolean selfChange) {
                    mMenuView.onEnabledFeaturesChanged();
                }
            };

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

    @VisibleForTesting
    AccessibilityFloatingMenu(Context context, SecureSettings secureSettings,
            AccessibilityFloatingMenuView menuView) {
        mContext = context;
        mSecureSettings = secureSettings;
        mMenuView = menuView;
        mMigrationTooltipView = new MigrationTooltipView(mContext, mMenuView);
        mDockTooltipView = new DockTooltipView(mContext, mMenuView);
    }

    @Override
    public boolean isShowing() {
        return mMenuView.isShowing();
    }

    @Override
    public void show() {
        if (isShowing()) {
            return;
        }
        final List<AccessibilityTarget> targetList = getTargets(mContext, ACCESSIBILITY_BUTTON);
        if (targetList.isEmpty()) {
            return;
        }

        mMenuView.show();
        mMenuView.onTargetsChanged(targetList);
        mMenuView.updateOpacityWith(isFadeEffectEnabled(),
                getOpacityValue());
        mMenuView.setSizeType(getSizeType());
        mMenuView.setShapeType(getShapeType());
        mMenuView.setOnDragEndListener(this::onDragEnd);

        showMigrationTooltipIfNecessary();

        registerContentObservers();
    }

    @Override
    public void hide() {
        if (!isShowing()) {
            return;
        }

        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() {
        if (isMigrationTooltipPromptEnabled()) {
            mMigrationTooltipView.show();

            mSecureSettings.putInt(
                    ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT, /* disabled */ 0);
        }
    }

    private boolean isMigrationTooltipPromptEnabled() {
        return mSecureSettings.getInt(
                ACCESSIBILITY_FLOATING_MENU_MIGRATION_TOOLTIP_PROMPT,
                DEFAULT_MIGRATION_TOOLTIP_PROMPT_IS_DISABLED) == /* enabled */ 1;
    }

    private void onDragEnd(Position position) {
        SysUiStatsLog.write(SysUiStatsLog.ACCESSIBILITY_FLOATING_MENU_UI_CHANGED,
                position.getPercentageX(), position.getPercentageY(),
                mContext.getResources().getConfiguration().orientation);
        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(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(context, HAS_SEEN_ACCESSIBILITY_FLOATING_MENU_DOCK_TOOLTIP, true);
        }
    }

    private boolean isFadeEffectEnabled() {
        return mSecureSettings.getInt(
                ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                DEFAULT_FADE_EFFECT_IS_ENABLED) == /* enabled */ 1;
    }

    private float getOpacityValue() {
        return mSecureSettings.getFloat(
                ACCESSIBILITY_FLOATING_MENU_OPACITY,
                DEFAULT_OPACITY_VALUE);
    }

    private int getSizeType() {
        return mSecureSettings.getInt(
                ACCESSIBILITY_FLOATING_MENU_SIZE, SizeType.SMALL);
    }

    private int getShapeType() {
        return mSecureSettings.getInt(
                ACCESSIBILITY_FLOATING_MENU_ICON_TYPE,
                ShapeType.OVAL);
    }

    private void registerContentObservers() {
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                /* notifyForDescendants */ false, mContentObserver,
                UserHandle.USER_CURRENT);
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS,
                /* notifyForDescendants */ false, mContentObserver,
                UserHandle.USER_CURRENT);
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE,
                /* notifyForDescendants */ false, mSizeContentObserver,
                UserHandle.USER_CURRENT);
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                /* notifyForDescendants */ false, mFadeOutContentObserver,
                UserHandle.USER_CURRENT);
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ACCESSIBILITY_FLOATING_MENU_OPACITY,
                /* notifyForDescendants */ false, mFadeOutContentObserver,
                UserHandle.USER_CURRENT);
        mSecureSettings.registerContentObserverForUser(
                Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
                /* notifyForDescendants */ false,
                mEnabledA11yServicesContentObserver, UserHandle.USER_CURRENT);
    }

    private void unregisterContentObservers() {
        mSecureSettings.unregisterContentObserver(mContentObserver);
        mSecureSettings.unregisterContentObserver(mSizeContentObserver);
        mSecureSettings.unregisterContentObserver(mFadeOutContentObserver);
        mSecureSettings.unregisterContentObserver(
                mEnabledA11yServicesContentObserver);
    }
}
+0 −921

File deleted.

Preview size limit exceeded, changes collapsed.

+0 −299
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 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.UiContext;
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.PixelFormat;
import android.graphics.Rect;
import android.graphics.drawable.GradientDrawable;
import android.graphics.drawable.ShapeDrawable;
import android.os.Bundle;
import android.text.method.MovementMethod;
import android.util.DisplayMetrics;
import android.view.Gravity;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.view.accessibility.AccessibilityNodeInfo;
import android.view.accessibility.AccessibilityNodeInfo.AccessibilityAction;
import android.widget.FrameLayout;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.android.settingslib.Utils;
import com.android.systemui.R;
import com.android.systemui.recents.TriangleShape;

/**
 * Base tooltip view that shows the information about the operation of the
 * Accessibility floating menu. In addition, the anchor view is only for {@link
 * AccessibilityFloatingMenuView}, it should be more suited for displaying one-off menus to avoid
 * the performance hit for the extra window.
 */
class BaseTooltipView 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 mScreenWidth;
    private boolean mIsShowing;
    private TextView mTextView;
    private final WindowManager.LayoutParams mCurrentLayoutParams;
    private final WindowManager mWindowManager;
    private final AccessibilityFloatingMenuView mAnchorView;

    BaseTooltipView(@UiContext Context context, AccessibilityFloatingMenuView anchorView) {
        super(context);
        mWindowManager = context.getSystemService(WindowManager.class);
        mAnchorView = anchorView;
        mCurrentLayoutParams = createDefaultLayoutParams();

        initViews();
    }

    @Override
    protected void onConfigurationChanged(Configuration newConfig) {
        super.onConfigurationChanged(newConfig);

        mAnchorView.onConfigurationChanged(newConfig);
        updateTooltipView();

        mWindowManager.updateViewLayout(this, mCurrentLayoutParams);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        if (event.getAction() == MotionEvent.ACTION_OUTSIDE) {
            hide();
        }

        return super.onTouchEvent(event);
    }

    @Override
    public void onInitializeAccessibilityNodeInfo(AccessibilityNodeInfo info) {
        super.onInitializeAccessibilityNodeInfo(info);

        info.addAction(AccessibilityAction.ACTION_DISMISS);
    }

    @Override
    public boolean performAccessibilityAction(int action, Bundle arguments) {
        if (action == AccessibilityAction.ACTION_DISMISS.getId()) {
            hide();
            return true;
        }

        return super.performAccessibilityAction(action, arguments);
    }

    void show() {
        if (isShowing()) {
            return;
        }

        mIsShowing = true;
        updateTooltipView();

        mWindowManager.addView(this, mCurrentLayoutParams);
    }

    void hide() {
        if (!isShowing()) {
            return;
        }

        mIsShowing = false;
        mWindowManager.removeView(this);
    }

    void setDescription(CharSequence text) {
        mTextView.setText(text);
    }

    void setMovementMethod(MovementMethod movement) {
        mTextView.setMovementMethod(movement);
    }

    private boolean isShowing() {
        return mIsShowing;
    }

    private void initViews() {
        final View contentView =
                LayoutInflater.from(getContext()).inflate(
                        R.layout.accessibility_floating_menu_tooltip, this, false);

        mTextView = contentView.findViewById(R.id.text);

        addView(contentView);
    }

    private static WindowManager.LayoutParams createDefaultLayoutParams() {
        final WindowManager.LayoutParams params = new WindowManager.LayoutParams(
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.WRAP_CONTENT,
                WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL,
                WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE
                        | WindowManager.LayoutParams.FLAG_WATCH_OUTSIDE_TOUCH,
                PixelFormat.TRANSLUCENT);
        params.windowAnimations = android.R.style.Animation_Translucent;
        params.gravity = Gravity.START | Gravity.TOP;

        return params;
    }

    private void updateDimensions() {
        final Resources res = getResources();
        final DisplayMetrics dm = res.getDisplayMetrics();
        mScreenWidth = dm.widthPixels;
        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);
    }

    private void updateTooltipView() {
        updateDimensions();
        updateTextView();

        final Rect anchorViewLocation = mAnchorView.getWindowLocationOnScreen();
        updateArrowWith(anchorViewLocation);
        updateWidthWith(anchorViewLocation);
        updateLocationWith(anchorViewLocation);
    }

    private void updateTextView() {
        mTextView.setTextSize(COMPLEX_UNIT_PX, mFontSize);
        mTextView.setPadding(mTextViewPadding, mTextViewPadding, mTextViewPadding,
                mTextViewPadding);

        final GradientDrawable gradientDrawable = (GradientDrawable) mTextView.getBackground();
        gradientDrawable.setCornerRadius(mTextViewCornerRadius);
        gradientDrawable.setColor(Utils.getColorAttrDefaultColor(getContext(),
                com.android.internal.R.attr.colorAccentPrimary));
    }

    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) arrowView.getLayoutParams();
        layoutParams.width = mArrowWidth;
        layoutParams.height = mArrowHeight;

        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) {
        final ViewGroup.LayoutParams layoutParams = mTextView.getLayoutParams();
        layoutParams.width = getTextWidthWith(anchorViewLocation);
        mTextView.setLayoutParams(layoutParams);
    }

    private void updateLocationWith(Rect anchorViewLocation) {
        mCurrentLayoutParams.x = isAnchorViewOnLeft(anchorViewLocation)
                ? anchorViewLocation.width()
                : mScreenWidth - getWindowWidthWith(anchorViewLocation)
                        - anchorViewLocation.width();
        mCurrentLayoutParams.y =
                anchorViewLocation.centerY() - (getTextHeightWith(anchorViewLocation) / 2);
    }

    private void drawArrow(View view, boolean isPointingLeft) {
        final ViewGroup.LayoutParams layoutParams = view.getLayoutParams();
        final TriangleShape triangleShape =
                TriangleShape.createHorizontal(layoutParams.width, layoutParams.height,
                        isPointingLeft);
        final ShapeDrawable arrowDrawable = new ShapeDrawable(triangleShape);
        final Paint arrowPaint = arrowDrawable.getPaint();
        arrowPaint.setColor(Utils.getColorAttrDefaultColor(getContext(),
                com.android.internal.R.attr.colorAccentPrimary));
        final CornerPathEffect effect = new CornerPathEffect(mArrowCornerRadius);
        arrowPaint.setPathEffect(effect);
        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);
        final int heightSpec =
                MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
        mTextView.measure(widthSpec, heightSpec);
        return mTextView.getMeasuredWidth();
    }

    private int getTextHeightWith(Rect anchorViewLocation) {
        final int widthSpec =
                MeasureSpec.makeMeasureSpec(getAvailableTextWidthWith(anchorViewLocation), AT_MOST);
        final int heightSpec =
                MeasureSpec.makeMeasureSpec(0, UNSPECIFIED);
        mTextView.measure(widthSpec, heightSpec);
        return mTextView.getMeasuredHeight();
    }

    private int getAvailableTextWidthWith(Rect anchorViewLocation) {
        return mScreenWidth - anchorViewLocation.width() - mArrowWidth - mArrowMargin
                - mTextViewMargin;
    }

    private int getWindowWidthWith(Rect anchorViewLocation) {
        return getTextWidthWith(anchorViewLocation) + mArrowWidth + mArrowMargin;
    }
}
+0 −50
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.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();
    }
}
+0 −141

File deleted.

Preview size limit exceeded, changes collapsed.

Loading