diff --git a/AconfigFlags.bp b/AconfigFlags.bp index f0b759878e20e2c0243da631a8ee085035ff068d..f2cae097f518374476cff6055156964c4ac03824 100644 --- a/AconfigFlags.bp +++ b/AconfigFlags.bp @@ -13,6 +13,7 @@ // limitations under the License. aconfig_srcjars = [ + ":android.content.pm.flags-aconfig-java{.generated_srcjars}", ":android.hardware.biometrics.flags-aconfig-java{.generated_srcjars}", ":android.nfc.flags-aconfig-java{.generated_srcjars}", ":android.os.flags-aconfig-java{.generated_srcjars}", @@ -145,6 +146,26 @@ java_aconfig_library { defaults: ["framework-minus-apex-aconfig-java-defaults"], } +// Package Manager +aconfig_declarations { + name: "android.content.pm.flags-aconfig", + package: "android.content.pm", + srcs: ["core/java/android/content/pm/flags.aconfig"], +} + +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java", + aconfig_declarations: "android.content.pm.flags-aconfig", + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + +java_aconfig_library { + name: "android.content.pm.flags-aconfig-java-host", + aconfig_declarations: "android.content.pm.flags-aconfig", + host_supported: true, + defaults: ["framework-minus-apex-aconfig-java-defaults"], +} + // Biometrics aconfig_declarations { name: "android.hardware.biometrics.flags-aconfig", diff --git a/core/java/android/content/pm/PackageInstaller.java b/core/java/android/content/pm/PackageInstaller.java index ef26235a38792e4f6dc43097f5a4c2b1613d50cf..f11b1b7ee76d76d86f8cf56f04d0386c43bd6d49 100644 --- a/core/java/android/content/pm/PackageInstaller.java +++ b/core/java/android/content/pm/PackageInstaller.java @@ -344,6 +344,13 @@ public class PackageInstaller { public static final String EXTRA_RESOLVED_BASE_PATH = "android.content.pm.extra.RESOLVED_BASE_PATH"; + /** + * A list of warnings that occurred during installation. + * + * @hide + */ + public static final String EXTRA_WARNINGS = "android.content.pm.extra.WARNINGS"; + /** * Streaming installation pending. * Caller should make sure DataLoader is able to prepare image and reinitiate the operation. diff --git a/core/java/android/content/pm/flags.aconfig b/core/java/android/content/pm/flags.aconfig new file mode 100644 index 0000000000000000000000000000000000000000..524253371e49b9cdebea19202ed7bc72014d0012 --- /dev/null +++ b/core/java/android/content/pm/flags.aconfig @@ -0,0 +1,8 @@ +package: "android.content.pm" + +flag { + name: "use_art_service_v2" + namespace: "package_manager_service" + description: "Feature flag to enable the features that rely on new ART Service APIs that are in the VIC version of the ART module." + bug: "304741685" +} diff --git a/services/core/java/com/android/server/pm/InstallRequest.java b/services/core/java/com/android/server/pm/InstallRequest.java index 6fc14e8145250b6f1ba00c6c7c381c09c3e2df4e..046dcc2dce7c12d89920fdaac307c43c505a81d1 100644 --- a/services/core/java/com/android/server/pm/InstallRequest.java +++ b/services/core/java/com/android/server/pm/InstallRequest.java @@ -22,6 +22,8 @@ import static android.content.pm.PackageManager.INSTALL_SCENARIO_DEFAULT; import static android.content.pm.PackageManager.INSTALL_SUCCEEDED; import static android.os.Process.INVALID_UID; +import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult; +import static com.android.server.art.model.DexoptResult.PackageDexoptResult; import static com.android.server.pm.PackageManagerService.SCAN_AS_INSTANT_APP; import static com.android.server.pm.PackageManagerService.TAG; @@ -30,6 +32,7 @@ import android.annotation.Nullable; import android.apex.ApexInfo; import android.app.AppOpsManager; import android.content.pm.DataLoaderType; +import android.content.pm.Flags; import android.content.pm.IPackageInstallObserver2; import android.content.pm.PackageInstaller; import android.content.pm.PackageManager; @@ -52,6 +55,7 @@ import com.android.server.pm.pkg.parsing.ParsingPackageUtils; import java.io.File; import java.util.ArrayList; +import java.util.LinkedHashSet; import java.util.List; final class InstallRequest { @@ -130,6 +134,9 @@ final class InstallRequest { private int mDexoptStatus; + @NonNull + private ArrayList<String> mWarnings = new ArrayList<>(); + // New install InstallRequest(InstallingSession params) { mUserId = params.getUser().getIdentifier(); @@ -615,6 +622,11 @@ final class InstallRequest { return mDexoptStatus; } + @NonNull + public ArrayList<String> getWarnings() { + return mWarnings; + } + public void setScanFlags(int scanFlags) { mScanFlags = scanFlags; } @@ -757,6 +769,10 @@ final class InstallRequest { } } + public void addWarning(@NonNull String warning) { + mWarnings.add(warning); + } + public void onPrepareStarted() { if (mPackageMetrics != null) { mPackageMetrics.onStepStarted(PackageMetrics.STEP_PREPARE); @@ -806,22 +822,37 @@ final class InstallRequest { } public void onDexoptFinished(DexoptResult dexoptResult) { - if (mPackageMetrics == null) { - return; - } - mDexoptStatus = dexoptResult.getFinalStatus(); - if (mDexoptStatus != DexoptResult.DEXOPT_PERFORMED) { - return; + // Only report external profile warnings when installing from adb. The goal is to warn app + // developers if they have provided bad external profiles, so it's not beneficial to report + // those warnings in the normal app install workflow. + if (isInstallFromAdb() && Flags.useArtServiceV2()) { + var externalProfileErrors = new LinkedHashSet<String>(); + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + externalProfileErrors.addAll(fileResult.getExternalProfileErrors()); + } + } + if (!externalProfileErrors.isEmpty()) { + addWarning("Error occurred during dexopt when processing external profiles:\n " + + String.join("\n ", externalProfileErrors)); + } } - long durationMillis = 0; - for (DexoptResult.PackageDexoptResult packageResult : - dexoptResult.getPackageDexoptResults()) { - for (DexoptResult.DexContainerFileDexoptResult fileResult : - packageResult.getDexContainerFileDexoptResults()) { - durationMillis += fileResult.getDex2oatWallTimeMillis(); + + // Report dexopt metrics. + if (mPackageMetrics != null) { + mDexoptStatus = dexoptResult.getFinalStatus(); + if (mDexoptStatus == DexoptResult.DEXOPT_PERFORMED) { + long durationMillis = 0; + for (PackageDexoptResult packageResult : dexoptResult.getPackageDexoptResults()) { + for (DexContainerFileDexoptResult fileResult : + packageResult.getDexContainerFileDexoptResults()) { + durationMillis += fileResult.getDex2oatWallTimeMillis(); + } + } + mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } } - mPackageMetrics.onStepFinished(PackageMetrics.STEP_DEXOPT, durationMillis); } public void onInstallCompleted() { diff --git a/services/core/java/com/android/server/pm/PackageInstallerSession.java b/services/core/java/com/android/server/pm/PackageInstallerSession.java index f0e38955f05004989ddf2908a1531e9b2264a1cb..3e72fe4f95b41f5a8c916b47b8f0064cb1da26d4 100644 --- a/services/core/java/com/android/server/pm/PackageInstallerSession.java +++ b/services/core/java/com/android/server/pm/PackageInstallerSession.java @@ -2714,15 +2714,40 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { * @return a future that will be completed when the whole process is completed. */ private CompletableFuture<Void> install() { + // `futures` either contains only one session (`this`) or contains one parent session + // (`this`) and n-1 child sessions. List<CompletableFuture<InstallResult>> futures = installNonStaged(); CompletableFuture<InstallResult>[] arr = new CompletableFuture[futures.size()]; return CompletableFuture.allOf(futures.toArray(arr)).whenComplete((r, t) -> { if (t == null) { setSessionApplied(); + var multiPackageWarnings = new ArrayList<String>(); + if (isMultiPackage()) { + // This is a parent session. Collect warnings from children. + for (CompletableFuture<InstallResult> f : futures) { + InstallResult result = f.join(); + if (result.session != this && result.extras != null) { + ArrayList<String> childWarnings = result.extras.getStringArrayList( + PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(childWarnings)) { + multiPackageWarnings.addAll(childWarnings); + } + } + } + } for (CompletableFuture<InstallResult> f : futures) { InstallResult result = f.join(); + Bundle extras = result.extras; + if (isMultiPackage() && result.session == this + && !multiPackageWarnings.isEmpty()) { + if (extras == null) { + extras = new Bundle(); + } + extras.putStringArrayList( + PackageInstaller.EXTRA_WARNINGS, multiPackageWarnings); + } result.session.dispatchSessionFinished( - INSTALL_SUCCEEDED, "Session installed", result.extras); + INSTALL_SUCCEEDED, "Session installed", extras); } } else { PackageManagerException e = (PackageManagerException) t.getCause(); @@ -4904,6 +4929,10 @@ public class PackageInstallerSession extends IPackageInstallerSession.Stub { if (!TextUtils.isEmpty(existing)) { fillIn.putExtra(PackageInstaller.EXTRA_OTHER_PACKAGE_NAME, existing); } + ArrayList<String> warnings = extras.getStringArrayList(PackageInstaller.EXTRA_WARNINGS); + if (!ArrayUtils.isEmpty(warnings)) { + fillIn.putStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS, warnings); + } } try { final BroadcastOptions options = BroadcastOptions.makeBasic(); diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java index f2b62eaf25f865c61e1750a40d14efbdc8401f05..20bd56d56945bc996e4dc4e6934b270d917fece0 100644 --- a/services/core/java/com/android/server/pm/PackageManagerService.java +++ b/services/core/java/com/android/server/pm/PackageManagerService.java @@ -1415,6 +1415,9 @@ public class PackageManagerService implements PackageSender, TestUtilityService break; } } + if (!request.getWarnings().isEmpty()) { + extras.putStringArrayList(PackageInstaller.EXTRA_WARNINGS, request.getWarnings()); + } return extras; } diff --git a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java index 2a7ebfb75dde77e68afd24eda01aaf7007885439..2327b85ed0bb28dfbce4af97f4670799649fddc6 100644 --- a/services/core/java/com/android/server/pm/PackageManagerShellCommand.java +++ b/services/core/java/com/android/server/pm/PackageManagerShellCommand.java @@ -3982,10 +3982,21 @@ class PackageManagerShellCommand extends ShellCommand { session.commit(receiver.getIntentSender()); if (!session.isStaged()) { final Intent result = receiver.getResult(); - final int status = result.getIntExtra(PackageInstaller.EXTRA_STATUS, - PackageInstaller.STATUS_FAILURE); + int status = result.getIntExtra( + PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); + List<String> warnings = + result.getStringArrayListExtra(PackageInstaller.EXTRA_WARNINGS); if (status == PackageInstaller.STATUS_SUCCESS) { - if (logSuccess) { + if (!ArrayUtils.isEmpty(warnings)) { + // Don't start the output string with "Success" because that will make adb + // treat this as a success. + for (String warning : warnings) { + pw.println("Warning: " + warning); + } + // Treat warnings as failure to draw app developers' attention. + status = PackageInstaller.STATUS_FAILURE; + pw.println("Completed with warning(s)"); + } else if (logSuccess) { pw.println("Success"); } } else {