diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java
index 672ad5e594284..cd383b9d1d7a1 100644
--- a/services/core/java/com/android/server/pm/Installer.java
+++ b/services/core/java/com/android/server/pm/Installer.java
@@ -23,6 +23,8 @@ import android.annotation.UserIdInt;
import android.content.Context;
import android.content.pm.PackageStats;
import android.os.Build;
+import android.os.CreateAppDataArgs;
+import android.os.CreateAppDataResult;
import android.os.IBinder;
import android.os.IBinder.DeathRecipient;
import android.os.IInstalld;
@@ -39,7 +41,10 @@ import dalvik.system.BlockGuard;
import dalvik.system.VMRuntime;
import java.io.FileDescriptor;
+import java.util.ArrayList;
import java.util.Arrays;
+import java.util.List;
+import java.util.concurrent.CompletableFuture;
public class Installer extends SystemService {
private static final String TAG = "Installer";
@@ -176,37 +181,140 @@ public class Installer extends SystemService {
}
}
+ private static CreateAppDataArgs buildCreateAppDataArgs(String uuid, String packageName,
+ int userId, int flags, int appId, String seInfo, int targetSdkVersion) {
+ final CreateAppDataArgs args = new CreateAppDataArgs();
+ args.uuid = uuid;
+ args.packageName = packageName;
+ args.userId = userId;
+ args.flags = flags;
+ args.appId = appId;
+ args.seInfo = seInfo;
+ args.targetSdkVersion = targetSdkVersion;
+ return args;
+ }
+
+ private static CreateAppDataResult buildPlaceholderCreateAppDataResult() {
+ final CreateAppDataResult result = new CreateAppDataResult();
+ result.ceDataInode = -1;
+ result.exceptionCode = 0;
+ result.exceptionMessage = null;
+ return result;
+ }
+
+ /**
+ * @deprecated callers are encouraged to migrate to using {@link Batch} to
+ * more efficiently handle operations in bulk.
+ */
+ @Deprecated
public long createAppData(String uuid, String packageName, int userId, int flags, int appId,
String seInfo, int targetSdkVersion) throws InstallerException {
- if (!checkBeforeRemote()) return -1;
+ final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+ appId, seInfo, targetSdkVersion);
+ final CreateAppDataResult result = createAppData(args);
+ if (result.exceptionCode == 0) {
+ return result.ceDataInode;
+ } else {
+ throw new InstallerException(result.exceptionMessage);
+ }
+ }
+
+ public @NonNull CreateAppDataResult createAppData(@NonNull CreateAppDataArgs args)
+ throws InstallerException {
+ if (!checkBeforeRemote()) {
+ return buildPlaceholderCreateAppDataResult();
+ }
try {
- return mInstalld.createAppData(uuid, packageName, userId, flags, appId, seInfo,
- targetSdkVersion);
+ return mInstalld.createAppData(args);
+ } catch (Exception e) {
+ throw InstallerException.from(e);
+ }
+ }
+
+ public @NonNull CreateAppDataResult[] createAppDataBatched(@NonNull CreateAppDataArgs[] args)
+ throws InstallerException {
+ if (!checkBeforeRemote()) {
+ final CreateAppDataResult[] results = new CreateAppDataResult[args.length];
+ Arrays.fill(results, buildPlaceholderCreateAppDataResult());
+ return results;
+ }
+ try {
+ return mInstalld.createAppDataBatched(args);
} catch (Exception e) {
throw InstallerException.from(e);
}
}
/**
- * Batched version of createAppData for use with multiple packages.
+ * Class that collects multiple {@code installd} operations together in an
+ * attempt to more efficiently execute them in bulk.
+ *
+ * Instead of returning results immediately, {@link CompletableFuture}
+ * instances are returned which can be used to chain follow-up work for each
+ * request.
+ *
+ * The creator of this object must invoke {@link #execute()}
+ * exactly once to begin execution of all pending operations. Once execution
+ * has been kicked off, no additional events can be enqueued into this
+ * instance, but multiple instances can safely exist in parallel.
*/
- public void createAppDataBatched(String[] uuids, String[] packageNames, int userId, int flags,
- int[] appIds, String[] seInfos, int[] targetSdkVersions) throws InstallerException {
- if (!checkBeforeRemote()) return;
- final int batchSize = 256;
- for (int i = 0; i < uuids.length; i += batchSize) {
- int to = i + batchSize;
- if (to > uuids.length) {
- to = uuids.length;
- }
+ public static class Batch {
+ private static final int CREATE_APP_DATA_BATCH_SIZE = 256;
- try {
- mInstalld.createAppDataBatched(Arrays.copyOfRange(uuids, i, to),
- Arrays.copyOfRange(packageNames, i, to), userId, flags,
- Arrays.copyOfRange(appIds, i, to), Arrays.copyOfRange(seInfos, i, to),
- Arrays.copyOfRange(targetSdkVersions, i, to));
- } catch (Exception e) {
- throw InstallerException.from(e);
+ private boolean mExecuted;
+
+ private final List mArgs = new ArrayList<>();
+ private final List> mFutures = new ArrayList<>();
+
+ /**
+ * Enqueue the given {@code installd} operation to be executed in the
+ * future when {@link #execute(Installer)} is invoked.
+ *
+ * Callers of this method are not required to hold a monitor lock on an
+ * {@link Installer} object.
+ */
+ public synchronized @NonNull CompletableFuture createAppData(String uuid,
+ String packageName, int userId, int flags, int appId, String seInfo,
+ int targetSdkVersion) {
+ if (mExecuted) throw new IllegalStateException();
+
+ final CreateAppDataArgs args = buildCreateAppDataArgs(uuid, packageName, userId, flags,
+ appId, seInfo, targetSdkVersion);
+ final CompletableFuture future = new CompletableFuture<>();
+ mArgs.add(args);
+ mFutures.add(future);
+ return future;
+ }
+
+ /**
+ * Execute all pending {@code installd} operations that have been
+ * collected by this batch in a blocking fashion.
+ *
+ * Callers of this method must hold a monitor lock on the given
+ * {@link Installer} object.
+ */
+ public synchronized void execute(@NonNull Installer installer) throws InstallerException {
+ if (mExecuted) throw new IllegalStateException();
+ mExecuted = true;
+
+ final int size = mArgs.size();
+ for (int i = 0; i < size; i += CREATE_APP_DATA_BATCH_SIZE) {
+ final CreateAppDataArgs[] args = new CreateAppDataArgs[Math.min(size - i,
+ CREATE_APP_DATA_BATCH_SIZE)];
+ for (int j = 0; j < args.length; j++) {
+ args[j] = mArgs.get(i + j);
+ }
+ final CreateAppDataResult[] results = installer.createAppDataBatched(args);
+ for (int j = 0; j < args.length; j++) {
+ final CreateAppDataResult result = results[j];
+ final CompletableFuture future = mFutures.get(i + j);
+ if (result.exceptionCode == 0) {
+ future.complete(result.ceDataInode);
+ } else {
+ future.completeExceptionally(
+ new InstallerException(result.exceptionMessage));
+ }
+ }
}
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 58a1648e51ad3..23443fd74eca2 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -418,6 +418,7 @@ import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Future;
@@ -3481,6 +3482,7 @@ public class PackageManagerService extends IPackageManager.Stub
return;
}
int count = 0;
+ final Installer.Batch batch = new Installer.Batch();
for (String pkgName : deferPackages) {
AndroidPackage pkg = null;
synchronized (mLock) {
@@ -3490,13 +3492,14 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
if (pkg != null) {
- synchronized (mInstallLock) {
- prepareAppDataAndMigrateLIF(pkg, UserHandle.USER_SYSTEM, storageFlags,
- true /* maybeMigrateAppData */);
- }
+ prepareAppDataAndMigrate(batch, pkg, UserHandle.USER_SYSTEM, storageFlags,
+ true /* maybeMigrateAppData */);
count++;
}
}
+ synchronized (mInstallLock) {
+ executeBatchLI(batch);
+ }
traceLog.traceEnd();
Slog.i(TAG, "Deferred reconcileAppsData finished " + count + " packages");
}, "prepareAppData");
@@ -22736,6 +22739,14 @@ public class PackageManagerService extends IPackageManager.Stub
}
}
+ private void executeBatchLI(@NonNull Installer.Batch batch) {
+ try {
+ batch.execute(mInstaller);
+ } catch (InstallerException e) {
+ Slog.w(TAG, "Failed to execute pending operations", e);
+ }
+ }
+
/**
* Examine all apps present on given mounted volume, and destroy apps that
* aren't expected, either due to uninstallation or reinstallation on
@@ -22875,6 +22886,8 @@ public class PackageManagerService extends IPackageManager.Stub
// Ensure that data directories are ready to roll for all packages
// installed for this volume and user
+ Trace.traceBegin(TRACE_TAG_PACKAGE_MANAGER, "prepareAppDataAndMigrate");
+ Installer.Batch batch = new Installer.Batch();
final List packages;
synchronized (mLock) {
packages = mSettings.getVolumePackagesLPr(volumeUuid);
@@ -22895,10 +22908,12 @@ public class PackageManagerService extends IPackageManager.Stub
}
if (ps.getInstalled(userId)) {
- prepareAppDataAndMigrateLIF(ps.pkg, userId, flags, migrateAppData);
+ prepareAppDataAndMigrate(batch, ps.pkg, userId, flags, migrateAppData);
preparedCount++;
}
}
+ executeBatchLI(batch);
+ Trace.traceEnd(TRACE_TAG_PACKAGE_MANAGER);
Slog.v(TAG, "reconcileAppsData finished " + preparedCount + " packages");
return result;
@@ -22923,6 +22938,7 @@ public class PackageManagerService extends IPackageManager.Stub
mSettings.writeKernelMappingLPr(ps);
}
+ Installer.Batch batch = new Installer.Batch();
UserManagerInternal umInternal = mInjector.getUserManagerInternal();
StorageManagerInternal smInternal = mInjector.getStorageManagerInternal();
for (UserInfo user : mUserManager.getUsers(false /*excludeDying*/)) {
@@ -22937,16 +22953,20 @@ public class PackageManagerService extends IPackageManager.Stub
if (ps.getInstalled(user.id)) {
// TODO: when user data is locked, mark that we're still dirty
- prepareAppDataLIF(pkg, user.id, flags);
-
- if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
- // Prepare app data on external storage; currently this is used to
- // setup any OBB dirs that were created by the installer correctly.
- int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
- smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
- }
+ prepareAppData(batch, pkg, user.id, flags).thenRun(() -> {
+ // Note: this code block is executed with the Installer lock
+ // already held, since it's invoked as a side-effect of
+ // executeBatchLI()
+ if (umInternal.isUserUnlockingOrUnlocked(user.id)) {
+ // Prepare app data on external storage; currently this is used to
+ // setup any OBB dirs that were created by the installer correctly.
+ int uid = UserHandle.getUid(user.id, UserHandle.getAppId(pkg.getUid()));
+ smInternal.prepareAppDataAfterInstall(pkg.getPackageName(), uid);
+ }
+ });
}
}
+ executeBatchLI(batch);
}
/**
@@ -22957,26 +22977,33 @@ public class PackageManagerService extends IPackageManager.Stub
* will try recovering system apps by wiping data; third-party app data is
* left intact.
*/
- private void prepareAppDataLIF(AndroidPackage pkg, int userId, int flags) {
+ private @NonNull CompletableFuture> prepareAppData(@NonNull Installer.Batch batch,
+ @Nullable AndroidPackage pkg, int userId, int flags) {
if (pkg == null) {
Slog.wtf(TAG, "Package was null!", new Throwable());
- return;
+ return CompletableFuture.completedFuture(null);
}
- prepareAppDataLeafLIF(pkg, userId, flags);
+ return prepareAppDataLeaf(batch, pkg, userId, flags);
}
- private void prepareAppDataAndMigrateLIF(AndroidPackage pkg, int userId, int flags,
- boolean maybeMigrateAppData) {
- prepareAppDataLIF(pkg, userId, flags);
-
- if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
- // We may have just shuffled around app data directories, so
- // prepare them one more time
- prepareAppDataLIF(pkg, userId, flags);
- }
+ private @NonNull CompletableFuture> prepareAppDataAndMigrate(@NonNull Installer.Batch batch,
+ @NonNull AndroidPackage pkg, int userId, int flags, boolean maybeMigrateAppData) {
+ return prepareAppData(batch, pkg, userId, flags).thenRun(() -> {
+ // Note: this code block is executed with the Installer lock
+ // already held, since it's invoked as a side-effect of
+ // executeBatchLI()
+ if (maybeMigrateAppData && maybeMigrateAppDataLIF(pkg, userId)) {
+ // We may have just shuffled around app data directories, so
+ // prepare them one more time
+ final Installer.Batch batchInner = new Installer.Batch();
+ prepareAppData(batchInner, pkg, userId, flags);
+ executeBatchLI(batchInner);
+ }
+ });
}
- private void prepareAppDataLeafLIF(AndroidPackage pkg, int userId, int flags) {
+ private @NonNull CompletableFuture> prepareAppDataLeaf(@NonNull Installer.Batch batch,
+ @NonNull AndroidPackage pkg, int userId, int flags) {
if (DEBUG_APP_DATA) {
Slog.v(TAG, "prepareAppData for " + pkg.getPackageName() + " u" + userId + " 0x"
+ Integer.toHexString(flags));
@@ -22996,57 +23023,67 @@ public class PackageManagerService extends IPackageManager.Stub
Preconditions.checkNotNull(pkgSeInfo);
final String seInfo = pkgSeInfo + (pkg.getSeInfoUser() != null ? pkg.getSeInfoUser() : "");
- long ceDataInode = -1;
- try {
- ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, seInfo, pkg.getTargetSdkVersion());
- } catch (InstallerException e) {
- if (pkg.isSystem()) {
- logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
- + ", but trying to recover: " + e);
- destroyAppDataLeafLIF(pkg, userId, flags);
- try {
- ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId, flags,
- appId, seInfo, pkg.getTargetSdkVersion());
- logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
- } catch (InstallerException e2) {
- logCriticalInfo(Log.DEBUG, "Recovery failed!");
- }
- } else {
- Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
- }
- }
- // Prepare the application profiles only for upgrades and first boot (so that we don't
- // repeat the same operation at each boot).
- // We only have to cover the upgrade and first boot here because for app installs we
- // prepare the profiles before invoking dexopt (in installPackageLI).
- //
- // We also have to cover non system users because we do not call the usual install package
- // methods for them.
- //
- // NOTE: in order to speed up first boot time we only create the current profile and do not
- // update the content of the reference profile. A system image should already be configured
- // with the right profile keys and the profiles for the speed-profile prebuilds should
- // already be copied. That's done in #performDexOptUpgrade.
- //
- // TODO(calin, mathieuc): We should use .dm files for prebuilds profiles instead of
- // manually copying them in #performDexOptUpgrade. When we do that we should have a more
- // granular check here and only update the existing profiles.
- if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
- mArtManagerService.prepareAppProfiles(pkg, userId,
- /* updateReferenceProfileContent= */ false);
- }
+ final int targetSdkVersion = pkg.getTargetSdkVersion();
- if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
- // TODO: mark this structure as dirty so we persist it!
- synchronized (mLock) {
- if (ps != null) {
- ps.setCeDataInode(ceDataInode, userId);
- }
- }
- }
+ return batch.createAppData(volumeUuid, packageName, userId, flags, appId, seInfo,
+ targetSdkVersion).whenComplete((ceDataInode, e) -> {
+ // Note: this code block is executed with the Installer lock
+ // already held, since it's invoked as a side-effect of
+ // executeBatchLI()
+ if ((e != null) && pkg.isSystem()) {
+ logCriticalInfo(Log.ERROR, "Failed to create app data for " + packageName
+ + ", but trying to recover: " + e);
+ destroyAppDataLeafLIF(pkg, userId, flags);
+ try {
+ ceDataInode = mInstaller.createAppData(volumeUuid, packageName, userId,
+ flags, appId, seInfo, pkg.getTargetSdkVersion());
+ logCriticalInfo(Log.DEBUG, "Recovery succeeded!");
+ } catch (InstallerException e2) {
+ logCriticalInfo(Log.DEBUG, "Recovery failed!");
+ }
+ } else if (e != null) {
+ Slog.e(TAG, "Failed to create app data for " + packageName + ": " + e);
+ }
- prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+ // Prepare the application profiles only for upgrades and
+ // first boot (so that we don't repeat the same operation at
+ // each boot).
+ //
+ // We only have to cover the upgrade and first boot here
+ // because for app installs we prepare the profiles before
+ // invoking dexopt (in installPackageLI).
+ //
+ // We also have to cover non system users because we do not
+ // call the usual install package methods for them.
+ //
+ // NOTE: in order to speed up first boot time we only create
+ // the current profile and do not update the content of the
+ // reference profile. A system image should already be
+ // configured with the right profile keys and the profiles
+ // for the speed-profile prebuilds should already be copied.
+ // That's done in #performDexOptUpgrade.
+ //
+ // TODO(calin, mathieuc): We should use .dm files for
+ // prebuilds profiles instead of manually copying them in
+ // #performDexOptUpgrade. When we do that we should have a
+ // more granular check here and only update the existing
+ // profiles.
+ if (mIsUpgrade || mFirstBoot || (userId != UserHandle.USER_SYSTEM)) {
+ mArtManagerService.prepareAppProfiles(pkg, userId,
+ /* updateReferenceProfileContent= */ false);
+ }
+
+ if ((flags & StorageManager.FLAG_STORAGE_CE) != 0 && ceDataInode != -1) {
+ // TODO: mark this structure as dirty so we persist it!
+ synchronized (mLock) {
+ if (ps != null) {
+ ps.setCeDataInode(ceDataInode, userId);
+ }
+ }
+ }
+
+ prepareAppDataContentsLeafLIF(pkg, ps, userId, flags);
+ });
}
private void prepareAppDataContentsLIF(AndroidPackage pkg, @Nullable PackageSetting pkgSetting,
diff --git a/services/core/java/com/android/server/pm/Settings.java b/services/core/java/com/android/server/pm/Settings.java
index 0061480c97f1b..826b082635f93 100644
--- a/services/core/java/com/android/server/pm/Settings.java
+++ b/services/core/java/com/android/server/pm/Settings.java
@@ -102,6 +102,7 @@ import com.android.internal.util.XmlUtils;
import com.android.permission.persistence.RuntimePermissionsPersistence;
import com.android.permission.persistence.RuntimePermissionsState;
import com.android.server.LocalServices;
+import com.android.server.pm.Installer.Batch;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.parsing.PackageInfoUtils;
import com.android.server.pm.parsing.pkg.AndroidPackage;
@@ -4148,24 +4149,12 @@ public final class Settings {
final TimingsTraceAndSlog t = new TimingsTraceAndSlog(TAG + "Timing",
Trace.TRACE_TAG_PACKAGE_MANAGER);
t.traceBegin("createNewUser-" + userHandle);
- String[] volumeUuids;
- String[] names;
- int[] appIds;
- String[] seinfos;
- int[] targetSdkVersions;
- int packagesCount;
+ Installer.Batch batch = new Installer.Batch();
final boolean skipPackageWhitelist = userTypeInstallablePackages == null;
synchronized (mLock) {
- Collection packages = mPackages.values();
- packagesCount = packages.size();
- volumeUuids = new String[packagesCount];
- names = new String[packagesCount];
- appIds = new int[packagesCount];
- seinfos = new String[packagesCount];
- targetSdkVersions = new int[packagesCount];
- Iterator packagesIterator = packages.iterator();
- for (int i = 0; i < packagesCount; i++) {
- PackageSetting ps = packagesIterator.next();
+ final int size = mPackages.size();
+ for (int i = 0; i < size; i++) {
+ final PackageSetting ps = mPackages.valueAt(i);
if (ps.pkg == null) {
continue;
}
@@ -4187,18 +4176,15 @@ public final class Settings {
}
// Need to create a data directory for all apps under this user. Accumulate all
// required args and call the installer after mPackages lock has been released
- volumeUuids[i] = ps.volumeUuid;
- names[i] = ps.name;
- appIds[i] = ps.appId;
- seinfos[i] = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
- targetSdkVersions[i] = ps.pkg.getTargetSdkVersion();
+ final String seInfo = AndroidPackageUtils.getSeInfo(ps.pkg, ps);
+ batch.createAppData(ps.volumeUuid, ps.name, userHandle,
+ StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE, ps.appId,
+ seInfo, ps.pkg.getTargetSdkVersion());
}
}
t.traceBegin("createAppData");
- final int flags = StorageManager.FLAG_STORAGE_CE | StorageManager.FLAG_STORAGE_DE;
try {
- installer.createAppDataBatched(volumeUuids, names, userHandle, flags, appIds, seinfos,
- targetSdkVersions);
+ batch.execute(installer);
} catch (InstallerException e) {
Slog.w(TAG, "Failed to prepare app data", e);
}