diff options
8 files changed, 353 insertions, 24 deletions
diff --git a/packages/SystemUI/aconfig/accessibility.aconfig b/packages/SystemUI/aconfig/accessibility.aconfig index 21263a92ae26..f7b1a26c9df9 100644 --- a/packages/SystemUI/aconfig/accessibility.aconfig +++ b/packages/SystemUI/aconfig/accessibility.aconfig @@ -10,6 +10,13 @@ flag { } 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 @@ flag { 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 f4b25a701825..8d560c37e5db 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 49e0df6f6afc..568b24dbd4f3 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 android.view.MotionEvent; 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 @@ class DragToInteractAnimationController { 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 000000000000..b5eeaa18447e --- /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 62d5feb7d024..6869bbaedcf3 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.common.ShortcutConstants.Access 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 androidx.lifecycle.Observer; 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 @@ class MenuViewLayer extends FrameLayout implements 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 @@ class MenuViewLayer extends FrameLayout implements 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 @@ class MenuViewLayer extends FrameLayout implements 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 @@ class MenuViewLayer extends FrameLayout implements @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 @@ class MenuViewLayer extends FrameLayout implements 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; + } + + // 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)); + + final TextView messageText = (TextView) mMessageView.getChildAt(Index.TEXT_VIEW); + messageText.setText(getMessageText(newTargetFeatures)); + } }); addView(mMenuView, LayerIndex.MENU_VIEW); @@ -456,6 +483,50 @@ class MenuViewLayer extends FrameLayout implements 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 @@ class MenuViewLayer extends FrameLayout implements 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 000000000000..9dd337e43b6a --- /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 be6f3ff8d3f9..68879a54cfe4 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.View.VISIBLE; 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.Build; 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.DynamicAnimation; 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 @@ public class MenuViewLayerTest extends SysuiTestCase { @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 @@ public class MenuViewLayerTest extends SysuiTestCase { @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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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 @@ public class MenuViewLayerTest extends SysuiTestCase { 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 b403a7fe8f12..7f542d130cf7 100644 --- a/proto/src/system_messages.proto +++ b/proto/src/system_messages.proto @@ -408,5 +408,9 @@ message SystemMessage { // 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; } } |