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

Commit 4f62018a authored by Peter Liang's avatar Peter Liang
Browse files

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

Actions of this change:
To avoid users covering the background information when it’s not being used, we support the enable/disable opacity settings for users to apply.

Bug: 227715451
Test: atest MenuInfoRepositoryTest
Change-Id: I8309e5b66383aa5b742393c0294e47e5d33c04df
parent e6ad7646
Loading
Loading
Loading
Loading
+55 −0
Original line number Diff line number Diff line
@@ -16,8 +16,13 @@

package com.android.systemui.accessibility.floatingmenu;

import static java.util.Objects.requireNonNull;

import android.animation.ValueAnimator;
import android.graphics.PointF;
import android.graphics.Rect;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import android.view.View;

@@ -39,13 +44,20 @@ class MenuAnimationController {
    private static final boolean DEBUG = false;
    private static final float MIN_PERCENT = 0.0f;
    private static final float MAX_PERCENT = 1.0f;
    private static final float COMPLETELY_OPAQUE = 1.0f;
    private static final float FLING_FRICTION_SCALAR = 1.9f;
    private static final float DEFAULT_FRICTION = 4.2f;
    private static final float SPRING_AFTER_FLING_DAMPING_RATIO = 0.85f;
    private static final float SPRING_STIFFNESS = 700f;
    private static final float ESCAPE_VELOCITY = 750f;

    private static final int FADE_OUT_DURATION_MS = 1000;
    private static final int FADE_EFFECT_DURATION_MS = 3000;

    private final MenuView mMenuView;
    private final ValueAnimator mFadeOutAnimator;
    private final Handler mHandler;
    private boolean mIsFadeEffectEnabled;

    // Cache the animations state of {@link DynamicAnimation.TRANSLATION_X} and {@link
    // DynamicAnimation.TRANSLATION_Y} to be well controlled by the touch handler
@@ -54,6 +66,12 @@ class MenuAnimationController {

    MenuAnimationController(MenuView menuView) {
        mMenuView = menuView;

        mHandler = createUiHandler();
        mFadeOutAnimator = new ValueAnimator();
        mFadeOutAnimator.setDuration(FADE_OUT_DURATION_MS);
        mFadeOutAnimator.addUpdateListener(
                (animation) -> menuView.setAlpha((float) animation.getAnimatedValue()));
    }

    void moveToPosition(PointF position) {
@@ -210,6 +228,43 @@ class MenuAnimationController {
        mMenuView.persistPositionAndUpdateEdge(new Position(percentageX, percentageY));
    }

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

        mHandler.removeCallbacksAndMessages(/* token= */ null);
        mFadeOutAnimator.cancel();
        mFadeOutAnimator.setFloatValues(COMPLETELY_OPAQUE, newOpacityValue);
        mHandler.post(() -> mMenuView.setAlpha(
                mIsFadeEffectEnabled ? newOpacityValue : COMPLETELY_OPAQUE));
    }

    void fadeInNowIfEnabled() {
        if (!mIsFadeEffectEnabled) {
            return;
        }

        cancelAndRemoveCallbacksAndMessages();
        mHandler.post(() -> mMenuView.setAlpha(COMPLETELY_OPAQUE));
    }

    void fadeOutIfEnabled() {
        if (!mIsFadeEffectEnabled) {
            return;
        }

        cancelAndRemoveCallbacksAndMessages();
        mHandler.postDelayed(mFadeOutAnimator::start, FADE_EFFECT_DURATION_MS);
    }

    private void cancelAndRemoveCallbacksAndMessages() {
        mFadeOutAnimator.cancel();
        mHandler.removeCallbacksAndMessages(/* token= */ null);
    }

    private Handler createUiHandler() {
        return new Handler(requireNonNull(Looper.myLooper(), "looper must not be null"));
    }

    static class MenuPositionProperty
            extends FloatPropertyCompat<MenuView> {
        private final DynamicAnimation.ViewProperty mProperty;
+25 −0
Original line number Diff line number Diff line
/*
 * 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 android.annotation.FloatRange

@FloatRange(from = 0.0, to = 1.0) const val DEFAULT_OPACITY_VALUE = 0.55f
const val DEFAULT_FADE_EFFECT_IS_ENABLED = 1

/** The data class for the fade effect info of the accessibility floating menu view. */
data class MenuFadeEffectInfo(val isFadeEffectEnabled: Boolean, val opacity: Float)
+45 −0
Original line number Diff line number Diff line
@@ -16,11 +16,15 @@

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

import static com.android.internal.accessibility.dialog.AccessibilityTargetHelper.getTargets;
import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_FADE_EFFECT_IS_ENABLED;
import static com.android.systemui.accessibility.floatingmenu.MenuFadeEffectInfoKt.DEFAULT_OPACITY_VALUE;
import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL;

import android.annotation.FloatRange;
@@ -72,6 +76,15 @@ class MenuInfoRepository {
                }
            };

    @VisibleForTesting
    final ContentObserver mMenuFadeOutContentObserver =
            new ContentObserver(mHandler) {
                @Override
                public void onChange(boolean selfChange) {
                    mSettingsContentsCallback.onFadeEffectInfoChanged(getMenuFadeEffectInfo());
                }
            };

    MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) {
        mContext = context;
        mSettingsContentsCallback = settingsContentsChanged;
@@ -91,6 +104,15 @@ class MenuInfoRepository {
        callback.onReady(getMenuSizeTypeFromSettings(mContext));
    }

    void loadMenuFadeEffectInfo(OnInfoReady<MenuFadeEffectInfo> callback) {
        callback.onReady(getMenuFadeEffectInfo());
    }

    private MenuFadeEffectInfo getMenuFadeEffectInfo() {
        return new MenuFadeEffectInfo(isMenuFadeEffectEnabledFromSettings(mContext),
                getMenuOpacityFromSettings(mContext));
    }

    void updateMenuSavingPosition(Position percentagePosition) {
        mPercentagePosition = percentagePosition;
        Prefs.putString(mContext, Prefs.Key.ACCESSIBILITY_FLOATING_MENU_POSITION,
@@ -119,17 +141,28 @@ class MenuInfoRepository {
                Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE),
                /* notifyForDescendants */ false, mMenuSizeContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED),
                /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
                UserHandle.USER_CURRENT);
        mContext.getContentResolver().registerContentObserver(
                Settings.Secure.getUriFor(ACCESSIBILITY_FLOATING_MENU_OPACITY),
                /* notifyForDescendants */ false, mMenuFadeOutContentObserver,
                UserHandle.USER_CURRENT);
    }

    void unregisterContentObservers() {
        mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver);
        mContext.getContentResolver().unregisterContentObserver(mMenuFadeOutContentObserver);
    }

    interface OnSettingsContentsChanged {
        void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures);

        void onSizeTypeChanged(int newSizeType);

        void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo);
    }

    interface OnInfoReady<T> {
@@ -140,4 +173,16 @@ class MenuInfoRepository {
        return Settings.Secure.getIntForUser(context.getContentResolver(),
                ACCESSIBILITY_FLOATING_MENU_SIZE, SMALL, UserHandle.USER_CURRENT);
    }

    private static boolean isMenuFadeEffectEnabledFromSettings(Context context) {
        return Settings.Secure.getIntForUser(context.getContentResolver(),
                ACCESSIBILITY_FLOATING_MENU_FADE_ENABLED,
                DEFAULT_FADE_EFFECT_IS_ENABLED, UserHandle.USER_CURRENT) == /* enabled */ 1;
    }

    private static float getMenuOpacityFromSettings(Context context) {
        return Settings.Secure.getFloatForUser(context.getContentResolver(),
                ACCESSIBILITY_FLOATING_MENU_OPACITY, DEFAULT_OPACITY_VALUE,
                UserHandle.USER_CURRENT);
    }
}
+1 −0
Original line number Diff line number Diff line
@@ -55,6 +55,7 @@ class MenuListViewTouchHandler implements RecyclerView.OnItemTouchListener {

        switch (motionEvent.getAction()) {
            case MotionEvent.ACTION_DOWN:
                mMenuAnimationController.fadeInNowIfEnabled();
                mTouchSlop = ViewConfiguration.get(recyclerView.getContext()).getScaledTouchSlop();
                mDown.set(motionEvent.getRawX(), motionEvent.getRawY());
                mMenuTranslationDown.set(menuView.getTranslationX(), menuView.getTranslationY());
+17 −0
Original line number Diff line number Diff line
@@ -53,6 +53,8 @@ class MenuView extends FrameLayout implements
    private final RecyclerView mTargetFeaturesView;
    private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater =
            this::updateSystemGestureExcludeRects;
    private final Observer<MenuFadeEffectInfo> mFadeEffectInfoObserver =
            this::onMenuFadeEffectInfoChanged;
    private final Observer<Position> mPercentagePositionObserver = this::onPercentagePosition;
    private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged;
    private final Observer<List<AccessibilityTarget>> mTargetFeaturesObserver =
@@ -154,6 +156,8 @@ class MenuView extends FrameLayout implements

    @SuppressLint("NotifyDataSetChanged")
    private void onSizeTypeChanged(int newSizeType) {
        mMenuAnimationController.fadeInNowIfEnabled();

        mMenuViewAppearance.setSizeType(newSizeType);

        mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding());
@@ -163,10 +167,14 @@ class MenuView extends FrameLayout implements
        onSizeChanged();
        onEdgeChanged();
        onPositionChanged();

        mMenuAnimationController.fadeOutIfEnabled();
    }

    private void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures) {
        // TODO(b/252756133): Should update specific item instead of the whole list
        mMenuAnimationController.fadeInNowIfEnabled();

        mTargetFeatures.clear();
        mTargetFeatures.addAll(newTargetFeatures);
        mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size());
@@ -176,6 +184,13 @@ class MenuView extends FrameLayout implements
        onSizeChanged();
        onEdgeChanged();
        onPositionChanged();

        mMenuAnimationController.fadeOutIfEnabled();
    }

    private void onMenuFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) {
        mMenuAnimationController.updateOpacityWith(fadeEffectInfo.isFadeEffectEnabled(),
                fadeEffectInfo.getOpacity());
    }

    Rect getMenuDraggableBounds() {
@@ -191,6 +206,7 @@ class MenuView extends FrameLayout implements

    void show() {
        mMenuViewModel.getPercentagePositionData().observeForever(mPercentagePositionObserver);
        mMenuViewModel.getFadeEffectInfoData().observeForever(mFadeEffectInfoObserver);
        mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver);
        mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver);
        setVisibility(VISIBLE);
@@ -203,6 +219,7 @@ class MenuView extends FrameLayout implements
        setVisibility(GONE);
        mBoundsInParent.setEmpty();
        mMenuViewModel.getPercentagePositionData().removeObserver(mPercentagePositionObserver);
        mMenuViewModel.getFadeEffectInfoData().removeObserver(mFadeEffectInfoObserver);
        mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver);
        mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver);
        mMenuViewModel.unregisterContentObservers();
Loading