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