diff options
15 files changed, 1011 insertions, 7 deletions
diff --git a/packages/SystemUI/res/values-night/dimens.xml b/packages/SystemUI/res/values-night/dimens.xml index d2d4198b7728..9fc86db48c80 100644 --- a/packages/SystemUI/res/values-night/dimens.xml +++ b/packages/SystemUI/res/values-night/dimens.xml @@ -20,4 +20,7 @@ <!-- Height of the background gradient behind the screenshot UI (taller in dark mode) --> <dimen name="screenshot_bg_protection_height">375dp</dimen> + <!-- Accessibility floating menu --> + <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen> + <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen> </resources>
\ No newline at end of file diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 2c6e6068c4b1..fa3ed21e203e 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1323,8 +1323,8 @@ <!-- Accessibility floating menu --> <dimen name="accessibility_floating_menu_elevation">3dp</dimen> - <dimen name="accessibility_floating_menu_stroke_width">1dp</dimen> - <dimen name="accessibility_floating_menu_stroke_inset">-2dp</dimen> + <dimen name="accessibility_floating_menu_stroke_width">0dp</dimen> + <dimen name="accessibility_floating_menu_stroke_inset">0dp</dimen> <dimen name="accessibility_floating_menu_margin">16dp</dimen> <dimen name="accessibility_floating_menu_small_padding">6dp</dimen> <dimen name="accessibility_floating_menu_small_width_height">36dp</dimen> 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 403941f8e639..ea334b27fa09 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 000000000000..698d60a5b13e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepository.java @@ -0,0 +1,112 @@ +/* + * 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.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; +import android.os.Handler; +import android.os.Looper; +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; + +/** + * 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)); + } + }; + + @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; + } + + void loadMenuTargetFeatures(OnInfoReady<List<AccessibilityTarget>> callback) { + callback.onReady(getTargets(mContext, ACCESSIBILITY_BUTTON)); + } + + void loadMenuSizeType(OnInfoReady<Integer> callback) { + callback.onReady(getMenuSizeTypeFromSettings(mContext)); + } + + 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); + 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<AccessibilityTarget> newTargetFeatures); + + void onSizeTypeChanged(int newSizeType); + } + + interface OnInfoReady<T> { + 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 new file mode 100644 index 000000000000..576f23ee780e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuView.java @@ -0,0 +1,153 @@ +/* + * 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<AccessibilityTarget> mTargetFeatures = new ArrayList<>(); + private final AccessibilityTargetAdapter mAdapter; + private final MenuViewModel mMenuViewModel; + private final RecyclerView mTargetFeaturesView; + private final Observer<Integer> mSizeTypeObserver = this::onSizeTypeChanged; + private final Observer<List<AccessibilityTarget>> 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)); + // Avoid drawing out of bounds of the parent view + setClipToOutline(true); + 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 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], + insets[3]); + + final GradientDrawable gradientDrawable = getContainerViewGradient(); + gradientDrawable.setCornerRadii(mMenuViewAppearance.getMenuRadii()); + gradientDrawable.setStroke(mMenuViewAppearance.getMenuStrokeWidth(), + mMenuViewAppearance.getMenuStrokeColor()); + } + + @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<AccessibilityTarget> newTargetFeatures) { + // TODO(b/252756133): Should update specific item instead of the whole list + mTargetFeatures.clear(); + mTargetFeatures.addAll(newTargetFeatures); + mMenuViewAppearance.setTargetFeaturesSize(mTargetFeatures.size()); + mAdapter.notifyDataSetChanged(); + + onSizeChanged(); + onEdgeChanged(); + } + + void show() { + mMenuViewModel.getTargetFeaturesData().observeForever(mTargetFeaturesObserver); + mMenuViewModel.getSizeTypeData().observeForever(mSizeTypeObserver); + setVisibility(VISIBLE); + mMenuViewModel.registerContentObservers(); + } + + void hide() { + setVisibility(GONE); + mMenuViewModel.getTargetFeaturesData().removeObserver(mTargetFeaturesObserver); + mMenuViewModel.getSizeTypeData().removeObserver(mSizeTypeObserver); + mMenuViewModel.unregisterContentObservers(); + } + + void loadLayoutResources() { + mMenuViewAppearance.update(); + + setBackground(mMenuViewAppearance.getMenuBackground()); + setElevation(mMenuViewAppearance.getMenuElevation()); + onItemSizeChanged(); + onSizeChanged(); + 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 000000000000..b9b7732605c0 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewAppearance.java @@ -0,0 +1,169 @@ +/* + * 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 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; + +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; + private int mElevation; + 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(); + + update(); + } + + 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); + 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 setSizeType(int sizeType) { + mSizeType = sizeType; + + mRadii = createRadii(getMenuRadius(mTargetFeaturesSize)); + } + + void setTargetFeaturesSize(int targetFeaturesSize) { + mTargetFeaturesSize = targetFeaturesSize; + + mRadii = createRadii(getMenuRadius(targetFeaturesSize)); + } + + Drawable getMenuBackground() { + return mBackgroundDrawable; + } + + int getMenuElevation() { + return mElevation; + } + + int getMenuHeight() { + return calculateActualMenuHeight(); + } + + int getMenuIconSize() { + return mSizeType == SMALL ? mSmallIconSize : mLargeIconSize; + } + + int getMenuPadding() { + return mSizeType == SMALL ? mSmallPadding : mLargePadding; + } + + 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 mSizeType == SMALL ? getSmallSize(itemCount) : getLargeSize(itemCount); + } + + @DimenRes + private int getSmallSize(int itemCount) { + 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/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java new file mode 100644 index 000000000000..4ea2f7799c30 --- /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 000000000000..1e15a599f796 --- /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 000000000000..c3ba43950b6e --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewModel.java @@ -0,0 +1,69 @@ +/* + * 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<List<AccessibilityTarget>> mTargetFeaturesData = + new MutableLiveData<>(); + private final MutableLiveData<Integer> mSizeTypeData = new MutableLiveData<>(); + private final MenuInfoRepository mInfoRepository; + + MenuViewModel(Context context) { + mInfoRepository = new MenuInfoRepository(context, /* settingsContentsChanged= */ this); + } + + @Override + public void onTargetFeaturesChanged(List<AccessibilityTarget> newTargetFeatures) { + mTargetFeaturesData.setValue(newTargetFeatures); + } + + @Override + public void onSizeTypeChanged(int newSizeType) { + mSizeTypeData.setValue(newSizeType); + } + + LiveData<Integer> getSizeTypeData() { + mInfoRepository.loadMenuSizeType(mSizeTypeData::setValue); + return mSizeTypeData; + } + + LiveData<List<AccessibilityTarget>> getTargetFeaturesData() { + mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue); + return mTargetFeaturesData; + } + + void registerContentObservers() { + mInfoRepository.registerContentObservers(); + } + + void unregisterContentObservers() { + mInfoRepository.unregisterContentObservers(); + } +}
\ No newline at end of file diff --git a/packages/SystemUI/src/com/android/systemui/flags/Flags.java b/packages/SystemUI/src/com/android/systemui/flags/Flags.java index aa57175420ef..e3342b2befa3 100644 --- a/packages/SystemUI/src/com/android/systemui/flags/Flags.java +++ b/packages/SystemUI/src/com/android/systemui/flags/Flags.java @@ -306,6 +306,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 8ca17b974100..19a6c66652dd 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<KeyguardUpdateMonitorCallback> 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/MenuInfoRepositoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuInfoRepositoryTest.java new file mode 100644 index 000000000000..d8b10e04705e --- /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()); + } +} 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 000000000000..f782a446c627 --- /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 000000000000..8883cb783438 --- /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); + } +} 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 000000000000..513044d2c20d --- /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); + } +} |