Support non-privileged APKs in APEX.
This change adds initial support for scanning APEX directories looking for APK, and make them available to the system as normal system APKs. For now privileged apps and resource overlay are not supported, and the support will be added in a later CL. Scanning features are inherited from the partition in which the preinstalled version of a given APEX module is located. Supports both flattened and un-flattened APEX configurations. Bug: 138429615 Test: Compiled a test apex with the Snake app installed in it. Verified app is correctly scanned and usable. Tried also installing the apex in /vendor and /product. Exempt-From-Owner-Approval: Approved in https://googleplex-android-review.git.corp.google.com/c/platform/frameworks/base/+/9533445 Merged-In: I8fb91317da0296567403b2b62babbb71e4b48938 Change-Id: I8fb91317da0296567403b2b62babbb71e4b48938
This commit is contained in:
@@ -55,6 +55,7 @@ public class Environment {
|
||||
private static final String ENV_VENDOR_ROOT = "VENDOR_ROOT";
|
||||
private static final String ENV_PRODUCT_ROOT = "PRODUCT_ROOT";
|
||||
private static final String ENV_SYSTEM_EXT_ROOT = "SYSTEM_EXT_ROOT";
|
||||
private static final String ENV_APEX_ROOT = "APEX_ROOT";
|
||||
|
||||
/** {@hide} */
|
||||
public static final String DIR_ANDROID = "Android";
|
||||
@@ -78,7 +79,9 @@ public class Environment {
|
||||
private static final File DIR_VENDOR_ROOT = getDirectory(ENV_VENDOR_ROOT, "/vendor");
|
||||
private static final File DIR_PRODUCT_ROOT = getDirectory(ENV_PRODUCT_ROOT, "/product");
|
||||
private static final File DIR_SYSTEM_EXT_ROOT = getDirectory(ENV_SYSTEM_EXT_ROOT,
|
||||
"/system_ext");
|
||||
"/system_ext");
|
||||
private static final File DIR_APEX_ROOT = getDirectory(ENV_APEX_ROOT,
|
||||
"/apex");
|
||||
|
||||
@UnsupportedAppUsage
|
||||
private static UserEnvironment sCurrentUser;
|
||||
@@ -245,6 +248,16 @@ public class Environment {
|
||||
return DIR_SYSTEM_EXT_ROOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return root directory of the apex mount point, where all the apex modules are made available
|
||||
* to the rest of the system.
|
||||
*
|
||||
* @hide
|
||||
*/
|
||||
public static @NonNull File getApexDirectory() {
|
||||
return DIR_APEX_ROOT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the system directory for a user. This is for use by system
|
||||
* services to store files relating to the user. This directory will be
|
||||
|
||||
@@ -32,6 +32,7 @@ import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageInstaller;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageParser;
|
||||
import android.os.Environment;
|
||||
import android.os.RemoteException;
|
||||
import android.os.ServiceManager;
|
||||
import android.sysprop.ApexProperties;
|
||||
@@ -47,6 +48,7 @@ import java.io.PrintWriter;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
@@ -64,9 +66,9 @@ abstract class ApexManager {
|
||||
static final int MATCH_FACTORY_PACKAGE = 1 << 1;
|
||||
|
||||
/**
|
||||
* 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}.
|
||||
* Returns an instance of either {@link ApexManagerImpl} or {@link ApexManagerFlattenedApex}
|
||||
* depending on whether this device supports APEX, i.e. {@link ApexProperties#updatable()}
|
||||
* evaluates to {@code true}.
|
||||
*/
|
||||
static ApexManager create(Context systemContext) {
|
||||
if (ApexProperties.updatable().orElse(false)) {
|
||||
@@ -77,10 +79,28 @@ abstract class ApexManager {
|
||||
throw new IllegalStateException("Required service apexservice not available");
|
||||
}
|
||||
} else {
|
||||
return new ApexManagerNoOp();
|
||||
return new ApexManagerFlattenedApex();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Minimal information about APEX mount points and the original APEX package they refer to.
|
||||
*/
|
||||
static class ActiveApexInfo {
|
||||
public final File apexDirectory;
|
||||
public final File preinstalledApexPath;
|
||||
|
||||
private ActiveApexInfo(File apexDirectory, File preinstalledApexPath) {
|
||||
this.apexDirectory = apexDirectory;
|
||||
this.preinstalledApexPath = preinstalledApexPath;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link ActiveApexInfo} records relative to all active APEX packages.
|
||||
*/
|
||||
abstract List<ActiveApexInfo> getActiveApexInfos();
|
||||
|
||||
abstract void systemReady();
|
||||
|
||||
/**
|
||||
@@ -258,6 +278,22 @@ abstract class ApexManager {
|
||||
return (packageInfo.applicationInfo.flags & ApplicationInfo.FLAG_SYSTEM) != 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
List<ActiveApexInfo> getActiveApexInfos() {
|
||||
try {
|
||||
return Arrays.stream(mApexService.getActivePackages())
|
||||
.map(apexInfo -> new ActiveApexInfo(
|
||||
new File(
|
||||
Environment.getApexDirectory() + File.separator
|
||||
+ apexInfo.moduleName),
|
||||
new File(apexInfo.modulePath))).collect(
|
||||
Collectors.toList());
|
||||
} catch (RemoteException e) {
|
||||
Slog.e(TAG, "Unable to retrieve packages from apexservice", e);
|
||||
}
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@Override
|
||||
void systemReady() {
|
||||
mContext.registerReceiver(new BroadcastReceiver() {
|
||||
@@ -549,7 +585,40 @@ abstract class ApexManager {
|
||||
* 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 {
|
||||
private static final class ApexManagerFlattenedApex extends ApexManager {
|
||||
|
||||
@Override
|
||||
List<ActiveApexInfo> getActiveApexInfos() {
|
||||
// There is no apexd running in case of flattened apex
|
||||
// We look up the /apex directory and identify the active APEX modules from there.
|
||||
// As "preinstalled" path, we just report /system since in the case of flattened APEX
|
||||
// the /apex directory is just a symlink to /system/apex.
|
||||
List<ActiveApexInfo> result = new ArrayList<>();
|
||||
File apexDir = Environment.getApexDirectory();
|
||||
// In flattened configuration, init special-case the art directory and bind-mounts
|
||||
// com.android.art.{release|debug} to com.android.art. At the time of writing, these
|
||||
// directories are copied from the kArtApexDirNames variable in
|
||||
// system/core/init/mount_namespace.cpp.
|
||||
String[] skipDirs = {"com.android.art.release", "com.android.art.debug"};
|
||||
if (apexDir.isDirectory()) {
|
||||
File[] files = apexDir.listFiles();
|
||||
// listFiles might be null if system server doesn't have permission to read
|
||||
// a directory.
|
||||
if (files != null) {
|
||||
for (File file : files) {
|
||||
if (file.isDirectory() && !file.getName().contains("@")) {
|
||||
for (String skipDir : skipDirs) {
|
||||
if (file.getName().equals(skipDir)) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
result.add(new ActiveApexInfo(file, Environment.getRootDirectory()));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
void systemReady() {
|
||||
|
||||
@@ -67,6 +67,15 @@ final class PackageAbiHelperImpl implements PackageAbiHelper {
|
||||
codeRoot = Environment.getSystemExtDirectory();
|
||||
} else if (FileUtils.contains(Environment.getOdmDirectory(), codePath)) {
|
||||
codeRoot = Environment.getOdmDirectory();
|
||||
} else if (FileUtils.contains(Environment.getApexDirectory(), codePath)) {
|
||||
String fullPath = codePath.getAbsolutePath();
|
||||
String[] parts = fullPath.split(File.separator);
|
||||
if (parts.length > 2) {
|
||||
codeRoot = new File(parts[1] + File.separator + parts[2]);
|
||||
} else {
|
||||
Slog.w(PackageManagerService.TAG, "Can't canonicalize code path " + codePath);
|
||||
codeRoot = Environment.getApexDirectory();
|
||||
}
|
||||
} else {
|
||||
// Unrecognized code path; take its top real segment as the apk root:
|
||||
// e.g. /something/app/blah.apk => /something
|
||||
|
||||
@@ -375,6 +375,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.BiConsumer;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.function.Predicate;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
/**
|
||||
* Keep track of all those APKs everywhere.
|
||||
@@ -767,6 +768,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
new SystemPartition(Environment.getSystemExtDirectory(), SCAN_AS_SYSTEM_EXT,
|
||||
true /* hasPriv */, true /* hasOverlays */)));
|
||||
|
||||
private final List<SystemPartition> mDirsToScanAsSystem;
|
||||
|
||||
/**
|
||||
* Unit tests will instantiate, extend and/or mock to mock dependencies / behaviors.
|
||||
*
|
||||
@@ -2563,7 +2566,18 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
|
||||
mProtectedPackages = new ProtectedPackages(mContext);
|
||||
|
||||
mApexManager = ApexManager.create(context);
|
||||
mApexManager = ApexManager.create(mContext);
|
||||
|
||||
mDirsToScanAsSystem = new ArrayList<>();
|
||||
mDirsToScanAsSystem.addAll(SYSTEM_PARTITIONS);
|
||||
mDirsToScanAsSystem.addAll(mApexManager.getActiveApexInfos().stream()
|
||||
.map(ai -> resolveApexToSystemPartition(ai))
|
||||
.filter(Objects::nonNull).collect(Collectors.toList()));
|
||||
Slog.d(TAG,
|
||||
"Directories scanned as system partitions: [" + mDirsToScanAsSystem.stream().map(
|
||||
d -> (d.folder.getAbsolutePath() + ":" + d.scanFlag))
|
||||
.collect(Collectors.joining(",")) + "]");
|
||||
|
||||
// CHECKSTYLE:OFF IndentationCheck
|
||||
synchronized (mInstallLock) {
|
||||
// writer
|
||||
@@ -2700,8 +2714,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
// reside in the right directory.
|
||||
final int systemParseFlags = mDefParseFlags | PackageParser.PARSE_IS_SYSTEM_DIR;
|
||||
final int systemScanFlags = scanFlags | SCAN_AS_SYSTEM;
|
||||
for (int i = SYSTEM_PARTITIONS.size() - 1; i >= 0; i--) {
|
||||
final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
|
||||
for (int i = mDirsToScanAsSystem.size() - 1; i >= 0; i--) {
|
||||
final SystemPartition partition = mDirsToScanAsSystem.get(i);
|
||||
if (partition.overlayFolder == null) {
|
||||
continue;
|
||||
}
|
||||
@@ -2717,8 +2731,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
throw new IllegalStateException(
|
||||
"Failed to load frameworks package; check log for warnings");
|
||||
}
|
||||
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
|
||||
final SystemPartition partition = SYSTEM_PARTITIONS.get(i);
|
||||
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
|
||||
final SystemPartition partition = mDirsToScanAsSystem.get(i);
|
||||
if (partition.privAppFolder != null) {
|
||||
scanDirTracedLI(partition.privAppFolder, systemParseFlags,
|
||||
systemScanFlags | SCAN_AS_PRIVILEGED | partition.scanFlag, 0);
|
||||
@@ -2896,8 +2910,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
|
||||
@ParseFlags int reparseFlags = 0;
|
||||
@ScanFlags int rescanFlags = 0;
|
||||
for (int i1 = 0, size = SYSTEM_PARTITIONS.size(); i1 < size; i1++) {
|
||||
SystemPartition partition = SYSTEM_PARTITIONS.get(i1);
|
||||
for (int i1 = 0, size = mDirsToScanAsSystem.size(); i1 < size; i1++) {
|
||||
SystemPartition partition = mDirsToScanAsSystem.get(i1);
|
||||
if (partition.containsPrivApp(scanFile)) {
|
||||
reparseFlags = systemParseFlags;
|
||||
rescanFlags = systemScanFlags | SCAN_AS_PRIVILEGED
|
||||
@@ -18375,6 +18389,7 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
}
|
||||
|
||||
static boolean locationIsPrivileged(String path) {
|
||||
// TODO(dariofreni): include APEX partitions when they will support priv apps.
|
||||
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
|
||||
SystemPartition partition = SYSTEM_PARTITIONS.get(i);
|
||||
if (partition.containsPrivPath(path)) {
|
||||
@@ -18384,6 +18399,18 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
return false;
|
||||
}
|
||||
|
||||
private static @Nullable SystemPartition resolveApexToSystemPartition(
|
||||
ApexManager.ActiveApexInfo apexInfo) {
|
||||
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
|
||||
SystemPartition sp = SYSTEM_PARTITIONS.get(i);
|
||||
if (apexInfo.preinstalledApexPath.getAbsolutePath().startsWith(
|
||||
sp.folder.getAbsolutePath())) {
|
||||
return new SystemPartition(apexInfo.apexDirectory, sp.scanFlag,
|
||||
false /* hasPriv */, false /* hasOverlays */);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/*
|
||||
* Tries to delete system package.
|
||||
@@ -18490,8 +18517,8 @@ public class PackageManagerService extends IPackageManager.Stub
|
||||
| PackageParser.PARSE_MUST_BE_APK
|
||||
| PackageParser.PARSE_IS_SYSTEM_DIR;
|
||||
@ScanFlags int scanFlags = SCAN_AS_SYSTEM;
|
||||
for (int i = 0, size = SYSTEM_PARTITIONS.size(); i < size; i++) {
|
||||
SystemPartition partition = SYSTEM_PARTITIONS.get(i);
|
||||
for (int i = 0, size = mDirsToScanAsSystem.size(); i < size; i++) {
|
||||
SystemPartition partition = mDirsToScanAsSystem.get(i);
|
||||
if (partition.containsPath(codePathString)) {
|
||||
scanFlags |= partition.scanFlag;
|
||||
if (partition.containsPrivPath(codePathString)) {
|
||||
|
||||
Reference in New Issue
Block a user