Merge changes from topic "apex_migration"

am: 8eacf37ef3

Change-Id: If897274a7d2b9258142a55a628525821d713af4b
This commit is contained in:
Bill Lin
2019-11-06 18:30:09 -08:00
committed by android-build-merger
5 changed files with 629 additions and 525 deletions

View File

@@ -619,21 +619,6 @@ public class PackageParser {
return path.endsWith(APK_FILE_EXTENSION);
}
/**
* Generate and return the {@link PackageInfo} for a parsed package.
*
* @param p the parsed package.
* @param flags indicating which optional information is included.
*/
@UnsupportedAppUsage
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state) {
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
grantedPermissions, state, UserHandle.getCallingUserId());
}
/**
* Returns true if the package is installed and not hidden, or if the caller
* explicitly wanted all uninstalled and hidden packages as well.
@@ -660,8 +645,45 @@ public class PackageParser {
return checkUseInstalledOrHidden(0, state, null);
}
/**
* Generate and return the {@link PackageInfo} for a parsed package.
*
* @param p the parsed package.
* @param flags indicating which optional information is included.
*/
@UnsupportedAppUsage
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int[] gids, int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state) {
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
grantedPermissions, state, UserHandle.getCallingUserId());
}
@UnsupportedAppUsage
public static PackageInfo generatePackageInfo(PackageParser.Package p,
int[] gids, int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
return generatePackageInfo(p, null, gids, flags, firstInstallTime, lastUpdateTime,
grantedPermissions, state, userId);
}
/**
* PackageInfo generator specifically for apex files.
*
* @param pkg Package to generate info from. Should be derived from an apex.
* @param apexInfo Apex info relating to the package.
* @return PackageInfo
* @throws PackageParserException
*/
public static PackageInfo generatePackageInfo(
PackageParser.Package pkg, ApexInfo apexInfo, int flags) {
return generatePackageInfo(pkg, apexInfo, EmptyArray.INT, flags, 0, 0,
Collections.emptySet(), new PackageUserState(), UserHandle.getCallingUserId());
}
private static PackageInfo generatePackageInfo(PackageParser.Package p, ApexInfo apexInfo,
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
Set<String> grantedPermissions, PackageUserState state, int userId) {
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
@@ -808,8 +830,27 @@ public class PackageParser {
}
}
}
if (apexInfo != null) {
File apexFile = new File(apexInfo.modulePath);
pi.applicationInfo.sourceDir = apexFile.getPath();
pi.applicationInfo.publicSourceDir = apexFile.getPath();
if (apexInfo.isFactory) {
pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
} else {
pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
}
if (apexInfo.isActive) {
pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
} else {
pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
pi.isApex = true;
}
// deprecated method of getting signing certificates
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
if ((flags & PackageManager.GET_SIGNATURES) != 0) {
if (p.mSigningDetails.hasPastSigningCertificates()) {
// Package has included signing certificate rotation information. Return the oldest
// cert so that programmatic checks keep working even if unaware of key rotation.
@@ -8397,61 +8438,4 @@ public class PackageParser {
}
}
// TODO(b/129261524): Clean up API
/**
* PackageInfo parser specifically for apex files.
* NOTE: It will collect certificates
*
* @param apexInfo
* @return PackageInfo
* @throws PackageParserException
*/
public static PackageInfo generatePackageInfoFromApex(ApexInfo apexInfo, int flags)
throws PackageParserException {
PackageParser pp = new PackageParser();
File apexFile = new File(apexInfo.modulePath);
final Package p = pp.parsePackage(apexFile, flags, false);
PackageUserState state = new PackageUserState();
PackageInfo pi = generatePackageInfo(p, EmptyArray.INT, flags, 0, 0,
Collections.emptySet(), state);
pi.applicationInfo.sourceDir = apexFile.getPath();
pi.applicationInfo.publicSourceDir = apexFile.getPath();
if (apexInfo.isFactory) {
pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
} else {
pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_SYSTEM;
}
if (apexInfo.isActive) {
pi.applicationInfo.flags |= ApplicationInfo.FLAG_INSTALLED;
} else {
pi.applicationInfo.flags &= ~ApplicationInfo.FLAG_INSTALLED;
}
pi.isApex = true;
// Collect certificates
if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
collectCertificates(p, apexFile, false);
// Keep legacy mechanism for handling signatures. While this is deprecated, it's
// still part of the public API and needs to be maintained
if (p.mSigningDetails.hasPastSigningCertificates()) {
// Package has included signing certificate rotation information. Return
// the oldest cert so that programmatic checks keep working even if unaware
// of key rotation.
pi.signatures = new Signature[1];
pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
} else if (p.mSigningDetails.hasSignatures()) {
// otherwise keep old behavior
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
if (p.mSigningDetails != SigningDetails.UNKNOWN) {
// only return a valid SigningInfo if there is signing information to report
pi.signingInfo = new SigningInfo(p.mSigningDetails);
} else {
pi.signingInfo = null;
}
}
return pi;
}
}

View File

@@ -508,7 +508,12 @@ public class PackageParserTest {
apexInfo.modulePath = apexFile.getPath();
apexInfo.versionCode = 191000070;
int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
PackageInfo pi = PackageParser.generatePackageInfoFromApex(apexInfo, flags);
PackageParser pp = new PackageParser();
Package p = pp.parsePackage(apexFile, flags, false);
PackageParser.collectCertificates(p, false);
PackageInfo pi = PackageParser.generatePackageInfo(p, apexInfo, flags);
assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
assertTrue(pi.applicationInfo.enabled);
assertEquals(28, pi.applicationInfo.targetSdkVersion);

View File

@@ -11,7 +11,7 @@
* 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.s
* limitations under the License.
*/
package com.android.server.pm;
@@ -29,14 +29,12 @@ import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.ServiceManager.ServiceNotFoundException;
import android.sysprop.ApexProperties;
import android.util.ArrayMap;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
@@ -56,117 +54,33 @@ import java.util.stream.Collectors;
* ApexManager class handles communications with the apex service to perform operation and queries,
* as well as providing caching to avoid unnecessary calls to the service.
*/
class ApexManager {
static final String TAG = "ApexManager";
private final IApexService mApexService;
private final Context mContext;
private final Object mLock = new Object();
/**
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
* AndroidManifest.xml}
*
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
* AndroidManifest.xml}.
*/
@GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache;
/**
* A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code
* AndroidManifest.xml}.
*
* <p>Note that key of this map is {@code apexName} field which corresponds to the {@code name}
* field of {@code apex_manifest.json}.
*/
// TODO(b/132324953): remove.
@GuardedBy("mLock")
private ArrayMap<String, PackageInfo> mApexNameToPackageInfoCache;
abstract class ApexManager {
ApexManager(Context context) {
mContext = context;
if (!isApexSupported()) {
mApexService = null;
return;
}
try {
mApexService = IApexService.Stub.asInterface(
ServiceManager.getServiceOrThrow("apexservice"));
} catch (ServiceNotFoundException e) {
throw new IllegalStateException("Required service apexservice not available");
}
}
private static final String TAG = "ApexManager";
static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
@IntDef(
flag = true,
prefix = { "MATCH_"},
value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
@Retention(RetentionPolicy.SOURCE)
@interface PackageInfoFlags{}
void systemReady() {
if (!isApexSupported()) return;
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
onBootCompleted();
mContext.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
return;
}
mApexNameToPackageInfoCache = new ArrayMap<>();
/**
* Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
* on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
* {@code true}.
*/
static ApexManager create(Context systemContext) {
if (ApexProperties.updatable().orElse(false)) {
try {
mAllPackagesCache = new ArrayList<>();
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
final ApexInfo[] allPkgs = mApexService.getAllPackages();
for (ApexInfo ai : allPkgs) {
// If the device is using flattened APEX, don't report any APEX
// packages since they won't be managed or updated by PackageManager.
if ((new File(ai.modulePath)).isDirectory()) {
break;
}
try {
final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
ai, PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES);
mAllPackagesCache.add(pkg);
if (ai.isActive) {
if (activePackagesSet.contains(pkg.packageName)) {
throw new IllegalStateException(
"Two active packages have the same name: "
+ pkg.packageName);
}
activePackagesSet.add(pkg.packageName);
// TODO(b/132324953): remove.
mApexNameToPackageInfoCache.put(ai.moduleName, pkg);
}
if (ai.isFactory) {
if (factoryPackagesSet.contains(pkg.packageName)) {
throw new IllegalStateException(
"Two factory packages have the same name: "
+ pkg.packageName);
}
factoryPackagesSet.add(pkg.packageName);
}
} catch (PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface(
ServiceManager.getServiceOrThrow("apexservice")));
} catch (ServiceManager.ServiceNotFoundException e) {
throw new IllegalStateException("Required service apexservice not available");
}
} else {
return new ApexManagerNoOp();
}
}
abstract void systemReady();
/**
* Retrieves information about an APEX package.
*
@@ -179,35 +93,8 @@ class ApexManager {
* @return a PackageInfo object with the information about the package, or null if the package
* is not found.
*/
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
if (!isApexSupported()) return null;
populateAllPackagesCacheIfNeeded();
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (PackageInfo packageInfo: mAllPackagesCache) {
if (!packageInfo.packageName.equals(packageName)) {
continue;
}
if ((!matchActive || isActive(packageInfo))
&& (!matchFactory || isFactory(packageInfo))) {
return packageInfo;
}
}
return null;
}
/**
* Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
*
* @deprecated this API will soon be deleted, please don't depend on it.
*/
// TODO(b/132324953): delete.
@Deprecated
@Nullable PackageInfo getPackageInfoForApexName(String apexName) {
if (!isApexSupported()) return null;
populateAllPackagesCacheIfNeeded();
return mApexNameToPackageInfoCache.get(apexName);
}
@Nullable
abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags);
/**
* Retrieves information about all active APEX packages.
@@ -215,14 +102,7 @@ class ApexManager {
* @return a List of PackageInfo object, each one containing information about a different
* active package.
*/
List<PackageInfo> getActivePackages() {
if (!isApexSupported()) return Collections.emptyList();
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> isActive(item))
.collect(Collectors.toList());
}
abstract List<PackageInfo> getActivePackages();
/**
* Retrieves information about all active pre-installed APEX packages.
@@ -230,14 +110,7 @@ class ApexManager {
* @return a List of PackageInfo object, each one containing information about a different
* active pre-installed package.
*/
List<PackageInfo> getFactoryPackages() {
if (!isApexSupported()) return Collections.emptyList();
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> isFactory(item))
.collect(Collectors.toList());
}
abstract List<PackageInfo> getFactoryPackages();
/**
* Retrieves information about all inactive APEX packages.
@@ -245,14 +118,7 @@ class ApexManager {
* @return a List of PackageInfo object, each one containing information about a different
* inactive package.
*/
List<PackageInfo> getInactivePackages() {
if (!isApexSupported()) return Collections.emptyList();
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> !isActive(item))
.collect(Collectors.toList());
}
abstract List<PackageInfo> getInactivePackages();
/**
* Checks if {@code packageName} is an apex package.
@@ -260,16 +126,7 @@ class ApexManager {
* @param packageName package to check.
* @return {@code true} if {@code packageName} is an apex package.
*/
boolean isApexPackage(String packageName) {
if (!isApexSupported()) return false;
populateAllPackagesCacheIfNeeded();
for (PackageInfo packageInfo : mAllPackagesCache) {
if (packageInfo.packageName.equals(packageName)) {
return true;
}
}
return false;
}
abstract boolean isApexPackage(String packageName);
/**
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
@@ -278,19 +135,8 @@ class ApexManager {
* @param sessionId the identifier of the session.
* @return an ApexSessionInfo object, or null if the session is not known.
*/
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
if (!isApexSupported()) return null;
try {
ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
if (apexSessionInfo.isUnknown) {
return null;
}
return apexSessionInfo;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
}
}
@Nullable
abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
/**
* Submit a staged session to apex service. This causes the apex service to perform some initial
@@ -302,47 +148,19 @@ class ApexManager {
* @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
* an array of identifiers of all the child sessions. Otherwise it should
* be an empty array.
* @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
* and will be filled with a list of {@link ApexInfo} objects, each of which
* contains metadata about one of the packages being submitted as part of
* the session.
* @return whether the submission of the session was successful.
* @throws PackageManagerException if call to apexd fails
*/
boolean submitStagedSession(
int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
if (!isApexSupported()) return false;
try {
mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
Slog.e(TAG, "apexd verification failed", e);
return false;
}
}
abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
throws PackageManagerException;
/**
* Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
* applied at next reboot.
*
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
* @return true upon success, false if the session is unknown.
* @throws PackageManagerException if call to apexd fails
*/
boolean markStagedSessionReady(int sessionId) {
if (!isApexSupported()) return false;
try {
mApexService.markStagedSessionReady(sessionId);
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
Slog.e(TAG, "Failed to mark session " + sessionId + " ready", e);
return false;
}
}
abstract void markStagedSessionReady(int sessionId) throws PackageManagerException;
/**
* Marks a staged session as successful.
@@ -352,44 +170,21 @@ class ApexManager {
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
* successful.
*/
void markStagedSessionSuccessful(int sessionId) {
if (!isApexSupported()) return;
try {
mApexService.markStagedSessionSuccessful(sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
// It is fine to just log an exception in this case. APEXd will be able to recover in
// case markStagedSessionSuccessful fails.
Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
}
}
abstract void markStagedSessionSuccessful(int sessionId);
/**
* Whether the current device supports the management of APEX packages.
*
* @return true if APEX packages can be managed on this device, false otherwise.
*/
boolean isApexSupported() {
return ApexProperties.updatable().orElse(false);
}
abstract boolean isApexSupported();
/**
* Abandons the (only) active session previously submitted.
*
* @return {@code true} upon success, {@code false} if any remote exception occurs
*/
boolean abortActiveSession() {
if (!isApexSupported()) return false;
try {
mApexService.abortActiveSession();
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
}
}
abstract boolean abortActiveSession();
/**
* Uninstalls given {@code apexPackage}.
@@ -399,64 +194,7 @@ class ApexManager {
* @param apexPackagePath package to uninstall.
* @return {@code true} upon successful uninstall, {@code false} otherwise.
*/
boolean uninstallApex(String apexPackagePath) {
if (!isApexSupported()) return false;
try {
mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
return true;
} catch (Exception e) {
return false;
}
}
/**
* Whether an APEX package is active or not.
*
* @param packageInfo the package to check
* @return {@code true} if this package is active, {@code false} otherwise.
*/
private static boolean isActive(PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
}
/**
* Whether the APEX package is pre-installed or not.
*
* @param packageInfo the package to check
* @return {@code true} if this package is pre-installed, {@code false} otherwise.
*/
private static boolean isFactory(PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
/**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
* information about that specific package will be dumped.
* @param ipw the {@link IndentingPrintWriter} object to send information to.
*/
void dumpFromPackagesCache(
List<PackageInfo> packagesCache,
@Nullable String packageName,
IndentingPrintWriter ipw) {
ipw.println();
ipw.increaseIndent();
for (PackageInfo pi : packagesCache) {
if (packageName != null && !packageName.equals(pi.packageName)) {
continue;
}
ipw.println(pi.packageName);
ipw.increaseIndent();
ipw.println("Version: " + pi.versionCode);
ipw.println("Path: " + pi.applicationInfo.sourceDir);
ipw.println("IsActive: " + isActive(pi));
ipw.println("IsFactory: " + isFactory(pi));
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
}
abstract boolean uninstallApex(String apexPackagePath);
/**
* Dumps various state information to the provided {@link PrintWriter} object.
@@ -465,54 +203,419 @@ class ApexManager {
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
* information about that specific package will be dumped.
*/
void dump(PrintWriter pw, @Nullable String packageName) {
if (!isApexSupported()) return;
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
try {
populateAllPackagesCacheIfNeeded();
ipw.println();
ipw.println("Active APEX packages:");
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
ipw.println("Inactive APEX packages:");
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
ipw.println("Factory APEX packages:");
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
ipw.increaseIndent();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = mApexService.getSessions();
for (ApexSessionInfo si : sessions) {
ipw.println("Session ID: " + si.sessionId);
ipw.increaseIndent();
if (si.isUnknown) {
ipw.println("State: UNKNOWN");
} else if (si.isVerified) {
ipw.println("State: VERIFIED");
} else if (si.isStaged) {
ipw.println("State: STAGED");
} else if (si.isActivated) {
ipw.println("State: ACTIVATED");
} else if (si.isActivationFailed) {
ipw.println("State: ACTIVATION FAILED");
} else if (si.isSuccess) {
ipw.println("State: SUCCESS");
} else if (si.isRollbackInProgress) {
ipw.println("State: ROLLBACK IN PROGRESS");
} else if (si.isRolledBack) {
ipw.println("State: ROLLED BACK");
} else if (si.isRollbackFailed) {
ipw.println("State: ROLLBACK FAILED");
abstract void dump(PrintWriter pw, @Nullable String packageName);
@IntDef(
flag = true,
prefix = { "MATCH_"},
value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
@Retention(RetentionPolicy.SOURCE)
@interface PackageInfoFlags{}
/**
* An implementation of {@link ApexManager} that should be used in case device supports updating
* APEX packages.
*/
private static class ApexManagerImpl extends ApexManager {
private final IApexService mApexService;
private final Context mContext;
private final Object mLock = new Object();
/**
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
* AndroidManifest.xml}
*
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
* AndroidManifest.xml}.
*/
@GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache;
ApexManagerImpl(Context context, IApexService apexService) {
mContext = context;
mApexService = apexService;
}
/**
* Whether an APEX package is active or not.
*
* @param packageInfo the package to check
* @return {@code true} if this package is active, {@code false} otherwise.
*/
private static boolean isActive(PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
}
/**
* Whether the APEX package is pre-installed or not.
*
* @param packageInfo the package to check
* @return {@code true} if this package is pre-installed, {@code false} otherwise.
*/
private static boolean isFactory(PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
@Override
void systemReady() {
mContext.registerReceiver(new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
populateAllPackagesCacheIfNeeded();
mContext.unregisterReceiver(this);
}
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
}
private void populateAllPackagesCacheIfNeeded() {
synchronized (mLock) {
if (mAllPackagesCache != null) {
return;
}
try {
mAllPackagesCache = new ArrayList<>();
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
final ApexInfo[] allPkgs = mApexService.getAllPackages();
for (ApexInfo ai : allPkgs) {
// If the device is using flattened APEX, don't report any APEX
// packages since they won't be managed or updated by PackageManager.
if ((new File(ai.modulePath)).isDirectory()) {
break;
}
int flags = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.GET_SIGNATURES;
PackageParser.Package pkg;
try {
File apexFile = new File(ai.modulePath);
PackageParser pp = new PackageParser();
pkg = pp.parsePackage(apexFile, flags, false);
PackageParser.collectCertificates(pkg, false);
} catch (PackageParser.PackageParserException pe) {
throw new IllegalStateException("Unable to parse: " + ai, pe);
}
final PackageInfo packageInfo =
PackageParser.generatePackageInfo(pkg, ai, flags);
mAllPackagesCache.add(packageInfo);
if (ai.isActive) {
if (activePackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two active packages have the same name: "
+ packageInfo.packageName);
}
activePackagesSet.add(packageInfo.packageName);
}
if (ai.isFactory) {
if (factoryPackagesSet.contains(packageInfo.packageName)) {
throw new IllegalStateException(
"Two factory packages have the same name: "
+ packageInfo.packageName);
}
factoryPackagesSet.add(packageInfo.packageName);
}
}
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
}
}
}
@Override
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
populateAllPackagesCacheIfNeeded();
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (PackageInfo packageInfo: mAllPackagesCache) {
if (!packageInfo.packageName.equals(packageName)) {
continue;
}
if ((!matchActive || isActive(packageInfo))
&& (!matchFactory || isFactory(packageInfo))) {
return packageInfo;
}
}
return null;
}
@Override
List<PackageInfo> getActivePackages() {
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> isActive(item))
.collect(Collectors.toList());
}
@Override
List<PackageInfo> getFactoryPackages() {
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> isFactory(item))
.collect(Collectors.toList());
}
@Override
List<PackageInfo> getInactivePackages() {
populateAllPackagesCacheIfNeeded();
return mAllPackagesCache
.stream()
.filter(item -> !isActive(item))
.collect(Collectors.toList());
}
@Override
boolean isApexPackage(String packageName) {
if (!isApexSupported()) return false;
populateAllPackagesCacheIfNeeded();
for (PackageInfo packageInfo : mAllPackagesCache) {
if (packageInfo.packageName.equals(packageName)) {
return true;
}
}
return false;
}
@Override
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
try {
ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
if (apexSessionInfo.isUnknown) {
return null;
}
return apexSessionInfo;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
}
}
@Override
ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
return apexInfoList;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
throw new PackageManagerException(
PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"apexd verification failed : " + e.getMessage());
}
}
@Override
void markStagedSessionReady(int sessionId) throws PackageManagerException {
try {
mApexService.markStagedSessionReady(sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
throw new PackageManagerException(
PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to mark apexd session as ready : " + e.getMessage());
}
}
@Override
void markStagedSessionSuccessful(int sessionId) {
try {
mApexService.markStagedSessionSuccessful(sessionId);
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
throw new RuntimeException(re);
} catch (Exception e) {
// It is fine to just log an exception in this case. APEXd will be able to recover
// in case markStagedSessionSuccessful fails.
Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
}
}
@Override
boolean isApexSupported() {
return true;
}
@Override
boolean abortActiveSession() {
try {
mApexService.abortActiveSession();
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
}
}
@Override
boolean uninstallApex(String apexPackagePath) {
try {
mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
return true;
} catch (Exception e) {
return false;
}
}
/**
* Dump information about the packages contained in a particular cache
* @param packagesCache the cache to print information about.
* @param packageName a {@link String} containing a package name, or {@code null}. If set,
* only information about that specific package will be dumped.
* @param ipw the {@link IndentingPrintWriter} object to send information to.
*/
void dumpFromPackagesCache(
List<PackageInfo> packagesCache,
@Nullable String packageName,
IndentingPrintWriter ipw) {
ipw.println();
ipw.increaseIndent();
for (PackageInfo pi : packagesCache) {
if (packageName != null && !packageName.equals(pi.packageName)) {
continue;
}
ipw.println(pi.packageName);
ipw.increaseIndent();
ipw.println("Version: " + pi.versionCode);
ipw.println("Path: " + pi.applicationInfo.sourceDir);
ipw.println("IsActive: " + isActive(pi));
ipw.println("IsFactory: " + isFactory(pi));
ipw.decreaseIndent();
}
ipw.decreaseIndent();
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
ipw.println();
}
@Override
void dump(PrintWriter pw, @Nullable String packageName) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
try {
populateAllPackagesCacheIfNeeded();
ipw.println();
ipw.println("Active APEX packages:");
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
ipw.println("Inactive APEX packages:");
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
ipw.println("Factory APEX packages:");
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
ipw.increaseIndent();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = mApexService.getSessions();
for (ApexSessionInfo si : sessions) {
ipw.println("Session ID: " + si.sessionId);
ipw.increaseIndent();
if (si.isUnknown) {
ipw.println("State: UNKNOWN");
} else if (si.isVerified) {
ipw.println("State: VERIFIED");
} else if (si.isStaged) {
ipw.println("State: STAGED");
} else if (si.isActivated) {
ipw.println("State: ACTIVATED");
} else if (si.isActivationFailed) {
ipw.println("State: ACTIVATION FAILED");
} else if (si.isSuccess) {
ipw.println("State: SUCCESS");
} else if (si.isRollbackInProgress) {
ipw.println("State: ROLLBACK IN PROGRESS");
} else if (si.isRolledBack) {
ipw.println("State: ROLLED BACK");
} else if (si.isRollbackFailed) {
ipw.println("State: ROLLBACK FAILED");
}
ipw.decreaseIndent();
}
ipw.decreaseIndent();
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
}
}
}
public void onBootCompleted() {
if (!isApexSupported()) return;
populateAllPackagesCacheIfNeeded();
/**
* An implementation of {@link ApexManager} that should be used in case device does not support
* updating APEX packages.
*/
private static final class ApexManagerNoOp extends ApexManager {
@Override
void systemReady() {
// No-op
}
@Override
PackageInfo getPackageInfo(String packageName, int flags) {
return null;
}
@Override
List<PackageInfo> getActivePackages() {
return Collections.emptyList();
}
@Override
List<PackageInfo> getFactoryPackages() {
return Collections.emptyList();
}
@Override
List<PackageInfo> getInactivePackages() {
return Collections.emptyList();
}
@Override
boolean isApexPackage(String packageName) {
return false;
}
@Override
ApexSessionInfo getStagedSessionInfo(int sessionId) {
throw new UnsupportedOperationException();
}
@Override
ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
throws PackageManagerException {
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
"Device doesn't support updating APEX");
}
@Override
void markStagedSessionReady(int sessionId) {
throw new UnsupportedOperationException();
}
@Override
void markStagedSessionSuccessful(int sessionId) {
throw new UnsupportedOperationException();
}
@Override
boolean isApexSupported() {
return false;
}
@Override
boolean abortActiveSession() {
throw new UnsupportedOperationException();
}
@Override
boolean uninstallApex(String apexPackagePath) {
throw new UnsupportedOperationException();
}
@Override
void dump(PrintWriter pw, String packageName) {
// No-op
}
}
}

View File

@@ -2490,7 +2490,8 @@ public class PackageManagerService extends IPackageManager.Stub
mProtectedPackages = new ProtectedPackages(mContext);
mApexManager = new ApexManager(context);
mApexManager = ApexManager.create(context);
// CHECKSTYLE:OFF IndentationCheck
synchronized (mInstallLock) {
// writer
synchronized (mPackages) {

View File

@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.SessionInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageParser;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
@@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor;
import android.os.PowerManager;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.util.IntArray;
import android.util.Slog;
import android.util.SparseArray;
import android.util.apk.ApkSignatureVerifier;
@@ -104,21 +106,22 @@ public class StagingManager {
return new ParceledListSlice<>(result);
}
private boolean validateApexSignature(String apexPath, String apexModuleName) {
private void validateApexSignature(String apexPath, String packageName)
throws PackageManagerException {
final SigningDetails signingDetails;
try {
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
return false;
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to parse APEX package " + apexPath, e);
}
final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(apexModuleName);
final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
if (packageInfo == null) {
// Don't allow installation of new APEX.
Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting");
return false;
// This should never happen, because submitSessionToApexService ensures that no new
// apexes were installed.
throw new IllegalStateException("Unknown apex package " + packageName);
}
final SigningDetails existingSigningDetails;
@@ -126,73 +129,99 @@ public class StagingManager {
existingSigningDetails = ApkSignatureVerifier.verify(
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
} catch (PackageParserException e) {
Slog.e(TAG, "Unable to parse APEX package: "
+ packageInfo.applicationInfo.sourceDir, e);
return false;
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e);
}
// Now that we have both sets of signatures, demand that they're an exact match.
if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
return true;
return;
}
return false;
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APK-container signature verification failed for package "
+ packageName + ". Signature of file "
+ apexPath + " does not match the signature of "
+ " the package already installed.");
}
private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
List<PackageInstallerSession> childSessions,
ApexInfoList apexInfoList) {
boolean submittedToApexd = mApexManager.submitStagedSession(
session.sessionId,
childSessions != null
? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
new int[]{},
apexInfoList);
if (!submittedToApexd) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APEX staging failed, check logcat messages from apexd for more details.");
return false;
}
for (ApexInfo newModule : apexInfoList.apexInfos) {
PackageInfo activePackage = mApexManager.getPackageInfoForApexName(
newModule.moduleName);
if (activePackage == null) {
continue;
private List<PackageInfo> submitSessionToApexService(
@NonNull PackageInstallerSession session) throws PackageManagerException {
final IntArray childSessionsIds = new IntArray();
if (session.isMultiPackage()) {
for (int id : session.getChildSessionIds()) {
if (isApexSession(mStagedSessions.get(id))) {
childSessionsIds.add(id);
}
}
long activeVersion = activePackage.applicationInfo.longVersionCode;
if (session.params.requiredInstalledVersionCode
!= PackageManager.VERSION_CODE_HIGHEST) {
if (activeVersion != session.params.requiredInstalledVersionCode) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Installed version of APEX package " + activePackage.packageName
}
// submitStagedSession will throw a PackageManagerException if apexd verification fails,
// which will be propagated to populate stagedSessionErrorMessage of this session.
final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
childSessionsIds.toArray());
final List<PackageInfo> result = new ArrayList<>();
for (ApexInfo apexInfo : apexInfoList.apexInfos) {
final PackageInfo packageInfo;
int flags = PackageManager.GET_META_DATA;
PackageParser.Package pkg;
try {
File apexFile = new File(apexInfo.modulePath);
PackageParser pp = new PackageParser();
pkg = pp.parsePackage(apexFile, flags, false);
} catch (PackageParserException e) {
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to parse APEX package " + apexInfo.modulePath, e);
}
packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags);
final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
ApexManager.MATCH_ACTIVE_PACKAGE);
if (activePackage == null) {
Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"It is forbidden to install new APEX packages.");
}
checkRequiredVersionCode(session, activePackage);
checkDowngrade(session, activePackage, packageInfo);
result.add(packageInfo);
}
return result;
}
private void checkRequiredVersionCode(final PackageInstallerSession session,
final PackageInfo activePackage) throws PackageManagerException {
if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
return;
}
final long activeVersion = activePackage.applicationInfo.longVersionCode;
if (activeVersion != session.params.requiredInstalledVersionCode) {
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
throw new PackageManagerException(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Installed version of APEX package " + activePackage.packageName
+ " does not match required. Active version: " + activeVersion
+ " required: " + session.params.requiredInstalledVersionCode);
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
return false;
}
}
boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
session.params.installFlags, activePackage.applicationInfo.flags);
if (activeVersion > newModule.versionCode && !allowsDowngrade) {
session.setStagedSessionFailed(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Downgrade of APEX package " + activePackage.packageName
+ " is not allowed. Active version: " + activeVersion
+ " attempted: " + newModule.versionCode);
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
return false;
}
}
return true;
}
private void checkDowngrade(final PackageInstallerSession session,
final PackageInfo activePackage, final PackageInfo newPackage)
throws PackageManagerException {
final long activeVersion = activePackage.applicationInfo.longVersionCode;
final long newVersionCode = newPackage.applicationInfo.longVersionCode;
boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
session.params.installFlags, activePackage.applicationInfo.flags);
if (activeVersion > newVersionCode && !allowsDowngrade) {
if (!mApexManager.abortActiveSession()) {
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
}
throw new PackageManagerException(
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Downgrade of APEX package " + newPackage.packageName
+ " is not allowed. Active version: " + activeVersion
+ " attempted: " + newVersionCode);
}
}
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
@@ -200,31 +229,20 @@ public class StagingManager {
}
private void preRebootVerification(@NonNull PackageInstallerSession session) {
boolean success = true;
final ApexInfoList apexInfoList = new ApexInfoList();
final boolean hasApex = sessionContainsApex(session);
// APEX checks. For single-package sessions, check if they contain an APEX. For
// multi-package sessions, find all the child sessions that contain an APEX.
if (!session.isMultiPackage()
&& isApexSession(session)) {
success = submitSessionToApexService(session, null, apexInfoList);
} else if (session.isMultiPackage()) {
List<PackageInstallerSession> childSessions =
Arrays.stream(session.getChildSessionIds())
// Retrieve cached sessions matching ids.
.mapToObj(i -> mStagedSessions.get(i))
// Filter only the ones containing APEX.
.filter(childSession -> isApexSession(childSession))
.collect(Collectors.toList());
if (!childSessions.isEmpty()) {
success = submitSessionToApexService(session, childSessions, apexInfoList);
} // else this is a staged multi-package session with no APEX files.
}
if (!success) {
// submitSessionToApexService will populate error.
return;
if (hasApex) {
try {
final List<PackageInfo> apexPackages = submitSessionToApexService(session);
for (PackageInfo apexPackage : apexPackages) {
validateApexSignature(apexPackage.applicationInfo.sourceDir,
apexPackage.packageName);
}
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
return;
}
}
if (sessionContainsApk(session)) {
@@ -237,25 +255,6 @@ public class StagingManager {
}
}
if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
// For APEXes, we validate the signature here before we mark the session as ready,
// so we fail the session early if there is a signature mismatch. For APKs, the
// signature verification will be done by the package manager at the point at which
// it applies the staged install.
for (ApexInfo apexModule : apexInfoList.apexInfos) {
if (!validateApexSignature(apexModule.modulePath,
apexModule.moduleName)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APK-container signature verification failed for package "
+ apexModule.moduleName + ". Signature of file "
+ apexModule.modulePath + " does not match the signature of "
+ " the package already installed.");
// TODO(b/118865310): abort the session on apexd.
return;
}
}
}
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
// If rollback is enabled for this session, we call through to the RollbackManager
// with the list of sessions it must enable rollback for. Note that notifyStagedSession
@@ -273,12 +272,24 @@ public class StagingManager {
}
}
// Proactively mark session as ready before calling apexd. Although this call order looks
// counter-intuitive, this is the easiest way to ensure that session won't end up in the
// inconsistent state:
// - If device gets rebooted right before call to apexd, then apexd will never activate
// apex files of this staged session. This will result in StagingManager failing the
// session.
// On the other hand, if the order of the calls was inverted (first call apexd, then mark
// session as ready), then if a device gets rebooted right after the call to apexd, only
// apex part of the train will be applied, leaving device in an inconsistent state.
session.setStagedSessionReady();
if (sessionContainsApex(session)
&& !mApexManager.markStagedSessionReady(session.sessionId)) {
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"APEX staging failed, check logcat messages from apexd for more "
+ "details.");
if (!hasApex) {
// Session doesn't contain apex, nothing to do.
return;
}
try {
mApexManager.markStagedSessionReady(session.sessionId);
} catch (PackageManagerException e) {
session.setStagedSessionFailed(e.error, e.getMessage());
}
}