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.ProfilePath.TmpProfilePath; 20 21 import android.R; 22 import android.annotation.NonNull; 23 import android.annotation.Nullable; 24 import android.app.role.RoleManager; 25 import android.apphibernation.AppHibernationManager; 26 import android.content.Context; 27 import android.os.Binder; 28 import android.os.Build; 29 import android.os.DeadObjectException; 30 import android.os.Process; 31 import android.os.RemoteException; 32 import android.os.ServiceSpecificException; 33 import android.os.SystemClock; 34 import android.os.SystemProperties; 35 import android.os.Trace; 36 import android.os.UserManager; 37 import android.text.TextUtils; 38 import android.util.Log; 39 import android.util.Pair; 40 import android.util.SparseArray; 41 42 import androidx.annotation.RequiresApi; 43 44 import com.android.modules.utils.pm.PackageStateModulesUtils; 45 import com.android.server.art.model.DexoptParams; 46 import com.android.server.pm.PackageManagerLocal; 47 import com.android.server.pm.pkg.AndroidPackage; 48 import com.android.server.pm.pkg.PackageState; 49 50 import dalvik.system.DexFile; 51 import dalvik.system.VMRuntime; 52 53 import com.google.auto.value.AutoValue; 54 55 import java.io.File; 56 import java.io.IOException; 57 import java.nio.file.Files; 58 import java.nio.file.Path; 59 import java.util.ArrayList; 60 import java.util.Collection; 61 import java.util.Collections; 62 import java.util.Comparator; 63 import java.util.List; 64 import java.util.Set; 65 import java.util.concurrent.CompletableFuture; 66 import java.util.concurrent.ExecutionException; 67 import java.util.concurrent.Executor; 68 import java.util.concurrent.Future; 69 import java.util.function.Supplier; 70 import java.util.stream.Collectors; 71 72 /** @hide */ 73 @RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE) 74 public final class Utils { 75 public static final String PLATFORM_PACKAGE_NAME = "android"; 76 77 /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */ 78 private static final long TRACE_TAG_DALVIK = 1L << 14; 79 Utils()80 private Utils() {} 81 82 /** 83 * Checks if given array is null or has zero elements. 84 */ isEmpty(@ullable Collection<T> array)85 public static <T> boolean isEmpty(@Nullable Collection<T> array) { 86 return array == null || array.isEmpty(); 87 } 88 89 /** 90 * Checks if given array is null or has zero elements. 91 */ isEmpty(@ullable SparseArray<T> array)92 public static <T> boolean isEmpty(@Nullable SparseArray<T> array) { 93 return array == null || array.size() == 0; 94 } 95 96 /** 97 * Checks if given array is null or has zero elements. 98 */ isEmpty(@ullable int[] array)99 public static boolean isEmpty(@Nullable int[] array) { 100 return array == null || array.length == 0; 101 } 102 103 /** Returns the ABI information for the package. The primary ABI comes first. */ 104 @NonNull getAllAbis(@onNull PackageState pkgState)105 public static List<Abi> getAllAbis(@NonNull PackageState pkgState) { 106 List<Abi> abis = new ArrayList<>(); 107 abis.add(getPrimaryAbi(pkgState)); 108 String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi(); 109 String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi(); 110 if (pkgSecondaryCpuAbi != null) { 111 Utils.check(pkgState.getPrimaryCpuAbi() != null); 112 String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi)); 113 if (isa != null) { 114 abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */)); 115 } 116 } 117 // Primary and secondary ABIs should be guaranteed to have different ISAs. 118 if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) { 119 throw new IllegalStateException(String.format( 120 "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')", 121 pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name())); 122 } 123 return abis; 124 } 125 126 /** 127 * Returns the ABI information for the ABIs with the given names. The primary ABI comes first, 128 * if given. 129 */ 130 @NonNull getAllAbisForNames( @onNull Set<String> abiNames, @NonNull PackageState pkgState)131 public static List<Abi> getAllAbisForNames( 132 @NonNull Set<String> abiNames, @NonNull PackageState pkgState) { 133 Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi)); 134 Abi pkgPrimaryAbi = getPrimaryAbi(pkgState); 135 return abiNames.stream() 136 .map(name 137 -> Abi.create(name, VMRuntime.getInstructionSet(name), 138 name.equals(pkgPrimaryAbi.name()))) 139 .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed()) 140 .collect(Collectors.toList()); 141 } 142 143 @NonNull getPrimaryAbi(@onNull PackageState pkgState)144 public static Abi getPrimaryAbi(@NonNull PackageState pkgState) { 145 String primaryCpuAbi = pkgState.getPrimaryCpuAbi(); 146 if (primaryCpuAbi != null) { 147 String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi)); 148 // Fall through if there is no native bridge support. 149 if (isa != null) { 150 return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */); 151 } 152 } 153 // This is the most common case. Either the package manager can't infer the ABIs, probably 154 // because the package doesn't contain any native library, or the primary ABI is a foreign 155 // one and there is no native bridge support. The app is launched with the device's 156 // preferred ABI. 157 String preferredAbi = Constants.getPreferredAbi(); 158 Utils.check(isNativeAbi(preferredAbi)); 159 return Abi.create( 160 preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */); 161 } 162 163 /** 164 * If the given ISA isn't native to the device, returns the ISA that the native bridge 165 * translates it to, or null if there is no native bridge support. Otherwise, returns the ISA as 166 * is. This is the ISA that the app is actually launched with and therefore the ISA that should 167 * be used to compile the app. 168 */ 169 @Nullable getTranslatedIsa(@onNull String isa)170 private static String getTranslatedIsa(@NonNull String isa) { 171 String abi64 = Constants.getNative64BitAbi(); 172 String abi32 = Constants.getNative32BitAbi(); 173 if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) 174 || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) { 175 return isa; 176 } 177 String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa); 178 if (TextUtils.isEmpty(translatedIsa)) { 179 return null; 180 } 181 return translatedIsa; 182 } 183 184 @NonNull nativeIsaToAbi(@onNull String isa)185 private static String nativeIsaToAbi(@NonNull String isa) { 186 String abi64 = Constants.getNative64BitAbi(); 187 if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) { 188 return abi64; 189 } 190 String abi32 = Constants.getNative32BitAbi(); 191 if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) { 192 return abi32; 193 } 194 throw new IllegalStateException(String.format("Non-native isa '%s'", isa)); 195 } 196 isNativeAbi(@onNull String abiName)197 public static boolean isNativeAbi(@NonNull String abiName) { 198 return abiName.equals(Constants.getNative64BitAbi()) 199 || abiName.equals(Constants.getNative32BitAbi()); 200 } 201 202 /** 203 * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache 204 * directory. 205 * 206 * This method is not needed for secondary dex files because they are always in writable 207 * locations. 208 */ 209 @NonNull isInDalvikCache(@onNull PackageState pkgState, @NonNull IArtd artd)210 public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd) 211 throws RemoteException { 212 try { 213 return artd.isInDalvikCache(pkgState.getAndroidPackage().getSplits().get(0).getPath()); 214 } catch (ServiceSpecificException e) { 215 // This should never happen. Ignore the error and conservatively use dalvik-cache to 216 // minimize the risk. 217 // TODO(jiakaiz): Throw the error instead of ignoring it. 218 AsLog.e("Failed to determine the location of the artifacts", e); 219 return true; 220 } 221 } 222 223 /** Returns true if the given string is a valid compiler filter. */ isValidArtServiceCompilerFilter(@onNull String compilerFilter)224 public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) { 225 if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) { 226 return true; 227 } 228 return DexFile.isValidCompilerFilter(compilerFilter); 229 } 230 implies(boolean cond1, boolean cond2)231 public static boolean implies(boolean cond1, boolean cond2) { 232 return cond1 ? cond2 : true; 233 } 234 check(boolean cond)235 public static void check(boolean cond) { 236 // This cannot be replaced with `assert` because `assert` is not enabled in Android. 237 if (!cond) { 238 throw new IllegalStateException("Check failed"); 239 } 240 } 241 242 @NonNull getPackageStateOrThrow( @onNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName)243 public static PackageState getPackageStateOrThrow( 244 @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) { 245 PackageState pkgState = snapshot.getPackageState(packageName); 246 if (pkgState == null) { 247 throw new IllegalArgumentException("Package not found: " + packageName); 248 } 249 return pkgState; 250 } 251 252 @NonNull getPackageOrThrow(@onNull PackageState pkgState)253 public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) { 254 AndroidPackage pkg = pkgState.getAndroidPackage(); 255 if (pkg == null) { 256 throw new IllegalArgumentException( 257 "Unable to get package " + pkgState.getPackageName()); 258 } 259 return pkg; 260 } 261 262 @NonNull assertNonEmpty(@ullable String str)263 public static String assertNonEmpty(@Nullable String str) { 264 if (TextUtils.isEmpty(str)) { 265 throw new IllegalArgumentException(); 266 } 267 return str; 268 } 269 executeAndWait(@onNull Executor executor, @NonNull Runnable runnable)270 public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) { 271 getFuture(CompletableFuture.runAsync(runnable, executor)); 272 } 273 executeAndWait(@onNull Executor executor, @NonNull Supplier<T> supplier)274 public static <T> T executeAndWait(@NonNull Executor executor, @NonNull Supplier<T> supplier) { 275 return getFuture(CompletableFuture.supplyAsync(supplier, executor)); 276 } 277 getFuture(Future<T> future)278 public static <T> T getFuture(Future<T> future) { 279 try { 280 return future.get(); 281 } catch (ExecutionException e) { 282 throw toRuntimeException(e.getCause()); 283 } catch (InterruptedException e) { 284 throw new RuntimeException(e); 285 } 286 } 287 288 @NonNull toRuntimeException(@onNull Throwable t)289 public static RuntimeException toRuntimeException(@NonNull Throwable t) { 290 if (t instanceof RuntimeException r) { 291 return r; 292 } 293 var r = new RuntimeException(t); 294 // Clear the unhelpful stack trace, which is the stack trace of the constructor call above, 295 // so that the user can focus on the stack trace of `t`. 296 r.setStackTrace(new StackTraceElement[0]); 297 return r; 298 } 299 300 /** 301 * Returns true if the given package is dexoptable. 302 * 303 * @param appHibernationManager the {@link AppHibernationManager} instance for checking 304 * hibernation status, or null to skip the check 305 */ canDexoptPackage( @onNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager)306 public static boolean canDexoptPackage( 307 @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) { 308 if (!PackageStateModulesUtils.isDexoptable(pkgState)) { 309 return false; 310 } 311 312 // We do not dexopt unused packages. 313 // If `appHibernationManager` is null, the caller's intention is to skip the check. 314 if (appHibernationManager != null 315 && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) { 316 return false; 317 } 318 319 return true; 320 } 321 shouldSkipDexoptDueToHibernation( @onNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager)322 public static boolean shouldSkipDexoptDueToHibernation( 323 @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) { 324 return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName()) 325 && appHibernationManager.isOatArtifactDeletionEnabled(); 326 } 327 getPackageLastActiveTime(@onNull PackageState pkgState, @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager)328 public static long getPackageLastActiveTime(@NonNull PackageState pkgState, 329 @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) { 330 long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName()); 331 // The time where the last user installed the package the first time. 332 long lastFirstInstallTimeMs = 333 userManager.getUserHandles(true /* excludeDying */) 334 .stream() 335 .map(handle -> pkgState.getStateForUser(handle)) 336 .map(userState -> userState.getFirstInstallTimeMillis()) 337 .max(Long::compare) 338 .orElse(0l); 339 return Math.max(lastUsedAtMs, lastFirstInstallTimeMs); 340 } 341 deleteIfExistsSafe(@ullable File file)342 public static void deleteIfExistsSafe(@Nullable File file) { 343 if (file != null) { 344 deleteIfExistsSafe(file.toPath()); 345 } 346 } 347 deleteIfExistsSafe(@onNull Path path)348 public static void deleteIfExistsSafe(@NonNull Path path) { 349 try { 350 Files.deleteIfExists(path); 351 } catch (IOException e) { 352 AsLog.e("Failed to delete file '" + path + "'", e); 353 } 354 } 355 isSystemUiPackage(@onNull Context context, @NonNull String packageName)356 public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) { 357 return packageName.equals(context.getString(R.string.config_systemUi)); 358 } 359 isLauncherPackage(@onNull Context context, @NonNull String packageName)360 public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) { 361 RoleManager roleManager = context.getSystemService(RoleManager.class); 362 return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName); 363 } 364 365 /** 366 * Gets the existing reference profile if one exists, or initializes a reference profile from an 367 * external profile. 368 * 369 * If the reference profile is initialized from an external profile, the returned profile path 370 * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the 371 * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link 372 * IArtd#deleteProfile}. 373 * 374 * Note: "External profile" means profiles that are external to the device, as opposed to local 375 * profiles, which are collected on the device. An embedded profile (a profile embedded in the 376 * dex file) is also an external profile. 377 * 378 * @param dexPath the path to the dex file that the profile is checked against 379 * @param refProfile the path where an existing reference profile would be found, if present 380 * @param externalProfiles a list of external profiles to initialize the reference profile from, 381 * in the order of preference 382 * @param enableEmbeddedProfile whether to allow initializing the reference profile from the 383 * embedded profile 384 * @param initOutput the final location to initialize the reference profile to 385 */ 386 @NonNull getOrInitReferenceProfile(@onNull IArtd artd, @NonNull String dexPath, @NonNull ProfilePath refProfile, @NonNull List<ProfilePath> externalProfiles, boolean enableEmbeddedProfile, @NonNull OutputProfile initOutput)387 public static InitProfileResult getOrInitReferenceProfile(@NonNull IArtd artd, 388 @NonNull String dexPath, @NonNull ProfilePath refProfile, 389 @NonNull List<ProfilePath> externalProfiles, boolean enableEmbeddedProfile, 390 @NonNull OutputProfile initOutput) throws RemoteException { 391 try { 392 if (artd.isProfileUsable(refProfile, dexPath)) { 393 boolean isOtherReadable = 394 artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE; 395 return InitProfileResult.create( 396 refProfile, isOtherReadable, List.of() /* externalProfileErrors */); 397 } 398 } catch (ServiceSpecificException e) { 399 AsLog.e("Failed to use the existing reference profile " 400 + AidlUtils.toString(refProfile), 401 e); 402 } 403 404 return initReferenceProfile( 405 artd, dexPath, externalProfiles, enableEmbeddedProfile, initOutput); 406 } 407 408 /** 409 * Similar to above, but never uses an existing profile. 410 * 411 * The {@link InitProfileResult#isOtherReadable} field is always set to true. The profile 412 * returned by this method is initialized from an external profile, meaning it has no user data, 413 * so it's always readable by others. 414 */ 415 @Nullable initReferenceProfile(@onNull IArtd artd, @NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles, boolean enableEmbeddedProfile, @NonNull OutputProfile output)416 public static InitProfileResult initReferenceProfile(@NonNull IArtd artd, 417 @NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles, 418 boolean enableEmbeddedProfile, @NonNull OutputProfile output) throws RemoteException { 419 // Each element is a pair of a profile name (for logging) and the corresponding initializer. 420 // The order matters. Non-embedded profiles should take precedence. 421 List<Pair<String, ProfileInitializer>> profileInitializers = new ArrayList<>(); 422 for (ProfilePath profile : externalProfiles) { 423 // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt 424 // one, rewriting the profile is unnecessary because the dex location is known at 425 // build time and is correctly set in the profile header. However, the APK can also 426 // be an installed one, in which case partners may place a profile file next to the 427 // APK at install time. Rewriting the profile in the latter case is necessary. 428 profileInitializers.add(Pair.create(AidlUtils.toString(profile), 429 () -> artd.copyAndRewriteProfile(profile, output, dexPath))); 430 } 431 if (enableEmbeddedProfile) { 432 profileInitializers.add(Pair.create( 433 "embedded profile", () -> artd.copyAndRewriteEmbeddedProfile(output, dexPath))); 434 } 435 436 List<String> externalProfileErrors = new ArrayList<>(); 437 for (var pair : profileInitializers) { 438 try { 439 CopyAndRewriteProfileResult result = pair.second.get(); 440 if (result.status == CopyAndRewriteProfileResult.Status.SUCCESS) { 441 return InitProfileResult.create(ProfilePath.tmpProfilePath(output.profilePath), 442 true /* isOtherReadable */, externalProfileErrors); 443 } 444 if (result.status == CopyAndRewriteProfileResult.Status.BAD_PROFILE) { 445 externalProfileErrors.add(result.errorMsg); 446 } 447 } catch (ServiceSpecificException e) { 448 AsLog.e("Failed to initialize profile from " + pair.first, e); 449 } 450 } 451 452 return InitProfileResult.create( 453 null /* profile */, true /* isOtherReadable */, externalProfileErrors); 454 } 455 logArtdException(@onNull RemoteException e)456 public static void logArtdException(@NonNull RemoteException e) { 457 String message = "An error occurred when calling artd"; 458 if (e instanceof DeadObjectException) { 459 // We assume that `DeadObjectException` only happens in two cases: 460 // 1. artd crashed, in which case a native stack trace was logged. 461 // 2. artd was killed before system server during device shutdown, in which case the 462 // exception is expected. 463 // In either case, we don't need to surface the exception from here. 464 // The Java stack trace is intentionally omitted because it's not helpful. 465 AsLog.e(message); 466 } else { 467 // Not expected. Log wtf to surface it. 468 AsLog.wtf(message, e); 469 } 470 } 471 isSystemOrRootOrShell()472 public static boolean isSystemOrRootOrShell() { 473 int uid = Binder.getCallingUid(); 474 return uid == Process.SYSTEM_UID || uid == Process.ROOT_UID || uid == Process.SHELL_UID; 475 } 476 sleep(long millis)477 public static void sleep(long millis) { 478 try { 479 Thread.sleep(millis); 480 } catch (InterruptedException e) { 481 AsLog.wtf("Sleep interrupted", e); 482 } 483 } 484 485 @AutoValue 486 public abstract static class Abi { create( @onNull String name, @NonNull String isa, boolean isPrimaryAbi)487 static @NonNull Abi create( 488 @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) { 489 return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi); 490 } 491 492 // The ABI name. E.g., "arm64-v8a". name()493 abstract @NonNull String name(); 494 495 // The instruction set name. E.g., "arm64". isa()496 abstract @NonNull String isa(); 497 isPrimaryAbi()498 abstract boolean isPrimaryAbi(); 499 } 500 501 public static class Tracing implements AutoCloseable { Tracing(@onNull String methodName)502 public Tracing(@NonNull String methodName) { 503 Trace.traceBegin(TRACE_TAG_DALVIK, methodName); 504 } 505 506 @Override close()507 public void close() { 508 Trace.traceEnd(TRACE_TAG_DALVIK); 509 } 510 } 511 512 public static class TracingWithTimingLogging extends Tracing { 513 @NonNull private final String mTag; 514 @NonNull private final String mMethodName; 515 @NonNull private final long mStartTimeMs; 516 TracingWithTimingLogging(@onNull String tag, @NonNull String methodName)517 public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) { 518 super(methodName); 519 mTag = tag; 520 mMethodName = methodName; 521 mStartTimeMs = SystemClock.elapsedRealtime(); 522 Log.d(tag, methodName); 523 } 524 525 @Override close()526 public void close() { 527 Log.d(mTag, 528 mMethodName + " took to complete: " 529 + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms"); 530 super.close(); 531 } 532 } 533 534 /** The result of {@link #getOrInitReferenceProfile} and {@link #initReferenceProfile}. */ 535 @AutoValue 536 @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava. 537 public abstract static class InitProfileResult { create(@ullable ProfilePath profile, boolean isOtherReadable, @NonNull List<String> externalProfileErrors)538 static @NonNull InitProfileResult create(@Nullable ProfilePath profile, 539 boolean isOtherReadable, @NonNull List<String> externalProfileErrors) { 540 return new AutoValue_Utils_InitProfileResult( 541 profile, isOtherReadable, Collections.unmodifiableList(externalProfileErrors)); 542 } 543 544 /** 545 * The found or initialized profile, or null if there is no reference profile or external 546 * profile to use. 547 */ profile()548 abstract @Nullable ProfilePath profile(); 549 550 /** 551 * Whether the profile is readable by others. 552 * 553 * If {@link #profile} returns null, this field is always true. 554 */ isOtherReadable()555 abstract boolean isOtherReadable(); 556 557 /** Errors encountered when initializing from external profiles. */ externalProfileErrors()558 abstract @NonNull List<String> externalProfileErrors(); 559 } 560 561 @FunctionalInterface 562 private interface ProfileInitializer { get()563 CopyAndRewriteProfileResult get() throws RemoteException; 564 } 565 } 566