Merge changes Id8085500,I986406b5,Ie9ee39c1

* changes:
  Refactor the design and improve the animations of Accessibility Floating Menu(3/n).
  Refactor the design and improve the animations of Accessibility Floating Menu(2/n).
  Refactor the design and improve the animations of Accessibility Floating Menu(1/n).
This commit is contained in:
PETER LIANG
2022-10-11 10:59:29 +00:00
committed by Android (Google) Code Review
15 changed files with 1011 additions and 7 deletions

View File

@@ -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>

View File

@@ -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>

View File

@@ -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();

View File

@@ -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);
}
}

View File

@@ -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);
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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;
}
}

View File

@@ -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();
}
}

View File

@@ -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 ==========================
// | |

View File

@@ -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;

View File

@@ -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());
}
}

View File

@@ -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));
}
}

View File

@@ -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);
}
}

View File

@@ -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);
}
}