• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2022 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.server.art;
18 
19 import static com.android.server.art.GetDexoptNeededResult.ArtifactsLocation;
20 import static com.android.server.art.OutputArtifacts.PermissionSettings;
21 import static com.android.server.art.ProfilePath.TmpProfilePath;
22 import static com.android.server.art.Utils.Abi;
23 import static com.android.server.art.model.ArtFlags.DexoptFlags;
24 import static com.android.server.art.model.DexoptResult.DexContainerFileDexoptResult;
25 
26 import android.R;
27 import android.annotation.NonNull;
28 import android.annotation.Nullable;
29 import android.app.role.RoleManager;
30 import android.content.Context;
31 import android.content.pm.ApplicationInfo;
32 import android.os.Build;
33 import android.os.CancellationSignal;
34 import android.os.RemoteException;
35 import android.os.ServiceSpecificException;
36 import android.os.SystemProperties;
37 import android.os.UserManager;
38 import android.os.storage.StorageManager;
39 import android.text.TextUtils;
40 import android.util.Log;
41 import android.util.Pair;
42 
43 import androidx.annotation.RequiresApi;
44 
45 import com.android.internal.annotations.VisibleForTesting;
46 import com.android.server.LocalManagerRegistry;
47 import com.android.server.art.model.ArtFlags;
48 import com.android.server.art.model.DetailedDexInfo;
49 import com.android.server.art.model.DexoptParams;
50 import com.android.server.art.model.DexoptResult;
51 import com.android.server.pm.PackageManagerLocal;
52 import com.android.server.pm.pkg.AndroidPackage;
53 import com.android.server.pm.pkg.PackageState;
54 
55 import dalvik.system.DexFile;
56 
57 import com.google.auto.value.AutoValue;
58 
59 import java.io.IOException;
60 import java.util.ArrayList;
61 import java.util.List;
62 import java.util.Map;
63 import java.util.Objects;
64 
65 /** @hide */
66 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
67 public abstract class Dexopter<DexInfoType extends DetailedDexInfo> {
68     private static final String TAG = ArtManagerLocal.TAG;
69     private static final List<String> ART_PACKAGE_NAMES =
70             List.of("com.google.android.art", "com.android.art", "com.google.android.go.art");
71 
72     @NonNull protected final Injector mInjector;
73     @NonNull protected final PackageState mPkgState;
74     /** This is always {@code mPkgState.getAndroidPackage()} and guaranteed to be non-null. */
75     @NonNull protected final AndroidPackage mPkg;
76     @NonNull protected final DexoptParams mParams;
77     @NonNull protected final CancellationSignal mCancellationSignal;
78 
Dexopter(@onNull Injector injector, @NonNull PackageState pkgState, @NonNull AndroidPackage pkg, @NonNull DexoptParams params, @NonNull CancellationSignal cancellationSignal)79     protected Dexopter(@NonNull Injector injector, @NonNull PackageState pkgState,
80             @NonNull AndroidPackage pkg, @NonNull DexoptParams params,
81             @NonNull CancellationSignal cancellationSignal) {
82         mInjector = injector;
83         mPkgState = pkgState;
84         mPkg = pkg;
85         mParams = params;
86         mCancellationSignal = cancellationSignal;
87         if (pkgState.getAppId() < 0) {
88             throw new IllegalStateException(
89                     "Package '" + pkgState.getPackageName() + "' has invalid app ID");
90         }
91     }
92 
93     /**
94      * DO NOT use this method directly. Use {@link
95      * ArtManagerLocal#dexoptPackage(PackageManagerLocal.FilteredSnapshot, String,
96      * DexoptParams)}.
97      */
98     @NonNull
dexopt()99     public final List<DexContainerFileDexoptResult> dexopt() throws RemoteException {
100         List<DexContainerFileDexoptResult> results = new ArrayList<>();
101 
102         boolean isInDalvikCache = isInDalvikCache();
103 
104         for (DexInfoType dexInfo : getDexInfoList()) {
105             ProfilePath profile = null;
106             boolean succeeded = true;
107             try {
108                 if (!isDexoptable(dexInfo)) {
109                     continue;
110                 }
111 
112                 String compilerFilter = adjustCompilerFilter(mParams.getCompilerFilter(), dexInfo);
113                 if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
114                     continue;
115                 }
116 
117                 boolean needsToBeShared = needsToBeShared(dexInfo);
118                 boolean isOtherReadable = true;
119                 // If true, implies that the profile has changed since the last compilation.
120                 boolean profileMerged = false;
121                 if (DexFile.isProfileGuidedCompilerFilter(compilerFilter)) {
122                     if (needsToBeShared) {
123                         profile = initReferenceProfile(dexInfo);
124                     } else {
125                         Pair<ProfilePath, Boolean> pair = getOrInitReferenceProfile(dexInfo);
126                         if (pair != null) {
127                             profile = pair.first;
128                             isOtherReadable = pair.second;
129                         }
130                         ProfilePath mergedProfile = mergeProfiles(dexInfo, profile);
131                         if (mergedProfile != null) {
132                             if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
133                                 mInjector.getArtd().deleteProfile(profile);
134                             }
135                             profile = mergedProfile;
136                             isOtherReadable = false;
137                             profileMerged = true;
138                         }
139                     }
140                     if (profile == null) {
141                         // A profile guided dexopt with no profile is essentially 'verify',
142                         // and dex2oat already makes this transformation. However, we need to
143                         // explicitly make this transformation here to guide the later decisions
144                         // such as whether the artifacts can be public and whether dexopt is needed.
145                         compilerFilter = needsToBeShared
146                                 ? ReasonMapping.getCompilerFilterForShared()
147                                 : "verify";
148                     }
149                 }
150                 boolean isProfileGuidedCompilerFilter =
151                         DexFile.isProfileGuidedCompilerFilter(compilerFilter);
152                 Utils.check(isProfileGuidedCompilerFilter == (profile != null));
153 
154                 boolean canBePublic = (!isProfileGuidedCompilerFilter || isOtherReadable)
155                         && isDexFilePublic(dexInfo);
156                 Utils.check(Utils.implies(needsToBeShared, canBePublic));
157                 PermissionSettings permissionSettings = getPermissionSettings(dexInfo, canBePublic);
158 
159                 DexoptOptions dexoptOptions =
160                         getDexoptOptions(dexInfo, isProfileGuidedCompilerFilter);
161 
162                 for (Abi abi : getAllAbis(dexInfo)) {
163                     @DexoptResult.DexoptResultStatus int status = DexoptResult.DEXOPT_SKIPPED;
164                     long wallTimeMs = 0;
165                     long cpuTimeMs = 0;
166                     long sizeBytes = 0;
167                     long sizeBeforeBytes = 0;
168                     boolean isSkippedDueToStorageLow = false;
169                     try {
170                         var target = DexoptTarget.<DexInfoType>builder()
171                                              .setDexInfo(dexInfo)
172                                              .setIsa(abi.isa())
173                                              .setIsInDalvikCache(isInDalvikCache)
174                                              .setCompilerFilter(compilerFilter)
175                                              .build();
176                         var options = GetDexoptNeededOptions.builder()
177                                               .setProfileMerged(profileMerged)
178                                               .setFlags(mParams.getFlags())
179                                               .setNeedsToBePublic(needsToBeShared)
180                                               .build();
181 
182                         GetDexoptNeededResult getDexoptNeededResult =
183                                 getDexoptNeeded(target, options);
184 
185                         if (!getDexoptNeededResult.isDexoptNeeded) {
186                             continue;
187                         }
188 
189                         try {
190                             // `StorageManager.getAllocatableBytes` returns (free space + space used
191                             // by clearable cache - low storage threshold). Since we only compare
192                             // the result with 0, the clearable cache doesn't make a difference.
193                             // When the free space is below the threshold, there should be no
194                             // clearable cache left because system cleans up cache every minute.
195                             if ((mParams.getFlags() & ArtFlags.FLAG_SKIP_IF_STORAGE_LOW) != 0
196                                     && mInjector.getStorageManager().getAllocatableBytes(
197                                                mPkg.getStorageUuid())
198                                             <= 0) {
199                                 isSkippedDueToStorageLow = true;
200                                 continue;
201                             }
202                         } catch (IOException e) {
203                             Log.e(TAG, "Failed to check storage. Assuming storage not low", e);
204                         }
205 
206                         IArtdCancellationSignal artdCancellationSignal =
207                                 mInjector.getArtd().createCancellationSignal();
208                         mCancellationSignal.setOnCancelListener(() -> {
209                             try {
210                                 artdCancellationSignal.cancel();
211                             } catch (RemoteException e) {
212                                 Log.e(TAG, "An error occurred when sending a cancellation signal",
213                                         e);
214                             }
215                         });
216 
217                         ArtdDexoptResult dexoptResult = dexoptFile(target, profile,
218                                 getDexoptNeededResult, permissionSettings,
219                                 mParams.getPriorityClass(), dexoptOptions, artdCancellationSignal);
220                         status = dexoptResult.cancelled ? DexoptResult.DEXOPT_CANCELLED
221                                                         : DexoptResult.DEXOPT_PERFORMED;
222                         wallTimeMs = dexoptResult.wallTimeMs;
223                         cpuTimeMs = dexoptResult.cpuTimeMs;
224                         sizeBytes = dexoptResult.sizeBytes;
225                         sizeBeforeBytes = dexoptResult.sizeBeforeBytes;
226 
227                         if (status == DexoptResult.DEXOPT_CANCELLED) {
228                             return results;
229                         }
230                     } catch (ServiceSpecificException e) {
231                         // Log the error and continue.
232                         Log.e(TAG,
233                                 String.format("Failed to dexopt [packageName = %s, dexPath = %s, "
234                                                 + "isa = %s, classLoaderContext = %s]",
235                                         mPkgState.getPackageName(), dexInfo.dexPath(), abi.isa(),
236                                         dexInfo.classLoaderContext()),
237                                 e);
238                         status = DexoptResult.DEXOPT_FAILED;
239                     } finally {
240                         var result = DexContainerFileDexoptResult.create(dexInfo.dexPath(),
241                                 abi.isPrimaryAbi(), abi.name(), compilerFilter, status, wallTimeMs,
242                                 cpuTimeMs, sizeBytes, sizeBeforeBytes, isSkippedDueToStorageLow);
243                         Log.i(TAG,
244                                 String.format("Dexopt result: [packageName = %s] %s",
245                                         mPkgState.getPackageName(), result));
246                         results.add(result);
247                         if (status != DexoptResult.DEXOPT_SKIPPED
248                                 && status != DexoptResult.DEXOPT_PERFORMED) {
249                             succeeded = false;
250                         }
251                         // Make sure artd does not leak even if the caller holds
252                         // `mCancellationSignal` forever.
253                         mCancellationSignal.setOnCancelListener(null);
254                     }
255                 }
256 
257                 if (profile != null && succeeded) {
258                     if (profile.getTag() == ProfilePath.tmpProfilePath) {
259                         // Commit the profile only if dexopt succeeds.
260                         if (commitProfileChanges(profile.getTmpProfilePath())) {
261                             profile = null;
262                         }
263                     }
264                     if (profileMerged) {
265                         // Note that this is just an optimization, to reduce the amount of data that
266                         // the runtime writes on every profile save. The profile merge result on the
267                         // next run won't change regardless of whether the cleanup is done or not
268                         // because profman only looks at the diff.
269                         // A caveat is that it may delete more than what has been merged, if the
270                         // runtime writes additional entries between the merge and the cleanup, but
271                         // this is fine because the runtime writes all JITed classes and methods on
272                         // every save and the additional entries will likely be written back on the
273                         // next save.
274                         cleanupCurProfiles(dexInfo);
275                     }
276                 }
277             } finally {
278                 if (profile != null && profile.getTag() == ProfilePath.tmpProfilePath) {
279                     mInjector.getArtd().deleteProfile(profile);
280                 }
281             }
282         }
283 
284         return results;
285     }
286 
287     @NonNull
adjustCompilerFilter( @onNull String targetCompilerFilter, @NonNull DexInfoType dexInfo)288     private String adjustCompilerFilter(
289             @NonNull String targetCompilerFilter, @NonNull DexInfoType dexInfo) {
290         if (mInjector.isSystemUiPackage(mPkgState.getPackageName())) {
291             String systemUiCompilerFilter = getSystemUiCompilerFilter();
292             if (!systemUiCompilerFilter.isEmpty()) {
293                 return systemUiCompilerFilter;
294             }
295         }
296 
297         if (mInjector.isLauncherPackage(mPkgState.getPackageName())) {
298             return "speed-profile";
299         }
300 
301         // We force vmSafeMode on debuggable apps as well:
302         //  - the runtime ignores their compiled code
303         //  - they generally have lots of methods that could make the compiler used run out of
304         //    memory (b/130828957)
305         // Note that forcing the compiler filter here applies to all compilations (even if they
306         // are done via adb shell commands). This is okay because the runtime will ignore the
307         // compiled code anyway.
308         if (mPkg.isVmSafeMode() || mPkg.isDebuggable()) {
309             return DexFile.getSafeModeCompilerFilter(targetCompilerFilter);
310         }
311 
312         // We cannot do AOT compilation if we don't have a valid class loader context.
313         if (dexInfo.classLoaderContext() == null) {
314             return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
315                                                                            : targetCompilerFilter;
316         }
317 
318         // This application wants to use the embedded dex in the APK, rather than extracted or
319         // locally compiled variants, so we only verify it.
320         // "verify" does not prevent dex2oat from extracting the dex code, but in practice, dex2oat
321         // won't extract the dex code because the APK is uncompressed, and the assumption is that
322         // such applications always use uncompressed APKs.
323         if (mPkg.isUseEmbeddedDex()) {
324             return DexFile.isOptimizedCompilerFilter(targetCompilerFilter) ? "verify"
325                                                                            : targetCompilerFilter;
326         }
327 
328         return targetCompilerFilter;
329     }
330 
331     @NonNull
getSystemUiCompilerFilter()332     private String getSystemUiCompilerFilter() {
333         String compilerFilter = SystemProperties.get("dalvik.vm.systemuicompilerfilter");
334         if (!compilerFilter.isEmpty() && !Utils.isValidArtServiceCompilerFilter(compilerFilter)) {
335             throw new IllegalStateException(
336                     "Got invalid compiler filter '" + compilerFilter + "' for System UI");
337         }
338         return compilerFilter;
339     }
340 
341     /** @see Utils#getOrInitReferenceProfile */
342     @Nullable
getOrInitReferenceProfile(@onNull DexInfoType dexInfo)343     private Pair<ProfilePath, Boolean> getOrInitReferenceProfile(@NonNull DexInfoType dexInfo)
344             throws RemoteException {
345         return Utils.getOrInitReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
346                 buildRefProfilePath(dexInfo), getExternalProfiles(dexInfo),
347                 buildOutputProfile(dexInfo, true /* isPublic */));
348     }
349 
350     @Nullable
initReferenceProfile(@onNull DexInfoType dexInfo)351     private ProfilePath initReferenceProfile(@NonNull DexInfoType dexInfo) throws RemoteException {
352         return Utils.initReferenceProfile(mInjector.getArtd(), dexInfo.dexPath(),
353                 getExternalProfiles(dexInfo), buildOutputProfile(dexInfo, true /* isPublic */));
354     }
355 
356     @NonNull
getDexoptOptions( @onNull DexInfoType dexInfo, boolean isProfileGuidedFilter)357     private DexoptOptions getDexoptOptions(
358             @NonNull DexInfoType dexInfo, boolean isProfileGuidedFilter) {
359         DexoptOptions dexoptOptions = new DexoptOptions();
360         dexoptOptions.compilationReason = mParams.getReason();
361         dexoptOptions.targetSdkVersion = mPkg.getTargetSdkVersion();
362         dexoptOptions.debuggable = mPkg.isDebuggable() || isAlwaysDebuggable();
363         // Generating a meaningful app image needs a profile to determine what to include in the
364         // image. Otherwise, the app image will be nearly empty.
365         dexoptOptions.generateAppImage =
366                 isProfileGuidedFilter && isAppImageAllowed(dexInfo) && isAppImageEnabled();
367         dexoptOptions.hiddenApiPolicyEnabled = isHiddenApiPolicyEnabled();
368         dexoptOptions.comments = String.format(
369                 "app-version-name:%s,app-version-code:%d,art-version:%d", mPkg.getVersionName(),
370                 mPkg.getLongVersionCode(), mInjector.getArtVersion());
371         return dexoptOptions;
372     }
373 
isAlwaysDebuggable()374     private boolean isAlwaysDebuggable() {
375         return SystemProperties.getBoolean("dalvik.vm.always_debuggable", false /* def */);
376     }
377 
isAppImageEnabled()378     private boolean isAppImageEnabled() {
379         return !SystemProperties.get("dalvik.vm.appimageformat").isEmpty();
380     }
381 
isHiddenApiPolicyEnabled()382     private boolean isHiddenApiPolicyEnabled() {
383         return mPkgState.getHiddenApiEnforcementPolicy()
384                 != ApplicationInfo.HIDDEN_API_ENFORCEMENT_DISABLED;
385     }
386 
387     @NonNull
getDexoptNeeded(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)388     GetDexoptNeededResult getDexoptNeeded(@NonNull DexoptTarget<DexInfoType> target,
389             @NonNull GetDexoptNeededOptions options) throws RemoteException {
390         int dexoptTrigger = getDexoptTrigger(target, options);
391 
392         // The result should come from artd even if all the bits of `dexoptTrigger` are set
393         // because the result also contains information about the usable VDEX file.
394         // Note that the class loader context can be null. In that case, we intentionally pass the
395         // null value down to lower levels to indicate that the class loader context check should be
396         // skipped because we are only going to verify the dex code (see `adjustCompilerFilter`).
397         GetDexoptNeededResult result = mInjector.getArtd().getDexoptNeeded(
398                 target.dexInfo().dexPath(), target.isa(), target.dexInfo().classLoaderContext(),
399                 target.compilerFilter(), dexoptTrigger);
400 
401         return result;
402     }
403 
getDexoptTrigger(@onNull DexoptTarget<DexInfoType> target, @NonNull GetDexoptNeededOptions options)404     int getDexoptTrigger(@NonNull DexoptTarget<DexInfoType> target,
405             @NonNull GetDexoptNeededOptions options) throws RemoteException {
406         if ((options.flags() & ArtFlags.FLAG_FORCE) != 0) {
407             return DexoptTrigger.COMPILER_FILTER_IS_BETTER | DexoptTrigger.COMPILER_FILTER_IS_SAME
408                     | DexoptTrigger.COMPILER_FILTER_IS_WORSE
409                     | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE
410                     | DexoptTrigger.NEED_EXTRACTION;
411         }
412 
413         if ((options.flags() & ArtFlags.FLAG_SHOULD_DOWNGRADE) != 0) {
414             return DexoptTrigger.COMPILER_FILTER_IS_WORSE;
415         }
416 
417         int dexoptTrigger = DexoptTrigger.COMPILER_FILTER_IS_BETTER
418                 | DexoptTrigger.PRIMARY_BOOT_IMAGE_BECOMES_USABLE | DexoptTrigger.NEED_EXTRACTION;
419         if (options.profileMerged()) {
420             dexoptTrigger |= DexoptTrigger.COMPILER_FILTER_IS_SAME;
421         }
422 
423         ArtifactsPath existingArtifactsPath = AidlUtils.buildArtifactsPath(
424                 target.dexInfo().dexPath(), target.isa(), target.isInDalvikCache());
425 
426         if (options.needsToBePublic()
427                 && mInjector.getArtd().getArtifactsVisibility(existingArtifactsPath)
428                         == FileVisibility.NOT_OTHER_READABLE) {
429             // Typically, this happens after an app starts being used by other apps.
430             // This case should be the same as force as we have no choice but to trigger a new
431             // dexopt.
432             dexoptTrigger |=
433                     DexoptTrigger.COMPILER_FILTER_IS_SAME | DexoptTrigger.COMPILER_FILTER_IS_WORSE;
434         }
435 
436         return dexoptTrigger;
437     }
438 
dexoptFile(@onNull DexoptTarget<DexInfoType> target, @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult, @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass, @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)439     private ArtdDexoptResult dexoptFile(@NonNull DexoptTarget<DexInfoType> target,
440             @Nullable ProfilePath profile, @NonNull GetDexoptNeededResult getDexoptNeededResult,
441             @NonNull PermissionSettings permissionSettings, @PriorityClass int priorityClass,
442             @NonNull DexoptOptions dexoptOptions, IArtdCancellationSignal artdCancellationSignal)
443             throws RemoteException {
444         OutputArtifacts outputArtifacts = AidlUtils.buildOutputArtifacts(target.dexInfo().dexPath(),
445                 target.isa(), target.isInDalvikCache(), permissionSettings);
446 
447         VdexPath inputVdex =
448                 getInputVdex(getDexoptNeededResult, target.dexInfo().dexPath(), target.isa());
449 
450         DexMetadataPath dmFile = getDmFile(target.dexInfo());
451         if (dmFile != null
452                 && ReasonMapping.REASONS_FOR_INSTALL.contains(dexoptOptions.compilationReason)) {
453             // If the DM file is passed to dex2oat, then add the "-dm" suffix to the reason (e.g.,
454             // "install-dm").
455             // Note that this only applies to reasons for app install because the goal is to give
456             // Play a signal that a DM file is downloaded at install time. We actually pass the DM
457             // file regardless of the compilation reason, but we don't append a suffix when the
458             // compilation reason is not a reason for app install.
459             // Also note that the "-dm" suffix does NOT imply anything in the DM file being used by
460             // dex2oat. dex2oat may ignore some contents of the DM file when appropriate. The
461             // compilation reason can still be "install-dm" even if dex2oat left all contents of the
462             // DM file unused or an empty DM file is passed to dex2oat.
463             dexoptOptions.compilationReason = dexoptOptions.compilationReason + "-dm";
464         }
465 
466         return mInjector.getArtd().dexopt(outputArtifacts, target.dexInfo().dexPath(), target.isa(),
467                 target.dexInfo().classLoaderContext(), target.compilerFilter(), profile, inputVdex,
468                 dmFile, priorityClass, dexoptOptions, artdCancellationSignal);
469     }
470 
471     @Nullable
getInputVdex(@onNull GetDexoptNeededResult getDexoptNeededResult, @NonNull String dexPath, @NonNull String isa)472     private VdexPath getInputVdex(@NonNull GetDexoptNeededResult getDexoptNeededResult,
473             @NonNull String dexPath, @NonNull String isa) {
474         if (!getDexoptNeededResult.isVdexUsable) {
475             return null;
476         }
477         switch (getDexoptNeededResult.artifactsLocation) {
478             case ArtifactsLocation.DALVIK_CACHE:
479                 return VdexPath.artifactsPath(
480                         AidlUtils.buildArtifactsPath(dexPath, isa, true /* isInDalvikCache */));
481             case ArtifactsLocation.NEXT_TO_DEX:
482                 return VdexPath.artifactsPath(
483                         AidlUtils.buildArtifactsPath(dexPath, isa, false /* isInDalvikCache */));
484             case ArtifactsLocation.DM:
485                 // The DM file is passed to dex2oat as a separate flag whenever it exists.
486                 return null;
487             default:
488                 // This should never happen as the value is got from artd.
489                 throw new IllegalStateException(
490                         "Unknown artifacts location " + getDexoptNeededResult.artifactsLocation);
491         }
492     }
493 
494     @Nullable
getDmFile(@onNull DexInfoType dexInfo)495     private DexMetadataPath getDmFile(@NonNull DexInfoType dexInfo) throws RemoteException {
496         DexMetadataPath path = buildDmPath(dexInfo);
497         if (path == null) {
498             return null;
499         }
500         try {
501             if (mInjector.getArtd().getDmFileVisibility(path) != FileVisibility.NOT_FOUND) {
502                 return path;
503             }
504         } catch (ServiceSpecificException e) {
505             Log.e(TAG, "Failed to check DM file for " + dexInfo.dexPath(), e);
506         }
507         return null;
508     }
509 
commitProfileChanges(@onNull TmpProfilePath profile)510     private boolean commitProfileChanges(@NonNull TmpProfilePath profile) throws RemoteException {
511         try {
512             mInjector.getArtd().commitTmpProfile(profile);
513             return true;
514         } catch (ServiceSpecificException e) {
515             Log.e(TAG, "Failed to commit profile changes " + AidlUtils.toString(profile.finalPath),
516                     e);
517             return false;
518         }
519     }
520 
521     @Nullable
mergeProfiles(@onNull DexInfoType dexInfo, @Nullable ProfilePath referenceProfile)522     private ProfilePath mergeProfiles(@NonNull DexInfoType dexInfo,
523             @Nullable ProfilePath referenceProfile) throws RemoteException {
524         OutputProfile output = buildOutputProfile(dexInfo, false /* isPublic */);
525 
526         try {
527             if (mInjector.getArtd().mergeProfiles(getCurProfiles(dexInfo), referenceProfile, output,
528                         List.of(dexInfo.dexPath()), new MergeProfileOptions())) {
529                 return ProfilePath.tmpProfilePath(output.profilePath);
530             }
531         } catch (ServiceSpecificException e) {
532             Log.e(TAG,
533                     "Failed to merge profiles " + AidlUtils.toString(output.profilePath.finalPath),
534                     e);
535         }
536 
537         return null;
538     }
539 
cleanupCurProfiles(@onNull DexInfoType dexInfo)540     private void cleanupCurProfiles(@NonNull DexInfoType dexInfo) throws RemoteException {
541         for (ProfilePath profile : getCurProfiles(dexInfo)) {
542             mInjector.getArtd().deleteProfile(profile);
543         }
544     }
545 
546     // Methods to be implemented by child classes.
547 
548     /** Returns true if the artifacts should be written to the global dalvik-cache directory. */
isInDalvikCache()549     protected abstract boolean isInDalvikCache() throws RemoteException;
550 
551     /** Returns information about all dex files. */
getDexInfoList()552     @NonNull protected abstract List<DexInfoType> getDexInfoList();
553 
554     /** Returns true if the given dex file should be dexopted. */
isDexoptable(@onNull DexInfoType dexInfo)555     protected abstract boolean isDexoptable(@NonNull DexInfoType dexInfo);
556 
557     /**
558      * Returns true if the artifacts should be shared with other apps. Note that this must imply
559      * {@link #isDexFilePublic(DexInfoType)}.
560      */
needsToBeShared(@onNull DexInfoType dexInfo)561     protected abstract boolean needsToBeShared(@NonNull DexInfoType dexInfo);
562 
563     /**
564      * Returns true if the filesystem permission of the dex file has the "read" bit for "others"
565      * (S_IROTH).
566      */
isDexFilePublic(@onNull DexInfoType dexInfo)567     protected abstract boolean isDexFilePublic(@NonNull DexInfoType dexInfo);
568 
569     /**
570      * Returns a list of external profiles (e.g., a DM profile) that the reference profile can be
571      * initialized from, in the order of preference.
572      */
getExternalProfiles(@onNull DexInfoType dexInfo)573     @NonNull protected abstract List<ProfilePath> getExternalProfiles(@NonNull DexInfoType dexInfo);
574 
575     /** Returns the permission settings to use for the artifacts of the given dex file. */
576     @NonNull
getPermissionSettings( @onNull DexInfoType dexInfo, boolean canBePublic)577     protected abstract PermissionSettings getPermissionSettings(
578             @NonNull DexInfoType dexInfo, boolean canBePublic);
579 
580     /** Returns all ABIs that the given dex file should be compiled for. */
getAllAbis(@onNull DexInfoType dexInfo)581     @NonNull protected abstract List<Abi> getAllAbis(@NonNull DexInfoType dexInfo);
582 
583     /** Returns the path to the reference profile of the given dex file. */
buildRefProfilePath(@onNull DexInfoType dexInfo)584     @NonNull protected abstract ProfilePath buildRefProfilePath(@NonNull DexInfoType dexInfo);
585 
586     /** Returns true if app image (--app-image-fd) is allowed. */
isAppImageAllowed(@onNull DexInfoType dexInfo)587     protected abstract boolean isAppImageAllowed(@NonNull DexInfoType dexInfo);
588 
589     /**
590      * Returns the data structure that represents the temporary profile to use during processing.
591      */
592     @NonNull
buildOutputProfile( @onNull DexInfoType dexInfo, boolean isPublic)593     protected abstract OutputProfile buildOutputProfile(
594             @NonNull DexInfoType dexInfo, boolean isPublic);
595 
596     /** Returns the paths to the current profiles of the given dex file. */
getCurProfiles(@onNull DexInfoType dexInfo)597     @NonNull protected abstract List<ProfilePath> getCurProfiles(@NonNull DexInfoType dexInfo);
598 
599     /**
600      * Returns the path to the DM file that should be passed to dex2oat, or null if no DM file
601      * should be passed.
602      */
buildDmPath(@onNull DexInfoType dexInfo)603     @Nullable protected abstract DexMetadataPath buildDmPath(@NonNull DexInfoType dexInfo);
604 
605     @AutoValue
606     abstract static class DexoptTarget<DexInfoType extends DetailedDexInfo> {
dexInfo()607         abstract @NonNull DexInfoType dexInfo();
isa()608         abstract @NonNull String isa();
isInDalvikCache()609         abstract boolean isInDalvikCache();
compilerFilter()610         abstract @NonNull String compilerFilter();
611 
builder()612         static <DexInfoType extends DetailedDexInfo> Builder<DexInfoType> builder() {
613             return new AutoValue_Dexopter_DexoptTarget.Builder<DexInfoType>();
614         }
615 
616         @AutoValue.Builder
617         abstract static class Builder<DexInfoType extends DetailedDexInfo> {
setDexInfo(@onNull DexInfoType value)618             abstract Builder setDexInfo(@NonNull DexInfoType value);
setIsa(@onNull String value)619             abstract Builder setIsa(@NonNull String value);
setIsInDalvikCache(boolean value)620             abstract Builder setIsInDalvikCache(boolean value);
setCompilerFilter(@onNull String value)621             abstract Builder setCompilerFilter(@NonNull String value);
build()622             abstract DexoptTarget<DexInfoType> build();
623         }
624     }
625 
626     @AutoValue
627     abstract static class GetDexoptNeededOptions {
flags()628         abstract @DexoptFlags int flags();
profileMerged()629         abstract boolean profileMerged();
needsToBePublic()630         abstract boolean needsToBePublic();
631 
builder()632         static Builder builder() {
633             return new AutoValue_Dexopter_GetDexoptNeededOptions.Builder();
634         }
635 
636         @AutoValue.Builder
637         abstract static class Builder {
setFlags(@exoptFlags int value)638             abstract Builder setFlags(@DexoptFlags int value);
setProfileMerged(boolean value)639             abstract Builder setProfileMerged(boolean value);
setNeedsToBePublic(boolean value)640             abstract Builder setNeedsToBePublic(boolean value);
build()641             abstract GetDexoptNeededOptions build();
642         }
643     }
644 
645     /**
646      * Injector pattern for testing purpose.
647      *
648      * @hide
649      */
650     @VisibleForTesting(visibility = VisibleForTesting.Visibility.PROTECTED)
651     public static class Injector {
652         @NonNull private final Context mContext;
653 
Injector(@onNull Context context)654         public Injector(@NonNull Context context) {
655             mContext = context;
656 
657             // Call the getters for various dependencies, to ensure correct initialization order.
658             getUserManager();
659             getDexUseManager();
660             getStorageManager();
661             ArtModuleServiceInitializer.getArtModuleServiceManager();
662         }
663 
isSystemUiPackage(@onNull String packageName)664         public boolean isSystemUiPackage(@NonNull String packageName) {
665             return Utils.isSystemUiPackage(mContext, packageName);
666         }
667 
isLauncherPackage(@onNull String packageName)668         public boolean isLauncherPackage(@NonNull String packageName) {
669             return Utils.isLauncherPackage(mContext, packageName);
670         }
671 
672         @NonNull
getUserManager()673         public UserManager getUserManager() {
674             return Objects.requireNonNull(mContext.getSystemService(UserManager.class));
675         }
676 
677         @NonNull
getDexUseManager()678         public DexUseManagerLocal getDexUseManager() {
679             return Objects.requireNonNull(
680                     LocalManagerRegistry.getManager(DexUseManagerLocal.class));
681         }
682 
683         @NonNull
getArtd()684         public IArtd getArtd() {
685             return Utils.getArtd();
686         }
687 
688         @NonNull
getStorageManager()689         public StorageManager getStorageManager() {
690             return Objects.requireNonNull(mContext.getSystemService(StorageManager.class));
691         }
692 
693         @NonNull
getPackageManagerLocal()694         private PackageManagerLocal getPackageManagerLocal() {
695             return Objects.requireNonNull(
696                     LocalManagerRegistry.getManager(PackageManagerLocal.class));
697         }
698 
getArtVersion()699         public long getArtVersion() {
700             try (var snapshot = getPackageManagerLocal().withUnfilteredSnapshot()) {
701                 Map<String, PackageState> packageStates = snapshot.getPackageStates();
702                 for (String artPackageName : ART_PACKAGE_NAMES) {
703                     PackageState pkgState = packageStates.get(artPackageName);
704                     if (pkgState != null) {
705                         AndroidPackage pkg = Utils.getPackageOrThrow(pkgState);
706                         return pkg.getLongVersionCode();
707                     }
708                 }
709             }
710             return -1;
711         }
712     }
713 }
714