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