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