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