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