From 2be49c9be939f25c7d4e7b117340a67228fca1e6 Mon Sep 17 00:00:00 2001 From: Peter_Liang Date: Thu, 10 Dec 2020 01:00:37 +0800 Subject: [PATCH] Add new floating action menu for Accessibility targets. (1/n) Goals: - Trigger actions of accessibility features would be grouped into the floating menu. Patch action: 1. Use current Accessibility Target functions to get the corresponding data. 2. The position of the floating menu is at the bottom-right corner of the screen initially. 3. Support dark theme. Bug: 173958541 Test: atest AccessibilityFloatingMenuTest AccessibilityFloatingMenuViewTest Change-Id: I28e729520d4eb8ecb0bcc41d4de9d1499380e7f5 --- ...accessibility_floating_menu_background.xml | 32 +++ .../accessibility_floating_menu_item.xml | 36 ++++ packages/SystemUI/res/values-night/colors.xml | 2 + packages/SystemUI/res/values/colors.xml | 4 + packages/SystemUI/res/values/dimens.xml | 10 + .../AccessibilityFloatingMenu.java | 88 ++++++++ .../AccessibilityFloatingMenuView.java | 195 ++++++++++++++++++ .../AccessibilityTargetAdapter.java | 82 ++++++++ .../IAccessibilityFloatingMenu.java | 38 ++++ .../AccessibilityFloatingMenuTest.java | 90 ++++++++ .../AccessibilityFloatingMenuViewTest.java | 134 ++++++++++++ 11 files changed, 711 insertions(+) create mode 100644 packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml create mode 100644 packages/SystemUI/res/layout/accessibility_floating_menu_item.xml create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java create mode 100644 packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java create mode 100644 packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java diff --git a/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml new file mode 100644 index 0000000000000..675d36f83093d --- /dev/null +++ b/packages/SystemUI/res/drawable/accessibility_floating_menu_background.xml @@ -0,0 +1,32 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml new file mode 100644 index 0000000000000..376b29d2f369f --- /dev/null +++ b/packages/SystemUI/res/layout/accessibility_floating_menu_item.xml @@ -0,0 +1,36 @@ + + + + + + + + + + \ No newline at end of file diff --git a/packages/SystemUI/res/values-night/colors.xml b/packages/SystemUI/res/values-night/colors.xml index 37ec576be4be3..d571f2f174978 100644 --- a/packages/SystemUI/res/values-night/colors.xml +++ b/packages/SystemUI/res/values-night/colors.xml @@ -102,4 +102,6 @@ #81C995 #FCAD70 + + #B3000000 diff --git a/packages/SystemUI/res/values/colors.xml b/packages/SystemUI/res/values/colors.xml index acd671cb62974..9a4cb5b31fd64 100644 --- a/packages/SystemUI/res/values/colors.xml +++ b/packages/SystemUI/res/values/colors.xml @@ -275,4 +275,8 @@ #1E8E3E #E8710A + + + #CCFFFFFF + #26FFFFFF diff --git a/packages/SystemUI/res/values/dimens.xml b/packages/SystemUI/res/values/dimens.xml index 89c849a0ef4a1..cc2d36acf577f 100644 --- a/packages/SystemUI/res/values/dimens.xml +++ b/packages/SystemUI/res/values/dimens.xml @@ -1352,6 +1352,16 @@ 20dp 6dp + + 5dp + 1dp + -2dp + 16dp + 6dp + 36dp + 25dp + 20dp + 48dp 24dp diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java new file mode 100644 index 0000000000000..f5356c1e48eb6 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenu.java @@ -0,0 +1,88 @@ +/* + * Copyright (C) 2021 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.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.annotations.VisibleForTesting; + +/** + * Contains logic for an accessibility floating menu view. + */ +public class AccessibilityFloatingMenu implements IAccessibilityFloatingMenu { + private final Context mContext; + private final AccessibilityFloatingMenuView mMenuView; + + private final ContentObserver mContentObserver = + new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange) { + mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON)); + } + }; + + public AccessibilityFloatingMenu(Context context) { + mContext = context; + mMenuView = new AccessibilityFloatingMenuView(context); + } + + @VisibleForTesting + AccessibilityFloatingMenu(Context context, AccessibilityFloatingMenuView menuView) { + mContext = context; + mMenuView = menuView; + } + + @Override + public boolean isShowing() { + return mMenuView.isShowing(); + } + + @Override + public void show() { + if (isShowing()) { + return; + } + + mMenuView.onTargetsChanged(getTargets(mContext, ACCESSIBILITY_BUTTON)); + mMenuView.show(); + + mContext.getContentResolver().registerContentObserver( + Settings.Secure.getUriFor(Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS), + /* notifyForDescendants */ false, mContentObserver, + UserHandle.USER_CURRENT); + } + + @Override + public void hide() { + if (!isShowing()) { + return; + } + + mMenuView.hide(); + + mContext.getContentResolver().unregisterContentObserver(mContentObserver); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java new file mode 100644 index 0000000000000..b393a892e9b77 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuView.java @@ -0,0 +1,195 @@ +/* + * Copyright (C) 2021 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.Configuration; +import android.content.res.Resources; +import android.graphics.PixelFormat; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.util.DisplayMetrics; +import android.view.Gravity; +import android.view.ViewGroup; +import android.view.WindowManager; +import android.widget.FrameLayout; + +import androidx.annotation.DimenRes; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.R; + +import java.util.ArrayList; +import java.util.List; + +/** + * Accessibility floating menu is used for the actions of accessibility features, it's also the + * action set. + * + *

The number of items would depend on strings key + * {@link android.provider.Settings.Secure#ACCESSIBILITY_BUTTON_TARGETS}. + */ +public class AccessibilityFloatingMenuView extends FrameLayout { + private static final float DEFAULT_LOCATION_Y_PERCENTAGE = 0.8f; + private static final int INDEX_MENU_ITEM = 0; + private boolean mIsShowing; + private RecyclerView mListView; + private final WindowManager.LayoutParams mLayoutParams; + private final WindowManager mWindowManager; + private final AccessibilityTargetAdapter mAdapter; + private final List mTargets = new ArrayList<>(); + + public AccessibilityFloatingMenuView(Context context) { + super(context); + + mWindowManager = context.getSystemService(WindowManager.class); + mLayoutParams = createDefaultLayoutParams(); + mAdapter = new AccessibilityTargetAdapter(mTargets); + + initListView(context, mAdapter); + updateStroke(); + } + + void show() { + if (isShowing()) { + return; + } + + mIsShowing = true; + mWindowManager.addView(this, mLayoutParams); + } + + void hide() { + if (!isShowing()) { + return; + } + + mIsShowing = false; + mWindowManager.removeView(this); + } + + boolean isShowing() { + return mIsShowing; + } + + void onTargetsChanged(List newTargets) { + mTargets.clear(); + mTargets.addAll(newTargets); + mAdapter.notifyDataSetChanged(); + + final boolean hasMoreItems = mAdapter.getItemCount() > 1; + final int resId = hasMoreItems + ? R.dimen.accessibility_floating_menu_multiple_radius + : R.dimen.accessibility_floating_menu_single_radius; + setRadius(resId); + } + + private void setRadius(@DimenRes int radiusResId) { + final float radius = getResources().getDimension(radiusResId); + final float[] radii = new float[]{radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius}; + getMenuGradientDrawable().setCornerRadii(radii); + } + + private void initListView(Context context, AccessibilityTargetAdapter adapter) { + final Resources res = context.getResources(); + final int margin = + res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_margin); + final int elevation = + res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_elevation); + final Drawable listViewBackground = + context.getDrawable(R.drawable.accessibility_floating_menu_background); + + mListView = new RecyclerView(context); + final LinearLayoutManager layoutManager = new LinearLayoutManager(context); + final LayoutParams layoutParams = + new LayoutParams(ViewGroup.LayoutParams.WRAP_CONTENT, + ViewGroup.LayoutParams.WRAP_CONTENT); + layoutParams.setMarginsRelative(margin, margin, /* end= */ 0, margin); + mListView.setLayoutParams(layoutParams); + mListView.setElevation(elevation); + mListView.setBackground(listViewBackground); + mListView.setAdapter(adapter); + mListView.setLayoutManager(layoutManager); + + addView(mListView); + } + + private WindowManager.LayoutParams createDefaultLayoutParams() { + final WindowManager.LayoutParams params = new WindowManager.LayoutParams( + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.WRAP_CONTENT, + WindowManager.LayoutParams.TYPE_NAVIGATION_BAR_PANEL, + WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE, + PixelFormat.TRANSLUCENT); + final DisplayMetrics dm = getResources().getDisplayMetrics(); + params.gravity = Gravity.START | Gravity.TOP; + params.x = dm.widthPixels; + params.y = (int) (dm.heightPixels * DEFAULT_LOCATION_Y_PERCENTAGE); + + return params; + } + + @Override + protected void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + updateLocation(); + updateColor(); + updateStroke(); + } + + private LayerDrawable getMenuLayerDrawable() { + return (LayerDrawable) mListView.getBackground(); + } + + private GradientDrawable getMenuGradientDrawable() { + return (GradientDrawable) getMenuLayerDrawable().getDrawable(INDEX_MENU_ITEM); + } + + private void updateLocation() { + final DisplayMetrics dm = getResources().getDisplayMetrics(); + mLayoutParams.x = dm.widthPixels; + mLayoutParams.y = (int) (dm.heightPixels * DEFAULT_LOCATION_Y_PERCENTAGE); + mWindowManager.updateViewLayout(this, mLayoutParams); + } + + private void updateColor() { + final int menuColorResId = R.color.accessibility_floating_menu_background; + getMenuGradientDrawable().setColor(getResources().getColor(menuColorResId)); + } + + private void updateStroke() { + final Resources res = getResources(); + final boolean isNightMode = + (res.getConfiguration().uiMode & Configuration.UI_MODE_NIGHT_MASK) + == Configuration.UI_MODE_NIGHT_YES; + + final int inset = + res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_inset); + final int insetRight = isNightMode ? inset : 0; + getMenuLayerDrawable().setLayerInset(INDEX_MENU_ITEM, 0, 0, insetRight, 0); + + final int width = + res.getDimensionPixelSize(R.dimen.accessibility_floating_menu_stroke_width); + final int strokeWidth = isNightMode ? width : 0; + final int strokeColor = res.getColor(R.color.accessibility_floating_menu_stroke_dark); + getMenuGradientDrawable().setStroke(strokeWidth, strokeColor); + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java new file mode 100644 index 0000000000000..8b586c54034d8 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/AccessibilityTargetAdapter.java @@ -0,0 +1,82 @@ +/* + * Copyright (C) 2020 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.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; +import androidx.recyclerview.widget.RecyclerView.Adapter; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.R; +import com.android.systemui.accessibility.floatingmenu.AccessibilityTargetAdapter.ViewHolder; + +import java.util.List; + +/** + * An adapter which shows the set of accessibility targets that can be performed. + */ +public class AccessibilityTargetAdapter extends Adapter { + + private final List mTargets; + public AccessibilityTargetAdapter(List targets) { + mTargets = targets; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int i) { + final View root = LayoutInflater.from(parent.getContext()).inflate( + R.layout.accessibility_floating_menu_item, parent, + /* attachToRoot= */ false); + + return new ViewHolder(root); + } + + @Override + public void onBindViewHolder(@NonNull ViewHolder holder, int position) { + holder.mIconView.setBackground(mTargets.get(position).getIcon()); + + final boolean isFirstItem = (position == 0); + final boolean isLastItem = (position == (getItemCount() - 1)); + final int padding = holder.itemView.getPaddingStart(); + final int paddingTop = isFirstItem ? padding : 0; + final int paddingBottom = isLastItem ? padding : 0; + holder.itemView.setPaddingRelative(padding, paddingTop, padding, paddingBottom); + holder.itemView.setOnClickListener((v) -> mTargets.get(position).onSelected()); + holder.mDivider.setVisibility(isLastItem ? View.GONE : View.VISIBLE); + } + + @Override + public int getItemCount() { + return mTargets.size(); + } + + static class ViewHolder extends RecyclerView.ViewHolder { + final View mIconView; + final View mDivider; + + ViewHolder(View itemView) { + super(itemView); + mIconView = itemView.findViewById(R.id.icon_view); + mDivider = itemView.findViewById(R.id.transparent_divider); + } + } +} diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java new file mode 100644 index 0000000000000..62f02a0444855 --- /dev/null +++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/IAccessibilityFloatingMenu.java @@ -0,0 +1,38 @@ +/* + * Copyright (C) 2021 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; + +/** + * Interface for managing the accessibility targets menu component. + */ +public interface IAccessibilityFloatingMenu { + + /** + * Checks if the menu was shown. + */ + boolean isShowing(); + + /** + * Shows the accessibility targets menu. + */ + void show(); + + /** + * Hides the accessibility targets menu. + */ + void hide(); +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java new file mode 100644 index 0000000000000..0489871578cfe --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuTest.java @@ -0,0 +1,90 @@ +/* + * Copyright (C) 2021 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.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; + +import static com.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.anyInt; +import static org.mockito.Mockito.doReturn; + +import android.content.Context; +import android.testing.AndroidTestingRunner; +import android.testing.TestableLooper; +import android.view.accessibility.AccessibilityManager; + +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; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for {@link AccessibilityFloatingMenu}. */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +@TestableLooper.RunWithLooper +public class AccessibilityFloatingMenuTest extends SysuiTestCase { + + @Mock + private AccessibilityManager mAccessibilityManager; + + private AccessibilityFloatingMenuView mMenuView; + private AccessibilityFloatingMenu mMenu; + + @Before + public void initMenu() { + MockitoAnnotations.initMocks(this); + + final List assignedTargets = new ArrayList<>(); + mContext.addMockSystemService(Context.ACCESSIBILITY_SERVICE, mAccessibilityManager); + assignedTargets.add(MAGNIFICATION_CONTROLLER_NAME); + doReturn(assignedTargets).when(mAccessibilityManager).getAccessibilityShortcutTargets( + anyInt()); + + mMenuView = new AccessibilityFloatingMenuView(mContext); + mMenu = new AccessibilityFloatingMenu(mContext, mMenuView); + } + + @Test + public void showMenuView_success() { + mMenu.show(); + + assertThat(mMenuView.isShowing()).isTrue(); + } + + @Test + public void hideMenuView_success() { + mMenu.show(); + mMenu.hide(); + + assertThat(mMenuView.isShowing()).isFalse(); + } + + @After + public void tearDown() { + mMenu.hide(); + } +} diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java new file mode 100644 index 0000000000000..583a68bcbbacf --- /dev/null +++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/AccessibilityFloatingMenuViewTest.java @@ -0,0 +1,134 @@ +/* + * Copyright (C) 2021 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.google.common.truth.Truth.assertThat; + +import static org.mockito.ArgumentMatchers.any; +import static org.mockito.ArgumentMatchers.eq; +import static org.mockito.Mockito.doAnswer; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; + +import android.content.Context; +import android.graphics.drawable.GradientDrawable; +import android.graphics.drawable.LayerDrawable; +import android.testing.AndroidTestingRunner; +import android.view.View; +import android.view.WindowManager; + +import androidx.test.filters.SmallTest; + +import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.systemui.R; +import com.android.systemui.SysuiTestCase; + +import org.junit.Before; +import org.junit.Test; +import org.junit.runner.RunWith; +import org.mockito.Mock; +import org.mockito.MockitoAnnotations; + +import java.util.ArrayList; +import java.util.List; + +/** Tests for {@link AccessibilityFloatingMenuView}. */ +@SmallTest +@RunWith(AndroidTestingRunner.class) +public class AccessibilityFloatingMenuViewTest extends SysuiTestCase { + private AccessibilityFloatingMenuView mMenuView; + + @Mock + private WindowManager mWindowManager; + + private final List mTargets = new ArrayList<>(); + + @Before + public void setUp() { + MockitoAnnotations.initMocks(this); + + final WindowManager wm = mContext.getSystemService(WindowManager.class); + doAnswer(invocation -> wm.getMaximumWindowMetrics()).when( + mWindowManager).getMaximumWindowMetrics(); + mContext.addMockSystemService(Context.WINDOW_SERVICE, mWindowManager); + + mTargets.add(mock(AccessibilityTarget.class)); + mMenuView = new AccessibilityFloatingMenuView(mContext); + } + + @Test + public void initListView_success() { + assertThat(mMenuView.getChildCount()).isEqualTo(1); + } + + @Test + public void showMenuView_success() { + mMenuView.show(); + + assertThat(mMenuView.isShowing()).isTrue(); + verify(mWindowManager).addView(eq(mMenuView), any(WindowManager.LayoutParams.class)); + } + + @Test + public void showMenuView_showTwice_addViewOnce() { + mMenuView.show(); + mMenuView.show(); + + assertThat(mMenuView.isShowing()).isTrue(); + verify(mWindowManager, times(1)).addView(eq(mMenuView), + any(WindowManager.LayoutParams.class)); + } + + @Test + public void hideMenuView_success() { + mMenuView.show(); + mMenuView.hide(); + + assertThat(mMenuView.isShowing()).isFalse(); + verify(mWindowManager).removeView(eq(mMenuView)); + } + + @Test + public void hideMenuView_hideTwice_removeViewOnce() { + mMenuView.show(); + mMenuView.hide(); + mMenuView.hide(); + + assertThat(mMenuView.isShowing()).isFalse(); + verify(mWindowManager, times(1)).removeView(eq(mMenuView)); + } + + @Test + public void updateListViewRadius_singleTarget_matchResult() { + final float radius = + getContext().getResources().getDimension( + R.dimen.accessibility_floating_menu_single_radius); + final float[] expectedRadii = + new float[] {radius, radius, 0.0f, 0.0f, 0.0f, 0.0f, radius, radius}; + + mMenuView.onTargetsChanged(mTargets); + final View view = mMenuView.getChildAt(0); + final LayerDrawable layerDrawable = (LayerDrawable) view.getBackground(); + final GradientDrawable gradientDrawable = + (GradientDrawable) layerDrawable.getDrawable(0); + final float[] actualRadii = gradientDrawable.getCornerRadii(); + + assertThat(actualRadii).isEqualTo(expectedRadii); + } +}