Merge "Support measuring split"

This commit is contained in:
Victor Hsieh
2023-02-03 15:42:55 +00:00
committed by Android (Google) Code Review
7 changed files with 318 additions and 174 deletions

View File

@@ -66,24 +66,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.
*
@@ -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();
}
}
}

View File

@@ -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);
}

View File

@@ -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;
}

View File

@@ -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;
@@ -165,36 +164,6 @@ public class BinaryTransparencyServiceTest {
BinaryTransparencyService.VBMETA_DIGEST_UNAVAILABLE), result);
}
@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,

View File

@@ -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"));
}
}
}

View File

@@ -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;
@@ -80,6 +81,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 {
@@ -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");
}
}
}

View File

@@ -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
}
}