From fa717b43ad9f67e1e665697eb17b1956203d85e0 Mon Sep 17 00:00:00 2001 From: Peter Liang Date: Fri, 26 Aug 2022 22:57:16 +0800 Subject: [PATCH 1/3] Refactor the design and improve the animations of Accessibility Floating Menu(1/n). MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Goal: The goal of all following patches is to refactor the design to be compatible with the other system UI components. Also, support the feature “Drag to remove” and collision avoidance between other system windows based on the implementation. See more details: go/improvement_of_dragging_of_a11y_fab Actions of this change: 1) Add the feature flag whether to apply the flag & spring animations into the menu. 2) Create the basic menu view related to accessibility features, and shown on the window. 3) Observe the settings content of the target features. Bug: 227715451 Test: atest AccessibilityFloatingMenuControllerTest MenuViewLayerControllerTest MenuViewLayerTest Change-Id: Ie9ee39c15ffceb1a967aeca2c80860d2411d85cb --- .../AccessibilityFloatingMenuController.java | 28 +++- .../floatingmenu/MenuInfoRepository.java | 83 ++++++++++++ .../accessibility/floatingmenu/MenuView.java | 123 ++++++++++++++++++ .../floatingmenu/MenuViewAppearance.java | 94 +++++++++++++ .../floatingmenu/MenuViewLayer.java | 67 ++++++++++ .../floatingmenu/MenuViewLayerController.java | 76 +++++++++++ .../floatingmenu/MenuViewModel.java | 58 +++++++++ .../src/com/android/systemui/flags/Flags.java | 4 + ...cessibilityFloatingMenuControllerTest.java | 62 ++++++++- .../MenuViewLayerControllerTest.java | 71 ++++++++++ .../floatingmenu/MenuViewLayerTest.java | 65 +++++++++ 11 files changed, 726 insertions(+), 5 deletions(-) create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java index 11353f67a7994..42ece257fe257 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuController.java @@ -17,10 +17,17 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; +import static android.view.Display.DEFAULT_DISPLAY; +import static android.view.WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL; + +import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS; import android.content.Context; +import android.hardware.display.DisplayManager; import android.os.UserHandle; import android.text.TextUtils; +import android.view.Display; +import android.view.WindowManager; import androidx.annotation.MainThread; @@ -31,6 +38,7 @@ import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonModeObserver.AccessibilityButtonMode; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; import com.android.systemui.dagger.SysUISingleton; +import com.android.systemui.flags.FeatureFlags; import javax.inject.Inject; @@ -46,6 +54,9 @@ public class AccessibilityFloatingMenuController implements private final KeyguardUpdateMonitor mKeyguardUpdateMonitor; private Context mContext; + private final WindowManager mWindowManager; + private final DisplayManager mDisplayManager; + private final FeatureFlags mFeatureFlags; @VisibleForTesting IAccessibilityFloatingMenu mFloatingMenu; private int mBtnMode; @@ -83,13 +94,19 @@ public class AccessibilityFloatingMenuController implements @Inject public AccessibilityFloatingMenuController(Context context, + WindowManager windowManager, + DisplayManager displayManager, AccessibilityButtonTargetsObserver accessibilityButtonTargetsObserver, AccessibilityButtonModeObserver accessibilityButtonModeObserver, - KeyguardUpdateMonitor keyguardUpdateMonitor) { + KeyguardUpdateMonitor keyguardUpdateMonitor, + FeatureFlags featureFlags) { mContext = context; + mWindowManager = windowManager; + mDisplayManager = displayManager; mAccessibilityButtonTargetsObserver = accessibilityButtonTargetsObserver; mAccessibilityButtonModeObserver = accessibilityButtonModeObserver; mKeyguardUpdateMonitor = keyguardUpdateMonitor; + mFeatureFlags = featureFlags; mIsKeyguardVisible = false; } @@ -159,7 +176,14 @@ public class AccessibilityFloatingMenuController implements private void showFloatingMenu() { if (mFloatingMenu == null) { - mFloatingMenu = new AccessibilityFloatingMenu(mContext); + if (mFeatureFlags.isEnabled(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS)) { + final Display defaultDisplay = mDisplayManager.getDisplay(DEFAULT_DISPLAY); + mFloatingMenu = new MenuViewLayerController( + mContext.createWindowContext(defaultDisplay, + TYPE_NAVIGATION_BAR_PANEL, /* options= */ null), mWindowManager); + } else { + mFloatingMenu = new AccessibilityFloatingMenu(mContext); + } } mFloatingMenu.show(); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java new file mode 100644 index 0000000000000..0f5c403f1f968 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -0,0 +1,83 @@ +/* + * 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 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 android.content.Context; +import android.database.ContentObserver; +import android.os.Handler; +import android.os.Looper; +import android.os.UserHandle; +import android.provider.Settings; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; + +import java.util.List; + +/** + * Stores and observe the settings contents for the menu view. + */ +class MenuInfoRepository { + private final Context mContext; + private final Handler mHandler = new Handler(Looper.getMainLooper()); + private final OnSettingsContentsChanged mSettingsContentsCallback; + + private final ContentObserver mMenuTargetFeaturesContentObserver = + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mSettingsContentsCallback.onTargetFeaturesChanged( + getTargets(mContext, ACCESSIBILITY_BUTTON)); + } + }; + + MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) { + mContext = context; + mSettingsContentsCallback = settingsContentsChanged; + } + + void loadMenuTargetFeatures(OnInfoReady> callback) { + callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON)); + } + + void registerContentObservers() { + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), + /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver, + UserHandle.USER_CURRENT); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(ENABLED_ACCESSIBILITY_SERVICES), + /* notifyForDescendants */ false, + mMenuTargetFeaturesContentObserver, UserHandle.USER_CURRENT); + } + + void unregisterContentObservers() { + mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver); + } + + interface OnSettingsContentsChanged { + void onTargetFeaturesChanged(List newTargetFeatures); + } + + interface OnInfoReady { + void onReady(T info); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java new file mode 100644 index 0000000000000..22c387ee2f5ac --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -0,0 +1,123 @@ +/* + * 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 static android.view.ViewGroup.LayoutParams.WRAP_CONTENT; + +import android.annotation.SuppressLint; +import android.content.Context; +import android.content.res.Configuration; +import android.graphics.drawable.GradientDrawable; +import android.widget.FrameLayout; + +import androidx.lifecycle.Observer; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; + +import java.util.ArrayList; +import java.util.List; + +/** + * The container view displays the accessibility features. + */ +@SuppressLint("ViewConstructor") +class MenuView extends FrameLayout { + private static final int INDEX_MENU_ITEM = 0; + private final List mTargetFeatures = new ArrayList<>(); + private final AccessibilityTargetAdapter mAdapter; + private final MenuViewModel mMenuViewModel; + private final RecyclerView mTargetFeaturesView; + private final Observer> mTargetFeaturesObserver = + this::onTargetFeaturesChanged; + private final MenuViewAppearance mMenuViewAppearance; + + MenuView(Context context, MenuViewModel menuViewModel, MenuViewAppearance menuViewAppearance) { + super(context); + + mMenuViewModel = menuViewModel; + mMenuViewAppearance = menuViewAppearance; + mAdapter = new AccessibilityTargetAdapter(mTargetFeatures); + mTargetFeaturesView = new RecyclerView(context); + mTargetFeaturesView.setAdapter(mAdapter); + mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); + setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + loadLayoutResources(); + + addView(mTargetFeaturesView); + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + loadLayoutResources(); + } + + @SuppressLint("NotifyDataSetChanged") + private void onItemSizeChanged() { + mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding()); + mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize()); + mAdapter.notifyDataSetChanged(); + } + + private void onEdgeChanged() { + final GradientDrawable gradientDrawable = getContainerViewGradient(); + gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuRadii()); + } + + @SuppressLint("NotifyDataSetChanged") + private void onTargetFeaturesChanged(List newTargetFeatures) { + // TODO(b/252756133): Should update specific item instead of the whole list + mTargetFeatures.clear(); + mTargetFeatures.addAll(newTargetFeatures); + mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size()); + mAdapter.notifyDataSetChanged(); + + onEdgeChanged(); + } + + void show() { + mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); + setVisibility(VISIBLE); + mMenuViewModel.registerContentObservers(); + } + + void hide() { + setVisibility(GONE); + mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver); + mMenuViewModel.unregisterContentObservers(); + } + + private void loadLayoutResources() { + mMenuViewAppearance.update(); + + setBackground(mMenuViewAppearance.getMenuBackground()); + setElevation(mMenuViewAppearance.getMenuElevation()); + onItemSizeChanged(); + onEdgeChanged(); + } + + private InstantInsetLayerDrawable getContainerViewInsetLayer() { + return (InstantInsetLayerDrawable) getBackground(); + } + + private GradientDrawable getContainerViewGradient() { + return (GradientDrawable) getContainerViewInsetLayer().getDrawable(INDEX_MENU_ITEM); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java new file mode 100644 index 0000000000000..ccb297fa00d05 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -0,0 +1,94 @@ +/* + * 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.content.Context; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; + +import com.android.systemui.R; + +/** + * Provides the layout resources information of the {@link MenuView}. + */ +class MenuViewAppearance { + private final Resources mRes; + private int mTargetFeaturesSize; + private int mSmallPadding; + private int mSmallIconSize; + private int mSmallSingleRadius; + private int mSmallMultipleRadius; + private int mElevation; + private float[] mRadii; + private Drawable mBackgroundDrawable; + + MenuViewAppearance(Context context) { + mRes = context.getResources(); + + update(); + } + + void update() { + mSmallPadding = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding); + mSmallIconSize = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height); + mSmallSingleRadius = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius); + mSmallMultipleRadius = mRes.getDimensionPixelSize( + R.dimen.accessibility_floating_menu_small_multiple_radius); + mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); + final Drawable drawable = + mRes.getDrawable(R.drawable.accessibility_floating_menu_background); + mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable}); + } + + void setTargetFeaturesSize(int targetFeaturesSize) { + mTargetFeaturesSize = targetFeaturesSize; + + mRadii = createRadii(getMenuRadius(targetFeaturesSize)); + } + + Drawable getMenuBackground() { + return mBackgroundDrawable; + } + + int getMenuElevation() { + return mElevation; + } + + int getMenuIconSize() { + return mSmallIconSize; + } + + int getMenuPadding() { + return mSmallPadding; + } + + float[] getMenuRadii() { + return mRadii; + } + + private int getMenuRadius(int itemCount) { + return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius; + } + + private static float[] createRadii(float radius) { + return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f}; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java new file mode 100644 index 0000000000000..4ea2f7799c30b --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java @@ -0,0 +1,67 @@ +/* + * 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.IntDef; +import android.annotation.SuppressLint; +import android.content.Context; +import android.widget.FrameLayout; + +import androidx.annotation.NonNull; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + +/** + * The basic interactions with the child view {@link MenuView}. + */ +@SuppressLint("ViewConstructor") +class MenuViewLayer extends FrameLayout { + private final MenuView mMenuView; + + @IntDef({ + LayerIndex.MENU_VIEW + }) + @Retention(RetentionPolicy.SOURCE) + @interface LayerIndex { + int MENU_VIEW = 0; + } + + MenuViewLayer(@NonNull Context context) { + super(context); + + final MenuViewModel menuViewModel = new MenuViewModel(context); + final MenuViewAppearance menuViewAppearance = new MenuViewAppearance(context); + mMenuView = new MenuView(context, menuViewModel, menuViewAppearance); + + addView(mMenuView, LayerIndex.MENU_VIEW); + } + + @Override + protected void onAttachedToWindow() { + super.onAttachedToWindow(); + + mMenuView.show(); + } + + @Override + protected void onDetachedFromWindow() { + super.onDetachedFromWindow(); + + mMenuView.hide(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java new file mode 100644 index 0000000000000..1e15a599f7966 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerController.java @@ -0,0 +1,76 @@ +/* + * 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 static android.view.WindowManager.LayoutParams.PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; + +import android.content.Context; +import android.graphics.PixelFormat; +import android.view.WindowManager; + +/** + * Controls the {@link MenuViewLayer} whether to be attached to the window via the interface + * of {@link IAccessibilityFloatingMenu}. + */ +class MenuViewLayerController implements IAccessibilityFloatingMenu { + private final WindowManager mWindowManager; + private final MenuViewLayer mMenuViewLayer; + private boolean mIsShowing; + + MenuViewLayerController(Context context, WindowManager windowManager) { + mWindowManager = windowManager; + mMenuViewLayer = new MenuViewLayer(context); + } + + @Override + public boolean isShowing() { + return mIsShowing; + } + + @Override + public void show() { + if (isShowing()) { + return; + } + + mIsShowing = true; + mWindowManager.addView(mMenuViewLayer, createDefaultLayerLayoutParams()); + } + + @Override + public void hide() { + if (!isShowing()) { + return; + } + + mIsShowing = false; + mWindowManager.removeView(mMenuViewLayer); + } + + private static WindowManager.LayoutParams createDefaultLayerLayoutParams() { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.MATCH_PARENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + params.privateFlags |= PRIVATE_FLAG_EXCLUDE_FROM_SCREEN_MAGNIFICATION; + params.windowAnimations = android.R.style.Animation_Translucent; + + return params; + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java new file mode 100644 index 0000000000000..090e23e2c9281 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -0,0 +1,58 @@ +/* + * 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.content.Context; + +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; + +import java.util.List; + +/** + * The view model provides the menu information from the repository{@link MenuInfoRepository} for + * the menu view{@link MenuView}. + */ +class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { + private final MutableLiveData> mTargetFeaturesData = + new MutableLiveData<>(); + private final MenuInfoRepository mInfoRepository; + + MenuViewModel(Context context) { + mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this); + } + + @Override + public void onTargetFeaturesChanged(List newTargetFeatures) { + mTargetFeaturesData.setValue(newTargetFeatures); + } + + LiveData> getTargetFeaturesData() { + mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue); + return mTargetFeaturesData; + } + + void registerContentObservers() { + mInfoRepository.registerContentObservers(); + } + + void unregisterContentObservers() { + mInfoRepository.unregisterContentObservers(); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index 54c1b28bb1066..31e1ecbe5b597 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -272,6 +272,10 @@ public class Flags { // 1500 - chooser public static final UnreleasedFlag CHOOSER_UNBUNDLED = new UnreleasedFlag(1500); + // 1600 - accessibility + public static final UnreleasedFlag A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS = + new UnreleasedFlag(1600); + // Pay no attention to the reflection behind the curtain. // ========================== Curtain ========================== // | | diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java index 8ca17b974100c..19a6c66652ddd 100644 --- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuControllerTest.java @@ -19,6 +19,8 @@ package com.android.systemui.accessibility.floatingmenu; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU; import static android.provider.Settings.Secure.ACCESSIBILITY_BUTTON_MODE_NAVIGATION_BAR; +import static com.android.systemui.flags.Flags.A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS; + import static com.google.common.truth.Truth.assertThat; import static org.mockito.ArgumentMatchers.any; @@ -27,10 +29,12 @@ import static org.mockito.Mockito.verify; import android.content.Context; import android.content.ContextWrapper; +import android.hardware.display.DisplayManager; import android.os.UserHandle; import android.provider.Settings; import android.testing.AndroidTestingRunner; import android.testing.TestableLooper; +import android.view.WindowManager; import androidx.test.filters.SmallTest; @@ -40,6 +44,7 @@ import com.android.systemui.Dependency; import com.android.systemui.SysuiTestCase; import com.android.systemui.accessibility.AccessibilityButtonModeObserver; import com.android.systemui.accessibility.AccessibilityButtonTargetsObserver; +import com.android.systemui.flags.FakeFeatureFlags; import org.junit.After; import org.junit.Before; @@ -53,7 +58,7 @@ import org.mockito.junit.MockitoRule; /** Test for {@link AccessibilityFloatingMenuController}. */ @RunWith(AndroidTestingRunner.class) -@TestableLooper.RunWithLooper +@TestableLooper.RunWithLooper(setAsMainLooper = true) @SmallTest public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @@ -70,6 +75,8 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { @Captor private ArgumentCaptor mKeyguardCallbackCaptor; private KeyguardUpdateMonitorCallback mKeyguardCallback; + private int mLastButtonMode; + private String mLastButtonTargets; @Before public void setUp() throws Exception { @@ -79,6 +86,11 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { return getBaseContext(); } }; + + mLastButtonTargets = Settings.Secure.getStringForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT); + mLastButtonMode = Settings.Secure.getIntForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, UserHandle.USER_CURRENT); } @After @@ -87,6 +99,13 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { mController.onAccessibilityButtonTargetsChanged(""); mController = null; } + + Settings.Secure.putStringForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastButtonTargets, + UserHandle.USER_CURRENT); + Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, mLastButtonMode, + UserHandle.USER_CURRENT); } @Test @@ -287,13 +306,50 @@ public class AccessibilityFloatingMenuControllerTest extends SysuiTestCase { assertThat(mController.mFloatingMenu).isNull(); } + @Test + public void onTargetsChanged_flingSpringAnimationsDisabled_floatingMenuIsCreated() { + Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, + UserHandle.USER_CURRENT); + final FakeFeatureFlags featureFlags = new FakeFeatureFlags(); + featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false); + + mController = setUpController(); + mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); + + assertThat(mController.mFloatingMenu).isInstanceOf(AccessibilityFloatingMenu.class); + } + + @Test + public void onTargetsChanged_isFloatingViewLayerControllerCreated() { + Settings.Secure.putIntForUser(mContextWrapper.getContentResolver(), + Settings.Secure.ACCESSIBILITY_BUTTON_MODE, ACCESSIBILITY_BUTTON_MODE_FLOATING_MENU, + UserHandle.USER_CURRENT); + final FakeFeatureFlags featureFlags = new FakeFeatureFlags(); + featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, true); + + mController = setUpController(featureFlags); + mController.onAccessibilityButtonTargetsChanged(TEST_A11Y_BTN_TARGETS); + + assertThat(mController.mFloatingMenu).isInstanceOf(MenuViewLayerController.class); + } + private AccessibilityFloatingMenuController setUpController() { + final FakeFeatureFlags featureFlags = new FakeFeatureFlags(); + featureFlags.set(A11Y_FLOATING_MENU_FLING_SPRING_ANIMATIONS, false); + return setUpController(featureFlags); + } + + private AccessibilityFloatingMenuController setUpController(FakeFeatureFlags featureFlags) { + final WindowManager windowManager = mContext.getSystemService(WindowManager.class); + final DisplayManager displayManager = mContext.getSystemService(DisplayManager.class); mTargetsObserver = spy(Dependency.get(AccessibilityButtonTargetsObserver.class)); mModeObserver = spy(Dependency.get(AccessibilityButtonModeObserver.class)); mKeyguardUpdateMonitor = Dependency.get(KeyguardUpdateMonitor.class); final AccessibilityFloatingMenuController controller = - new AccessibilityFloatingMenuController(mContextWrapper, mTargetsObserver, - mModeObserver, mKeyguardUpdateMonitor); + new AccessibilityFloatingMenuController(mContextWrapper, windowManager, + displayManager, mTargetsObserver, mModeObserver, mKeyguardUpdateMonitor, + featureFlags); controller.init(); return controller; diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java new file mode 100644 index 0000000000000..f782a446c6277 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerControllerTest.java @@ -0,0 +1,71 @@ +/* + * 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 static org.mockito.ArgumentMatchers.any; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager; + +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; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Tests for {@link MenuViewLayerController}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class MenuViewLayerControllerTest extends SysuiTestCase { + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private WindowManager mWindowManager; + + private MenuViewLayerController mMenuViewLayerController; + + @Before + public void setUp() throws Exception { + mMenuViewLayerController = new MenuViewLayerController(mContext, mWindowManager); + } + + @Test + public void show_shouldAddViewToWindow() { + mMenuViewLayerController.show(); + + verify(mWindowManager).addView(any(View.class), any(ViewGroup.LayoutParams.class)); + } + + @Test + public void hide_menuIsShowing_removeViewFromWindow() { + mMenuViewLayerController.show(); + + mMenuViewLayerController.hide(); + + verify(mWindowManager).removeView(any(View.class)); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java new file mode 100644 index 0000000000000..8883cb7834382 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java @@ -0,0 +1,65 @@ +/* + * 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 static android.view.View.GONE; +import static android.view.View.VISIBLE; + +import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex; + +import static com.google.common.truth.Truth.assertThat; + +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.View; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link MenuViewLayer}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class MenuViewLayerTest extends SysuiTestCase { + private MenuViewLayer mMenuViewLayer; + + @Before + public void setUp() throws Exception { + mMenuViewLayer = new MenuViewLayer(mContext); + } + + @Test + public void onAttachedToWindow_menuIsVisible() { + mMenuViewLayer.onAttachedToWindow(); + final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); + + assertThat(menuView.getVisibility()).isEqualTo(VISIBLE); + } + + @Test + public void onAttachedToWindow_menuIsGone() { + mMenuViewLayer.onDetachedFromWindow(); + final View menuView = mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW); + + assertThat(menuView.getVisibility()).isEqualTo(GONE); + } +} From 28665aa07fb4a1fb22cf904689ddc0496e10f84e Mon Sep 17 00:00:00 2001 From: Peter Liang Date: Sat, 27 Aug 2022 02:37:28 +0800 Subject: [PATCH 2/3] Refactor the design and improve the animations of Accessibility Floating Menu(2/n). Actions of this change: 1) Support the changes of the dark theme and configurations including display size, rotation. Bug: 227715451 Test: atest MenuViewTest Change-Id: I986406b5144c538d87544964e1e1d835d139025b --- packages/SystemUI/res/values-night/dimens.xml | 3 + packages/SystemUI/res/values/dimens.xml | 4 +- .../accessibility/floatingmenu/MenuView.java | 8 +- .../floatingmenu/MenuViewAppearance.java | 25 ++++++ .../floatingmenu/MenuViewTest.java | 81 +++++++++++++++++++ 5 files changed, 118 insertions(+), 3 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml index d2d4198b77286..9fc86db48c804 100644 --- a/packages/SystemUI/res/values-night/dimens.xml +++ b/packages/SystemUI/res/values-night/dimens.xml @@ -20,4 +20,7 @@ 375dp + + 1dp + -2dp \ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index ede6260cfdea1..5dc705093665d 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1323,8 +1323,8 @@ 3dp - 1dp - -2dp + 0dp + 0dp 16dp 6dp 36dp 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 22c387ee2f5ac..7e53d3a6ed52f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -77,8 +77,14 @@ class MenuView extends FrameLayout { } private void onEdgeChanged() { + final int[] insets = mMenuViewAppearance.getMenuInsets(); + getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2], + insets[3]); + final GradientDrawable gradientDrawable = getContainerViewGradient(); gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuRadii()); + gradientDrawable.setStroke(mMenuViewAppearance.getMenuStrokeWidth(), + mMenuViewAppearance.getMenuStrokeColor()); } @SuppressLint("NotifyDataSetChanged") @@ -104,7 +110,7 @@ class MenuView extends FrameLayout { mMenuViewModel.unregisterContentObservers(); } - private void loadLayoutResources() { + void loadLayoutResources() { mMenuViewAppearance.update(); setBackground(mMenuViewAppearance.getMenuBackground()); diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index ccb297fa00d05..8af14dad8d19f 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -20,6 +20,8 @@ import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import androidx.annotation.DimenRes; + import com.android.systemui.R; /** @@ -32,6 +34,9 @@ class MenuViewAppearance { private int mSmallIconSize; private int mSmallSingleRadius; private int mSmallMultipleRadius; + private int mStrokeWidth; + private int mStrokeColor; + private int mInset; private int mElevation; private float[] mRadii; private Drawable mBackgroundDrawable; @@ -52,6 +57,9 @@ class MenuViewAppearance { mSmallMultipleRadius = mRes.getDimensionPixelSize( R.dimen.accessibility_floating_menu_small_multiple_radius); mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + mStrokeWidth = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width); + mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark); + mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); mElevation = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); final Drawable drawable = mRes.getDrawable(R.drawable.accessibility_floating_menu_background); @@ -80,11 +88,28 @@ class MenuViewAppearance { return mSmallPadding; } + int[] getMenuInsets() { + return new int[]{mInset, 0, 0, 0}; + } + + int getMenuStrokeWidth() { + return mStrokeWidth; + } + + int getMenuStrokeColor() { + return mStrokeColor; + } + float[] getMenuRadii() { return mRadii; } private int getMenuRadius(int itemCount) { + return getSmallSize(itemCount); + } + + @DimenRes + private int getSmallSize(int itemCount) { return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius; } diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java new file mode 100644 index 0000000000000..513044d2c20d6 --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewTest.java @@ -0,0 +1,81 @@ +/* + * 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 static android.app.UiModeManager.MODE_NIGHT_YES; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.Mockito.spy; +import static org.mockito.Mockito.verify; + +import android.app.UiModeManager; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; + +/** Tests for {@link MenuView}. */ +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper(setAsMainLooper = true) +@SmallTest +public class MenuViewTest extends SysuiTestCase { + private static final int INDEX_MENU_ITEM = 0; + private int mNightMode; + private UiModeManager mUiModeManager; + private MenuView mMenuView; + + @Before + public void setUp() throws Exception { + mUiModeManager = mContext.getSystemService(UiModeManager.class); + mNightMode = mUiModeManager.getNightMode(); + mUiModeManager.setNightMode(MODE_NIGHT_YES); + final MenuViewModel stubMenuViewModel = new MenuViewModel(mContext); + final MenuViewAppearance stubMenuViewAppearance = new MenuViewAppearance(mContext); + mMenuView = spy(new MenuView(mContext, stubMenuViewModel, stubMenuViewAppearance)); + } + + @Test + public void onConfigurationChanged_updateViewModel() { + mMenuView.onConfigurationChanged(/* newConfig= */ null); + + verify(mMenuView).loadLayoutResources(); + } + + @Test + public void insetsOnDarkTheme_menuOnLeft_matchInsets() { + mMenuView.onConfigurationChanged(/* newConfig= */ null); + final InstantInsetLayerDrawable insetLayerDrawable = + (InstantInsetLayerDrawable) mMenuView.getBackground(); + final boolean areInsetsMatched = insetLayerDrawable.getLayerInsetLeft(INDEX_MENU_ITEM) != 0 + && insetLayerDrawable.getLayerInsetRight(INDEX_MENU_ITEM) == 0; + + assertThat(areInsetsMatched).isTrue(); + } + + @After + public void tearDown() throws Exception { + mUiModeManager.setNightMode(mNightMode); + } +} From b32a9d206f2beb3855417d003aa5b222104bde5f Mon Sep 17 00:00:00 2001 From: Peter Liang Date: Sun, 4 Sep 2022 21:22:36 +0800 Subject: [PATCH 3/3] Refactor the design and improve the animations of Accessibility Floating Menu(3/n). Actions of this change: 1) Support the change of the menu size type. Bug: 227715451 Test: atest MenuInfoRepositoryTest Change-Id: Id8085500b3a2b2571e41a14758e1a1b05501fe53 --- .../floatingmenu/MenuInfoRepository.java | 29 ++++++++++ .../accessibility/floatingmenu/MenuView.java | 24 ++++++++ .../floatingmenu/MenuViewAppearance.java | 56 ++++++++++++++++++- .../floatingmenu/MenuViewModel.java | 13 ++++- .../floatingmenu/MenuInfoRepositoryTest.java | 54 ++++++++++++++++++ 5 files changed, 172 insertions(+), 4 deletions(-) create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java 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 0f5c403f1f968..698d60a5b13e9 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -16,10 +16,12 @@ package com.android.systemui.accessibility.floatingmenu; +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.MenuViewAppearance.MenuSizeType.SMALL; import android.content.Context; import android.database.ContentObserver; @@ -29,6 +31,7 @@ import android.os.UserHandle; import android.provider.Settings; import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.internal.annotations.VisibleForTesting; import java.util.List; @@ -49,6 +52,16 @@ class MenuInfoRepository { } }; + @VisibleForTesting + final ContentObserver mMenuSizeContentObserver = + new ContentObserver(mHandler) { + @Override + public void onChange(boolean selfChange) { + mSettingsContentsCallback.onSizeTypeChanged( + getMenuSizeTypeFromSettings(mContext)); + } + }; + MenuInfoRepository(Context context, OnSettingsContentsChanged settingsContentsChanged) { mContext = context; mSettingsContentsCallback = settingsContentsChanged; @@ -58,6 +71,10 @@ class MenuInfoRepository { callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON)); } + void loadMenuSizeType(OnInfoReady callback) { + callback.onReady(getMenuSizeTypeFromSettings(mContext)); + } + void registerContentObservers() { mContext.getContentResolver().registerContentObserver( Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), @@ -67,17 +84,29 @@ class MenuInfoRepository { Settings.Secure.getUriFor(ENABLED_ACCESSIBILITY_SERVICES), /* notifyForDescendants */ false, mMenuTargetFeaturesContentObserver, UserHandle.USER_CURRENT); + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_FLOATING_MENU_SIZE), + /* notifyForDescendants */ false, mMenuSizeContentObserver, + UserHandle.USER_CURRENT); } void unregisterContentObservers() { mContext.getContentResolver().unregisterContentObserver(mMenuTargetFeaturesContentObserver); + mContext.getContentResolver().unregisterContentObserver(mMenuSizeContentObserver); } interface OnSettingsContentsChanged { void onTargetFeaturesChanged(List newTargetFeatures); + + void onSizeTypeChanged(int newSizeType); } interface OnInfoReady { void onReady(T info); } + + private static int getMenuSizeTypeFromSettings(Context context) { + return Settings.Secure.getIntForUser(context.getContentResolver(), + ACCESSIBILITY_FLOATING_MENU_SIZE, SMALL, UserHandle.USER_CURRENT); + } } 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 7e53d3a6ed52f..576f23ee780ec 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -43,6 +43,7 @@ class MenuView extends FrameLayout { private final AccessibilityTargetAdapter mAdapter; private final MenuViewModel mMenuViewModel; private final RecyclerView mTargetFeaturesView; + private final Observer mSizeTypeObserver = this::onSizeTypeChanged; private final Observer> mTargetFeaturesObserver = this::onTargetFeaturesChanged; private final MenuViewAppearance mMenuViewAppearance; @@ -57,6 +58,8 @@ class MenuView extends FrameLayout { mTargetFeaturesView.setAdapter(mAdapter); mTargetFeaturesView.setLayoutManager(new LinearLayoutManager(context)); setLayoutParams(new FrameLayout.LayoutParams(WRAP_CONTENT, WRAP_CONTENT)); + // Avoid drawing out of bounds of the parent view + setClipToOutline(true); loadLayoutResources(); addView(mTargetFeaturesView); @@ -76,6 +79,12 @@ class MenuView extends FrameLayout { mAdapter.notifyDataSetChanged(); } + private void onSizeChanged() { + final FrameLayout.LayoutParams layoutParams = (FrameLayout.LayoutParams) getLayoutParams(); + layoutParams.height = mMenuViewAppearance.getMenuHeight(); + setLayoutParams(layoutParams); + } + private void onEdgeChanged() { final int[] insets = mMenuViewAppearance.getMenuInsets(); getContainerViewInsetLayer().setLayerInset(INDEX_MENU_ITEM, insets[0], insets[1], insets[2], @@ -88,6 +97,17 @@ class MenuView extends FrameLayout { } @SuppressLint("NotifyDataSetChanged") + private void onSizeTypeChanged(int newSizeType) { + mMenuViewAppearance.setSizeType(newSizeType); + + mAdapter.setItemPadding(mMenuViewAppearance.getMenuPadding()); + mAdapter.setIconWidthHeight(mMenuViewAppearance.getMenuIconSize()); + mAdapter.notifyDataSetChanged(); + + onSizeChanged(); + onEdgeChanged(); + } + private void onTargetFeaturesChanged(List newTargetFeatures) { // TODO(b/252756133): Should update specific item instead of the whole list mTargetFeatures.clear(); @@ -95,11 +115,13 @@ class MenuView extends FrameLayout { mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size()); mAdapter.notifyDataSetChanged(); + onSizeChanged(); onEdgeChanged(); } void show() { mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); + mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver); setVisibility(VISIBLE); mMenuViewModel.registerContentObservers(); } @@ -107,6 +129,7 @@ class MenuView extends FrameLayout { void hide() { setVisibility(GONE); mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver); + mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver); mMenuViewModel.unregisterContentObservers(); } @@ -116,6 +139,7 @@ class MenuView extends FrameLayout { setBackground(mMenuViewAppearance.getMenuBackground()); setElevation(mMenuViewAppearance.getMenuElevation()); onItemSizeChanged(); + onSizeChanged(); onEdgeChanged(); } diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java index 8af14dad8d19f..b9b7732605c0b 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -16,6 +16,9 @@ package com.android.systemui.accessibility.floatingmenu; +import static com.android.systemui.accessibility.floatingmenu.MenuViewAppearance.MenuSizeType.SMALL; + +import android.annotation.IntDef; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; @@ -24,16 +27,24 @@ import androidx.annotation.DimenRes; import com.android.systemui.R; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; + /** * Provides the layout resources information of the {@link MenuView}. */ class MenuViewAppearance { private final Resources mRes; private int mTargetFeaturesSize; + private int mSizeType; private int mSmallPadding; + private int mLargePadding; private int mSmallIconSize; + private int mLargeIconSize; private int mSmallSingleRadius; private int mSmallMultipleRadius; + private int mLargeSingleRadius; + private int mLargeMultipleRadius; private int mStrokeWidth; private int mStrokeColor; private int mInset; @@ -41,6 +52,16 @@ class MenuViewAppearance { private float[] mRadii; private Drawable mBackgroundDrawable; + @IntDef({ + SMALL, + MenuSizeType.LARGE + }) + @Retention(RetentionPolicy.SOURCE) + @interface MenuSizeType { + int SMALL = 0; + int LARGE = 1; + } + MenuViewAppearance(Context context) { mRes = context.getResources(); @@ -50,13 +71,21 @@ class MenuViewAppearance { void update() { mSmallPadding = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_padding); + mLargePadding = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_padding); mSmallIconSize = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_width_height); + mLargeIconSize = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_width_height); mSmallSingleRadius = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_small_single_radius); mSmallMultipleRadius = mRes.getDimensionPixelSize( R.dimen.accessibility_floating_menu_small_multiple_radius); mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + mLargeSingleRadius = + mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_large_single_radius); + mLargeMultipleRadius = mRes.getDimensionPixelSize( + R.dimen.accessibility_floating_menu_large_multiple_radius); mStrokeWidth = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width); mStrokeColor = mRes.getColor(R.color.accessibility_floating_menu_stroke_dark); mInset = mRes.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); @@ -66,6 +95,12 @@ class MenuViewAppearance { mBackgroundDrawable = new InstantInsetLayerDrawable(new Drawable[]{drawable}); } + void setSizeType(int sizeType) { + mSizeType = sizeType; + + mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + } + void setTargetFeaturesSize(int targetFeaturesSize) { mTargetFeaturesSize = targetFeaturesSize; @@ -80,12 +115,16 @@ class MenuViewAppearance { return mElevation; } + int getMenuHeight() { + return calculateActualMenuHeight(); + } + int getMenuIconSize() { - return mSmallIconSize; + return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize; } int getMenuPadding() { - return mSmallPadding; + return mSizeType == SMALL ? mSmallPadding : mLargePadding; } int[] getMenuInsets() { @@ -105,7 +144,7 @@ class MenuViewAppearance { } private int getMenuRadius(int itemCount) { - return getSmallSize(itemCount); + return mSizeType == SMALL ? getSmallSize(itemCount) : getLargeSize(itemCount); } @DimenRes @@ -113,7 +152,18 @@ class MenuViewAppearance { return itemCount > 1 ? mSmallMultipleRadius : mSmallSingleRadius; } + @DimenRes + private int getLargeSize(int itemCount) { + return itemCount > 1 ? mLargeMultipleRadius : mLargeSingleRadius; + } + private static float[] createRadii(float radius) { return new float[]{0.0f, 0.0f, radius, radius, radius, radius, 0.0f, 0.0f}; } + + private int calculateActualMenuHeight() { + final int menuPadding = getMenuPadding(); + + return (menuPadding + getMenuIconSize()) * mTargetFeaturesSize + menuPadding; + } } 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 090e23e2c9281..c3ba43950b6e0 100644 --- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -32,6 +32,7 @@ import java.util.List; class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { private final MutableLiveData> mTargetFeaturesData = new MutableLiveData<>(); + private final MutableLiveData mSizeTypeData = new MutableLiveData<>(); private final MenuInfoRepository mInfoRepository; MenuViewModel(Context context) { @@ -43,6 +44,16 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { mTargetFeaturesData.setValue(newTargetFeatures); } + @Override + public void onSizeTypeChanged(int newSizeType) { + mSizeTypeData.setValue(newSizeType); + } + + LiveData getSizeTypeData() { + mInfoRepository.loadMenuSizeType(mSizeTypeData::setValue); + return mSizeTypeData; + } + LiveData> getTargetFeaturesData() { mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue); return mTargetFeaturesData; @@ -55,4 +66,4 @@ class MenuViewModel implements MenuInfoRepository.OnSettingsContentsChanged { void unregisterContentObservers() { mInfoRepository.unregisterContentObservers(); } -} +} \ No newline at end of file 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 new file mode 100644 index 0000000000000..d8b10e04705eb --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java @@ -0,0 +1,54 @@ +/* + * 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 static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.verify; + +import android.testing.AndroidTestingRunner; + +import androidx.test.filters.SmallTest; + +import com.android.systemui.SysuiTestCase; + +import org.junit.Rule; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.junit.MockitoJUnit; +import org.mockito.junit.MockitoRule; + +/** Tests for {@link MenuInfoRepository}. */ +@RunWith(AndroidTestingRunner.class) +@SmallTest +public class MenuInfoRepositoryTest extends SysuiTestCase { + @Rule + public MockitoRule mockito = MockitoJUnit.rule(); + + @Mock + private MenuInfoRepository.OnSettingsContentsChanged mMockSettingsContentsChanged; + + @Test + public void menuSizeTypeChanged_verifyOnSizeTypeChanged() { + final MenuInfoRepository menuInfoRepository = + new MenuInfoRepository(mContext, mMockSettingsContentsChanged); + + menuInfoRepository.mMenuSizeContentObserver.onChange(true); + + verify(mMockSettingsContentsChanged).onSizeTypeChanged(anyInt()); + } +}