diff options
| author | 2024-01-05 14:45:34 +0000 | |
|---|---|---|
| committer | 2024-01-08 13:42:26 +0000 | |
| commit | 93f35d39de15c555b0ddea16121b0ee3f0aa9f91 (patch) | |
| tree | c547ed626236f156c03b16e65c7ca3cc59821432 | |
| parent | 9bbb9f368274de998317cee1ebd300266b957e6d (diff) | |
Move code to a helper class ArtFileManager.
This makes ArtManagerLocal.java shorter and enables code reuse, to
prepare for the subsequent change.
Also, this CL also changes PrimaryDexUtils to only return dex info items
that have code except for finding a split specified by the user. This is
done because dex info items that don't have code are generally not
useful to ART Service.
This CL is a no-op change. Existing tests are still passing.
Bug: 317081212
Test: atest ArtServiceTests
Change-Id: Ibce2d62bd5c0311fb4d7f92098dcd3cf38f5984f
6 files changed, 471 insertions, 227 deletions
diff --git a/libartservice/service/java/com/android/server/art/ArtFileManager.java b/libartservice/service/java/com/android/server/art/ArtFileManager.java new file mode 100644 index 0000000000..3db91642b6 --- /dev/null +++ b/libartservice/service/java/com/android/server/art/ArtFileManager.java @@ -0,0 +1,310 @@ +/* + * Copyright (C) 2023 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.server.art; + +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.Utils.Abi; + +import android.annotation.NonNull; +import android.content.Context; +import android.os.Build; +import android.os.RemoteException; +import android.os.ServiceSpecificException; +import android.os.UserManager; +import android.util.Log; +import android.util.Pair; + +import androidx.annotation.RequiresApi; + +import com.android.internal.annotations.Immutable; +import com.android.internal.annotations.VisibleForTesting; +import com.android.server.LocalManagerRegistry; +import com.android.server.art.model.DetailedDexInfo; +import com.android.server.pm.pkg.AndroidPackage; +import com.android.server.pm.pkg.PackageState; + +import dalvik.system.DexFile; + +import com.google.auto.value.AutoValue; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +/** + * A helper class to list files that ART Service consumes or produces. + * + * @hide + */ +@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) +public class ArtFileManager { + private static final String TAG = ArtManagerLocal.TAG; + + @NonNull private final Injector mInjector; + + public ArtFileManager(@NonNull Context context) { + this(new Injector(context)); + } + + @VisibleForTesting + public ArtFileManager(@NonNull Injector injector) { + mInjector = injector; + } + + /** + * @param excludeNotFound If true, excludes secondary dex files that are not found on the + * filesystem. + */ + @NonNull + public List<Pair<DetailedDexInfo, Abi>> getDexAndAbis(@NonNull PackageState pkgState, + @NonNull AndroidPackage pkg, boolean forPrimaryDex, boolean forSecondaryDex, + boolean excludeNotFound) { + List<Pair<DetailedDexInfo, Abi>> dexAndAbis = new ArrayList<>(); + if (forPrimaryDex) { + for (DetailedPrimaryDexInfo dexInfo : + PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) { + for (Abi abi : Utils.getAllAbis(pkgState)) { + dexAndAbis.add(Pair.create(dexInfo, abi)); + } + } + } + if (forSecondaryDex) { + List<? extends SecondaryDexInfo> dexInfos = excludeNotFound + ? mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo( + pkgState.getPackageName()) + : mInjector.getDexUseManager().getSecondaryDexInfo(pkgState.getPackageName()); + for (SecondaryDexInfo dexInfo : dexInfos) { + for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { + dexAndAbis.add(Pair.create(dexInfo, abi)); + } + } + } + return dexAndAbis; + } + + /** + * Returns the writable paths of artifacts, regardless of whether the artifacts exist or + * whether they are usable. + */ + @NonNull + public WritableArtifactLists getWritableArtifacts( + @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) throws RemoteException { + List<ArtifactsPath> artifacts = new ArrayList<>(); + List<RuntimeArtifactsPath> runtimeArtifacts = new ArrayList<>(); + + boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd()); + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + for (Abi abi : Utils.getAllAbis(pkgState)) { + artifacts.add(AidlUtils.buildArtifactsPath( + dexInfo.dexPath(), abi.isa(), isInDalvikCache)); + // Runtime images are only generated for primary dex files. + runtimeArtifacts.add(AidlUtils.buildRuntimeArtifactsPath( + pkgState.getPackageName(), dexInfo.dexPath(), abi.isa())); + } + } + + for (SecondaryDexInfo dexInfo : + mInjector.getDexUseManager().getSecondaryDexInfo(pkgState.getPackageName())) { + for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { + artifacts.add(AidlUtils.buildArtifactsPath( + dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */)); + } + } + + return WritableArtifactLists.create(artifacts, runtimeArtifacts); + } + + /** Returns artifacts that are usable, regardless of whether they are writable. */ + @NonNull + public UsableArtifactLists getUsableArtifacts( + @NonNull PackageState pkgState, @NonNull AndroidPackage pkg) throws RemoteException { + List<ArtifactsPath> artifacts = new ArrayList<>(); + List<VdexPath> vdexFiles = new ArrayList<>(); + List<RuntimeArtifactsPath> runtimeArtifacts = new ArrayList<>(); + + for (Pair<DetailedDexInfo, Abi> pair : + getDexAndAbis(pkgState, pkg, true /* forPrimaryDex */, true /* forSecondaryDex */, + true /* excludeNotFound */)) { + DetailedDexInfo dexInfo = pair.first; + Abi abi = pair.second; + try { + GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus( + dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext()); + if (result.artifactsLocation == ArtifactsLocation.DALVIK_CACHE + || result.artifactsLocation == ArtifactsLocation.NEXT_TO_DEX) { + ArtifactsPath thisArtifacts = AidlUtils.buildArtifactsPath(dexInfo.dexPath(), + abi.isa(), result.artifactsLocation == ArtifactsLocation.DALVIK_CACHE); + if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) { + // Only the VDEX file is usable. + vdexFiles.add(VdexPath.artifactsPath(thisArtifacts)); + } else { + artifacts.add(thisArtifacts); + } + // Runtime images are only generated for primary dex files. + if (dexInfo instanceof DetailedPrimaryDexInfo + && !DexFile.isOptimizedCompilerFilter(result.compilerFilter)) { + runtimeArtifacts.add(AidlUtils.buildRuntimeArtifactsPath( + pkgState.getPackageName(), dexInfo.dexPath(), abi.isa())); + } + } + } catch (ServiceSpecificException e) { + Log.e(TAG, + String.format( + "Failed to get dexopt status [packageName = %s, dexPath = %s, " + + "isa = %s, classLoaderContext = %s]", + pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(), + dexInfo.classLoaderContext()), + e); + } + } + + return UsableArtifactLists.create(artifacts, vdexFiles, runtimeArtifacts); + } + + @NonNull + public ProfileLists getProfiles(@NonNull PackageState pkgState, @NonNull AndroidPackage pkg, + boolean alsoForSecondaryDex, boolean excludeDexNotFound) { + List<ProfilePath> refProfiles = new ArrayList<>(); + List<ProfilePath> curProfiles = new ArrayList<>(); + + for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { + refProfiles.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo)); + curProfiles.addAll( + PrimaryDexUtils.getCurProfiles(mInjector.getUserManager(), pkgState, dexInfo)); + } + if (alsoForSecondaryDex) { + List<? extends SecondaryDexInfo> dexInfos = excludeDexNotFound + ? mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo( + pkgState.getPackageName()) + : mInjector.getDexUseManager().getSecondaryDexInfo(pkgState.getPackageName()); + for (SecondaryDexInfo dexInfo : dexInfos) { + refProfiles.add(AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath())); + curProfiles.add(AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); + } + } + + return ProfileLists.create(refProfiles, curProfiles); + } + + @Immutable + @AutoValue + @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. + public abstract static class WritableArtifactLists { + protected WritableArtifactLists() {} + + public static @NonNull WritableArtifactLists create(@NonNull List<ArtifactsPath> artifacts, + @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) { + return new AutoValue_ArtFileManager_WritableArtifactLists( + Collections.unmodifiableList(artifacts), + Collections.unmodifiableList(runtimeArtifacts)); + } + + public abstract @NonNull List<ArtifactsPath> artifacts(); + public abstract @NonNull List<RuntimeArtifactsPath> runtimeArtifacts(); + } + + @Immutable + @AutoValue + @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. + public abstract static class UsableArtifactLists { + protected UsableArtifactLists() {} + + public static @NonNull UsableArtifactLists create(@NonNull List<ArtifactsPath> artifacts, + @NonNull List<VdexPath> vdexFiles, + @NonNull List<RuntimeArtifactsPath> runtimeArtifacts) { + return new AutoValue_ArtFileManager_UsableArtifactLists( + Collections.unmodifiableList(artifacts), + Collections.unmodifiableList(vdexFiles), + Collections.unmodifiableList(runtimeArtifacts)); + } + + public abstract @NonNull List<ArtifactsPath> artifacts(); + public abstract @NonNull List<VdexPath> vdexFiles(); + + // Those not added to the list are definitely unusable, but those added to the list are not + // necessarily usable. For example, runtime artifacts can be outdated when the corresponding + // dex file is updated, but they may still show up in this list. + // + // However, this is not a severe problem. For `ArtManagerLocal.cleanup`, the worst result is + // only that we are keeping more runtime artifacts than needed. For + // `ArtManagerLocal.getArtManagedFileStats`, this is an edge case because the API call is + // transitively initiated by the app itself, and the runtime refreshes unusable runtime + // artifacts as soon as the app starts. + // + // TODO(jiakaiz): Improve this. + public abstract @NonNull List<RuntimeArtifactsPath> runtimeArtifacts(); + } + + @Immutable + @AutoValue + @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. + public abstract static class ProfileLists { + protected ProfileLists() {} + + public static @NonNull ProfileLists create( + @NonNull List<ProfilePath> refProfiles, @NonNull List<ProfilePath> curProfiles) { + return new AutoValue_ArtFileManager_ProfileLists( + Collections.unmodifiableList(refProfiles), + Collections.unmodifiableList(curProfiles)); + } + + public abstract @NonNull List<ProfilePath> refProfiles(); + public abstract @NonNull List<ProfilePath> curProfiles(); + + public @NonNull List<ProfilePath> allProfiles() { + List<ProfilePath> profiles = new ArrayList<>(); + profiles.addAll(refProfiles()); + profiles.addAll(curProfiles()); + return profiles; + } + } + + /**Injector pattern for testing purpose. */ + @VisibleForTesting + public static class Injector { + @NonNull private final Context mContext; + + Injector(@NonNull Context context) { + mContext = context; + + // Call the getters for the dependencies that aren't optional, to ensure correct + // initialization order. + ArtModuleServiceInitializer.getArtModuleServiceManager(); + getUserManager(); + getDexUseManager(); + } + + @NonNull + public IArtd getArtd() { + return Utils.getArtd(); + } + + @NonNull + public UserManager getUserManager() { + return Objects.requireNonNull(mContext.getSystemService(UserManager.class)); + } + + @NonNull + public DexUseManagerLocal getDexUseManager() { + return Objects.requireNonNull( + LocalManagerRegistry.getManager(DexUseManagerLocal.class)); + } + } +} diff --git a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java index 6b9973bb4b..6c692256c2 100644 --- a/libartservice/service/java/com/android/server/art/ArtManagerLocal.java +++ b/libartservice/service/java/com/android/server/art/ArtManagerLocal.java @@ -16,8 +16,9 @@ package com.android.server.art; -import static com.android.server.art.DexUseManagerLocal.DetailedSecondaryDexInfo; -import static com.android.server.art.DexUseManagerLocal.SecondaryDexInfo; +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.PrimaryDexUtils.DetailedPrimaryDexInfo; import static com.android.server.art.PrimaryDexUtils.PrimaryDexInfo; import static com.android.server.art.ReasonMapping.BatchDexoptReason; @@ -195,29 +196,14 @@ public final class ArtManagerLocal { try { long freedBytes = 0; - - boolean isInDalvikCache = Utils.isInDalvikCache(pkgState, mInjector.getArtd()); - for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { - if (!dexInfo.hasCode()) { - continue; - } - for (Abi abi : Utils.getAllAbis(pkgState)) { - freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath( - dexInfo.dexPath(), abi.isa(), isInDalvikCache)); - freedBytes += mInjector.getArtd().deleteRuntimeArtifacts( - AidlUtils.buildRuntimeArtifactsPath( - packageName, dexInfo.dexPath(), abi.isa())); - } + WritableArtifactLists list = + mInjector.getArtFileManager().getWritableArtifacts(pkgState, pkg); + for (ArtifactsPath artifacts : list.artifacts()) { + freedBytes += mInjector.getArtd().deleteArtifacts(artifacts); } - - for (SecondaryDexInfo dexInfo : - mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { - for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { - freedBytes += mInjector.getArtd().deleteArtifacts(AidlUtils.buildArtifactsPath( - dexInfo.dexPath(), abi.isa(), false /* isInDalvikCache */)); - } + for (RuntimeArtifactsPath runtimeArtifacts : list.runtimeArtifacts()) { + freedBytes += mInjector.getArtd().deleteRuntimeArtifacts(runtimeArtifacts); } - return DeleteResult.create(freedBytes); } catch (RemoteException e) { Utils.logArtdException(e); @@ -226,7 +212,8 @@ public final class ArtManagerLocal { } /** - * Returns the dexopt status of a package. + * Returns the dexopt status of all known dex container files of a package, even if some of them + * aren't readable. * * Uses the default flags ({@link ArtFlags#defaultGetStatusFlags()}). * @@ -257,29 +244,9 @@ public final class ArtManagerLocal { PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); - - List<Pair<DetailedDexInfo, Abi>> dexAndAbis = new ArrayList<>(); - - if ((flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0) { - for (DetailedPrimaryDexInfo dexInfo : - PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) { - if (!dexInfo.hasCode()) { - continue; - } - for (Abi abi : Utils.getAllAbis(pkgState)) { - dexAndAbis.add(Pair.create(dexInfo, abi)); - } - } - } - - if ((flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0) { - for (SecondaryDexInfo dexInfo : - mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { - for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { - dexAndAbis.add(Pair.create(dexInfo, abi)); - } - } - } + List<Pair<DetailedDexInfo, Abi>> dexAndAbis = mInjector.getArtFileManager().getDexAndAbis( + pkgState, pkg, (flags & ArtFlags.FLAG_FOR_PRIMARY_DEX) != 0, + (flags & ArtFlags.FLAG_FOR_SECONDARY_DEX) != 0, false /* excludeNotFound */); try { List<DexContainerFileDexoptStatus> statuses = new ArrayList<>(); @@ -333,26 +300,13 @@ public final class ArtManagerLocal { AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); try { - for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { - if (!dexInfo.hasCode()) { - continue; - } - mInjector.getArtd().deleteProfile( - PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo)); - for (ProfilePath profile : PrimaryDexUtils.getCurProfiles( - mInjector.getUserManager(), pkgState, dexInfo)) { - mInjector.getArtd().deleteProfile(profile); - } - } - - // This only deletes the profiles of known secondary dex files. If there are unknown - // secondary dex files, their profiles will be deleted by `cleanup`. - for (SecondaryDexInfo dexInfo : - mInjector.getDexUseManager().getSecondaryDexInfo(packageName)) { - mInjector.getArtd().deleteProfile( - AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath())); - mInjector.getArtd().deleteProfile( - AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); + // We want to delete as many profiles as possible, so this deletes profiles of all known + // secondary dex files. If there are unknown secondary dex files, their profiles will be + // deleted by `cleanup`. + ProfileLists list = mInjector.getArtFileManager().getProfiles( + pkgState, pkg, true /* alsoForSecondaryDex */, false /* excludeDexNotFound */); + for (ProfilePath profile : list.allProfiles()) { + mInjector.getArtd().deleteProfile(profile); } } catch (RemoteException e) { Utils.logArtdException(e); @@ -847,14 +801,9 @@ public final class ArtManagerLocal { // check. if (Utils.canDexoptPackage(appPkgState, null /* appHibernationManager */)) { AndroidPackage appPkg = Utils.getPackageOrThrow(appPkgState); - for (PrimaryDexInfo appDexInfo : PrimaryDexUtils.getDexInfo(appPkg)) { - if (!appDexInfo.hasCode()) { - continue; - } - profiles.add(PrimaryDexUtils.buildRefProfilePath(appPkgState, appDexInfo)); - profiles.addAll(PrimaryDexUtils.getCurProfiles( - mInjector.getUserManager(), appPkgState, appDexInfo)); - } + ProfileLists list = mInjector.getArtFileManager().getProfiles(appPkgState, appPkg, + false /* alsoForSecondaryDex */, true /* excludeDexNotFound */); + profiles.addAll(list.allProfiles()); } }); @@ -958,36 +907,16 @@ public final class ArtManagerLocal { continue; } AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); - boolean keepArtifacts = !Utils.shouldSkipDexoptDueToHibernation( - pkgState, mInjector.getAppHibernationManager()); - for (DetailedPrimaryDexInfo dexInfo : - PrimaryDexUtils.getDetailedDexInfo(pkgState, pkg)) { - if (!dexInfo.hasCode()) { - continue; - } - profilesToKeep.add(PrimaryDexUtils.buildRefProfilePath(pkgState, dexInfo)); - profilesToKeep.addAll(PrimaryDexUtils.getCurProfiles( - mInjector.getUserManager(), pkgState, dexInfo)); - if (keepArtifacts) { - for (Abi abi : Utils.getAllAbis(pkgState)) { - maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, - runtimeArtifactsToKeep, pkgState, dexInfo, abi); - } - } - } - for (DetailedSecondaryDexInfo dexInfo : - mInjector.getDexUseManager().getFilteredDetailedSecondaryDexInfo( - pkgState.getPackageName())) { - profilesToKeep.add( - AidlUtils.buildProfilePathForSecondaryRef(dexInfo.dexPath())); - profilesToKeep.add( - AidlUtils.buildProfilePathForSecondaryCur(dexInfo.dexPath())); - if (keepArtifacts) { - for (Abi abi : Utils.getAllAbisForNames(dexInfo.abiNames(), pkgState)) { - maybeKeepArtifacts(artifactsToKeep, vdexFilesToKeep, - runtimeArtifactsToKeep, pkgState, dexInfo, abi); - } - } + ProfileLists profileLists = mInjector.getArtFileManager().getProfiles(pkgState, pkg, + true /* alsoForSecondaryDex */, true /* excludeDexNotFound */); + profilesToKeep.addAll(profileLists.allProfiles()); + if (!Utils.shouldSkipDexoptDueToHibernation( + pkgState, mInjector.getAppHibernationManager())) { + UsableArtifactLists artifactLists = + mInjector.getArtFileManager().getUsableArtifacts(pkgState, pkg); + artifactsToKeep.addAll(artifactLists.artifacts()); + vdexFilesToKeep.addAll(artifactLists.vdexFiles()); + runtimeArtifactsToKeep.addAll(artifactLists.runtimeArtifacts()); } } return mInjector.getArtd().cleanup( @@ -999,47 +928,6 @@ public final class ArtManagerLocal { } /** - * Checks if the artifacts are up-to-date, and maybe adds them to {@code artifactsToKeep} or - * {@code vdexFilesToKeep} based on the result. - */ - @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) - private void maybeKeepArtifacts(@NonNull List<ArtifactsPath> artifactsToKeep, - @NonNull List<VdexPath> vdexFilesToKeep, - @NonNull List<RuntimeArtifactsPath> runtimeArtifactsToKeep, - @NonNull PackageState pkgState, @NonNull DetailedDexInfo dexInfo, @NonNull Abi abi) - throws RemoteException { - try { - GetDexoptStatusResult result = mInjector.getArtd().getDexoptStatus( - dexInfo.dexPath(), abi.isa(), dexInfo.classLoaderContext()); - if (result.artifactsLocation == ArtifactsLocation.DALVIK_CACHE - || result.artifactsLocation == ArtifactsLocation.NEXT_TO_DEX) { - ArtifactsPath artifacts = AidlUtils.buildArtifactsPath(dexInfo.dexPath(), abi.isa(), - result.artifactsLocation == ArtifactsLocation.DALVIK_CACHE); - if (result.compilationReason.equals(ArtConstants.REASON_VDEX)) { - // Only the VDEX file is usable. - vdexFilesToKeep.add(VdexPath.artifactsPath(artifacts)); - } else { - artifactsToKeep.add(artifacts); - } - // Runtime images are only generated for primary dex files. - if (dexInfo instanceof DetailedPrimaryDexInfo - && !DexFile.isOptimizedCompilerFilter(result.compilerFilter)) { - runtimeArtifactsToKeep.add(AidlUtils.buildRuntimeArtifactsPath( - pkgState.getPackageName(), dexInfo.dexPath(), abi.isa())); - } - } - } catch (ServiceSpecificException e) { - // Don't add the artifacts to the lists. They should be cleaned up. - Log.e(TAG, - String.format("Failed to get dexopt status [packageName = %s, dexPath = %s, " - + "isa = %s, classLoaderContext = %s]", - pkgState.getPackageName(), dexInfo.dexPath(), abi.isa(), - dexInfo.classLoaderContext()), - e); - } - } - - /** * Should be used by {@link BackgroundDexoptJobService} ONLY. * * @hide @@ -1452,5 +1340,11 @@ public final class ArtManagerLocal { // This is a path that system_server is known to have full access to. return "/data/system"; } + + @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) + @NonNull + public ArtFileManager getArtFileManager() { + return new ArtFileManager(getContext()); + } } } diff --git a/libartservice/service/java/com/android/server/art/ArtShellCommand.java b/libartservice/service/java/com/android/server/art/ArtShellCommand.java index f9e610c124..592f9cd36f 100644 --- a/libartservice/service/java/com/android/server/art/ArtShellCommand.java +++ b/libartservice/service/java/com/android/server/art/ArtShellCommand.java @@ -557,9 +557,6 @@ public final class ArtShellCommand extends BasicShellCommandHandler { AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); try (var tracing = new Utils.Tracing("dump profiles")) { for (PrimaryDexInfo dexInfo : PrimaryDexUtils.getDexInfo(pkg)) { - if (!dexInfo.hasCode()) { - continue; - } String profileName = PrimaryDexUtils.getProfileName(dexInfo.splitName()); // The path is intentionally inconsistent with the one for "snapshot-profile". This // is to match the behavior of the legacy PM shell command. @@ -818,20 +815,17 @@ public final class ArtShellCommand extends BasicShellCommandHandler { PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); - List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg); - - for (PrimaryDexInfo dexInfo : dexInfoList) { - if (splitArg.equals(dexInfo.splitName())) { - return splitArg; - } + PrimaryDexInfo dexInfo = + PrimaryDexUtils.findDexInfo(pkg, info -> splitArg.equals(info.splitName())); + if (dexInfo != null) { + return splitArg; } - - for (PrimaryDexInfo dexInfo : dexInfoList) { - if (splitArg.equals(new File(dexInfo.dexPath()).getName())) { - pw.println("Warning: Specifying a split using a filename is deprecated. Please " - + "use a split name (or an empty string for the base APK) instead"); - return dexInfo.splitName(); - } + dexInfo = PrimaryDexUtils.findDexInfo( + pkg, info -> splitArg.equals(new File(info.dexPath()).getName())); + if (dexInfo != null) { + pw.println("Warning: Specifying a split using a filename is deprecated. Please " + + "use a split name (or an empty string for the base APK) instead"); + return dexInfo.splitName(); } throw new IllegalArgumentException(String.format("Split '%s' not found", splitArg)); @@ -842,14 +836,11 @@ public final class ArtShellCommand extends BasicShellCommandHandler { @NonNull String packageName, @NonNull String fullPath) { PackageState pkgState = Utils.getPackageStateOrThrow(snapshot, packageName); AndroidPackage pkg = Utils.getPackageOrThrow(pkgState); - List<PrimaryDexInfo> dexInfoList = PrimaryDexUtils.getDexInfo(pkg); - - for (PrimaryDexInfo dexInfo : dexInfoList) { - if (fullPath.equals(dexInfo.dexPath())) { - return dexInfo.splitName(); - } + PrimaryDexInfo dexInfo = + PrimaryDexUtils.findDexInfo(pkg, info -> fullPath.equals(info.dexPath())); + if (dexInfo != null) { + return dexInfo.splitName(); } - throw new IllegalArgumentException(String.format("Code path '%s' not found", fullPath)); } diff --git a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java index 64f9bc5418..d58175e8f6 100644 --- a/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java +++ b/libartservice/service/java/com/android/server/art/PrimaryDexUtils.java @@ -41,6 +41,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Objects; +import java.util.function.Predicate; import java.util.stream.Collectors; /** @hide */ @@ -50,15 +51,15 @@ public class PrimaryDexUtils { private static final String SHARED_LIBRARY_LOADER_TYPE = PathClassLoader.class.getName(); /** - * Returns the basic information about all primary dex files belonging to the package. The - * return value is a list where the entry at index 0 is the information about the base APK, and - * the entry at index i is the information about the (i-1)-th split APK. + * Returns the basic information about all primary dex files belonging to the package, excluding + * files that have no code. */ @NonNull public static List<PrimaryDexInfo> getDexInfo(@NonNull AndroidPackage pkg) { return getDexInfoImpl(pkg) .stream() .map(builder -> builder.build()) + .filter(info -> info.hasCode()) .collect(Collectors.toList()); } @@ -72,6 +73,7 @@ public class PrimaryDexUtils { return getDetailedDexInfoImpl(pkgState, pkg) .stream() .map(builder -> builder.buildDetailed()) + .filter(info -> info.hasCode()) .collect(Collectors.toList()); } @@ -79,18 +81,27 @@ public class PrimaryDexUtils { @NonNull public static PrimaryDexInfo getDexInfoBySplitName( @NonNull AndroidPackage pkg, @Nullable String splitName) { - if (splitName == null) { - return getDexInfo(pkg).get(0); - } else { - return getDexInfo(pkg) - .stream() - .filter(info -> splitName.equals(info.splitName())) - .findFirst() - .orElseThrow(() -> { - return new IllegalArgumentException( - String.format("Split '%s' not found", splitName)); - }); + PrimaryDexInfo dexInfo = + findDexInfo(pkg, info -> Objects.equals(info.splitName(), splitName)); + if (dexInfo == null) { + throw new IllegalArgumentException(String.format("Split '%s' not found", splitName)); } + return dexInfo; + } + + /** + * Returns the basic information about the first dex file matching {@code predicate}, or null if + * not found. + */ + @Nullable + public static PrimaryDexInfo findDexInfo( + @NonNull AndroidPackage pkg, Predicate<PrimaryDexInfo> predicate) { + return getDexInfoImpl(pkg) + .stream() + .map(builder -> builder.build()) + .filter(predicate) + .findFirst() + .orElse(null); } @NonNull diff --git a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java index 794694135e..f1415ab4f7 100644 --- a/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java +++ b/libartservice/service/javatests/com/android/server/art/ArtManagerLocalTest.java @@ -117,6 +117,7 @@ public class ArtManagerLocalTest { SystemProperties.class, Constants.class, PackageStateModulesUtils.class); @Mock private ArtManagerLocal.Injector mInjector; + @Mock private ArtFileManager.Injector mArtFileManagerInjector; @Mock private PackageManagerLocal mPackageManagerLocal; @Mock private PackageManagerLocal.FilteredSnapshot mSnapshot; @Mock private IArtd mArtd; @@ -127,7 +128,8 @@ public class ArtManagerLocalTest { @Mock private StorageManager mStorageManager; private PackageState mPkgState1; private AndroidPackage mPkg1; - private List<DetailedSecondaryDexInfo> mSecondaryDexInfo1; + private DetailedSecondaryDexInfo mPkg1SecondaryDexInfo1; + private DetailedSecondaryDexInfo mPkg1SecondaryDexInfoNotFound; private Config mConfig; // True if the artifacts should be in dalvik-cache. @@ -158,6 +160,13 @@ public class ArtManagerLocalTest { lenient().when(mInjector.getDexUseManager()).thenReturn(mDexUseManager); lenient().when(mInjector.getCurrentTimeMillis()).thenReturn(CURRENT_TIME_MS); lenient().when(mInjector.getStorageManager()).thenReturn(mStorageManager); + lenient() + .when(mInjector.getArtFileManager()) + .thenReturn(new ArtFileManager(mArtFileManagerInjector)); + + lenient().when(mArtFileManagerInjector.getArtd()).thenReturn(mArtd); + lenient().when(mArtFileManagerInjector.getUserManager()).thenReturn(mUserManager); + lenient().when(mArtFileManagerInjector.getDexUseManager()).thenReturn(mDexUseManager); Path tempDir = Files.createTempDirectory("temp"); tempDir.toFile().deleteOnExit(); @@ -201,13 +210,14 @@ public class ArtManagerLocalTest { // All packages are by default recently used. lenient().when(mDexUseManager.getPackageLastUsedAtMs(any())).thenReturn(RECENT_TIME_MS); - mSecondaryDexInfo1 = createSecondaryDexInfo(); + mPkg1SecondaryDexInfo1 = createSecondaryDexInfo("/data/user/0/foo/1.apk"); + mPkg1SecondaryDexInfoNotFound = createSecondaryDexInfo("/data/user/0/foo/not_found.apk"); lenient() - .doReturn(mSecondaryDexInfo1) + .doReturn(List.of(mPkg1SecondaryDexInfo1, mPkg1SecondaryDexInfoNotFound)) .when(mDexUseManager) .getSecondaryDexInfo(eq(PKG_NAME_1)); lenient() - .doReturn(mSecondaryDexInfo1) + .doReturn(List.of(mPkg1SecondaryDexInfo1)) .when(mDexUseManager) .getFilteredDetailedSecondaryDexInfo(eq(PKG_NAME_1)); @@ -241,7 +251,7 @@ public class ArtManagerLocalTest { } @Test - public void testdeleteDexoptArtifacts() throws Exception { + public void testDeleteDexoptArtifacts() throws Exception { final long DEXOPT_ARTIFACTS_FREED = 1l; final long RUNTIME_ARTIFACTS_FREED = 100l; @@ -250,7 +260,7 @@ public class ArtManagerLocalTest { DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); assertThat(result.getFreedBytes()) - .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED); + .isEqualTo(6 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED); verify(mArtd).deleteArtifacts(deepEq( AidlUtils.buildArtifactsPath("/data/app/foo/base.apk", "arm64", mIsInDalvikCache))); @@ -262,6 +272,8 @@ public class ArtManagerLocalTest { "/data/app/foo/split_0.apk", "arm", mIsInDalvikCache))); verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( "/data/user/0/foo/1.apk", "arm64", false /* isInDalvikCache */))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/user/0/foo/not_found.apk", "arm64", false /* isInDalvikCache */))); verify(mArtd).deleteRuntimeArtifacts(deepEq(AidlUtils.buildRuntimeArtifactsPath( PKG_NAME_1, "/data/app/foo/base.apk", "arm64"))); @@ -273,12 +285,12 @@ public class ArtManagerLocalTest { PKG_NAME_1, "/data/app/foo/split_0.apk", "arm"))); // Verify that there are no more calls than the ones above. - verify(mArtd, times(5)).deleteArtifacts(any()); + verify(mArtd, times(6)).deleteArtifacts(any()); verify(mArtd, times(4)).deleteRuntimeArtifacts(any()); } @Test - public void testdeleteDexoptArtifactsTranslatedIsas() throws Exception { + public void testDeleteDexoptArtifactsTranslatedIsas() throws Exception { final long DEXOPT_ARTIFACTS_FREED = 1l; final long RUNTIME_ARTIFACTS_FREED = 100l; @@ -287,14 +299,15 @@ public class ArtManagerLocalTest { lenient().when(Constants.getPreferredAbi()).thenReturn("x86_64"); lenient().when(Constants.getNative64BitAbi()).thenReturn("x86_64"); lenient().when(Constants.getNative32BitAbi()).thenReturn("x86"); - when(mSecondaryDexInfo1.get(0).abiNames()).thenReturn(Set.of("x86_64")); + when(mPkg1SecondaryDexInfo1.abiNames()).thenReturn(Set.of("x86_64")); + when(mPkg1SecondaryDexInfoNotFound.abiNames()).thenReturn(Set.of("x86_64")); when(mArtd.deleteArtifacts(any())).thenReturn(DEXOPT_ARTIFACTS_FREED); when(mArtd.deleteRuntimeArtifacts(any())).thenReturn(RUNTIME_ARTIFACTS_FREED); DeleteResult result = mArtManagerLocal.deleteDexoptArtifacts(mSnapshot, PKG_NAME_1); assertThat(result.getFreedBytes()) - .isEqualTo(5 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED); + .isEqualTo(6 * DEXOPT_ARTIFACTS_FREED + 4 * RUNTIME_ARTIFACTS_FREED); verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( "/data/app/foo/base.apk", "x86_64", mIsInDalvikCache))); @@ -317,9 +330,11 @@ public class ArtManagerLocalTest { // We assume that the ISA got from `DexUseManagerLocal` is already the translated one. verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( "/data/user/0/foo/1.apk", "x86_64", false /* isInDalvikCache */))); + verify(mArtd).deleteArtifacts(deepEq(AidlUtils.buildArtifactsPath( + "/data/user/0/foo/not_found.apk", "x86_64", false /* isInDalvikCache */))); // Verify that there are no more calls than the ones above. - verify(mArtd, times(5)).deleteArtifacts(any()); + verify(mArtd, times(6)).deleteArtifacts(any()); verify(mArtd, times(4)).deleteRuntimeArtifacts(any()); } @@ -359,6 +374,10 @@ public class ArtManagerLocalTest { "run-from-apk", "unknown", "unknown", ArtifactsLocation.NEXT_TO_DEX)) .when(mArtd) .getDexoptStatus("/data/user/0/foo/1.apk", "arm64", "CLC"); + doReturn(createGetDexoptStatusResult( + "unknown", "unknown", "error", ArtifactsLocation.NONE_OR_ERROR)) + .when(mArtd) + .getDexoptStatus("/data/user/0/foo/not_found.apk", "arm64", "CLC"); DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); @@ -379,7 +398,10 @@ public class ArtManagerLocalTest { "extract", "compilation-reason-3", "location-debug-string-3"), DexContainerFileDexoptStatus.create("/data/user/0/foo/1.apk", false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", - "run-from-apk", "unknown", "unknown")); + "run-from-apk", "unknown", "unknown"), + DexContainerFileDexoptStatus.create("/data/user/0/foo/not_found.apk", + false /* isPrimaryDex */, true /* isPrimaryAbi */, "arm64-v8a", + "unknown", "unknown", "error")); } @Test(expected = IllegalArgumentException.class) @@ -404,7 +426,7 @@ public class ArtManagerLocalTest { DexoptStatus result = mArtManagerLocal.getDexoptStatus(mSnapshot, PKG_NAME_1); List<DexContainerFileDexoptStatus> statuses = result.getDexContainerFileDexoptStatuses(); - assertThat(statuses.size()).isEqualTo(5); + assertThat(statuses.size()).isEqualTo(6); for (DexContainerFileDexoptStatus status : statuses) { assertThat(status.getCompilerFilter()).isEqualTo("error"); @@ -428,6 +450,11 @@ public class ArtManagerLocalTest { deepEq(AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/1.apk"))); verify(mArtd).deleteProfile( deepEq(AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/1.apk"))); + + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForSecondaryRef("/data/user/0/foo/not_found.apk"))); + verify(mArtd).deleteProfile(deepEq( + AidlUtils.buildProfilePathForSecondaryCur("/data/user/0/foo/not_found.apk"))); } @Test(expected = IllegalArgumentException.class) @@ -1170,12 +1197,12 @@ public class ArtManagerLocalTest { return getDexoptStatusResult; } - private List<DetailedSecondaryDexInfo> createSecondaryDexInfo() throws Exception { + private DetailedSecondaryDexInfo createSecondaryDexInfo(String dexPath) throws Exception { var dexInfo = mock(DetailedSecondaryDexInfo.class); - lenient().when(dexInfo.dexPath()).thenReturn("/data/user/0/foo/1.apk"); + lenient().when(dexInfo.dexPath()).thenReturn(dexPath); lenient().when(dexInfo.abiNames()).thenReturn(Set.of("arm64-v8a")); lenient().when(dexInfo.classLoaderContext()).thenReturn("CLC"); - return List.of(dexInfo); + return dexInfo; } private void simulateStorageLow() throws Exception { diff --git a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java index a54b3714cc..e0e70636b1 100644 --- a/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java +++ b/libartservice/service/javatests/com/android/server/art/PrimaryDexUtilsTest.java @@ -24,6 +24,8 @@ import static com.google.common.truth.Truth.assertThat; import static org.mockito.Mockito.lenient; import static org.mockito.Mockito.mock; +import android.util.Pair; + import androidx.test.filters.SmallTest; import com.android.server.pm.pkg.AndroidPackage; @@ -35,6 +37,8 @@ import dalvik.system.DelegateLastClassLoader; import dalvik.system.DexClassLoader; import dalvik.system.PathClassLoader; +import com.google.common.truth.Correspondence; + import org.junit.Before; import org.junit.Test; import org.junit.runner.RunWith; @@ -68,15 +72,21 @@ public class PrimaryDexUtilsTest { + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}" + "}"; - assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext); - assertThat(infos.get(1).classLoaderContext()) - .isEqualTo("PCL[base.apk]" + sharedLibrariesContext); - assertThat(infos.get(2).classLoaderContext()).isEqualTo(null); - assertThat(infos.get(3).classLoaderContext()) - .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext); - assertThat(infos.get(4).classLoaderContext()) - .isEqualTo("PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]" - + sharedLibrariesContext); + assertThat(infos) + .comparingElementsUsing(Correspondence.transforming( + (DetailedPrimaryDexInfo info) + -> Pair.create(info.splitName(), info.classLoaderContext()), + "has split name and CLC of")) + .containsExactly(Pair.create(null, "PCL[]" + sharedLibrariesContext), + Pair.create("split_0", "PCL[base.apk]" + sharedLibrariesContext), + Pair.create("split_2", + "PCL[base.apk:split_0.apk:split_1.apk]" + sharedLibrariesContext), + Pair.create("split_3", + "PCL[base.apk:split_0.apk:split_1.apk:split_2.apk]" + + sharedLibrariesContext), + Pair.create("split_4", + "PCL[base.apk:split_0.apk:split_1.apk:split_2.apk:split_3.apk]" + + sharedLibrariesContext)); } @Test @@ -91,14 +101,17 @@ public class PrimaryDexUtilsTest { + "PCL[library_4.jar]{PCL[library_1_dex_1.jar:library_1_dex_2.jar]}" + "}"; - assertThat(infos.get(0).classLoaderContext()).isEqualTo("PCL[]" + sharedLibrariesContext); - assertThat(infos.get(1).classLoaderContext()) - .isEqualTo("PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext); - assertThat(infos.get(2).classLoaderContext()).isEqualTo(null); - assertThat(infos.get(3).classLoaderContext()) - .isEqualTo("DLC[];PCL[base.apk]" + sharedLibrariesContext); - assertThat(infos.get(4).classLoaderContext()).isEqualTo("PCL[]"); - assertThat(infos.get(5).classLoaderContext()).isEqualTo("PCL[];PCL[split_3.apk]"); + assertThat(infos) + .comparingElementsUsing(Correspondence.transforming( + (DetailedPrimaryDexInfo info) + -> Pair.create(info.splitName(), info.classLoaderContext()), + "has split name and CLC of")) + .containsExactly(Pair.create(null, "PCL[]" + sharedLibrariesContext), + Pair.create("split_0", + "PCL[];DLC[split_2.apk];PCL[base.apk]" + sharedLibrariesContext), + Pair.create("split_2", "DLC[];PCL[base.apk]" + sharedLibrariesContext), + Pair.create("split_3", "PCL[]"), + Pair.create("split_4", "PCL[];PCL[split_3.apk]")); } private <T extends PrimaryDexInfo> void checkBasicInfo(List<T> infos) { @@ -110,21 +123,19 @@ public class PrimaryDexUtilsTest { assertThat(infos.get(1).hasCode()).isTrue(); assertThat(infos.get(1).splitName()).isEqualTo("split_0"); - assertThat(infos.get(2).dexPath()).isEqualTo("/data/app/foo/split_1.apk"); - assertThat(infos.get(2).hasCode()).isFalse(); - assertThat(infos.get(2).splitName()).isEqualTo("split_1"); + // split_1 is skipped because it has no code. - assertThat(infos.get(3).dexPath()).isEqualTo("/data/app/foo/split_2.apk"); + assertThat(infos.get(2).dexPath()).isEqualTo("/data/app/foo/split_2.apk"); + assertThat(infos.get(2).hasCode()).isTrue(); + assertThat(infos.get(2).splitName()).isEqualTo("split_2"); + + assertThat(infos.get(3).dexPath()).isEqualTo("/data/app/foo/split_3.apk"); assertThat(infos.get(3).hasCode()).isTrue(); - assertThat(infos.get(3).splitName()).isEqualTo("split_2"); + assertThat(infos.get(3).splitName()).isEqualTo("split_3"); - assertThat(infos.get(4).dexPath()).isEqualTo("/data/app/foo/split_3.apk"); + assertThat(infos.get(4).dexPath()).isEqualTo("/data/app/foo/split_4.apk"); assertThat(infos.get(4).hasCode()).isTrue(); - assertThat(infos.get(4).splitName()).isEqualTo("split_3"); - - assertThat(infos.get(5).dexPath()).isEqualTo("/data/app/foo/split_4.apk"); - assertThat(infos.get(5).hasCode()).isTrue(); - assertThat(infos.get(5).splitName()).isEqualTo("split_4"); + assertThat(infos.get(4).splitName()).isEqualTo("split_4"); } private AndroidPackage createPackage(boolean isIsolatedSplitLoading) { |