From fa717b43ad9f67e1e665697eb17b1956203d85e0 Mon Sep 17 00:00:00 2001 From: Peter Liang Date: Fri, 26 Aug 2022 22:57:16 +0800 Subject: [PATCH] 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); + } +}