blob: 211a1c9f3edf9180cfcfcc2a249c433e4384122a [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 }
296 File expectedCookeFile = computeInstantCookieFile(pkg, userId);
297 if (!currentCookieFile.equals(expectedCookeFile)) {
298 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
299 + " changed - dropping cookie");
Svet Ganov312c6cc2017-02-17 20:48:24 -0800300 // Make sure a pending write for the old signed app is cancelled
301 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800302 currentCookieFile.delete();
303 }
304 }
305 }
306
307 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
308 @NonNull int[] userIds) {
309 PackageSetting ps = (PackageSetting) pkg.mExtras;
310 if (ps == null) {
311 return;
312 }
313
314 for (int userId : userIds) {
315 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
316 continue;
317 }
318
Todd Kennedybe0b8892017-02-15 14:13:52 -0800319 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800320 // Add a record for an uninstalled instant app
321 addUninstalledInstantAppLPw(pkg, userId);
322 removeInstantAppLPw(userId, ps.appId);
323 } else {
324 // Deleting an app prunes all instant state such as cookie
325 deleteDir(getInstantApplicationDir(pkg.packageName, userId));
Svetoslav Ganov8aa379e2017-04-04 18:12:27 -0700326 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800327 removeAppLPw(userId, ps.appId);
328 }
329 }
330 }
331
332 public void onUserRemovedLPw(int userId) {
333 if (mUninstalledInstantApps != null) {
334 mUninstalledInstantApps.remove(userId);
335 if (mUninstalledInstantApps.size() <= 0) {
336 mUninstalledInstantApps = null;
337 }
338 }
339 if (mInstalledInstantAppUids != null) {
340 mInstalledInstantAppUids.remove(userId);
341 if (mInstalledInstantAppUids.size() <= 0) {
342 mInstalledInstantAppUids = null;
343 }
344 }
345 if (mInstantGrants != null) {
346 mInstantGrants.remove(userId);
347 if (mInstantGrants.size() <= 0) {
348 mInstantGrants = null;
349 }
350 }
351 deleteDir(getInstantApplicationsDir(userId));
352 }
353
354 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
355 int instantAppId) {
356 if (mInstantGrants == null) {
357 return false;
358 }
359 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
360 if (targetAppList == null) {
361 return false;
362 }
363 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
364 if (instantGrantList == null) {
365 return false;
366 }
367 return instantGrantList.get(instantAppId);
368 }
369
370 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
371 int targetAppId, int instantAppId) {
372 if (mInstalledInstantAppUids == null) {
373 return; // no instant apps installed; no need to grant
374 }
375 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
376 if (instantAppList == null || !instantAppList.get(instantAppId)) {
377 return; // instant app id isn't installed; no need to grant
378 }
379 if (instantAppList.get(targetAppId)) {
380 return; // target app id is an instant app; no need to grant
381 }
382 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
383 final Set<String> categories = intent.getCategories();
384 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
385 return; // launched via VIEW/BROWSABLE intent; no need to grant
386 }
387 }
388 if (mInstantGrants == null) {
389 mInstantGrants = new SparseArray<>();
390 }
391 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
392 if (targetAppList == null) {
393 targetAppList = new SparseArray<>();
394 mInstantGrants.put(userId, targetAppList);
395 }
396 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
397 if (instantGrantList == null) {
398 instantGrantList = new SparseBooleanArray();
399 targetAppList.put(targetAppId, instantGrantList);
400 }
401 instantGrantList.put(instantAppId, true /*granted*/);
402 }
403
404 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
405 if (mInstalledInstantAppUids == null) {
406 mInstalledInstantAppUids = new SparseArray<>();
407 }
408 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
409 if (instantAppList == null) {
410 instantAppList = new SparseBooleanArray();
411 mInstalledInstantAppUids.put(userId, instantAppList);
412 }
413 instantAppList.put(instantAppId, true /*installed*/);
414 }
415
416 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
417 // remove from the installed list
418 if (mInstalledInstantAppUids == null) {
419 return; // no instant apps on the system
420 }
421 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
422 if (instantAppList == null) {
423 return;
424 }
425
426 instantAppList.delete(instantAppId);
427
428 // remove any grants
429 if (mInstantGrants == null) {
430 return; // no grants on the system
431 }
432 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
433 if (targetAppList == null) {
434 return; // no grants for this user
435 }
436 for (int i = targetAppList.size() - 1; i >= 0; --i) {
437 targetAppList.valueAt(i).delete(instantAppId);
438 }
439 }
440
441 private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
442 // remove from the installed list
443 if (mInstantGrants == null) {
444 return; // no grants on the system
445 }
446 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
447 if (targetAppList == null) {
448 return; // no grants for this user
449 }
450 targetAppList.delete(targetAppId);
451 }
452
453 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
454 @UserIdInt int userId) {
455 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
456 pkg, userId, false);
457 if (uninstalledApp == null) {
458 return;
459 }
460 if (mUninstalledInstantApps == null) {
461 mUninstalledInstantApps = new SparseArray<>();
462 }
463 List<UninstalledInstantAppState> uninstalledAppStates =
464 mUninstalledInstantApps.get(userId);
465 if (uninstalledAppStates == null) {
466 uninstalledAppStates = new ArrayList<>();
467 mUninstalledInstantApps.put(userId, uninstalledAppStates);
468 }
469 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
470 uninstalledApp, System.currentTimeMillis());
471 uninstalledAppStates.add(uninstalledAppState);
472
473 writeUninstalledInstantAppMetadata(uninstalledApp, userId);
474 writeInstantApplicationIconLPw(pkg, userId);
475 }
476
477 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
478 @UserIdInt int userId) {
479 File appDir = getInstantApplicationDir(pkg.packageName, userId);
480 if (!appDir.exists()) {
481 return;
482 }
483
484 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
485
486 final Bitmap bitmap;
487 if (icon instanceof BitmapDrawable) {
488 bitmap = ((BitmapDrawable) icon).getBitmap();
489 } else {
490 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
491 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
492 Canvas canvas = new Canvas(bitmap);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800493 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800494 icon.draw(canvas);
495 }
496
497 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
498 INSTANT_APP_ICON_FILE);
499
500 try (FileOutputStream out = new FileOutputStream(iconFile)) {
501 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
502 } catch (Exception e) {
503 Slog.e(LOG_TAG, "Error writing instant app icon", e);
504 }
505 }
506
507 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
508 @UserIdInt int userId) {
509 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
510 state.mInstantAppInfo.getPackageName().equals(packageName),
511 userId);
512
513 File instantAppDir = getInstantApplicationDir(packageName, userId);
514 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
515 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
Chad Brubaker0d277a72017-04-12 16:56:53 -0700516 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800517 File cookie = peekInstantCookieFile(packageName, userId);
518 if (cookie != null) {
519 cookie.delete();
520 }
521 }
522
523 private void removeUninstalledInstantAppStateLPw(
524 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
525 if (mUninstalledInstantApps == null) {
526 return;
527 }
528 List<UninstalledInstantAppState> uninstalledAppStates =
529 mUninstalledInstantApps.get(userId);
530 if (uninstalledAppStates == null) {
531 return;
532 }
533 final int appCount = uninstalledAppStates.size();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800534 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800535 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
536 if (!criteria.test(uninstalledAppState)) {
537 continue;
538 }
539 uninstalledAppStates.remove(i);
540 if (uninstalledAppStates.isEmpty()) {
541 mUninstalledInstantApps.remove(userId);
542 if (mUninstalledInstantApps.size() <= 0) {
543 mUninstalledInstantApps = null;
544 }
545 return;
546 }
547 }
548 }
549
Svet Ganovf36d53c2017-05-24 00:27:21 -0700550 void pruneInstantApps() {
551 final long maxInstalledCacheDuration = Settings.Global.getLong(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800552 mService.mContext.getContentResolver(),
Svet Ganovf36d53c2017-05-24 00:27:21 -0700553 Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
554 DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800555
Svet Ganovf36d53c2017-05-24 00:27:21 -0700556 final long maxUninstalledCacheDuration = Settings.Global.getLong(
557 mService.mContext.getContentResolver(),
558 Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
559 DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800560
Svet Ganovf36d53c2017-05-24 00:27:21 -0700561 try {
562 pruneInstantApps(Long.MAX_VALUE,
563 maxInstalledCacheDuration, maxUninstalledCacheDuration);
564 } catch (IOException e) {
565 Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
566 }
567 }
568
569 boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
570 try {
571 return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
572 } catch (IOException e) {
573 Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
574 return false;
575 }
576 }
577
578 boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
579 try {
580 return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
581 } catch (IOException e) {
582 Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
583 return false;
584 }
585 }
586
587 /**
588 * Prunes instant apps until there is enough <code>neededSpace</code>. Both
589 * installed and uninstalled instant apps are pruned that are older than
590 * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code>
591 * respectively. All times are in milliseconds.
592 *
593 * @param neededSpace The space to ensure is free.
594 * @param maxInstalledCacheDuration The max duration for caching installed apps in millis.
595 * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis.
596 * @return Whether enough space was freed.
597 *
598 * @throws IOException
599 */
600 private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
601 long maxUninstalledCacheDuration) throws IOException {
602 final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
603 final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
604
605 if (file.getUsableSpace() >= neededSpace) {
606 return true;
607 }
608
609 List<String> packagesToDelete = null;
610
611 final int[] allUsers;
612 final long now = System.currentTimeMillis();
613
614 // Prune first installed instant apps
615 synchronized (mService.mPackages) {
616 allUsers = PackageManagerService.sUserManager.getUserIds();
617
618 final int packageCount = mService.mPackages.size();
619 for (int i = 0; i < packageCount; i++) {
620 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
621 if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800622 continue;
623 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700624 if (!(pkg.mExtras instanceof PackageSetting)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800625 continue;
626 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700627 final PackageSetting ps = (PackageSetting) pkg.mExtras;
628 boolean installedOnlyAsInstantApp = false;
629 for (int userId : allUsers) {
630 if (ps.getInstalled(userId)) {
631 if (ps.getInstantApp(userId)) {
632 installedOnlyAsInstantApp = true;
633 } else {
634 installedOnlyAsInstantApp = false;
635 break;
636 }
637 }
638 }
639 if (installedOnlyAsInstantApp) {
640 if (packagesToDelete == null) {
641 packagesToDelete = new ArrayList<>();
642 }
643 packagesToDelete.add(pkg.packageName);
644 }
645 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800646
Svet Ganovf36d53c2017-05-24 00:27:21 -0700647 if (packagesToDelete != null) {
648 packagesToDelete.sort((String lhs, String rhs) -> {
649 final PackageParser.Package lhsPkg = mService.mPackages.get(lhs);
650 final PackageParser.Package rhsPkg = mService.mPackages.get(rhs);
651 if (lhsPkg == null && rhsPkg == null) {
652 return 0;
653 } else if (lhsPkg == null) {
654 return -1;
655 } else if (rhsPkg == null) {
656 return 1;
657 } else {
658 if (lhsPkg.getLatestPackageUseTimeInMills() >
659 rhsPkg.getLatestPackageUseTimeInMills()) {
660 return 1;
661 } else if (lhsPkg.getLatestPackageUseTimeInMills() <
662 rhsPkg.getLatestPackageUseTimeInMills()) {
663 return -1;
664 } else {
665 if (lhsPkg.mExtras instanceof PackageSetting
666 && rhsPkg.mExtras instanceof PackageSetting) {
667 final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras;
668 final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras;
669 if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) {
670 return 1;
671 } else {
672 return -1;
673 }
674 } else {
675 return 0;
676 }
677 }
678 }
679 });
680 }
681 }
682
683 if (packagesToDelete != null) {
684 final int packageCount = packagesToDelete.size();
685 for (int i = 0; i < packageCount; i++) {
686 final String packageToDelete = packagesToDelete.get(i);
687 if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
688 UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
689 == PackageManager.DELETE_SUCCEEDED) {
690 if (file.getUsableSpace() >= neededSpace) {
691 return true;
692 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800693 }
694 }
695 }
Svet Ganovf36d53c2017-05-24 00:27:21 -0700696
697 // Prune uninstalled instant apps
698 synchronized (mService.mPackages) {
699 // TODO: Track last used time for uninstalled instant apps for better pruning
700 for (int userId : UserManagerService.getInstance().getUserIds()) {
701 // Prune in-memory state
702 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
703 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
704 return (elapsedCachingMillis > maxUninstalledCacheDuration);
705 }, userId);
706
707 // Prune on-disk state
708 File instantAppsDir = getInstantApplicationsDir(userId);
709 if (!instantAppsDir.exists()) {
710 continue;
711 }
712 File[] files = instantAppsDir.listFiles();
713 if (files == null) {
714 continue;
715 }
716 for (File instantDir : files) {
717 if (!instantDir.isDirectory()) {
718 continue;
719 }
720
721 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
722 if (!metadataFile.exists()) {
723 continue;
724 }
725
726 final long elapsedCachingMillis = System.currentTimeMillis()
727 - metadataFile.lastModified();
728 if (elapsedCachingMillis > maxUninstalledCacheDuration) {
729 deleteDir(instantDir);
730 if (file.getUsableSpace() >= neededSpace) {
731 return true;
732 }
733 }
734 }
735 }
736 }
737
738 return false;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800739 }
740
741 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
742 @UserIdInt int userId) {
743 List<InstantAppInfo> result = null;
744
745 final int packageCount = mService.mPackages.size();
746 for (int i = 0; i < packageCount; i++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800747 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
748 final PackageSetting ps = (PackageSetting) pkg.mExtras;
749 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800750 continue;
751 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800752 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800753 pkg, userId, true);
754 if (info == null) {
755 continue;
756 }
757 if (result == null) {
758 result = new ArrayList<>();
759 }
760 result.add(info);
761 }
762
763 return result;
764 }
765
766 private @NonNull
767 InstantAppInfo createInstantAppInfoForPackage(
768 @NonNull PackageParser.Package pkg, @UserIdInt int userId,
769 boolean addApplicationInfo) {
770 PackageSetting ps = (PackageSetting) pkg.mExtras;
771 if (ps == null) {
772 return null;
773 }
774 if (!ps.getInstalled(userId)) {
775 return null;
776 }
777
778 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
779 pkg.requestedPermissions.toArray(requestedPermissions);
780
781 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
782 String[] grantedPermissions = new String[permissions.size()];
783 permissions.toArray(grantedPermissions);
784
785 if (addApplicationInfo) {
786 return new InstantAppInfo(pkg.applicationInfo,
787 requestedPermissions, grantedPermissions);
788 } else {
789 return new InstantAppInfo(pkg.applicationInfo.packageName,
790 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
791 requestedPermissions, grantedPermissions);
792 }
793 }
794
795 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
796 @UserIdInt int userId) {
797 List<UninstalledInstantAppState> uninstalledAppStates =
798 getUninstalledInstantAppStatesLPr(userId);
799 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
800 return null;
801 }
802
803 List<InstantAppInfo> uninstalledApps = null;
804 final int stateCount = uninstalledAppStates.size();
805 for (int i = 0; i < stateCount; i++) {
806 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
807 if (uninstalledApps == null) {
808 uninstalledApps = new ArrayList<>();
809 }
810 uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
811 }
812 return uninstalledApps;
813 }
814
815 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
816 @UserIdInt int userId) {
817 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
818 packageName, userId);
819 if (appInfo == null) {
820 return;
821 }
822 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
823 return;
824 }
825 final long identity = Binder.clearCallingIdentity();
826 try {
827 for (String grantedPermission : appInfo.getGrantedPermissions()) {
828 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
829 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
830 mService.grantRuntimePermission(packageName, grantedPermission, userId);
831 }
832 }
833 } finally {
834 Binder.restoreCallingIdentity(identity);
835 }
836 }
837
838 private @NonNull
839 InstantAppInfo peekOrParseUninstalledInstantAppInfo(
840 @NonNull String packageName, @UserIdInt int userId) {
841 if (mUninstalledInstantApps != null) {
842 List<UninstalledInstantAppState> uninstalledAppStates =
843 mUninstalledInstantApps.get(userId);
844 if (uninstalledAppStates != null) {
845 final int appCount = uninstalledAppStates.size();
846 for (int i = 0; i < appCount; i++) {
847 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
848 if (uninstalledAppState.mInstantAppInfo
849 .getPackageName().equals(packageName)) {
850 return uninstalledAppState.mInstantAppInfo;
851 }
852 }
853 }
854 }
855
856 File metadataFile = new File(getInstantApplicationDir(packageName, userId),
857 INSTANT_APP_METADATA_FILE);
858 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
859 if (uninstalledAppState == null) {
860 return null;
861 }
862
863 return uninstalledAppState.mInstantAppInfo;
864 }
865
866 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
867 @UserIdInt int userId) {
868 List<UninstalledInstantAppState> uninstalledAppStates = null;
869 if (mUninstalledInstantApps != null) {
870 uninstalledAppStates = mUninstalledInstantApps.get(userId);
871 if (uninstalledAppStates != null) {
872 return uninstalledAppStates;
873 }
874 }
875
876 File instantAppsDir = getInstantApplicationsDir(userId);
877 if (instantAppsDir.exists()) {
878 File[] files = instantAppsDir.listFiles();
879 if (files != null) {
880 for (File instantDir : files) {
881 if (!instantDir.isDirectory()) {
882 continue;
883 }
884 File metadataFile = new File(instantDir,
885 INSTANT_APP_METADATA_FILE);
886 UninstalledInstantAppState uninstalledAppState =
887 parseMetadataFile(metadataFile);
888 if (uninstalledAppState == null) {
889 continue;
890 }
891 if (uninstalledAppStates == null) {
892 uninstalledAppStates = new ArrayList<>();
893 }
894 uninstalledAppStates.add(uninstalledAppState);
895 }
896 }
897 }
898
899 if (uninstalledAppStates != null) {
900 if (mUninstalledInstantApps == null) {
901 mUninstalledInstantApps = new SparseArray<>();
902 }
903 mUninstalledInstantApps.put(userId, uninstalledAppStates);
904 }
905
906 return uninstalledAppStates;
907 }
908
909 private static @Nullable UninstalledInstantAppState parseMetadataFile(
910 @NonNull File metadataFile) {
911 if (!metadataFile.exists()) {
912 return null;
913 }
914 FileInputStream in;
915 try {
916 in = new AtomicFile(metadataFile).openRead();
917 } catch (FileNotFoundException fnfe) {
918 Slog.i(LOG_TAG, "No instant metadata file");
919 return null;
920 }
921
922 final File instantDir = metadataFile.getParentFile();
923 final long timestamp = metadataFile.lastModified();
924 final String packageName = instantDir.getName();
925
926 try {
927 XmlPullParser parser = Xml.newPullParser();
928 parser.setInput(in, StandardCharsets.UTF_8.name());
929 return new UninstalledInstantAppState(
930 parseMetadata(parser, packageName), timestamp);
931 } catch (XmlPullParserException | IOException e) {
932 throw new IllegalStateException("Failed parsing instant"
933 + " metadata file: " + metadataFile, e);
934 } finally {
935 IoUtils.closeQuietly(in);
936 }
937 }
938
939 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
940 @UserIdInt int userId) {
941 File appDir = getInstantApplicationDir(pkg.packageName, userId);
942 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
943 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
944 return new File(appDir, cookieFile);
945 }
946
947 private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
948 @UserIdInt int userId) {
949 File appDir = getInstantApplicationDir(packageName, userId);
950 if (!appDir.exists()) {
951 return null;
952 }
953 File[] files = appDir.listFiles();
954 if (files == null) {
955 return null;
956 }
957 for (File file : files) {
958 if (!file.isDirectory()
959 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
960 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
961 return file;
962 }
963 }
964 return null;
965 }
966
967 private static @Nullable
968 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
969 @NonNull String packageName)
970 throws IOException, XmlPullParserException {
971 final int outerDepth = parser.getDepth();
972 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
973 if (TAG_PACKAGE.equals(parser.getName())) {
974 return parsePackage(parser, packageName);
975 }
976 }
977 return null;
978 }
979
980 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
981 @NonNull String packageName)
982 throws IOException, XmlPullParserException {
983 String label = parser.getAttributeValue(null, ATTR_LABEL);
984
985 List<String> outRequestedPermissions = new ArrayList<>();
986 List<String> outGrantedPermissions = new ArrayList<>();
987
988 final int outerDepth = parser.getDepth();
989 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
990 if (TAG_PERMISSIONS.equals(parser.getName())) {
991 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
992 }
993 }
994
995 String[] requestedPermissions = new String[outRequestedPermissions.size()];
996 outRequestedPermissions.toArray(requestedPermissions);
997
998 String[] grantedPermissions = new String[outGrantedPermissions.size()];
999 outGrantedPermissions.toArray(grantedPermissions);
1000
1001 return new InstantAppInfo(packageName, label,
1002 requestedPermissions, grantedPermissions);
1003 }
1004
1005 private static void parsePermissions(@NonNull XmlPullParser parser,
1006 @NonNull List<String> outRequestedPermissions,
1007 @NonNull List<String> outGrantedPermissions)
1008 throws IOException, XmlPullParserException {
1009 final int outerDepth = parser.getDepth();
1010 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
1011 if (TAG_PERMISSION.equals(parser.getName())) {
1012 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1013 outRequestedPermissions.add(permission);
1014 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
1015 outGrantedPermissions.add(permission);
1016 }
1017 }
1018 }
1019 }
1020
1021 private void writeUninstalledInstantAppMetadata(
1022 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
1023 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
1024 if (!appDir.exists() && !appDir.mkdirs()) {
1025 return;
1026 }
1027
1028 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
1029
1030 AtomicFile destination = new AtomicFile(metadataFile);
1031 FileOutputStream out = null;
1032 try {
1033 out = destination.startWrite();
1034
1035 XmlSerializer serializer = Xml.newSerializer();
1036 serializer.setOutput(out, StandardCharsets.UTF_8.name());
1037 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1038
1039 serializer.startDocument(null, true);
1040
1041 serializer.startTag(null, TAG_PACKAGE);
1042 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
1043 mService.mContext.getPackageManager()).toString());
1044
1045 serializer.startTag(null, TAG_PERMISSIONS);
1046 for (String permission : instantApp.getRequestedPermissions()) {
1047 serializer.startTag(null, TAG_PERMISSION);
1048 serializer.attribute(null, ATTR_NAME, permission);
1049 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
1050 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
1051 }
1052 serializer.endTag(null, TAG_PERMISSION);
1053 }
1054 serializer.endTag(null, TAG_PERMISSIONS);
1055
1056 serializer.endTag(null, TAG_PACKAGE);
1057
1058 serializer.endDocument();
1059 destination.finishWrite(out);
1060 } catch (Throwable t) {
1061 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
1062 destination.failWrite(out);
1063 } finally {
1064 IoUtils.closeQuietly(out);
1065 }
1066 }
1067
1068 private static @NonNull File getInstantApplicationsDir(int userId) {
1069 return new File(Environment.getUserSystemDirectory(userId),
1070 INSTANT_APPS_FOLDER);
1071 }
1072
1073 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
1074 return new File (getInstantApplicationsDir(userId), packageName);
1075 }
1076
1077 private static void deleteDir(@NonNull File dir) {
1078 File[] files = dir.listFiles();
1079 if (files != null) {
1080 for (File file : files) {
1081 deleteDir(file);
1082 }
1083 }
1084 dir.delete();
1085 }
1086
1087 private static final class UninstalledInstantAppState {
1088 final InstantAppInfo mInstantAppInfo;
1089 final long mTimestamp;
1090
1091 public UninstalledInstantAppState(InstantAppInfo instantApp,
1092 long timestamp) {
1093 mInstantAppInfo = instantApp;
1094 mTimestamp = timestamp;
1095 }
1096 }
1097
1098 private final class CookiePersistence extends Handler {
1099 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
1100
1101 // In case you wonder why we stash the cookies aside, we use
1102 // the user id for the message id and the package for the payload.
1103 // Handler allows removing messages by id and tag where the
Svet Ganovee2028c2017-02-17 17:23:29 -08001104 // tag is compared using ==. So to allow cancelling the
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001105 // pending persistence for an app under a given user we use
Svet Ganov312c6cc2017-02-17 20:48:24 -08001106 // the fact that package are cached by the system so the ==
1107 // comparison would match and we end up with a way to cancel
1108 // persisting the cookie for a user and package.
1109 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
1110 = new SparseArray<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001111
1112 public CookiePersistence(Looper looper) {
1113 super(looper);
1114 }
1115
Svet Ganov312c6cc2017-02-17 20:48:24 -08001116 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1117 @NonNull byte[] cookie) {
1118 File cookieFile = computeInstantCookieFile(pkg, userId);
1119 cancelPendingPersistLPw(pkg, userId);
1120 addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
1121 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001122 PERSIST_COOKIE_DELAY_MILLIS);
1123 }
1124
Svet Ganov312c6cc2017-02-17 20:48:24 -08001125 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1126 @UserIdInt int userId) {
1127 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1128 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001129 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001130 SomeArgs state = pendingWorkForUser.get(pkg);
1131 if (state != null) {
1132 return (byte[]) state.arg1;
1133 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001134 }
1135 return null;
1136 }
1137
Svet Ganov312c6cc2017-02-17 20:48:24 -08001138 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
1139 @UserIdInt int userId) {
1140 removeMessages(userId, pkg);
1141 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1142 if (state != null) {
1143 state.recycle();
1144 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001145 }
1146
Svet Ganov312c6cc2017-02-17 20:48:24 -08001147 private void addPendingPersistCookieLPw(@UserIdInt int userId,
1148 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
1149 @NonNull File cookieFile) {
1150 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1151 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001152 if (pendingWorkForUser == null) {
1153 pendingWorkForUser = new ArrayMap<>();
1154 mPendingPersistCookies.put(userId, pendingWorkForUser);
1155 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001156 SomeArgs args = SomeArgs.obtain();
1157 args.arg1 = cookie;
1158 args.arg2 = cookieFile;
1159 pendingWorkForUser.put(pkg, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001160 }
1161
Svet Ganov312c6cc2017-02-17 20:48:24 -08001162 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1163 @UserIdInt int userId) {
1164 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1165 mPendingPersistCookies.get(userId);
1166 SomeArgs state = null;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001167 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001168 state = pendingWorkForUser.remove(pkg);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001169 if (pendingWorkForUser.isEmpty()) {
1170 mPendingPersistCookies.remove(userId);
1171 }
1172 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001173 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001174 }
1175
1176 @Override
1177 public void handleMessage(Message message) {
1178 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -08001179 PackageParser.Package pkg = (PackageParser.Package) message.obj;
1180 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1181 if (state == null) {
1182 return;
1183 }
1184 byte[] cookie = (byte[]) state.arg1;
1185 File cookieFile = (File) state.arg2;
1186 state.recycle();
1187 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001188 }
1189 }
1190}