Merge changes from topic "apex_migration"
* changes: Clean up generatePackageInfoFromApex() API Populate error message if apexd.markStagedSessionReady fails Populate error message if apexd verification fails Make ApexManager an abstract class Don't use ApexInfo.packageName in StagingManager
This commit is contained in:
@@ -617,21 +617,6 @@ public class PackageParser {
|
||||
return path.endsWith(APK_FILE_EXTENSION);
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate and return the {@link PackageInfo} for a parsed package.
|
||||
*
|
||||
* @param p the parsed package.
|
||||
* @param flags indicating which optional information is included.
|
||||
*/
|
||||
@UnsupportedAppUsage
|
||||
public static PackageInfo generatePackageInfo(PackageParser.Package p,
|
||||
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
|
||||
Set<String> grantedPermissions, PackageUserState state) {
|
||||
|
||||
return generatePackageInfo(p, gids, flags, firstInstallTime, lastUpdateTime,
|
||||
grantedPermissions, state, UserHandle.getCallingUserId());
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns true if the package is installed and not hidden, or if the caller
|
||||
* explicitly wanted all uninstalled and hidden packages as well.
|
||||
@@ -658,8 +643,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)) {
|
||||
@@ -806,8 +828,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.
|
||||
@@ -8379,61 +8420,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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
|
||||
@@ -11,7 +11,7 @@
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.s
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.pm;
|
||||
@@ -29,14 +29,12 @@ import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.content.pm.PackageParser.PackageParserException;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.os.ServiceManager.ServiceNotFoundException;
|
||||
import android.sysprop.ApexProperties;
|
||||
import android.util.ArrayMap;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.GuardedBy;
|
||||
@@ -56,117 +54,33 @@ import java.util.stream.Collectors;
|
||||
* ApexManager class handles communications with the apex service to perform operation and queries,
|
||||
* as well as providing caching to avoid unnecessary calls to the service.
|
||||
*/
|
||||
class ApexManager {
|
||||
static final String TAG = "ApexManager";
|
||||
private final IApexService mApexService;
|
||||
private final Context mContext;
|
||||
private final Object mLock = new Object();
|
||||
/**
|
||||
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
|
||||
* AndroidManifest.xml}
|
||||
*
|
||||
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
|
||||
* AndroidManifest.xml}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private List<PackageInfo> mAllPackagesCache;
|
||||
/**
|
||||
* A map from {@code apexName} to the {@Link PackageInfo} generated from the {@code
|
||||
* AndroidManifest.xml}.
|
||||
*
|
||||
* <p>Note that key of this map is {@code apexName} field which corresponds to the {@code name}
|
||||
* field of {@code apex_manifest.json}.
|
||||
*/
|
||||
// TODO(b/132324953): remove.
|
||||
@GuardedBy("mLock")
|
||||
private ArrayMap<String, PackageInfo> mApexNameToPackageInfoCache;
|
||||
abstract class ApexManager {
|
||||
|
||||
|
||||
ApexManager(Context context) {
|
||||
mContext = context;
|
||||
if (!isApexSupported()) {
|
||||
mApexService = null;
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mApexService = IApexService.Stub.asInterface(
|
||||
ServiceManager.getServiceOrThrow("apexservice"));
|
||||
} catch (ServiceNotFoundException e) {
|
||||
throw new IllegalStateException("Required service apexservice not available");
|
||||
}
|
||||
}
|
||||
private static final String TAG = "ApexManager";
|
||||
|
||||
static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
|
||||
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
|
||||
@IntDef(
|
||||
flag = true,
|
||||
prefix = { "MATCH_"},
|
||||
value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface PackageInfoFlags{}
|
||||
|
||||
void systemReady() {
|
||||
if (!isApexSupported()) return;
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
onBootCompleted();
|
||||
mContext.unregisterReceiver(this);
|
||||
}
|
||||
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
|
||||
}
|
||||
|
||||
private void populateAllPackagesCacheIfNeeded() {
|
||||
synchronized (mLock) {
|
||||
if (mAllPackagesCache != null) {
|
||||
return;
|
||||
}
|
||||
mApexNameToPackageInfoCache = new ArrayMap<>();
|
||||
/**
|
||||
* Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
|
||||
* on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
|
||||
* {@code true}.
|
||||
*/
|
||||
static ApexManager create(Context systemContext) {
|
||||
if (ApexProperties.updatable().orElse(false)) {
|
||||
try {
|
||||
mAllPackagesCache = new ArrayList<>();
|
||||
HashSet<String> activePackagesSet = new HashSet<>();
|
||||
HashSet<String> factoryPackagesSet = new HashSet<>();
|
||||
final ApexInfo[] allPkgs = mApexService.getAllPackages();
|
||||
for (ApexInfo ai : allPkgs) {
|
||||
// If the device is using flattened APEX, don't report any APEX
|
||||
// packages since they won't be managed or updated by PackageManager.
|
||||
if ((new File(ai.modulePath)).isDirectory()) {
|
||||
break;
|
||||
}
|
||||
try {
|
||||
final PackageInfo pkg = PackageParser.generatePackageInfoFromApex(
|
||||
ai, PackageManager.GET_META_DATA
|
||||
| PackageManager.GET_SIGNING_CERTIFICATES);
|
||||
mAllPackagesCache.add(pkg);
|
||||
if (ai.isActive) {
|
||||
if (activePackagesSet.contains(pkg.packageName)) {
|
||||
throw new IllegalStateException(
|
||||
"Two active packages have the same name: "
|
||||
+ pkg.packageName);
|
||||
}
|
||||
activePackagesSet.add(pkg.packageName);
|
||||
// TODO(b/132324953): remove.
|
||||
mApexNameToPackageInfoCache.put(ai.moduleName, pkg);
|
||||
}
|
||||
if (ai.isFactory) {
|
||||
if (factoryPackagesSet.contains(pkg.packageName)) {
|
||||
throw new IllegalStateException(
|
||||
"Two factory packages have the same name: "
|
||||
+ pkg.packageName);
|
||||
}
|
||||
factoryPackagesSet.add(pkg.packageName);
|
||||
}
|
||||
} catch (PackageParserException pe) {
|
||||
throw new IllegalStateException("Unable to parse: " + ai, pe);
|
||||
}
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
|
||||
throw new RuntimeException(re);
|
||||
return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface(
|
||||
ServiceManager.getServiceOrThrow("apexservice")));
|
||||
} catch (ServiceManager.ServiceNotFoundException e) {
|
||||
throw new IllegalStateException("Required service apexservice not available");
|
||||
}
|
||||
} else {
|
||||
return new ApexManagerNoOp();
|
||||
}
|
||||
}
|
||||
|
||||
abstract void systemReady();
|
||||
|
||||
/**
|
||||
* Retrieves information about an APEX package.
|
||||
*
|
||||
@@ -179,35 +93,8 @@ class ApexManager {
|
||||
* @return a PackageInfo object with the information about the package, or null if the package
|
||||
* is not found.
|
||||
*/
|
||||
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
|
||||
if (!isApexSupported()) return null;
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
|
||||
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
|
||||
for (PackageInfo packageInfo: mAllPackagesCache) {
|
||||
if (!packageInfo.packageName.equals(packageName)) {
|
||||
continue;
|
||||
}
|
||||
if ((!matchActive || isActive(packageInfo))
|
||||
&& (!matchFactory || isFactory(packageInfo))) {
|
||||
return packageInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a {@link PackageInfo} for an active APEX package keyed by it's {@code apexName}.
|
||||
*
|
||||
* @deprecated this API will soon be deleted, please don't depend on it.
|
||||
*/
|
||||
// TODO(b/132324953): delete.
|
||||
@Deprecated
|
||||
@Nullable PackageInfo getPackageInfoForApexName(String apexName) {
|
||||
if (!isApexSupported()) return null;
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mApexNameToPackageInfoCache.get(apexName);
|
||||
}
|
||||
@Nullable
|
||||
abstract PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags);
|
||||
|
||||
/**
|
||||
* Retrieves information about all active APEX packages.
|
||||
@@ -215,14 +102,7 @@ class ApexManager {
|
||||
* @return a List of PackageInfo object, each one containing information about a different
|
||||
* active package.
|
||||
*/
|
||||
List<PackageInfo> getActivePackages() {
|
||||
if (!isApexSupported()) return Collections.emptyList();
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> isActive(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
abstract List<PackageInfo> getActivePackages();
|
||||
|
||||
/**
|
||||
* Retrieves information about all active pre-installed APEX packages.
|
||||
@@ -230,14 +110,7 @@ class ApexManager {
|
||||
* @return a List of PackageInfo object, each one containing information about a different
|
||||
* active pre-installed package.
|
||||
*/
|
||||
List<PackageInfo> getFactoryPackages() {
|
||||
if (!isApexSupported()) return Collections.emptyList();
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> isFactory(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
abstract List<PackageInfo> getFactoryPackages();
|
||||
|
||||
/**
|
||||
* Retrieves information about all inactive APEX packages.
|
||||
@@ -245,14 +118,7 @@ class ApexManager {
|
||||
* @return a List of PackageInfo object, each one containing information about a different
|
||||
* inactive package.
|
||||
*/
|
||||
List<PackageInfo> getInactivePackages() {
|
||||
if (!isApexSupported()) return Collections.emptyList();
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> !isActive(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
abstract List<PackageInfo> getInactivePackages();
|
||||
|
||||
/**
|
||||
* Checks if {@code packageName} is an apex package.
|
||||
@@ -260,16 +126,7 @@ class ApexManager {
|
||||
* @param packageName package to check.
|
||||
* @return {@code true} if {@code packageName} is an apex package.
|
||||
*/
|
||||
boolean isApexPackage(String packageName) {
|
||||
if (!isApexSupported()) return false;
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
for (PackageInfo packageInfo : mAllPackagesCache) {
|
||||
if (packageInfo.packageName.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
abstract boolean isApexPackage(String packageName);
|
||||
|
||||
/**
|
||||
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
|
||||
@@ -278,19 +135,8 @@ class ApexManager {
|
||||
* @param sessionId the identifier of the session.
|
||||
* @return an ApexSessionInfo object, or null if the session is not known.
|
||||
*/
|
||||
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
|
||||
if (!isApexSupported()) return null;
|
||||
try {
|
||||
ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
|
||||
if (apexSessionInfo.isUnknown) {
|
||||
return null;
|
||||
}
|
||||
return apexSessionInfo;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
}
|
||||
}
|
||||
@Nullable
|
||||
abstract ApexSessionInfo getStagedSessionInfo(int sessionId);
|
||||
|
||||
/**
|
||||
* Submit a staged session to apex service. This causes the apex service to perform some initial
|
||||
@@ -302,47 +148,19 @@ class ApexManager {
|
||||
* @param childSessionIds if {@code sessionId} is a multi-package session, this should contain
|
||||
* an array of identifiers of all the child sessions. Otherwise it should
|
||||
* be an empty array.
|
||||
* @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
|
||||
* and will be filled with a list of {@link ApexInfo} objects, each of which
|
||||
* contains metadata about one of the packages being submitted as part of
|
||||
* the session.
|
||||
* @return whether the submission of the session was successful.
|
||||
* @throws PackageManagerException if call to apexd fails
|
||||
*/
|
||||
boolean submitStagedSession(
|
||||
int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
|
||||
if (!isApexSupported()) return false;
|
||||
try {
|
||||
mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
|
||||
return true;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "apexd verification failed", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
|
||||
throws PackageManagerException;
|
||||
|
||||
/**
|
||||
* Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
|
||||
* applied at next reboot.
|
||||
*
|
||||
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
|
||||
* @return true upon success, false if the session is unknown.
|
||||
* @throws PackageManagerException if call to apexd fails
|
||||
*/
|
||||
boolean markStagedSessionReady(int sessionId) {
|
||||
if (!isApexSupported()) return false;
|
||||
try {
|
||||
mApexService.markStagedSessionReady(sessionId);
|
||||
return true;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Failed to mark session " + sessionId + " ready", e);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
abstract void markStagedSessionReady(int sessionId) throws PackageManagerException;
|
||||
|
||||
/**
|
||||
* Marks a staged session as successful.
|
||||
@@ -352,44 +170,21 @@ class ApexManager {
|
||||
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
|
||||
* successful.
|
||||
*/
|
||||
void markStagedSessionSuccessful(int sessionId) {
|
||||
if (!isApexSupported()) return;
|
||||
try {
|
||||
mApexService.markStagedSessionSuccessful(sessionId);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
// It is fine to just log an exception in this case. APEXd will be able to recover in
|
||||
// case markStagedSessionSuccessful fails.
|
||||
Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
|
||||
}
|
||||
}
|
||||
abstract void markStagedSessionSuccessful(int sessionId);
|
||||
|
||||
/**
|
||||
* Whether the current device supports the management of APEX packages.
|
||||
*
|
||||
* @return true if APEX packages can be managed on this device, false otherwise.
|
||||
*/
|
||||
boolean isApexSupported() {
|
||||
return ApexProperties.updatable().orElse(false);
|
||||
}
|
||||
abstract boolean isApexSupported();
|
||||
|
||||
/**
|
||||
* Abandons the (only) active session previously submitted.
|
||||
*
|
||||
* @return {@code true} upon success, {@code false} if any remote exception occurs
|
||||
*/
|
||||
boolean abortActiveSession() {
|
||||
if (!isApexSupported()) return false;
|
||||
try {
|
||||
mApexService.abortActiveSession();
|
||||
return true;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
abstract boolean abortActiveSession();
|
||||
|
||||
/**
|
||||
* Uninstalls given {@code apexPackage}.
|
||||
@@ -399,64 +194,7 @@ class ApexManager {
|
||||
* @param apexPackagePath package to uninstall.
|
||||
* @return {@code true} upon successful uninstall, {@code false} otherwise.
|
||||
*/
|
||||
boolean uninstallApex(String apexPackagePath) {
|
||||
if (!isApexSupported()) return false;
|
||||
try {
|
||||
mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an APEX package is active or not.
|
||||
*
|
||||
* @param packageInfo the package to check
|
||||
* @return {@code true} if this package is active, {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isActive(PackageInfo packageInfo) {
|
||||
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the APEX package is pre-installed or not.
|
||||
*
|
||||
* @param packageInfo the package to check
|
||||
* @return {@code true} if this package is pre-installed, {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isFactory(PackageInfo packageInfo) {
|
||||
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump information about the packages contained in a particular cache
|
||||
* @param packagesCache the cache to print information about.
|
||||
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
|
||||
* information about that specific package will be dumped.
|
||||
* @param ipw the {@link IndentingPrintWriter} object to send information to.
|
||||
*/
|
||||
void dumpFromPackagesCache(
|
||||
List<PackageInfo> packagesCache,
|
||||
@Nullable String packageName,
|
||||
IndentingPrintWriter ipw) {
|
||||
ipw.println();
|
||||
ipw.increaseIndent();
|
||||
for (PackageInfo pi : packagesCache) {
|
||||
if (packageName != null && !packageName.equals(pi.packageName)) {
|
||||
continue;
|
||||
}
|
||||
ipw.println(pi.packageName);
|
||||
ipw.increaseIndent();
|
||||
ipw.println("Version: " + pi.versionCode);
|
||||
ipw.println("Path: " + pi.applicationInfo.sourceDir);
|
||||
ipw.println("IsActive: " + isActive(pi));
|
||||
ipw.println("IsFactory: " + isFactory(pi));
|
||||
ipw.decreaseIndent();
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
ipw.println();
|
||||
}
|
||||
abstract boolean uninstallApex(String apexPackagePath);
|
||||
|
||||
/**
|
||||
* Dumps various state information to the provided {@link PrintWriter} object.
|
||||
@@ -465,54 +203,419 @@ class ApexManager {
|
||||
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
|
||||
* information about that specific package will be dumped.
|
||||
*/
|
||||
void dump(PrintWriter pw, @Nullable String packageName) {
|
||||
if (!isApexSupported()) return;
|
||||
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
|
||||
try {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
ipw.println();
|
||||
ipw.println("Active APEX packages:");
|
||||
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
|
||||
ipw.println("Inactive APEX packages:");
|
||||
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
|
||||
ipw.println("Factory APEX packages:");
|
||||
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
|
||||
ipw.increaseIndent();
|
||||
ipw.println("APEX session state:");
|
||||
ipw.increaseIndent();
|
||||
final ApexSessionInfo[] sessions = mApexService.getSessions();
|
||||
for (ApexSessionInfo si : sessions) {
|
||||
ipw.println("Session ID: " + si.sessionId);
|
||||
ipw.increaseIndent();
|
||||
if (si.isUnknown) {
|
||||
ipw.println("State: UNKNOWN");
|
||||
} else if (si.isVerified) {
|
||||
ipw.println("State: VERIFIED");
|
||||
} else if (si.isStaged) {
|
||||
ipw.println("State: STAGED");
|
||||
} else if (si.isActivated) {
|
||||
ipw.println("State: ACTIVATED");
|
||||
} else if (si.isActivationFailed) {
|
||||
ipw.println("State: ACTIVATION FAILED");
|
||||
} else if (si.isSuccess) {
|
||||
ipw.println("State: SUCCESS");
|
||||
} else if (si.isRollbackInProgress) {
|
||||
ipw.println("State: ROLLBACK IN PROGRESS");
|
||||
} else if (si.isRolledBack) {
|
||||
ipw.println("State: ROLLED BACK");
|
||||
} else if (si.isRollbackFailed) {
|
||||
ipw.println("State: ROLLBACK FAILED");
|
||||
abstract void dump(PrintWriter pw, @Nullable String packageName);
|
||||
|
||||
@IntDef(
|
||||
flag = true,
|
||||
prefix = { "MATCH_"},
|
||||
value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@interface PackageInfoFlags{}
|
||||
|
||||
/**
|
||||
* An implementation of {@link ApexManager} that should be used in case device supports updating
|
||||
* APEX packages.
|
||||
*/
|
||||
private static class ApexManagerImpl extends ApexManager {
|
||||
private final IApexService mApexService;
|
||||
private final Context mContext;
|
||||
private final Object mLock = new Object();
|
||||
/**
|
||||
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
|
||||
* AndroidManifest.xml}
|
||||
*
|
||||
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
|
||||
* AndroidManifest.xml}.
|
||||
*/
|
||||
@GuardedBy("mLock")
|
||||
private List<PackageInfo> mAllPackagesCache;
|
||||
|
||||
ApexManagerImpl(Context context, IApexService apexService) {
|
||||
mContext = context;
|
||||
mApexService = apexService;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether an APEX package is active or not.
|
||||
*
|
||||
* @param packageInfo the package to check
|
||||
* @return {@code true} if this package is active, {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isActive(PackageInfo packageInfo) {
|
||||
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_INSTALLED) != 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the APEX package is pre-installed or not.
|
||||
*
|
||||
* @param packageInfo the package to check
|
||||
* @return {@code true} if this package is pre-installed, {@code false} otherwise.
|
||||
*/
|
||||
private static boolean isFactory(PackageInfo packageInfo) {
|
||||
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
void systemReady() {
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@Override
|
||||
public void onReceive(Context context, Intent intent) {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
mContext.unregisterReceiver(this);
|
||||
}
|
||||
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
|
||||
}
|
||||
|
||||
private void populateAllPackagesCacheIfNeeded() {
|
||||
synchronized (mLock) {
|
||||
if (mAllPackagesCache != null) {
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mAllPackagesCache = new ArrayList<>();
|
||||
HashSet<String> activePackagesSet = new HashSet<>();
|
||||
HashSet<String> factoryPackagesSet = new HashSet<>();
|
||||
final ApexInfo[] allPkgs = mApexService.getAllPackages();
|
||||
for (ApexInfo ai : allPkgs) {
|
||||
// If the device is using flattened APEX, don't report any APEX
|
||||
// packages since they won't be managed or updated by PackageManager.
|
||||
if ((new File(ai.modulePath)).isDirectory()) {
|
||||
break;
|
||||
}
|
||||
int flags = PackageManager.GET_META_DATA
|
||||
| PackageManager.GET_SIGNING_CERTIFICATES
|
||||
| PackageManager.GET_SIGNATURES;
|
||||
PackageParser.Package pkg;
|
||||
try {
|
||||
File apexFile = new File(ai.modulePath);
|
||||
PackageParser pp = new PackageParser();
|
||||
pkg = pp.parsePackage(apexFile, flags, false);
|
||||
PackageParser.collectCertificates(pkg, false);
|
||||
} catch (PackageParser.PackageParserException pe) {
|
||||
throw new IllegalStateException("Unable to parse: " + ai, pe);
|
||||
}
|
||||
|
||||
final PackageInfo packageInfo =
|
||||
PackageParser.generatePackageInfo(pkg, ai, flags);
|
||||
mAllPackagesCache.add(packageInfo);
|
||||
if (ai.isActive) {
|
||||
if (activePackagesSet.contains(packageInfo.packageName)) {
|
||||
throw new IllegalStateException(
|
||||
"Two active packages have the same name: "
|
||||
+ packageInfo.packageName);
|
||||
}
|
||||
activePackagesSet.add(packageInfo.packageName);
|
||||
}
|
||||
if (ai.isFactory) {
|
||||
if (factoryPackagesSet.contains(packageInfo.packageName)) {
|
||||
throw new IllegalStateException(
|
||||
"Two factory packages have the same name: "
|
||||
+ packageInfo.packageName);
|
||||
}
|
||||
factoryPackagesSet.add(packageInfo.packageName);
|
||||
}
|
||||
|
||||
}
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString());
|
||||
throw new RuntimeException(re);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
boolean matchActive = (flags & MATCH_ACTIVE_PACKAGE) != 0;
|
||||
boolean matchFactory = (flags & MATCH_FACTORY_PACKAGE) != 0;
|
||||
for (PackageInfo packageInfo: mAllPackagesCache) {
|
||||
if (!packageInfo.packageName.equals(packageName)) {
|
||||
continue;
|
||||
}
|
||||
if ((!matchActive || isActive(packageInfo))
|
||||
&& (!matchFactory || isFactory(packageInfo))) {
|
||||
return packageInfo;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getActivePackages() {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> isActive(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getFactoryPackages() {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> isFactory(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getInactivePackages() {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
return mAllPackagesCache
|
||||
.stream()
|
||||
.filter(item -> !isActive(item))
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isApexPackage(String packageName) {
|
||||
if (!isApexSupported()) return false;
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
for (PackageInfo packageInfo : mAllPackagesCache) {
|
||||
if (packageInfo.packageName.equals(packageName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
|
||||
try {
|
||||
ApexSessionInfo apexSessionInfo = mApexService.getStagedSessionInfo(sessionId);
|
||||
if (apexSessionInfo.isUnknown) {
|
||||
return null;
|
||||
}
|
||||
return apexSessionInfo;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
|
||||
throws PackageManagerException {
|
||||
try {
|
||||
final ApexInfoList apexInfoList = new ApexInfoList();
|
||||
mApexService.submitStagedSession(sessionId, childSessionIds, apexInfoList);
|
||||
return apexInfoList;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
throw new PackageManagerException(
|
||||
PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"apexd verification failed : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void markStagedSessionReady(int sessionId) throws PackageManagerException {
|
||||
try {
|
||||
mApexService.markStagedSessionReady(sessionId);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
throw new PackageManagerException(
|
||||
PackageInstaller.SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Failed to mark apexd session as ready : " + e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
void markStagedSessionSuccessful(int sessionId) {
|
||||
try {
|
||||
mApexService.markStagedSessionSuccessful(sessionId);
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
throw new RuntimeException(re);
|
||||
} catch (Exception e) {
|
||||
// It is fine to just log an exception in this case. APEXd will be able to recover
|
||||
// in case markStagedSessionSuccessful fails.
|
||||
Slog.e(TAG, "Failed to mark session " + sessionId + " as successful", e);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isApexSupported() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean abortActiveSession() {
|
||||
try {
|
||||
mApexService.abortActiveSession();
|
||||
return true;
|
||||
} catch (RemoteException re) {
|
||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean uninstallApex(String apexPackagePath) {
|
||||
try {
|
||||
mApexService.unstagePackages(Collections.singletonList(apexPackagePath));
|
||||
return true;
|
||||
} catch (Exception e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Dump information about the packages contained in a particular cache
|
||||
* @param packagesCache the cache to print information about.
|
||||
* @param packageName a {@link String} containing a package name, or {@code null}. If set,
|
||||
* only information about that specific package will be dumped.
|
||||
* @param ipw the {@link IndentingPrintWriter} object to send information to.
|
||||
*/
|
||||
void dumpFromPackagesCache(
|
||||
List<PackageInfo> packagesCache,
|
||||
@Nullable String packageName,
|
||||
IndentingPrintWriter ipw) {
|
||||
ipw.println();
|
||||
ipw.increaseIndent();
|
||||
for (PackageInfo pi : packagesCache) {
|
||||
if (packageName != null && !packageName.equals(pi.packageName)) {
|
||||
continue;
|
||||
}
|
||||
ipw.println(pi.packageName);
|
||||
ipw.increaseIndent();
|
||||
ipw.println("Version: " + pi.versionCode);
|
||||
ipw.println("Path: " + pi.applicationInfo.sourceDir);
|
||||
ipw.println("IsActive: " + isActive(pi));
|
||||
ipw.println("IsFactory: " + isFactory(pi));
|
||||
ipw.decreaseIndent();
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
} catch (RemoteException e) {
|
||||
ipw.println("Couldn't communicate with apexd.");
|
||||
ipw.println();
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter pw, @Nullable String packageName) {
|
||||
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
|
||||
try {
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
ipw.println();
|
||||
ipw.println("Active APEX packages:");
|
||||
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
|
||||
ipw.println("Inactive APEX packages:");
|
||||
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
|
||||
ipw.println("Factory APEX packages:");
|
||||
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
|
||||
ipw.increaseIndent();
|
||||
ipw.println("APEX session state:");
|
||||
ipw.increaseIndent();
|
||||
final ApexSessionInfo[] sessions = mApexService.getSessions();
|
||||
for (ApexSessionInfo si : sessions) {
|
||||
ipw.println("Session ID: " + si.sessionId);
|
||||
ipw.increaseIndent();
|
||||
if (si.isUnknown) {
|
||||
ipw.println("State: UNKNOWN");
|
||||
} else if (si.isVerified) {
|
||||
ipw.println("State: VERIFIED");
|
||||
} else if (si.isStaged) {
|
||||
ipw.println("State: STAGED");
|
||||
} else if (si.isActivated) {
|
||||
ipw.println("State: ACTIVATED");
|
||||
} else if (si.isActivationFailed) {
|
||||
ipw.println("State: ACTIVATION FAILED");
|
||||
} else if (si.isSuccess) {
|
||||
ipw.println("State: SUCCESS");
|
||||
} else if (si.isRollbackInProgress) {
|
||||
ipw.println("State: ROLLBACK IN PROGRESS");
|
||||
} else if (si.isRolledBack) {
|
||||
ipw.println("State: ROLLED BACK");
|
||||
} else if (si.isRollbackFailed) {
|
||||
ipw.println("State: ROLLBACK FAILED");
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
}
|
||||
ipw.decreaseIndent();
|
||||
} catch (RemoteException e) {
|
||||
ipw.println("Couldn't communicate with apexd.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void onBootCompleted() {
|
||||
if (!isApexSupported()) return;
|
||||
populateAllPackagesCacheIfNeeded();
|
||||
/**
|
||||
* An implementation of {@link ApexManager} that should be used in case device does not support
|
||||
* updating APEX packages.
|
||||
*/
|
||||
private static final class ApexManagerNoOp extends ApexManager {
|
||||
|
||||
@Override
|
||||
void systemReady() {
|
||||
// No-op
|
||||
}
|
||||
|
||||
@Override
|
||||
PackageInfo getPackageInfo(String packageName, int flags) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getActivePackages() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getFactoryPackages() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
List<PackageInfo> getInactivePackages() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isApexPackage(String packageName) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
ApexSessionInfo getStagedSessionInfo(int sessionId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
ApexInfoList submitStagedSession(int sessionId, int[] childSessionIds)
|
||||
throws PackageManagerException {
|
||||
throw new PackageManagerException(PackageManager.INSTALL_FAILED_INTERNAL_ERROR,
|
||||
"Device doesn't support updating APEX");
|
||||
}
|
||||
|
||||
@Override
|
||||
void markStagedSessionReady(int sessionId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
void markStagedSessionSuccessful(int sessionId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean isApexSupported() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean abortActiveSession() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
boolean uninstallApex(String apexPackagePath) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
void dump(PrintWriter pw, String packageName) {
|
||||
// No-op
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2474,7 +2474,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) {
|
||||
|
||||
@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageInstaller.SessionInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.content.pm.PackageParser.PackageParserException;
|
||||
import android.content.pm.PackageParser.SigningDetails;
|
||||
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
|
||||
@@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor;
|
||||
import android.os.PowerManager;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.util.IntArray;
|
||||
import android.util.Slog;
|
||||
import android.util.SparseArray;
|
||||
import android.util.apk.ApkSignatureVerifier;
|
||||
@@ -104,21 +106,22 @@ public class StagingManager {
|
||||
return new ParceledListSlice<>(result);
|
||||
}
|
||||
|
||||
private boolean validateApexSignature(String apexPath, String apexModuleName) {
|
||||
private void validateApexSignature(String apexPath, String packageName)
|
||||
throws PackageManagerException {
|
||||
final SigningDetails signingDetails;
|
||||
try {
|
||||
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
|
||||
} catch (PackageParserException e) {
|
||||
Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
|
||||
return false;
|
||||
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Failed to parse APEX package " + apexPath, e);
|
||||
}
|
||||
|
||||
final PackageInfo packageInfo = mApexManager.getPackageInfoForApexName(apexModuleName);
|
||||
|
||||
final PackageInfo packageInfo = mApexManager.getPackageInfo(packageName,
|
||||
ApexManager.MATCH_ACTIVE_PACKAGE);
|
||||
if (packageInfo == null) {
|
||||
// Don't allow installation of new APEX.
|
||||
Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting");
|
||||
return false;
|
||||
// This should never happen, because submitSessionToApexService ensures that no new
|
||||
// apexes were installed.
|
||||
throw new IllegalStateException("Unknown apex package " + packageName);
|
||||
}
|
||||
|
||||
final SigningDetails existingSigningDetails;
|
||||
@@ -126,73 +129,99 @@ public class StagingManager {
|
||||
existingSigningDetails = ApkSignatureVerifier.verify(
|
||||
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
|
||||
} catch (PackageParserException e) {
|
||||
Slog.e(TAG, "Unable to parse APEX package: "
|
||||
+ packageInfo.applicationInfo.sourceDir, e);
|
||||
return false;
|
||||
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e);
|
||||
}
|
||||
|
||||
// Now that we have both sets of signatures, demand that they're an exact match.
|
||||
if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
|
||||
return true;
|
||||
return;
|
||||
}
|
||||
|
||||
return false;
|
||||
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"APK-container signature verification failed for package "
|
||||
+ packageName + ". Signature of file "
|
||||
+ apexPath + " does not match the signature of "
|
||||
+ " the package already installed.");
|
||||
}
|
||||
|
||||
private boolean submitSessionToApexService(@NonNull PackageInstallerSession session,
|
||||
List<PackageInstallerSession> childSessions,
|
||||
ApexInfoList apexInfoList) {
|
||||
boolean submittedToApexd = mApexManager.submitStagedSession(
|
||||
session.sessionId,
|
||||
childSessions != null
|
||||
? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
|
||||
new int[]{},
|
||||
apexInfoList);
|
||||
if (!submittedToApexd) {
|
||||
session.setStagedSessionFailed(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"APEX staging failed, check logcat messages from apexd for more details.");
|
||||
return false;
|
||||
}
|
||||
for (ApexInfo newModule : apexInfoList.apexInfos) {
|
||||
PackageInfo activePackage = mApexManager.getPackageInfoForApexName(
|
||||
newModule.moduleName);
|
||||
if (activePackage == null) {
|
||||
continue;
|
||||
private List<PackageInfo> submitSessionToApexService(
|
||||
@NonNull PackageInstallerSession session) throws PackageManagerException {
|
||||
final IntArray childSessionsIds = new IntArray();
|
||||
if (session.isMultiPackage()) {
|
||||
for (int id : session.getChildSessionIds()) {
|
||||
if (isApexSession(mStagedSessions.get(id))) {
|
||||
childSessionsIds.add(id);
|
||||
}
|
||||
}
|
||||
long activeVersion = activePackage.applicationInfo.longVersionCode;
|
||||
if (session.params.requiredInstalledVersionCode
|
||||
!= PackageManager.VERSION_CODE_HIGHEST) {
|
||||
if (activeVersion != session.params.requiredInstalledVersionCode) {
|
||||
session.setStagedSessionFailed(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Installed version of APEX package " + activePackage.packageName
|
||||
}
|
||||
// submitStagedSession will throw a PackageManagerException if apexd verification fails,
|
||||
// which will be propagated to populate stagedSessionErrorMessage of this session.
|
||||
final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
|
||||
childSessionsIds.toArray());
|
||||
final List<PackageInfo> result = new ArrayList<>();
|
||||
for (ApexInfo apexInfo : apexInfoList.apexInfos) {
|
||||
final PackageInfo packageInfo;
|
||||
int flags = PackageManager.GET_META_DATA;
|
||||
PackageParser.Package pkg;
|
||||
try {
|
||||
File apexFile = new File(apexInfo.modulePath);
|
||||
PackageParser pp = new PackageParser();
|
||||
pkg = pp.parsePackage(apexFile, flags, false);
|
||||
} catch (PackageParserException e) {
|
||||
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Failed to parse APEX package " + apexInfo.modulePath, e);
|
||||
}
|
||||
packageInfo = PackageParser.generatePackageInfo(pkg, apexInfo, flags);
|
||||
final PackageInfo activePackage = mApexManager.getPackageInfo(packageInfo.packageName,
|
||||
ApexManager.MATCH_ACTIVE_PACKAGE);
|
||||
if (activePackage == null) {
|
||||
Slog.w(TAG, "Attempting to install new APEX package " + packageInfo.packageName);
|
||||
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"It is forbidden to install new APEX packages.");
|
||||
}
|
||||
checkRequiredVersionCode(session, activePackage);
|
||||
checkDowngrade(session, activePackage, packageInfo);
|
||||
result.add(packageInfo);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private void checkRequiredVersionCode(final PackageInstallerSession session,
|
||||
final PackageInfo activePackage) throws PackageManagerException {
|
||||
if (session.params.requiredInstalledVersionCode == PackageManager.VERSION_CODE_HIGHEST) {
|
||||
return;
|
||||
}
|
||||
final long activeVersion = activePackage.applicationInfo.longVersionCode;
|
||||
if (activeVersion != session.params.requiredInstalledVersionCode) {
|
||||
if (!mApexManager.abortActiveSession()) {
|
||||
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
|
||||
}
|
||||
throw new PackageManagerException(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Installed version of APEX package " + activePackage.packageName
|
||||
+ " does not match required. Active version: " + activeVersion
|
||||
+ " required: " + session.params.requiredInstalledVersionCode);
|
||||
|
||||
if (!mApexManager.abortActiveSession()) {
|
||||
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
|
||||
session.params.installFlags, activePackage.applicationInfo.flags);
|
||||
if (activeVersion > newModule.versionCode && !allowsDowngrade) {
|
||||
session.setStagedSessionFailed(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Downgrade of APEX package " + activePackage.packageName
|
||||
+ " is not allowed. Active version: " + activeVersion
|
||||
+ " attempted: " + newModule.versionCode);
|
||||
|
||||
if (!mApexManager.abortActiveSession()) {
|
||||
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
private void checkDowngrade(final PackageInstallerSession session,
|
||||
final PackageInfo activePackage, final PackageInfo newPackage)
|
||||
throws PackageManagerException {
|
||||
final long activeVersion = activePackage.applicationInfo.longVersionCode;
|
||||
final long newVersionCode = newPackage.applicationInfo.longVersionCode;
|
||||
boolean allowsDowngrade = PackageManagerServiceUtils.isDowngradePermitted(
|
||||
session.params.installFlags, activePackage.applicationInfo.flags);
|
||||
if (activeVersion > newVersionCode && !allowsDowngrade) {
|
||||
if (!mApexManager.abortActiveSession()) {
|
||||
Slog.e(TAG, "Failed to abort apex session " + session.sessionId);
|
||||
}
|
||||
throw new PackageManagerException(
|
||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"Downgrade of APEX package " + newPackage.packageName
|
||||
+ " is not allowed. Active version: " + activeVersion
|
||||
+ " attempted: " + newVersionCode);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
|
||||
@@ -200,31 +229,20 @@ public class StagingManager {
|
||||
}
|
||||
|
||||
private void preRebootVerification(@NonNull PackageInstallerSession session) {
|
||||
boolean success = true;
|
||||
|
||||
final ApexInfoList apexInfoList = new ApexInfoList();
|
||||
final boolean hasApex = sessionContainsApex(session);
|
||||
// APEX checks. For single-package sessions, check if they contain an APEX. For
|
||||
// multi-package sessions, find all the child sessions that contain an APEX.
|
||||
if (!session.isMultiPackage()
|
||||
&& isApexSession(session)) {
|
||||
success = submitSessionToApexService(session, null, apexInfoList);
|
||||
|
||||
} else if (session.isMultiPackage()) {
|
||||
List<PackageInstallerSession> childSessions =
|
||||
Arrays.stream(session.getChildSessionIds())
|
||||
// Retrieve cached sessions matching ids.
|
||||
.mapToObj(i -> mStagedSessions.get(i))
|
||||
// Filter only the ones containing APEX.
|
||||
.filter(childSession -> isApexSession(childSession))
|
||||
.collect(Collectors.toList());
|
||||
if (!childSessions.isEmpty()) {
|
||||
success = submitSessionToApexService(session, childSessions, apexInfoList);
|
||||
} // else this is a staged multi-package session with no APEX files.
|
||||
}
|
||||
|
||||
if (!success) {
|
||||
// submitSessionToApexService will populate error.
|
||||
return;
|
||||
if (hasApex) {
|
||||
try {
|
||||
final List<PackageInfo> apexPackages = submitSessionToApexService(session);
|
||||
for (PackageInfo apexPackage : apexPackages) {
|
||||
validateApexSignature(apexPackage.applicationInfo.sourceDir,
|
||||
apexPackage.packageName);
|
||||
}
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (sessionContainsApk(session)) {
|
||||
@@ -237,25 +255,6 @@ public class StagingManager {
|
||||
}
|
||||
}
|
||||
|
||||
if (apexInfoList.apexInfos != null && apexInfoList.apexInfos.length > 0) {
|
||||
// For APEXes, we validate the signature here before we mark the session as ready,
|
||||
// so we fail the session early if there is a signature mismatch. For APKs, the
|
||||
// signature verification will be done by the package manager at the point at which
|
||||
// it applies the staged install.
|
||||
for (ApexInfo apexModule : apexInfoList.apexInfos) {
|
||||
if (!validateApexSignature(apexModule.modulePath,
|
||||
apexModule.moduleName)) {
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"APK-container signature verification failed for package "
|
||||
+ apexModule.moduleName + ". Signature of file "
|
||||
+ apexModule.modulePath + " does not match the signature of "
|
||||
+ " the package already installed.");
|
||||
// TODO(b/118865310): abort the session on apexd.
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
|
||||
// If rollback is enabled for this session, we call through to the RollbackManager
|
||||
// with the list of sessions it must enable rollback for. Note that notifyStagedSession
|
||||
@@ -273,12 +272,24 @@ public class StagingManager {
|
||||
}
|
||||
}
|
||||
|
||||
// Proactively mark session as ready before calling apexd. Although this call order looks
|
||||
// counter-intuitive, this is the easiest way to ensure that session won't end up in the
|
||||
// inconsistent state:
|
||||
// - If device gets rebooted right before call to apexd, then apexd will never activate
|
||||
// apex files of this staged session. This will result in StagingManager failing the
|
||||
// session.
|
||||
// On the other hand, if the order of the calls was inverted (first call apexd, then mark
|
||||
// session as ready), then if a device gets rebooted right after the call to apexd, only
|
||||
// apex part of the train will be applied, leaving device in an inconsistent state.
|
||||
session.setStagedSessionReady();
|
||||
if (sessionContainsApex(session)
|
||||
&& !mApexManager.markStagedSessionReady(session.sessionId)) {
|
||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||
"APEX staging failed, check logcat messages from apexd for more "
|
||||
+ "details.");
|
||||
if (!hasApex) {
|
||||
// Session doesn't contain apex, nothing to do.
|
||||
return;
|
||||
}
|
||||
try {
|
||||
mApexManager.markStagedSessionReady(session.sessionId);
|
||||
} catch (PackageManagerException e) {
|
||||
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user