Merge changes I761a9e5c,I7f03720f

* changes:
  Move shared library info out of PackageManagerService (3/n)
  Move shared library info out of PackageManagerService (2/n)
This commit is contained in:
Rhed Jao
2022-01-07 10:23:36 +00:00
committed by Android (Google) Code Review
10 changed files with 839 additions and 424 deletions

View File

@@ -4788,7 +4788,7 @@ public class ComputerEngine implements Computer {
@Override
public List<PackageStateInternal> findSharedNonSystemLibraries(
@NonNull PackageStateInternal pkgSetting) {
List<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
List<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
if (!deps.isEmpty()) {
List<PackageStateInternal> retValue = new ArrayList<>();
for (SharedLibraryInfo info : deps) {

View File

@@ -379,7 +379,7 @@ final class DexOptHelper {
// at boot, or background job), the passed 'targetCompilerFilter' stays the same,
// and the first package that uses the library will dexopt it. The
// others will see that the compiled code for the library is up to date.
Collection<SharedLibraryInfo> deps = SharedLibraryHelper.findSharedLibraries(pkgSetting);
Collection<SharedLibraryInfo> deps = SharedLibraryUtils.findSharedLibraries(pkgSetting);
final String[] instructionSets = getAppDexInstructionSets(
AndroidPackageUtils.getPrimaryCpuAbi(p, pkgSetting),
AndroidPackageUtils.getSecondaryCpuAbi(p, pkgSetting));

View File

@@ -894,8 +894,6 @@ final class InstallPackageHelper {
final Map<String, PackageInstalledInfo> installResults = new ArrayMap<>(requests.size());
final Map<String, PrepareResult> prepareResults = new ArrayMap<>(requests.size());
final Map<String, Settings.VersionInfo> versionInfos = new ArrayMap<>(requests.size());
final Map<String, PackageSetting> lastStaticSharedLibSettings =
new ArrayMap<>(requests.size());
final Map<String, Boolean> createdAppId = new ArrayMap<>(requests.size());
boolean success = false;
try {
@@ -955,35 +953,22 @@ final class InstallPackageHelper {
createdAppId.put(packageName, optimisticallyRegisterAppId(result));
versionInfos.put(result.mPkgSetting.getPkg().getPackageName(),
mPm.getSettingsVersionForPackage(result.mPkgSetting.getPkg()));
if (result.mStaticSharedLibraryInfo != null) {
final PackageSetting staticSharedLibLatestVersionSetting =
mSharedLibraries.getStaticSharedLibLatestVersionSetting(result);
if (staticSharedLibLatestVersionSetting != null) {
lastStaticSharedLibSettings.put(
result.mPkgSetting.getPkg().getPackageName(),
staticSharedLibLatestVersionSetting);
}
}
} catch (PackageManagerException e) {
request.mInstallResult.setError("Scanning Failed.", e);
return;
}
}
ReconcileRequest
reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults,
prepareResults,
mSharedLibraries.getAll(),
Collections.unmodifiableMap(mPm.mPackages), versionInfos,
lastStaticSharedLibSettings);
ReconcileRequest reconcileRequest = new ReconcileRequest(preparedScans, installArgs,
installResults, prepareResults,
Collections.unmodifiableMap(mPm.mPackages), versionInfos);
CommitRequest commitRequest = null;
synchronized (mPm.mLock) {
Map<String, ReconciledPackage> reconciledPackages;
try {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "reconcilePackages");
reconciledPackages = ReconcilePackageUtils.reconcilePackages(
reconcileRequest, mPm.mSettings.getKeySetManagerService(),
mPm.mInjector);
reconcileRequest, mSharedLibraries,
mPm.mSettings.getKeySetManagerService());
} catch (ReconcileFailure e) {
for (InstallRequest request : requests) {
request.mInstallResult.setError("Reconciliation failed...", e);
@@ -3586,15 +3571,12 @@ final class InstallPackageHelper {
final String pkgName = scanResult.mPkgSetting.getPackageName();
final ReconcileRequest reconcileRequest = new ReconcileRequest(
Collections.singletonMap(pkgName, scanResult),
mSharedLibraries.getAll(), mPm.mPackages,
mPm.mPackages,
Collections.singletonMap(pkgName,
mPm.getSettingsVersionForPackage(parsedPackage)),
Collections.singletonMap(pkgName,
mSharedLibraries.getStaticSharedLibLatestVersionSetting(
scanResult)));
mPm.getSettingsVersionForPackage(parsedPackage)));
final Map<String, ReconciledPackage> reconcileResult =
ReconcilePackageUtils.reconcilePackages(reconcileRequest,
mPm.mSettings.getKeySetManagerService(), mPm.mInjector);
mSharedLibraries, mPm.mSettings.getKeySetManagerService());
appIdCreated = optimisticallyRegisterAppId(scanResult);
commitReconciledScanResultLocked(reconcileResult.get(pkgName),
mPm.mUserManager.getUserIds());

View File

@@ -42,8 +42,8 @@ import java.util.Map;
final class ReconcilePackageUtils {
public static Map<String, ReconciledPackage> reconcilePackages(
final ReconcileRequest request, KeySetManagerService ksms,
PackageManagerServiceInjector injector)
final ReconcileRequest request, SharedLibrariesImpl sharedLibraries,
KeySetManagerService ksms)
throws ReconcileFailure {
final Map<String, ScanResult> scannedPackages = request.mScannedPackages;
@@ -67,11 +67,10 @@ final class ReconcilePackageUtils {
// in the first pass, we'll build up the set of incoming shared libraries
final List<SharedLibraryInfo> allowedSharedLibInfos =
SharedLibraryHelper.getAllowedSharedLibInfos(scanResult,
request.mSharedLibrarySource);
sharedLibraries.getAllowedSharedLibInfos(scanResult);
if (allowedSharedLibInfos != null) {
for (SharedLibraryInfo info : allowedSharedLibInfos) {
if (!SharedLibraryHelper.addSharedLibraryToPackageVersionMap(
if (!SharedLibraryUtils.addSharedLibraryToPackageVersionMap(
incomingSharedLibraries, info)) {
throw new ReconcileFailure("Shared Library " + info.getName()
+ " is being installed twice in this set!");
@@ -113,7 +112,8 @@ final class ReconcilePackageUtils {
final PackageSetting disabledPkgSetting = scanResult.mRequest.mDisabledPkgSetting;
final PackageSetting lastStaticSharedLibSetting =
request.mLastStaticSharedLibSettings.get(installPackageName);
scanResult.mStaticSharedLibraryInfo == null ? null
: sharedLibraries.getStaticSharedLibLatestVersionSetting(scanResult);
final PackageSetting signatureCheckPs =
(prepareResult != null && lastStaticSharedLibSetting != null)
? lastStaticSharedLibSetting
@@ -264,11 +264,9 @@ final class ReconcilePackageUtils {
}
try {
result.get(installPackageName).mCollectedSharedLibraryInfos =
SharedLibraryHelper.collectSharedLibraryInfos(
scanResult.mRequest.mParsedPackage,
combinedPackages, request.mSharedLibrarySource,
incomingSharedLibraries, injector.getCompatibility());
sharedLibraries.collectSharedLibraryInfos(
scanResult.mRequest.mParsedPackage, combinedPackages,
incomingSharedLibraries);
} catch (PackageManagerException e) {
throw new ReconcileFailure(e.error, e.getMessage());
}

View File

@@ -16,10 +16,7 @@
package com.android.server.pm;
import android.content.pm.SharedLibraryInfo;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.Collections;
import java.util.Map;
@@ -37,38 +34,29 @@ final class ReconcileRequest {
public final Map<String, ScanResult> mScannedPackages;
public final Map<String, AndroidPackage> mAllPackages;
public final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> mSharedLibrarySource;
public final Map<String, InstallArgs> mInstallArgs;
public final Map<String, PackageInstalledInfo> mInstallResults;
public final Map<String, PrepareResult> mPreparedPackages;
public final Map<String, Settings.VersionInfo> mVersionInfos;
public final Map<String, PackageSetting> mLastStaticSharedLibSettings;
ReconcileRequest(Map<String, ScanResult> scannedPackages,
Map<String, InstallArgs> installArgs,
Map<String, PackageInstalledInfo> installResults,
Map<String, PrepareResult> preparedPackages,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
Map<String, PackageSetting> lastStaticSharedLibSettings) {
Map<String, Settings.VersionInfo> versionInfos) {
mScannedPackages = scannedPackages;
mInstallArgs = installArgs;
mInstallResults = installResults;
mPreparedPackages = preparedPackages;
mSharedLibrarySource = sharedLibrarySource;
mAllPackages = allPackages;
mVersionInfos = versionInfos;
mLastStaticSharedLibSettings = lastStaticSharedLibSettings;
}
ReconcileRequest(Map<String, ScanResult> scannedPackages,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> sharedLibrarySource,
Map<String, AndroidPackage> allPackages,
Map<String, Settings.VersionInfo> versionInfos,
Map<String, PackageSetting> lastStaticSharedLibSettings) {
Map<String, Settings.VersionInfo> versionInfos) {
this(scannedPackages, Collections.emptyMap(), Collections.emptyMap(),
Collections.emptyMap(), sharedLibrarySource, allPackages, versionInfos,
lastStaticSharedLibSettings);
Collections.emptyMap(), allPackages, versionInfos);
}
}

View File

@@ -16,19 +16,27 @@
package com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME;
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.PackageManager;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.content.pm.VersionedPackage;
import android.os.Build;
import android.os.Process;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.service.pm.PackageServiceDumpProto;
import android.util.ArraySet;
import android.util.PackageUtils;
import android.util.Pair;
import android.util.Slog;
import android.util.proto.ProtoOutputStream;
@@ -36,8 +44,10 @@ import android.util.proto.ProtoOutputStream;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemConfig;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.AndroidPackageUtils;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.utils.Snappable;
import com.android.server.utils.SnapshotCache;
import com.android.server.utils.Watchable;
@@ -47,10 +57,13 @@ import com.android.server.utils.WatchedArrayMap;
import com.android.server.utils.WatchedLongSparseArray;
import com.android.server.utils.Watcher;
import libcore.util.HexEncoding;
import java.io.File;
import java.io.IOException;
import java.io.PrintWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.LinkedHashSet;
import java.util.List;
@@ -62,6 +75,24 @@ import java.util.function.BiConsumer;
* Current known shared libraries on the device.
*/
public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable, Snappable {
private static final boolean DEBUG_SHARED_LIBRARIES = false;
/**
* Apps targeting Android S and above need to declare dependencies to the public native
* shared libraries that are defined by the device maker using {@code uses-native-library} tag
* in its {@code AndroidManifest.xml}.
*
* If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
* the package manager rejects to install the app. The dependency can be specified as optional
* using {@code android:required} attribute in the tag, in which case failing to satisfy the
* dependency doesn't stop the installation.
* <p>Once installed, an app is provided with only the native shared libraries that are
* specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
* in the app manifest will fail even if it actually exists on the device.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
// TODO(b/200588896): remove PMS dependency
private final PackageManagerService mPm;
@@ -493,10 +524,8 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
@Nullable AndroidPackage changingLib, @Nullable PackageSetting changingLibSetting,
@NonNull Map<String, AndroidPackage> availablePackages)
throws PackageManagerException {
final ArrayList<SharedLibraryInfo> sharedLibraryInfos =
SharedLibraryHelper.collectSharedLibraryInfos(
pkgSetting.getPkg(), availablePackages, mSharedLibraries,
null /* newLibraries */, mInjector.getCompatibility());
final ArrayList<SharedLibraryInfo> sharedLibraryInfos = collectSharedLibraryInfos(
pkgSetting.getPkg(), availablePackages, null /* newLibraries */);
executeSharedLibrariesUpdateLPw(pkg, pkgSetting, changingLib, changingLibSetting,
sharedLibraryInfos, mPm.mUserManager.getUserIds());
}
@@ -734,6 +763,234 @@ public final class SharedLibrariesImpl implements SharedLibrariesRead, Watchable
return true;
}
/**
* Compare the newly scanned package with current system state to see which of its declared
* shared libraries should be allowed to be added to the system.
*/
List<SharedLibraryInfo> getAllowedSharedLibInfos(ScanResult scanResult) {
// Let's used the parsed package as scanResult.pkgSetting may be null
final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null
&& scanResult.mDynamicSharedLibraryInfos == null) {
return null;
}
// Any app can add new SDKs and static shared libraries.
if (scanResult.mSdkSharedLibraryInfo != null) {
return Collections.singletonList(scanResult.mSdkSharedLibraryInfo);
}
if (scanResult.mStaticSharedLibraryInfo != null) {
return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
}
final boolean hasDynamicLibraries = parsedPackage.isSystem()
&& scanResult.mDynamicSharedLibraryInfos != null;
if (!hasDynamicLibraries) {
return null;
}
final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
.isUpdatedSystemApp();
// We may not yet have disabled the updated package yet, so be sure to grab the
// current setting if that's the case.
final PackageSetting updatedSystemPs = isUpdatedSystemApp
? scanResult.mRequest.mDisabledPkgSetting == null
? scanResult.mRequest.mOldPkgSetting
: scanResult.mRequest.mDisabledPkgSetting
: null;
if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
|| updatedSystemPs.getPkg().getLibraryNames() == null)) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ " declares libraries that are not declared on the system image; skipping");
return null;
}
final ArrayList<SharedLibraryInfo> infos =
new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
final String name = info.getName();
if (isUpdatedSystemApp) {
// New library entries can only be added through the
// system image. This is important to get rid of a lot
// of nasty edge cases: for example if we allowed a non-
// system update of the app to add a library, then uninstalling
// the update would make the library go away, and assumptions
// we made such as through app install filtering would now
// have allowed apps on the device which aren't compatible
// with it. Better to just have the restriction here, be
// conservative, and create many fewer cases that can negatively
// impact the user experience.
if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ " declares library " + name
+ " that is not declared on system image; skipping");
continue;
}
}
synchronized (mPm.mLock) {
if (getSharedLibraryInfo(name, SharedLibraryInfo.VERSION_UNDEFINED) != null) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
+ name + " that already exists; skipping");
continue;
}
}
infos.add(info);
}
return infos;
}
/**
* Collects shared library infos that are being used by the given package.
*
* @param pkg The package using shared libraries.
* @param availablePackages The available packages which are installed and being installed,
* @param newLibraries Shared libraries defined by packages which are being installed.
* @return A list of shared library infos
*/
ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(@Nullable AndroidPackage pkg,
@NonNull Map<String, AndroidPackage> availablePackages,
@Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
throws PackageManagerException {
if (pkg == null) {
return null;
}
final PlatformCompat platformCompat = mInjector.getCompatibility();
// The collection used here must maintain the order of addition (so
// that libraries are searched in the correct order) and must have no
// duplicates.
ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
if (!pkg.getUsesLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
availablePackages, newLibraries);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, newLibraries);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, newLibraries);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
if (!pkg.getUsesNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
newLibraries);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
newLibraries);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
availablePackages, newLibraries);
}
return usesLibraryInfos;
}
private ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
@Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
final String libName = requestedLibraries.get(i);
final long libVersion = requiredVersions != null ? requiredVersions[i]
: SharedLibraryInfo.VERSION_UNDEFINED;
final SharedLibraryInfo libraryInfo;
synchronized (mPm.mLock) {
libraryInfo = SharedLibraryUtils.getSharedLibraryInfo(
libName, libVersion, mSharedLibraries, newLibraries);
}
if (libraryInfo == null) {
if (required) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library " + libName + "; failing!");
} else if (DEBUG_SHARED_LIBRARIES) {
Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+ " library " + libName + "; ignoring!");
}
} else {
if (requiredVersions != null && requiredCertDigests != null) {
if (libraryInfo.getLongVersion() != requiredVersions[i]) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library " + libName + " version "
+ libraryInfo.getLongVersion() + "; failing!");
}
AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
if (libPkg == null) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library; failing!");
}
final String[] expectedCertDigests = requiredCertDigests[i];
if (expectedCertDigests.length > 1) {
// For apps targeting O MR1 we require explicit enumeration of all certs.
final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
? PackageUtils.computeSignaturesSha256Digests(
libPkg.getSignatures())
: PackageUtils.computeSignaturesSha256Digests(
new Signature[]{libPkg.getSignatures()[0]});
// Take a shortcut if sizes don't match. Note that if an app doesn't
// target O we don't parse the "additional-certificate" tags similarly
// how we only consider all certs only for apps targeting O (see above).
// Therefore, the size check is safe to make.
if (expectedCertDigests.length != libCertDigests.length) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
// Use a predictable order as signature order may vary
Arrays.sort(libCertDigests);
Arrays.sort(expectedCertDigests);
final int certCount = libCertDigests.length;
for (int j = 0; j < certCount; j++) {
if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
throw new PackageManagerException(
INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
}
} else {
// lib signing cert could have rotated beyond the one expected, check to see
// if the new one has been blessed by the old
byte[] digestBytes = HexEncoding.decode(
expectedCertDigests[0], false /* allowSingleChar */);
if (!libPkg.hasSha256Certificate(digestBytes)) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
}
}
if (outUsedLibraries == null) {
outUsedLibraries = new ArrayList<>();
}
outUsedLibraries.add(libraryInfo);
}
}
return outUsedLibraries;
}
/**
* Dump all shared libraries.
*/

View File

@@ -1,366 +0,0 @@
/*
* Copyright (C) 2021 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 com.android.server.pm;
import static android.content.pm.PackageManager.INSTALL_FAILED_MISSING_SHARED_LIBRARY;
import static com.android.server.pm.PackageManagerService.TAG;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.compat.annotation.ChangeId;
import android.compat.annotation.EnabledAfter;
import android.content.pm.SharedLibraryInfo;
import android.content.pm.Signature;
import android.content.pm.SigningDetails;
import android.os.Build;
import android.util.PackageUtils;
import android.util.Slog;
import com.android.server.compat.PlatformCompat;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.pm.parsing.pkg.ParsedPackage;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.WatchedLongSparseArray;
import libcore.util.HexEncoding;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class SharedLibraryHelper {
private static final boolean DEBUG_SHARED_LIBRARIES = false;
/**
* Apps targeting Android S and above need to declare dependencies to the public native
* shared libraries that are defined by the device maker using {@code uses-native-library} tag
* in its {@code AndroidManifest.xml}.
*
* If any of the dependencies cannot be satisfied, i.e. one of the dependency doesn't exist,
* the package manager rejects to install the app. The dependency can be specified as optional
* using {@code android:required} attribute in the tag, in which case failing to satisfy the
* dependency doesn't stop the installation.
* <p>Once installed, an app is provided with only the native shared libraries that are
* specified in the app manifest. {@code dlopen}ing a native shared library that doesn't appear
* in the app manifest will fail even if it actually exists on the device.
*/
@ChangeId
@EnabledAfter(targetSdkVersion = Build.VERSION_CODES.R)
private static final long ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES = 142191088;
/**
* Compare the newly scanned package with current system state to see which of its declared
* shared libraries should be allowed to be added to the system.
*/
public static List<SharedLibraryInfo> getAllowedSharedLibInfos(
ScanResult scanResult,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingSharedLibraries) {
// Let's used the parsed package as scanResult.pkgSetting may be null
final ParsedPackage parsedPackage = scanResult.mRequest.mParsedPackage;
if (scanResult.mSdkSharedLibraryInfo == null && scanResult.mStaticSharedLibraryInfo == null
&& scanResult.mDynamicSharedLibraryInfos == null) {
return null;
}
// Any app can add new SDKs and static shared libraries.
if (scanResult.mSdkSharedLibraryInfo != null) {
return Collections.singletonList(scanResult.mSdkSharedLibraryInfo);
}
if (scanResult.mStaticSharedLibraryInfo != null) {
return Collections.singletonList(scanResult.mStaticSharedLibraryInfo);
}
final boolean hasDynamicLibraries = parsedPackage.isSystem()
&& scanResult.mDynamicSharedLibraryInfos != null;
if (!hasDynamicLibraries) {
return null;
}
final boolean isUpdatedSystemApp = scanResult.mPkgSetting.getPkgState()
.isUpdatedSystemApp();
// We may not yet have disabled the updated package yet, so be sure to grab the
// current setting if that's the case.
final PackageSetting updatedSystemPs = isUpdatedSystemApp
? scanResult.mRequest.mDisabledPkgSetting == null
? scanResult.mRequest.mOldPkgSetting
: scanResult.mRequest.mDisabledPkgSetting
: null;
if (isUpdatedSystemApp && (updatedSystemPs.getPkg() == null
|| updatedSystemPs.getPkg().getLibraryNames() == null)) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ " declares libraries that are not declared on the system image; skipping");
return null;
}
final ArrayList<SharedLibraryInfo> infos =
new ArrayList<>(scanResult.mDynamicSharedLibraryInfos.size());
for (SharedLibraryInfo info : scanResult.mDynamicSharedLibraryInfos) {
final String name = info.getName();
if (isUpdatedSystemApp) {
// New library entries can only be added through the
// system image. This is important to get rid of a lot
// of nasty edge cases: for example if we allowed a non-
// system update of the app to add a library, then uninstalling
// the update would make the library go away, and assumptions
// we made such as through app install filtering would now
// have allowed apps on the device which aren't compatible
// with it. Better to just have the restriction here, be
// conservative, and create many fewer cases that can negatively
// impact the user experience.
if (!updatedSystemPs.getPkg().getLibraryNames().contains(name)) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName()
+ " declares library " + name
+ " that is not declared on system image; skipping");
continue;
}
}
if (sharedLibExists(
name, SharedLibraryInfo.VERSION_UNDEFINED, existingSharedLibraries)) {
Slog.w(TAG, "Package " + parsedPackage.getPackageName() + " declares library "
+ name + " that already exists; skipping");
continue;
}
infos.add(info);
}
return infos;
}
public static boolean sharedLibExists(final String name, final long version,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> librarySource) {
WatchedLongSparseArray<SharedLibraryInfo> versionedLib = librarySource.get(name);
return versionedLib != null && versionedLib.indexOfKey(version) >= 0;
}
/**
* Returns false if the adding shared library already exists in the map and so could not be
* added.
*/
public static boolean addSharedLibraryToPackageVersionMap(
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
SharedLibraryInfo library) {
final String name = library.getName();
if (target.containsKey(name)) {
if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
// We've already added this non-version-specific library to the map.
return false;
} else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
// We've already added this version of a version-specific library to the map.
return false;
}
} else {
target.put(name, new WatchedLongSparseArray<>());
}
target.get(name).put(library.getLongVersion(), library);
return true;
}
public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(AndroidPackage pkg,
Map<String, AndroidPackage> availablePackages,
@NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
@Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries,
PlatformCompat platformCompat) throws PackageManagerException {
if (pkg == null) {
return null;
}
// The collection used here must maintain the order of addition (so
// that libraries are searched in the correct order) and must have no
// duplicates.
ArrayList<SharedLibraryInfo> usesLibraryInfos = null;
if (!pkg.getUsesLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesLibraries(), null, null,
pkg.getPackageName(), "shared", true, pkg.getTargetSdkVersion(), null,
availablePackages, existingLibraries, newLibraries);
}
if (!pkg.getUsesStaticLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesStaticLibraries(),
pkg.getUsesStaticLibrariesVersions(), pkg.getUsesStaticLibrariesCertDigests(),
pkg.getPackageName(), "static shared", true, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
}
if (!pkg.getUsesOptionalLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalLibraries(), null, null,
pkg.getPackageName(), "shared", false, pkg.getTargetSdkVersion(),
usesLibraryInfos, availablePackages, existingLibraries, newLibraries);
}
if (platformCompat.isChangeEnabledInternal(ENFORCE_NATIVE_SHARED_LIBRARY_DEPENDENCIES,
pkg.getPackageName(), pkg.getTargetSdkVersion())) {
if (!pkg.getUsesNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesNativeLibraries(), null,
null, pkg.getPackageName(), "native shared", true,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
existingLibraries, newLibraries);
}
if (!pkg.getUsesOptionalNativeLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesOptionalNativeLibraries(),
null, null, pkg.getPackageName(), "native shared", false,
pkg.getTargetSdkVersion(), usesLibraryInfos, availablePackages,
existingLibraries, newLibraries);
}
}
if (!pkg.getUsesSdkLibraries().isEmpty()) {
usesLibraryInfos = collectSharedLibraryInfos(pkg.getUsesSdkLibraries(),
pkg.getUsesSdkLibrariesVersionsMajor(), pkg.getUsesSdkLibrariesCertDigests(),
pkg.getPackageName(), "sdk", true, pkg.getTargetSdkVersion(), usesLibraryInfos,
availablePackages, existingLibraries, newLibraries);
}
return usesLibraryInfos;
}
public static ArrayList<SharedLibraryInfo> collectSharedLibraryInfos(
@NonNull List<String> requestedLibraries,
@Nullable long[] requiredVersions, @Nullable String[][] requiredCertDigests,
@NonNull String packageName, @NonNull String libraryType, boolean required,
int targetSdk, @Nullable ArrayList<SharedLibraryInfo> outUsedLibraries,
@NonNull final Map<String, AndroidPackage> availablePackages,
@NonNull final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
@Nullable final Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries)
throws PackageManagerException {
final int libCount = requestedLibraries.size();
for (int i = 0; i < libCount; i++) {
final String libName = requestedLibraries.get(i);
final long libVersion = requiredVersions != null ? requiredVersions[i]
: SharedLibraryInfo.VERSION_UNDEFINED;
final SharedLibraryInfo libraryInfo =
getSharedLibraryInfo(libName, libVersion, existingLibraries, newLibraries);
if (libraryInfo == null) {
if (required) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library " + libName + "; failing!");
} else if (DEBUG_SHARED_LIBRARIES) {
Slog.i(TAG, "Package " + packageName + " desires unavailable " + libraryType
+ " library " + libName + "; ignoring!");
}
} else {
if (requiredVersions != null && requiredCertDigests != null) {
if (libraryInfo.getLongVersion() != requiredVersions[i]) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library " + libName + " version "
+ libraryInfo.getLongVersion() + "; failing!");
}
AndroidPackage pkg = availablePackages.get(libraryInfo.getPackageName());
SigningDetails libPkg = pkg == null ? null : pkg.getSigningDetails();
if (libPkg == null) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires unavailable " + libraryType
+ " library; failing!");
}
final String[] expectedCertDigests = requiredCertDigests[i];
if (expectedCertDigests.length > 1) {
// For apps targeting O MR1 we require explicit enumeration of all certs.
final String[] libCertDigests = (targetSdk >= Build.VERSION_CODES.O_MR1)
? PackageUtils.computeSignaturesSha256Digests(
libPkg.getSignatures())
: PackageUtils.computeSignaturesSha256Digests(
new Signature[]{libPkg.getSignatures()[0]});
// Take a shortcut if sizes don't match. Note that if an app doesn't
// target O we don't parse the "additional-certificate" tags similarly
// how we only consider all certs only for apps targeting O (see above).
// Therefore, the size check is safe to make.
if (expectedCertDigests.length != libCertDigests.length) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
// Use a predictable order as signature order may vary
Arrays.sort(libCertDigests);
Arrays.sort(expectedCertDigests);
final int certCount = libCertDigests.length;
for (int j = 0; j < certCount; j++) {
if (!libCertDigests[j].equalsIgnoreCase(expectedCertDigests[j])) {
throw new PackageManagerException(
INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
}
} else {
// lib signing cert could have rotated beyond the one expected, check to see
// if the new one has been blessed by the old
byte[] digestBytes = HexEncoding.decode(
expectedCertDigests[0], false /* allowSingleChar */);
if (!libPkg.hasSha256Certificate(digestBytes)) {
throw new PackageManagerException(INSTALL_FAILED_MISSING_SHARED_LIBRARY,
"Package " + packageName + " requires differently signed "
+ libraryType + " library; failing!");
}
}
}
if (outUsedLibraries == null) {
outUsedLibraries = new ArrayList<>();
}
outUsedLibraries.add(libraryInfo);
}
}
return outUsedLibraries;
}
@Nullable
public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
@Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
if (newLibraries != null) {
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
SharedLibraryInfo info = null;
if (versionedLib != null) {
info = versionedLib.get(version);
}
if (info != null) {
return info;
}
}
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
if (versionedLib == null) {
return null;
}
return versionedLib.get(version);
}
public static List<SharedLibraryInfo> findSharedLibraries(PackageStateInternal pkgSetting) {
if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) {
ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
Set<String> collectedNames = new HashSet<>();
for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
findSharedLibrariesRecursive(info, retValue, collectedNames);
}
return retValue;
} else {
return Collections.emptyList();
}
}
private static void findSharedLibrariesRecursive(SharedLibraryInfo info,
ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) {
if (!collectedNames.contains(info.getName())) {
collectedNames.add(info.getName());
collected.add(info);
if (info.getDependencies() != null) {
for (SharedLibraryInfo dep : info.getDependencies()) {
findSharedLibrariesRecursive(dep, collected, collectedNames);
}
}
}
}
}

View File

@@ -0,0 +1,105 @@
/*
* Copyright (C) 2021 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 com.android.server.pm;
import android.annotation.Nullable;
import android.content.pm.SharedLibraryInfo;
import com.android.server.pm.pkg.PackageStateInternal;
import com.android.server.utils.WatchedLongSparseArray;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
final class SharedLibraryUtils {
/**
* Returns false if the adding shared library already exists in the map and so could not be
* added.
*/
public static boolean addSharedLibraryToPackageVersionMap(
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> target,
SharedLibraryInfo library) {
final String name = library.getName();
if (target.containsKey(name)) {
if (library.getType() != SharedLibraryInfo.TYPE_STATIC) {
// We've already added this non-version-specific library to the map.
return false;
} else if (target.get(name).indexOfKey(library.getLongVersion()) >= 0) {
// We've already added this version of a version-specific library to the map.
return false;
}
} else {
target.put(name, new WatchedLongSparseArray<>());
}
target.get(name).put(library.getLongVersion(), library);
return true;
}
@Nullable
public static SharedLibraryInfo getSharedLibraryInfo(String name, long version,
Map<String, WatchedLongSparseArray<SharedLibraryInfo>> existingLibraries,
@Nullable Map<String, WatchedLongSparseArray<SharedLibraryInfo>> newLibraries) {
if (newLibraries != null) {
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = newLibraries.get(name);
SharedLibraryInfo info = null;
if (versionedLib != null) {
info = versionedLib.get(version);
}
if (info != null) {
return info;
}
}
final WatchedLongSparseArray<SharedLibraryInfo> versionedLib = existingLibraries.get(name);
if (versionedLib == null) {
return null;
}
return versionedLib.get(version);
}
public static List<SharedLibraryInfo> findSharedLibraries(PackageStateInternal pkgSetting) {
if (!pkgSetting.getTransientState().getUsesLibraryInfos().isEmpty()) {
ArrayList<SharedLibraryInfo> retValue = new ArrayList<>();
Set<String> collectedNames = new HashSet<>();
for (SharedLibraryInfo info : pkgSetting.getTransientState().getUsesLibraryInfos()) {
findSharedLibrariesRecursive(info, retValue, collectedNames);
}
return retValue;
} else {
return Collections.emptyList();
}
}
private static void findSharedLibrariesRecursive(SharedLibraryInfo info,
ArrayList<SharedLibraryInfo> collected, Set<String> collectedNames) {
if (!collectedNames.contains(info.getName())) {
collectedNames.add(info.getName());
collected.add(info);
if (info.getDependencies() != null) {
for (SharedLibraryInfo dep : info.getDependencies()) {
findSharedLibrariesRecursive(dep, collected, collectedNames);
}
}
}
}
}

View File

@@ -74,6 +74,7 @@ import com.android.server.testutils.mock
import com.android.server.testutils.nullable
import com.android.server.testutils.whenever
import com.android.server.utils.WatchedArrayMap
import libcore.util.HexEncoding
import org.junit.Assert
import org.junit.rules.TestRule
import org.junit.runner.Description
@@ -140,6 +141,7 @@ class MockSystem(withSession: (StaticMockitoSessionBuilder) -> Unit = {}) {
.mockStatic(EventLog::class.java)
.mockStatic(LocalServices::class.java)
.mockStatic(DeviceConfig::class.java)
.mockStatic(HexEncoding::class.java)
.apply(withSession)
session = apply.startMocking()
whenever(mocks.settings.insertPackageSettingLPw(

View File

@@ -0,0 +1,449 @@
/*
* Copyright (C) 2021 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 com.android.server.pm
import android.content.pm.ApplicationInfo
import android.content.pm.PackageManager
import android.content.pm.SharedLibraryInfo
import android.content.pm.VersionedPackage
import android.os.Build
import android.os.storage.StorageManager
import android.util.ArrayMap
import android.util.PackageUtils
import com.android.server.SystemConfig.SharedLibraryEntry
import com.android.server.compat.PlatformCompat
import com.android.server.extendedtestutils.wheneverStatic
import com.android.server.pm.PackageManagerService.PLATFORM_PACKAGE_NAME
import com.android.server.pm.parsing.pkg.AndroidPackage
import com.android.server.pm.parsing.pkg.PackageImpl
import com.android.server.pm.parsing.pkg.ParsedPackage
import com.android.server.testutils.any
import com.android.server.testutils.eq
import com.android.server.testutils.mock
import com.android.server.testutils.nullable
import com.android.server.testutils.spy
import com.android.server.testutils.whenever
import com.android.server.utils.WatchedLongSparseArray
import com.google.common.truth.Truth.assertThat
import libcore.util.HexEncoding
import org.junit.Before
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.runners.JUnit4
import org.mockito.Mock
import org.mockito.Mockito.doAnswer
import org.mockito.Mockito.verify
import org.mockito.MockitoAnnotations
import java.io.File
import kotlin.test.assertFailsWith
import kotlin.test.assertFalse
import kotlin.test.assertTrue
@RunWith(JUnit4::class)
class SharedLibrariesImplTest {
companion object {
const val TEST_LIB_NAME = "test.lib"
const val TEST_LIB_PACKAGE_NAME = "com.android.lib.test"
const val BUILTIN_LIB_NAME = "builtin.lib"
const val STATIC_LIB_NAME = "static.lib"
const val STATIC_LIB_VERSION = 7L
const val STATIC_LIB_PACKAGE_NAME = "com.android.lib.static.provider"
const val DYNAMIC_LIB_NAME = "dynamic.lib"
const val DYNAMIC_LIB_PACKAGE_NAME = "com.android.lib.dynamic.provider"
const val CONSUMER_PACKAGE_NAME = "com.android.lib.consumer"
const val VERSION_UNDEFINED = SharedLibraryInfo.VERSION_UNDEFINED.toLong()
}
@Rule
@JvmField
val mRule = MockSystemRule()
private val mExistingPackages: ArrayMap<String, AndroidPackage> = ArrayMap()
private val mExistingSettings: MutableMap<String, PackageSetting> = mutableMapOf()
private lateinit var mSharedLibrariesImpl: SharedLibrariesImpl
private lateinit var mPms: PackageManagerService
private lateinit var mSettings: Settings
@Mock
private lateinit var mDeletePackageHelper: DeletePackageHelper
@Mock
private lateinit var mStorageManager: StorageManager
@Mock
private lateinit var mFile: File
@Mock
private lateinit var mPlatformCompat: PlatformCompat
@Before
fun setup() {
MockitoAnnotations.initMocks(this)
mRule.system().stageNominalSystemState()
addExistingPackages()
val testParams = PackageManagerServiceTestParams().apply {
packages = mExistingPackages
}
mPms = spy(PackageManagerService(mRule.mocks().injector, testParams))
mSettings = mRule.mocks().injector.settings
mSharedLibrariesImpl = SharedLibrariesImpl(mPms, mRule.mocks().injector)
mSharedLibrariesImpl.setDeletePackageHelper(mDeletePackageHelper)
addExistingSharedLibraries()
whenever(mSettings.getPackageLPr(any())) { mExistingSettings[arguments[0]] }
whenever(mRule.mocks().injector.getSystemService(StorageManager::class.java))
.thenReturn(mStorageManager)
whenever(mStorageManager.findPathForUuid(nullable())).thenReturn(mFile)
doAnswer { it.arguments[0] }.`when`(mPms).resolveInternalPackageNameLPr(any(), any())
whenever(mDeletePackageHelper.deletePackageX(any(), any(), any(), any(), any()))
.thenReturn(PackageManager.DELETE_SUCCEEDED)
whenever(mRule.mocks().injector.compatibility).thenReturn(mPlatformCompat)
wheneverStatic { HexEncoding.decode(STATIC_LIB_NAME, false) }
.thenReturn(PackageUtils.computeSha256DigestBytes(
mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
.pkg.signingDetails.signatures!![0].toByteArray()))
}
@Test
fun snapshot_shouldSealed() {
val builtinLibs = mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME]
assertThat(builtinLibs).isNotNull()
assertFailsWith(IllegalStateException::class) {
mSharedLibrariesImpl.snapshot().all[BUILTIN_LIB_NAME] = WatchedLongSparseArray()
}
assertFailsWith(IllegalStateException::class) {
builtinLibs!!.put(VERSION_UNDEFINED, libOfBuiltin(BUILTIN_LIB_NAME))
}
}
@Test
fun addBuiltInSharedLibrary() {
mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(TEST_LIB_NAME))
assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME)).isNotNull()
assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(TEST_LIB_NAME, VERSION_UNDEFINED))
.isNotNull()
}
@Test
fun addBuiltInSharedLibrary_withDuplicateLibName() {
val duplicate = libEntry(BUILTIN_LIB_NAME, "duplicate.path")
mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(duplicate)
val sharedLibInfo = mSharedLibrariesImpl
.getSharedLibraryInfo(BUILTIN_LIB_NAME, VERSION_UNDEFINED)
assertThat(sharedLibInfo).isNotNull()
assertThat(sharedLibInfo!!.path).isNotEqualTo(duplicate.filename)
}
@Test
fun commitSharedLibraryInfo_withStaticSharedLib() {
val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
mSharedLibrariesImpl.commitSharedLibraryInfoLPw(testInfo)
val sharedLibInfos = mSharedLibrariesImpl
.getStaticLibraryInfos(testInfo.declaringPackage.packageName)
assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(TEST_LIB_NAME))
.isNotNull()
assertThat(mSharedLibrariesImpl.getSharedLibraryInfo(testInfo.name, testInfo.longVersion))
.isNotNull()
assertThat(sharedLibInfos).isNotNull()
assertThat(sharedLibInfos.get(testInfo.longVersion)).isNotNull()
}
@Test
fun removeSharedLibrary() {
doAnswer { mutableListOf(VersionedPackage(CONSUMER_PACKAGE_NAME, 1L)) }.`when`(mPms)
.getPackagesUsingSharedLibrary(any(), any(), any(), any())
val staticInfo = mSharedLibrariesImpl
.getSharedLibraryInfo(STATIC_LIB_NAME, STATIC_LIB_VERSION)!!
mSharedLibrariesImpl.removeSharedLibraryLPw(STATIC_LIB_NAME, STATIC_LIB_VERSION)
assertThat(mSharedLibrariesImpl.getSharedLibraryInfos(STATIC_LIB_NAME)).isNull()
assertThat(mSharedLibrariesImpl
.getStaticLibraryInfos(staticInfo.declaringPackage.packageName)).isNull()
verify(mExistingSettings[CONSUMER_PACKAGE_NAME]!!)
.setOverlayPathsForLibrary(any(), nullable(), any())
}
@Test
fun pruneUnusedStaticSharedLibraries() {
mSharedLibrariesImpl.pruneUnusedStaticSharedLibraries(Long.MAX_VALUE, 0)
verify(mDeletePackageHelper)
.deletePackageX(eq(STATIC_LIB_PACKAGE_NAME), any(), any(), any(), any())
}
@Test
fun getLatestSharedLibraVersion() {
val newLibSetting = addPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L,
staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L)
val latestInfo = mSharedLibrariesImpl.getLatestSharedLibraVersionLPr(newLibSetting.pkg)!!
assertThat(latestInfo).isNotNull()
assertThat(latestInfo.name).isEqualTo(STATIC_LIB_NAME)
assertThat(latestInfo.longVersion).isEqualTo(STATIC_LIB_VERSION)
}
@Test
fun getStaticSharedLibLatestVersionSetting() {
val pair = createBasicAndroidPackage(STATIC_LIB_PACKAGE_NAME + "_" + 10, 10L,
staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = 10L)
val parsedPackage = pair.second as ParsedPackage
val scanRequest = ScanRequest(parsedPackage, null, null, null,
null, null, null, 0, 0, false, null, null)
val scanResult = ScanResult(scanRequest, true, null, null, false, 0, null, null, null)
val latestInfoSetting =
mSharedLibrariesImpl.getStaticSharedLibLatestVersionSetting(scanResult)!!
assertThat(latestInfoSetting).isNotNull()
assertThat(latestInfoSetting.packageName).isEqualTo(STATIC_LIB_PACKAGE_NAME)
}
@Test
fun updateSharedLibraries_withDynamicLibPackage() {
val testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
}
@Test
fun updateSharedLibraries_withStaticLibPackage() {
val testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
}
@Test
fun updateSharedLibraries_withConsumerPackage() {
val testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).isEmpty()
mSharedLibrariesImpl.updateSharedLibrariesLPw(testPackageSetting.pkg, testPackageSetting,
null /* changingLib */, null /* changingLibSetting */, mExistingPackages)
assertThat(testPackageSetting.usesLibraryFiles).hasSize(2)
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
}
@Test
fun updateAllSharedLibraries() {
mExistingSettings.forEach {
assertThat(it.value.usesLibraryFiles).isEmpty()
}
mSharedLibrariesImpl.updateAllSharedLibrariesLPw(
null /* updatedPkg */, null /* updatedPkgSetting */, mExistingPackages)
var testPackageSetting = mExistingSettings[DYNAMIC_LIB_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).hasSize(1)
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
testPackageSetting = mExistingSettings[STATIC_LIB_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).hasSize(2)
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
testPackageSetting = mExistingSettings[CONSUMER_PACKAGE_NAME]!!
assertThat(testPackageSetting.usesLibraryFiles).hasSize(3)
assertThat(testPackageSetting.usesLibraryFiles).contains(builtinLibPath(BUILTIN_LIB_NAME))
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(DYNAMIC_LIB_PACKAGE_NAME))
assertThat(testPackageSetting.usesLibraryFiles).contains(apkPath(STATIC_LIB_PACKAGE_NAME))
}
@Test
fun getAllowedSharedLibInfos_withStaticSharedLibInfo() {
val testInfo = libOfStatic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME, 1L)
val scanResult = ScanResult(mock(), true, null, null,
false, 0, null, testInfo, null)
val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
}
@Test
fun getAllowedSharedLibInfos_withDynamicSharedLibInfo() {
val testInfo = libOfDynamic(TEST_LIB_PACKAGE_NAME, TEST_LIB_NAME)
val pair = createBasicAndroidPackage(
TEST_LIB_PACKAGE_NAME, 10L, libraries = arrayOf(TEST_LIB_NAME))
val parsedPackage = pair.second.apply {
isSystem = true
} as ParsedPackage
val packageSetting = mRule.system()
.createBasicSettingBuilder(pair.first.parentFile, parsedPackage.hideAsFinal())
.setPkgFlags(ApplicationInfo.FLAG_SYSTEM).build()
val scanRequest = ScanRequest(parsedPackage, null, null, null,
null, null, null, 0, 0, false, null, null)
val scanResult = ScanResult(scanRequest, true, packageSetting, null,
false, 0, null, null, listOf(testInfo))
val allowedInfos = mSharedLibrariesImpl.getAllowedSharedLibInfos(scanResult)
assertThat(allowedInfos).hasSize(1)
assertThat(allowedInfos[0].name).isEqualTo(TEST_LIB_NAME)
}
private fun addExistingPackages() {
// add a dynamic shared library that is using the builtin library
addPackage(DYNAMIC_LIB_PACKAGE_NAME, 1L,
libraries = arrayOf(DYNAMIC_LIB_NAME),
usesLibraries = arrayOf(BUILTIN_LIB_NAME))
// add a static shared library v7 that is using the dynamic shared library
addPackage(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_VERSION,
staticLibrary = STATIC_LIB_NAME, staticLibraryVersion = STATIC_LIB_VERSION,
usesLibraries = arrayOf(DYNAMIC_LIB_NAME))
// add a consumer package that is using the dynamic and static shared library
addPackage(CONSUMER_PACKAGE_NAME, 1L,
usesLibraries = arrayOf(DYNAMIC_LIB_NAME),
usesStaticLibraries = arrayOf(STATIC_LIB_NAME),
usesStaticLibraryVersions = arrayOf(STATIC_LIB_VERSION))
}
private fun addExistingSharedLibraries() {
mSharedLibrariesImpl.addBuiltInSharedLibraryLPw(libEntry(BUILTIN_LIB_NAME))
mSharedLibrariesImpl.commitSharedLibraryInfoLPw(
libOfDynamic(DYNAMIC_LIB_PACKAGE_NAME, DYNAMIC_LIB_NAME))
mSharedLibrariesImpl.commitSharedLibraryInfoLPw(
libOfStatic(STATIC_LIB_PACKAGE_NAME, STATIC_LIB_NAME, STATIC_LIB_VERSION))
}
private fun addPackage(
packageName: String,
version: Long,
libraries: Array<String>? = null,
staticLibrary: String? = null,
staticLibraryVersion: Long = 0L,
usesLibraries: Array<String>? = null,
usesStaticLibraries: Array<String>? = null,
usesStaticLibraryVersions: Array<Long>? = null
): PackageSetting {
val pair = createBasicAndroidPackage(packageName, version, libraries, staticLibrary,
staticLibraryVersion, usesLibraries, usesStaticLibraries, usesStaticLibraryVersions)
val apkPath = pair.first
val parsingPackage = pair.second
val spyPkg = spy((parsingPackage as ParsedPackage).hideAsFinal())
mExistingPackages[packageName] = spyPkg
val spyPackageSetting = spy(mRule.system()
.createBasicSettingBuilder(apkPath.parentFile, spyPkg).build())
mExistingSettings[spyPackageSetting.packageName] = spyPackageSetting
return spyPackageSetting
}
private fun createBasicAndroidPackage(
packageName: String,
version: Long,
libraries: Array<String>? = null,
staticLibrary: String? = null,
staticLibraryVersion: Long = 0L,
usesLibraries: Array<String>? = null,
usesStaticLibraries: Array<String>? = null,
usesStaticLibraryVersions: Array<Long>? = null
): Pair<File, PackageImpl> {
assertFalse { libraries != null && staticLibrary != null }
assertTrue { (usesStaticLibraries?.size ?: -1) == (usesStaticLibraryVersions?.size ?: -1) }
val pair = mRule.system()
.createBasicAndroidPackage(mRule.system().dataAppDirectory, packageName, version)
pair.second.apply {
setTargetSdkVersion(Build.VERSION_CODES.S)
libraries?.forEach { addLibraryName(it) }
staticLibrary?.let {
setStaticSharedLibName(it)
setStaticSharedLibVersion(staticLibraryVersion)
setStaticSharedLibrary(true)
}
usesLibraries?.forEach { addUsesLibrary(it) }
usesStaticLibraries?.forEachIndexed { index, s ->
addUsesStaticLibrary(s,
usesStaticLibraryVersions?.get(index) ?: 0L,
arrayOf(s))
}
}
return pair
}
private fun libEntry(libName: String, path: String? = null): SharedLibraryEntry =
SharedLibraryEntry(libName, path ?: builtinLibPath(libName),
arrayOfNulls(0), false /* isNative */)
private fun libOfBuiltin(libName: String): SharedLibraryInfo =
SharedLibraryInfo(builtinLibPath(libName),
null /* packageName */,
null /* codePaths */,
libName,
VERSION_UNDEFINED,
SharedLibraryInfo.TYPE_BUILTIN,
VersionedPackage(PLATFORM_PACKAGE_NAME, 0L /* versionCode */),
null /* dependentPackages */,
null /* dependencies */,
false /* isNative */)
private fun libOfStatic(
packageName: String,
libName: String,
version: Long
): SharedLibraryInfo =
SharedLibraryInfo(null /* path */,
packageName,
listOf(apkPath(packageName)),
libName,
version,
SharedLibraryInfo.TYPE_STATIC,
VersionedPackage(packageName, version /* versionCode */),
null /* dependentPackages */,
null /* dependencies */,
false /* isNative */)
private fun libOfDynamic(packageName: String, libName: String): SharedLibraryInfo =
SharedLibraryInfo(null /* path */,
packageName,
listOf(apkPath(packageName)),
libName,
VERSION_UNDEFINED,
SharedLibraryInfo.TYPE_DYNAMIC,
VersionedPackage(packageName, 1L /* versionCode */),
null /* dependentPackages */,
null /* dependencies */,
false /* isNative */)
private fun builtinLibPath(libName: String): String = "/system/app/$libName/$libName.jar"
private fun apkPath(packageName: String): String =
File(mRule.system().dataAppDirectory, packageName).path
}