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