blob: d9d949154ce1bd9b1968ec76906a30405a008599 [file] [log] [blame]
/*
* Copyright (C) 2019 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.android.server.pm;
import static android.os.Trace.TRACE_TAG_PACKAGE_MANAGER;
import android.annotation.IntDef;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.apex.ApexInfo;
import android.apex.ApexInfoList;
import android.apex.ApexSessionInfo;
import android.apex.ApexSessionParams;
import android.apex.IApexService;
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.content.pm.parsing.PackageInfoWithoutStateUtils;
import android.os.Binder;
import android.os.Environment;
import android.os.RemoteException;
import android.os.ServiceManager;
import android.os.Trace;
import android.sysprop.ApexProperties;
import android.util.ArrayMap;
import android.util.ArraySet;
import android.util.Singleton;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
import com.android.internal.util.IndentingPrintWriter;
import com.android.internal.util.Preconditions;
import com.android.server.pm.parsing.PackageParser2;
import com.android.server.pm.parsing.pkg.AndroidPackage;
import com.android.server.utils.TimingsTraceAndSlog;
import com.google.android.collect.Lists;
import java.io.File;
import java.io.PrintWriter;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.concurrent.ExecutorService;
/**
* 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.
*/
public abstract class ApexManager {
private static final String TAG = "ApexManager";
public static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
private static final Singleton<ApexManager> sApexManagerSingleton =
new Singleton<ApexManager>() {
@Override
protected ApexManager create() {
if (ApexProperties.updatable().orElse(false)) {
return new ApexManagerImpl();
} else {
return new ApexManagerFlattenedApex();
}
}
};
/**
* Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
* depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
* evaluates to {@code true}.
* @hide
*/
public static ApexManager getInstance() {
return sApexManagerSingleton.get();
}
/**
* Minimal information about APEX mount points and the original APEX package they refer to.
* @hide
*/
public static class ActiveApexInfo {
@Nullable public final String apexModuleName;
public final File apexDirectory;
public final File preInstalledApexPath;
private ActiveApexInfo(File apexDirectory, File preInstalledApexPath) {
this(null, apexDirectory, preInstalledApexPath);
}
private ActiveApexInfo(@Nullable String apexModuleName, File apexDirectory,
File preInstalledApexPath) {
this.apexModuleName = apexModuleName;
this.apexDirectory = apexDirectory;
this.preInstalledApexPath = preInstalledApexPath;
}
private ActiveApexInfo(ApexInfo apexInfo) {
this(
apexInfo.moduleName,
new File(Environment.getApexDirectory() + File.separator
+ apexInfo.moduleName),
new File(apexInfo.preinstalledModulePath));
}
}
/**
* Returns {@link ActiveApexInfo} records relative to all active APEX packages.
*
* @hide
*/
public abstract List<ActiveApexInfo> getActiveApexInfos();
/**
* Called by package manager service to scan apex package files when device boots up.
*
* @param packageParser The package parser to support apex package parsing and caching parsed
* results.
* @param executorService An executor to support parallel package parsing.
*/
abstract void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService);
/**
* Retrieves information about an APEX package.
*
* @param packageName the package name to look for. Note that this is the package name reported
* in the APK container manifest (i.e. AndroidManifest.xml), which might
* differ from the one reported in the APEX manifest (i.e.
* apex_manifest.json).
* @param flags the type of package to return. This may match to active packages
* and factory (pre-installed) packages.
* @return a PackageInfo object with the information about the package, or null if the package
* is not found.
*/
@Nullable
public abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags);
/**
* Retrieves information about all active APEX packages.
*
* @return a List of PackageInfo object, each one containing information about a different
* active package.
*/
abstract List<PackageInfo> getActivePackages();
/**
* Retrieves information about all active pre-installed APEX packages.
*
* @return a List of PackageInfo object, each one containing information about a different
* active pre-installed package.
*/
abstract List<PackageInfo> getFactoryPackages();
/**
* Retrieves information about all inactive APEX packages.
*
* @return a List of PackageInfo object, each one containing information about a different
* inactive package.
*/
abstract List<PackageInfo> getInactivePackages();
/**
* Checks if {@code packageName} is an apex package.
*
* @param packageName package to check.
* @return {@code true} if {@code packageName} is an apex package.
*/
abstract boolean isApexPackage(String packageName);
/**
* 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.
*/
public static boolean isFactory(@NonNull PackageInfo packageInfo) {
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
}
/**
* Returns the active apex package's name that contains the (apk) package.
*
* @param containedPackage The (apk) package that might be in a apex
* @return the apex package's name of {@code null} if the {@code containedPackage} is not inside
* any apex.
*/
@Nullable
public abstract String getActiveApexPackageNameContainingPackage(
@NonNull AndroidPackage containedPackage);
/**
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
* track the different states of a session.
*
* @param sessionId the identifier of the session.
* @return an ApexSessionInfo object, or null if the session is not known.
*/
@Nullable
abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
/**
* Submit a staged session to apex service. This causes the apex service to perform some initial
* verification and accept or reject the session. Submitting a session successfully is not
* enough for it to be activated at the next boot, the caller needs to call
* {@link #markStagedSessionReady(int)}.
*
* @throws PackageManagerException if call to apexd fails
*/
abstract ApexInfoList submitStagedSession(ApexSessionParams params)
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.
* @throws PackageManagerException if call to apexd fails
*/
abstract void markStagedSessionReady(int sessionId) throws PackageManagerException;
/**
* Marks a staged session as successful.
*
* <p>Only activated session can be marked as successful.
*
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
* successful.
*/
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.
*/
abstract boolean isApexSupported();
/**
* Abandons the (only) active session previously submitted.
*
* @return {@code true} upon success, {@code false} if any remote exception occurs
*/
abstract boolean revertActiveSessions();
/**
* Abandons the staged session with the given sessionId.
*
* @return {@code true} upon success, {@code false} if any remote exception occurs
*/
abstract boolean abortStagedSession(int sessionId) throws PackageManagerException;
/**
* Uninstalls given {@code apexPackage}.
*
* <p>NOTE. Device must be rebooted in order for uninstall to take effect.
*
* @param apexPackagePath package to uninstall.
* @return {@code true} upon successful uninstall, {@code false} otherwise.
*/
abstract boolean uninstallApex(String apexPackagePath);
/**
* Registers an APK package as an embedded apk of apex.
*/
abstract void registerApkInApex(AndroidPackage pkg);
/**
* Reports error raised during installation of apk-in-apex.
*
* @param scanDir the directory of the apex inside which apk-in-apex resides.
*/
abstract void reportErrorWithApkInApex(String scanDirPath);
/**
* Returns true if there were no errors when installing apk-in-apex inside
* {@param apexPackageName}, otherwise false.
*
* @param apexPackageName Package name of the apk container of apex
*/
abstract boolean isApkInApexInstallSuccess(String apexPackageName);
/**
* Returns list of {@code packageName} of apks inside the given apex.
* @param apexPackageName Package name of the apk container of apex
*/
abstract List<String> getApksInApex(String apexPackageName);
/**
* Returns the apex module name for the given package name, if the package is an APEX. Otherwise
* returns {@code null}.
*/
@Nullable
public abstract String getApexModuleNameForPackageName(String apexPackageName);
/**
* Copies the CE apex data directory for the given {@code userId} to a backup location, for use
* in case of rollback.
*
* @return long inode for the snapshot directory if the snapshot was successful, or -1 if not
*/
public abstract long snapshotCeData(int userId, int rollbackId, String apexPackageName);
/**
* Restores the snapshot of the CE apex data directory for the given {@code userId}.
* Note the snapshot will be deleted after restoration succeeded.
*
* @return boolean true if the restore was successful
*/
public abstract boolean restoreCeData(int userId, int rollbackId, String apexPackageName);
/**
* Deletes snapshots of the device encrypted apex data directories for the given
* {@code rollbackId}.
*
* @return boolean true if the delete was successful
*/
public abstract boolean destroyDeSnapshots(int rollbackId);
/**
* Deletes snapshots of the credential encrypted apex data directories for the specified user,
* where the rollback id is not included in {@code retainRollbackIds}.
*
* @return boolean true if the delete was successful
*/
public abstract boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds);
/**
* Dumps various state information to the provided {@link PrintWriter} object.
*
* @param pw the {@link PrintWriter} object to send information to.
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
* information about that specific package will be dumped.
*/
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.
*/
@VisibleForTesting
protected static class ApexManagerImpl extends ApexManager {
private final Object mLock = new Object();
@GuardedBy("mLock")
private Set<ActiveApexInfo> mActiveApexInfosCache;
/**
* Contains the list of {@code packageName}s of apks-in-apex for given
* {@code apexModuleName}. See {@link #mPackageNameToApexModuleName} to understand the
* difference between {@code packageName} and {@code apexModuleName}.
*/
@GuardedBy("mLock")
private ArrayMap<String, List<String>> mApksInApex = new ArrayMap<>();
/**
* Contains the list of {@code Exception}s that were raised when installing apk-in-apex
* inside {@code apexModuleName}.
*/
@GuardedBy("mLock")
private Set<String> mErrorWithApkInApex = new ArraySet<>();
@GuardedBy("mLock")
private List<PackageInfo> mAllPackagesCache;
/**
* An APEX is a file format that delivers the apex-payload wrapped in an apk container. The
* apk container has a reference name, called {@code packageName}, which is found inside the
* {@code AndroidManifest.xml}. The apex payload inside the container also has a reference
* name, called {@code apexModuleName}, which is found in {@code apex_manifest.json} file.
*
* {@link #mPackageNameToApexModuleName} contains the mapping from {@code packageName} of
* the apk container to {@code apexModuleName} of the apex-payload inside.
*/
@GuardedBy("mLock")
private ArrayMap<String, String> mPackageNameToApexModuleName;
/**
* 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;
}
/**
* Retrieve the service from ServiceManager. If the service is not running, it will be
* started, and this function will block until it is ready.
*/
@VisibleForTesting
protected IApexService waitForApexService() {
// Since apexd is a trusted platform component, synchronized calls are allowable
return IApexService.Stub.asInterface(
Binder.allowBlocking(ServiceManager.waitForService("apexservice")));
}
@Override
public List<ActiveApexInfo> getActiveApexInfos() {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_APEX_MANAGER);
synchronized (mLock) {
if (mActiveApexInfosCache == null) {
t.traceBegin("getActiveApexInfos_noCache");
try {
mActiveApexInfosCache = new ArraySet<>();
final ApexInfo[] activePackages = waitForApexService().getActivePackages();
for (int i = 0; i < activePackages.length; i++) {
ApexInfo apexInfo = activePackages[i];
mActiveApexInfosCache.add(new ActiveApexInfo(apexInfo));
}
} catch (RemoteException e) {
Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
}
t.traceEnd();
}
if (mActiveApexInfosCache != null) {
return new ArrayList<>(mActiveApexInfosCache);
} else {
return Collections.emptyList();
}
}
}
@Override
void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService) {
Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "scanApexPackagesTraced");
try {
synchronized (mLock) {
scanApexPackagesInternalLocked(packageParser, executorService);
}
} finally {
Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
}
}
@GuardedBy("mLock")
private void scanApexPackagesInternalLocked(PackageParser2 packageParser,
ExecutorService executorService) {
final ApexInfo[] allPkgs;
try {
mAllPackagesCache = new ArrayList<>();
mPackageNameToApexModuleName = new ArrayMap<>();
allPkgs = waitForApexService().getAllPackages();
} catch (RemoteException re) {
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
throw new RuntimeException(re);
}
if (allPkgs.length == 0) {
return;
}
final int flags = PackageManager.GET_META_DATA
| PackageManager.GET_SIGNING_CERTIFICATES
| PackageManager.GET_SIGNATURES;
ArrayMap<File, ApexInfo> parsingApexInfo = new ArrayMap<>();
ParallelPackageParser parallelPackageParser =
new ParallelPackageParser(packageParser, executorService);
for (ApexInfo ai : allPkgs) {
File apexFile = new File(ai.modulePath);
parallelPackageParser.submit(apexFile, PackageParser.PARSE_COLLECT_CERTIFICATES);
parsingApexInfo.put(apexFile, ai);
}
HashSet<String> activePackagesSet = new HashSet<>();
HashSet<String> factoryPackagesSet = new HashSet<>();
// Process results one by one
for (int i = 0; i < parsingApexInfo.size(); i++) {
ParallelPackageParser.ParseResult parseResult = parallelPackageParser.take();
Throwable throwable = parseResult.throwable;
ApexInfo ai = parsingApexInfo.get(parseResult.scanFile);
if (throwable == null) {
final PackageInfo packageInfo = PackageInfoWithoutStateUtils.generate(
parseResult.parsedPackage, ai, flags);
if (packageInfo == null) {
throw new IllegalStateException("Unable to generate package info: "
+ ai.modulePath);
}
mAllPackagesCache.add(packageInfo);
mPackageNameToApexModuleName.put(packageInfo.packageName, ai.moduleName);
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);
}
} else if (throwable instanceof PackageParserException) {
final PackageParserException e = (PackageParserException) throwable;
// Skip parsing non-coreApp apex file if system is in minimal boot state.
if (e.error == PackageManager.INSTALL_PARSE_FAILED_ONLY_COREAPP_ALLOWED) {
Slog.w(TAG, "Scan apex failed, not a coreApp:" + ai.modulePath);
continue;
}
throw new IllegalStateException("Unable to parse: " + ai.modulePath, throwable);
} else {
throw new IllegalStateException("Unexpected exception occurred while parsing "
+ ai.modulePath, throwable);
}
}
}
@Override
@Nullable
public PackageInfo getPackageInfo(String packageName,
@PackageInfoFlags int flags) {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
final PackageInfo packageInfo = mAllPackagesCache.get(i);
if (!packageInfo.packageName.equals(packageName)) {
continue;
}
if ((matchActive && isActive(packageInfo))
|| (matchFactory && isFactory(packageInfo))) {
return packageInfo;
}
}
return null;
}
@Override
List<PackageInfo> getActivePackages() {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
final List<PackageInfo> activePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
final PackageInfo packageInfo = mAllPackagesCache.get(i);
if (isActive(packageInfo)) {
activePackages.add(packageInfo);
}
}
return activePackages;
}
@Override
List<PackageInfo> getFactoryPackages() {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
final List<PackageInfo> factoryPackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
final PackageInfo packageInfo = mAllPackagesCache.get(i);
if (isFactory(packageInfo)) {
factoryPackages.add(packageInfo);
}
}
return factoryPackages;
}
@Override
List<PackageInfo> getInactivePackages() {
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
final List<PackageInfo> inactivePackages = new ArrayList<>();
for (int i = 0; i < mAllPackagesCache.size(); i++) {
final PackageInfo packageInfo = mAllPackagesCache.get(i);
if (!isActive(packageInfo)) {
inactivePackages.add(packageInfo);
}
}
return inactivePackages;
}
@Override
boolean isApexPackage(String packageName) {
if (!isApexSupported()) return false;
Preconditions.checkState(mAllPackagesCache != null,
"APEX packages have not been scanned");
for (int i = 0, size = mAllPackagesCache.size(); i < size; i++) {
final PackageInfo packageInfo = mAllPackagesCache.get(i);
if (packageInfo.packageName.equals(packageName)) {
return true;
}
}
return false;
}
@Override
@Nullable
public String getActiveApexPackageNameContainingPackage(
@NonNull AndroidPackage containedPackage) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
Objects.requireNonNull(containedPackage);
synchronized (mLock) {
int numApksInApex = mApksInApex.size();
for (int apkInApexNum = 0; apkInApexNum < numApksInApex; apkInApexNum++) {
if (mApksInApex.valueAt(apkInApexNum).contains(
containedPackage.getPackageName())) {
String apexModuleName = mApksInApex.keyAt(apkInApexNum);
int numApexPkgs = mPackageNameToApexModuleName.size();
for (int apexPkgNum = 0; apexPkgNum < numApexPkgs; apexPkgNum++) {
if (mPackageNameToApexModuleName.valueAt(apexPkgNum).equals(
apexModuleName)) {
return mPackageNameToApexModuleName.keyAt(apexPkgNum);
}
}
}
}
}
return null;
}
@Override
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
try {
ApexSessionInfo apexSessionInfo =
waitForApexService().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(ApexSessionParams params) throws PackageManagerException {
try {
final ApexInfoList apexInfoList = new ApexInfoList();
waitForApexService().submitStagedSession(params, 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 {
waitForApexService().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 {
waitForApexService().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 revertActiveSessions() {
try {
waitForApexService().revertActiveSessions();
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), e);
return false;
}
}
@Override
boolean abortStagedSession(int sessionId) throws PackageManagerException {
try {
waitForApexService().abortStagedSession(sessionId);
return true;
} catch (RemoteException re) {
Slog.e(TAG, "Unable to contact apexservice", re);
return false;
} catch (Exception e) {
throw new PackageManagerException(
PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
"Failed to abort staged session : " + e.getMessage());
}
}
@Override
boolean uninstallApex(String apexPackagePath) {
try {
waitForApexService().unstagePackages(Collections.singletonList(apexPackagePath));
return true;
} catch (Exception e) {
return false;
}
}
@Override
void registerApkInApex(AndroidPackage pkg) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
if (pkg.getBaseCodePath().startsWith(aai.apexDirectory.getAbsolutePath())) {
List<String> apks = mApksInApex.get(aai.apexModuleName);
if (apks == null) {
apks = Lists.newArrayList();
mApksInApex.put(aai.apexModuleName, apks);
}
apks.add(pkg.getPackageName());
}
}
}
}
@Override
void reportErrorWithApkInApex(String scanDirPath) {
synchronized (mLock) {
for (ActiveApexInfo aai : mActiveApexInfosCache) {
if (scanDirPath.startsWith(aai.apexDirectory.getAbsolutePath())) {
mErrorWithApkInApex.add(aai.apexModuleName);
}
}
}
}
@Override
boolean isApkInApexInstallSuccess(String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
if (moduleName == null) {
return false;
}
return !mErrorWithApkInApex.contains(moduleName);
}
}
@Override
List<String> getApksInApex(String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
String moduleName = mPackageNameToApexModuleName.get(apexPackageName);
if (moduleName == null) {
return Collections.emptyList();
}
return mApksInApex.getOrDefault(moduleName, Collections.emptyList());
}
}
@Override
@Nullable
public String getApexModuleNameForPackageName(String apexPackageName) {
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
return mPackageNameToApexModuleName.get(apexPackageName);
}
}
@Override
public long snapshotCeData(int userId, int rollbackId, String apexPackageName) {
String apexModuleName;
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
}
if (apexModuleName == null) {
Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
return -1;
}
try {
return waitForApexService().snapshotCeData(userId, rollbackId, apexModuleName);
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), e);
return -1;
}
}
@Override
public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
String apexModuleName;
synchronized (mLock) {
Preconditions.checkState(mPackageNameToApexModuleName != null,
"APEX packages have not been scanned");
apexModuleName = mPackageNameToApexModuleName.get(apexPackageName);
}
if (apexModuleName == null) {
Slog.e(TAG, "Invalid apex package name: " + apexPackageName);
return false;
}
try {
waitForApexService().restoreCeData(userId, rollbackId, apexModuleName);
return true;
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), e);
return false;
}
}
@Override
public boolean destroyDeSnapshots(int rollbackId) {
try {
waitForApexService().destroyDeSnapshots(rollbackId);
return true;
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), e);
return false;
}
}
@Override
public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
try {
waitForApexService().destroyCeSnapshotsNotSpecified(userId, retainRollbackIds);
return true;
} catch (Exception e) {
Slog.e(TAG, e.getMessage(), 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 (int i = 0, size = packagesCache.size(); i < size; i++) {
final PackageInfo pi = packagesCache.get(i);
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();
}
@Override
void dump(PrintWriter pw, @Nullable String packageName) {
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
try {
ipw.println();
ipw.println("APEX session state:");
ipw.increaseIndent();
final ApexSessionInfo[] sessions = waitForApexService().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.isRevertInProgress) {
ipw.println("State: REVERT IN PROGRESS");
} else if (si.isReverted) {
ipw.println("State: REVERTED");
} else if (si.isRevertFailed) {
ipw.println("State: REVERT FAILED");
}
ipw.decreaseIndent();
}
ipw.decreaseIndent();
ipw.println();
if (mAllPackagesCache == null) {
ipw.println("APEX packages have not been scanned");
return;
}
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);
} catch (RemoteException e) {
ipw.println("Couldn't communicate with apexd.");
}
}
}
/**
* An implementation of {@link ApexManager} that should be used in case device does not support
* updating APEX packages.
*/
private static final class ApexManagerFlattenedApex extends ApexManager {
@Override
public List<ActiveApexInfo> getActiveApexInfos() {
// There is no apexd running in case of flattened apex
// We look up the /apex directory and identify the active APEX modules from there.
// As "preinstalled" path, we just report /system since in the case of flattened APEX
// the /apex directory is just a symlink to /system/apex.
List<ActiveApexInfo> result = new ArrayList<>();
File apexDir = Environment.getApexDirectory();
// In flattened configuration, init special-case the art directory and bind-mounts
// com.android.art.{release|debug} to com.android.art. At the time of writing, these
// directories are copied from the kArtApexDirNames variable in
// system/core/init/mount_namespace.cpp.
String[] skipDirs = {"com.android.art.release", "com.android.art.debug"};
if (apexDir.isDirectory()) {
File[] files = apexDir.listFiles();
// listFiles might be null if system server doesn't have permission to read
// a directory.
if (files != null) {
for (File file : files) {
if (file.isDirectory() && !file.getName().contains("@")) {
for (String skipDir : skipDirs) {
if (file.getName().equals(skipDir)) {
continue;
}
}
result.add(new ActiveApexInfo(file, Environment.getRootDirectory()));
}
}
}
}
return result;
}
@Override
void scanApexPackagesTraced(@NonNull PackageParser2 packageParser,
@NonNull ExecutorService executorService) {
// No-op
}
@Override
public 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
@Nullable
public String getActiveApexPackageNameContainingPackage(
@NonNull AndroidPackage containedPackage) {
Objects.requireNonNull(containedPackage);
return null;
}
@Override
ApexSessionInfo getStagedSessionInfo(int sessionId) {
throw new UnsupportedOperationException();
}
@Override
ApexInfoList submitStagedSession(ApexSessionParams params)
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 revertActiveSessions() {
throw new UnsupportedOperationException();
}
@Override
boolean abortStagedSession(int sessionId) throws PackageManagerException {
throw new UnsupportedOperationException();
}
@Override
boolean uninstallApex(String apexPackagePath) {
throw new UnsupportedOperationException();
}
@Override
void registerApkInApex(AndroidPackage pkg) {
// No-op
}
@Override
void reportErrorWithApkInApex(String scanDirPath) {
// No-op
}
@Override
boolean isApkInApexInstallSuccess(String apexPackageName) {
return true;
}
@Override
List<String> getApksInApex(String apexPackageName) {
return Collections.emptyList();
}
@Override
@Nullable
public String getApexModuleNameForPackageName(String apexPackageName) {
return null;
}
@Override
public long snapshotCeData(int userId, int rollbackId, String apexPackageName) {
throw new UnsupportedOperationException();
}
@Override
public boolean restoreCeData(int userId, int rollbackId, String apexPackageName) {
throw new UnsupportedOperationException();
}
@Override
public boolean destroyDeSnapshots(int rollbackId) {
throw new UnsupportedOperationException();
}
@Override
public boolean destroyCeSnapshotsNotSpecified(int userId, int[] retainRollbackIds) {
return true;
}
@Override
void dump(PrintWriter pw, String packageName) {
// No-op
}
}
}