diff --git a/res/values/accessibility_shortcut_keys.xml b/res/values/accessibility_shortcut_keys.xml index 0d409a8304a..4992a544eb0 100644 --- a/res/values/accessibility_shortcut_keys.xml +++ b/res/values/accessibility_shortcut_keys.xml @@ -16,6 +16,7 @@ --> + shortcut_description shortcut_volume_keys_pref shortcut_gesture_pref shortcut_nav_button_pref diff --git a/res/values/strings.xml b/res/values/strings.xml index 76c7106fae3..b1236082236 100644 --- a/res/values/strings.xml +++ b/res/values/strings.xml @@ -5112,6 +5112,10 @@ Hold volume keys Triple tap screen + + Edit accessibility shortcuts + + Chose your shortcut for %1$s Continue diff --git a/res/xml/accessibility_edit_shortcuts.xml b/res/xml/accessibility_edit_shortcuts.xml index 06cbeddc6ab..8be0ee50857 100644 --- a/res/xml/accessibility_edit_shortcuts.xml +++ b/res/xml/accessibility_edit_shortcuts.xml @@ -18,62 +18,66 @@ - + - + - + - + - + - + - + + + + \ No newline at end of file diff --git a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java index a3cbb57ba5b..5a3b13ab640 100644 --- a/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java +++ b/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragment.java @@ -27,16 +27,21 @@ import static com.android.internal.accessibility.AccessibilityShortcutController import static com.android.internal.accessibility.AccessibilityShortcutController.MAGNIFICATION_CONTROLLER_NAME; import static com.android.settings.SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE; +import android.app.Activity; import android.app.settings.SettingsEnums; import android.content.ComponentName; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.database.ContentObserver; +import android.icu.text.ListFormatter; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.provider.Settings; import android.text.TextUtils; +import android.util.ArrayMap; +import android.util.Pair; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; @@ -48,9 +53,12 @@ import androidx.annotation.VisibleForTesting; import androidx.preference.Preference; import androidx.recyclerview.widget.RecyclerView; +import com.android.internal.accessibility.dialog.AccessibilityTarget; +import com.android.internal.accessibility.dialog.AccessibilityTargetHelper; import com.android.settings.R; import com.android.settings.SetupWizardUtils; import com.android.settings.accessibility.AccessibilitySetupWizardUtils; +import com.android.settings.accessibility.Flags; import com.android.settings.accessibility.PreferredShortcuts; import com.android.settings.core.SubSettingLauncher; import com.android.settings.dashboard.DashboardFragment; @@ -60,7 +68,10 @@ import com.google.android.setupcompat.template.FooterBarMixin; import com.google.android.setupcompat.util.WizardManagerHelper; import com.google.android.setupdesign.GlifPreferenceLayout; +import java.util.ArrayList; import java.util.Collection; +import java.util.List; +import java.util.Map; import java.util.Set; /** @@ -171,6 +182,36 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment { registerSettingsObserver(); } + @Override + public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { + super.onCreatePreferences(savedInstanceState, rootKey); + + Activity activity = getActivity(); + + if (!activity.getIntent().getAction().equals( + Settings.ACTION_ACCESSIBILITY_SHORTCUT_SETTINGS) + || !Flags.editShortcutsInFullScreen()) { + return; + } + + // TODO(b/325664350): Implement shortcut type for "all shortcuts" + List accessibilityTargets = + AccessibilityTargetHelper.getInstalledTargets( + activity.getBaseContext(), AccessibilityManager.ACCESSIBILITY_SHORTCUT_KEY); + + Pair titles = getTitlesFromAccessibilityTargetList( + mShortcutTargets, + accessibilityTargets, + activity.getResources() + ); + + activity.setTitle(titles.first); + + String categoryKey = activity.getResources().getString( + R.string.accessibility_shortcut_description_pref); + findPreference(categoryKey).setTitle(titles.second); + } + @NonNull @Override public RecyclerView onCreateRecyclerView( @@ -275,7 +316,6 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment { } mShortcutTargets = Set.of(targets); - // TODO(318748373): use 'targets' to populate title when no title is given } @Override @@ -356,4 +396,52 @@ public class EditShortcutsPreferenceFragment extends DashboardFragment { // A11y Nav Button refreshPreferenceController(NavButtonShortcutOptionController.class); } + + /** + * Generates a title & subtitle pair describing the features whose shortcuts are being edited. + * + * @param shortcutTargets string list of component names corresponding to + * the relevant shortcut targets. + * @param accessibilityTargets list of accessibility targets + * to try and find corresponding labels in. + * @return pair of strings to be used as page title and subtitle. + * If there is only one shortcut label, It is displayed in the title and the subtitle is null. + * Otherwise, the title is a generic prompt and the subtitle lists all shortcut labels. + */ + @VisibleForTesting + static Pair getTitlesFromAccessibilityTargetList( + Set shortcutTargets, + List accessibilityTargets, + Resources resources) { + ArrayList featureLabels = new ArrayList<>(); + + Map accessibilityTargetLabels = new ArrayMap<>(); + accessibilityTargets.forEach((target) -> accessibilityTargetLabels.put( + target.getId(), target.getLabel())); + + for (String target: shortcutTargets) { + if (accessibilityTargetLabels.containsKey(target)) { + featureLabels.add(accessibilityTargetLabels.get(target)); + } else { + throw new IllegalStateException("Shortcut target does not have a label: " + target); + } + } + + if (featureLabels.size() == 1) { + return new Pair<>( + resources.getString( + R.string.accessibility_shortcut_title, featureLabels.get(0)), + null + ); + } else if (featureLabels.size() == 0) { + throw new IllegalStateException("Found no labels for any shortcut targets."); + } else { + return new Pair<>( + resources.getString(R.string.accessibility_shortcut_edit_screen_title), + resources.getString( + R.string.accessibility_shortcut_edit_screen_prompt, + ListFormatter.getInstance().format(featureLabels)) + ); + } + } } diff --git a/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java index 13f0b24236e..b2ddb6c3cd5 100644 --- a/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/shortcuts/EditShortcutsPreferenceFragmentTest.java @@ -28,6 +28,9 @@ import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_P import static com.google.android.setupcompat.util.WizardManagerHelper.EXTRA_IS_SETUP_FLOW; import static com.google.common.truth.Truth.assertThat; +import static org.junit.Assert.assertThrows; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; import static org.robolectric.Shadows.shadowOf; import android.content.ComponentName; @@ -35,7 +38,9 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.platform.test.flag.junit.SetFlagsRule; import android.provider.Settings; +import android.util.Pair; import android.view.accessibility.AccessibilityManager; import androidx.fragment.app.FragmentActivity; @@ -46,6 +51,7 @@ import androidx.preference.TwoStatePreference; import androidx.test.core.app.ApplicationProvider; import com.android.internal.accessibility.common.ShortcutConstants; +import com.android.internal.accessibility.dialog.AccessibilityTarget; import com.android.internal.accessibility.util.ShortcutUtils; import com.android.settings.R; import com.android.settings.SettingsActivity; @@ -60,6 +66,7 @@ import com.google.android.setupcompat.util.WizardManagerHelper; import org.junit.After; import org.junit.Before; +import org.junit.Rule; import org.junit.Test; import org.junit.runner.RunWith; import org.robolectric.Robolectric; @@ -89,6 +96,9 @@ public class EditShortcutsPreferenceFragmentTest { private static final String TARGET = MAGNIFICATION_CONTROLLER_NAME; private static final Set TARGETS = Set.of(TARGET); + @Rule + public final SetFlagsRule mSetFlagsRule = new SetFlagsRule(); + private final Context mContext = ApplicationProvider.getApplicationContext(); private FragmentActivity mActivity; private FragmentScenario mFragmentScenario; @@ -414,6 +424,60 @@ public class EditShortcutsPreferenceFragmentTest { } + @Test + public void findTitles_withSingleTarget_hasNullSubtitle() { + final String fake_label = "FAKE"; + List accessibilityTargets = List.of( + generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label)); + + Pair titles = EditShortcutsPreferenceFragment + .getTitlesFromAccessibilityTargetList( + Set.of(TARGET_FAKE_COMPONENT.flattenToString()), + accessibilityTargets, mActivity.getResources() + ); + + assertThat(titles.first).isNotNull(); + assertThat(titles.first).contains(fake_label); + assertThat(titles.second).isNull(); + } + + @Test + public void findTitles_withMoreTargets_hasSubtitle() { + final String fake_label = "FAKE"; + final String magnification_label = "MAGNIFICATION"; + List accessibilityTargets = List.of( + generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label), + generateAccessibilityTargetMock(MAGNIFICATION_COMPONENT_NAME, magnification_label)); + + Pair titles = EditShortcutsPreferenceFragment + .getTitlesFromAccessibilityTargetList( + Set.of(TARGET_FAKE_COMPONENT.flattenToString(), + MAGNIFICATION_COMPONENT_NAME.flattenToString()), + accessibilityTargets, mActivity.getResources() + ); + + assertThat(titles.first).isNotNull(); + assertThat(titles.second).isNotNull(); + assertThat(titles.second).contains(fake_label); + assertThat(titles.second).contains(magnification_label); + } + + @Test + public void findTitles_targetMissing_labelNotInTitles() { + final String fake_label = "FAKE"; + List accessibilityTargets = List.of( + generateAccessibilityTargetMock(TARGET_FAKE_COMPONENT, fake_label)); + + assertThrows(IllegalStateException.class, + () -> EditShortcutsPreferenceFragment + .getTitlesFromAccessibilityTargetList( + Set.of(MAGNIFICATION_COMPONENT_NAME.flattenToString()), + accessibilityTargets, mActivity.getResources() + )); + } + + + private void assertLaunchSubSettingWithCurrentTargetComponents( String componentName, boolean isInSuw) { Intent intent = shadowOf(mActivity.getApplication()).getNextStartedActivity(); @@ -480,4 +544,12 @@ public class EditShortcutsPreferenceFragmentTest { intent.putExtra(EXTRA_IS_DEFERRED_SETUP, isInSuw); return intent; } + + private AccessibilityTarget generateAccessibilityTargetMock( + ComponentName componentName, String label) { + AccessibilityTarget target = mock(AccessibilityTarget.class); + when(target.getComponentName()).thenReturn(componentName); + when(target.getLabel()).thenReturn(label); + return target; + } }