Home | History | Annotate | Download | only in pm
      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 
     17 package com.android.server.pm;
     18 
     19 import android.annotation.NonNull;
     20 import android.annotation.Nullable;
     21 import android.annotation.UserIdInt;
     22 import android.content.Intent;
     23 import android.content.pm.InstantAppInfo;
     24 import android.content.pm.PackageManager;
     25 import android.content.pm.PackageParser;
     26 import android.graphics.Bitmap;
     27 import android.graphics.BitmapFactory;
     28 import android.graphics.Canvas;
     29 import android.graphics.drawable.BitmapDrawable;
     30 import android.graphics.drawable.Drawable;
     31 import android.os.Binder;
     32 import android.os.Environment;
     33 import android.os.Handler;
     34 import android.os.Looper;
     35 import android.os.Message;
     36 import android.os.UserHandle;
     37 import android.os.storage.StorageManager;
     38 import android.provider.Settings;
     39 import android.util.ArrayMap;
     40 import android.util.AtomicFile;
     41 import android.util.ByteStringUtils;
     42 import android.util.PackageUtils;
     43 import android.util.Slog;
     44 import android.util.SparseArray;
     45 import android.util.SparseBooleanArray;
     46 import android.util.Xml;
     47 
     48 import com.android.internal.annotations.GuardedBy;
     49 import com.android.internal.os.BackgroundThread;
     50 import com.android.internal.os.SomeArgs;
     51 import com.android.internal.util.ArrayUtils;
     52 import com.android.internal.util.XmlUtils;
     53 
     54 import libcore.io.IoUtils;
     55 
     56 import org.xmlpull.v1.XmlPullParser;
     57 import org.xmlpull.v1.XmlPullParserException;
     58 import org.xmlpull.v1.XmlSerializer;
     59 
     60 import java.io.File;
     61 import java.io.FileInputStream;
     62 import java.io.FileNotFoundException;
     63 import java.io.FileOutputStream;
     64 import java.io.IOException;
     65 import java.nio.charset.StandardCharsets;
     66 import java.security.SecureRandom;
     67 import java.util.ArrayList;
     68 import java.util.List;
     69 import java.util.Locale;
     70 import java.util.Set;
     71 import 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  */
     80 class 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