diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig index 3ed618bd6e0..3092b8fe72b 100644 --- a/aconfig/accessibility/accessibility_flags.aconfig +++ b/aconfig/accessibility/accessibility_flags.aconfig @@ -20,6 +20,16 @@ flag { } } +flag { + name: "check_prebundled_is_preinstalled" + namespace: "accessibility" + description: "Checks that all 'prebundled' components, used for grouping, are also preinstalled" + bug: "353888087" + metadata { + purpose: PURPOSE_BUGFIX + } +} + flag { name: "edit_shortcuts_in_full_screen" namespace: "accessibility" diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java index 23f8ec7fab1..d01806aa3a8 100644 --- a/src/com/android/settings/accessibility/AccessibilitySettings.java +++ b/src/com/android/settings/accessibility/AccessibilitySettings.java @@ -75,7 +75,8 @@ public class AccessibilitySettings extends DashboardFragment implements private static final String CATEGORY_AUDIO = "audio_category"; private static final String CATEGORY_SPEECH = "speech_category"; private static final String CATEGORY_DISPLAY = "display_category"; - private static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category"; + @VisibleForTesting + static final String CATEGORY_DOWNLOADED_SERVICES = "user_installed_services_category"; private static final String CATEGORY_KEYBOARD_OPTIONS = "physical_keyboard_options_category"; @VisibleForTesting static final String CATEGORY_INTERACTION_CONTROL = "interaction_control_category"; @@ -380,6 +381,7 @@ public class AccessibilitySettings extends DashboardFragment implements } protected void updateServicePreferences() { + final AccessibilityManager a11yManager = AccessibilityManager.getInstance(getPrefContext()); // Since services category is auto generated we have to do a pass // to generate it since services can come and go and then based on // the global accessibility state to decided whether it is enabled. @@ -410,8 +412,18 @@ public class AccessibilitySettings extends DashboardFragment implements AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL)); - final List preferenceList = getInstalledAccessibilityList( - getPrefContext()); + final List installedShortcutList = + a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(), + UserHandle.myUserId()); + final List modifiableInstalledServiceList = + new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList()); + final List preferenceList = getInstalledAccessibilityPreferences( + getPrefContext(), installedShortcutList, modifiableInstalledServiceList); + + if (Flags.checkPrebundledIsPreinstalled()) { + removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap, + installedShortcutList, modifiableInstalledServiceList); + } final PreferenceCategory downloadedServicesCategory = mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES); @@ -456,13 +468,21 @@ public class AccessibilitySettings extends DashboardFragment implements updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS); } - private List getInstalledAccessibilityList(Context context) { - final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context); + /** + * Gets a list of {@link RestrictedPreference}s for the provided a11y shortcuts and services. + * + *

{@code modifiableInstalledServiceList} may be modified to remove any entries with + * matching package name and label as an entry in {@code installedShortcutList}. + * + * @param installedShortcutList A list of installed {@link AccessibilityShortcutInfo}s. + * @param modifiableInstalledServiceList A modifiable list of installed + * {@link AccessibilityServiceInfo}s. + */ + private List getInstalledAccessibilityPreferences(Context context, + List installedShortcutList, + List modifiableInstalledServiceList) { final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context); - final List installedShortcutList = - a11yManager.getInstalledAccessibilityShortcutListAsUser(context, - UserHandle.myUserId()); final List activityList = preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList); final Set> packageLabelPairs = @@ -471,16 +491,14 @@ public class AccessibilitySettings extends DashboardFragment implements a11yActivityPref.getPackageName(), a11yActivityPref.getLabel()) ).collect(Collectors.toSet()); - // Remove duplicate item here, new a ArrayList to copy unmodifiable list result - // (getInstalledAccessibilityServiceList). - final List installedServiceList = new ArrayList<>( - a11yManager.getInstalledAccessibilityServiceList()); + // Remove duplicate A11yServices that are already shown as A11yActivities. if (!packageLabelPairs.isEmpty()) { - installedServiceList.removeIf( + modifiableInstalledServiceList.removeIf( target -> containsPackageAndLabelInList(packageLabelPairs, target)); } final List serviceList = - preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList); + preferenceHelper.createAccessibilityServicePreferenceList( + modifiableInstalledServiceList); final List preferenceList = new ArrayList<>(); preferenceList.addAll(activityList); @@ -489,6 +507,22 @@ public class AccessibilitySettings extends DashboardFragment implements return preferenceList; } + private static void removeNonPreinstalledComponents( + Map componentToCategory, + List shortcutInfos, + List serviceInfos) { + for (AccessibilityShortcutInfo info : shortcutInfos) { + if (!info.getActivityInfo().applicationInfo.isSystemApp()) { + componentToCategory.remove(info.getComponentName()); + } + } + for (AccessibilityServiceInfo info : serviceInfos) { + if (!info.getResolveInfo().serviceInfo.applicationInfo.isSystemApp()) { + componentToCategory.remove(info.getComponentName()); + } + } + } + private boolean containsPackageAndLabelInList( Set> packageLabelPairs, AccessibilityServiceInfo targetServiceInfo) { diff --git a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java index 1463cd0b7f9..cb2429c558f 100644 --- a/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java +++ b/tests/robotests/src/com/android/settings/accessibility/AccessibilitySettingsTest.java @@ -26,11 +26,9 @@ import static org.robolectric.Shadows.shadowOf; import static java.util.Collections.singletonList; import android.accessibilityservice.AccessibilityServiceInfo; -import android.accessibilityservice.AccessibilityShortcutInfo; import android.content.ComponentName; import android.content.Context; import android.content.Intent; -import android.content.pm.ActivityInfo; import android.content.pm.ApplicationInfo; import android.content.pm.ResolveInfo; import android.content.pm.ServiceInfo; @@ -110,9 +108,7 @@ public class AccessibilitySettingsTest { private final Context mContext = ApplicationProvider.getApplicationContext(); @Spy private final AccessibilityServiceInfo mServiceInfo = getMockAccessibilityServiceInfo( - PACKAGE_NAME, CLASS_NAME); - @Mock - private AccessibilityShortcutInfo mShortcutInfo; + new ComponentName(PACKAGE_NAME, CLASS_NAME)); private ShadowAccessibilityManager mShadowAccessibilityManager; @Mock private LocalBluetoothManager mLocalBluetoothManager; @@ -125,7 +121,6 @@ public class AccessibilitySettingsTest { mShadowAccessibilityManager.setInstalledAccessibilityServiceList(new ArrayList<>()); mContext.setTheme(androidx.appcompat.R.style.Theme_AppCompat); ShadowBluetoothUtils.sLocalBluetoothManager = mLocalBluetoothManager; - setMockAccessibilityShortcutInfo(mShortcutInfo); Intent intent = new Intent(); intent.putExtra(SettingsActivity.EXTRA_SHOW_FRAGMENT, @@ -398,14 +393,25 @@ public class AccessibilitySettingsTest { public void testAccessibilityMenuInSystem_IncludedInInteractionControl() { mShadowAccessibilityManager.setInstalledAccessibilityServiceList( List.of(getMockAccessibilityServiceInfo( - AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM))); + AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, + /*isSystemApp=*/true))); setupFragment(); - final RestrictedPreference pref = mFragment.getPreferenceScreen().findPreference( - AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM.flattenToString()); - final String prefCategory = mFragment.mServicePreferenceToPreferenceCategoryMap.get( - pref).getKey(); - assertThat(prefCategory).isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL); + assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM)) + .isEqualTo(AccessibilitySettings.CATEGORY_INTERACTION_CONTROL); + } + + @Test + @EnableFlags(com.android.settings.accessibility.Flags.FLAG_CHECK_PREBUNDLED_IS_PREINSTALLED) + public void testNonPreinstalledApp_IncludedInDownloadedCategory() { + mShadowAccessibilityManager.setInstalledAccessibilityServiceList( + List.of(getMockAccessibilityServiceInfo( + AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM, + /*isSystemApp=*/false))); + setupFragment(); + + assertThat(getPreferenceCategory(AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM)) + .isEqualTo(AccessibilitySettings.CATEGORY_DOWNLOADED_SERVICES); } @Test @@ -418,13 +424,20 @@ public class AccessibilitySettingsTest { assertThat(pref).isNull(); } - private AccessibilityServiceInfo getMockAccessibilityServiceInfo(String packageName, - String className) { - return getMockAccessibilityServiceInfo(new ComponentName(packageName, className)); + private String getPreferenceCategory(ComponentName componentName) { + return mFragment.mServicePreferenceToPreferenceCategoryMap.get( + mFragment.getPreferenceScreen().findPreference( + componentName.flattenToString())).getKey(); } private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName) { - final ApplicationInfo applicationInfo = new ApplicationInfo(); + return getMockAccessibilityServiceInfo(componentName, true); + } + + private AccessibilityServiceInfo getMockAccessibilityServiceInfo(ComponentName componentName, + boolean isSystemApp) { + final ApplicationInfo applicationInfo = Mockito.mock(ApplicationInfo.class); + when(applicationInfo.isSystemApp()).thenReturn(isSystemApp); final ServiceInfo serviceInfo = new ServiceInfo(); applicationInfo.packageName = componentName.getPackageName(); serviceInfo.packageName = componentName.getPackageName(); @@ -445,16 +458,6 @@ public class AccessibilitySettingsTest { return null; } - private void setMockAccessibilityShortcutInfo(AccessibilityShortcutInfo mockInfo) { - final ActivityInfo activityInfo = Mockito.mock(ActivityInfo.class); - activityInfo.applicationInfo = new ApplicationInfo(); - when(mockInfo.getActivityInfo()).thenReturn(activityInfo); - when(activityInfo.loadLabel(any())).thenReturn(DEFAULT_LABEL); - when(mockInfo.loadSummary(any())).thenReturn(DEFAULT_SUMMARY); - when(mockInfo.loadDescription(any())).thenReturn(DEFAULT_DESCRIPTION); - when(mockInfo.getComponentName()).thenReturn(COMPONENT_NAME); - } - private void setInvisibleToggleFragmentType(AccessibilityServiceInfo info) { info.getResolveInfo().serviceInfo.applicationInfo.targetSdkVersion = Build.VERSION_CODES.R; info.flags |= AccessibilityServiceInfo.FLAG_REQUEST_ACCESSIBILITY_BUTTON;