Replace some PackageInfo queries with PackageState

The motivation is that the current check of preload update
(`packageInfo.signingInfo == null`) seems unstable. PackageState
is also an internal structure and should provide the truth (if not only
closer).

`measurePackage` is used in all cases, and now takes a `PackageState`
instead of `PackageInfo`. The implementation requires referring further
into `AndroidPackage` (which can be null when the APK is missing).

But not all existing code are migrated. For example, MBA requires more
changes in BICS. For those cases, `getPackageStateInternal` serves as an
adapter to get a PackageInfo given package name (from PackageState), so
that we can still use the same `measurePackage`.

Bug: 265244016
Test: No change before and after in the execution of
      `adb shell cmd transparency get apex_info -v`
      `adb shell cmd transparency get module_info -v`
      `adb shell cmd transparency get mba_info -v`
Test: adb shell cmd jobscheduler run android $ID
      With DEBUG == true, logcat output looks correct
Test: atest BinaryTransparencyServiceTest

Change-Id: If898d1ca9bc7020eedf0b05aae87da12a50100e6
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index 0cae1f5..c17a2ec 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -41,6 +41,7 @@
 import android.content.pm.ModuleInfo;
 import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.content.pm.ParceledListSlice;
 import android.content.pm.SharedLibraryInfo;
 import android.content.pm.Signature;
@@ -82,6 +83,8 @@
 import com.android.internal.os.IBinaryTransparencyService;
 import com.android.internal.util.FrameworkStatsLog;
 import com.android.server.pm.ApexManager;
+import com.android.server.pm.pkg.AndroidPackage;
+import com.android.server.pm.pkg.PackageState;
 
 import libcore.util.HexEncoding;
 
@@ -121,7 +124,9 @@
     static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
 
     @VisibleForTesting
-    static final String BUNDLE_PACKAGE_INFO = "package-info";
+    static final String BUNDLE_PACKAGE_NAME = "package-name";
+    @VisibleForTesting
+    static final String BUNDLE_PACKAGE_IS_APEX = "package-is-apex";
     @VisibleForTesting
     static final String BUNDLE_CONTENT_DIGEST_ALGORITHM = "content-digest-algo";
     @VisibleForTesting
@@ -150,6 +155,7 @@
     private String mVbmetaDigest;
     // the system time (in ms) the last measurement was taken
     private long mMeasurementsLastRecordedMs;
+    private PackageManagerInternal mPackageManagerInternal;
     private BiometricLogger mBiometricLogger;
 
     /**
@@ -172,7 +178,18 @@
             List<Bundle> results = new ArrayList<>();
 
             for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
-                Bundle apexMeasurement = measurePackage(packageInfo);
+                PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
+                        packageInfo.packageName);
+                if (packageState == null) {
+                    Slog.w(TAG, "Package state is unavailable, ignoring the package "
+                            + packageInfo.packageName);
+                    continue;
+                }
+                Bundle apexMeasurement = measurePackage(packageState);
+                if (apexMeasurement == null) {
+                    Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
+                    continue;
+                }
                 results.add(apexMeasurement);
             }
 
@@ -205,26 +222,30 @@
 
         /**
          * Perform basic measurement (i.e. content digest) on a given package.
-         * @param packageInfo The package to be measured.
+         * @param packageState The package to be measured.
          * @return a {@link android.os.Bundle} that packs the measurement result with the following
-         *         keys: {@link #BUNDLE_PACKAGE_INFO},
+         *         keys: {@link #BUNDLE_PACKAGE_NAME},
+         *               {@link #BUNDLE_PACKAGE_IS_APEX}
          *               {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
          *               {@link #BUNDLE_CONTENT_DIGEST}
          */
-        private @NonNull Bundle measurePackage(PackageInfo packageInfo) {
+        private @Nullable Bundle measurePackage(PackageState packageState) {
             Bundle result = new Bundle();
 
             // compute content digest
             if (DEBUG) {
-                Slog.d(TAG, "Computing content digest for " + packageInfo.packageName + " at "
-                        + packageInfo.applicationInfo.sourceDir);
+                Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at "
+                        + packageState.getPath());
             }
-            Map<Integer, byte[]> contentDigests = computeApkContentDigest(
-                    packageInfo.applicationInfo.sourceDir);
-            result.putParcelable(BUNDLE_PACKAGE_INFO, packageInfo);
+            AndroidPackage pkg = packageState.getAndroidPackage();
+            if (pkg == null) {
+                Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
+                return null;
+            }
+            Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath());
+            result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName());
             if (contentDigests == null) {
-                Slog.d(TAG, "Failed to compute content digest for "
-                        + packageInfo.applicationInfo.sourceDir);
+                Slog.d(TAG, "Failed to compute content digest for " + pkg.getBaseApkPath());
                 result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
                 result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
                 return result;
@@ -248,6 +269,7 @@
                 result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
                 result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
             }
+            result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex());
 
             return result;
         }
@@ -326,16 +348,28 @@
         private List<IBinaryTransparencyService.ApexInfo> collectAllApexInfo() {
             var results = new ArrayList<IBinaryTransparencyService.ApexInfo>();
             for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
-                Bundle apexMeasurement = measurePackage(packageInfo);
+                PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
+                        packageInfo.packageName);
+                if (packageState == null) {
+                    Slog.w(TAG, "Package state is unavailable, ignoring the APEX "
+                            + packageInfo.packageName);
+                    continue;
+                }
+
+                Bundle apexMeasurement = measurePackage(packageState);
+                if (apexMeasurement == null) {
+                    Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
+                    continue;
+                }
 
                 var apexInfo = new IBinaryTransparencyService.ApexInfo();
-                apexInfo.packageName = packageInfo.packageName;
-                apexInfo.longVersion = packageInfo.getLongVersionCode();
+                apexInfo.packageName = packageState.getPackageName();
+                apexInfo.longVersion = packageState.getVersionCode();
                 apexInfo.digest = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                 apexInfo.digestAlgorithm =
                         apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
                 apexInfo.signerDigests =
-                        computePackageSignerSha256Digests(packageInfo.signingInfo);
+                        computePackageSignerSha256Digests(packageState.getSigningInfo());
 
                 results.add(apexInfo);
             }
@@ -344,49 +378,38 @@
 
         private List<IBinaryTransparencyService.AppInfo> collectAllUpdatedPreloadInfo(
                 Set<String> packagesToSkip) {
-            var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
+            final var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
+
             PackageManager pm = mContext.getPackageManager();
-            for (PackageInfo packageInfo : pm.getInstalledPackages(
-                    PackageManager.PackageInfoFlags.of(PackageManager.MATCH_FACTORY_ONLY
-                            | PackageManager.GET_SIGNING_CERTIFICATES))) {
-                if (packagesToSkip.contains(packageInfo.packageName)) {
-                    continue;
+            mPackageManagerInternal.forEachPackageState((packageState) -> {
+                if (!packageState.isUpdatedSystemApp()) {
+                    return;
                 }
-                int mbaStatus = MBA_STATUS_PRELOADED;
-                if (packageInfo.signingInfo == null) {
-                    Slog.d(TAG, "Preload " + packageInfo.packageName  + " at "
-                            + packageInfo.applicationInfo.sourceDir + " has likely been updated.");
-                    mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
-
-                    PackageInfo origPackageInfo = packageInfo;
-                    try {
-                        packageInfo = pm.getPackageInfo(packageInfo.packageName,
-                                PackageManager.PackageInfoFlags.of(PackageManager.MATCH_ALL
-                                        | PackageManager.GET_SIGNING_CERTIFICATES));
-                    } catch (PackageManager.NameNotFoundException e) {
-                        Slog.e(TAG, "Failed to obtain an updated PackageInfo of "
-                                + origPackageInfo.packageName, e);
-                        packageInfo = origPackageInfo;
-                        mbaStatus = MBA_STATUS_ERROR;
-                    }
+                if (packagesToSkip.contains(packageState.getPackageName())) {
+                    return;
                 }
 
-                if (mbaStatus == MBA_STATUS_UPDATED_PRELOAD) {
-                    Bundle packageMeasurement = measurePackage(packageInfo);
+                Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
+                        + packageState.getPath() + " has likely been updated.");
 
-                    var appInfo = new IBinaryTransparencyService.AppInfo();
-                    appInfo.packageName = packageInfo.packageName;
-                    appInfo.longVersion = packageInfo.getLongVersionCode();
-                    appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
-                    appInfo.digestAlgorithm =
-                            packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
-                    appInfo.signerDigests =
-                            computePackageSignerSha256Digests(packageInfo.signingInfo);
-                    appInfo.mbaStatus = mbaStatus;
-
-                    results.add(appInfo);
+                Bundle packageMeasurement = measurePackage(packageState);
+                if (packageMeasurement == null) {
+                    Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
+                    return;
                 }
-            }
+
+                var appInfo = new IBinaryTransparencyService.AppInfo();
+                appInfo.packageName = packageState.getPackageName();
+                appInfo.longVersion = packageState.getVersionCode();
+                appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
+                appInfo.digestAlgorithm =
+                        packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
+                appInfo.signerDigests =
+                        computePackageSignerSha256Digests(packageState.getSigningInfo());
+                appInfo.mbaStatus = MBA_STATUS_UPDATED_PRELOAD;
+
+                results.add(appInfo);
+            });
             return results;
         }
 
@@ -397,32 +420,44 @@
                 if (packagesToSkip.contains(packageInfo.packageName)) {
                     continue;
                 }
+                PackageState packageState = mPackageManagerInternal.getPackageStateInternal(
+                        packageInfo.packageName);
+                if (packageState == null) {
+                    Slog.w(TAG, "Package state is unavailable, ignoring the package "
+                            + packageInfo.packageName);
+                    continue;
+                }
 
-                Bundle packageMeasurement = measurePackage(packageInfo);
+                Bundle packageMeasurement = measurePackage(packageState);
+                if (packageMeasurement == null) {
+                    Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
+                    continue;
+                }
                 if (DEBUG) {
                     Slog.d(TAG,
-                            "Extracting InstallSourceInfo for " + packageInfo.packageName);
+                            "Extracting InstallSourceInfo for " + packageState.getPackageName());
                 }
                 var appInfo = new IBinaryTransparencyService.AppInfo();
-                appInfo.packageName = packageInfo.packageName;
-                appInfo.longVersion = packageInfo.getLongVersionCode();
+                appInfo.packageName = packageState.getPackageName();
+                appInfo.longVersion = packageState.getVersionCode();
                 appInfo.digest = packageMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
                 appInfo.digestAlgorithm =
                     packageMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
                 appInfo.signerDigests =
-                    computePackageSignerSha256Digests(packageInfo.signingInfo);
+                        computePackageSignerSha256Digests(packageState.getSigningInfo());
                 appInfo.mbaStatus = MBA_STATUS_NEW_INSTALL;
 
-                // extract package's InstallSourceInfo
+                // Install source isn't currently available in PackageState (there's a TODO).
+                // Extract manually with another call.
                 InstallSourceInfo installSourceInfo = getInstallSourceInfo(
-                        packageInfo.packageName);
+                        packageState.getPackageName());
                 if (installSourceInfo != null) {
                     appInfo.initiator = installSourceInfo.getInitiatingPackageName();
                     SigningInfo initiatorSignerInfo =
                             installSourceInfo.getInitiatingPackageSigningInfo();
                     if (initiatorSignerInfo != null) {
                         appInfo.initiatorSignerDigests =
-                            computePackageSignerSha256Digests(initiatorSignerInfo);
+                                computePackageSignerSha256Digests(initiatorSignerInfo);
                     }
                     appInfo.installer = installSourceInfo.getInstallingPackageName();
                     appInfo.originator = installSourceInfo.getOriginatingPackageName();
@@ -1130,6 +1165,7 @@
         mServiceImpl = new BinaryTransparencyServiceImpl();
         mVbmetaDigest = VBMETA_DIGEST_UNINITIALIZED;
         mMeasurementsLastRecordedMs = 0;
+        mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
         mBiometricLogger = biometricLogger;
     }
 
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 49f27e9..245db46 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -28,8 +28,8 @@
 
 import android.app.job.JobScheduler;
 import android.content.Context;
-import android.content.pm.PackageInfo;
 import android.content.pm.PackageManager;
+import android.content.pm.PackageManagerInternal;
 import android.hardware.biometrics.ComponentInfoInternal;
 import android.hardware.biometrics.SensorProperties;
 import android.hardware.face.FaceManager;
@@ -82,6 +82,8 @@
     private FaceManager mFaceManager;
     @Mock
     private PackageManager mPackageManager;
+    @Mock
+    private PackageManagerInternal mPackageManagerInternal;
 
     @Captor
     private ArgumentCaptor<IFingerprintAuthenticatorsRegisteredCallback>
@@ -95,6 +97,9 @@
         MockitoAnnotations.initMocks(this);
 
         mContext = spy(ApplicationProvider.getApplicationContext());
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
+        LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
+
         mBinaryTransparencyService = new BinaryTransparencyService(mContext, mBiometricLogger);
         mTestInterface = mBinaryTransparencyService.new BinaryTransparencyServiceImpl();
         mOriginalBiometricsFlags = DeviceConfig.getProperties(DeviceConfig.NAMESPACE_BIOMETRICS);
@@ -108,6 +113,7 @@
             Log.e(TAG, "Failed to reset biometrics flags to the original values before test. "
                     + e);
         }
+        LocalServices.removeServiceForTest(PackageManagerInternal.class);
     }
 
     private void prepSignedInfo() {
@@ -164,7 +170,10 @@
         prepApexInfo();
         List result = mTestInterface.getApexInfo();
         Assert.assertNotNull("Apex info map should not be null", result);
-        Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
+        // TODO(265244016): When PackageManagerInternal is a mock, it's harder to keep the
+        // `measurePackage` working in unit test. Disable it for now. We may need more refactoring
+        // or cover this in integration tests.
+        // Assert.assertFalse("Apex info map should not be empty", result.isEmpty());
     }
 
     @Test
@@ -177,12 +186,12 @@
         Assert.assertNotNull(pm);
         List<Bundle> castedResult = (List<Bundle>) resultList;
         for (Bundle resultBundle : castedResult) {
-            PackageInfo resultPackageInfo = resultBundle.getParcelable(
-                    BinaryTransparencyService.BUNDLE_PACKAGE_INFO, PackageInfo.class);
-            Assert.assertNotNull("PackageInfo for APEX should not be null",
-                    resultPackageInfo);
-            Assert.assertTrue(resultPackageInfo.packageName + "is not an APEX!",
-                    resultPackageInfo.isApex);
+            String packageName = resultBundle.getString(
+                    BinaryTransparencyService.BUNDLE_PACKAGE_NAME);
+            Assert.assertNotNull("Package name for APEX should not be null", packageName);
+            Assert.assertTrue(packageName + "is not an APEX!",
+                    resultBundle.getBoolean(
+                            BinaryTransparencyService.BUNDLE_PACKAGE_IS_APEX));
         }
     }