Refactor the design and improve the animations of Accessibility Floating Menu(1/n).
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
This commit is contained in:
@@ -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();
|
||||
|
||||
@@ -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<List<AccessibilityTarget>> 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<AccessibilityTarget> newTargetFeatures);
|
||||
}
|
||||
|
||||
interface OnInfoReady<T> {
|
||||
void onReady(T info);
|
||||
}
|
||||
}
|
||||
@@ -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<AccessibilityTarget> mTargetFeatures = new ArrayList<>();
|
||||
private final AccessibilityTargetAdapter mAdapter;
|
||||
private final MenuViewModel mMenuViewModel;
|
||||
private final RecyclerView mTargetFeaturesView;
|
||||
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));
|
||||
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<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();
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
@@ -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};
|
||||
}
|
||||
}
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<List<AccessibilityTarget>> mTargetFeaturesData =
|
||||
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);
|
||||
}
|
||||
|
||||
LiveData<List<AccessibilityTarget>> getTargetFeaturesData() {
|
||||
mInfoRepository.loadMenuTargetFeatures(mTargetFeaturesData::setValue);
|
||||
return mTargetFeaturesData;
|
||||
}
|
||||
|
||||
void registerContentObservers() {
|
||||
mInfoRepository.registerContentObservers();
|
||||
}
|
||||
|
||||
void unregisterContentObservers() {
|
||||
mInfoRepository.unregisterContentObservers();
|
||||
}
|
||||
}
|
||||
@@ -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 ==========================
|
||||
// | |
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user