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:
@@ -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;
|
||||
|
||||
@@ -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)
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -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();
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user