PackageManagerServiceUtils.java revision fad6dc10b0e723f81a264ce608a47f2b7c279b2a
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 com.android.server.pm.dex.DexManager;
20import com.android.server.pm.dex.PackageDexUsage;
21
22import static com.android.server.pm.PackageManagerService.DEBUG_DEXOPT;
23import static com.android.server.pm.PackageManagerService.TAG;
24
25import com.android.internal.util.ArrayUtils;
26
27import android.annotation.NonNull;
28import android.app.AppGlobals;
29import android.content.Intent;
30import android.content.pm.PackageParser;
31import android.content.pm.ResolveInfo;
32import android.os.Build;
33import android.os.RemoteException;
34import android.os.UserHandle;
35import android.system.ErrnoException;
36import android.system.Os;
37import android.util.ArraySet;
38import android.util.Log;
39import android.util.Slog;
40import android.util.jar.StrictJarFile;
41import dalvik.system.VMRuntime;
42import libcore.io.Libcore;
43
44import java.io.File;
45import java.io.IOException;
46import java.util.ArrayList;
47import java.util.Collection;
48import java.util.Collections;
49import java.util.Iterator;
50import java.util.LinkedList;
51import java.util.List;
52import java.util.function.Predicate;
53import java.util.zip.ZipEntry;
54
55/**
56 * Class containing helper methods for the PackageManagerService.
57 *
58 * {@hide}
59 */
60public class PackageManagerServiceUtils {
61    private final static long SEVEN_DAYS_IN_MILLISECONDS = 7 * 24 * 60 * 60 * 1000;
62
63    private static ArraySet<String> getPackageNamesForIntent(Intent intent, int userId) {
64        List<ResolveInfo> ris = null;
65        try {
66            ris = AppGlobals.getPackageManager().queryIntentReceivers(intent, null, 0, userId)
67                    .getList();
68        } catch (RemoteException e) {
69        }
70        ArraySet<String> pkgNames = new ArraySet<String>();
71        if (ris != null) {
72            for (ResolveInfo ri : ris) {
73                pkgNames.add(ri.activityInfo.packageName);
74            }
75        }
76        return pkgNames;
77    }
78
79    // Sort a list of apps by their last usage, most recently used apps first. The order of
80    // packages without usage data is undefined (but they will be sorted after the packages
81    // that do have usage data).
82    public static void sortPackagesByUsageDate(List<PackageParser.Package> pkgs,
83            PackageManagerService packageManagerService) {
84        if (!packageManagerService.isHistoricalPackageUsageAvailable()) {
85            return;
86        }
87
88        Collections.sort(pkgs, (pkg1, pkg2) ->
89                Long.compare(pkg2.getLatestForegroundPackageUseTimeInMills(),
90                        pkg1.getLatestForegroundPackageUseTimeInMills()));
91    }
92
93    // Apply the given {@code filter} to all packages in {@code packages}. If tested positive, the
94    // package will be removed from {@code packages} and added to {@code result} with its
95    // dependencies. If usage data is available, the positive packages will be sorted by usage
96    // data (with {@code sortTemp} as temporary storage).
97    private static void applyPackageFilter(Predicate<PackageParser.Package> filter,
98            Collection<PackageParser.Package> result,
99            Collection<PackageParser.Package> packages,
100            @NonNull List<PackageParser.Package> sortTemp,
101            PackageManagerService packageManagerService) {
102        for (PackageParser.Package pkg : packages) {
103            if (filter.test(pkg)) {
104                sortTemp.add(pkg);
105            }
106        }
107
108        sortPackagesByUsageDate(sortTemp, packageManagerService);
109        packages.removeAll(sortTemp);
110
111        for (PackageParser.Package pkg : sortTemp) {
112            result.add(pkg);
113
114            Collection<PackageParser.Package> deps =
115                    packageManagerService.findSharedNonSystemLibraries(pkg);
116            if (!deps.isEmpty()) {
117                deps.removeAll(result);
118                result.addAll(deps);
119                packages.removeAll(deps);
120            }
121        }
122
123        sortTemp.clear();
124    }
125
126    // Sort apps by importance for dexopt ordering. Important apps are given
127    // more priority in case the device runs out of space.
128    public static List<PackageParser.Package> getPackagesForDexopt(
129            Collection<PackageParser.Package> packages,
130            PackageManagerService packageManagerService) {
131        ArrayList<PackageParser.Package> remainingPkgs = new ArrayList<>(packages);
132        LinkedList<PackageParser.Package> result = new LinkedList<>();
133        ArrayList<PackageParser.Package> sortTemp = new ArrayList<>(remainingPkgs.size());
134
135        // Give priority to core apps.
136        applyPackageFilter((pkg) -> pkg.coreApp, result, remainingPkgs, sortTemp,
137                packageManagerService);
138
139        // Give priority to system apps that listen for pre boot complete.
140        Intent intent = new Intent(Intent.ACTION_PRE_BOOT_COMPLETED);
141        final ArraySet<String> pkgNames = getPackageNamesForIntent(intent, UserHandle.USER_SYSTEM);
142        applyPackageFilter((pkg) -> pkgNames.contains(pkg.packageName), result, remainingPkgs,
143                sortTemp, packageManagerService);
144
145        // Give priority to apps used by other apps.
146        DexManager dexManager = packageManagerService.getDexManager();
147        applyPackageFilter((pkg) ->
148                dexManager.getPackageUseInfoOrDefault(pkg.packageName).isUsedByOtherApps(), result,
149                remainingPkgs, sortTemp, packageManagerService);
150
151        // Filter out packages that aren't recently used, add all remaining apps.
152        // TODO: add a property to control this?
153        Predicate<PackageParser.Package> remainingPredicate;
154        if (!remainingPkgs.isEmpty() && packageManagerService.isHistoricalPackageUsageAvailable()) {
155            if (DEBUG_DEXOPT) {
156                Log.i(TAG, "Looking at historical package use");
157            }
158            // Get the package that was used last.
159            PackageParser.Package lastUsed = Collections.max(remainingPkgs, (pkg1, pkg2) ->
160                    Long.compare(pkg1.getLatestForegroundPackageUseTimeInMills(),
161                            pkg2.getLatestForegroundPackageUseTimeInMills()));
162            if (DEBUG_DEXOPT) {
163                Log.i(TAG, "Taking package " + lastUsed.packageName + " as reference in time use");
164            }
165            long estimatedPreviousSystemUseTime =
166                    lastUsed.getLatestForegroundPackageUseTimeInMills();
167            // Be defensive if for some reason package usage has bogus data.
168            if (estimatedPreviousSystemUseTime != 0) {
169                final long cutoffTime = estimatedPreviousSystemUseTime - SEVEN_DAYS_IN_MILLISECONDS;
170                remainingPredicate =
171                        (pkg) -> pkg.getLatestForegroundPackageUseTimeInMills() >= cutoffTime;
172            } else {
173                // No meaningful historical info. Take all.
174                remainingPredicate = (pkg) -> true;
175            }
176            sortPackagesByUsageDate(remainingPkgs, packageManagerService);
177        } else {
178            // No historical info. Take all.
179            remainingPredicate = (pkg) -> true;
180        }
181        applyPackageFilter(remainingPredicate, result, remainingPkgs, sortTemp,
182                packageManagerService);
183
184        if (DEBUG_DEXOPT) {
185            Log.i(TAG, "Packages to be dexopted: " + packagesToString(result));
186            Log.i(TAG, "Packages skipped from dexopt: " + packagesToString(remainingPkgs));
187        }
188
189        return result;
190    }
191
192    /**
193     * Checks if the package was inactive during since <code>thresholdTimeinMillis</code>.
194     * Package is considered active, if:
195     * 1) It was active in foreground.
196     * 2) It was active in background and also used by other apps.
197     *
198     * If it doesn't have sufficient information about the package, it return <code>false</code>.
199     */
200    static boolean isUnusedSinceTimeInMillis(long firstInstallTime, long currentTimeInMillis,
201            long thresholdTimeinMillis, PackageDexUsage.PackageUseInfo packageUseInfo,
202            long latestPackageUseTimeInMillis, long latestForegroundPackageUseTimeInMillis) {
203
204        if (currentTimeInMillis - firstInstallTime < thresholdTimeinMillis) {
205            return false;
206        }
207
208        // If the app was active in foreground during the threshold period.
209        boolean isActiveInForeground = (currentTimeInMillis
210                - latestForegroundPackageUseTimeInMillis)
211                < thresholdTimeinMillis;
212
213        if (isActiveInForeground) {
214            return false;
215        }
216
217        // If the app was active in background during the threshold period and was used
218        // by other packages.
219        boolean isActiveInBackgroundAndUsedByOtherPackages = ((currentTimeInMillis
220                - latestPackageUseTimeInMillis)
221                < thresholdTimeinMillis)
222                && packageUseInfo.isUsedByOtherApps();
223
224        return !isActiveInBackgroundAndUsedByOtherPackages;
225    }
226
227    /**
228     * Returns the canonicalized path of {@code path} as per {@code realpath(3)}
229     * semantics.
230     */
231    public static String realpath(File path) throws IOException {
232        try {
233            return Os.realpath(path.getAbsolutePath());
234        } catch (ErrnoException ee) {
235            throw ee.rethrowAsIOException();
236        }
237    }
238
239    public static String packagesToString(Collection<PackageParser.Package> c) {
240        StringBuilder sb = new StringBuilder();
241        for (PackageParser.Package pkg : c) {
242            if (sb.length() > 0) {
243                sb.append(", ");
244            }
245            sb.append(pkg.packageName);
246        }
247        return sb.toString();
248    }
249
250    /**
251     * Verifies that the given string {@code isa} is a valid supported isa on
252     * the running device.
253     */
254    public static boolean checkISA(String isa) {
255        for (String abi : Build.SUPPORTED_ABIS) {
256            if (VMRuntime.getInstructionSet(abi).equals(isa)) {
257                return true;
258            }
259        }
260        return false;
261    }
262
263    /**
264     * Checks that the archive located at {@code fileName} has uncompressed dex file and so
265     * files that can be direclty mapped.
266     */
267    public static void logApkHasUncompressedCode(String fileName) {
268        StrictJarFile jarFile = null;
269        try {
270            jarFile = new StrictJarFile(fileName,
271                    false /*verify*/, false /*signatureSchemeRollbackProtectionsEnforced*/);
272            Iterator<ZipEntry> it = jarFile.iterator();
273            while (it.hasNext()) {
274                ZipEntry entry = it.next();
275                if (entry.getName().endsWith(".dex")) {
276                    if (entry.getMethod() != ZipEntry.STORED) {
277                        Slog.wtf(TAG, "APK " + fileName + " has compressed dex code " +
278                                entry.getName());
279                    } else if ((entry.getDataOffset() & 0x3) != 0) {
280                        Slog.wtf(TAG, "APK " + fileName + " has unaligned dex code " +
281                                entry.getName());
282                    }
283                } else if (entry.getName().endsWith(".so")) {
284                    if (entry.getMethod() != ZipEntry.STORED) {
285                        Slog.wtf(TAG, "APK " + fileName + " has compressed native code " +
286                                entry.getName());
287                    } else if ((entry.getDataOffset() & (0x1000 - 1)) != 0) {
288                        Slog.wtf(TAG, "APK " + fileName + " has unaligned native code " +
289                                entry.getName());
290                    }
291                }
292            }
293        } catch (IOException ignore) {
294            Slog.wtf(TAG, "Error when parsing APK " + fileName);
295        } finally {
296            try {
297                if (jarFile != null) {
298                    jarFile.close();
299                }
300            } catch (IOException ignore) {}
301        }
302        return;
303    }
304
305    /**
306     * Checks that the APKs in the given package have uncompressed dex file and so
307     * files that can be direclty mapped.
308     */
309    public static void logPackageHasUncompressedCode(PackageParser.Package pkg) {
310        logApkHasUncompressedCode(pkg.baseCodePath);
311        if (!ArrayUtils.isEmpty(pkg.splitCodePaths)) {
312            for (int i = 0; i < pkg.splitCodePaths.length; i++) {
313                logApkHasUncompressedCode(pkg.splitCodePaths[i]);
314            }
315        }
316    }
317}
318