Merge "Support measuring split"
This commit is contained in:
@@ -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();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user