[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
This commit is contained in:
Katie Dektar
2025-02-14 19:26:12 +00:00
parent e0dc58fc79
commit e94d88c74e
4 changed files with 129 additions and 18 deletions

View File

@@ -5330,6 +5330,16 @@
{4,number,integer}. Lift finger to stop magnification
]]>
</string>
<!-- Instructions on the accessibility preference screen teaching the user how to control magnification with a keyboard. [CHAR LIMIT=none] -->
<string name="accessibility_screen_magnification_keyboard_summary">
<![CDATA[
<b>To zoom with the keyboard:</b><br/>
{0,number,integer}. Use the shortcut to start magnification<br/>
{1,number,integer}. Hold down <xliff:g id="meta1">%1$s</xliff:g> and <xliff:g id="alt1">%2$s</xliff:g> and press + or - to zoom in or out<br/>
{2,number,integer}. Hold down <xliff:g id="meta2">%3$s</xliff:g> and <xliff:g id="alt2">%4$s</xliff:g> and press the arrow keys to move around the screen<br/>
{3,number,integer}. Use the shortcut to stop magnification
]]>
</string>
<!-- Instructions on the accessibility preference screen teaching the user how to interact with screen magnification when one finger panning feature is turned off. [CHAR LIMIT=none] -->
<string name="accessibility_screen_magnification_summary_one_finger_panning_off">
<![CDATA[

View File

@@ -35,6 +35,7 @@ import android.os.Bundle;
import android.provider.DeviceConfig;
import android.provider.Settings;
import android.text.TextUtils;
import android.view.InputDevice;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
@@ -231,9 +232,32 @@ public class ToggleScreenMagnificationPreferenceFragment extends
if (!arguments.containsKey(AccessibilitySettings.EXTRA_HTML_DESCRIPTION)
&& !Flags.enableMagnificationOneFingerPanningGesture()) {
String summary = MessageFormat.format(
context.getString(R.string.accessibility_screen_magnification_summary),
new Object[]{1, 2, 3, 4, 5});
String summary = "";
boolean hasTouchscreen = hasTouchscreen();
if (Flags.enableMagnificationKeyboardControl() && hasHardKeyboard()) {
// Include the keyboard summary when a keyboard is plugged in.
final String meta = context.getString(R.string.modifier_keys_meta);
final String alt = context.getString(R.string.modifier_keys_alt);
summary += MessageFormat.format(
context.getString(
R.string.accessibility_screen_magnification_keyboard_summary,
meta, alt, meta, alt),
new Object[]{1, 2, 3, 4});
if (hasTouchscreen) {
// Add a newline before the touchscreen text.
summary += "<br/><br/>";
}
}
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)

View File

@@ -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(

View File

@@ -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;
}
}