From e6a20cb2ba81df5a9aaffaefca43cd58d2e4b780 Mon Sep 17 00:00:00 2001 From: Bill Lin Date: Fri, 16 Apr 2021 11:45:52 +0800 Subject: [PATCH 1/2] 2/ Implement Shortcut for One Handed mode feature - Define ONE_HANDED_COMPONENT_NAME - Define one handed mode string - Add one handed mode temp drawable - Add ToggleAllowListingFeatureTarget for OHM - Add ONE_HANDED_COMPONEMENT_NAME to featuresMap Test: manual enable shortcut of one handed mode Bug: 182425480 Change-Id: I5993cd8dbd3925efeebb9b75bb10d423c9a02601 --- .../AccessibilityShortcutController.java | 9 ++++++- .../dialog/AccessibilityTargetHelper.java | 12 +++++++++ .../drawable/ic_accessibility_one_handed.xml | 25 ++++++++++++++++++ ...ic_accessibility_one_handed_foreground.xml | 26 +++++++++++++++++++ core/res/res/values/strings.xml | 3 +++ core/res/res/values/symbols.xml | 3 +++ 6 files changed, 77 insertions(+), 1 deletion(-) create mode 100644 core/res/res/drawable/ic_accessibility_one_handed.xml create mode 100644 core/res/res/drawable/ic_accessibility_one_handed_foreground.xml diff --git a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java index 2b4e09d668517..83def0cc1fd6a 100644 --- a/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java +++ b/core/java/com/android/internal/accessibility/AccessibilityShortcutController.java @@ -80,6 +80,8 @@ public class AccessibilityShortcutController { "com.android.server.accessibility.MagnificationController"; public static final ComponentName MAGNIFICATION_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "Magnification"); + public static final ComponentName ONE_HANDED_COMPONENT_NAME = + new ComponentName("com.android.server.accessibility", "OneHandedMode"); public static final ComponentName REDUCE_BRIGHT_COLORS_COMPONENT_NAME = new ComponentName("com.android.server.accessibility", "ReduceBrightColors"); @@ -117,7 +119,7 @@ public class AccessibilityShortcutController { public static Map getFrameworkShortcutFeaturesMap() { if (sFrameworkShortcutFeaturesMap == null) { - Map featuresMap = new ArrayMap<>(2); + Map featuresMap = new ArrayMap<>(4); featuresMap.put(COLOR_INVERSION_COMPONENT_NAME, new ToggleableFrameworkFeatureInfo( Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED, @@ -128,6 +130,11 @@ public class AccessibilityShortcutController { Settings.Secure.ACCESSIBILITY_DISPLAY_DALTONIZER_ENABLED, "1" /* Value to enable */, "0" /* Value to disable */, R.string.color_correction_feature_name)); + featuresMap.put(ONE_HANDED_COMPONENT_NAME, + new ToggleableFrameworkFeatureInfo( + Settings.Secure.ONE_HANDED_MODE_ACTIVATED, + "1" /* Value to enable */, "0" /* Value to disable */, + R.string.one_handed_mode_feature_name)); featuresMap.put(REDUCE_BRIGHT_COLORS_COMPONENT_NAME, new ToggleableFrameworkFeatureInfo( Settings.Secure.REDUCE_BRIGHT_COLORS_ACTIVATED, diff --git a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java index f2d91ba308426..4a20ad0f33f5e 100644 --- a/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java +++ b/core/java/com/android/internal/accessibility/dialog/AccessibilityTargetHelper.java @@ -21,6 +21,7 @@ import static android.view.accessibility.AccessibilityManager.ACCESSIBILITY_BUTT import static com.android.internal.accessibility.AccessibilityShortcutController.COLOR_INVERSION_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.DALTONIZER_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; +import static com.android.internal.accessibility.AccessibilityShortcutController.ONE_HANDED_COMPONENT_NAME; import static com.android.internal.accessibility.AccessibilityShortcutController.REDUCE_BRIGHT_COLORS_COMPONENT_NAME; import static com.android.internal.accessibility.util.AccessibilityUtils.getAccessibilityServiceFragmentType; import static com.android.internal.accessibility.util.ShortcutUtils.isShortcutContained; @@ -230,6 +231,16 @@ public final class AccessibilityTargetHelper { context.getDrawable(R.drawable.ic_accessibility_color_inversion), Settings.Secure.ACCESSIBILITY_DISPLAY_INVERSION_ENABLED); + final ToggleAllowListingFeatureTarget oneHandedMode = + new ToggleAllowListingFeatureTarget(context, + shortcutType, + isShortcutContained(context, shortcutType, + ONE_HANDED_COMPONENT_NAME.flattenToString()), + ONE_HANDED_COMPONENT_NAME.flattenToString(), + context.getString(R.string.one_handed_mode_feature_name), + context.getDrawable(R.drawable.ic_accessibility_one_handed), + Settings.Secure.ONE_HANDED_MODE_ACTIVATED); + final ToggleAllowListingFeatureTarget reduceBrightColors = new ToggleAllowListingFeatureTarget(context, shortcutType, @@ -243,6 +254,7 @@ public final class AccessibilityTargetHelper { targets.add(magnification); targets.add(daltonizer); targets.add(colorInversion); + targets.add(oneHandedMode); targets.add(reduceBrightColors); return targets; diff --git a/core/res/res/drawable/ic_accessibility_one_handed.xml b/core/res/res/drawable/ic_accessibility_one_handed.xml new file mode 100644 index 0000000000000..0a469ba731eec --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_one_handed.xml @@ -0,0 +1,25 @@ + + + + + + + + + \ No newline at end of file diff --git a/core/res/res/drawable/ic_accessibility_one_handed_foreground.xml b/core/res/res/drawable/ic_accessibility_one_handed_foreground.xml new file mode 100644 index 0000000000000..18e56187efb33 --- /dev/null +++ b/core/res/res/drawable/ic_accessibility_one_handed_foreground.xml @@ -0,0 +1,26 @@ + + + + + + + \ No newline at end of file diff --git a/core/res/res/values/strings.xml b/core/res/res/values/strings.xml index 270b98ebebe36..94baee41306d0 100644 --- a/core/res/res/values/strings.xml +++ b/core/res/res/values/strings.xml @@ -4636,6 +4636,9 @@ shown in the warning dialog about the accessibility shortcut. --> Color Correction + + One-Handed mode + Extra dim diff --git a/core/res/res/values/symbols.xml b/core/res/res/values/symbols.xml index 3fec918f7ac05..c48c53a52edfd 100644 --- a/core/res/res/values/symbols.xml +++ b/core/res/res/values/symbols.xml @@ -3409,9 +3409,12 @@ + + + From eb9b719bc5d8119cf027165cf88a8954efc96b5b Mon Sep 17 00:00:00 2001 From: Bill Lin Date: Fri, 23 Apr 2021 15:43:17 +0800 Subject: [PATCH 2/2] 3/ Listen ONE_HANDED_MODE_ACTIVATED for A11y shortcut action - Observe ONE_HANDED_MODE_ACTIVATED secure settings key - Skip action when OneHandedState is in transition 1. STATE_ENTERING 2. STATE_EXITING - Skip action if current OneHandedState = request activate 1. STATE NONE -> Shortcut request ACTIVATED : StartOneHanded 2. STATE_ACTIVE -> Shortcut request non ACTIVATED : StopOneHanded 3. STATE_NONE -> Shortcut request non ACTIVATED : SKip 4. STATE_ACTIVE -> Shortcut request ACTIVATED : Skip - Controller will update latest state to secure setting ONE_HANDED_MODE_ACTIVATED either trigger by gesture or shortcut Test: atest WMShellUnitTests Test: enable shortcut and click Bug: 182425480 Change-Id: I03bd2679a5a08801efc9868148adfddef87171f4 --- .../shell/onehanded/OneHandedController.java | 38 ++++++++++ .../onehanded/OneHandedSettingsUtil.java | 26 ++++++- .../onehanded/OneHandedControllerTest.java | 69 +++++++++++++++++++ 3 files changed, 131 insertions(+), 2 deletions(-) diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java index 04ec3917428e4..575e5c75c1562 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedController.java @@ -135,6 +135,7 @@ public class OneHandedController implements RemoteCallable } }; + private final ContentObserver mActivatedObserver; private final ContentObserver mEnabledObserver; private final ContentObserver mTimeoutObserver; private final ContentObserver mTaskChangeExitObserver; @@ -170,11 +171,13 @@ public class OneHandedController implements RemoteCallable @Override public void onStartFinished(Rect bounds) { mState.setState(STATE_ACTIVE); + notifyShortcutState(STATE_ACTIVE); } @Override public void onStopFinished(Rect bounds) { mState.setState(STATE_NONE); + notifyShortcutState(STATE_NONE); } }; @@ -287,6 +290,7 @@ public class OneHandedController implements RemoteCallable context.getContentResolver(), mUserId); mTimeoutHandler = timeoutHandler; + mActivatedObserver = getObserver(this::onActivatedActionChanged); mEnabledObserver = getObserver(this::onEnabledSettingChanged); mTimeoutObserver = getObserver(this::onTimeoutSettingChanged); mTaskChangeExitObserver = getObserver(this::onTaskChangeExitSettingChanged); @@ -347,6 +351,12 @@ public class OneHandedController implements RemoteCallable updateOneHandedEnabled(); } + @VisibleForTesting + void notifyShortcutState(@OneHandedState.State int state) { + mOneHandedSettingsUtil.setOneHandedModeActivated( + mContext.getContentResolver(), state == STATE_ACTIVE ? 1 : 0, mUserId); + } + @VisibleForTesting void startOneHanded() { if (isLockedDisabled()) { @@ -416,6 +426,9 @@ public class OneHandedController implements RemoteCallable } private void registerSettingObservers(int newUserId) { + mOneHandedSettingsUtil.registerSettingsKeyObserver( + Settings.Secure.ONE_HANDED_MODE_ACTIVATED, + mContext.getContentResolver(), mActivatedObserver, newUserId); mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_ENABLED, mContext.getContentResolver(), mEnabledObserver, newUserId); mOneHandedSettingsUtil.registerSettingsKeyObserver(Settings.Secure.ONE_HANDED_MODE_TIMEOUT, @@ -465,6 +478,26 @@ public class OneHandedController implements RemoteCallable }; } + @VisibleForTesting + void onActivatedActionChanged() { + if (mState.isTransitioning() || !isOneHandedEnabled()) { + return; + } + + final boolean isActivated = mState.getState() == STATE_ACTIVE; + final boolean requestActivated = mOneHandedSettingsUtil.getOneHandedModeActivated( + mContext.getContentResolver(), mUserId); + // When gesture trigger action, we will update settings and introduce observer callback + // again, then the following logic will just ignore the second redundant callback. + if (isActivated ^ requestActivated) { + if (requestActivated) { + startOneHanded(); + } else { + stopOneHanded(); + } + } + } + @VisibleForTesting void onEnabledSettingChanged() { final boolean enabled = mOneHandedSettingsUtil.getSettingsOneHandedModeEnabled( @@ -544,6 +577,11 @@ public class OneHandedController implements RemoteCallable return mLockedDisabled; } + @VisibleForTesting + boolean isOneHandedEnabled() { + return mIsOneHandedEnabled; + } + private void updateOneHandedEnabled() { if (mState.getState() == STATE_ENTERING || mState.getState() == STATE_ACTIVE) { mMainExecutor.execute(() -> stopOneHanded()); diff --git a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java index 1b2fcdd6313e6..232b6f9cfdd81 100644 --- a/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java +++ b/libs/WindowManager/Shell/src/com/android/wm/shell/onehanded/OneHandedSettingsUtil.java @@ -131,8 +131,28 @@ public final class OneHandedSettingsUtil { * Returns whether swipe bottom to notification gesture enabled or not. */ public boolean getSettingsSwipeToNotificationEnabled(ContentResolver resolver, int userId) { - return Settings.Secure.getInt(resolver, - Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0 /* Default OFF */) == 1; + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.SWIPE_BOTTOM_TO_NOTIFICATION_ENABLED, 0, userId) == 1; + } + + /** + * Sets one handed activated or not to notify state for shortcut + * + * @return activated or not + */ + public boolean getOneHandedModeActivated(ContentResolver resolver, int userId) { + return Settings.Secure.getIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_ACTIVATED, 0, userId) == 1; + } + + /** + * Sets one handed activated or not to notify state for shortcut + * + * @return activated or not + */ + public boolean setOneHandedModeActivated(ContentResolver resolver, int state, int userId) { + return Settings.Secure.putIntForUser(resolver, + Settings.Secure.ONE_HANDED_MODE_ACTIVATED, state, userId); } void dump(PrintWriter pw, String prefix, ContentResolver resolver, @@ -145,6 +165,8 @@ public final class OneHandedSettingsUtil { pw.println(getSettingsOneHandedModeTimeout(resolver, userId)); pw.print(innerPrefix + "tapsAppToExit="); pw.println(getSettingsTapsAppToExit(resolver, userId)); + pw.print(innerPrefix + "shortcutActivated="); + pw.println(getOneHandedModeActivated(resolver, userId)); } public OneHandedSettingsUtil() { diff --git a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java index c1282c9d7b125..99342f02463af 100644 --- a/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java +++ b/libs/WindowManager/Shell/tests/unittest/src/com/android/wm/shell/onehanded/OneHandedControllerTest.java @@ -16,7 +16,9 @@ package com.android.wm.shell.onehanded; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_ACTIVE; import static com.android.wm.shell.onehanded.OneHandedState.STATE_ENTERING; +import static com.android.wm.shell.onehanded.OneHandedState.STATE_EXITING; import static com.android.wm.shell.onehanded.OneHandedState.STATE_NONE; import static com.google.common.truth.Truth.assertThat; @@ -360,4 +362,71 @@ public class OneHandedControllerTest extends OneHandedTestCase { verify(mMockGestureHandler).onGestureEnabled(isOneHandedEnabled); } + + @Test + public void testStateActive_shortcutRequestActivate_skipActions() { + when(mSpiedTransitionState.getState()).thenReturn(STATE_ACTIVE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController, never()).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } + + @Test + public void testStateNotActive_shortcutRequestInActivate_skipAction() { + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(false); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController, never()).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } + + @Test + public void testStateNotActive_shortcutRequestActivate_doAction() { + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } + + @Test + public void testEnteringTransition_shortcutRequestActivate_skipActions() { + when(mSpiedTransitionState.getState()).thenReturn(STATE_ENTERING); + when(mSpiedTransitionState.isTransitioning()).thenReturn(true); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController, never()).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } + + @Test + public void testExitingTransition_shortcutRequestActivate_skipActions() { + when(mSpiedTransitionState.getState()).thenReturn(STATE_EXITING); + when(mSpiedTransitionState.isTransitioning()).thenReturn(true); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController, never()).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } + + @Test + public void testOneHandedDisabled_shortcutEnabled_skipActions() { + when(mSpiedOneHandedController.isOneHandedEnabled()).thenReturn(false); + when(mSpiedTransitionState.getState()).thenReturn(STATE_NONE); + when(mSpiedTransitionState.isTransitioning()).thenReturn(false); + when(mMockSettingsUitl.getOneHandedModeActivated(any(), anyInt())).thenReturn(true); + mSpiedOneHandedController.onActivatedActionChanged(); + + verify(mSpiedOneHandedController, never()).startOneHanded(); + verify(mSpiedOneHandedController, never()).stopOneHanded(); + } }