Merge "Support measuring split"
diff --git a/core/java/android/transparency/BinaryTransparencyManager.java b/core/java/android/transparency/BinaryTransparencyManager.java
index d77bbcc..c18adfc 100644
--- a/core/java/android/transparency/BinaryTransparencyManager.java
+++ b/core/java/android/transparency/BinaryTransparencyManager.java
@@ -67,24 +67,6 @@
}
/**
- * 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 @@
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 e782aa7..c8340ac 100644
--- a/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
+++ b/core/java/com/android/internal/os/IBinaryTransparencyService.aidl
@@ -28,8 +28,6 @@
interface IBinaryTransparencyService {
String getSignedImageInfo();
- List getApexInfo();
-
void recordMeasurementsForAllPackages();
parcelable ApexInfo {
@@ -60,4 +58,5 @@
/** 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 b6a2a0e..fc81675 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.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.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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 @@
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 245db46..ae78dfe 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.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 @@
}
@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 0000000..3e94f25
--- /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<InstallMultiple> { 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 6fe548f..b8e9a17 100644
--- a/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
+++ b/tests/BinaryTransparencyHostTest/src/android/transparency/test/BinaryTransparencyHostTest.java
@@ -18,6 +18,7 @@
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 @@
}
@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 @@
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 176bc28e..c087a85 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 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 @@
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
+ }
}