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