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