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
This commit is contained in:
Victor Hsieh
2023-01-31 09:30:03 -08:00
parent 1f279326ef
commit 38b94091a9
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
}
}