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