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