InstantAppRegistry.java revision cdd685c07504223e37e7831ce592446ec4ac6f6a
1/*
2 * Copyright (C) 2015 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 android.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.UserIdInt;
22import android.content.Intent;
23import android.content.pm.InstantAppInfo;
24import android.content.pm.PackageManager;
25import android.content.pm.PackageParser;
26import android.graphics.Bitmap;
27import android.graphics.BitmapFactory;
28import android.graphics.Canvas;
29import android.graphics.drawable.BitmapDrawable;
30import android.graphics.drawable.Drawable;
31import android.os.Binder;
32import android.os.Environment;
33import android.os.Handler;
34import android.os.Looper;
35import android.os.Message;
36import android.os.UserHandle;
37import android.os.storage.StorageManager;
38import android.provider.Settings;
39import android.util.ArrayMap;
40import android.util.AtomicFile;
41import android.util.ByteStringUtils;
42import android.util.PackageUtils;
43import android.util.Slog;
44import android.util.SparseArray;
45import android.util.SparseBooleanArray;
46import android.util.Xml;
47import com.android.internal.annotations.GuardedBy;
48import com.android.internal.os.BackgroundThread;
49import com.android.internal.os.SomeArgs;
50import com.android.internal.util.ArrayUtils;
51import com.android.internal.util.XmlUtils;
52import libcore.io.IoUtils;
53import org.xmlpull.v1.XmlPullParser;
54import org.xmlpull.v1.XmlPullParserException;
55import org.xmlpull.v1.XmlSerializer;
56
57import java.io.File;
58import java.io.FileInputStream;
59import java.io.FileNotFoundException;
60import java.io.FileOutputStream;
61import java.io.IOException;
62import java.nio.charset.StandardCharsets;
63import java.security.SecureRandom;
64import java.util.ArrayList;
65import java.util.List;
66import java.util.Locale;
67import java.util.Set;
68import java.util.function.Predicate;
69
70/**
71 * This class is a part of the package manager service that is responsible
72 * for managing data associated with instant apps such as cached uninstalled
73 * instant apps and instant apps' cookies. In addition it is responsible for
74 * pruning installed instant apps and meta-data for uninstalled instant apps
75 * when free space is needed.
76 */
77class InstantAppRegistry {
78    private static final boolean DEBUG = false;
79
80    private static final String LOG_TAG = "InstantAppRegistry";
81
82    static final long DEFAULT_INSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
83            DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */
84
85    private static final long DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD =
86            DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
87
88    static final long DEFAULT_UNINSTALLED_INSTANT_APP_MIN_CACHE_PERIOD =
89            DEBUG ? 30 * 1000L /* thirty seconds */ : 7 * 24 * 60 * 60 * 1000L; /* one week */
90
91    private static final long DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD =
92            DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
93
94    private static final String INSTANT_APPS_FOLDER = "instant";
95    private static final String INSTANT_APP_ICON_FILE = "icon.png";
96    private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
97    private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
98    private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
99    private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
100
101    private static final String TAG_PACKAGE = "package";
102    private static final String TAG_PERMISSIONS = "permissions";
103    private static final String TAG_PERMISSION = "permission";
104
105    private static final String ATTR_LABEL = "label";
106    private static final String ATTR_NAME = "name";
107    private static final String ATTR_GRANTED = "granted";
108
109    private final PackageManagerService mService;
110    private final CookiePersistence mCookiePersistence;
111
112    /** State for uninstalled instant apps */
113    @GuardedBy("mService.mPackages")
114    private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
115
116    /**
117     * Automatic grants for access to instant app metadata.
118     * The key is the target application UID.
119     * The value is a set of instant app UIDs.
120     * UserID -> TargetAppId -> InstantAppId
121     */
122    @GuardedBy("mService.mPackages")
123    private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants;
124
125    /** The set of all installed instant apps. UserID -> AppID */
126    @GuardedBy("mService.mPackages")
127    private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
128
129    public InstantAppRegistry(PackageManagerService service) {
130        mService = service;
131        mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
132    }
133
134    public byte[] getInstantAppCookieLPw(@NonNull String packageName,
135            @UserIdInt int userId) {
136        // Only installed packages can get their own cookie
137        PackageParser.Package pkg = mService.mPackages.get(packageName);
138        if (pkg == null) {
139            return null;
140        }
141
142        byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
143        if (pendingCookie != null) {
144            return pendingCookie;
145        }
146        File cookieFile = peekInstantCookieFile(packageName, userId);
147        if (cookieFile != null && cookieFile.exists()) {
148            try {
149                return IoUtils.readFileAsByteArray(cookieFile.toString());
150            } catch (IOException e) {
151                Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
152            }
153        }
154        return null;
155    }
156
157    public boolean setInstantAppCookieLPw(@NonNull String packageName,
158            @Nullable byte[] cookie, @UserIdInt int userId) {
159        if (cookie != null && cookie.length > 0) {
160            final int maxCookieSize = mService.mContext.getPackageManager()
161                    .getInstantAppCookieMaxBytes();
162            if (cookie.length > maxCookieSize) {
163                Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
164                        + cookie.length + " bytes while max size is " + maxCookieSize);
165                return false;
166            }
167        }
168
169        // Only an installed package can set its own cookie
170        PackageParser.Package pkg = mService.mPackages.get(packageName);
171        if (pkg == null) {
172            return false;
173        }
174
175        mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
176        return true;
177    }
178
179    private void persistInstantApplicationCookie(@Nullable byte[] cookie,
180            @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
181        synchronized (mService.mPackages) {
182            File appDir = getInstantApplicationDir(packageName, userId);
183            if (!appDir.exists() && !appDir.mkdirs()) {
184                Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
185                return;
186            }
187
188            if (cookieFile.exists() && !cookieFile.delete()) {
189                Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
190            }
191
192            // No cookie or an empty one means delete - done
193            if (cookie == null || cookie.length <= 0) {
194                return;
195            }
196        }
197        try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
198            fos.write(cookie, 0, cookie.length);
199        } catch (IOException e) {
200            Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
201        }
202    }
203
204    public Bitmap getInstantAppIconLPw(@NonNull String packageName,
205                                       @UserIdInt int userId) {
206        File iconFile = new File(getInstantApplicationDir(packageName, userId),
207                INSTANT_APP_ICON_FILE);
208        if (iconFile.exists()) {
209            return BitmapFactory.decodeFile(iconFile.toString());
210        }
211        return null;
212    }
213
214    public String getInstantAppAndroidIdLPw(@NonNull String packageName,
215                                            @UserIdInt int userId) {
216        File idFile = new File(getInstantApplicationDir(packageName, userId),
217                INSTANT_APP_ANDROID_ID_FILE);
218        if (idFile.exists()) {
219            try {
220                return IoUtils.readFileAsString(idFile.getAbsolutePath());
221            } catch (IOException e) {
222                Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
223            }
224        }
225        return generateInstantAppAndroidIdLPw(packageName, userId);
226    }
227
228    private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
229                                                @UserIdInt int userId) {
230        byte[] randomBytes = new byte[8];
231        new SecureRandom().nextBytes(randomBytes);
232        String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
233        File appDir = getInstantApplicationDir(packageName, userId);
234        if (!appDir.exists() && !appDir.mkdirs()) {
235            Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
236            return id;
237        }
238        File idFile = new File(getInstantApplicationDir(packageName, userId),
239                INSTANT_APP_ANDROID_ID_FILE);
240        try (FileOutputStream fos = new FileOutputStream(idFile)) {
241            fos.write(id.getBytes());
242        } catch (IOException e) {
243            Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
244        }
245        return id;
246
247    }
248
249    public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
250        List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
251        List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
252        if (installedApps != null) {
253            if (uninstalledApps != null) {
254                installedApps.addAll(uninstalledApps);
255            }
256            return installedApps;
257        }
258        return uninstalledApps;
259    }
260
261    public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
262        PackageSetting ps = (PackageSetting) pkg.mExtras;
263        if (ps == null) {
264            return;
265        }
266
267        for (int userId : userIds) {
268            // Ignore not installed apps
269            if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
270                continue;
271            }
272
273            // Propagate permissions before removing any state
274            propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
275
276            // Track instant apps
277            if (ps.getInstantApp(userId)) {
278                addInstantAppLPw(userId, ps.appId);
279            }
280
281            // Remove the in-memory state
282            removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
283                            state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
284                    userId);
285
286            // Remove the on-disk state except the cookie
287            File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
288            new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
289            new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
290
291            // If app signature changed - wipe the cookie
292            File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
293            if (currentCookieFile == null) {
294                continue;
295            }
296
297            // Before we used only the first signature to compute the SHA 256 but some
298            // apps could be singed by multiple certs and the cert order is undefined.
299            // We prefer the modern computation procedure where all certs are taken
300            // into account but also allow the value from the old computation to avoid
301            // data loss.
302            final String[] signaturesSha256Digests = PackageUtils.computeSignaturesSha256Digests(
303                    pkg.mSignatures);
304            final String signaturesSha256Digest = PackageUtils.computeSignaturesSha256Digest(
305                    signaturesSha256Digests);
306
307            // We prefer a match based on all signatures
308            if (currentCookieFile.equals(computeInstantCookieFile(pkg.packageName,
309                    signaturesSha256Digest, userId))) {
310                return;
311            }
312
313            // For backwards compatibility we accept match based on first signature
314            if (pkg.mSignatures.length > 1 && currentCookieFile.equals(computeInstantCookieFile(
315                    pkg.packageName, signaturesSha256Digests[0], userId))) {
316                return;
317            }
318
319            // Sorry, you are out of luck - different signatures - nuke data
320            Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
321                    + " changed - dropping cookie");
322                // Make sure a pending write for the old signed app is cancelled
323            mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
324            currentCookieFile.delete();
325        }
326    }
327
328    public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
329            @NonNull int[] userIds) {
330        PackageSetting ps = (PackageSetting) pkg.mExtras;
331        if (ps == null) {
332            return;
333        }
334
335        for (int userId : userIds) {
336            if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
337                continue;
338            }
339
340            if (ps.getInstantApp(userId)) {
341                // Add a record for an uninstalled instant app
342                addUninstalledInstantAppLPw(pkg, userId);
343                removeInstantAppLPw(userId, ps.appId);
344            } else {
345                // Deleting an app prunes all instant state such as cookie
346                deleteDir(getInstantApplicationDir(pkg.packageName, userId));
347                mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
348                removeAppLPw(userId, ps.appId);
349            }
350        }
351    }
352
353    public void onUserRemovedLPw(int userId) {
354        if (mUninstalledInstantApps != null) {
355            mUninstalledInstantApps.remove(userId);
356            if (mUninstalledInstantApps.size() <= 0) {
357                mUninstalledInstantApps = null;
358            }
359        }
360        if (mInstalledInstantAppUids != null) {
361            mInstalledInstantAppUids.remove(userId);
362            if (mInstalledInstantAppUids.size() <= 0) {
363                mInstalledInstantAppUids = null;
364            }
365        }
366        if (mInstantGrants != null) {
367            mInstantGrants.remove(userId);
368            if (mInstantGrants.size() <= 0) {
369                mInstantGrants = null;
370            }
371        }
372        deleteDir(getInstantApplicationsDir(userId));
373    }
374
375    public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
376            int instantAppId) {
377        if (mInstantGrants == null) {
378            return false;
379        }
380        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
381        if (targetAppList == null) {
382            return false;
383        }
384        final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
385        if (instantGrantList == null) {
386            return false;
387        }
388        return instantGrantList.get(instantAppId);
389    }
390
391    public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
392            int targetAppId, int instantAppId) {
393        if (mInstalledInstantAppUids == null) {
394            return;     // no instant apps installed; no need to grant
395        }
396        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
397        if (instantAppList == null || !instantAppList.get(instantAppId)) {
398            return;     // instant app id isn't installed; no need to grant
399        }
400        if (instantAppList.get(targetAppId)) {
401            return;     // target app id is an instant app; no need to grant
402        }
403        if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
404            final Set<String> categories = intent.getCategories();
405            if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
406                return;  // launched via VIEW/BROWSABLE intent; no need to grant
407            }
408        }
409        if (mInstantGrants == null) {
410            mInstantGrants = new SparseArray<>();
411        }
412        SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
413        if (targetAppList == null) {
414            targetAppList = new SparseArray<>();
415            mInstantGrants.put(userId, targetAppList);
416        }
417        SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
418        if (instantGrantList == null) {
419            instantGrantList = new SparseBooleanArray();
420            targetAppList.put(targetAppId, instantGrantList);
421        }
422        instantGrantList.put(instantAppId, true /*granted*/);
423    }
424
425    public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
426        if (mInstalledInstantAppUids == null) {
427            mInstalledInstantAppUids = new SparseArray<>();
428        }
429        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
430        if (instantAppList == null) {
431            instantAppList = new SparseBooleanArray();
432            mInstalledInstantAppUids.put(userId, instantAppList);
433        }
434        instantAppList.put(instantAppId, true /*installed*/);
435    }
436
437    private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
438        // remove from the installed list
439        if (mInstalledInstantAppUids == null) {
440            return; // no instant apps on the system
441        }
442        final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
443        if (instantAppList == null) {
444            return;
445        }
446
447        instantAppList.delete(instantAppId);
448
449        // remove any grants
450        if (mInstantGrants == null) {
451            return; // no grants on the system
452        }
453        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
454        if (targetAppList == null) {
455            return; // no grants for this user
456        }
457        for (int i = targetAppList.size() - 1; i >= 0; --i) {
458            targetAppList.valueAt(i).delete(instantAppId);
459        }
460    }
461
462    private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
463        // remove from the installed list
464        if (mInstantGrants == null) {
465            return; // no grants on the system
466        }
467        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
468        if (targetAppList == null) {
469            return; // no grants for this user
470        }
471        targetAppList.delete(targetAppId);
472    }
473
474    private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
475            @UserIdInt int userId) {
476        InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
477                pkg, userId, false);
478        if (uninstalledApp == null) {
479            return;
480        }
481        if (mUninstalledInstantApps == null) {
482            mUninstalledInstantApps = new SparseArray<>();
483        }
484        List<UninstalledInstantAppState> uninstalledAppStates =
485                mUninstalledInstantApps.get(userId);
486        if (uninstalledAppStates == null) {
487            uninstalledAppStates = new ArrayList<>();
488            mUninstalledInstantApps.put(userId, uninstalledAppStates);
489        }
490        UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
491                uninstalledApp, System.currentTimeMillis());
492        uninstalledAppStates.add(uninstalledAppState);
493
494        writeUninstalledInstantAppMetadata(uninstalledApp, userId);
495        writeInstantApplicationIconLPw(pkg, userId);
496    }
497
498    private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
499            @UserIdInt int userId) {
500        File appDir = getInstantApplicationDir(pkg.packageName, userId);
501        if (!appDir.exists()) {
502            return;
503        }
504
505        Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
506
507        final Bitmap bitmap;
508        if (icon instanceof BitmapDrawable) {
509            bitmap = ((BitmapDrawable) icon).getBitmap();
510        } else  {
511            bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
512                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
513            Canvas canvas = new Canvas(bitmap);
514            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
515            icon.draw(canvas);
516        }
517
518        File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
519                INSTANT_APP_ICON_FILE);
520
521        try (FileOutputStream out = new FileOutputStream(iconFile)) {
522            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
523        } catch (Exception e) {
524            Slog.e(LOG_TAG, "Error writing instant app icon", e);
525        }
526    }
527
528    public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
529            @UserIdInt int userId) {
530        removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
531                state.mInstantAppInfo.getPackageName().equals(packageName),
532                userId);
533
534        File instantAppDir = getInstantApplicationDir(packageName, userId);
535        new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
536        new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
537        new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
538        File cookie = peekInstantCookieFile(packageName, userId);
539        if (cookie != null) {
540            cookie.delete();
541        }
542    }
543
544    private void removeUninstalledInstantAppStateLPw(
545            @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
546        if (mUninstalledInstantApps == null) {
547            return;
548        }
549        List<UninstalledInstantAppState> uninstalledAppStates =
550                mUninstalledInstantApps.get(userId);
551        if (uninstalledAppStates == null) {
552            return;
553        }
554        final int appCount = uninstalledAppStates.size();
555        for (int i = appCount - 1; i >= 0; --i) {
556            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
557            if (!criteria.test(uninstalledAppState)) {
558                continue;
559            }
560            uninstalledAppStates.remove(i);
561            if (uninstalledAppStates.isEmpty()) {
562                mUninstalledInstantApps.remove(userId);
563                if (mUninstalledInstantApps.size() <= 0) {
564                    mUninstalledInstantApps = null;
565                }
566                return;
567            }
568        }
569    }
570
571    void pruneInstantApps() {
572        final long maxInstalledCacheDuration = Settings.Global.getLong(
573                mService.mContext.getContentResolver(),
574                Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
575                DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
576
577        final long maxUninstalledCacheDuration = Settings.Global.getLong(
578                mService.mContext.getContentResolver(),
579                Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
580                DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
581
582        try {
583            pruneInstantApps(Long.MAX_VALUE,
584                    maxInstalledCacheDuration, maxUninstalledCacheDuration);
585        } catch (IOException e) {
586            Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
587        }
588    }
589
590    boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
591        try {
592            return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
593        } catch (IOException e) {
594            Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
595            return false;
596        }
597    }
598
599    boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
600        try {
601            return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
602        } catch (IOException e) {
603            Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
604            return false;
605        }
606    }
607
608    /**
609     * Prunes instant apps until there is enough <code>neededSpace</code>. Both
610     * installed and uninstalled instant apps are pruned that are older than
611     * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code>
612     * respectively. All times are in milliseconds.
613     *
614     * @param neededSpace The space to ensure is free.
615     * @param maxInstalledCacheDuration The max duration for caching installed apps in millis.
616     * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis.
617     * @return Whether enough space was freed.
618     *
619     * @throws IOException
620     */
621    private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
622            long maxUninstalledCacheDuration) throws IOException {
623        final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
624        final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
625
626        if (file.getUsableSpace() >= neededSpace) {
627            return true;
628        }
629
630        List<String> packagesToDelete = null;
631
632        final int[] allUsers;
633        final long now = System.currentTimeMillis();
634
635        // Prune first installed instant apps
636        synchronized (mService.mPackages) {
637            allUsers = PackageManagerService.sUserManager.getUserIds();
638
639            final int packageCount = mService.mPackages.size();
640            for (int i = 0; i < packageCount; i++) {
641                final PackageParser.Package pkg = mService.mPackages.valueAt(i);
642                if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) {
643                    continue;
644                }
645                if (!(pkg.mExtras instanceof PackageSetting)) {
646                    continue;
647                }
648                final PackageSetting  ps = (PackageSetting) pkg.mExtras;
649                boolean installedOnlyAsInstantApp = false;
650                for (int userId : allUsers) {
651                    if (ps.getInstalled(userId)) {
652                        if (ps.getInstantApp(userId)) {
653                            installedOnlyAsInstantApp = true;
654                        } else {
655                            installedOnlyAsInstantApp = false;
656                            break;
657                        }
658                    }
659                }
660                if (installedOnlyAsInstantApp) {
661                    if (packagesToDelete == null) {
662                        packagesToDelete = new ArrayList<>();
663                    }
664                    packagesToDelete.add(pkg.packageName);
665                }
666            }
667
668            if (packagesToDelete != null) {
669                packagesToDelete.sort((String lhs, String rhs) -> {
670                    final PackageParser.Package lhsPkg = mService.mPackages.get(lhs);
671                    final PackageParser.Package rhsPkg = mService.mPackages.get(rhs);
672                    if (lhsPkg == null && rhsPkg == null) {
673                        return 0;
674                    } else if (lhsPkg == null) {
675                        return -1;
676                    } else if (rhsPkg == null) {
677                        return 1;
678                    } else {
679                        if (lhsPkg.getLatestPackageUseTimeInMills() >
680                                rhsPkg.getLatestPackageUseTimeInMills()) {
681                            return 1;
682                        } else if (lhsPkg.getLatestPackageUseTimeInMills() <
683                                rhsPkg.getLatestPackageUseTimeInMills()) {
684                            return -1;
685                        } else {
686                            if (lhsPkg.mExtras instanceof PackageSetting
687                                    && rhsPkg.mExtras instanceof PackageSetting) {
688                                final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras;
689                                final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras;
690                                if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) {
691                                    return 1;
692                                } else {
693                                    return -1;
694                                }
695                            } else {
696                                return 0;
697                            }
698                        }
699                    }
700                });
701            }
702        }
703
704        if (packagesToDelete != null) {
705            final int packageCount = packagesToDelete.size();
706            for (int i = 0; i < packageCount; i++) {
707                final String packageToDelete = packagesToDelete.get(i);
708                if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
709                        UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
710                                == PackageManager.DELETE_SUCCEEDED) {
711                    if (file.getUsableSpace() >= neededSpace) {
712                        return true;
713                    }
714                }
715            }
716        }
717
718        // Prune uninstalled instant apps
719        synchronized (mService.mPackages) {
720            // TODO: Track last used time for uninstalled instant apps for better pruning
721            for (int userId : UserManagerService.getInstance().getUserIds()) {
722                // Prune in-memory state
723                removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
724                    final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
725                    return (elapsedCachingMillis > maxUninstalledCacheDuration);
726                }, userId);
727
728                // Prune on-disk state
729                File instantAppsDir = getInstantApplicationsDir(userId);
730                if (!instantAppsDir.exists()) {
731                    continue;
732                }
733                File[] files = instantAppsDir.listFiles();
734                if (files == null) {
735                    continue;
736                }
737                for (File instantDir : files) {
738                    if (!instantDir.isDirectory()) {
739                        continue;
740                    }
741
742                    File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
743                    if (!metadataFile.exists()) {
744                        continue;
745                    }
746
747                    final long elapsedCachingMillis = System.currentTimeMillis()
748                            - metadataFile.lastModified();
749                    if (elapsedCachingMillis > maxUninstalledCacheDuration) {
750                        deleteDir(instantDir);
751                        if (file.getUsableSpace() >= neededSpace) {
752                            return true;
753                        }
754                    }
755                }
756            }
757        }
758
759        return false;
760    }
761
762    private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
763            @UserIdInt int userId) {
764        List<InstantAppInfo> result = null;
765
766        final int packageCount = mService.mPackages.size();
767        for (int i = 0; i < packageCount; i++) {
768            final PackageParser.Package pkg = mService.mPackages.valueAt(i);
769            final PackageSetting ps = (PackageSetting) pkg.mExtras;
770            if (ps == null || !ps.getInstantApp(userId)) {
771                continue;
772            }
773            final InstantAppInfo info = createInstantAppInfoForPackage(
774                    pkg, userId, true);
775            if (info == null) {
776                continue;
777            }
778            if (result == null) {
779                result = new ArrayList<>();
780            }
781            result.add(info);
782        }
783
784        return result;
785    }
786
787    private @NonNull
788    InstantAppInfo createInstantAppInfoForPackage(
789            @NonNull PackageParser.Package pkg, @UserIdInt int userId,
790            boolean addApplicationInfo) {
791        PackageSetting ps = (PackageSetting) pkg.mExtras;
792        if (ps == null) {
793            return null;
794        }
795        if (!ps.getInstalled(userId)) {
796            return null;
797        }
798
799        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
800        pkg.requestedPermissions.toArray(requestedPermissions);
801
802        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
803        String[] grantedPermissions = new String[permissions.size()];
804        permissions.toArray(grantedPermissions);
805
806        if (addApplicationInfo) {
807            return new InstantAppInfo(pkg.applicationInfo,
808                    requestedPermissions, grantedPermissions);
809        } else {
810            return new InstantAppInfo(pkg.applicationInfo.packageName,
811                    pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
812                    requestedPermissions, grantedPermissions);
813        }
814    }
815
816    private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
817            @UserIdInt int userId) {
818        List<UninstalledInstantAppState> uninstalledAppStates =
819                getUninstalledInstantAppStatesLPr(userId);
820        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
821            return null;
822        }
823
824        List<InstantAppInfo> uninstalledApps = null;
825        final int stateCount = uninstalledAppStates.size();
826        for (int i = 0; i < stateCount; i++) {
827            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
828            if (uninstalledApps == null) {
829                uninstalledApps = new ArrayList<>();
830            }
831            uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
832        }
833        return uninstalledApps;
834    }
835
836    private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
837            @UserIdInt int userId) {
838        InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
839                packageName, userId);
840        if (appInfo == null) {
841            return;
842        }
843        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
844            return;
845        }
846        final long identity = Binder.clearCallingIdentity();
847        try {
848            for (String grantedPermission : appInfo.getGrantedPermissions()) {
849                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
850                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
851                    mService.grantRuntimePermission(packageName, grantedPermission, userId);
852                }
853            }
854        } finally {
855            Binder.restoreCallingIdentity(identity);
856        }
857    }
858
859    private @NonNull
860    InstantAppInfo peekOrParseUninstalledInstantAppInfo(
861            @NonNull String packageName, @UserIdInt int userId) {
862        if (mUninstalledInstantApps != null) {
863            List<UninstalledInstantAppState> uninstalledAppStates =
864                    mUninstalledInstantApps.get(userId);
865            if (uninstalledAppStates != null) {
866                final int appCount = uninstalledAppStates.size();
867                for (int i = 0; i < appCount; i++) {
868                    UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
869                    if (uninstalledAppState.mInstantAppInfo
870                            .getPackageName().equals(packageName)) {
871                        return uninstalledAppState.mInstantAppInfo;
872                    }
873                }
874            }
875        }
876
877        File metadataFile = new File(getInstantApplicationDir(packageName, userId),
878                INSTANT_APP_METADATA_FILE);
879        UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
880        if (uninstalledAppState == null) {
881            return null;
882        }
883
884        return uninstalledAppState.mInstantAppInfo;
885    }
886
887    private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
888            @UserIdInt int userId) {
889        List<UninstalledInstantAppState> uninstalledAppStates = null;
890        if (mUninstalledInstantApps != null) {
891            uninstalledAppStates = mUninstalledInstantApps.get(userId);
892            if (uninstalledAppStates != null) {
893                return uninstalledAppStates;
894            }
895        }
896
897        File instantAppsDir = getInstantApplicationsDir(userId);
898        if (instantAppsDir.exists()) {
899            File[] files = instantAppsDir.listFiles();
900            if (files != null) {
901                for (File instantDir : files) {
902                    if (!instantDir.isDirectory()) {
903                        continue;
904                    }
905                    File metadataFile = new File(instantDir,
906                            INSTANT_APP_METADATA_FILE);
907                    UninstalledInstantAppState uninstalledAppState =
908                            parseMetadataFile(metadataFile);
909                    if (uninstalledAppState == null) {
910                        continue;
911                    }
912                    if (uninstalledAppStates == null) {
913                        uninstalledAppStates = new ArrayList<>();
914                    }
915                    uninstalledAppStates.add(uninstalledAppState);
916                }
917            }
918        }
919
920        if (uninstalledAppStates != null) {
921            if (mUninstalledInstantApps == null) {
922                mUninstalledInstantApps = new SparseArray<>();
923            }
924            mUninstalledInstantApps.put(userId, uninstalledAppStates);
925        }
926
927        return uninstalledAppStates;
928    }
929
930    private static @Nullable UninstalledInstantAppState parseMetadataFile(
931            @NonNull File metadataFile) {
932        if (!metadataFile.exists()) {
933            return null;
934        }
935        FileInputStream in;
936        try {
937            in = new AtomicFile(metadataFile).openRead();
938        } catch (FileNotFoundException fnfe) {
939            Slog.i(LOG_TAG, "No instant metadata file");
940            return null;
941        }
942
943        final File instantDir = metadataFile.getParentFile();
944        final long timestamp = metadataFile.lastModified();
945        final String packageName = instantDir.getName();
946
947        try {
948            XmlPullParser parser = Xml.newPullParser();
949            parser.setInput(in, StandardCharsets.UTF_8.name());
950            return new UninstalledInstantAppState(
951                    parseMetadata(parser, packageName), timestamp);
952        } catch (XmlPullParserException | IOException e) {
953            throw new IllegalStateException("Failed parsing instant"
954                    + " metadata file: " + metadataFile, e);
955        } finally {
956            IoUtils.closeQuietly(in);
957        }
958    }
959
960    private static @NonNull File computeInstantCookieFile(@NonNull String packageName,
961            @NonNull String sha256Digest, @UserIdInt int userId) {
962        final File appDir = getInstantApplicationDir(packageName, userId);
963        final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX
964                + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX;
965        return new File(appDir, cookieFile);
966    }
967
968    private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
969            @UserIdInt int userId) {
970        File appDir = getInstantApplicationDir(packageName, userId);
971        if (!appDir.exists()) {
972            return null;
973        }
974        File[] files = appDir.listFiles();
975        if (files == null) {
976            return null;
977        }
978        for (File file : files) {
979            if (!file.isDirectory()
980                    && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
981                    && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
982                return file;
983            }
984        }
985        return null;
986    }
987
988    private static @Nullable
989    InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
990                                 @NonNull String packageName)
991            throws IOException, XmlPullParserException {
992        final int outerDepth = parser.getDepth();
993        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
994            if (TAG_PACKAGE.equals(parser.getName())) {
995                return parsePackage(parser, packageName);
996            }
997        }
998        return null;
999    }
1000
1001    private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
1002                                               @NonNull String packageName)
1003            throws IOException, XmlPullParserException {
1004        String label = parser.getAttributeValue(null, ATTR_LABEL);
1005
1006        List<String> outRequestedPermissions = new ArrayList<>();
1007        List<String> outGrantedPermissions = new ArrayList<>();
1008
1009        final int outerDepth = parser.getDepth();
1010        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1011            if (TAG_PERMISSIONS.equals(parser.getName())) {
1012                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
1013            }
1014        }
1015
1016        String[] requestedPermissions = new String[outRequestedPermissions.size()];
1017        outRequestedPermissions.toArray(requestedPermissions);
1018
1019        String[] grantedPermissions = new String[outGrantedPermissions.size()];
1020        outGrantedPermissions.toArray(grantedPermissions);
1021
1022        return new InstantAppInfo(packageName, label,
1023                requestedPermissions, grantedPermissions);
1024    }
1025
1026    private static void parsePermissions(@NonNull XmlPullParser parser,
1027            @NonNull List<String> outRequestedPermissions,
1028            @NonNull List<String> outGrantedPermissions)
1029            throws IOException, XmlPullParserException {
1030        final int outerDepth = parser.getDepth();
1031        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
1032            if (TAG_PERMISSION.equals(parser.getName())) {
1033                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1034                outRequestedPermissions.add(permission);
1035                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
1036                    outGrantedPermissions.add(permission);
1037                }
1038            }
1039        }
1040    }
1041
1042    private void writeUninstalledInstantAppMetadata(
1043            @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
1044        File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
1045        if (!appDir.exists() && !appDir.mkdirs()) {
1046            return;
1047        }
1048
1049        File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
1050
1051        AtomicFile destination = new AtomicFile(metadataFile);
1052        FileOutputStream out = null;
1053        try {
1054            out = destination.startWrite();
1055
1056            XmlSerializer serializer = Xml.newSerializer();
1057            serializer.setOutput(out, StandardCharsets.UTF_8.name());
1058            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1059
1060            serializer.startDocument(null, true);
1061
1062            serializer.startTag(null, TAG_PACKAGE);
1063            serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
1064                    mService.mContext.getPackageManager()).toString());
1065
1066            serializer.startTag(null, TAG_PERMISSIONS);
1067            for (String permission : instantApp.getRequestedPermissions()) {
1068                serializer.startTag(null, TAG_PERMISSION);
1069                serializer.attribute(null, ATTR_NAME, permission);
1070                if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
1071                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
1072                }
1073                serializer.endTag(null, TAG_PERMISSION);
1074            }
1075            serializer.endTag(null, TAG_PERMISSIONS);
1076
1077            serializer.endTag(null, TAG_PACKAGE);
1078
1079            serializer.endDocument();
1080            destination.finishWrite(out);
1081        } catch (Throwable t) {
1082            Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
1083            destination.failWrite(out);
1084        } finally {
1085            IoUtils.closeQuietly(out);
1086        }
1087    }
1088
1089    private static @NonNull File getInstantApplicationsDir(int userId) {
1090        return new File(Environment.getUserSystemDirectory(userId),
1091                INSTANT_APPS_FOLDER);
1092    }
1093
1094    private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
1095        return new File (getInstantApplicationsDir(userId), packageName);
1096    }
1097
1098    private static void deleteDir(@NonNull File dir) {
1099        File[] files = dir.listFiles();
1100        if (files != null) {
1101            for (File file : files) {
1102                deleteDir(file);
1103            }
1104        }
1105        dir.delete();
1106    }
1107
1108    private static final class UninstalledInstantAppState {
1109        final InstantAppInfo mInstantAppInfo;
1110        final long mTimestamp;
1111
1112        public UninstalledInstantAppState(InstantAppInfo instantApp,
1113                long timestamp) {
1114            mInstantAppInfo = instantApp;
1115            mTimestamp = timestamp;
1116        }
1117    }
1118
1119    private final class CookiePersistence extends Handler {
1120        private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
1121
1122        // In case you wonder why we stash the cookies aside, we use
1123        // the user id for the message id and the package for the payload.
1124        // Handler allows removing messages by id and tag where the
1125        // tag is compared using ==. So to allow cancelling the
1126        // pending persistence for an app under a given user we use
1127        // the fact that package are cached by the system so the ==
1128        // comparison would match and we end up with a way to cancel
1129        // persisting the cookie for a user and package.
1130        private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
1131                = new SparseArray<>();
1132
1133        public CookiePersistence(Looper looper) {
1134            super(looper);
1135        }
1136
1137        public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1138                @NonNull byte[] cookie) {
1139            // Before we used only the first signature to compute the SHA 256 but some
1140            // apps could be singed by multiple certs and the cert order is undefined.
1141            // We prefer the modern computation procedure where all certs are taken
1142            // into account and delete the file derived via the legacy hash computation.
1143            File newCookieFile = computeInstantCookieFile(pkg.packageName,
1144                    PackageUtils.computeSignaturesSha256Digest(pkg.mSignatures), userId);
1145            if (pkg.mSignatures.length > 0) {
1146                File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
1147                if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
1148                    oldCookieFile.delete();
1149                }
1150            }
1151            cancelPendingPersistLPw(pkg, userId);
1152            addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
1153            sendMessageDelayed(obtainMessage(userId, pkg),
1154                    PERSIST_COOKIE_DELAY_MILLIS);
1155        }
1156
1157        public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1158                @UserIdInt int userId) {
1159            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1160                    mPendingPersistCookies.get(userId);
1161            if (pendingWorkForUser != null) {
1162                SomeArgs state = pendingWorkForUser.get(pkg);
1163                if (state != null) {
1164                    return (byte[]) state.arg1;
1165                }
1166            }
1167            return null;
1168        }
1169
1170        public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
1171                @UserIdInt int userId) {
1172            removeMessages(userId, pkg);
1173            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1174            if (state != null) {
1175                state.recycle();
1176            }
1177        }
1178
1179        private void addPendingPersistCookieLPw(@UserIdInt int userId,
1180                @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
1181                @NonNull File cookieFile) {
1182            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1183                    mPendingPersistCookies.get(userId);
1184            if (pendingWorkForUser == null) {
1185                pendingWorkForUser = new ArrayMap<>();
1186                mPendingPersistCookies.put(userId, pendingWorkForUser);
1187            }
1188            SomeArgs args = SomeArgs.obtain();
1189            args.arg1 = cookie;
1190            args.arg2 = cookieFile;
1191            pendingWorkForUser.put(pkg, args);
1192        }
1193
1194        private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1195                @UserIdInt int userId) {
1196            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1197                    mPendingPersistCookies.get(userId);
1198            SomeArgs state = null;
1199            if (pendingWorkForUser != null) {
1200                state = pendingWorkForUser.remove(pkg);
1201                if (pendingWorkForUser.isEmpty()) {
1202                    mPendingPersistCookies.remove(userId);
1203                }
1204            }
1205            return state;
1206        }
1207
1208        @Override
1209        public void handleMessage(Message message) {
1210            int userId = message.what;
1211            PackageParser.Package pkg = (PackageParser.Package) message.obj;
1212            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1213            if (state == null) {
1214                return;
1215            }
1216            byte[] cookie = (byte[]) state.arg1;
1217            File cookieFile = (File) state.arg2;
1218            state.recycle();
1219            persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
1220        }
1221    }
1222}
1223