diff --git a/services/core/java/com/android/server/pm/ApexManager.java b/services/core/java/com/android/server/pm/ApexManager.java index 5430f4c8daa0d..497385fef39c2 100644 --- a/services/core/java/com/android/server/pm/ApexManager.java +++ b/services/core/java/com/android/server/pm/ApexManager.java @@ -22,22 +22,21 @@ import android.apex.ApexInfo; import android.apex.ApexInfoList; import android.apex.ApexSessionInfo; import android.apex.IApexService; -import android.content.BroadcastReceiver; import android.content.Context; -import android.content.Intent; -import android.content.IntentFilter; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.PackageParser; import android.content.pm.PackageParser.PackageParserException; +import android.os.HandlerThread; import android.os.RemoteException; import android.os.ServiceManager; import android.os.ServiceManager.ServiceNotFoundException; +import android.os.SystemClock; import android.sysprop.ApexProperties; import android.util.Slog; -import com.android.internal.annotations.GuardedBy; import com.android.internal.util.IndentingPrintWriter; +import com.android.server.SystemService; import java.io.File; import java.io.PrintWriter; @@ -46,75 +45,108 @@ import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; +import java.util.concurrent.CountDownLatch; import java.util.function.Function; 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. + * + * @hide */ -class ApexManager { - static final String TAG = "ApexManager"; - private final IApexService mApexService; - private final Context mContext; - private final Object mLock = new Object(); - @GuardedBy("mLock") +public final class ApexManager extends SystemService { + private static final String TAG = "ApexManager"; + private IApexService mApexService; + + private final CountDownLatch mActivePackagesCacheLatch = new CountDownLatch(1); private Map mActivePackagesCache; - ApexManager(Context context) { + private final CountDownLatch mApexFilesCacheLatch = new CountDownLatch(1); + private ApexInfo[] mApexFiles; + + public ApexManager(Context context) { + super(context); + } + + @Override + public void onStart() { try { mApexService = IApexService.Stub.asInterface( - ServiceManager.getServiceOrThrow("apexservice")); + ServiceManager.getServiceOrThrow("apexservice")); } catch (ServiceNotFoundException e) { throw new IllegalStateException("Required service apexservice not available"); } - mContext = context; + publishLocalService(ApexManager.class, this); + HandlerThread oneShotThread = new HandlerThread("ApexManagerOneShotHandler"); + oneShotThread.start(); + oneShotThread.getThreadHandler().post(this::initSequence); + oneShotThread.quitSafely(); } - void systemReady() { - mContext.registerReceiver(new BroadcastReceiver() { - @Override - public void onReceive(Context context, Intent intent) { - onBootCompleted(); - mContext.unregisterReceiver(this); - } - }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + private void initSequence() { + populateApexFilesCache(); + parseApexFiles(); } - private void populateActivePackagesCacheIfNeeded() { - synchronized (mLock) { - if (mActivePackagesCache != null) { - return; - } + private void populateApexFilesCache() { + if (mApexFiles != null) { + return; + } + long startTimeMicros = SystemClock.currentTimeMicro(); + Slog.i(TAG, "Starting to populate apex files cache"); + try { + mApexFiles = mApexService.getActivePackages(); + Slog.i(TAG, "IPC to apexd finished in " + (SystemClock.currentTimeMicro() + - startTimeMicros) + " μs"); + } catch (RemoteException re) { + // TODO: make sure this error is propagated to system server. + Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); + re.rethrowAsRuntimeException(); + } + mApexFilesCacheLatch.countDown(); + Slog.i(TAG, "Finished populating apex files cache in " + (SystemClock.currentTimeMicro() + - startTimeMicros) + " μs"); + } + + private void parseApexFiles() { + waitForLatch(mApexFilesCacheLatch); + if (mApexFiles == null) { + throw new IllegalStateException("mApexFiles must be populated"); + } + long startTimeMicros = SystemClock.currentTimeMicro(); + Slog.i(TAG, "Starting to parse apex files"); + List list = new ArrayList<>(); + // TODO: this can be parallelized. + for (ApexInfo ai : mApexFiles) { try { - List list = new ArrayList<>(); - final ApexInfo[] activePkgs = mApexService.getActivePackages(); - for (ApexInfo ai : activePkgs) { - // 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.packagePath)).isDirectory()) { - break; - } - try { - list.add(PackageParser.generatePackageInfoFromApex( - new File(ai.packagePath), PackageManager.GET_META_DATA - | PackageManager.GET_SIGNING_CERTIFICATES)); - } catch (PackageParserException pe) { - throw new IllegalStateException("Unable to parse: " + ai, pe); - } + // 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.packagePath)).isDirectory()) { + break; } - mActivePackagesCache = list.stream().collect( - Collectors.toMap(p -> p.packageName, Function.identity())); - } catch (RemoteException re) { - Slog.e(TAG, "Unable to retrieve packages from apexservice: " + re.toString()); - throw new RuntimeException(re); + list.add(PackageParser.generatePackageInfoFromApex( + new File(ai.packagePath), PackageManager.GET_META_DATA + | PackageManager.GET_SIGNING_CERTIFICATES)); + } catch (PackageParserException pe) { + // TODO: make sure this error is propagated to system server. + throw new IllegalStateException("Unable to parse: " + ai, pe); } } + mActivePackagesCache = list.stream().collect( + Collectors.toMap(p -> p.packageName, Function.identity())); + mActivePackagesCacheLatch.countDown(); + Slog.i(TAG, "Finished parsing apex files in " + (SystemClock.currentTimeMicro() + - startTimeMicros) + " μs"); } /** * Retrieves information about an active APEX package. * + *

This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in + * case {@link #parseApexFiles()}} throws an exception this method will never finish + * essentially putting device into a boot loop. + * * @param packageName the package name to look for. Note that this is the package name reported * in the APK container manifest (i.e. AndroidManifest.xml), which might * differ from the one reported in the APEX manifest (i.e. @@ -123,30 +155,43 @@ class ApexManager { * is not found. */ @Nullable PackageInfo getActivePackage(String packageName) { - populateActivePackagesCacheIfNeeded(); + waitForLatch(mActivePackagesCacheLatch); return mActivePackagesCache.get(packageName); } /** * Retrieves information about all active APEX packages. * + *

This method blocks caller thread until {@link #parseApexFiles()} succeeds. Note that in + * case {@link #parseApexFiles()}} throws an exception this method will never finish + * essentially putting device into a boot loop. + * * @return a Collection of PackageInfo object, each one containing information about a different * active package. */ Collection getActivePackages() { - populateActivePackagesCacheIfNeeded(); + waitForLatch(mActivePackagesCacheLatch); return mActivePackagesCache.values(); } /** * Checks if {@code packageName} is an apex package. * + *

This method blocks caller thread until {@link #populateApexFilesCache()} succeeds. Note + * that in case {@link #populateApexFilesCache()} throws an exception this method will never + * finish essentially putting device into a boot loop. + * * @param packageName package to check. * @return {@code true} if {@code packageName} is an apex package. */ boolean isApexPackage(String packageName) { - populateActivePackagesCacheIfNeeded(); - return mActivePackagesCache.containsKey(packageName); + waitForLatch(mApexFilesCacheLatch); + for (ApexInfo ai : mApexFiles) { + if (ai.packageName.equals(packageName)) { + return true; + } + } + return false; } /** @@ -273,6 +318,19 @@ class ApexManager { } } + /** + * Blocks current thread until {@code latch} has counted down to zero. + * + * @throws RuntimeException if thread was interrupted while waiting. + */ + private void waitForLatch(CountDownLatch latch) { + try { + latch.await(); + } catch (InterruptedException e) { + throw new RuntimeException("Interrupted waiting for cache to be populated", e); + } + } + /** * Dumps various state information to the provided {@link PrintWriter} object. * @@ -286,7 +344,7 @@ class ApexManager { ipw.println("Active APEX packages:"); ipw.increaseIndent(); try { - populateActivePackagesCacheIfNeeded(); + waitForLatch(mActivePackagesCacheLatch); for (PackageInfo pi : mActivePackagesCache.values()) { if (packageName != null && !packageName.equals(pi.packageName)) { continue; @@ -331,8 +389,4 @@ class ApexManager { ipw.println("Couldn't communicate with apexd."); } } - - public void onBootCompleted() { - populateActivePackagesCacheIfNeeded(); - } } diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index e08af6f3521fb..e62402e065cdb 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -2374,6 +2374,8 @@ public class PackageManagerService extends IPackageManager.Stub public PackageManagerService(Context context, Installer installer, boolean factoryTest, boolean onlyCore) { + mApexManager = LocalServices.getService(ApexManager.class); + LockGuard.installLock(mPackages, LockGuard.INDEX_PACKAGES); Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "create package manager"); EventLog.writeEvent(EventLogTags.BOOT_PROGRESS_PMS_START, @@ -2470,7 +2472,6 @@ public class PackageManagerService extends IPackageManager.Stub mProtectedPackages = new ProtectedPackages(mContext); - mApexManager = new ApexManager(context); synchronized (mInstallLock) { // writer synchronized (mPackages) { @@ -21462,7 +21463,6 @@ public class PackageManagerService extends IPackageManager.Stub storage.registerListener(mStorageListener); mInstallerService.systemReady(); - mApexManager.systemReady(); mPackageDexOptimizer.systemReady(); getStorageManagerInternal().addExternalStoragePolicy( diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java index be7dd31380bad..4ac8342e6e607 100644 --- a/services/java/com/android/server/SystemServer.java +++ b/services/java/com/android/server/SystemServer.java @@ -115,6 +115,7 @@ import com.android.server.om.OverlayManagerService; import com.android.server.os.BugreportManagerService; import com.android.server.os.DeviceIdentifiersPolicyService; import com.android.server.os.SchedulingPolicyService; +import com.android.server.pm.ApexManager; import com.android.server.pm.BackgroundDexOptService; import com.android.server.pm.CrossProfileAppsService; import com.android.server.pm.DynamicCodeLoggingService; @@ -627,6 +628,12 @@ public final class SystemServer { watchdog.start(); traceEnd(); + // Start ApexManager as early as we can to give it enough time to call apexd and populate + // cache of known apex packages. Note that calling apexd will happen asynchronously. + traceBeginAndSlog("StartApexManager"); + mSystemServiceManager.startService(ApexManager.class); + traceEnd(); + Slog.i(TAG, "Reading configuration..."); final String TAG_SYSTEM_CONFIG = "ReadingSystemConfig"; traceBeginAndSlog(TAG_SYSTEM_CONFIG);