summaryrefslogtreecommitdiff
path: root/libartservice/service/java/com/android/server/art/Utils.java
blob: 82418beb020e1b2633addf20643eea1270dccc0e (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
/*
 * Copyright (C) 2022 The Android Open Source Project
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package com.android.server.art;

import static com.android.server.art.ProfilePath.TmpProfilePath;

import android.R;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.role.RoleManager;
import android.apphibernation.AppHibernationManager;
import android.content.Context;
import android.os.Build;
import android.os.DeadObjectException;
import android.os.RemoteException;
import android.os.ServiceSpecificException;
import android.os.SystemClock;
import android.os.SystemProperties;
import android.os.Trace;
import android.os.UserManager;
import android.text.TextUtils;
import android.util.Log;
import android.util.Pair;
import android.util.Slog;
import android.util.SparseArray;

import androidx.annotation.RequiresApi;

import com.android.modules.utils.pm.PackageStateModulesUtils;
import com.android.server.art.model.DexoptParams;
import com.android.server.pm.PackageManagerLocal;
import com.android.server.pm.pkg.AndroidPackage;
import com.android.server.pm.pkg.PackageState;

import dalvik.system.DexFile;
import dalvik.system.VMRuntime;

import com.google.auto.value.AutoValue;

import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
import java.util.concurrent.Future;
import java.util.function.Supplier;
import java.util.stream.Collectors;

/** @hide */
@RequiresApi(Build.VERSION_CODES.UPSIDE_DOWN_CAKE)
public final class Utils {
    public static final String TAG = ArtManagerLocal.TAG;
    public static final String PLATFORM_PACKAGE_NAME = "android";

    /** A copy of {@link android.os.Trace.TRACE_TAG_DALVIK}. */
    private static final long TRACE_TAG_DALVIK = 1L << 14;

    private Utils() {}

    /**
     * Checks if given array is null or has zero elements.
     */
    public static <T> boolean isEmpty(@Nullable Collection<T> array) {
        return array == null || array.isEmpty();
    }

    /**
     * Checks if given array is null or has zero elements.
     */
    public static <T> boolean isEmpty(@Nullable SparseArray<T> array) {
        return array == null || array.size() == 0;
    }

    /**
     * Checks if given array is null or has zero elements.
     */
    public static boolean isEmpty(@Nullable int[] array) {
        return array == null || array.length == 0;
    }

    /** Returns the ABI information for the package. The primary ABI comes first. */
    @NonNull
    public static List<Abi> getAllAbis(@NonNull PackageState pkgState) {
        List<Abi> abis = new ArrayList<>();
        abis.add(getPrimaryAbi(pkgState));
        String pkgPrimaryCpuAbi = pkgState.getPrimaryCpuAbi();
        String pkgSecondaryCpuAbi = pkgState.getSecondaryCpuAbi();
        if (pkgSecondaryCpuAbi != null) {
            Utils.check(pkgState.getPrimaryCpuAbi() != null);
            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(pkgSecondaryCpuAbi));
            abis.add(Abi.create(nativeIsaToAbi(isa), isa, false /* isPrimaryAbi */));
        }
        // Primary and secondary ABIs should be guaranteed to have different ISAs.
        if (abis.size() == 2 && abis.get(0).isa().equals(abis.get(1).isa())) {
            throw new IllegalStateException(String.format(
                    "Duplicate ISA: primary ABI '%s' ('%s'), secondary ABI '%s' ('%s')",
                    pkgPrimaryCpuAbi, abis.get(0).name(), pkgSecondaryCpuAbi, abis.get(1).name()));
        }
        return abis;
    }

    /**
     * Returns the ABI information for the ABIs with the given names. The primary ABI comes first,
     * if given.
     */
    @NonNull
    public static List<Abi> getAllAbisForNames(
            @NonNull Set<String> abiNames, @NonNull PackageState pkgState) {
        Utils.check(abiNames.stream().allMatch(Utils::isNativeAbi));
        Abi pkgPrimaryAbi = getPrimaryAbi(pkgState);
        return abiNames.stream()
                .map(name
                        -> Abi.create(name, VMRuntime.getInstructionSet(name),
                                name.equals(pkgPrimaryAbi.name())))
                .sorted(Comparator.comparing(Abi::isPrimaryAbi).reversed())
                .collect(Collectors.toList());
    }

    @NonNull
    public static Abi getPrimaryAbi(@NonNull PackageState pkgState) {
        String primaryCpuAbi = pkgState.getPrimaryCpuAbi();
        if (primaryCpuAbi != null) {
            String isa = getTranslatedIsa(VMRuntime.getInstructionSet(primaryCpuAbi));
            return Abi.create(nativeIsaToAbi(isa), isa, true /* isPrimaryAbi */);
        }
        // This is the most common case. The package manager can't infer the ABIs, probably because
        // the package doesn't contain any native library. The app is launched with the device's
        // preferred ABI.
        String preferredAbi = Constants.getPreferredAbi();
        Utils.check(isNativeAbi(preferredAbi));
        return Abi.create(
                preferredAbi, VMRuntime.getInstructionSet(preferredAbi), true /* isPrimaryAbi */);
    }

    /**
     * If the given ISA isn't native to the device, returns the ISA that the native bridge
     * translates it to. Otherwise, returns the ISA as is. This is the ISA that the app is actually
     * launched with and therefore the ISA that should be used to compile the app.
     */
    @NonNull
    private static String getTranslatedIsa(@NonNull String isa) {
        String abi64 = Constants.getNative64BitAbi();
        String abi32 = Constants.getNative32BitAbi();
        if ((abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64)))
                || (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32)))) {
            return isa;
        }
        String translatedIsa = SystemProperties.get("ro.dalvik.vm.isa." + isa);
        if (TextUtils.isEmpty(translatedIsa)) {
            throw new IllegalStateException(String.format("Unsupported isa '%s'", isa));
        }
        return translatedIsa;
    }

    @NonNull
    private static String nativeIsaToAbi(@NonNull String isa) {
        String abi64 = Constants.getNative64BitAbi();
        if (abi64 != null && isa.equals(VMRuntime.getInstructionSet(abi64))) {
            return abi64;
        }
        String abi32 = Constants.getNative32BitAbi();
        if (abi32 != null && isa.equals(VMRuntime.getInstructionSet(abi32))) {
            return abi32;
        }
        throw new IllegalStateException(String.format("Non-native isa '%s'", isa));
    }

    private static boolean isNativeAbi(@NonNull String abiName) {
        return abiName.equals(Constants.getNative64BitAbi())
                || abiName.equals(Constants.getNative32BitAbi());
    }

    /**
     * Returns whether the artifacts of the primary dex files should be in the global dalvik-cache
     * directory.
     *
     * This method is not needed for secondary dex files because they are always in writable
     * locations.
     */
    @NonNull
    public static boolean isInDalvikCache(@NonNull PackageState pkgState, @NonNull IArtd artd)
            throws RemoteException {
        try {
            return artd.isInDalvikCache(pkgState.getAndroidPackage().getSplits().get(0).getPath());
        } catch (ServiceSpecificException e) {
            // This should never happen. Ignore the error and conservatively use dalvik-cache to
            // minimize the risk.
            // TODO(jiakaiz): Throw the error instead of ignoring it.
            Log.e(TAG, "Failed to determine the location of the artifacts", e);
            return true;
        }
    }

    /** Returns true if the given string is a valid compiler filter. */
    public static boolean isValidArtServiceCompilerFilter(@NonNull String compilerFilter) {
        if (compilerFilter.equals(DexoptParams.COMPILER_FILTER_NOOP)) {
            return true;
        }
        return DexFile.isValidCompilerFilter(compilerFilter);
    }

    @NonNull
    public static IArtd getArtd() {
        IArtd artd = IArtd.Stub.asInterface(ArtModuleServiceInitializer.getArtModuleServiceManager()
                                                    .getArtdServiceRegisterer()
                                                    .waitForService());
        if (artd == null) {
            throw new IllegalStateException("Unable to connect to artd");
        }
        return artd;
    }

    public static boolean implies(boolean cond1, boolean cond2) {
        return cond1 ? cond2 : true;
    }

    public static void check(boolean cond) {
        // This cannot be replaced with `assert` because `assert` is not enabled in Android.
        if (!cond) {
            throw new IllegalStateException("Check failed");
        }
    }

    @NonNull
    public static PackageState getPackageStateOrThrow(
            @NonNull PackageManagerLocal.FilteredSnapshot snapshot, @NonNull String packageName) {
        PackageState pkgState = snapshot.getPackageState(packageName);
        if (pkgState == null) {
            throw new IllegalArgumentException("Package not found: " + packageName);
        }
        return pkgState;
    }

    @NonNull
    public static AndroidPackage getPackageOrThrow(@NonNull PackageState pkgState) {
        AndroidPackage pkg = pkgState.getAndroidPackage();
        if (pkg == null) {
            throw new IllegalArgumentException(
                    "Unable to get package " + pkgState.getPackageName());
        }
        return pkg;
    }

    @NonNull
    public static String assertNonEmpty(@Nullable String str) {
        if (TextUtils.isEmpty(str)) {
            throw new IllegalArgumentException();
        }
        return str;
    }

    public static void executeAndWait(@NonNull Executor executor, @NonNull Runnable runnable) {
        getFuture(CompletableFuture.runAsync(runnable, executor));
    }

    public static <T> T executeAndWait(@NonNull Executor executor, @NonNull Supplier<T> supplier) {
        return getFuture(CompletableFuture.supplyAsync(supplier, executor));
    }

    public static <T> T getFuture(Future<T> future) {
        try {
            return future.get();
        } catch (ExecutionException e) {
            Throwable cause = e.getCause();
            if (cause instanceof RuntimeException) {
                throw (RuntimeException) cause;
            }
            throw new RuntimeException(cause);
        } catch (InterruptedException e) {
            throw new RuntimeException(e);
        }
    }

    /**
     * Returns true if the given package is dexoptable.
     *
     * @param appHibernationManager the {@link AppHibernationManager} instance for checking
     *         hibernation status, or null to skip the check
     */
    public static boolean canDexoptPackage(
            @NonNull PackageState pkgState, @Nullable AppHibernationManager appHibernationManager) {
        if (!PackageStateModulesUtils.isDexoptable(pkgState)) {
            return false;
        }

        // We do not dexopt unused packages.
        // If `appHibernationManager` is null, the caller's intention is to skip the check.
        if (appHibernationManager != null
                && shouldSkipDexoptDueToHibernation(pkgState, appHibernationManager)) {
            return false;
        }

        return true;
    }

    public static boolean shouldSkipDexoptDueToHibernation(
            @NonNull PackageState pkgState, @NonNull AppHibernationManager appHibernationManager) {
        return appHibernationManager.isHibernatingGlobally(pkgState.getPackageName())
                && appHibernationManager.isOatArtifactDeletionEnabled();
    }

    public static long getPackageLastActiveTime(@NonNull PackageState pkgState,
            @NonNull DexUseManagerLocal dexUseManager, @NonNull UserManager userManager) {
        long lastUsedAtMs = dexUseManager.getPackageLastUsedAtMs(pkgState.getPackageName());
        // The time where the last user installed the package the first time.
        long lastFirstInstallTimeMs =
                userManager.getUserHandles(true /* excludeDying */)
                        .stream()
                        .map(handle -> pkgState.getStateForUser(handle))
                        .map(userState -> userState.getFirstInstallTimeMillis())
                        .max(Long::compare)
                        .orElse(0l);
        return Math.max(lastUsedAtMs, lastFirstInstallTimeMs);
    }

    public static void deleteIfExistsSafe(@Nullable File file) {
        if (file != null) {
            deleteIfExistsSafe(file.toPath());
        }
    }

    public static void deleteIfExistsSafe(@NonNull Path path) {
        try {
            Files.deleteIfExists(path);
        } catch (IOException e) {
            Log.e(TAG, "Failed to delete file '" + path + "'", e);
        }
    }

    public static boolean isSystemUiPackage(@NonNull Context context, @NonNull String packageName) {
        return packageName.equals(context.getString(R.string.config_systemUi));
    }

    public static boolean isLauncherPackage(@NonNull Context context, @NonNull String packageName) {
        RoleManager roleManager = context.getSystemService(RoleManager.class);
        return roleManager.getRoleHolders(RoleManager.ROLE_HOME).contains(packageName);
    }

    /**
     * Gets the existing reference profile if one exists, or initializes a reference profile from an
     * external profile.
     *
     * If the reference profile is initialized from an external profile, the returned profile path
     * will be a {@link TmpProfilePath}. It's the callers responsibility to either commit it to the
     * final location by calling {@link IArtd#commitTmpProfile} or clean it up by calling {@link
     * IArtd#deleteProfile}.
     *
     * Note: "External profile" means profiles that are external to the device, as opposed to local
     * profiles, which are collected on the device. An embedded profile (a profile embedded in the
     * dex file) is also an external profile.
     *
     * @param dexPath the path to the dex file that the profile is checked against
     * @param refProfile the path where an existing reference profile would be found, if present
     * @param externalProfiles a list of external profiles to initialize the reference profile from,
     *         in the order of preference
     * @param initOutput the final location to initialize the reference profile to
     */
    @NonNull
    public static InitProfileResult getOrInitReferenceProfile(@NonNull IArtd artd,
            @NonNull String dexPath, @NonNull ProfilePath refProfile,
            @NonNull List<ProfilePath> externalProfiles, @NonNull OutputProfile initOutput)
            throws RemoteException {
        try {
            if (artd.isProfileUsable(refProfile, dexPath)) {
                boolean isOtherReadable =
                        artd.getProfileVisibility(refProfile) == FileVisibility.OTHER_READABLE;
                return InitProfileResult.create(
                        refProfile, isOtherReadable, List.of() /* externalProfileErrors */);
            }
        } catch (ServiceSpecificException e) {
            Log.e(TAG,
                    "Failed to use the existing reference profile "
                            + AidlUtils.toString(refProfile),
                    e);
        }

        return initReferenceProfile(artd, dexPath, externalProfiles, initOutput);
    }

    /**
     * Similar to above, but never uses an existing profile.
     *
     * The {@link InitProfileResult#isOtherReadable} field is always set to true. The profile
     * returned by this method is initialized from an external profile, meaning it has no user data,
     * so it's always readable by others.
     */
    @Nullable
    public static InitProfileResult initReferenceProfile(@NonNull IArtd artd,
            @NonNull String dexPath, @NonNull List<ProfilePath> externalProfiles,
            @NonNull OutputProfile output) throws RemoteException {
        // Each element is a pair of a profile name (for logging) and the corresponding initializer.
        // The order matters. Non-embedded profiles should take precedence.
        List<Pair<String, ProfileInitializer>> profileInitializers = new ArrayList<>();
        for (ProfilePath profile : externalProfiles) {
            // If the profile path is a PrebuiltProfilePath, and the APK is really a prebuilt
            // one, rewriting the profile is unnecessary because the dex location is known at
            // build time and is correctly set in the profile header. However, the APK can also
            // be an installed one, in which case partners may place a profile file next to the
            // APK at install time. Rewriting the profile in the latter case is necessary.
            profileInitializers.add(Pair.create(AidlUtils.toString(profile),
                    () -> artd.copyAndRewriteProfile(profile, output, dexPath)));
        }
        profileInitializers.add(Pair.create(
                "embedded profile", () -> artd.copyAndRewriteEmbeddedProfile(output, dexPath)));

        List<String> externalProfileErrors = new ArrayList<>();
        for (var pair : profileInitializers) {
            try {
                CopyAndRewriteProfileResult result = pair.second.get();
                if (result.status == CopyAndRewriteProfileResult.Status.SUCCESS) {
                    return InitProfileResult.create(ProfilePath.tmpProfilePath(output.profilePath),
                            true /* isOtherReadable */, externalProfileErrors);
                }
                if (result.status == CopyAndRewriteProfileResult.Status.BAD_PROFILE) {
                    externalProfileErrors.add(result.errorMsg);
                }
            } catch (ServiceSpecificException e) {
                Log.e(TAG, "Failed to initialize profile from " + pair.first, e);
            }
        }

        return InitProfileResult.create(
                null /* profile */, true /* isOtherReadable */, externalProfileErrors);
    }

    public static void logArtdException(@NonNull RemoteException e) {
        String message = "An error occurred when calling artd";
        if (e instanceof DeadObjectException) {
            // We assume that `DeadObjectException` only happens in two cases:
            // 1. artd crashed, in which case a native stack trace was logged.
            // 2. artd was killed before system server during device shutdown, in which case the
            //    exception is expected.
            // In either case, we don't need to surface the exception from here.
            // The Java stack trace is intentionally omitted because it's not helpful.
            Log.e(TAG, message);
        } else {
            // Not expected. Log wtf to surface it.
            Slog.wtf(TAG, message, e);
        }
    }

    @AutoValue
    public abstract static class Abi {
        static @NonNull Abi create(
                @NonNull String name, @NonNull String isa, boolean isPrimaryAbi) {
            return new AutoValue_Utils_Abi(name, isa, isPrimaryAbi);
        }

        // The ABI name. E.g., "arm64-v8a".
        abstract @NonNull String name();

        // The instruction set name. E.g., "arm64".
        abstract @NonNull String isa();

        abstract boolean isPrimaryAbi();
    }

    public static class Tracing implements AutoCloseable {
        public Tracing(@NonNull String methodName) {
            Trace.traceBegin(TRACE_TAG_DALVIK, methodName);
        }

        @Override
        public void close() {
            Trace.traceEnd(TRACE_TAG_DALVIK);
        }
    }

    public static class TracingWithTimingLogging extends Tracing {
        @NonNull private final String mTag;
        @NonNull private final String mMethodName;
        @NonNull private final long mStartTimeMs;

        public TracingWithTimingLogging(@NonNull String tag, @NonNull String methodName) {
            super(methodName);
            mTag = tag;
            mMethodName = methodName;
            mStartTimeMs = SystemClock.elapsedRealtime();
            Log.d(tag, methodName);
        }

        @Override
        public void close() {
            Log.d(mTag,
                    mMethodName + " took to complete: "
                            + (SystemClock.elapsedRealtime() - mStartTimeMs) + "ms");
            super.close();
        }
    }

    /** The result of {@link #getOrInitReferenceProfile} and {@link #initReferenceProfile}. */
    @AutoValue
    @SuppressWarnings("AutoValueImmutableFields") // Can't use ImmutableList because it's in Guava.
    public abstract static class InitProfileResult {
        static @NonNull InitProfileResult create(@Nullable ProfilePath profile,
                boolean isOtherReadable, @NonNull List<String> externalProfileErrors) {
            return new AutoValue_Utils_InitProfileResult(
                    profile, isOtherReadable, Collections.unmodifiableList(externalProfileErrors));
        }

        /**
         * The found or initialized profile, or null if there is no reference profile or external
         * profile to use.
         */
        abstract @Nullable ProfilePath profile();

        /**
         * Whether the profile is readable by others.
         *
         * If {@link #profile} returns null, this field is always true.
         */
        abstract boolean isOtherReadable();

        /** Errors encountered when initializing from external profiles. */
        abstract @NonNull List<String> externalProfileErrors();
    }

    @FunctionalInterface
    private interface ProfileInitializer {
        CopyAndRewriteProfileResult get() throws RemoteException;
    }
}