Merge changes from topic "apex_migration" am: 8eacf37ef3
am: 42a049c287
Change-Id: Ic951da75a0b4548fe30f583b5af59b811f6b3db5
This commit is contained in:
@@ -619,21 +619,6 @@ public class PackageParser {
|
|||||||
return path.endsWith(APK_FILE_EXTENSION);
|
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
|
* Returns true if the package is installed and not hidden, or if the caller
|
||||||
* explicitly wanted all uninstalled and hidden packages as well.
|
* explicitly wanted all uninstalled and hidden packages as well.
|
||||||
@@ -660,8 +645,45 @@ public class PackageParser {
|
|||||||
return checkUseInstalledOrHidden(0, state, null);
|
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
|
@UnsupportedAppUsage
|
||||||
public static PackageInfo generatePackageInfo(PackageParser.Package p,
|
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,
|
int gids[], int flags, long firstInstallTime, long lastUpdateTime,
|
||||||
Set<String> grantedPermissions, PackageUserState state, int userId) {
|
Set<String> grantedPermissions, PackageUserState state, int userId) {
|
||||||
if (!checkUseInstalledOrHidden(flags, state, p.applicationInfo) || !p.isMatch(flags)) {
|
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
|
// deprecated method of getting signing certificates
|
||||||
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
|
if ((flags & PackageManager.GET_SIGNATURES) != 0) {
|
||||||
if (p.mSigningDetails.hasPastSigningCertificates()) {
|
if (p.mSigningDetails.hasPastSigningCertificates()) {
|
||||||
// Package has included signing certificate rotation information. Return the oldest
|
// Package has included signing certificate rotation information. Return the oldest
|
||||||
// cert so that programmatic checks keep working even if unaware of key rotation.
|
// 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.modulePath = apexFile.getPath();
|
||||||
apexInfo.versionCode = 191000070;
|
apexInfo.versionCode = 191000070;
|
||||||
int flags = PackageManager.GET_META_DATA | PackageManager.GET_SIGNING_CERTIFICATES;
|
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);
|
assertEquals("com.google.android.tzdata", pi.applicationInfo.packageName);
|
||||||
assertTrue(pi.applicationInfo.enabled);
|
assertTrue(pi.applicationInfo.enabled);
|
||||||
assertEquals(28, pi.applicationInfo.targetSdkVersion);
|
assertEquals(28, pi.applicationInfo.targetSdkVersion);
|
||||||
|
|||||||
@@ -11,7 +11,7 @@
|
|||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.s
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.android.server.pm;
|
package com.android.server.pm;
|
||||||
@@ -29,14 +29,12 @@ import android.content.Intent;
|
|||||||
import android.content.IntentFilter;
|
import android.content.IntentFilter;
|
||||||
import android.content.pm.ApplicationInfo;
|
import android.content.pm.ApplicationInfo;
|
||||||
import android.content.pm.PackageInfo;
|
import android.content.pm.PackageInfo;
|
||||||
|
import android.content.pm.PackageInstaller;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.pm.PackageParser;
|
import android.content.pm.PackageParser;
|
||||||
import android.content.pm.PackageParser.PackageParserException;
|
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
import android.os.ServiceManager.ServiceNotFoundException;
|
|
||||||
import android.sysprop.ApexProperties;
|
import android.sysprop.ApexProperties;
|
||||||
import android.util.ArrayMap;
|
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
|
|
||||||
import com.android.internal.annotations.GuardedBy;
|
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,
|
* 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.
|
* as well as providing caching to avoid unnecessary calls to the service.
|
||||||
*/
|
*/
|
||||||
class ApexManager {
|
abstract 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;
|
|
||||||
|
|
||||||
|
private static final String TAG = "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");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
|
static final int MATCH_ACTIVE_PACKAGE = 1 << 0;
|
||||||
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
|
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;
|
* Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerNoOp} depending
|
||||||
mContext.registerReceiver(new BroadcastReceiver() {
|
* on whenever this device supports APEX, i.e. {@link ApexProperties#updatable()} evaluates to
|
||||||
@Override
|
* {@code true}.
|
||||||
public void onReceive(Context context, Intent intent) {
|
*/
|
||||||
onBootCompleted();
|
static ApexManager create(Context systemContext) {
|
||||||
mContext.unregisterReceiver(this);
|
if (ApexProperties.updatable().orElse(false)) {
|
||||||
}
|
|
||||||
}, new IntentFilter(Intent.ACTION_BOOT_COMPLETED));
|
|
||||||
}
|
|
||||||
|
|
||||||
private void populateAllPackagesCacheIfNeeded() {
|
|
||||||
synchronized (mLock) {
|
|
||||||
if (mAllPackagesCache != null) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
mApexNameToPackageInfoCache = new ArrayMap<>();
|
|
||||||
try {
|
try {
|
||||||
mAllPackagesCache = new ArrayList<>();
|
return new ApexManagerImpl(systemContext, IApexService.Stub.asInterface(
|
||||||
HashSet<String> activePackagesSet = new HashSet<>();
|
ServiceManager.getServiceOrThrow("apexservice")));
|
||||||
HashSet<String> factoryPackagesSet = new HashSet<>();
|
} catch (ServiceManager.ServiceNotFoundException e) {
|
||||||
final ApexInfo[] allPkgs = mApexService.getAllPackages();
|
throw new IllegalStateException("Required service apexservice not available");
|
||||||
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);
|
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
return new ApexManagerNoOp();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
abstract void systemReady();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about an APEX package.
|
* 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
|
* @return a PackageInfo object with the information about the package, or null if the package
|
||||||
* is not found.
|
* is not found.
|
||||||
*/
|
*/
|
||||||
@Nullable PackageInfo getPackageInfo(String packageName, @PackageInfoFlags int flags) {
|
@Nullable
|
||||||
if (!isApexSupported()) return null;
|
abstract 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;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about all active APEX packages.
|
* 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
|
* @return a List of PackageInfo object, each one containing information about a different
|
||||||
* active package.
|
* active package.
|
||||||
*/
|
*/
|
||||||
List<PackageInfo> getActivePackages() {
|
abstract List<PackageInfo> getActivePackages();
|
||||||
if (!isApexSupported()) return Collections.emptyList();
|
|
||||||
populateAllPackagesCacheIfNeeded();
|
|
||||||
return mAllPackagesCache
|
|
||||||
.stream()
|
|
||||||
.filter(item -> isActive(item))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about all active pre-installed APEX packages.
|
* 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
|
* @return a List of PackageInfo object, each one containing information about a different
|
||||||
* active pre-installed package.
|
* active pre-installed package.
|
||||||
*/
|
*/
|
||||||
List<PackageInfo> getFactoryPackages() {
|
abstract List<PackageInfo> getFactoryPackages();
|
||||||
if (!isApexSupported()) return Collections.emptyList();
|
|
||||||
populateAllPackagesCacheIfNeeded();
|
|
||||||
return mAllPackagesCache
|
|
||||||
.stream()
|
|
||||||
.filter(item -> isFactory(item))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about all inactive APEX packages.
|
* 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
|
* @return a List of PackageInfo object, each one containing information about a different
|
||||||
* inactive package.
|
* inactive package.
|
||||||
*/
|
*/
|
||||||
List<PackageInfo> getInactivePackages() {
|
abstract List<PackageInfo> getInactivePackages();
|
||||||
if (!isApexSupported()) return Collections.emptyList();
|
|
||||||
populateAllPackagesCacheIfNeeded();
|
|
||||||
return mAllPackagesCache
|
|
||||||
.stream()
|
|
||||||
.filter(item -> !isActive(item))
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Checks if {@code packageName} is an apex package.
|
* Checks if {@code packageName} is an apex package.
|
||||||
@@ -260,16 +126,7 @@ class ApexManager {
|
|||||||
* @param packageName package to check.
|
* @param packageName package to check.
|
||||||
* @return {@code true} if {@code packageName} is an apex package.
|
* @return {@code true} if {@code packageName} is an apex package.
|
||||||
*/
|
*/
|
||||||
boolean isApexPackage(String packageName) {
|
abstract boolean isApexPackage(String packageName);
|
||||||
if (!isApexSupported()) return false;
|
|
||||||
populateAllPackagesCacheIfNeeded();
|
|
||||||
for (PackageInfo packageInfo : mAllPackagesCache) {
|
|
||||||
if (packageInfo.packageName.equals(packageName)) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Retrieves information about an apexd staged session i.e. the internal state used by apexd to
|
* 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.
|
* @param sessionId the identifier of the session.
|
||||||
* @return an ApexSessionInfo object, or null if the session is not known.
|
* @return an ApexSessionInfo object, or null if the session is not known.
|
||||||
*/
|
*/
|
||||||
@Nullable ApexSessionInfo getStagedSessionInfo(int sessionId) {
|
@Nullable
|
||||||
if (!isApexSupported()) return null;
|
abstract 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Submit a staged session to apex service. This causes the apex service to perform some initial
|
* 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
|
* @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
|
* an array of identifiers of all the child sessions. Otherwise it should
|
||||||
* be an empty array.
|
* be an empty array.
|
||||||
* @param apexInfoList this is an output parameter, which needs to be initialized by tha caller
|
* @throws PackageManagerException if call to apexd fails
|
||||||
* 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.
|
|
||||||
*/
|
*/
|
||||||
boolean submitStagedSession(
|
abstract ApexInfoList submitStagedSession(int sessionId, @NonNull int[] childSessionIds)
|
||||||
int sessionId, @NonNull int[] childSessionIds, @NonNull ApexInfoList apexInfoList) {
|
throws PackageManagerException;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
|
* Mark a staged session previously submitted using {@code submitStagedSession} as ready to be
|
||||||
* applied at next reboot.
|
* applied at next reboot.
|
||||||
*
|
*
|
||||||
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as ready.
|
* @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) {
|
abstract void markStagedSessionReady(int sessionId) throws PackageManagerException;
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Marks a staged session as successful.
|
* Marks a staged session as successful.
|
||||||
@@ -352,44 +170,21 @@ class ApexManager {
|
|||||||
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
|
* @param sessionId the identifier of the {@link PackageInstallerSession} being marked as
|
||||||
* successful.
|
* successful.
|
||||||
*/
|
*/
|
||||||
void markStagedSessionSuccessful(int sessionId) {
|
abstract 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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the current device supports the management of APEX packages.
|
* Whether the current device supports the management of APEX packages.
|
||||||
*
|
*
|
||||||
* @return true if APEX packages can be managed on this device, false otherwise.
|
* @return true if APEX packages can be managed on this device, false otherwise.
|
||||||
*/
|
*/
|
||||||
boolean isApexSupported() {
|
abstract boolean isApexSupported();
|
||||||
return ApexProperties.updatable().orElse(false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abandons the (only) active session previously submitted.
|
* Abandons the (only) active session previously submitted.
|
||||||
*
|
*
|
||||||
* @return {@code true} upon success, {@code false} if any remote exception occurs
|
* @return {@code true} upon success, {@code false} if any remote exception occurs
|
||||||
*/
|
*/
|
||||||
boolean abortActiveSession() {
|
abstract boolean abortActiveSession();
|
||||||
if (!isApexSupported()) return false;
|
|
||||||
try {
|
|
||||||
mApexService.abortActiveSession();
|
|
||||||
return true;
|
|
||||||
} catch (RemoteException re) {
|
|
||||||
Slog.e(TAG, "Unable to contact apexservice", re);
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Uninstalls given {@code apexPackage}.
|
* Uninstalls given {@code apexPackage}.
|
||||||
@@ -399,64 +194,7 @@ class ApexManager {
|
|||||||
* @param apexPackagePath package to uninstall.
|
* @param apexPackagePath package to uninstall.
|
||||||
* @return {@code true} upon successful uninstall, {@code false} otherwise.
|
* @return {@code true} upon successful uninstall, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
boolean uninstallApex(String apexPackagePath) {
|
abstract 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();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Dumps various state information to the provided {@link PrintWriter} object.
|
* 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
|
* @param packageName a {@link String} containing a package name, or {@code null}. If set, only
|
||||||
* information about that specific package will be dumped.
|
* information about that specific package will be dumped.
|
||||||
*/
|
*/
|
||||||
void dump(PrintWriter pw, @Nullable String packageName) {
|
abstract void dump(PrintWriter pw, @Nullable String packageName);
|
||||||
if (!isApexSupported()) return;
|
|
||||||
final IndentingPrintWriter ipw = new IndentingPrintWriter(pw, " ", 120);
|
@IntDef(
|
||||||
try {
|
flag = true,
|
||||||
populateAllPackagesCacheIfNeeded();
|
prefix = { "MATCH_"},
|
||||||
ipw.println();
|
value = {MATCH_ACTIVE_PACKAGE, MATCH_FACTORY_PACKAGE})
|
||||||
ipw.println("Active APEX packages:");
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
dumpFromPackagesCache(getActivePackages(), packageName, ipw);
|
@interface PackageInfoFlags{}
|
||||||
ipw.println("Inactive APEX packages:");
|
|
||||||
dumpFromPackagesCache(getInactivePackages(), packageName, ipw);
|
/**
|
||||||
ipw.println("Factory APEX packages:");
|
* An implementation of {@link ApexManager} that should be used in case device supports updating
|
||||||
dumpFromPackagesCache(getFactoryPackages(), packageName, ipw);
|
* APEX packages.
|
||||||
ipw.increaseIndent();
|
*/
|
||||||
ipw.println("APEX session state:");
|
private static class ApexManagerImpl extends ApexManager {
|
||||||
ipw.increaseIndent();
|
private final IApexService mApexService;
|
||||||
final ApexSessionInfo[] sessions = mApexService.getSessions();
|
private final Context mContext;
|
||||||
for (ApexSessionInfo si : sessions) {
|
private final Object mLock = new Object();
|
||||||
ipw.println("Session ID: " + si.sessionId);
|
/**
|
||||||
ipw.increaseIndent();
|
* A map from {@code APEX packageName} to the {@Link PackageInfo} generated from the {@code
|
||||||
if (si.isUnknown) {
|
* AndroidManifest.xml}
|
||||||
ipw.println("State: UNKNOWN");
|
*
|
||||||
} else if (si.isVerified) {
|
* <p>Note that key of this map is {@code packageName} field of the corresponding {@code
|
||||||
ipw.println("State: VERIFIED");
|
* AndroidManifest.xml}.
|
||||||
} else if (si.isStaged) {
|
*/
|
||||||
ipw.println("State: STAGED");
|
@GuardedBy("mLock")
|
||||||
} else if (si.isActivated) {
|
private List<PackageInfo> mAllPackagesCache;
|
||||||
ipw.println("State: ACTIVATED");
|
|
||||||
} else if (si.isActivationFailed) {
|
ApexManagerImpl(Context context, IApexService apexService) {
|
||||||
ipw.println("State: ACTIVATION FAILED");
|
mContext = context;
|
||||||
} else if (si.isSuccess) {
|
mApexService = apexService;
|
||||||
ipw.println("State: SUCCESS");
|
}
|
||||||
} else if (si.isRollbackInProgress) {
|
|
||||||
ipw.println("State: ROLLBACK IN PROGRESS");
|
/**
|
||||||
} else if (si.isRolledBack) {
|
* Whether an APEX package is active or not.
|
||||||
ipw.println("State: ROLLED BACK");
|
*
|
||||||
} else if (si.isRollbackFailed) {
|
* @param packageInfo the package to check
|
||||||
ipw.println("State: ROLLBACK FAILED");
|
* @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();
|
||||||
}
|
}
|
||||||
ipw.decreaseIndent();
|
ipw.decreaseIndent();
|
||||||
} catch (RemoteException e) {
|
ipw.println();
|
||||||
ipw.println("Couldn't communicate with apexd.");
|
}
|
||||||
|
|
||||||
|
@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;
|
* An implementation of {@link ApexManager} that should be used in case device does not support
|
||||||
populateAllPackagesCacheIfNeeded();
|
* 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);
|
mProtectedPackages = new ProtectedPackages(mContext);
|
||||||
|
|
||||||
mApexManager = new ApexManager(context);
|
mApexManager = ApexManager.create(context);
|
||||||
|
// CHECKSTYLE:OFF IndentationCheck
|
||||||
synchronized (mInstallLock) {
|
synchronized (mInstallLock) {
|
||||||
// writer
|
// writer
|
||||||
synchronized (mPackages) {
|
synchronized (mPackages) {
|
||||||
|
|||||||
@@ -30,6 +30,7 @@ import android.content.pm.PackageInfo;
|
|||||||
import android.content.pm.PackageInstaller;
|
import android.content.pm.PackageInstaller;
|
||||||
import android.content.pm.PackageInstaller.SessionInfo;
|
import android.content.pm.PackageInstaller.SessionInfo;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.content.pm.PackageParser;
|
||||||
import android.content.pm.PackageParser.PackageParserException;
|
import android.content.pm.PackageParser.PackageParserException;
|
||||||
import android.content.pm.PackageParser.SigningDetails;
|
import android.content.pm.PackageParser.SigningDetails;
|
||||||
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
|
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
|
||||||
@@ -43,6 +44,7 @@ import android.os.ParcelFileDescriptor;
|
|||||||
import android.os.PowerManager;
|
import android.os.PowerManager;
|
||||||
import android.os.RemoteException;
|
import android.os.RemoteException;
|
||||||
import android.os.ServiceManager;
|
import android.os.ServiceManager;
|
||||||
|
import android.util.IntArray;
|
||||||
import android.util.Slog;
|
import android.util.Slog;
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import android.util.apk.ApkSignatureVerifier;
|
import android.util.apk.ApkSignatureVerifier;
|
||||||
@@ -104,21 +106,22 @@ public class StagingManager {
|
|||||||
return new ParceledListSlice<>(result);
|
return new ParceledListSlice<>(result);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean validateApexSignature(String apexPath, String apexModuleName) {
|
private void validateApexSignature(String apexPath, String packageName)
|
||||||
|
throws PackageManagerException {
|
||||||
final SigningDetails signingDetails;
|
final SigningDetails signingDetails;
|
||||||
try {
|
try {
|
||||||
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
|
signingDetails = ApkSignatureVerifier.verify(apexPath, SignatureSchemeVersion.JAR);
|
||||||
} catch (PackageParserException e) {
|
} catch (PackageParserException e) {
|
||||||
Slog.e(TAG, "Unable to parse APEX package: " + apexPath, e);
|
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||||
return false;
|
"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) {
|
if (packageInfo == null) {
|
||||||
// Don't allow installation of new APEX.
|
// This should never happen, because submitSessionToApexService ensures that no new
|
||||||
Slog.e(TAG, "Attempted to install a new apex " + apexModuleName + ". Rejecting");
|
// apexes were installed.
|
||||||
return false;
|
throw new IllegalStateException("Unknown apex package " + packageName);
|
||||||
}
|
}
|
||||||
|
|
||||||
final SigningDetails existingSigningDetails;
|
final SigningDetails existingSigningDetails;
|
||||||
@@ -126,73 +129,99 @@ public class StagingManager {
|
|||||||
existingSigningDetails = ApkSignatureVerifier.verify(
|
existingSigningDetails = ApkSignatureVerifier.verify(
|
||||||
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
|
packageInfo.applicationInfo.sourceDir, SignatureSchemeVersion.JAR);
|
||||||
} catch (PackageParserException e) {
|
} catch (PackageParserException e) {
|
||||||
Slog.e(TAG, "Unable to parse APEX package: "
|
throw new PackageManagerException(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
||||||
+ packageInfo.applicationInfo.sourceDir, e);
|
"Failed to parse APEX package " + packageInfo.applicationInfo.sourceDir, e);
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now that we have both sets of signatures, demand that they're an exact match.
|
// Now that we have both sets of signatures, demand that they're an exact match.
|
||||||
if (Signature.areExactMatch(existingSigningDetails.signatures, signingDetails.signatures)) {
|
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,
|
private List<PackageInfo> submitSessionToApexService(
|
||||||
List<PackageInstallerSession> childSessions,
|
@NonNull PackageInstallerSession session) throws PackageManagerException {
|
||||||
ApexInfoList apexInfoList) {
|
final IntArray childSessionsIds = new IntArray();
|
||||||
boolean submittedToApexd = mApexManager.submitStagedSession(
|
if (session.isMultiPackage()) {
|
||||||
session.sessionId,
|
for (int id : session.getChildSessionIds()) {
|
||||||
childSessions != null
|
if (isApexSession(mStagedSessions.get(id))) {
|
||||||
? childSessions.stream().mapToInt(s -> s.sessionId).toArray() :
|
childSessionsIds.add(id);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
long activeVersion = activePackage.applicationInfo.longVersionCode;
|
}
|
||||||
if (session.params.requiredInstalledVersionCode
|
// submitStagedSession will throw a PackageManagerException if apexd verification fails,
|
||||||
!= PackageManager.VERSION_CODE_HIGHEST) {
|
// which will be propagated to populate stagedSessionErrorMessage of this session.
|
||||||
if (activeVersion != session.params.requiredInstalledVersionCode) {
|
final ApexInfoList apexInfoList = mApexManager.submitStagedSession(session.sessionId,
|
||||||
session.setStagedSessionFailed(
|
childSessionsIds.toArray());
|
||||||
SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
final List<PackageInfo> result = new ArrayList<>();
|
||||||
"Installed version of APEX package " + activePackage.packageName
|
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
|
+ " does not match required. Active version: " + activeVersion
|
||||||
+ " required: " + session.params.requiredInstalledVersionCode);
|
+ " 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) {
|
private static boolean isApexSession(@NonNull PackageInstallerSession session) {
|
||||||
@@ -200,31 +229,20 @@ public class StagingManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void preRebootVerification(@NonNull PackageInstallerSession session) {
|
private void preRebootVerification(@NonNull PackageInstallerSession session) {
|
||||||
boolean success = true;
|
final boolean hasApex = sessionContainsApex(session);
|
||||||
|
|
||||||
final ApexInfoList apexInfoList = new ApexInfoList();
|
|
||||||
// APEX checks. For single-package sessions, check if they contain an APEX. For
|
// 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.
|
// multi-package sessions, find all the child sessions that contain an APEX.
|
||||||
if (!session.isMultiPackage()
|
if (hasApex) {
|
||||||
&& isApexSession(session)) {
|
try {
|
||||||
success = submitSessionToApexService(session, null, apexInfoList);
|
final List<PackageInfo> apexPackages = submitSessionToApexService(session);
|
||||||
|
for (PackageInfo apexPackage : apexPackages) {
|
||||||
} else if (session.isMultiPackage()) {
|
validateApexSignature(apexPackage.applicationInfo.sourceDir,
|
||||||
List<PackageInstallerSession> childSessions =
|
apexPackage.packageName);
|
||||||
Arrays.stream(session.getChildSessionIds())
|
}
|
||||||
// Retrieve cached sessions matching ids.
|
} catch (PackageManagerException e) {
|
||||||
.mapToObj(i -> mStagedSessions.get(i))
|
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||||
// Filter only the ones containing APEX.
|
return;
|
||||||
.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 (sessionContainsApk(session)) {
|
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 ((session.params.installFlags & PackageManager.INSTALL_ENABLE_ROLLBACK) != 0) {
|
||||||
// If rollback is enabled for this session, we call through to the RollbackManager
|
// 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
|
// 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();
|
session.setStagedSessionReady();
|
||||||
if (sessionContainsApex(session)
|
if (!hasApex) {
|
||||||
&& !mApexManager.markStagedSessionReady(session.sessionId)) {
|
// Session doesn't contain apex, nothing to do.
|
||||||
session.setStagedSessionFailed(SessionInfo.STAGED_SESSION_VERIFICATION_FAILED,
|
return;
|
||||||
"APEX staging failed, check logcat messages from apexd for more "
|
}
|
||||||
+ "details.");
|
try {
|
||||||
|
mApexManager.markStagedSessionReady(session.sessionId);
|
||||||
|
} catch (PackageManagerException e) {
|
||||||
|
session.setStagedSessionFailed(e.error, e.getMessage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user