From e94d88c74e2eafc2640d471bea0a18024f75d574 Mon Sep 17 00:00:00 2001 From: Katie Dektar Date: Fri, 14 Feb 2025 19:26:12 +0000 Subject: [PATCH] [Mag] Keyboard shortcut info shown when physical keyboard present Shows keyboard shortcut info in magnification settings when a physical keyboard is present. See bug for screenshots. Hides touchscreen info in magnification settings when a touchscreen is not present. Adds ShadowInputDevice support for physical full keyboards. Bug: b/388847050 Test: Manual, atest ToggleScreenMagnificationPreferenceFragmentTest Flag: com.android.server.accessibility.enable_magnification_keyboard_control Change-Id: Ib53fbd8f929d1cc8e294f6f04bab405c9bb576a9 --- res/values/strings.xml | 10 ++++ ...ScreenMagnificationPreferenceFragment.java | 49 ++++++++++++++++- ...enMagnificationPreferenceFragmentTest.java | 33 +++++++++++ .../testutils/shadow/ShadowInputDevice.java | 55 ++++++++++++++----- 4 files changed, 129 insertions(+), 18 deletions(-) diff --git a/res/values/strings.xml b/res/values/strings.xml index c22ce6ce790..f8937f71842 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5330,6 +5330,16 @@ {4,number,integer}. Lift finger to stop magnification ]]> + + + To zoom with the keyboard:
+ {0,number,integer}. Use the shortcut to start magnification
+ {1,number,integer}. Hold down %1$s and %2$s and press + or - to zoom in or out
+ {2,number,integer}. Hold down %3$s and %4$s and press the arrow keys to move around the screen
+ {3,number,integer}. Use the shortcut to stop magnification + ]]> +

"; + } + + } + if (hasTouchscreen || TextUtils.isEmpty(summary)) { + // Always show the touchscreen summary if there is no summary yet, even if the + // touchscreen is missing. + // If the keyboard summary is present and there is no touchscreen, then we can + // ignore the touchscreen summary. + summary += MessageFormat.format( + context.getString(R.string.accessibility_screen_magnification_summary), + new Object[]{1, 2, 3, 4, 5}); + } arguments.putCharSequence(AccessibilitySettings.EXTRA_HTML_DESCRIPTION, summary); } @@ -610,6 +634,25 @@ public class ToggleScreenMagnificationPreferenceFragment extends getPrefContext(), MAGNIFICATION_CONTROLLER_NAME); } + private boolean hasHardKeyboard() { + final int[] devices = InputDevice.getDeviceIds(); + for (int i = 0; i < devices.length; i++) { + InputDevice device = InputDevice.getDevice(devices[i]); + if (device == null || device.isVirtual() || !device.isFullKeyboard()) { + continue; + } + + return true; + } + return false; + } + + private boolean hasTouchscreen() { + return getPackageManager() + .hasSystemFeature(PackageManager.FEATURE_TOUCHSCREEN) + || getPackageManager().hasSystemFeature(PackageManager.FEATURE_FAKETOUCH); + } + public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = new BaseSearchIndexProvider() { // LINT.IfChange(search_data) diff --git a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java index 4b28085b0bc..3c136f04356 100644 --- a/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/ToggleScreenMagnificationPreferenceFragmentTest.java @@ -51,6 +51,7 @@ import android.platform.test.annotations.EnableFlags; import android.platform.test.flag.junit.SetFlagsRule; import android.provider.DeviceConfig; import android.provider.Settings; +import android.view.InputDevice; import android.view.accessibility.AccessibilityManager; import android.view.accessibility.AccessibilityNodeInfo; @@ -68,6 +69,7 @@ import com.android.settings.accessibility.AccessibilityDialogUtils.DialogEnums; import com.android.settings.accessibility.MagnificationCapabilities.MagnificationMode; import com.android.settings.testutils.shadow.ShadowAccessibilityManager; import com.android.settings.testutils.shadow.ShadowDeviceConfig; +import com.android.settings.testutils.shadow.ShadowInputDevice; import com.android.settings.testutils.shadow.ShadowStorageManager; import com.android.settings.testutils.shadow.ShadowUserManager; import com.android.settingslib.core.lifecycle.LifecycleObserver; @@ -169,6 +171,7 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { @After public void tearDown() { ShadowDeviceConfig.reset(); + ShadowInputDevice.reset(); } @Test @@ -671,6 +674,36 @@ public class ToggleScreenMagnificationPreferenceFragmentTest { assertThat(fragment.getCurrentHtmlDescription().toString()).isNotEmpty(); } + @Test + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) + public void getCurrentHtmlDescription_doesNotIncludeKeyboardInfoIfNoKeyboardAttached() { + ToggleScreenMagnificationPreferenceFragment fragment = + mFragController.create( + R.id.main_content, /* bundle= */ null).start().resume().get(); + + String htmlDescription = fragment.getCurrentHtmlDescription().toString(); + assertThat(htmlDescription).isNotEmpty(); + assertThat(htmlDescription).doesNotContain("keyboard"); + } + + @Test + @EnableFlags(Flags.FLAG_ENABLE_MAGNIFICATION_KEYBOARD_CONTROL) + @Config(shadows = ShadowInputDevice.class) + public void getCurrentHtmlDescription_includesKeyboardInfoIfKeyboardAttached() { + int deviceId = 1; + ShadowInputDevice.sDeviceIds = new int[]{deviceId}; + InputDevice device = ShadowInputDevice.makeFullKeyboardInputDevicebyId(deviceId); + ShadowInputDevice.addDevice(deviceId, device); + + ToggleScreenMagnificationPreferenceFragment fragment = + mFragController.create( + R.id.main_content, /* bundle= */ null).start().resume().get(); + + String htmlDescription = fragment.getCurrentHtmlDescription().toString(); + assertThat(htmlDescription).isNotEmpty(); + assertThat(htmlDescription).contains("keyboard"); + } + @Test public void getSummary_magnificationEnabled_returnShortcutOnWithSummary() { mShadowAccessibilityManager.setAccessibilityShortcutTargets( diff --git a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java index 145c2e9be04..a448e17d36b 100644 --- a/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java +++ b/tests/robotests/src/com/android/settings/testutils/shadow/ShadowInputDevice.java @@ -37,6 +37,8 @@ public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice private int mSources; + private boolean mIsFullKeyboard; + @Implementation protected static int[] getDeviceIds() { return sDeviceIds; @@ -62,25 +64,10 @@ public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice return mDeviceId; } - public static InputDevice makeInputDevicebyId(int id) { - final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); - final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); - shadowInputDevice.setId(id); - return inputDevice; - } - public void setId(int id) { mDeviceId = id; } - public static InputDevice makeInputDevicebyIdWithSources(int id, int sources) { - final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); - final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); - shadowInputDevice.setId(id); - shadowInputDevice.setSources(sources); - return inputDevice; - } - @Implementation public int getSources() { return mSources; @@ -89,4 +76,42 @@ public class ShadowInputDevice extends org.robolectric.shadows.ShadowInputDevice public void setSources(int sources) { mSources = sources; } + + @Implementation + public boolean isFullKeyboard() { + return mIsFullKeyboard; + } + + public void setFullKeyboard(boolean isFullKeyboard) { + mIsFullKeyboard = isFullKeyboard; + } + + public static InputDevice makeInputDevicebyId(int id) { + final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); + final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); + shadowInputDevice.setId(id); + return inputDevice; + } + + public static InputDevice makeInputDevicebyIdWithSources(int id, int sources) { + final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); + final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); + shadowInputDevice.setId(id); + shadowInputDevice.setSources(sources); + return inputDevice; + } + + /** + * Create a full keyboard input device shadow. + * @param id The ID to use. If the ID is < 1, the device is considered virtual. + * @return The shadow InputDevice + */ + public static InputDevice makeFullKeyboardInputDevicebyId(int id) { + final InputDevice inputDevice = Shadow.newInstanceOf(InputDevice.class); + final ShadowInputDevice shadowInputDevice = Shadow.extract(inputDevice); + shadowInputDevice.setId(id); + shadowInputDevice.setFullKeyboard(true); + shadowInputDevice.setSources(InputDevice.SOURCE_KEYBOARD); + return inputDevice; + } }