[pm] fix SCAN_AS_STOPPED_SYSTEM_APP for restoreDisabledSystemPackage

Use ParsedPackage to directly check if the package has a launcher entry
or not. This way, we can unify the system-apps-stopped code path for
1) installing new system apps after an OTA, and 2) restoring disabled system
apps after uninstalling the update.

BUG: 286459841
Test: manual with a system app that doesn't have a launcher entry
Test: also manually checked that a system app with a launcher entry will
be stopped by default

(cherry picked from commit c3415b9f2ff755cde0403eb121e99de924007edd)
(cherry picked from https://googleplex-android-review.googlesource.com/q/commit:90451b716d8b42bed7454e9a3d891f7a6c10d192)
Merged-In: Ic51a9ceb7db6d887cebc390bb2878db8810cddbf
Change-Id: Ic51a9ceb7db6d887cebc390bb2878db8810cddbf
diff --git a/services/core/java/com/android/server/pm/InstallPackageHelper.java b/services/core/java/com/android/server/pm/InstallPackageHelper.java
index 50f1673..2b55a61 100644
--- a/services/core/java/com/android/server/pm/InstallPackageHelper.java
+++ b/services/core/java/com/android/server/pm/InstallPackageHelper.java
@@ -110,6 +110,7 @@
 import android.content.ContentResolver;
 import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
 import android.content.IntentSender;
 import android.content.pm.ApplicationInfo;
 import android.content.pm.DataLoaderType;
@@ -119,7 +120,6 @@
 import android.content.pm.PackageManager;
 import android.content.pm.PermissionGroupInfo;
 import android.content.pm.PermissionInfo;
-import android.content.pm.ResolveInfo;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
 import android.content.pm.SigningDetails;
@@ -184,7 +184,9 @@
 import com.android.server.pm.pkg.PackageStateInternal;
 import com.android.server.pm.pkg.SharedLibraryWrapper;
 import com.android.server.pm.pkg.component.ComponentMutateUtils;
+import com.android.server.pm.pkg.component.ParsedActivity;
 import com.android.server.pm.pkg.component.ParsedInstrumentation;
+import com.android.server.pm.pkg.component.ParsedIntentInfo;
 import com.android.server.pm.pkg.component.ParsedPermission;
 import com.android.server.pm.pkg.component.ParsedPermissionGroup;
 import com.android.server.pm.pkg.parsing.ParsingPackageUtils;
@@ -207,6 +209,7 @@
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
+import java.util.HashSet;
 import java.util.List;
 import java.util.Map;
 import java.util.Set;
@@ -3925,23 +3928,6 @@
             }
         }
 
-        // If this is a system app we hadn't seen before, and this is a first boot or OTA,
-        // we need to unstop it if it doesn't have a launcher entry.
-        if (mPm.mShouldStopSystemPackagesByDefault && scanResult.mRequest.mPkgSetting == null
-                && ((scanFlags & SCAN_FIRST_BOOT_OR_UPGRADE) != 0)
-                && ((scanFlags & SCAN_AS_SYSTEM) != 0)) {
-            final Intent launcherIntent = new Intent(Intent.ACTION_MAIN);
-            launcherIntent.addCategory(Intent.CATEGORY_LAUNCHER);
-            launcherIntent.setPackage(parsedPackage.getPackageName());
-            final List<ResolveInfo> launcherActivities =
-                    mPm.snapshotComputer().queryIntentActivitiesInternal(launcherIntent, null,
-                            PackageManager.MATCH_DIRECT_BOOT_AWARE
-                            | PackageManager.MATCH_DIRECT_BOOT_UNAWARE, 0);
-            if (launcherActivities.isEmpty()) {
-                scanResult.mPkgSetting.setStopped(false, 0);
-            }
-        }
-
         if (mIncrementalManager != null && isIncrementalPath(parsedPackage.getPath())) {
             if (scanResult.mPkgSetting != null && scanResult.mPkgSetting.isLoading()) {
                 // Continue monitoring loading progress of active incremental packages
@@ -4314,6 +4300,7 @@
         //   - It's an APEX or overlay package since stopped state does not affect them.
         //   - It is enumerated with a <initial-package-state> tag having the stopped attribute
         //     set to false
+        //   - It doesn't have a launcher entry which means the user doesn't have a way to unstop it
         final boolean isApexPkg = (scanFlags & SCAN_AS_APEX) != 0;
         if (mPm.mShouldStopSystemPackagesByDefault
                 && scanSystemPartition
@@ -4322,8 +4309,9 @@
                 && !parsedPackage.isOverlayIsStatic()
         ) {
             String packageName = parsedPackage.getPackageName();
-            if (!mPm.mInitialNonStoppedSystemPackages.contains(packageName)
-                    && !"android".contentEquals(packageName)) {
+            if (!"android".contentEquals(packageName)
+                    && !mPm.mInitialNonStoppedSystemPackages.contains(packageName)
+                    && hasLauncherEntry(parsedPackage)) {
                 scanFlags |= SCAN_AS_STOPPED_SYSTEM_APP;
             }
         }
@@ -4333,6 +4321,22 @@
         return new Pair<>(scanResult, shouldHideSystemApp);
     }
 
+    private static boolean hasLauncherEntry(ParsedPackage parsedPackage) {
+        final HashSet<String> categories = new HashSet<>();
+        categories.add(Intent.CATEGORY_LAUNCHER);
+        final List<ParsedActivity> activities = parsedPackage.getActivities();
+        for (int indexActivity = 0; indexActivity < activities.size(); indexActivity++) {
+            final List<ParsedIntentInfo> intents = activities.get(indexActivity).getIntents();
+            for (int indexIntent = 0; indexIntent < intents.size(); indexIntent++) {
+                final IntentFilter intentFilter = intents.get(indexIntent).getIntentFilter();
+                if (intentFilter != null && intentFilter.matchCategories(categories) == null) {
+                    return true;
+                }
+            }
+        }
+        return false;
+    }
+
     /**
      * Returns if forced apk verification can be skipped for the whole package, including splits.
      */