summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--core/java/android/content/pm/PackageParser.java130
-rw-r--r--core/tests/coretests/src/android/content/pm/PackageParserTest.java7
-rw-r--r--services/core/java/com/android/server/pm/ApexManager.java771
-rw-r--r--services/core/java/com/android/server/pm/PackageManagerService.java3
-rw-r--r--services/core/java/com/android/server/pm/StagingManager.java225
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());
}
}