• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 
InstantAppRegistry(PackageManagerService service)129     public InstantAppRegistry(PackageManagerService service) {
130         mService = service;
131         mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
132     }
133 
getInstantAppCookieLPw(@onNull String packageName, @UserIdInt int userId)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 
setInstantAppCookieLPw(@onNull String packageName, @Nullable byte[] cookie, @UserIdInt int userId)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 
persistInstantApplicationCookie(@ullable byte[] cookie, @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId)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 
getInstantAppIconLPw(@onNull String packageName, @UserIdInt int userId)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 
getInstantAppAndroidIdLPw(@onNull String packageName, @UserIdInt int userId)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 
generateInstantAppAndroidIdLPw(@onNull String packageName, @UserIdInt int userId)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 
getInstantAppsLPr(@serIdInt int userId)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 
onPackageInstalledLPw(@onNull PackageParser.Package pkg, @NonNull int[] userIds)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 
onPackageUninstalledLPw(@onNull PackageParser.Package pkg, @NonNull int[] userIds)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 
onUserRemovedLPw(int userId)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 
isInstantAccessGranted(@serIdInt int userId, int targetAppId, int instantAppId)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 
grantInstantAccessLPw(@serIdInt int userId, @Nullable Intent intent, int targetAppId, int instantAppId)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 
addInstantAppLPw(@serIdInt int userId, int instantAppId)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 
removeInstantAppLPw(@serIdInt int userId, int instantAppId)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 
removeAppLPw(@serIdInt int userId, int targetAppId)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 
addUninstalledInstantAppLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)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 
writeInstantApplicationIconLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)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 
hasInstantApplicationMetadataLPr(String packageName, int userId)528     boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
529         return hasUninstalledInstantAppStateLPr(packageName, userId)
530                 || hasInstantAppMetadataLPr(packageName, userId);
531     }
532 
deleteInstantApplicationMetadataLPw(@onNull String packageName, @UserIdInt int userId)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 
removeUninstalledInstantAppStateLPw( @onNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId)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 
hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId)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 
hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId)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 
pruneInstantApps()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 
pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration)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 
pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration)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      */
pruneInstantApps(long neededSpace, long maxInstalledCacheDuration, long maxUninstalledCacheDuration)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 
getInstalledInstantApplicationsLPr( @serIdInt int userId)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
createInstantAppInfoForPackage( @onNull PackageParser.Package pkg, @UserIdInt int userId, boolean addApplicationInfo)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 
getUninstalledInstantApplicationsLPr( @serIdInt int userId)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 
propagateInstantAppPermissionsIfNeeded(@onNull String packageName, @UserIdInt int userId)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
peekOrParseUninstalledInstantAppInfo( @onNull String packageName, @UserIdInt int userId)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 
getUninstalledInstantAppStatesLPr( @serIdInt int userId)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 
parseMetadataFile( @onNull File metadataFile)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 
computeInstantCookieFile(@onNull String packageName, @NonNull String sha256Digest, @UserIdInt int userId)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 
peekInstantCookieFile(@onNull String packageName, @UserIdInt int userId)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
parseMetadata(@onNull XmlPullParser parser, @NonNull String packageName)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 
parsePackage(@onNull XmlPullParser parser, @NonNull String packageName)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 
parsePermissions(@onNull XmlPullParser parser, @NonNull List<String> outRequestedPermissions, @NonNull List<String> outGrantedPermissions)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 
writeUninstalledInstantAppMetadata( @onNull InstantAppInfo instantApp, @UserIdInt int userId)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 
getInstantApplicationsDir(int userId)1121     private static @NonNull File getInstantApplicationsDir(int userId) {
1122         return new File(Environment.getUserSystemDirectory(userId),
1123                 INSTANT_APPS_FOLDER);
1124     }
1125 
getInstantApplicationDir(String packageName, int userId)1126     private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
1127         return new File(getInstantApplicationsDir(userId), packageName);
1128     }
1129 
deleteDir(@onNull File dir)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 
UninstalledInstantAppState(InstantAppInfo instantApp, long timestamp)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 
CookiePersistence(Looper looper)1165         public CookiePersistence(Looper looper) {
1166             super(looper);
1167         }
1168 
schedulePersistLPw(@serIdInt int userId, @NonNull PackageParser.Package pkg, @NonNull byte[] cookie)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 
getPendingPersistCookieLPr(@onNull PackageParser.Package pkg, @UserIdInt int userId)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 
cancelPendingPersistLPw(@onNull PackageParser.Package pkg, @UserIdInt int userId)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 
addPendingPersistCookieLPw(@serIdInt int userId, @NonNull PackageParser.Package pkg, @NonNull byte[] cookie, @NonNull File cookieFile)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 
removePendingPersistCookieLPr(@onNull PackageParser.Package pkg, @UserIdInt int userId)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
handleMessage(Message message)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