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