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