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