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
This commit is contained in:
Peter Liang
2022-09-05 17:45:35 +08:00
parent e6ad76466c
commit 4f62018aa2
7 changed files with 172 additions and 4 deletions

View File

@@ -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;

View File

@@ -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)

View File

@@ -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);
}
}

View File

@@ -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());

View File

@@ -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();

View File

@@ -33,6 +33,8 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged {
private final MutableLiveData<List<AccessibilityTarget>> mTargetFeaturesData =
new MutableLiveData<>();
private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>();
private final MutableLiveData<MenuFadeEffectInfo> mFadeEffectInfoData =
new MutableLiveData<>();
private final MutableLiveData<Position> 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<MenuFadeEffectInfo> getFadeEffectInfoData() {
mInfoRepository.loadMenuFadeEffectInfo(mFadeEffectInfoData::setValue);
return mFadeEffectInfoData;
}
LiveData<List<AccessibilityTarget>> getTargetFeaturesData() {
mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue);
return mTargetFeaturesData;

View File

@@ -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));
}
}