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