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