[Drag To Hide] Shows notification when user drag-to-hide a11y floating button
* Click on notification to bring back a11y floating button
* Swipe notification to remove a11y floating button and its services
* A11y service key changed to dismiss notification
* Align to create ACCESSIBILITY channel in systemui NotificationChannels
Bug: 298718415
Flag: ACONFIG com.android.systemui.floating_menu_drag_to_hide DEVELOPMENT
Test: atest MenuViewLayerTest MenuNotificationFactoryTest NotificationChannelsTest
Change-Id: Ie209ec946ea15903acfa6913492605277e9d7057
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig
index 21263a9..f7b1a26 100644
--- a/packages/SystemUI/aconfig/accessibility.aconfig
+++ b/packages/SystemUI/aconfig/accessibility.aconfig
@@ -10,6 +10,13 @@
}
flag {
+ name: "floating_menu_drag_to_hide"
+ namespace: "accessibility"
+ description: "Allows users to hide the FAB then use notification to dismiss or bring it back."
+ bug: "298718415"
+}
+
+flag {
name: "floating_menu_ime_displacement_animation"
namespace: "accessibility"
description: "Adds an animation for when the FAB is displaced by an IME becoming visible."
@@ -28,4 +35,4 @@
namespace: "accessibility"
description: "Animates the floating menu's transition between curved and jagged edges."
bug: "281140482"
-}
\ No newline at end of file
+}
diff --git a/packages/SystemUI/res/values/strings.xml b/packages/SystemUI/res/values/strings.xml
index f4b25a7..8d560c3 100644
--- a/packages/SystemUI/res/values/strings.xml
+++ b/packages/SystemUI/res/values/strings.xml
@@ -2274,6 +2274,8 @@
<string name="notification_channel_storage">Storage</string>
<!-- Title for the notification channel for hints and suggestions. [CHAR LIMIT=NONE] -->
<string name="notification_channel_hints">Hints</string>
+ <!-- Title for the notification channel for accessibility related (i.e. accessibility floating menu). [CHAR LIMIT=NONE] -->
+ <string name="notification_channel_accessibility">Accessibility</string>
<!-- App label of the instant apps notification [CHAR LIMIT=60] -->
<string name="instant_apps">Instant Apps</string>
@@ -2544,6 +2546,11 @@
<string name="accessibility_floating_button_docking_tooltip">Move button to the edge to hide it temporarily</string>
<!-- Text for the undo action button of the message view of the accessibility floating menu to perform undo operation. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo">Undo</string>
+ <!-- Notification title shown when accessibility floating button is in hidden state. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_floating_button_hidden_notification_title">Accessibility button hidden</string>
+ <!-- Notification content text to explain user can tap notification to bring back accessibility floating button. [CHAR LIMIT=NONE] -->
+ <string name="accessibility_floating_button_hidden_notification_text">Tap to show accessibility button</string>
+
<!-- Text for the message view with undo action of the accessibility floating menu to show which feature shortcut was removed. [CHAR LIMIT=30]-->
<string name="accessibility_floating_button_undo_message_label_text"><xliff:g id="feature name" example="Magnification">%s</xliff:g> shortcut removed</string>
<!-- Text for the message view with undo action of the accessibility floating menu to show how many features shortcuts were removed. [CHAR LIMIT=30]-->
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
index 49e0df6..568b24d 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/DragToInteractAnimationController.java
@@ -24,6 +24,7 @@
import androidx.annotation.NonNull;
import androidx.dynamicanimation.animation.DynamicAnimation;
+import com.android.internal.annotations.VisibleForTesting;
import com.android.wm.shell.common.bubbles.DismissView;
import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
@@ -116,6 +117,11 @@
mMagnetizedObject.setMagnetListener(magnetListener);
}
+ @VisibleForTesting
+ MagnetizedObject.MagnetListener getMagnetListener() {
+ return mMagnetizedObject.getMagnetListener();
+ }
+
void maybeConsumeDownMotionEvent(MotionEvent event) {
mMagnetizedObject.maybeConsumeMotionEvent(event);
}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
new file mode 100644
index 0000000..b5eeaa1
--- /dev/null
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactory.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2023 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.app.Notification;
+import android.app.PendingIntent;
+import android.content.Context;
+import android.content.Intent;
+import android.os.UserHandle;
+
+import com.android.systemui.res.R;
+import com.android.systemui.util.NotificationChannels;
+
+class MenuNotificationFactory {
+ public static final String ACTION_UNDO =
+ "com.android.systemui.accessibility.floatingmenu.action.UNDO";
+ public static final String ACTION_DELETE =
+ "com.android.systemui.accessibility.floatingmenu.action.DELETE";
+
+ private final Context mContext;
+
+ MenuNotificationFactory(Context context) {
+ mContext = context;
+ }
+
+ public Notification createHiddenNotification() {
+ final CharSequence title = mContext.getText(
+ R.string.accessibility_floating_button_hidden_notification_title);
+ final CharSequence content = mContext.getText(
+ R.string.accessibility_floating_button_hidden_notification_text);
+
+ return new Notification.Builder(mContext, NotificationChannels.ALERTS)
+ .setContentTitle(title)
+ .setContentText(content)
+ .setSmallIcon(R.drawable.ic_settings_24dp)
+ .setContentIntent(buildUndoIntent())
+ .setDeleteIntent(buildDeleteIntent())
+ .setColor(mContext.getResources().getColor(
+ com.android.internal.R.color.system_notification_accent_color))
+ .setLocalOnly(true)
+ .setCategory(Notification.CATEGORY_SYSTEM)
+ .build();
+ }
+
+ private PendingIntent buildUndoIntent() {
+ final Intent intent = new Intent(ACTION_UNDO);
+
+ return PendingIntent.getBroadcast(mContext, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_IMMUTABLE);
+
+ }
+
+ private PendingIntent buildDeleteIntent() {
+ final Intent intent = new Intent(ACTION_DELETE);
+
+ return PendingIntent.getBroadcastAsUser(mContext, /* requestCode= */ 0, intent,
+ PendingIntent.FLAG_CANCEL_CURRENT | PendingIntent.FLAG_ONE_SHOT
+ | PendingIntent.FLAG_IMMUTABLE, UserHandle.CURRENT);
+
+ }
+}
diff --git a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
index 62d5feb..6869bba 100644
--- a/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
+++ b/packages/SystemUI/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayer.java
@@ -26,16 +26,21 @@
import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType;
import static com.android.internal.accessibility.util.AccessibilityUtils.setAccessibilityServiceState;
import static com.android.systemui.accessibility.floatingmenu.MenuMessageView.Index;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.util.PluralMessageFormaterKt.icuMessageFormat;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.annotation.IntDef;
import android.annotation.StringDef;
import android.annotation.SuppressLint;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentCallbacks;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
+import android.content.IntentFilter;
import android.content.res.Configuration;
import android.content.res.Resources;
import android.graphics.Rect;
@@ -58,6 +63,7 @@
import com.android.internal.accessibility.dialog.AccessibilityTarget;
import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.internal.util.Preconditions;
import com.android.systemui.Flags;
import com.android.systemui.res.R;
@@ -91,6 +97,8 @@
private final MenuViewAppearance mMenuViewAppearance;
private final MenuAnimationController mMenuAnimationController;
private final AccessibilityManager mAccessibilityManager;
+ private final NotificationManager mNotificationManager;
+ private final MenuNotificationFactory mNotificationFactory;
private final Handler mHandler = new Handler(Looper.getMainLooper());
private final IAccessibilityFloatingMenu mFloatingMenu;
private final SecureSettings mSecureSettings;
@@ -103,7 +111,9 @@
private final Rect mImeInsetsRect = new Rect();
private boolean mIsMigrationTooltipShowing;
private boolean mShouldShowDockTooltip;
+ private boolean mIsNotificationShown;
private Optional<MenuEduTooltipView> mEduTooltipView = Optional.empty();
+ private BroadcastReceiver mNotificationActionReceiver;
@IntDef({
LayerIndex.MENU_VIEW,
@@ -184,10 +194,16 @@
mMenuViewAppearance = new MenuViewAppearance(context, windowManager);
mMenuView = new MenuView(context, mMenuViewModel, mMenuViewAppearance);
mMenuAnimationController = mMenuView.getMenuAnimationController();
- mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+ if (Flags.floatingMenuDragToHide()) {
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowNotification);
+ } else {
+ mMenuAnimationController.setDismissCallback(this::hideMenuAndShowMessage);
+ }
mMenuAnimationController.setSpringAnimationsEndAction(this::onSpringAnimationsEndAction);
mDismissView = new DismissView(context);
DismissViewUtils.setup(mDismissView);
+ mNotificationFactory = new MenuNotificationFactory(context);
+ mNotificationManager = context.getSystemService(NotificationManager.class);
mDragToInteractAnimationController = new DragToInteractAnimationController(
mDismissView, mMenuView);
mDragToInteractAnimationController.setMagnetListener(new MagnetizedObject.MagnetListener() {
@@ -204,7 +220,11 @@
@Override
public void onReleasedInTarget(@NonNull MagnetizedObject.MagneticTarget target) {
- hideMenuAndShowMessage();
+ if (Flags.floatingMenuDragToHide()) {
+ hideMenuAndShowNotification();
+ } else {
+ hideMenuAndShowMessage();
+ }
mDismissView.hide();
mDragToInteractAnimationController.animateDismissMenu(/* scaleUp= */ false);
}
@@ -218,18 +238,25 @@
mMessageView = new MenuMessageView(context);
mMenuView.setOnTargetFeaturesChangeListener(newTargetFeatures -> {
- if (newTargetFeatures.size() < 1) {
- return;
- }
-
- // During the undo action period, the pending action will be canceled and undo back
- // to the previous state if users did any action related to the accessibility features.
- if (mMessageView.getVisibility() == VISIBLE) {
+ if (Flags.floatingMenuDragToHide()) {
+ dismissNotification();
undo();
- }
+ } else {
+ if (newTargetFeatures.size() < 1) {
+ return;
+ }
- final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
- messageText.setText(getMessageText(newTargetFeatures));
+ // During the undo action period, the pending action will be canceled and undo back
+ // to the previous state if users did any action related to the accessibility
+ // features.
+ if (mMessageView.getVisibility() == VISIBLE) {
+ undo();
+ }
+
+
+ final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW);
+ messageText.setText(getMessageText(newTargetFeatures));
+ }
});
addView(mMenuView, LayerIndex.MENU_VIEW);
@@ -456,6 +483,50 @@
mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
}
+ private void hideMenuAndShowNotification() {
+ mMenuAnimationController.startShrinkAnimation(() -> mMenuView.setVisibility(GONE));
+ showNotification();
+ }
+
+ private void showNotification() {
+ registerReceiverIfNeeded();
+ if (!mIsNotificationShown) {
+ mNotificationManager.notify(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN,
+ mNotificationFactory.createHiddenNotification());
+ mIsNotificationShown = true;
+ }
+ }
+
+ private void dismissNotification() {
+ unregisterReceiverIfNeeded();
+ if (mIsNotificationShown) {
+ mNotificationManager.cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ mIsNotificationShown = false;
+ }
+ }
+
+ private void registerReceiverIfNeeded() {
+ if (mNotificationActionReceiver != null) {
+ return;
+ }
+ mNotificationActionReceiver = new MenuNotificationActionReceiver();
+ final IntentFilter intentFilter = new IntentFilter();
+ intentFilter.addAction(ACTION_UNDO);
+ intentFilter.addAction(ACTION_DELETE);
+ getContext().registerReceiver(mNotificationActionReceiver, intentFilter,
+ Context.RECEIVER_EXPORTED);
+ }
+
+ private void unregisterReceiverIfNeeded() {
+ if (mNotificationActionReceiver == null) {
+ return;
+ }
+ getContext().unregisterReceiver(mNotificationActionReceiver);
+ mNotificationActionReceiver = null;
+ }
+
private void undo() {
mHandler.removeCallbacksAndMessages(/* token= */ null);
mMessageView.setVisibility(GONE);
@@ -464,4 +535,23 @@
mMenuView.setVisibility(VISIBLE);
mMenuAnimationController.startGrowAnimation();
}
+
+ @VisibleForTesting
+ DragToInteractAnimationController getDragToInteractAnimationController() {
+ return mDragToInteractAnimationController;
+ }
+
+ private class MenuNotificationActionReceiver extends BroadcastReceiver {
+ @Override
+ public void onReceive(Context context, Intent intent) {
+ String action = intent.getAction();
+ if (ACTION_UNDO.equals(action)) {
+ dismissNotification();
+ undo();
+ } else if (ACTION_DELETE.equals(action)) {
+ dismissNotification();
+ mDismissMenuAction.run();
+ }
+ }
+ }
}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
new file mode 100644
index 0000000..9dd337e
--- /dev/null
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuNotificationFactoryTest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright (C) 2023 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 android.app.Notification;
+import android.testing.AndroidTestingRunner;
+
+import androidx.test.filters.SmallTest;
+
+import com.android.systemui.SysuiTestCase;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidTestingRunner.class)
+@SmallTest
+public class MenuNotificationFactoryTest extends SysuiTestCase {
+ private MenuNotificationFactory mMenuNotificationFactory;
+
+ @Before
+ public void setUp() {
+ mMenuNotificationFactory = new MenuNotificationFactory(mContext);
+ }
+
+ @Test
+ public void createHiddenNotification_hasUndoAndDeleteAction() {
+ Notification notification = mMenuNotificationFactory.createHiddenNotification();
+
+ assertThat(notification.contentIntent.getIntent().getAction()).isEqualTo(
+ MenuNotificationFactory.ACTION_UNDO);
+ assertThat(notification.deleteIntent.getIntent().getAction()).isEqualTo(
+ MenuNotificationFactory.ACTION_DELETE);
+ }
+}
diff --git a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
index be6f3ff..68879a5 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
+++ b/packages/SystemUI/tests/src/com/android/systemui/accessibility/floatingmenu/MenuViewLayerTest.java
@@ -21,15 +21,30 @@
import static android.view.WindowInsets.Type.displayCutout;
import static android.view.WindowInsets.Type.ime;
import static android.view.WindowInsets.Type.systemBars;
+
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_DELETE;
+import static com.android.systemui.accessibility.floatingmenu.MenuNotificationFactory.ACTION_UNDO;
import static com.android.systemui.accessibility.floatingmenu.MenuViewLayer.LayerIndex;
+
import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.any;
+import static org.mockito.ArgumentMatchers.anyInt;
import static org.mockito.Mockito.doReturn;
+import static org.mockito.Mockito.eq;
+import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.spy;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import android.accessibilityservice.AccessibilityServiceInfo;
+import android.app.Notification;
+import android.app.NotificationManager;
+import android.content.BroadcastReceiver;
import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.ResolveInfo;
import android.content.pm.ServiceInfo;
@@ -40,6 +55,8 @@
import android.os.UserHandle;
import android.platform.test.annotations.DisableFlags;
import android.platform.test.annotations.EnableFlags;
+import android.platform.test.flag.junit.CheckFlagsRule;
+import android.platform.test.flag.junit.DeviceFlagsValueProvider;
import android.provider.Settings;
import android.testing.AndroidTestingRunner;
import android.testing.TestableLooper;
@@ -53,16 +70,21 @@
import androidx.dynamicanimation.animation.SpringAnimation;
import androidx.test.filters.SmallTest;
+import com.android.internal.messages.nano.SystemMessageProto;
import com.android.systemui.Flags;
import com.android.systemui.SysuiTestCase;
+import com.android.systemui.SysuiTestableContext;
import com.android.systemui.util.settings.SecureSettings;
+import com.android.wm.shell.common.magnetictarget.MagnetizedObject;
import org.junit.After;
import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
import org.junit.runner.RunWith;
+import org.mockito.ArgumentCaptor;
import org.mockito.Mock;
+import org.mockito.Spy;
import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
@@ -98,6 +120,12 @@
@Rule
public MockitoRule mockito = MockitoJUnit.rule();
+ @Rule
+ public final CheckFlagsRule mCheckFlagsRule =
+ DeviceFlagsValueProvider.createCheckFlagsRule();
+
+ @Spy
+ private SysuiTestableContext mSpyContext = getContext();
@Mock
private IAccessibilityFloatingMenu mFloatingMenu;
@@ -110,8 +138,12 @@
@Mock
private AccessibilityManager mStubAccessibilityManager;
+ private final NotificationManager mMockNotificationManager = mock(NotificationManager.class);
+
@Before
public void setUp() throws Exception {
+ mSpyContext.addMockSystemService(Context.NOTIFICATION_SERVICE, mMockNotificationManager);
+
final Rect mDisplayBounds = new Rect();
mDisplayBounds.set(/* left= */ 0, /* top= */ 0, DISPLAY_WINDOW_WIDTH,
DISPLAY_WINDOW_HEIGHT);
@@ -119,31 +151,31 @@
new WindowMetrics(mDisplayBounds, fakeDisplayInsets(), /* density = */ 0.0f));
doReturn(mWindowMetrics).when(mStubWindowManager).getCurrentWindowMetrics();
- mMenuViewLayer = new MenuViewLayer(mContext, mStubWindowManager, mStubAccessibilityManager,
- mFloatingMenu, mSecureSettings);
+ mMenuViewLayer = new MenuViewLayer(mSpyContext, mStubWindowManager,
+ mStubAccessibilityManager, mFloatingMenu, mSecureSettings);
mMenuView = (MenuView) mMenuViewLayer.getChildAt(LayerIndex.MENU_VIEW);
mMenuAnimationController = mMenuView.getMenuAnimationController();
mLastAccessibilityButtonTargets =
- Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, UserHandle.USER_CURRENT);
mLastEnabledAccessibilityServices =
- Settings.Secure.getStringForUser(mContext.getContentResolver(),
+ Settings.Secure.getStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, UserHandle.USER_CURRENT);
mMenuViewLayer.onAttachedToWindow();
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, "", UserHandle.USER_CURRENT);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, "", UserHandle.USER_CURRENT);
}
@After
public void tearDown() throws Exception {
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ACCESSIBILITY_BUTTON_TARGETS, mLastAccessibilityButtonTargets,
UserHandle.USER_CURRENT);
- Settings.Secure.putStringForUser(mContext.getContentResolver(),
+ Settings.Secure.putStringForUser(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES, mLastEnabledAccessibilityServices,
UserHandle.USER_CURRENT);
@@ -188,7 +220,7 @@
setupEnabledAccessibilityServiceList();
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
assertThat(value).isEqualTo("");
@@ -203,7 +235,7 @@
AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY)).thenReturn(stubShortcutTargets);
mMenuViewLayer.mDismissMenuAction.run();
- final String value = Settings.Secure.getString(mContext.getContentResolver(),
+ final String value = Settings.Secure.getString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES);
assertThat(value).isEqualTo(TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -278,9 +310,60 @@
assertThat(mMenuView.getTranslationX()).isEqualTo(beforePosition.x);
assertThat(mMenuView.getTranslationY()).isEqualTo(beforePosition.y);
}
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void onReleasedInTarget_hideMenuAndShowNotificationWithExpectedActions() {
+ dragMenuThenReleasedInTarget();
+
+ verify(mMockNotificationManager).notify(
+ eq(SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN),
+ any(Notification.class));
+ ArgumentCaptor<IntentFilter> intentFilterCaptor = ArgumentCaptor.forClass(
+ IntentFilter.class);
+ verify(mSpyContext).registerReceiver(
+ any(BroadcastReceiver.class),
+ intentFilterCaptor.capture(),
+ anyInt());
+ assertThat(intentFilterCaptor.getValue().matchAction(ACTION_UNDO)).isTrue();
+ assertThat(intentFilterCaptor.getValue().matchAction(ACTION_DELETE)).isTrue();
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void receiveActionUndo_dismissNotificationAndMenuVisible() {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ dragMenuThenReleasedInTarget();
+
+ verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+ any(IntentFilter.class), anyInt());
+ broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_UNDO));
+
+ verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+ verify(mMockNotificationManager).cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ assertThat(mMenuView.getVisibility()).isEqualTo(VISIBLE);
+ }
+
+ @Test
+ @EnableFlags(Flags.FLAG_FLOATING_MENU_DRAG_TO_HIDE)
+ public void receiveActionDelete_dismissNotificationAndHideMenu() {
+ ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(
+ BroadcastReceiver.class);
+ dragMenuThenReleasedInTarget();
+
+ verify(mSpyContext).registerReceiver(broadcastReceiverCaptor.capture(),
+ any(IntentFilter.class), anyInt());
+ broadcastReceiverCaptor.getValue().onReceive(mSpyContext, new Intent(ACTION_DELETE));
+
+ verify(mSpyContext).unregisterReceiver(broadcastReceiverCaptor.getValue());
+ verify(mMockNotificationManager).cancel(
+ SystemMessageProto.SystemMessage.NOTE_A11Y_FLOATING_MENU_HIDDEN);
+ verify(mFloatingMenu).hide();
+ }
private void setupEnabledAccessibilityServiceList() {
- Settings.Secure.putString(mContext.getContentResolver(),
+ Settings.Secure.putString(mSpyContext.getContentResolver(),
Settings.Secure.ENABLED_ACCESSIBILITY_SERVICES,
TEST_SELECT_TO_SPEAK_COMPONENT_NAME.flattenToString());
@@ -344,6 +427,12 @@
springAnimation.skipToEnd();
springAnimation.doAnimationFrame(500);
});
+ }
+ private void dragMenuThenReleasedInTarget() {
+ MagnetizedObject.MagnetListener magnetListener =
+ mMenuViewLayer.getDragToInteractAnimationController().getMagnetListener();
+ magnetListener.onReleasedInTarget(
+ new MagnetizedObject.MagneticTarget(mock(View.class), 200));
}
}
diff --git a/proto/src/system_messages.proto b/proto/src/system_messages.proto
index b403a7f..7f542d1 100644
--- a/proto/src/system_messages.proto
+++ b/proto/src/system_messages.proto
@@ -408,5 +408,9 @@
// Notify the user about external display events related to screenshot.
// Package: com.android.systemui
NOTE_GLOBAL_SCREENSHOT_EXTERNAL_DISPLAY = 1008;
+
+ // Notify the user that accessibility floating menu is hidden.
+ // Package: com.android.systemui
+ NOTE_A11Y_FLOATING_MENU_HIDDEN = 1009;
}
}