diff options
-rw-r--r-- | artd/artd.cc | 56 | ||||
-rw-r--r-- | artd/artd.h | 5 | ||||
-rw-r--r-- | artd/artd_test.cc | 87 | ||||
-rw-r--r-- | artd/binder/com/android/server/art/IArtd.aidl | 16 | ||||
-rw-r--r-- | artd/file_utils.cc | 14 | ||||
-rw-r--r-- | artd/file_utils.h | 6 | ||||
-rw-r--r-- | artd/path_utils.cc | 15 | ||||
-rw-r--r-- | artd/path_utils.h | 3 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/AidlUtils.java | 13 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/ArtManagerLocal.java | 95 | ||||
-rw-r--r-- | libartservice/service/java/com/android/server/art/DexUseManagerLocal.java | 6 | ||||
-rw-r--r-- | libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java | 63 | ||||
-rw-r--r-- | libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java | 8 |
13 files changed, 381 insertions, 6 deletions
diff --git a/artd/artd.cc b/artd/artd.cc index 6154c8207b..0c9b69a697 100644 --- a/artd/artd.cc +++ b/artd/artd.cc @@ -129,6 +129,7 @@ using ::art::tools::NonFatal; using ::ndk::ScopedAStatus; using TmpProfilePath = ProfilePath::TmpProfilePath; +using WritableProfilePath = ProfilePath::WritableProfilePath; constexpr const char* kServiceName = "artd"; constexpr const char* kPreRebootServiceName = "artd_pre_reboot"; @@ -1370,6 +1371,61 @@ ScopedAStatus Artd::getProfileSize(const ProfilePath& in_profile, int64_t* _aidl return ScopedAStatus::ok(); } +ScopedAStatus Artd::commitPreRebootStagedFiles( + const std::vector<ArtifactsPath>& in_artifacts, + const std::vector<WritableProfilePath>& in_profiles) { + RETURN_FATAL_IF_PRE_REBOOT(options_); + + std::vector<std::pair<std::string, std::string>> files_to_move; + std::vector<std::string> files_to_remove; + + for (const ArtifactsPath& artifacts : in_artifacts) { + RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(artifacts, "artifacts"); + + ArtifactsPath pre_reboot_artifacts = artifacts; + pre_reboot_artifacts.isPreReboot = true; + + auto src_artifacts = std::make_unique<RawArtifactsPath>( + OR_RETURN_FATAL(BuildArtifactsPath(pre_reboot_artifacts))); + auto dst_artifacts = + std::make_unique<RawArtifactsPath>(OR_RETURN_FATAL(BuildArtifactsPath(artifacts))); + + if (OS::FileExists(src_artifacts->oat_path.c_str())) { + files_to_move.emplace_back(src_artifacts->oat_path, dst_artifacts->oat_path); + files_to_move.emplace_back(src_artifacts->vdex_path, dst_artifacts->vdex_path); + if (OS::FileExists(src_artifacts->art_path.c_str())) { + files_to_move.emplace_back(src_artifacts->art_path, dst_artifacts->art_path); + } else { + files_to_remove.push_back(dst_artifacts->art_path); + } + } + } + + for (const WritableProfilePath& profile : in_profiles) { + RETURN_FATAL_IF_ARG_IS_PRE_REBOOT(profile, "profiles"); + + WritableProfilePath pre_reboot_profile = profile; + PreRebootFlag(pre_reboot_profile) = true; + + auto src_profile = std::make_unique<std::string>( + OR_RETURN_FATAL(BuildWritableProfilePath(pre_reboot_profile))); + auto dst_profile = + std::make_unique<std::string>(OR_RETURN_FATAL(BuildWritableProfilePath(profile))); + + if (OS::FileExists(src_profile->c_str())) { + files_to_move.emplace_back(*src_profile, *dst_profile); + } + } + + OR_RETURN_NON_FATAL(MoveAllOrAbandon(files_to_move, files_to_remove)); + + for (const auto& [src_path, dst_path] : files_to_move) { + LOG(INFO) << ART_FORMAT("Committed Pre-reboot staged file '{}' to '{}'", src_path, dst_path); + } + + return ScopedAStatus::ok(); +} + Result<void> Artd::Start() { OR_RETURN(SetLogVerbosity()); MemMap::Init(); diff --git a/artd/artd.h b/artd/artd.h index a1f3541fc0..37b6d0c42f 100644 --- a/artd/artd.h +++ b/artd/artd.h @@ -221,6 +221,11 @@ class Artd : public aidl::com::android::server::art::BnArtd { ndk::ScopedAStatus getProfileSize(const aidl::com::android::server::art::ProfilePath& in_profile, int64_t* _aidl_return) override; + ndk::ScopedAStatus commitPreRebootStagedFiles( + const std::vector<aidl::com::android::server::art::ArtifactsPath>& in_artifacts, + const std::vector<aidl::com::android::server::art::ProfilePath::WritableProfilePath>& + in_profiles) override; + ndk::ScopedAStatus preRebootInit() override; ndk::ScopedAStatus validateDexPath(const std::string& in_dexFile, diff --git a/artd/artd_test.cc b/artd/artd_test.cc index fccf091464..fb99bbb60b 100644 --- a/artd/artd_test.cc +++ b/artd/artd_test.cc @@ -2446,6 +2446,93 @@ TEST_F(ArtdTest, getProfileSize) { EXPECT_EQ(aidl_return, 1); } +TEST_F(ArtdTest, commitPreRebootStagedFiles) { + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex.staged", + "new_odex_1"); + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex.staged", + "new_vdex_1"); + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art.staged", + "new_art_1"); + + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex", + "old_odex_1"); + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex", + "old_vdex_1"); + CreateFile(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art", "old_art_1"); + + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.odex", "old_odex_2"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.vdex", "old_vdex_2"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.art", "old_art_2"); + + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.odex.staged", "new_odex_2"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.vdex.staged", "new_vdex_2"); + + CreateFile(android_data_ + "/app/com.android.foo/oat/arm/base.odex", "old_odex_3"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm/base.vdex", "old_vdex_3"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm/base.art", "old_art_3"); + + CreateFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof", "old_prof_1"); + CreateFile(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof.staged", + "new_prof_1"); + + CreateFile(android_data_ + "/misc/profiles/ref/com.android.bar/primary.prof", "old_prof_2"); + + ASSERT_STATUS_OK(artd_->commitPreRebootStagedFiles( + { + // Has all new files. All old files should be replaced. + ArtifactsPath{ + .dexPath = "/system/app/Foo/Foo.apk", .isa = "arm64", .isInDalvikCache = true}, + // Has new files but not ".art" file. Old ".odex" and ".vdex" files should be replaced, + // and old ".art" file should be removed. + ArtifactsPath{.dexPath = android_data_ + "/app/com.android.foo/base.apk", + .isa = "arm64", + .isInDalvikCache = false}, + // Has no new file. All old files should be kept. + ArtifactsPath{.dexPath = android_data_ + "/app/com.android.foo/base.apk", + .isa = "arm", + .isInDalvikCache = false}, + }, + { + // Has new file. + PrimaryRefProfilePath{.packageName = "com.android.foo", .profileName = "primary"}, + // Has no new file. + PrimaryRefProfilePath{.packageName = "com.android.bar", .profileName = "primary"}, + })); + + CheckContent(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex", + "new_odex_1"); + CheckContent(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex", + "new_vdex_1"); + CheckContent(android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art", + "new_art_1"); + + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.odex", "new_odex_2"); + CreateFile(android_data_ + "/app/com.android.foo/oat/arm64/base.vdex", "new_vdex_2"); + EXPECT_FALSE(std::filesystem::exists(android_data_ + "/app/com.android.foo/oat/arm64/base.art")); + + CheckContent(android_data_ + "/app/com.android.foo/oat/arm/base.odex", "old_odex_3"); + CheckContent(android_data_ + "/app/com.android.foo/oat/arm/base.vdex", "old_vdex_3"); + CheckContent(android_data_ + "/app/com.android.foo/oat/arm/base.art", "old_art_3"); + + CheckContent(android_data_ + "/misc/profiles/ref/com.android.foo/primary.prof", "new_prof_1"); + + CheckContent(android_data_ + "/misc/profiles/ref/com.android.bar/primary.prof", "old_prof_2"); + + // All staged files are gone. + EXPECT_FALSE(std::filesystem::exists( + android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.dex.staged")); + EXPECT_FALSE(std::filesystem::exists( + android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.vdex.staged")); + EXPECT_FALSE(std::filesystem::exists( + android_data_ + "/dalvik-cache/arm64/system@app@Foo@Foo.apk@classes.art.staged")); + EXPECT_FALSE( + std::filesystem::exists(android_data_ + "/app/com.android.foo/oat/arm64/base.odex.staged")); + EXPECT_FALSE( + std::filesystem::exists(android_data_ + "/app/com.android.foo/oat/arm64/base.vdex.staged")); + EXPECT_FALSE(std::filesystem::exists(android_data_ + + "/misc/profiles/ref/com.android.foo/primary.prof.staged")); +} + class ArtdPreRebootTest : public ArtdTest { protected: void SetUp() override { diff --git a/artd/binder/com/android/server/art/IArtd.aidl b/artd/binder/com/android/server/art/IArtd.aidl index 7f0bc5690c..4532a8fb11 100644 --- a/artd/binder/com/android/server/art/IArtd.aidl +++ b/artd/binder/com/android/server/art/IArtd.aidl @@ -251,6 +251,22 @@ interface IArtd { */ long getProfileSize(in com.android.server.art.ProfilePath profile); + /** + * Moves the staged files of the given artifacts and profiles to the permanent locations, + * replacing old files if they exist. Removes the staged files and restores the old files at + * best effort if any error occurs. + * + * This is intended to be called for a superset of the packages that we actually expect to have + * staged files, so missing files are expected. + * + * Not supported in Pre-reboot Dexopt mode. + * + * Throws fatal and non-fatal errors. + */ + void commitPreRebootStagedFiles( + in List<com.android.server.art.ArtifactsPath> artifacts, + in List<com.android.server.art.ProfilePath.WritableProfilePath> profiles); + // The methods below are only for Pre-reboot Dexopt and only supported in Pre-reboot Dexopt // mode. diff --git a/artd/file_utils.cc b/artd/file_utils.cc index 1415efbdb3..195daf6162 100644 --- a/artd/file_utils.cc +++ b/artd/file_utils.cc @@ -230,6 +230,20 @@ Result<void> MoveAllOrAbandon( return {}; } +android::base::Result<void> MoveAllOrAbandon( + const std::vector<std::pair<std::string, std::string>>& files_to_move, + const std::vector<std::string>& files_to_remove) { + std::vector<std::pair<std::string_view, std::string_view>> files_to_move_view; + std::vector<std::string_view> files_to_remove_view; + for (const auto& [src, dst] : files_to_move) { + files_to_move_view.emplace_back(src, dst); + } + for (const std::string& file : files_to_remove) { + files_to_remove_view.emplace_back(file); + } + return MoveAllOrAbandon(files_to_move_view, files_to_remove_view); +} + std::string NewFile::BuildTempPath(std::string_view final_path, const std::string& id) { return ART_FORMAT("{}.{}.tmp", final_path, id); } diff --git a/artd/file_utils.h b/artd/file_utils.h index a97f52c42a..77c7ffb19c 100644 --- a/artd/file_utils.h +++ b/artd/file_utils.h @@ -20,6 +20,7 @@ #include <sys/types.h> #include <memory> +#include <string> #include <string_view> #include <utility> #include <vector> @@ -147,6 +148,11 @@ android::base::Result<void> MoveAllOrAbandon( const std::vector<std::pair<std::string_view, std::string_view>>& files_to_move, const std::vector<std::string_view>& files_to_remove = {}); +// Same as above, but takes `std::string`s. +android::base::Result<void> MoveAllOrAbandon( + const std::vector<std::pair<std::string, std::string>>& files_to_move, + const std::vector<std::string>& files_to_remove = {}); + } // namespace artd } // namespace art diff --git a/artd/path_utils.cc b/artd/path_utils.cc index d3eb6d4dfe..7cd9413dea 100644 --- a/artd/path_utils.cc +++ b/artd/path_utils.cc @@ -240,18 +240,21 @@ Result<std::string> BuildSecondaryCurProfilePath( "{}/oat/{}.cur.prof", dex_path.parent_path().string(), dex_path.filename().string()); } -Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) { - const WritableProfilePath& final_path = tmp_profile_path.finalPath; - switch (final_path.getTag()) { +Result<std::string> BuildWritableProfilePath(const WritableProfilePath& profile_path) { + switch (profile_path.getTag()) { case WritableProfilePath::forPrimary: - return BuildPrimaryRefProfilePath(final_path.get<WritableProfilePath::forPrimary>()); + return BuildPrimaryRefProfilePath(profile_path.get<WritableProfilePath::forPrimary>()); case WritableProfilePath::forSecondary: - return BuildSecondaryRefProfilePath(final_path.get<WritableProfilePath::forSecondary>()); + return BuildSecondaryRefProfilePath(profile_path.get<WritableProfilePath::forSecondary>()); // No default. All cases should be explicitly handled, or the compilation will fail. } // This should never happen. Just in case we get a non-enumerator value. LOG(FATAL) << ART_FORMAT("Unexpected writable profile path type {}", - fmt::underlying(final_path.getTag())); + fmt::underlying(profile_path.getTag())); +} + +Result<std::string> BuildFinalProfilePath(const TmpProfilePath& tmp_profile_path) { + return BuildWritableProfilePath(tmp_profile_path.finalPath); } Result<std::string> BuildTmpProfilePath(const TmpProfilePath& tmp_profile_path) { diff --git a/artd/path_utils.h b/artd/path_utils.h index a4a6e4f2a7..43a1043b42 100644 --- a/artd/path_utils.h +++ b/artd/path_utils.h @@ -78,6 +78,9 @@ android::base::Result<std::string> BuildSecondaryCurProfilePath( const aidl::com::android::server::art::ProfilePath::SecondaryCurProfilePath& secondary_cur_profile_path); +android::base::Result<std::string> BuildWritableProfilePath( + const aidl::com::android::server::art::ProfilePath::WritableProfilePath& profile_path); + android::base::Result<std::string> BuildFinalProfilePath( const aidl::com::android::server::art::ProfilePath::TmpProfilePath& tmp_profile_path); diff --git a/libartservice/service/java/com/android/server/art/AidlUtils.java b/libartservice/service/java/com/android/server/art/AidlUtils.java index d42a6539c4..6531e61668 100644 --- a/libartservice/service/java/com/android/server/art/AidlUtils.java +++ b/libartservice/service/java/com/android/server/art/AidlUtils.java @@ -208,6 +208,19 @@ public final class AidlUtils { } @NonNull + public static WritableProfilePath toWritableProfilePath(@NonNull ProfilePath profile) { + switch (profile.getTag()) { + case ProfilePath.primaryRefProfilePath: + return WritableProfilePath.forPrimary(profile.getPrimaryRefProfilePath()); + case ProfilePath.secondaryRefProfilePath: + return WritableProfilePath.forSecondary(profile.getSecondaryRefProfilePath()); + default: + throw new IllegalStateException("ProfilePath tag " + profile.getTag() + + " does not represent a writable type"); + } + } + + @NonNull public static String toString(@NonNull PrimaryRefProfilePath profile) { return String.format( "PrimaryRefProfilePath[packageName = %s, profileName = %s, isPreReboot = %b]", diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index 276e6eb7f5..afe2ab0538 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -20,8 +20,10 @@ import static com.android.server.art.ArtFileManager.ProfileLists; import static com.android.server.art.ArtFileManager.UsableArtifactLists; import static com.android.server.art.ArtFileManager.WritableArtifactLists; import static com.android.server.art.DexMetadataHelper.DexMetadataInfo; +import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; import static com.android.server.art.PrimaryDexUtils.DetailedPrimaryDexInfo; import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; +import static com.android.server.art.ProfilePath.WritableProfilePath; import static com.android.server.art.ReasonMapping.BatchDexoptReason; import static com.android.server.art.ReasonMapping.BootReason; import static com.android.server.art.Utils.Abi; @@ -39,7 +41,10 @@ import android.annotation.SystemApi; import android.annotation.SystemService; import android.app.job.JobInfo; import android.apphibernation.AppHibernationManager; +import android.content.BroadcastReceiver; import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.os.Binder; import android.os.Build; import android.os.CancellationSignal; @@ -58,6 +63,7 @@ import android.util.Pair; import androidx.annotation.RequiresApi; import com.android.internal.annotations.VisibleForTesting; +import com.android.modules.utils.build.SdkLevel; import com.android.server.LocalManagerRegistry; import com.android.server.art.model.ArtFlags; import com.android.server.art.model.ArtManagedFileStats; @@ -126,6 +132,8 @@ public final class ArtManagerLocal { @NonNull private final Injector mInjector; + private boolean mShouldCommitPreRebootStagedFiles = false; + @Deprecated public ArtManagerLocal() { mInjector = new Injector(); @@ -864,12 +872,49 @@ public final class ArtManagerLocal { @Nullable @CallbackExecutor Executor progressCallbackExecutor, @Nullable Consumer<OperationProgress> progressCallback) { try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) { + if ((bootReason.equals(ReasonMapping.REASON_BOOT_AFTER_OTA) + || bootReason.equals(ReasonMapping.REASON_BOOT_AFTER_MAINLINE_UPDATE)) + && SdkLevel.isAtLeastV()) { + // The staged files have to be committed in two phases, one during boot, for primary + // dex files, and another after boot complete, for secondary dex files. We need to + // commit files for primary dex files early because apps will start using them as + // soon as the package manager is initialized. We need to wait until boot complete + // to commit files for secondary dex files because they are not decrypted before + // then. + mShouldCommitPreRebootStagedFiles = true; + commitPreRebootStagedFiles(snapshot, false /* forSecondary */); + } dexoptPackages(snapshot, bootReason, new CancellationSignal(), progressCallbackExecutor, progressCallback != null ? Map.of(ArtFlags.PASS_MAIN, progressCallback) : null); } } /** + * Notifies this class that {@link Context#registerReceiver} is ready for use. + * + * Should be used by {@link DexUseManagerLocal} ONLY. + * + * @hide + */ + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + void systemReady() { + if (mShouldCommitPreRebootStagedFiles) { + mInjector.getContext().registerReceiver(new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + context.unregisterReceiver(this); + if (!SdkLevel.isAtLeastV()) { + throw new IllegalStateException("Broadcast receiver unexpectedly called"); + } + try (var snapshot = mInjector.getPackageManagerLocal().withFilteredSnapshot()) { + commitPreRebootStagedFiles(snapshot, true /* forSecondary */); + } + } + }, new IntentFilter(Intent.ACTION_BOOT_COMPLETED)); + } + } + + /** * Notifies ART Service that there are apexes staged for installation on next reboot (see * <a href="https://source.android.com/docs/core/ota/apex#apex-manager">the update sequence of * an APEX</a>). ART Service may use this to schedule a pre-reboot dexopt job. This might change @@ -1056,6 +1101,56 @@ public final class ArtManagerLocal { } } + /** @param forSecondary true for secondary dex files; false for primary dex files. */ + @RequiresApi(Build.VERSION_CODES.VANILLA_ICE_CREAM) + private void commitPreRebootStagedFiles( + @NonNull PackageManagerLocal.FilteredSnapshot snapshot, boolean forSecondary) { + try { + // Because we don't know for which packages the Pre-reboot Dexopt job has generated + // staged files, we call artd for all dexoptable packages, which is a superset of the + // packages that we actually expect to have staged files. + for (PackageState pkgState : snapshot.getPackageStates().values()) { + if (!Utils.canDexoptPackage(pkgState, null /* appHibernationManager */)) { + continue; + } + AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); + var options = ArtFileManager.Options.builder() + .setForPrimaryDex(!forSecondary) + .setForSecondaryDex(forSecondary) + .setExcludeForObsoleteDexesAndLoaders(true) + .build(); + List<ArtifactsPath> artifacts = + mInjector.getArtFileManager() + .getWritableArtifacts(pkgState, pkg, options) + .artifacts(); + List<WritableProfilePath> profiles = mInjector.getArtFileManager() + .getProfiles(pkgState, pkg, options) + .refProfiles() + .stream() + .map(AidlUtils::toWritableProfilePath) + .collect(Collectors.toList()); + try { + // The artd method commits all files somewhat transactionally. Here, we are + // committing files transactionally at the package level just for simplicity. In + // fact, we only need transaction on the split level: the artifacts and the + // profile of the same split must be committed transactionally. Consider the + // case where the staged artifacts and profile have less methods than the active + // ones generated by background dexopt, committing the artifacts while failing + // to commit the profile can potentially cause a permanent performance + // regression. + mInjector.getArtd().commitPreRebootStagedFiles(artifacts, profiles); + } catch (ServiceSpecificException e) { + Log.e(TAG, + "Failed to commit Pre-reboot staged files for package '" + + pkgState.getPackageName() + "'", + e); + } + } + } catch (RemoteException e) { + Utils.logArtdException(e); + } + } + /** * Should be used by {@link BackgroundDexoptJobService} ONLY. * diff --git a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java index c6394d3bd6..d5b0bab949 100644 --- a/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/DexUseManagerLocal.java @@ -169,6 +169,7 @@ public class DexUseManagerLocal { /** Notifies dex use manager that {@link Context#registerReceiver} is ready for use. */ public void systemReady() { Utils.check(!mInjector.isPreReboot()); + mInjector.getArtManagerLocal().systemReady(); // Save the data when the device is being shut down. The receiver is blocking, with a // 10s timeout. mInjector.getContext().registerReceiver(new BroadcastReceiver() { @@ -1221,5 +1222,10 @@ public class DexUseManagerLocal { return Objects.requireNonNull( LocalManagerRegistry.getManager(PackageManagerLocal.class)); } + + @NonNull + public ArtManagerLocal getArtManagerLocal() { + return Objects.requireNonNull(LocalManagerRegistry.getManager(ArtManagerLocal.class)); + } } } diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index fa217ab89d..098c28b108 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -48,6 +48,9 @@ import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.apphibernation.AppHibernationManager; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; import android.os.CancellationSignal; import android.os.ParcelFileDescriptor; import android.os.Process; @@ -83,6 +86,7 @@ import org.junit.runner.RunWith; import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameter; import org.junit.runners.Parameterized.Parameters; +import org.mockito.ArgumentCaptor; import org.mockito.Mock; import java.io.File; @@ -132,12 +136,14 @@ public class ArtManagerLocalTest { @Mock private StorageManager mStorageManager; @Mock private ArtdRefCache.Pin mArtdPin; @Mock private DexMetadataHelper.Injector mDexMetadataHelperInjector; + @Mock private Context mContext; private PackageState mPkgState1; private AndroidPackage mPkg1; private CheckedSecondaryDexInfo mPkg1SecondaryDexInfo1; private CheckedSecondaryDexInfo mPkg1SecondaryDexInfoNotFound; private Config mConfig; private DexMetadataHelper mDexMetadataHelper; + private ArgumentCaptor<BroadcastReceiver> mBroadcastReceiverCaptor; // True if the artifacts should be in dalvik-cache. @Parameter(0) public boolean mIsInDalvikCache; @@ -173,6 +179,7 @@ public class ArtManagerLocalTest { .when(mInjector.getArtFileManager()) .thenReturn(new ArtFileManager(mArtFileManagerInjector)); lenient().when(mInjector.getDexMetadataHelper()).thenReturn(mDexMetadataHelper); + lenient().when(mInjector.getContext()).thenReturn(mContext); lenient().when(mArtFileManagerInjector.getArtd()).thenReturn(mArtd); lenient().when(mArtFileManagerInjector.getUserManager()).thenReturn(mUserManager); @@ -186,6 +193,7 @@ public class ArtManagerLocalTest { lenient().when(SystemProperties.get(eq("pm.dexopt.install"))).thenReturn("speed-profile"); lenient().when(SystemProperties.get(eq("pm.dexopt.bg-dexopt"))).thenReturn("speed-profile"); lenient().when(SystemProperties.get(eq("pm.dexopt.first-boot"))).thenReturn("verify"); + lenient().when(SystemProperties.get(eq("pm.dexopt.boot-after-ota"))).thenReturn("verify"); lenient() .when(SystemProperties.get(eq("pm.dexopt.boot-after-mainline-update"))) .thenReturn("verify"); @@ -269,6 +277,11 @@ public class ArtManagerLocalTest { .when(mDexMetadataHelperInjector.openZipFile(any())) .thenThrow(NoSuchFileException.class); + mBroadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); + lenient() + .when(mContext.registerReceiver(mBroadcastReceiverCaptor.capture(), any())) + .thenReturn(mock(Intent.class)); + mArtManagerLocal = new ArtManagerLocal(mInjector); } @@ -1330,6 +1343,56 @@ public class ArtManagerLocalTest { verify(mArtd, times(expectedGetProfileSizeCalls)).getProfileSize(any()); } + @Test + public void testCommitPreRebootStagedFiles() throws Exception { + when(mSnapshot.getPackageStates()).thenReturn(Map.of(PKG_NAME_1, mPkgState1)); + + mArtManagerLocal.onBoot(ReasonMapping.REASON_BOOT_AFTER_OTA, + null /* progressCallbackExecutor */, null /* progressCallback */); + + // It should commit files for primary dex files on boot. + verify(mArtd).commitPreRebootStagedFiles( + inAnyOrderDeepEquals( + AidlUtils.buildArtifactsPathAsInput( + "/somewhere/app/foo/base.apk", "arm64", mIsInDalvikCache), + AidlUtils.buildArtifactsPathAsInput( + "/somewhere/app/foo/base.apk", "arm", mIsInDalvikCache), + AidlUtils.buildArtifactsPathAsInput( + "/somewhere/app/foo/split_0.apk", "arm64", mIsInDalvikCache), + AidlUtils.buildArtifactsPathAsInput( + "/somewhere/app/foo/split_0.apk", "arm", mIsInDalvikCache)), + inAnyOrderDeepEquals(AidlUtils.toWritableProfilePath( + AidlUtils.buildProfilePathForPrimaryRefAsInput( + PKG_NAME_1, "primary")), + AidlUtils.toWritableProfilePath( + AidlUtils.buildProfilePathForPrimaryRefAsInput( + PKG_NAME_1, "split_0.split")))); + verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any()); + + mArtManagerLocal.systemReady(); + + // It should not commit anything on system ready. + verify(mArtd, times(1)).commitPreRebootStagedFiles(any(), any()); + + mBroadcastReceiverCaptor.getValue().onReceive(mContext, mock(Intent.class)); + + // It should commit files for secondary dex files on boot complete. + verify(mArtd).commitPreRebootStagedFiles( + inAnyOrderDeepEquals(AidlUtils.buildArtifactsPathAsInput( + "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */)), + inAnyOrderDeepEquals(AidlUtils.toWritableProfilePath( + AidlUtils.buildProfilePathForSecondaryRefAsInput( + "/data/user/0/foo/1.apk")))); + verify(mArtd, times(2)).commitPreRebootStagedFiles(any(), any()); + } + + @Test + public void testCommitPreRebootStagedFilesOnBootNotCalled() throws Exception { + mArtManagerLocal.systemReady(); + + verify(mContext, never()).registerReceiver(any(), any()); + } + private AndroidPackage createPackage(boolean multiSplit) { AndroidPackage pkg = mock(AndroidPackage.class); diff --git a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java index 11810dfb35..ffc702f338 100644 --- a/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java +++ b/libartservice/service/javatests/com/android/server/art/DexUseManagerTest.java @@ -28,6 +28,7 @@ import static org.mockito.Mockito.argThat; import static org.mockito.Mockito.eq; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.verify; import static org.mockito.Mockito.when; import android.content.BroadcastReceiver; @@ -97,6 +98,7 @@ public class DexUseManagerTest { @Mock private DexUseManagerLocal.Injector mInjector; @Mock private IArtd mArtd; @Mock private Context mContext; + @Mock private ArtManagerLocal mArtManagerLocal; private DexUseManagerLocal mDexUseManager; private String mCeDir; private String mDeDir; @@ -167,6 +169,7 @@ public class DexUseManagerTest { lenient().when(mInjector.getContext()).thenReturn(mContext); lenient().when(mInjector.getAllPackageNames()).thenReturn(mPackageStates.keySet()); lenient().when(mInjector.isPreReboot()).thenReturn(false); + lenient().when(mInjector.getArtManagerLocal()).thenReturn(mArtManagerLocal); mDexUseManager = new DexUseManagerLocal(mInjector); mDexUseManager.systemReady(); @@ -831,6 +834,11 @@ public class DexUseManagerTest { true /* isUsedByOtherApps */, mDefaultFileVisibility)); } + @Test + public void testSystemReady() { + verify(mArtManagerLocal).systemReady(); + } + @Test(expected = IllegalStateException.class) public void testPreRebootNoUpdate() throws Exception { when(mInjector.isPreReboot()).thenReturn(true); |