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