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