PackageManagerServiceUtils.java revision dab38e000436bf8234955b0333eaecf389e65b6f
1/*
2 * Copyright (C) 2016 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
17package com.android.server.pm;
18
19import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
20import static com.android.server.pm.PackageManagerService.TAG;
21
22import android.annotation.NonNull;
23import android.app.AppGlobals;
24import android.content.Intent;
25import android.content.pm.PackageParser;
26import android.content.pm.ResolveInfo;
27import android.os.RemoteException;
28import android.os.UserHandle;
29import android.system.ErrnoException;
30import android.util.ArraySet;
31import android.util.Log;
32import libcore.io.Libcore;
33
34import java.io.File;
35import java.io.IOException;
36import java.util.ArrayList;
37import java.util.Collection;
38import java.util.Collections;
39import java.util.LinkedList;
40import java.util.List;
41import java.util.function.Predicate;
42
43/**
44 * Class containing helper methods for the PackageManagerService.
45 *
46 * {@hide}
47 */
48public class PackageManagerServiceUtils {
49    private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
50
51    private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
52        List<ResolveInfo> ris = null;
53        try {
54            ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
55                    .getList();
56        } catch (RemoteException e) {
57        }
58        ArraySet<String> pkgNames = new ArraySet<String>();
59        if (ris != null) {
60            for (ResolveInfo ri : ris) {
61                pkgNames.add(ri.activityInfo.packageName);
62            }
63        }
64        return pkgNames;
65    }
66
67    // Sort a list of apps by their last usage, most recently used apps first. The order of
68    // packages without usage data is undefined (but they will be sorted after the packages
69    // that do have usage data).
70    public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
71            PackageManagerService packageManagerService) {
72        if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
73            return;
74        }
75
76        Collections.sort(pkgs, (pkg1, pkg2) ->
77                Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
78                        pkg1.getLatestForegroundPackageUseTimeInMills()));
79    }
80
81    // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
82    // package will be removed from {@code packages} and added to {@code result} with its
83    // dependencies. If usage data is available, the positive packages will be sorted by usage
84    // data (with {@code sortTemp} as temporary storage).
85    private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
86            Collection<PackageParser.Package> result,
87            Collection<PackageParser.Package> packages,
88            @NonNull List<PackageParser.Package> sortTemp,
89            PackageManagerService packageManagerService) {
90        for (PackageParser.Package pkg : packages) {
91            if (filter.test(pkg)) {
92                sortTemp.add(pkg);
93            }
94        }
95
96        sortPackagesByUsageDate(sortTemp, packageManagerService);
97        packages.removeAll(sortTemp);
98
99        for (PackageParser.Package pkg : sortTemp) {
100            result.add(pkg);
101
102            Collection<PackageParser.Package> deps =
103                    packageManagerService.findSharedNonSystemLibraries(pkg);
104            if (!deps.isEmpty()) {
105                deps.removeAll(result);
106                result.addAll(deps);
107                packages.removeAll(deps);
108            }
109        }
110
111        sortTemp.clear();
112    }
113
114    // Sort apps by importance for dexopt ordering. Important apps are given
115    // more priority in case the device runs out of space.
116    public static List<PackageParser.Package> getPackagesForDexopt(
117            Collection<PackageParser.Package> packages,
118            PackageManagerService packageManagerService) {
119        ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
120        LinkedList<PackageParser.Package> result = new LinkedList<>();
121        ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
122
123        // Give priority to core apps.
124        applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
125                packageManagerService);
126
127        // Give priority to system apps that listen for pre boot complete.
128        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
129        final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
130        applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
131                sortTemp, packageManagerService);
132
133        // Give priority to apps used by other apps.
134        applyPackageFilter((pkg) -> PackageDexOptimizer.isUsedByOtherApps(pkg), result,
135                remainingPkgs, sortTemp, packageManagerService);
136
137        // Filter out packages that aren't recently used, add all remaining apps.
138        // TODO: add a property to control this?
139        Predicate<PackageParser.Package> remainingPredicate;
140        if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
141            if (DEBUG_DEXOPT) {
142                Log.i(TAG, "Looking at historical package use");
143            }
144            // Get the package that was used last.
145            PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
146                    Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
147                            pkg2.getLatestForegroundPackageUseTimeInMills()));
148            if (DEBUG_DEXOPT) {
149                Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
150            }
151            long estimatedPreviousSystemUseTime =
152                    lastUsed.getLatestForegroundPackageUseTimeInMills();
153            // Be defensive if for some reason package usage has bogus data.
154            if (estimatedPreviousSystemUseTime != 0) {
155                final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
156                remainingPredicate =
157                        (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
158            } else {
159                // No meaningful historical info. Take all.
160                remainingPredicate = (pkg) -> true;
161            }
162            sortPackagesByUsageDate(remainingPkgs, packageManagerService);
163        } else {
164            // No historical info. Take all.
165            remainingPredicate = (pkg) -> true;
166        }
167        applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
168                packageManagerService);
169
170        if (DEBUG_DEXOPT) {
171            Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
172            Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
173        }
174
175        return result;
176    }
177
178    /**
179     * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
180     * semantics.
181     */
182    public static String realpath(File path) throws IOException {
183        try {
184            return Libcore.os.realpath(path.getAbsolutePath());
185        } catch (ErrnoException ee) {
186            throw ee.rethrowAsIOException();
187        }
188    }
189
190    public static String packagesToString(Collection<PackageParser.Package> c) {
191        StringBuilder sb = new StringBuilder();
192        for (PackageParser.Package pkg : c) {
193            if (sb.length() > 0) {
194                sb.append(", ");
195            }
196            sb.append(pkg.packageName);
197        }
198        return sb.toString();
199    }
200}
201