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