summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
author Victor Hsieh <victorhsieh@google.com> 2023-01-31 09:30:03 -0800
committer Victor Hsieh <victorhsieh@google.com> 2023-02-01 10:34:31 -0800
commit38b94091a9b23c9aced530315bee50287a9a44c3 (patch)
treee742ba7559516e25a429947f42909c1b3e629814
parent1f279326efc589a7b79db9e25449c912845dd9b8 (diff)
Support measuring split
measurePackage is changed into measureApk, which would hint that the method deals with a file instead of a "package" in the package manager's term. collectAppInfo is added to measure an app package, which may include split. Test * Add a test that leverages debug.transparency.bg-install-apps of BICs. * Copy BaseInstallMultiple from another test in frameworks/base to support split install. Misc * Delete getApexInfo and corresponding unit tests, since it's now coverred through collectAllApexInfo in integration test. This also makes it easy for refactoring without having to keeping the Bundle works as the return type. Bug: 264296226 Test: atest BinaryTransparencyServiceTest BinaryTransparencyHostTest Change-Id: Iaa4118dfa8605acda313dbcc2466ae96a60b4721
-rw-r--r--core/java/android/transparency/BinaryTransparencyManager.java35
-rw-r--r--core/java/com/android/internal/os/IBinaryTransparencyService.aidl3
-rw-r--r--services/core/java/com/android/server/BinaryTransparencyService.java219
-rw-r--r--services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java31
-rw-r--r--tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java140
-rw-r--r--tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java32
-rw-r--r--tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java32
7 files changed, 318 insertions, 174 deletions
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index d77bbcc836ec..c18adfc86d54 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -67,24 +67,6 @@ public class BinaryTransparencyManager {
}
/**
- * Gets binary measurements of all installed APEXs, each packed in a Bundle.
- * @return A List of {@link android.os.Bundle}s with the following keys:
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_PACKAGE_INFO}
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST_ALGORITHM}
- * {@link com.android.server.BinaryTransparencyService#BUNDLE_CONTENT_DIGEST}
- */
- // TODO(b/259422958): Fix static constants referenced here - should be defined here
- @NonNull
- public List getApexInfo() {
- try {
- Slog.d(TAG, "Calling backend's getApexInfo()");
- return mService.getApexInfo();
- } catch (RemoteException e) {
- throw e.rethrowFromSystemServer();
- }
- }
-
- /**
* Collects the APEX information on the device.
*
* @param includeTestOnly Whether to include test only data in the returned ApexInfo.
@@ -116,4 +98,21 @@ public class BinaryTransparencyManager {
throw e.rethrowFromSystemServer();
}
}
+
+ /**
+ * Collects the silent installed MBA information on the device.
+ *
+ * @return A List containing the MBA info of silent installed.
+ * @hide
+ */
+ @NonNull
+ public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
+ Bundle packagesToSkip) {
+ try {
+ Slog.d(TAG, "Calling backend's collectAllSilentInstalledMbaInfo()");
+ return mService.collectAllSilentInstalledMbaInfo(packagesToSkip);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
}
diff --git a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
index e782aa7643a1..c8340acc5d8f 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -28,8 +28,6 @@ import android.os.Bundle;
interface IBinaryTransparencyService {
String getSignedImageInfo();
- List getApexInfo();
-
void recordMeasurementsForAllPackages();
parcelable ApexInfo {
@@ -60,4 +58,5 @@ interface IBinaryTransparencyService {
/** Test only */
List<ApexInfo> collectAllApexInfo(boolean includeTestOnly);
List<AppInfo> collectAllUpdatedPreloadInfo(in Bundle packagesToSkip);
+ List<AppInfo> collectAllSilentInstalledMbaInfo(in Bundle packagesToSkip);
} \ No newline at end of file
diff --git a/services/core/java/com/android/server/BinaryTransparencyService.java b/services/core/java/com/android/server/BinaryTransparencyService.java
index b6a2a0eb3ac3..fc81675682b6 100644
--- a/services/core/java/com/android/server/BinaryTransparencyService.java
+++ b/services/core/java/com/android/server/BinaryTransparencyService.java
@@ -34,6 +34,7 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApexStagedEvent;
import android.content.pm.ApplicationInfo;
+import android.content.pm.Checksum;
import android.content.pm.IBackgroundInstallControlService;
import android.content.pm.IPackageManagerNative;
import android.content.pm.IStagedApexObserver;
@@ -84,6 +85,7 @@ 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.AndroidPackageSplit;
import com.android.server.pm.pkg.PackageState;
import libcore.util.HexEncoding;
@@ -120,15 +122,6 @@ public class BinaryTransparencyService extends SystemService {
static final long RECORD_MEASUREMENTS_COOLDOWN_MS = 24 * 60 * 60 * 1000;
- @VisibleForTesting
- 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
- static final String BUNDLE_CONTENT_DIGEST = "content-digest";
-
static final String APEX_PRELOAD_LOCATION_ERROR = "could-not-be-determined";
// used for indicating any type of error during MBA measurement
@@ -170,29 +163,6 @@ public class BinaryTransparencyService extends SystemService {
return mVbmetaDigest;
}
- @Override
- public List getApexInfo() {
- List<Bundle> results = new ArrayList<>();
-
- for (PackageInfo packageInfo : getCurrentInstalledApexs()) {
- 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);
- }
-
- return results;
- }
-
/**
* A helper function to compute the SHA256 digest of APK package signer.
* @param signingInfo The signingInfo of a package, usually {@link PackageInfo#signingInfo}.
@@ -217,58 +187,102 @@ public class BinaryTransparencyService extends SystemService {
return resultList.toArray(new String[1]);
}
- /**
- * Perform basic measurement (i.e. content digest) on a given package.
+ /*
+ * Perform basic measurement (i.e. content digest) on a given app, including the split APKs.
+ *
* @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_NAME},
- * {@link #BUNDLE_PACKAGE_IS_APEX}
- * {@link #BUNDLE_CONTENT_DIGEST_ALGORITHM}
- * {@link #BUNDLE_CONTENT_DIGEST}
+ * @param mbaStatus Assign this value of MBA status to the returned elements.
+ * @return a @{@code List<IBinaryTransparencyService.AppInfo>}
*/
- private @Nullable Bundle measurePackage(PackageState packageState) {
- Bundle result = new Bundle();
-
+ private @NonNull List<IBinaryTransparencyService.AppInfo> collectAppInfo(
+ PackageState packageState, int mbaStatus) {
// compute content digest
if (DEBUG) {
Slog.d(TAG, "Computing content digest for " + packageState.getPackageName() + " at "
+ packageState.getPath());
}
+
+ var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
+
+ // Same attributes across base and splits.
+ String packageName = packageState.getPackageName();
+ long versionCode = packageState.getVersionCode();
+ String[] signerDigests =
+ computePackageSignerSha256Digests(packageState.getSigningInfo());
+
AndroidPackage pkg = packageState.getAndroidPackage();
- if (pkg == null) {
- Slog.w(TAG, "Skipping the missing APK in " + packageState.getPath());
- return null;
+ for (AndroidPackageSplit split : pkg.getSplits()) {
+ var appInfo = new IBinaryTransparencyService.AppInfo();
+ appInfo.packageName = packageName;
+ appInfo.longVersion = versionCode;
+ appInfo.splitName = split.getName(); // base's split name is null
+ // Signer digests are consistent between splits, guaranteed by Package Manager.
+ appInfo.signerDigests = signerDigests;
+ appInfo.mbaStatus = mbaStatus;
+
+ // Only digest and split name are different between splits.
+ Checksum checksum = measureApk(split.getPath());
+ appInfo.digest = checksum.getValue();
+ appInfo.digestAlgorithm = checksum.getType();
+
+ results.add(appInfo);
}
- Map<Integer, byte[]> contentDigests = computeApkContentDigest(pkg.getBaseApkPath());
- result.putString(BUNDLE_PACKAGE_NAME, pkg.getPackageName());
+
+ // InstallSourceInfo is only available per package name, so store it only on the base
+ // APK. It's not current currently available in PackageState (there's a TODO), to we
+ // need to extract manually with another call.
+ //
+ // Base APK is already the 0-th split from getSplits() and can't be null.
+ AppInfo base = results.get(0);
+ InstallSourceInfo installSourceInfo = getInstallSourceInfo(
+ packageState.getPackageName());
+ if (installSourceInfo != null) {
+ base.initiator = installSourceInfo.getInitiatingPackageName();
+ SigningInfo initiatorSignerInfo =
+ installSourceInfo.getInitiatingPackageSigningInfo();
+ if (initiatorSignerInfo != null) {
+ base.initiatorSignerDigests =
+ computePackageSignerSha256Digests(initiatorSignerInfo);
+ }
+ base.installer = installSourceInfo.getInstallingPackageName();
+ base.originator = installSourceInfo.getOriginatingPackageName();
+ }
+
+ return results;
+ }
+
+ /**
+ * Perform basic measurement (i.e. content digest) on a given APK.
+ *
+ * @param apkPath The APK (or APEX, since it's also an APK) file to be measured.
+ * @return a {@link android.content.pm.Checksum} with preferred digest algorithm type and
+ * the checksum.
+ */
+ private @Nullable Checksum measureApk(@NonNull String apkPath) {
+ // compute content digest
+ Map<Integer, byte[]> contentDigests = computeApkContentDigest(apkPath);
if (contentDigests == null) {
- 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;
+ Slog.d(TAG, "Failed to compute content digest for " + apkPath);
+ return new Checksum(0, new byte[] { -1 });
}
// in this iteration, we'll be supporting only 2 types of digests:
// CHUNKED_SHA256 and CHUNKED_SHA512.
// And only one of them will be available per package.
if (contentDigests.containsKey(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256)) {
- Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256;
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ return new Checksum(
+ Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA256,
+ contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA256));
} else if (contentDigests.containsKey(
ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512)) {
- Integer algorithmId = ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512;
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, algorithmId);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, contentDigests.get(algorithmId));
+ return new Checksum(
+ Checksum.TYPE_PARTIAL_MERKLE_ROOT_1M_SHA512,
+ contentDigests.get(ApkSigningBlockUtils.CONTENT_DIGEST_CHUNKED_SHA512));
} else {
// TODO(b/259423111): considering putting the raw values for the algorithm & digest
// into the bundle to track potential other digest algorithms that may be in use
- result.putInt(BUNDLE_CONTENT_DIGEST_ALGORITHM, 0);
- result.putByteArray(BUNDLE_CONTENT_DIGEST, null);
+ return new Checksum(0, new byte[] { -1 });
}
- result.putBoolean(BUNDLE_PACKAGE_IS_APEX, packageState.isApex());
-
- return result;
}
@@ -330,7 +344,7 @@ public class BinaryTransparencyService extends SystemService {
if (CompatChanges.isChangeEnabled(LOG_MBA_INFO)) {
// lastly measure all newly installed MBAs
List<IBinaryTransparencyService.AppInfo> allMbaInfo =
- collectAllMbaInfo(packagesMeasured);
+ collectAllSilentInstalledMbaInfo(packagesMeasured);
for (IBinaryTransparencyService.AppInfo appInfo : allUpdatedPreloadInfo) {
packagesMeasured.putBoolean(appInfo.packageName, true);
writeAppInfoToLog(appInfo);
@@ -356,18 +370,22 @@ public class BinaryTransparencyService extends SystemService {
continue;
}
- Bundle apexMeasurement = measurePackage(packageState);
- if (apexMeasurement == null) {
- Slog.w(TAG, "Skipping the missing APEX in " + packageState.getPath());
+ AndroidPackage pkg = packageState.getAndroidPackage();
+ if (pkg == null) {
+ Slog.w(TAG, "Skipping the missing APK in " + pkg.getPath());
+ continue;
+ }
+ Checksum apexChecksum = measureApk(pkg.getPath());
+ if (apexChecksum == null) {
+ Slog.w(TAG, "Skipping the missing APEX in " + pkg.getPath());
continue;
}
var apexInfo = new IBinaryTransparencyService.ApexInfo();
apexInfo.packageName = packageState.getPackageName();
apexInfo.longVersion = packageState.getVersionCode();
- apexInfo.digest = apexMeasurement.getByteArray(BUNDLE_CONTENT_DIGEST);
- apexInfo.digestAlgorithm =
- apexMeasurement.getInt(BUNDLE_CONTENT_DIGEST_ALGORITHM);
+ apexInfo.digest = apexChecksum.getValue();
+ apexInfo.digestAlgorithm = apexChecksum.getType();
apexInfo.signerDigests =
computePackageSignerSha256Digests(packageState.getSigningInfo());
@@ -398,28 +416,16 @@ public class BinaryTransparencyService extends SystemService {
Slog.d(TAG, "Preload " + packageState.getPackageName() + " at "
+ packageState.getPath() + " has likely been updated.");
- 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);
+ List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
+ packageState, MBA_STATUS_UPDATED_PRELOAD);
+ results.addAll(resultsForApp);
});
return results;
}
- public List<IBinaryTransparencyService.AppInfo> collectAllMbaInfo(Bundle packagesToSkip) {
+ @Override
+ public List<IBinaryTransparencyService.AppInfo> collectAllSilentInstalledMbaInfo(
+ Bundle packagesToSkip) {
var results = new ArrayList<IBinaryTransparencyService.AppInfo>();
for (PackageInfo packageInfo : getNewlyInstalledMbas()) {
if (packagesToSkip.containsKey(packageInfo.packageName)) {
@@ -433,42 +439,9 @@ public class BinaryTransparencyService extends SystemService {
continue;
}
- 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 " + packageState.getPackageName());
- }
- 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_NEW_INSTALL;
-
- // Install source isn't currently available in PackageState (there's a TODO).
- // Extract manually with another call.
- InstallSourceInfo installSourceInfo = getInstallSourceInfo(
- packageState.getPackageName());
- if (installSourceInfo != null) {
- appInfo.initiator = installSourceInfo.getInitiatingPackageName();
- SigningInfo initiatorSignerInfo =
- installSourceInfo.getInitiatingPackageSigningInfo();
- if (initiatorSignerInfo != null) {
- appInfo.initiatorSignerDigests =
- computePackageSignerSha256Digests(initiatorSignerInfo);
- }
- appInfo.installer = installSourceInfo.getInstallingPackageName();
- appInfo.originator = installSourceInfo.getOriginatingPackageName();
- }
-
- results.add(appInfo);
+ List<IBinaryTransparencyService.AppInfo> resultsForApp = collectAppInfo(
+ packageState, MBA_STATUS_NEW_INSTALL);
+ results.addAll(resultsForApp);
}
return results;
}
diff --git a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
index 245db4654248..ae78dfe624c6 100644
--- a/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
+++ b/services/tests/servicestests/src/com/android/server/BinaryTransparencyServiceTest.java
@@ -40,7 +40,6 @@ import android.hardware.fingerprint.FingerprintManager;
import android.hardware.fingerprint.FingerprintSensorProperties;
import android.hardware.fingerprint.FingerprintSensorPropertiesInternal;
import android.hardware.fingerprint.IFingerprintAuthenticatorsRegisteredCallback;
-import android.os.Bundle;
import android.os.RemoteException;
import android.os.ResultReceiver;
import android.os.SystemProperties;
@@ -166,36 +165,6 @@ public class BinaryTransparencyServiceTest {
}
@Test
- public void getApexInfo_postInitialize_returnsValidEntries() throws RemoteException {
- prepApexInfo();
- List result = mTestInterface.getApexInfo();
- Assert.assertNotNull("Apex info map should not be null", result);
- // 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
- public void getApexInfo_postInitialize_returnsActualApexs()
- throws RemoteException, PackageManager.NameNotFoundException {
- prepApexInfo();
- List resultList = mTestInterface.getApexInfo();
-
- PackageManager pm = mContext.getPackageManager();
- Assert.assertNotNull(pm);
- List<Bundle> castedResult = (List<Bundle>) resultList;
- for (Bundle resultBundle : castedResult) {
- 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));
- }
- }
-
- @Test
public void testCollectBiometricProperties_disablesFeature() {
DeviceConfig.setProperty(DeviceConfig.NAMESPACE_BIOMETRICS,
BinaryTransparencyService.KEY_ENABLE_BIOMETRIC_PROPERTY_VERIFICATION,
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java
new file mode 100644
index 000000000000..3e94f25b5fc7
--- /dev/null
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BaseInstallMultiple.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package android.transparency.test;
+
+import com.android.compatibility.common.tradefed.build.CompatibilityBuildHelper;
+import com.android.tradefed.build.IBuildInfo;
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+
+import junit.framework.TestCase;
+
+import java.io.File;
+import java.io.FileNotFoundException;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Base class for invoking the install-multiple command via ADB. Subclass this for less typing:
+ *
+ * <code> private class InstallMultiple extends BaseInstallMultiple&lt;InstallMultiple&gt; { public
+ * InstallMultiple() { super(getDevice(), null); } } </code>
+ */
+/*package*/ class BaseInstallMultiple<T extends BaseInstallMultiple<?>> {
+
+ private final ITestDevice mDevice;
+ private final IBuildInfo mBuild;
+
+ private final List<String> mArgs = new ArrayList<>();
+ private final Map<File, String> mFileToRemoteMap = new HashMap<>();
+
+ /*package*/ BaseInstallMultiple(ITestDevice device, IBuildInfo buildInfo) {
+ mDevice = device;
+ mBuild = buildInfo;
+ addArg("-g");
+ }
+
+ T addArg(String arg) {
+ mArgs.add(arg);
+ return (T) this;
+ }
+
+ T addFile(String filename) throws FileNotFoundException {
+ return addFile(filename, filename);
+ }
+
+ T addFile(String filename, String remoteName) throws FileNotFoundException {
+ CompatibilityBuildHelper buildHelper = new CompatibilityBuildHelper(mBuild);
+ mFileToRemoteMap.put(buildHelper.getTestFile(filename), remoteName);
+ return (T) this;
+ }
+
+ T inheritFrom(String packageName) {
+ addArg("-r");
+ addArg("-p " + packageName);
+ return (T) this;
+ }
+
+ void run() throws DeviceNotAvailableException {
+ run(true);
+ }
+
+ void runExpectingFailure() throws DeviceNotAvailableException {
+ run(false);
+ }
+
+ private void run(boolean expectingSuccess) throws DeviceNotAvailableException {
+ final ITestDevice device = mDevice;
+
+ // Create an install session
+ final StringBuilder cmd = new StringBuilder();
+ cmd.append("pm install-create");
+ for (String arg : mArgs) {
+ cmd.append(' ').append(arg);
+ }
+
+ String result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+
+ final int start = result.lastIndexOf("[");
+ final int end = result.lastIndexOf("]");
+ int sessionId = -1;
+ try {
+ if (start != -1 && end != -1 && start < end) {
+ sessionId = Integer.parseInt(result.substring(start + 1, end));
+ }
+ } catch (NumberFormatException e) {
+ throw new IllegalStateException("Failed to parse install session: " + result);
+ }
+ if (sessionId == -1) {
+ throw new IllegalStateException("Failed to create install session: " + result);
+ }
+
+ // Push our files into session. Ideally we'd use stdin streaming,
+ // but ddmlib doesn't support it yet.
+ for (final Map.Entry<File, String> entry : mFileToRemoteMap.entrySet()) {
+ final File file = entry.getKey();
+ final String remoteName = entry.getValue();
+ final String remotePath = "/data/local/tmp/" + file.getName();
+ if (!device.pushFile(file, remotePath)) {
+ throw new IllegalStateException("Failed to push " + file);
+ }
+
+ cmd.setLength(0);
+ cmd.append("pm install-write");
+ cmd.append(' ').append(sessionId);
+ cmd.append(' ').append(remoteName);
+ cmd.append(' ').append(remotePath);
+
+ result = device.executeShellCommand(cmd.toString());
+ TestCase.assertTrue(result, result.startsWith("Success"));
+ }
+
+ // Everything staged; let's pull trigger
+ cmd.setLength(0);
+ cmd.append("pm install-commit");
+ cmd.append(' ').append(sessionId);
+
+ result = device.executeShellCommand(cmd.toString());
+ if (expectingSuccess) {
+ TestCase.assertTrue(result, result.contains("Success"));
+ } else {
+ TestCase.assertFalse(result, result.contains("Success"));
+ }
+ }
+}
diff --git a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
index 6fe548fe0ba4..b8e9a1751775 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -18,6 +18,7 @@ package android.transparency.test;
import static com.google.common.truth.Truth.assertThat;
+import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.fail;
@@ -81,6 +82,28 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
}
@Test
+ public void testCollectAllSilentInstalledMbaInfo() throws Exception {
+ try {
+ new InstallMultiple()
+ .addFile("ApkVerityTestApp.apk")
+ .addFile("ApkVerityTestAppSplit.apk")
+ .run();
+ updatePreloadApp();
+ assertNotNull(getDevice().getAppPackageInfo("com.android.apkverity"));
+ assertNotNull(getDevice().getAppPackageInfo("com.android.egg"));
+
+ assertTrue(getDevice().setProperty("debug.transparency.bg-install-apps",
+ "com.android.apkverity,com.android.egg"));
+ runDeviceTest("testCollectAllSilentInstalledMbaInfo");
+ } finally {
+ // No need to wait until job complete, since we can't verifying very meaningfully.
+ cancelPendingJob();
+ uninstallPackage("com.android.apkverity");
+ uninstallPackage("com.android.egg");
+ }
+ }
+
+ @Test
public void testRebootlessApexUpdateTriggersJobScheduling() throws Exception {
try {
installRebootlessApex();
@@ -171,4 +194,13 @@ public final class BinaryTransparencyHostTest extends BaseHostJUnit4Test {
result = getDevice().executeShellV2Command("pm install " + path);
assertTrue(result.getStatus() == CommandStatus.SUCCESS);
}
+
+ private class InstallMultiple extends BaseInstallMultiple<InstallMultiple> {
+ InstallMultiple() {
+ super(getDevice(), getBuild());
+ // Needed since in getMockBackgroundInstalledPackages, getPackageInfo runs as the caller
+ // uid. This also makes it consistent with installPackage's behavior.
+ addArg("--force-queryable");
+ }
+ }
}
diff --git a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
index 176bc28e8d6f..c087a85da2a8 100644
--- a/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
+++ b/tests/BinaryTransparencyHostTest/test-app/src/android/transparency/test/app/BinaryTransparencyTest.java
@@ -36,6 +36,7 @@ import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.HexFormat;
+import java.util.Set;
import java.util.stream.Collectors;
@RunWith(AndroidJUnit4.class)
@@ -111,4 +112,35 @@ public class BinaryTransparencyTest {
assertThat(updatedPreload.mbaStatus).isEqualTo(/* MBA_STATUS_UPDATED_PRELOAD */ 2);
assertThat(updatedPreload.signerDigests).asList().containsNoneOf(null, "");
}
+
+ @Test
+ public void testCollectAllSilentInstalledMbaInfo() {
+ // Action
+ var appInfoList = mBt.collectAllSilentInstalledMbaInfo(new Bundle());
+
+ // Verify
+ assertThat(appInfoList).isNotEmpty(); // because we just installed from the host side
+
+ var expectedAppNames = Set.of("com.android.apkverity", "com.android.egg");
+ var actualAppNames = appInfoList.stream().map((appInfo) -> appInfo.packageName)
+ .collect(Collectors.toList());
+ assertThat(actualAppNames).containsAtLeastElementsIn(expectedAppNames);
+
+ var actualSplitNames = new ArrayList<String>();
+ for (var appInfo : appInfoList) {
+ Log.d(TAG, "Received " + appInfo.packageName + " as a silent install");
+ if (expectedAppNames.contains(appInfo.packageName)) {
+ assertThat(appInfo.longVersion).isGreaterThan(0);
+ assertThat(appInfo.digestAlgorithm).isGreaterThan(0);
+ assertThat(appInfo.digest).isNotEmpty();
+ assertThat(appInfo.mbaStatus).isEqualTo(/* MBA_STATUS_NEW_INSTALL */ 3);
+ assertThat(appInfo.signerDigests).asList().containsNoneOf(null, "");
+
+ if (appInfo.splitName != null) {
+ actualSplitNames.add(appInfo.splitName);
+ }
+ }
+ }
+ assertThat(actualSplitNames).containsExactly("feature_x"); // Name of ApkVerityTestAppSplit
+ }
}