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