From 4f62018aa2ed90cefcba08a7bd9e1a59c1c5106e Mon Sep 17 00:00:00 2001 From: Peter Liang Date: Mon, 5 Sep 2022 17:45:35 +0800 Subject: [PATCH] Refactor the design and improve the animations of Accessibility Floating Menu(6/n). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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 --- .../floatingmenu/MenuAnimationController.java | 55 +++++++++++++++++++ .../floatingmenu/MenuFadeEffectInfo.kt | 25 +++++++++ .../floatingmenu/MenuInfoRepository.java | 45 +++++++++++++++ .../MenuListViewTouchHandler.java | 1 + .../accessibility/floatingmenu/MenuView.java | 17 ++++++ .../floatingmenu/MenuViewModel.java | 12 ++++ .../floatingmenu/MenuInfoRepositoryTest.java | 21 +++++-- 7 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java index 5d4c77663dabf..3976d63f52202 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuAnimationController.java @@ -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 { private final DynamicAnimation.ViewProperty mProperty; diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt new file mode 100644 index 0000000000000..83c344cca2140 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuFadeEffectInfo.kt @@ -0,0 +1,25 @@ +/* + * 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) diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java index 0649463c8238d..57019de762a4a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -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 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 newTargetFeatures); void onSizeTypeChanged(int newSizeType); + + void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo); } interface OnInfoReady { @@ -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); + } } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java index c9db964972b62..1c312e235fd0a 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuListViewTouchHandler.java @@ -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()); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java index e90e54cc71e5e..e2c2bc40a9a8f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -53,6 +53,8 @@ class MenuView extends FrameLayout implements private final RecyclerView mTargetFeaturesView; private final ViewTreeObserver.OnDrawListener mSystemGestureExcludeUpdater = this::updateSystemGestureExcludeRects; + private final Observer mFadeEffectInfoObserver = + this::onMenuFadeEffectInfoChanged; private final Observer mPercentagePositionObserver = this::onPercentagePosition; private final Observer mSizeTypeObserver = this::onSizeTypeChanged; private final Observer> 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 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(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java index 24bed3ef9c47f..e8a2b6e8767ba 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -33,6 +33,8 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { private final MutableLiveData> mTargetFeaturesData = new MutableLiveData<>(); private final MutableLiveData mSizeTypeData = new MutableLiveData<>(); + private final MutableLiveData mFadeEffectInfoData = + new MutableLiveData<>(); private final MutableLiveData mPercentagePositionData = new MutableLiveData<>(); private final MenuInfoRepository mInfoRepository; @@ -50,6 +52,11 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { mSizeTypeData.setValue(newSizeType); } + @Override + public void onFadeEffectInfoChanged(MenuFadeEffectInfo fadeEffectInfo) { + mFadeEffectInfoData.setValue(fadeEffectInfo); + } + void updateMenuSavingPosition(Position percentagePosition) { mInfoRepository.updateMenuSavingPosition(percentagePosition); } @@ -64,6 +71,11 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { return mSizeTypeData; } + LiveData getFadeEffectInfoData() { + mInfoRepository.loadMenuFadeEffectInfo(mFadeEffectInfoData::setValue); + return mFadeEffectInfoData; + } + LiveData> getTargetFeaturesData() { mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue); return mTargetFeaturesData; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java index d8b10e04705eb..e62a3295a7e27 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java @@ -17,6 +17,7 @@ package com.android.systemui.accessibility.floatingmenu; import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.any; import static org.mockito.Mockito.verify; import android.testing.AndroidTestingRunner; @@ -25,6 +26,7 @@ import androidx.test.filters.SmallTest; import com.android.systemui.SysuiTestCase; +import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; @@ -42,13 +44,24 @@ public class MenuInfoRepositoryTest extends SysuiTestCase { @Mock private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged; + private MenuInfoRepository mMenuInfoRepository; + + @Before + public void setUp() { + mMenuInfoRepository = new MenuInfoRepository(mContext, mMockSettingsContentsChanged); + } + @Test public void menuSizeTypeChanged_verifyOnSizeTypeChanged() { - final MenuInfoRepository menuInfoRepository = - new MenuInfoRepository(mContext, mMockSettingsContentsChanged); - - menuInfoRepository.mMenuSizeContentObserver.onChange(true); + mMenuInfoRepository.mMenuSizeContentObserver.onChange(true); verify(mMockSettingsContentsChanged).onSizeTypeChanged(anyInt()); } + + @Test + public void menuOpacityChanged_verifyOnFadeEffectChanged() { + mMenuInfoRepository.mMenuFadeOutContentObserver.onChange(true); + + verify(mMockSettingsContentsChanged).onFadeEffectInfoChanged(any(MenuFadeEffectInfo.class)); + } }