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