diff options
5 files changed, 620 insertions, 516 deletions
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java index 0aff881ad6db..a81d7ec915d4 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -620,21 +620,6 @@ public class PackageParser { } /** - * 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. * @param appInfo The applicationInfo of the app being checked. @@ -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; - } } diff --git a/core/tests/coretests/src/android/content/pm/PackageParserTest.java b/core/tests/coretests/src/android/content/pm/PackageParserTest.java index 58c43ac2cf91..5e41355601e4 100644 --- a/core/tests/coretests/src/android/content/pm/PackageParserTest.java +++ b/core/tests/coretests/src/android/content/pm/PackageParserTest.java @@ -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); diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index a0c00f8c9b69..a1b6d492a4a3 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -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,120 +194,428 @@ 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; - } - } + abstract boolean uninstallApex(String apexPackagePath); /** - * Whether an APEX package is active or not. + * Dumps various state information to the provided {@link PrintWriter} object. * - * @param packageInfo the package to check - * @return {@code true} if this package is active, {@code false} otherwise. + * @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. */ - private static boolean isActive(PackageInfo packageInfo) { - return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0; - } + abstract void dump(PrintWriter pw, @Nullable 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. - */ - private static boolean isFactory(PackageInfo packageInfo) { - return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0; - } + @IntDef( + flag = true, + prefix = { "MATCH_"}, + value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE}) + @Retention(RetentionPolicy.SOURCE) + @interface PackageInfoFlags{} /** - * 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. + * An implementation of {@link ApexManager} that should be used in case device supports updating + * APEX packages. */ - 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; + 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); + } } - 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(); - } - /** - * 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. - */ - void dump(PrintWriter pw, @Nullable String packageName) { - if (!isApexSupported()) return; - final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120); - try { + @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.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"); + 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 + } } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index 953d8d3e2cce..fa465fab7377 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -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) { diff --git a/services/core/java/com/android/server/pm/StagingManager.java b/services/core/java/com/android/server/pm/StagingManager.java index 88d681f973c4..2705455078ff 100644 --- a/services/core/java/com/android/server/pm/StagingManager.java +++ b/services/core/java/com/android/server/pm/StagingManager.java @@ -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; + 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); + } + } } - for (ApexInfo newModule : apexInfoList.apexInfos) { - PackageInfo activePackage = mApexManager.getPackageInfoForApexName( - newModule.moduleName); + // 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) { - continue; - } - 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 - + " 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; - } + 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; + } - 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); + 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; + 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); } - return true; } 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()); } } |