blob: fde13acb8f38475739463fc742b5a7052a62f528 [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;
Dan Cashman1dbe6d02018-01-23 11:18:28 -080047
Svetoslav Ganov096d3042017-01-30 16:34:13 -080048import com.android.internal.annotations.GuardedBy;
49import com.android.internal.os.BackgroundThread;
Svet Ganovee2028c2017-02-17 17:23:29 -080050import com.android.internal.os.SomeArgs;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080051import com.android.internal.util.ArrayUtils;
52import com.android.internal.util.XmlUtils;
Todd Kennedy91a39d12017-09-27 12:37:04 -070053
Svetoslav Ganov096d3042017-01-30 16:34:13 -080054import libcore.io.IoUtils;
Dan Cashman1dbe6d02018-01-23 11:18:28 -080055
Svetoslav Ganov096d3042017-01-30 16:34:13 -080056import org.xmlpull.v1.XmlPullParser;
57import org.xmlpull.v1.XmlPullParserException;
58import org.xmlpull.v1.XmlSerializer;
59
60import java.io.File;
61import java.io.FileInputStream;
62import java.io.FileNotFoundException;
63import java.io.FileOutputStream;
64import java.io.IOException;
65import java.nio.charset.StandardCharsets;
Chad Brubaker0d277a72017-04-12 16:56:53 -070066import java.security.SecureRandom;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080067import java.util.ArrayList;
68import java.util.List;
Chad Brubaker0d277a72017-04-12 16:56:53 -070069import java.util.Locale;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080070import java.util.Set;
71import 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 */
80class InstantAppRegistry {
81 private static final boolean DEBUG = false;
82
83 private static final String LOG_TAG = "InstantAppRegistry";
84
Svet Ganovf36d53c2017-05-24 00:27:21 -070085 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 =
Svetoslav Ganov096d3042017-01-30 16:34:13 -080095 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";
Chad Brubaker0d277a72017-04-12 16:56:53 -0700102 private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800103
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
132 public InstantAppRegistry(PackageManagerService service) {
133 mService = service;
134 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
135 }
136
137 public byte[] getInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800138 @UserIdInt int userId) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800139 // 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);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800146 if (pendingCookie != null) {
147 return pendingCookie;
148 }
149 File cookieFile = peekInstantCookieFile(packageName, userId);
150 if (cookieFile != null && cookieFile.exists()) {
151 try {
152 return IoUtils.readFileAsByteArray(cookieFile.toString());
153 } catch (IOException e) {
154 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
155 }
156 }
157 return null;
158 }
159
160 public boolean setInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800161 @Nullable byte[] cookie, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800162 if (cookie != null && cookie.length > 0) {
163 final int maxCookieSize = mService.mContext.getPackageManager()
Svetoslav Ganov345ffa52017-04-18 16:08:41 -0700164 .getInstantAppCookieMaxBytes();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800165 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
Svet Ganov312c6cc2017-02-17 20:48:24 -0800172 // Only an installed package can set its own cookie
Svet Ganovee2028c2017-02-17 17:23:29 -0800173 PackageParser.Package pkg = mService.mPackages.get(packageName);
174 if (pkg == null) {
175 return false;
176 }
177
Svet Ganov312c6cc2017-02-17 20:48:24 -0800178 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800179 return true;
180 }
181
182 private void persistInstantApplicationCookie(@Nullable byte[] cookie,
Svet Ganovee2028c2017-02-17 17:23:29 -0800183 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800184 synchronized (mService.mPackages) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800185 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
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800191 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 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800199 }
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);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800204 }
205 }
206
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
Chad Brubaker0d277a72017-04-12 16:56:53 -0700217 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
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);
Chad Brubaker64c8b792017-04-28 11:02:26 -0700236 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 }
Chad Brubaker0d277a72017-04-12 16:56:53 -0700241 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
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800252 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
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
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800277 propagateInstantAppPermissionsIfNeeded(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800278
279 // Track instant apps
Todd Kennedybe0b8892017-02-15 14:13:52 -0800280 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800281 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 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700299
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800300 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
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700305 // 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.
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800310 if (pkg.mSigningDetails.checkCapability(currentCookieSha256,
311 PackageParser.SigningDetails.CertCapabilities.INSTALLED_DATA)) {
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700312 return;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800313 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700314
Dan Cashman928b7032018-04-16 14:18:48 -0700315 // For backwards compatibility we accept match based on any signature, since we may have
316 // recorded only the first for multiply-signed packages
Dan Cashman1dbe6d02018-01-23 11:18:28 -0800317 final String[] signaturesSha256Digests =
318 PackageUtils.computeSignaturesSha256Digests(pkg.mSigningDetails.signatures);
Dan Cashman928b7032018-04-16 14:18:48 -0700319 for (String s : signaturesSha256Digests) {
320 if (s.equals(currentCookieSha256)) {
321 return;
322 }
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700323 }
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();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800331 }
332 }
333
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
Todd Kennedybe0b8892017-02-15 14:13:52 -0800346 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800347 // 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));
Svetoslav Ganov8aa379e2017-04-04 18:12:27 -0700353 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800354 removeAppLPw(userId, ps.appId);
355 }
356 }
357 }
358
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
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
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
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
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
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
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
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);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800520 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800521 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
Svet Ganovf935a702017-08-22 12:15:58 -0700534 boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
535 return hasUninstalledInstantAppStateLPr(packageName, userId)
536 || hasInstantAppMetadataLPr(packageName, userId);
537 }
538
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800539 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();
Chad Brubaker0d277a72017-04-12 16:56:53 -0700548 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800549 File cookie = peekInstantCookieFile(packageName, userId);
550 if (cookie != null) {
551 cookie.delete();
552 }
553 }
554
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();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800566 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800567 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
Svet Ganovf935a702017-08-22 12:15:58 -0700582 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
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
Svet Ganovf36d53c2017-05-24 00:27:21 -0700609 void pruneInstantApps() {
610 final long maxInstalledCacheDuration = Settings.Global.getLong(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800611 mService.mContext.getContentResolver(),
Svet Ganovf36d53c2017-05-24 00:27:21 -0700612 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
613 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800614
Svet Ganovf36d53c2017-05-24 00:27:21 -0700615 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);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800619
Svet Ganovf36d53c2017-05-24 00:27:21 -0700620 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
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
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 */
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) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800681 continue;
682 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700683 if (!(pkg.mExtras instanceof PackageSetting)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800684 continue;
685 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700686 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 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800705
Svet Ganovf36d53c2017-05-24 00:27:21 -0700706 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 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800752 }
753 }
754 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700755
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;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800798 }
799
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++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800806 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
807 final PackageSetting ps = (PackageSetting) pkg.mExtras;
808 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800809 continue;
810 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800811 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800812 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
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
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
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800874 private void propagateInstantAppPermissionsIfNeeded(@NonNull PackageParser.Package pkg,
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800875 @UserIdInt int userId) {
876 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800877 pkg.packageName, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800878 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()) {
Todd Kennedy0eb97382017-10-03 16:57:22 -0700887 final boolean propagatePermission =
888 mService.mSettings.canPropagatePermissionToInstantApp(grantedPermission);
Todd Kennedy1a3216b2018-02-08 14:52:57 -0800889 if (propagatePermission && pkg.requestedPermissions.contains(grantedPermission)) {
890 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800891 }
892 }
893 } finally {
894 Binder.restoreCallingIdentity(identity);
895 }
896 }
897
898 private @NonNull
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
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
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
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -0700999 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;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001004 return new File(appDir, cookieFile);
1005 }
1006
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
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
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
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
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
1128 private static @NonNull File getInstantApplicationsDir(int userId) {
1129 return new File(Environment.getUserSystemDirectory(userId),
1130 INSTANT_APPS_FOLDER);
1131 }
1132
1133 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
Svet Ganovf935a702017-08-22 12:15:58 -07001134 return new File(getInstantApplicationsDir(userId), packageName);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001135 }
1136
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
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
Svet Ganovee2028c2017-02-17 17:23:29 -08001164 // tag is compared using ==. So to allow cancelling the
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001165 // pending persistence for an app under a given user we use
Svet Ganov312c6cc2017-02-17 20:48:24 -08001166 // 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<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001171
1172 public CookiePersistence(Looper looper) {
1173 super(looper);
1174 }
1175
Svet Ganov312c6cc2017-02-17 20:48:24 -08001176 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1177 @NonNull byte[] cookie) {
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001178 // 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,
Patrick Baumann420d58a2017-12-19 10:17:21 -08001183 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();
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001190 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001191 cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganovcdd685c2017-08-22 14:35:04 -07001192 addPendingPersistCookieLPw(userId, pkg, cookie, newCookieFile);
Svet Ganov312c6cc2017-02-17 20:48:24 -08001193 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001194 PERSIST_COOKIE_DELAY_MILLIS);
1195 }
1196
Svet Ganov312c6cc2017-02-17 20:48:24 -08001197 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1198 @UserIdInt int userId) {
1199 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1200 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001201 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001202 SomeArgs state = pendingWorkForUser.get(pkg);
1203 if (state != null) {
1204 return (byte[]) state.arg1;
1205 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001206 }
1207 return null;
1208 }
1209
Svet Ganov312c6cc2017-02-17 20:48:24 -08001210 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 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001217 }
1218
Svet Ganov312c6cc2017-02-17 20:48:24 -08001219 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);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001224 if (pendingWorkForUser == null) {
1225 pendingWorkForUser = new ArrayMap<>();
1226 mPendingPersistCookies.put(userId, pendingWorkForUser);
1227 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001228 SomeArgs args = SomeArgs.obtain();
1229 args.arg1 = cookie;
1230 args.arg2 = cookieFile;
1231 pendingWorkForUser.put(pkg, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001232 }
1233
Svet Ganov312c6cc2017-02-17 20:48:24 -08001234 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;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001239 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001240 state = pendingWorkForUser.remove(pkg);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001241 if (pendingWorkForUser.isEmpty()) {
1242 mPendingPersistCookies.remove(userId);
1243 }
1244 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001245 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001246 }
1247
1248 @Override
1249 public void handleMessage(Message message) {
1250 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -08001251 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);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001260 }
1261 }
1262}