InstantAppRegistry.java revision 928b70303c08afda899b23c186b66d0a644bb74c
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 any signature, since we may have
316            // recorded only the first for multiply-signed packages
317            final String[] signaturesSha256Digests =
318                    PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures);
319            for (String s : signaturesSha256Digests) {
320                if (s.equals(currentCookieSha256)) {
321                    return;
322                }
323            }
324
325            // Sorry, you are out of luck - different signatures - nuke data
326            Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
327                    + " changed - dropping cookie");
328                // Make sure a pending write for the old signed app is cancelled
329            mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
330            currentCookieFile.delete();
331        }
332    }
333
334    public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
335            @NonNull int[] userIds) {
336        PackageSetting ps = (PackageSetting) pkg.mExtras;
337        if (ps == null) {
338            return;
339        }
340
341        for (int userId : userIds) {
342            if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
343                continue;
344            }
345
346            if (ps.getInstantApp(userId)) {
347                // Add a record for an uninstalled instant app
348                addUninstalledInstantAppLPw(pkg, userId);
349                removeInstantAppLPw(userId, ps.appId);
350            } else {
351                // Deleting an app prunes all instant state such as cookie
352                deleteDir(getInstantApplicationDir(pkg.packageName, userId));
353                mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
354                removeAppLPw(userId, ps.appId);
355            }
356        }
357    }
358
359    public void onUserRemovedLPw(int userId) {
360        if (mUninstalledInstantApps != null) {
361            mUninstalledInstantApps.remove(userId);
362            if (mUninstalledInstantApps.size() <= 0) {
363                mUninstalledInstantApps = null;
364            }
365        }
366        if (mInstalledInstantAppUids != null) {
367            mInstalledInstantAppUids.remove(userId);
368            if (mInstalledInstantAppUids.size() <= 0) {
369                mInstalledInstantAppUids = null;
370            }
371        }
372        if (mInstantGrants != null) {
373            mInstantGrants.remove(userId);
374            if (mInstantGrants.size() <= 0) {
375                mInstantGrants = null;
376            }
377        }
378        deleteDir(getInstantApplicationsDir(userId));
379    }
380
381    public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
382            int instantAppId) {
383        if (mInstantGrants == null) {
384            return false;
385        }
386        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
387        if (targetAppList == null) {
388            return false;
389        }
390        final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
391        if (instantGrantList == null) {
392            return false;
393        }
394        return instantGrantList.get(instantAppId);
395    }
396
397    public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
398            int targetAppId, int instantAppId) {
399        if (mInstalledInstantAppUids == null) {
400            return;     // no instant apps installed; no need to grant
401        }
402        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
403        if (instantAppList == null || !instantAppList.get(instantAppId)) {
404            return;     // instant app id isn't installed; no need to grant
405        }
406        if (instantAppList.get(targetAppId)) {
407            return;     // target app id is an instant app; no need to grant
408        }
409        if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
410            final Set<String> categories = intent.getCategories();
411            if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
412                return;  // launched via VIEW/BROWSABLE intent; no need to grant
413            }
414        }
415        if (mInstantGrants == null) {
416            mInstantGrants = new SparseArray<>();
417        }
418        SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
419        if (targetAppList == null) {
420            targetAppList = new SparseArray<>();
421            mInstantGrants.put(userId, targetAppList);
422        }
423        SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
424        if (instantGrantList == null) {
425            instantGrantList = new SparseBooleanArray();
426            targetAppList.put(targetAppId, instantGrantList);
427        }
428        instantGrantList.put(instantAppId, true /*granted*/);
429    }
430
431    public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
432        if (mInstalledInstantAppUids == null) {
433            mInstalledInstantAppUids = new SparseArray<>();
434        }
435        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
436        if (instantAppList == null) {
437            instantAppList = new SparseBooleanArray();
438            mInstalledInstantAppUids.put(userId, instantAppList);
439        }
440        instantAppList.put(instantAppId, true /*installed*/);
441    }
442
443    private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
444        // remove from the installed list
445        if (mInstalledInstantAppUids == null) {
446            return; // no instant apps on the system
447        }
448        final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
449        if (instantAppList == null) {
450            return;
451        }
452
453        instantAppList.delete(instantAppId);
454
455        // remove any grants
456        if (mInstantGrants == null) {
457            return; // no grants on the system
458        }
459        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
460        if (targetAppList == null) {
461            return; // no grants for this user
462        }
463        for (int i = targetAppList.size() - 1; i >= 0; --i) {
464            targetAppList.valueAt(i).delete(instantAppId);
465        }
466    }
467
468    private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
469        // remove from the installed list
470        if (mInstantGrants == null) {
471            return; // no grants on the system
472        }
473        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
474        if (targetAppList == null) {
475            return; // no grants for this user
476        }
477        targetAppList.delete(targetAppId);
478    }
479
480    private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
481            @UserIdInt int userId) {
482        InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
483                pkg, userId, false);
484        if (uninstalledApp == null) {
485            return;
486        }
487        if (mUninstalledInstantApps == null) {
488            mUninstalledInstantApps = new SparseArray<>();
489        }
490        List<UninstalledInstantAppState> uninstalledAppStates =
491                mUninstalledInstantApps.get(userId);
492        if (uninstalledAppStates == null) {
493            uninstalledAppStates = new ArrayList<>();
494            mUninstalledInstantApps.put(userId, uninstalledAppStates);
495        }
496        UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
497                uninstalledApp, System.currentTimeMillis());
498        uninstalledAppStates.add(uninstalledAppState);
499
500        writeUninstalledInstantAppMetadata(uninstalledApp, userId);
501        writeInstantApplicationIconLPw(pkg, userId);
502    }
503
504    private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
505            @UserIdInt int userId) {
506        File appDir = getInstantApplicationDir(pkg.packageName, userId);
507        if (!appDir.exists()) {
508            return;
509        }
510
511        Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
512
513        final Bitmap bitmap;
514        if (icon instanceof BitmapDrawable) {
515            bitmap = ((BitmapDrawable) icon).getBitmap();
516        } else  {
517            bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
518                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
519            Canvas canvas = new Canvas(bitmap);
520            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
521            icon.draw(canvas);
522        }
523
524        File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
525                INSTANT_APP_ICON_FILE);
526
527        try (FileOutputStream out = new FileOutputStream(iconFile)) {
528            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
529        } catch (Exception e) {
530            Slog.e(LOG_TAG, "Error writing instant app icon", e);
531        }
532    }
533
534    boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
535        return hasUninstalledInstantAppStateLPr(packageName, userId)
536                || hasInstantAppMetadataLPr(packageName, userId);
537    }
538
539    public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
540            @UserIdInt int userId) {
541        removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
542                state.mInstantAppInfo.getPackageName().equals(packageName),
543                userId);
544
545        File instantAppDir = getInstantApplicationDir(packageName, userId);
546        new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
547        new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
548        new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
549        File cookie = peekInstantCookieFile(packageName, userId);
550        if (cookie != null) {
551            cookie.delete();
552        }
553    }
554
555    private void removeUninstalledInstantAppStateLPw(
556            @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
557        if (mUninstalledInstantApps == null) {
558            return;
559        }
560        List<UninstalledInstantAppState> uninstalledAppStates =
561                mUninstalledInstantApps.get(userId);
562        if (uninstalledAppStates == null) {
563            return;
564        }
565        final int appCount = uninstalledAppStates.size();
566        for (int i = appCount - 1; i >= 0; --i) {
567            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
568            if (!criteria.test(uninstalledAppState)) {
569                continue;
570            }
571            uninstalledAppStates.remove(i);
572            if (uninstalledAppStates.isEmpty()) {
573                mUninstalledInstantApps.remove(userId);
574                if (mUninstalledInstantApps.size() <= 0) {
575                    mUninstalledInstantApps = null;
576                }
577                return;
578            }
579        }
580    }
581
582    private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
583        if (mUninstalledInstantApps == null) {
584            return false;
585        }
586        final List<UninstalledInstantAppState> uninstalledAppStates =
587                mUninstalledInstantApps.get(userId);
588        if (uninstalledAppStates == null) {
589            return false;
590        }
591        final int appCount = uninstalledAppStates.size();
592        for (int i = 0; i < appCount; i++) {
593            final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
594            if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
595                return true;
596            }
597        }
598        return false;
599    }
600
601    private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
602        final File instantAppDir = getInstantApplicationDir(packageName, userId);
603        return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
604                || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
605                || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists()
606                || peekInstantCookieFile(packageName, userId) != null;
607    }
608
609    void pruneInstantApps() {
610        final long maxInstalledCacheDuration = Settings.Global.getLong(
611                mService.mContext.getContentResolver(),
612                Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
613                DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
614
615        final long maxUninstalledCacheDuration = Settings.Global.getLong(
616                mService.mContext.getContentResolver(),
617                Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
618                DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
619
620        try {
621            pruneInstantApps(Long.MAX_VALUE,
622                    maxInstalledCacheDuration, maxUninstalledCacheDuration);
623        } catch (IOException e) {
624            Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
625        }
626    }
627
628    boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
629        try {
630            return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
631        } catch (IOException e) {
632            Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
633            return false;
634        }
635    }
636
637    boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
638        try {
639            return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
640        } catch (IOException e) {
641            Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
642            return false;
643        }
644    }
645
646    /**
647     * Prunes instant apps until there is enough <code>neededSpace</code>. Both
648     * installed and uninstalled instant apps are pruned that are older than
649     * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code>
650     * respectively. All times are in milliseconds.
651     *
652     * @param neededSpace The space to ensure is free.
653     * @param maxInstalledCacheDuration The max duration for caching installed apps in millis.
654     * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis.
655     * @return Whether enough space was freed.
656     *
657     * @throws IOException
658     */
659    private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
660            long maxUninstalledCacheDuration) throws IOException {
661        final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
662        final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
663
664        if (file.getUsableSpace() >= neededSpace) {
665            return true;
666        }
667
668        List<String> packagesToDelete = null;
669
670        final int[] allUsers;
671        final long now = System.currentTimeMillis();
672
673        // Prune first installed instant apps
674        synchronized (mService.mPackages) {
675            allUsers = PackageManagerService.sUserManager.getUserIds();
676
677            final int packageCount = mService.mPackages.size();
678            for (int i = 0; i < packageCount; i++) {
679                final PackageParser.Package pkg = mService.mPackages.valueAt(i);
680                if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) {
681                    continue;
682                }
683                if (!(pkg.mExtras instanceof PackageSetting)) {
684                    continue;
685                }
686                final PackageSetting  ps = (PackageSetting) pkg.mExtras;
687                boolean installedOnlyAsInstantApp = false;
688                for (int userId : allUsers) {
689                    if (ps.getInstalled(userId)) {
690                        if (ps.getInstantApp(userId)) {
691                            installedOnlyAsInstantApp = true;
692                        } else {
693                            installedOnlyAsInstantApp = false;
694                            break;
695                        }
696                    }
697                }
698                if (installedOnlyAsInstantApp) {
699                    if (packagesToDelete == null) {
700                        packagesToDelete = new ArrayList<>();
701                    }
702                    packagesToDelete.add(pkg.packageName);
703                }
704            }
705
706            if (packagesToDelete != null) {
707                packagesToDelete.sort((String lhs, String rhs) -> {
708                    final PackageParser.Package lhsPkg = mService.mPackages.get(lhs);
709                    final PackageParser.Package rhsPkg = mService.mPackages.get(rhs);
710                    if (lhsPkg == null && rhsPkg == null) {
711                        return 0;
712                    } else if (lhsPkg == null) {
713                        return -1;
714                    } else if (rhsPkg == null) {
715                        return 1;
716                    } else {
717                        if (lhsPkg.getLatestPackageUseTimeInMills() >
718                                rhsPkg.getLatestPackageUseTimeInMills()) {
719                            return 1;
720                        } else if (lhsPkg.getLatestPackageUseTimeInMills() <
721                                rhsPkg.getLatestPackageUseTimeInMills()) {
722                            return -1;
723                        } else {
724                            if (lhsPkg.mExtras instanceof PackageSetting
725                                    && rhsPkg.mExtras instanceof PackageSetting) {
726                                final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras;
727                                final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras;
728                                if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) {
729                                    return 1;
730                                } else {
731                                    return -1;
732                                }
733                            } else {
734                                return 0;
735                            }
736                        }
737                    }
738                });
739            }
740        }
741
742        if (packagesToDelete != null) {
743            final int packageCount = packagesToDelete.size();
744            for (int i = 0; i < packageCount; i++) {
745                final String packageToDelete = packagesToDelete.get(i);
746                if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
747                        UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
748                                == PackageManager.DELETE_SUCCEEDED) {
749                    if (file.getUsableSpace() >= neededSpace) {
750                        return true;
751                    }
752                }
753            }
754        }
755
756        // Prune uninstalled instant apps
757        synchronized (mService.mPackages) {
758            // TODO: Track last used time for uninstalled instant apps for better pruning
759            for (int userId : UserManagerService.getInstance().getUserIds()) {
760                // Prune in-memory state
761                removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
762                    final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
763                    return (elapsedCachingMillis > maxUninstalledCacheDuration);
764                }, userId);
765
766                // Prune on-disk state
767                File instantAppsDir = getInstantApplicationsDir(userId);
768                if (!instantAppsDir.exists()) {
769                    continue;
770                }
771                File[] files = instantAppsDir.listFiles();
772                if (files == null) {
773                    continue;
774                }
775                for (File instantDir : files) {
776                    if (!instantDir.isDirectory()) {
777                        continue;
778                    }
779
780                    File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
781                    if (!metadataFile.exists()) {
782                        continue;
783                    }
784
785                    final long elapsedCachingMillis = System.currentTimeMillis()
786                            - metadataFile.lastModified();
787                    if (elapsedCachingMillis > maxUninstalledCacheDuration) {
788                        deleteDir(instantDir);
789                        if (file.getUsableSpace() >= neededSpace) {
790                            return true;
791                        }
792                    }
793                }
794            }
795        }
796
797        return false;
798    }
799
800    private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
801            @UserIdInt int userId) {
802        List<InstantAppInfo> result = null;
803
804        final int packageCount = mService.mPackages.size();
805        for (int i = 0; i < packageCount; i++) {
806            final PackageParser.Package pkg = mService.mPackages.valueAt(i);
807            final PackageSetting ps = (PackageSetting) pkg.mExtras;
808            if (ps == null || !ps.getInstantApp(userId)) {
809                continue;
810            }
811            final InstantAppInfo info = createInstantAppInfoForPackage(
812                    pkg, userId, true);
813            if (info == null) {
814                continue;
815            }
816            if (result == null) {
817                result = new ArrayList<>();
818            }
819            result.add(info);
820        }
821
822        return result;
823    }
824
825    private @NonNull
826    InstantAppInfo createInstantAppInfoForPackage(
827            @NonNull PackageParser.Package pkg, @UserIdInt int userId,
828            boolean addApplicationInfo) {
829        PackageSetting ps = (PackageSetting) pkg.mExtras;
830        if (ps == null) {
831            return null;
832        }
833        if (!ps.getInstalled(userId)) {
834            return null;
835        }
836
837        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
838        pkg.requestedPermissions.toArray(requestedPermissions);
839
840        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
841        String[] grantedPermissions = new String[permissions.size()];
842        permissions.toArray(grantedPermissions);
843
844        if (addApplicationInfo) {
845            return new InstantAppInfo(pkg.applicationInfo,
846                    requestedPermissions, grantedPermissions);
847        } else {
848            return new InstantAppInfo(pkg.applicationInfo.packageName,
849                    pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
850                    requestedPermissions, grantedPermissions);
851        }
852    }
853
854    private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
855            @UserIdInt int userId) {
856        List<UninstalledInstantAppState> uninstalledAppStates =
857                getUninstalledInstantAppStatesLPr(userId);
858        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
859            return null;
860        }
861
862        List<InstantAppInfo> uninstalledApps = null;
863        final int stateCount = uninstalledAppStates.size();
864        for (int i = 0; i < stateCount; i++) {
865            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
866            if (uninstalledApps == null) {
867                uninstalledApps = new ArrayList<>();
868            }
869            uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
870        }
871        return uninstalledApps;
872    }
873
874    private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg,
875            @UserIdInt int userId) {
876        InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
877                pkg.packageName, userId);
878        if (appInfo == null) {
879            return;
880        }
881        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
882            return;
883        }
884        final long identity = Binder.clearCallingIdentity();
885        try {
886            for (String grantedPermission : appInfo.getGrantedPermissions()) {
887                final boolean propagatePermission =
888                        mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
889                if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) {
890                    mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
891                }
892            }
893        } finally {
894            Binder.restoreCallingIdentity(identity);
895        }
896    }
897
898    private @NonNull
899    InstantAppInfo peekOrParseUninstalledInstantAppInfo(
900            @NonNull String packageName, @UserIdInt int userId) {
901        if (mUninstalledInstantApps != null) {
902            List<UninstalledInstantAppState> uninstalledAppStates =
903                    mUninstalledInstantApps.get(userId);
904            if (uninstalledAppStates != null) {
905                final int appCount = uninstalledAppStates.size();
906                for (int i = 0; i < appCount; i++) {
907                    UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
908                    if (uninstalledAppState.mInstantAppInfo
909                            .getPackageName().equals(packageName)) {
910                        return uninstalledAppState.mInstantAppInfo;
911                    }
912                }
913            }
914        }
915
916        File metadataFile = new File(getInstantApplicationDir(packageName, userId),
917                INSTANT_APP_METADATA_FILE);
918        UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
919        if (uninstalledAppState == null) {
920            return null;
921        }
922
923        return uninstalledAppState.mInstantAppInfo;
924    }
925
926    private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
927            @UserIdInt int userId) {
928        List<UninstalledInstantAppState> uninstalledAppStates = null;
929        if (mUninstalledInstantApps != null) {
930            uninstalledAppStates = mUninstalledInstantApps.get(userId);
931            if (uninstalledAppStates != null) {
932                return uninstalledAppStates;
933            }
934        }
935
936        File instantAppsDir = getInstantApplicationsDir(userId);
937        if (instantAppsDir.exists()) {
938            File[] files = instantAppsDir.listFiles();
939            if (files != null) {
940                for (File instantDir : files) {
941                    if (!instantDir.isDirectory()) {
942                        continue;
943                    }
944                    File metadataFile = new File(instantDir,
945                            INSTANT_APP_METADATA_FILE);
946                    UninstalledInstantAppState uninstalledAppState =
947                            parseMetadataFile(metadataFile);
948                    if (uninstalledAppState == null) {
949                        continue;
950                    }
951                    if (uninstalledAppStates == null) {
952                        uninstalledAppStates = new ArrayList<>();
953                    }
954                    uninstalledAppStates.add(uninstalledAppState);
955                }
956            }
957        }
958
959        if (uninstalledAppStates != null) {
960            if (mUninstalledInstantApps == null) {
961                mUninstalledInstantApps = new SparseArray<>();
962            }
963            mUninstalledInstantApps.put(userId, uninstalledAppStates);
964        }
965
966        return uninstalledAppStates;
967    }
968
969    private static @Nullable UninstalledInstantAppState parseMetadataFile(
970            @NonNull File metadataFile) {
971        if (!metadataFile.exists()) {
972            return null;
973        }
974        FileInputStream in;
975        try {
976            in = new AtomicFile(metadataFile).openRead();
977        } catch (FileNotFoundException fnfe) {
978            Slog.i(LOG_TAG, "No instant metadata file");
979            return null;
980        }
981
982        final File instantDir = metadataFile.getParentFile();
983        final long timestamp = metadataFile.lastModified();
984        final String packageName = instantDir.getName();
985
986        try {
987            XmlPullParser parser = Xml.newPullParser();
988            parser.setInput(in, StandardCharsets.UTF_8.name());
989            return new UninstalledInstantAppState(
990                    parseMetadata(parser, packageName), timestamp);
991        } catch (XmlPullParserException | IOException e) {
992            throw new IllegalStateException("Failed parsing instant"
993                    + " metadata file: " + metadataFile, e);
994        } finally {
995            IoUtils.closeQuietly(in);
996        }
997    }
998
999    private static @NonNull File computeInstantCookieFile(@NonNull String packageName,
1000            @NonNull String sha256Digest, @UserIdInt int userId) {
1001        final File appDir = getInstantApplicationDir(packageName, userId);
1002        final String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX
1003                + sha256Digest + INSTANT_APP_COOKIE_FILE_SIFFIX;
1004        return new File(appDir, cookieFile);
1005    }
1006
1007    private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
1008            @UserIdInt int userId) {
1009        File appDir = getInstantApplicationDir(packageName, userId);
1010        if (!appDir.exists()) {
1011            return null;
1012        }
1013        File[] files = appDir.listFiles();
1014        if (files == null) {
1015            return null;
1016        }
1017        for (File file : files) {
1018            if (!file.isDirectory()
1019                    && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
1020                    && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
1021                return file;
1022            }
1023        }
1024        return null;
1025    }
1026
1027    private static @Nullable
1028    InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
1029                                 @NonNull String packageName)
1030            throws IOException, XmlPullParserException {
1031        final int outerDepth = parser.getDepth();
1032        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1033            if (TAG_PACKAGE.equals(parser.getName())) {
1034                return parsePackage(parser, packageName);
1035            }
1036        }
1037        return null;
1038    }
1039
1040    private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
1041                                               @NonNull String packageName)
1042            throws IOException, XmlPullParserException {
1043        String label = parser.getAttributeValue(null, ATTR_LABEL);
1044
1045        List<String> outRequestedPermissions = new ArrayList<>();
1046        List<String> outGrantedPermissions = new ArrayList<>();
1047
1048        final int outerDepth = parser.getDepth();
1049        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1050            if (TAG_PERMISSIONS.equals(parser.getName())) {
1051                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
1052            }
1053        }
1054
1055        String[] requestedPermissions = new String[outRequestedPermissions.size()];
1056        outRequestedPermissions.toArray(requestedPermissions);
1057
1058        String[] grantedPermissions = new String[outGrantedPermissions.size()];
1059        outGrantedPermissions.toArray(grantedPermissions);
1060
1061        return new InstantAppInfo(packageName, label,
1062                requestedPermissions, grantedPermissions);
1063    }
1064
1065    private static void parsePermissions(@NonNull XmlPullParser parser,
1066            @NonNull List<String> outRequestedPermissions,
1067            @NonNull List<String> outGrantedPermissions)
1068            throws IOException, XmlPullParserException {
1069        final int outerDepth = parser.getDepth();
1070        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
1071            if (TAG_PERMISSION.equals(parser.getName())) {
1072                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1073                outRequestedPermissions.add(permission);
1074                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
1075                    outGrantedPermissions.add(permission);
1076                }
1077            }
1078        }
1079    }
1080
1081    private void writeUninstalledInstantAppMetadata(
1082            @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
1083        File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
1084        if (!appDir.exists() && !appDir.mkdirs()) {
1085            return;
1086        }
1087
1088        File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
1089
1090        AtomicFile destination = new AtomicFile(metadataFile);
1091        FileOutputStream out = null;
1092        try {
1093            out = destination.startWrite();
1094
1095            XmlSerializer serializer = Xml.newSerializer();
1096            serializer.setOutput(out, StandardCharsets.UTF_8.name());
1097            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1098
1099            serializer.startDocument(null, true);
1100
1101            serializer.startTag(null, TAG_PACKAGE);
1102            serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
1103                    mService.mContext.getPackageManager()).toString());
1104
1105            serializer.startTag(null, TAG_PERMISSIONS);
1106            for (String permission : instantApp.getRequestedPermissions()) {
1107                serializer.startTag(null, TAG_PERMISSION);
1108                serializer.attribute(null, ATTR_NAME, permission);
1109                if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
1110                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
1111                }
1112                serializer.endTag(null, TAG_PERMISSION);
1113            }
1114            serializer.endTag(null, TAG_PERMISSIONS);
1115
1116            serializer.endTag(null, TAG_PACKAGE);
1117
1118            serializer.endDocument();
1119            destination.finishWrite(out);
1120        } catch (Throwable t) {
1121            Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
1122            destination.failWrite(out);
1123        } finally {
1124            IoUtils.closeQuietly(out);
1125        }
1126    }
1127
1128    private static @NonNull File getInstantApplicationsDir(int userId) {
1129        return new File(Environment.getUserSystemDirectory(userId),
1130                INSTANT_APPS_FOLDER);
1131    }
1132
1133    private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
1134        return new File(getInstantApplicationsDir(userId), packageName);
1135    }
1136
1137    private static void deleteDir(@NonNull File dir) {
1138        File[] files = dir.listFiles();
1139        if (files != null) {
1140            for (File file : files) {
1141                deleteDir(file);
1142            }
1143        }
1144        dir.delete();
1145    }
1146
1147    private static final class UninstalledInstantAppState {
1148        final InstantAppInfo mInstantAppInfo;
1149        final long mTimestamp;
1150
1151        public UninstalledInstantAppState(InstantAppInfo instantApp,
1152                long timestamp) {
1153            mInstantAppInfo = instantApp;
1154            mTimestamp = timestamp;
1155        }
1156    }
1157
1158    private final class CookiePersistence extends Handler {
1159        private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
1160
1161        // In case you wonder why we stash the cookies aside, we use
1162        // the user id for the message id and the package for the payload.
1163        // Handler allows removing messages by id and tag where the
1164        // tag is compared using ==. So to allow cancelling the
1165        // pending persistence for an app under a given user we use
1166        // the fact that package are cached by the system so the ==
1167        // comparison would match and we end up with a way to cancel
1168        // persisting the cookie for a user and package.
1169        private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
1170                = new SparseArray<>();
1171
1172        public CookiePersistence(Looper looper) {
1173            super(looper);
1174        }
1175
1176        public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1177                @NonNull byte[] cookie) {
1178            // Before we used only the first signature to compute the SHA 256 but some
1179            // apps could be singed by multiple certs and the cert order is undefined.
1180            // We prefer the modern computation procedure where all certs are taken
1181            // into account and delete the file derived via the legacy hash computation.
1182            File newCookieFile = computeInstantCookieFile(pkg.packageName,
1183                    PackageUtils.computeSignaturesSha256Digest(pkg.mSigningDetails.signatures), userId);
1184            if (!pkg.mSigningDetails.hasSignatures()) {
1185                Slog.wtf(LOG_TAG, "Parsed Instant App contains no valid signatures!");
1186            }
1187            File oldCookieFile = peekInstantCookieFile(pkg.packageName, userId);
1188            if (oldCookieFile != null && !newCookieFile.equals(oldCookieFile)) {
1189                oldCookieFile.delete();
1190            }
1191            cancelPendingPersistLPw(pkg, userId);
1192            addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
1193            sendMessageDelayed(obtainMessage(userId, pkg),
1194                    PERSIST_COOKIE_DELAY_MILLIS);
1195        }
1196
1197        public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1198                @UserIdInt int userId) {
1199            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1200                    mPendingPersistCookies.get(userId);
1201            if (pendingWorkForUser != null) {
1202                SomeArgs state = pendingWorkForUser.get(pkg);
1203                if (state != null) {
1204                    return (byte[]) state.arg1;
1205                }
1206            }
1207            return null;
1208        }
1209
1210        public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
1211                @UserIdInt int userId) {
1212            removeMessages(userId, pkg);
1213            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1214            if (state != null) {
1215                state.recycle();
1216            }
1217        }
1218
1219        private void addPendingPersistCookieLPw(@UserIdInt int userId,
1220                @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
1221                @NonNull File cookieFile) {
1222            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1223                    mPendingPersistCookies.get(userId);
1224            if (pendingWorkForUser == null) {
1225                pendingWorkForUser = new ArrayMap<>();
1226                mPendingPersistCookies.put(userId, pendingWorkForUser);
1227            }
1228            SomeArgs args = SomeArgs.obtain();
1229            args.arg1 = cookie;
1230            args.arg2 = cookieFile;
1231            pendingWorkForUser.put(pkg, args);
1232        }
1233
1234        private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1235                @UserIdInt int userId) {
1236            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1237                    mPendingPersistCookies.get(userId);
1238            SomeArgs state = null;
1239            if (pendingWorkForUser != null) {
1240                state = pendingWorkForUser.remove(pkg);
1241                if (pendingWorkForUser.isEmpty()) {
1242                    mPendingPersistCookies.remove(userId);
1243                }
1244            }
1245            return state;
1246        }
1247
1248        @Override
1249        public void handleMessage(Message message) {
1250            int userId = message.what;
1251            PackageParser.Package pkg = (PackageParser.Package) message.obj;
1252            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1253            if (state == null) {
1254                return;
1255            }
1256            byte[] cookie = (byte[]) state.arg1;
1257            File cookieFile = (File) state.arg2;
1258            state.recycle();
1259            persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
1260        }
1261    }
1262}
1263