Merge "Checks that prebundled categories contain only preinstalled contents." into main
diff --git a/aconfig/accessibility/accessibility_flags.aconfig b/aconfig/accessibility/accessibility_flags.aconfig
index 3ed618b..3092b8f 100644
--- a/aconfig/accessibility/accessibility_flags.aconfig
+++ b/aconfig/accessibility/accessibility_flags.aconfig
@@ -21,6 +21,16 @@
 }
 
 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"
   description: "Show the edit shorcuts screen in full screen, since the shortcut options are increasing."
diff --git a/src/com/android/settings/accessibility/AccessibilitySettings.java b/src/com/android/settings/accessibility/AccessibilitySettings.java
index 23f8ec7..d01806a 100644
--- a/src/com/android/settings/accessibility/AccessibilitySettings.java
+++ b/src/com/android/settings/accessibility/AccessibilitySettings.java
@@ -75,7 +75,8 @@
     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 @@
     }
 
     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 @@
                 AccessibilityUtils.ACCESSIBILITY_MENU_IN_SYSTEM,
                 mCategoryToPrefCategoryMap.get(CATEGORY_INTERACTION_CONTROL));
 
-        final List<RestrictedPreference> preferenceList = getInstalledAccessibilityList(
-                getPrefContext());
+        final List<AccessibilityShortcutInfo> installedShortcutList =
+                a11yManager.getInstalledAccessibilityShortcutListAsUser(getPrefContext(),
+                        UserHandle.myUserId());
+        final List<AccessibilityServiceInfo> modifiableInstalledServiceList =
+                new ArrayList<>(a11yManager.getInstalledAccessibilityServiceList());
+        final List<RestrictedPreference> preferenceList = getInstalledAccessibilityPreferences(
+                getPrefContext(), installedShortcutList, modifiableInstalledServiceList);
+
+        if (Flags.checkPrebundledIsPreinstalled()) {
+            removeNonPreinstalledComponents(mPreBundledServiceComponentToCategoryMap,
+                    installedShortcutList, modifiableInstalledServiceList);
+        }
 
         final PreferenceCategory downloadedServicesCategory =
                 mCategoryToPrefCategoryMap.get(CATEGORY_DOWNLOADED_SERVICES);
@@ -456,13 +468,21 @@
         updatePreferenceCategoryVisibility(CATEGORY_KEYBOARD_OPTIONS);
     }
 
-    private List<RestrictedPreference> getInstalledAccessibilityList(Context context) {
-        final AccessibilityManager a11yManager = AccessibilityManager.getInstance(context);
+    /**
+     * Gets a list of {@link RestrictedPreference}s for the provided a11y shortcuts and services.
+     *
+     * <p>{@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<RestrictedPreference> getInstalledAccessibilityPreferences(Context context,
+            List<AccessibilityShortcutInfo> installedShortcutList,
+            List<AccessibilityServiceInfo> modifiableInstalledServiceList) {
         final RestrictedPreferenceHelper preferenceHelper = new RestrictedPreferenceHelper(context);
 
-        final List<AccessibilityShortcutInfo> installedShortcutList =
-                a11yManager.getInstalledAccessibilityShortcutListAsUser(context,
-                        UserHandle.myUserId());
         final List<AccessibilityActivityPreference> activityList =
                 preferenceHelper.createAccessibilityActivityPreferenceList(installedShortcutList);
         final Set<Pair<String, CharSequence>> packageLabelPairs =
@@ -471,16 +491,14 @@
                                 a11yActivityPref.getPackageName(), a11yActivityPref.getLabel())
                         ).collect(Collectors.toSet());
 
-        // Remove duplicate item here, new a ArrayList to copy unmodifiable list result
-        // (getInstalledAccessibilityServiceList).
-        final List<AccessibilityServiceInfo> 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<RestrictedPreference> serviceList =
-                preferenceHelper.createAccessibilityServicePreferenceList(installedServiceList);
+                preferenceHelper.createAccessibilityServicePreferenceList(
+                        modifiableInstalledServiceList);
 
         final List<RestrictedPreference> preferenceList = new ArrayList<>();
         preferenceList.addAll(activityList);
@@ -489,6 +507,22 @@
         return preferenceList;
     }
 
+    private static void removeNonPreinstalledComponents(
+            Map<ComponentName, PreferenceCategory> componentToCategory,
+            List<AccessibilityShortcutInfo> shortcutInfos,
+            List<AccessibilityServiceInfo> 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<Pair<String, CharSequence>> 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 1463cd0..cb2429c 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 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 @@
     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 @@
         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 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 @@
         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 @@
         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;