blob: b1659841d5acbecf9cf4e841fbe91758bfd6c7c3 [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;
24import android.content.pm.PackageParser;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.Canvas;
28import android.graphics.drawable.BitmapDrawable;
29import android.graphics.drawable.Drawable;
30import android.os.Binder;
31import android.os.Environment;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.provider.Settings;
36import android.util.ArrayMap;
37import android.util.AtomicFile;
Chad Brubaker0d277a72017-04-12 16:56:53 -070038import android.util.ByteStringUtils;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080039import android.util.PackageUtils;
40import android.util.Slog;
41import android.util.SparseArray;
42import android.util.SparseBooleanArray;
43import android.util.Xml;
44import com.android.internal.annotations.GuardedBy;
45import com.android.internal.os.BackgroundThread;
Svet Ganovee2028c2017-02-17 17:23:29 -080046import com.android.internal.os.SomeArgs;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080047import com.android.internal.util.ArrayUtils;
48import com.android.internal.util.XmlUtils;
49import libcore.io.IoUtils;
50import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlSerializer;
53
54import java.io.File;
55import java.io.FileInputStream;
56import java.io.FileNotFoundException;
57import java.io.FileOutputStream;
58import java.io.IOException;
59import java.nio.charset.StandardCharsets;
Chad Brubaker0d277a72017-04-12 16:56:53 -070060import java.security.SecureRandom;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080061import java.util.ArrayList;
62import java.util.List;
Chad Brubaker0d277a72017-04-12 16:56:53 -070063import java.util.Locale;
Svetoslav Ganov096d3042017-01-30 16:34:13 -080064import java.util.Set;
65import java.util.function.Predicate;
66
67/**
68 * This class is a part of the package manager service that is responsible
69 * for managing data associated with instant apps such as cached uninstalled
70 * instant apps and instant apps' cookies. In addition it is responsible for
71 * pruning installed instant apps and meta-data for uninstalled instant apps
72 * when free space is needed.
73 */
74class InstantAppRegistry {
75 private static final boolean DEBUG = false;
76
77 private static final String LOG_TAG = "InstantAppRegistry";
78
79 private static final long DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS =
80 DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
81
82 private static final String INSTANT_APPS_FOLDER = "instant";
83 private static final String INSTANT_APP_ICON_FILE = "icon.png";
84 private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
85 private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
86 private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
Chad Brubaker0d277a72017-04-12 16:56:53 -070087 private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
Svetoslav Ganov096d3042017-01-30 16:34:13 -080088
89 private static final String TAG_PACKAGE = "package";
90 private static final String TAG_PERMISSIONS = "permissions";
91 private static final String TAG_PERMISSION = "permission";
92
93 private static final String ATTR_LABEL = "label";
94 private static final String ATTR_NAME = "name";
95 private static final String ATTR_GRANTED = "granted";
96
97 private final PackageManagerService mService;
98 private final CookiePersistence mCookiePersistence;
99
100 /** State for uninstalled instant apps */
101 @GuardedBy("mService.mPackages")
102 private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
103
104 /**
105 * Automatic grants for access to instant app metadata.
106 * The key is the target application UID.
107 * The value is a set of instant app UIDs.
108 * UserID -> TargetAppId -> InstantAppId
109 */
110 @GuardedBy("mService.mPackages")
111 private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants;
112
113 /** The set of all installed instant apps. UserID -> AppID */
114 @GuardedBy("mService.mPackages")
115 private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
116
117 public InstantAppRegistry(PackageManagerService service) {
118 mService = service;
119 mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
120 }
121
122 public byte[] getInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800123 @UserIdInt int userId) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800124 // Only installed packages can get their own cookie
125 PackageParser.Package pkg = mService.mPackages.get(packageName);
126 if (pkg == null) {
127 return null;
128 }
129
130 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800131 if (pendingCookie != null) {
132 return pendingCookie;
133 }
134 File cookieFile = peekInstantCookieFile(packageName, userId);
135 if (cookieFile != null && cookieFile.exists()) {
136 try {
137 return IoUtils.readFileAsByteArray(cookieFile.toString());
138 } catch (IOException e) {
139 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
140 }
141 }
142 return null;
143 }
144
145 public boolean setInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800146 @Nullable byte[] cookie, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800147 if (cookie != null && cookie.length > 0) {
148 final int maxCookieSize = mService.mContext.getPackageManager()
Svetoslav Ganov345ffa52017-04-18 16:08:41 -0700149 .getInstantAppCookieMaxBytes();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800150 if (cookie.length > maxCookieSize) {
151 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
152 + cookie.length + " bytes while max size is " + maxCookieSize);
153 return false;
154 }
155 }
156
Svet Ganov312c6cc2017-02-17 20:48:24 -0800157 // Only an installed package can set its own cookie
Svet Ganovee2028c2017-02-17 17:23:29 -0800158 PackageParser.Package pkg = mService.mPackages.get(packageName);
159 if (pkg == null) {
160 return false;
161 }
162
Svet Ganov312c6cc2017-02-17 20:48:24 -0800163 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800164 return true;
165 }
166
167 private void persistInstantApplicationCookie(@Nullable byte[] cookie,
Svet Ganovee2028c2017-02-17 17:23:29 -0800168 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800169 synchronized (mService.mPackages) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800170 File appDir = getInstantApplicationDir(packageName, userId);
171 if (!appDir.exists() && !appDir.mkdirs()) {
172 Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
173 return;
174 }
175
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800176 if (cookieFile.exists() && !cookieFile.delete()) {
177 Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
178 }
179
180 // No cookie or an empty one means delete - done
181 if (cookie == null || cookie.length <= 0) {
182 return;
183 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800184 }
185 try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
186 fos.write(cookie, 0, cookie.length);
187 } catch (IOException e) {
188 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800189 }
190 }
191
192 public Bitmap getInstantAppIconLPw(@NonNull String packageName,
193 @UserIdInt int userId) {
194 File iconFile = new File(getInstantApplicationDir(packageName, userId),
195 INSTANT_APP_ICON_FILE);
196 if (iconFile.exists()) {
197 return BitmapFactory.decodeFile(iconFile.toString());
198 }
199 return null;
200 }
201
Chad Brubaker0d277a72017-04-12 16:56:53 -0700202 public String getInstantAppAndroidIdLPw(@NonNull String packageName,
203 @UserIdInt int userId) {
204 File idFile = new File(getInstantApplicationDir(packageName, userId),
205 INSTANT_APP_ANDROID_ID_FILE);
206 if (idFile.exists()) {
207 try {
208 return IoUtils.readFileAsString(idFile.getAbsolutePath());
209 } catch (IOException e) {
210 Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
211 }
212 }
213 return generateInstantAppAndroidIdLPw(packageName, userId);
214 }
215
216 private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
217 @UserIdInt int userId) {
218 byte[] randomBytes = new byte[8];
219 new SecureRandom().nextBytes(randomBytes);
220 String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
Chad Brubaker64c8b792017-04-28 11:02:26 -0700221 File appDir = getInstantApplicationDir(packageName, userId);
222 if (!appDir.exists() && !appDir.mkdirs()) {
223 Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
224 return id;
225 }
Chad Brubaker0d277a72017-04-12 16:56:53 -0700226 File idFile = new File(getInstantApplicationDir(packageName, userId),
227 INSTANT_APP_ANDROID_ID_FILE);
228 try (FileOutputStream fos = new FileOutputStream(idFile)) {
229 fos.write(id.getBytes());
230 } catch (IOException e) {
231 Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
232 }
233 return id;
234
235 }
236
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800237 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
238 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
239 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
240 if (installedApps != null) {
241 if (uninstalledApps != null) {
242 installedApps.addAll(uninstalledApps);
243 }
244 return installedApps;
245 }
246 return uninstalledApps;
247 }
248
249 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
250 PackageSetting ps = (PackageSetting) pkg.mExtras;
251 if (ps == null) {
252 return;
253 }
254
255 for (int userId : userIds) {
256 // Ignore not installed apps
257 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
258 continue;
259 }
260
261 // Propagate permissions before removing any state
262 propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
263
264 // Track instant apps
Todd Kennedybe0b8892017-02-15 14:13:52 -0800265 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800266 addInstantAppLPw(userId, ps.appId);
267 }
268
269 // Remove the in-memory state
270 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
271 state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
272 userId);
273
274 // Remove the on-disk state except the cookie
275 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
276 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
277 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
278
279 // If app signature changed - wipe the cookie
280 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
281 if (currentCookieFile == null) {
282 continue;
283 }
284 File expectedCookeFile = computeInstantCookieFile(pkg, userId);
285 if (!currentCookieFile.equals(expectedCookeFile)) {
286 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
287 + " changed - dropping cookie");
Svet Ganov312c6cc2017-02-17 20:48:24 -0800288 // Make sure a pending write for the old signed app is cancelled
289 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800290 currentCookieFile.delete();
291 }
292 }
293 }
294
295 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
296 @NonNull int[] userIds) {
297 PackageSetting ps = (PackageSetting) pkg.mExtras;
298 if (ps == null) {
299 return;
300 }
301
302 for (int userId : userIds) {
303 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
304 continue;
305 }
306
Todd Kennedybe0b8892017-02-15 14:13:52 -0800307 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800308 // Add a record for an uninstalled instant app
309 addUninstalledInstantAppLPw(pkg, userId);
310 removeInstantAppLPw(userId, ps.appId);
311 } else {
312 // Deleting an app prunes all instant state such as cookie
313 deleteDir(getInstantApplicationDir(pkg.packageName, userId));
Svetoslav Ganov8aa379e2017-04-04 18:12:27 -0700314 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800315 removeAppLPw(userId, ps.appId);
316 }
317 }
318 }
319
320 public void onUserRemovedLPw(int userId) {
321 if (mUninstalledInstantApps != null) {
322 mUninstalledInstantApps.remove(userId);
323 if (mUninstalledInstantApps.size() <= 0) {
324 mUninstalledInstantApps = null;
325 }
326 }
327 if (mInstalledInstantAppUids != null) {
328 mInstalledInstantAppUids.remove(userId);
329 if (mInstalledInstantAppUids.size() <= 0) {
330 mInstalledInstantAppUids = null;
331 }
332 }
333 if (mInstantGrants != null) {
334 mInstantGrants.remove(userId);
335 if (mInstantGrants.size() <= 0) {
336 mInstantGrants = null;
337 }
338 }
339 deleteDir(getInstantApplicationsDir(userId));
340 }
341
342 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
343 int instantAppId) {
344 if (mInstantGrants == null) {
345 return false;
346 }
347 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
348 if (targetAppList == null) {
349 return false;
350 }
351 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
352 if (instantGrantList == null) {
353 return false;
354 }
355 return instantGrantList.get(instantAppId);
356 }
357
358 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
359 int targetAppId, int instantAppId) {
360 if (mInstalledInstantAppUids == null) {
361 return; // no instant apps installed; no need to grant
362 }
363 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
364 if (instantAppList == null || !instantAppList.get(instantAppId)) {
365 return; // instant app id isn't installed; no need to grant
366 }
367 if (instantAppList.get(targetAppId)) {
368 return; // target app id is an instant app; no need to grant
369 }
370 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
371 final Set<String> categories = intent.getCategories();
372 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
373 return; // launched via VIEW/BROWSABLE intent; no need to grant
374 }
375 }
376 if (mInstantGrants == null) {
377 mInstantGrants = new SparseArray<>();
378 }
379 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
380 if (targetAppList == null) {
381 targetAppList = new SparseArray<>();
382 mInstantGrants.put(userId, targetAppList);
383 }
384 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
385 if (instantGrantList == null) {
386 instantGrantList = new SparseBooleanArray();
387 targetAppList.put(targetAppId, instantGrantList);
388 }
389 instantGrantList.put(instantAppId, true /*granted*/);
390 }
391
392 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
393 if (mInstalledInstantAppUids == null) {
394 mInstalledInstantAppUids = new SparseArray<>();
395 }
396 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
397 if (instantAppList == null) {
398 instantAppList = new SparseBooleanArray();
399 mInstalledInstantAppUids.put(userId, instantAppList);
400 }
401 instantAppList.put(instantAppId, true /*installed*/);
402 }
403
404 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
405 // remove from the installed list
406 if (mInstalledInstantAppUids == null) {
407 return; // no instant apps on the system
408 }
409 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
410 if (instantAppList == null) {
411 return;
412 }
413
414 instantAppList.delete(instantAppId);
415
416 // remove any grants
417 if (mInstantGrants == null) {
418 return; // no grants on the system
419 }
420 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
421 if (targetAppList == null) {
422 return; // no grants for this user
423 }
424 for (int i = targetAppList.size() - 1; i >= 0; --i) {
425 targetAppList.valueAt(i).delete(instantAppId);
426 }
427 }
428
429 private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
430 // remove from the installed list
431 if (mInstantGrants == null) {
432 return; // no grants on the system
433 }
434 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
435 if (targetAppList == null) {
436 return; // no grants for this user
437 }
438 targetAppList.delete(targetAppId);
439 }
440
441 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
442 @UserIdInt int userId) {
443 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
444 pkg, userId, false);
445 if (uninstalledApp == null) {
446 return;
447 }
448 if (mUninstalledInstantApps == null) {
449 mUninstalledInstantApps = new SparseArray<>();
450 }
451 List<UninstalledInstantAppState> uninstalledAppStates =
452 mUninstalledInstantApps.get(userId);
453 if (uninstalledAppStates == null) {
454 uninstalledAppStates = new ArrayList<>();
455 mUninstalledInstantApps.put(userId, uninstalledAppStates);
456 }
457 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
458 uninstalledApp, System.currentTimeMillis());
459 uninstalledAppStates.add(uninstalledAppState);
460
461 writeUninstalledInstantAppMetadata(uninstalledApp, userId);
462 writeInstantApplicationIconLPw(pkg, userId);
463 }
464
465 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
466 @UserIdInt int userId) {
467 File appDir = getInstantApplicationDir(pkg.packageName, userId);
468 if (!appDir.exists()) {
469 return;
470 }
471
472 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
473
474 final Bitmap bitmap;
475 if (icon instanceof BitmapDrawable) {
476 bitmap = ((BitmapDrawable) icon).getBitmap();
477 } else {
478 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
479 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
480 Canvas canvas = new Canvas(bitmap);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800481 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800482 icon.draw(canvas);
483 }
484
485 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
486 INSTANT_APP_ICON_FILE);
487
488 try (FileOutputStream out = new FileOutputStream(iconFile)) {
489 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
490 } catch (Exception e) {
491 Slog.e(LOG_TAG, "Error writing instant app icon", e);
492 }
493 }
494
495 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
496 @UserIdInt int userId) {
497 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
498 state.mInstantAppInfo.getPackageName().equals(packageName),
499 userId);
500
501 File instantAppDir = getInstantApplicationDir(packageName, userId);
502 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
503 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
Chad Brubaker0d277a72017-04-12 16:56:53 -0700504 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800505 File cookie = peekInstantCookieFile(packageName, userId);
506 if (cookie != null) {
507 cookie.delete();
508 }
509 }
510
511 private void removeUninstalledInstantAppStateLPw(
512 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
513 if (mUninstalledInstantApps == null) {
514 return;
515 }
516 List<UninstalledInstantAppState> uninstalledAppStates =
517 mUninstalledInstantApps.get(userId);
518 if (uninstalledAppStates == null) {
519 return;
520 }
521 final int appCount = uninstalledAppStates.size();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800522 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800523 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
524 if (!criteria.test(uninstalledAppState)) {
525 continue;
526 }
527 uninstalledAppStates.remove(i);
528 if (uninstalledAppStates.isEmpty()) {
529 mUninstalledInstantApps.remove(userId);
530 if (mUninstalledInstantApps.size() <= 0) {
531 mUninstalledInstantApps = null;
532 }
533 return;
534 }
535 }
536 }
537
538 public void pruneInstantAppsLPw() {
539 // For now we prune only state for uninstalled instant apps
540 final long maxCacheDurationMillis = Settings.Global.getLong(
541 mService.mContext.getContentResolver(),
542 Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS,
543 DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS);
544
545 for (int userId : UserManagerService.getInstance().getUserIds()) {
546 // Prune in-memory state
547 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
548 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
549 return (elapsedCachingMillis > maxCacheDurationMillis);
550 }, userId);
551
552 // Prune on-disk state
553 File instantAppsDir = getInstantApplicationsDir(userId);
554 if (!instantAppsDir.exists()) {
555 continue;
556 }
557 File[] files = instantAppsDir.listFiles();
558 if (files == null) {
559 continue;
560 }
561 for (File instantDir : files) {
562 if (!instantDir.isDirectory()) {
563 continue;
564 }
565
566 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
567 if (!metadataFile.exists()) {
568 continue;
569 }
570
571 final long elapsedCachingMillis = System.currentTimeMillis()
572 - metadataFile.lastModified();
573 if (elapsedCachingMillis > maxCacheDurationMillis) {
574 deleteDir(instantDir);
575 }
576 }
577 }
578 }
579
580 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
581 @UserIdInt int userId) {
582 List<InstantAppInfo> result = null;
583
584 final int packageCount = mService.mPackages.size();
585 for (int i = 0; i < packageCount; i++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800586 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
587 final PackageSetting ps = (PackageSetting) pkg.mExtras;
588 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800589 continue;
590 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800591 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800592 pkg, userId, true);
593 if (info == null) {
594 continue;
595 }
596 if (result == null) {
597 result = new ArrayList<>();
598 }
599 result.add(info);
600 }
601
602 return result;
603 }
604
605 private @NonNull
606 InstantAppInfo createInstantAppInfoForPackage(
607 @NonNull PackageParser.Package pkg, @UserIdInt int userId,
608 boolean addApplicationInfo) {
609 PackageSetting ps = (PackageSetting) pkg.mExtras;
610 if (ps == null) {
611 return null;
612 }
613 if (!ps.getInstalled(userId)) {
614 return null;
615 }
616
617 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
618 pkg.requestedPermissions.toArray(requestedPermissions);
619
620 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
621 String[] grantedPermissions = new String[permissions.size()];
622 permissions.toArray(grantedPermissions);
623
624 if (addApplicationInfo) {
625 return new InstantAppInfo(pkg.applicationInfo,
626 requestedPermissions, grantedPermissions);
627 } else {
628 return new InstantAppInfo(pkg.applicationInfo.packageName,
629 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
630 requestedPermissions, grantedPermissions);
631 }
632 }
633
634 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
635 @UserIdInt int userId) {
636 List<UninstalledInstantAppState> uninstalledAppStates =
637 getUninstalledInstantAppStatesLPr(userId);
638 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
639 return null;
640 }
641
642 List<InstantAppInfo> uninstalledApps = null;
643 final int stateCount = uninstalledAppStates.size();
644 for (int i = 0; i < stateCount; i++) {
645 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
646 if (uninstalledApps == null) {
647 uninstalledApps = new ArrayList<>();
648 }
649 uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
650 }
651 return uninstalledApps;
652 }
653
654 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
655 @UserIdInt int userId) {
656 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
657 packageName, userId);
658 if (appInfo == null) {
659 return;
660 }
661 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
662 return;
663 }
664 final long identity = Binder.clearCallingIdentity();
665 try {
666 for (String grantedPermission : appInfo.getGrantedPermissions()) {
667 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
668 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
669 mService.grantRuntimePermission(packageName, grantedPermission, userId);
670 }
671 }
672 } finally {
673 Binder.restoreCallingIdentity(identity);
674 }
675 }
676
677 private @NonNull
678 InstantAppInfo peekOrParseUninstalledInstantAppInfo(
679 @NonNull String packageName, @UserIdInt int userId) {
680 if (mUninstalledInstantApps != null) {
681 List<UninstalledInstantAppState> uninstalledAppStates =
682 mUninstalledInstantApps.get(userId);
683 if (uninstalledAppStates != null) {
684 final int appCount = uninstalledAppStates.size();
685 for (int i = 0; i < appCount; i++) {
686 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
687 if (uninstalledAppState.mInstantAppInfo
688 .getPackageName().equals(packageName)) {
689 return uninstalledAppState.mInstantAppInfo;
690 }
691 }
692 }
693 }
694
695 File metadataFile = new File(getInstantApplicationDir(packageName, userId),
696 INSTANT_APP_METADATA_FILE);
697 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
698 if (uninstalledAppState == null) {
699 return null;
700 }
701
702 return uninstalledAppState.mInstantAppInfo;
703 }
704
705 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
706 @UserIdInt int userId) {
707 List<UninstalledInstantAppState> uninstalledAppStates = null;
708 if (mUninstalledInstantApps != null) {
709 uninstalledAppStates = mUninstalledInstantApps.get(userId);
710 if (uninstalledAppStates != null) {
711 return uninstalledAppStates;
712 }
713 }
714
715 File instantAppsDir = getInstantApplicationsDir(userId);
716 if (instantAppsDir.exists()) {
717 File[] files = instantAppsDir.listFiles();
718 if (files != null) {
719 for (File instantDir : files) {
720 if (!instantDir.isDirectory()) {
721 continue;
722 }
723 File metadataFile = new File(instantDir,
724 INSTANT_APP_METADATA_FILE);
725 UninstalledInstantAppState uninstalledAppState =
726 parseMetadataFile(metadataFile);
727 if (uninstalledAppState == null) {
728 continue;
729 }
730 if (uninstalledAppStates == null) {
731 uninstalledAppStates = new ArrayList<>();
732 }
733 uninstalledAppStates.add(uninstalledAppState);
734 }
735 }
736 }
737
738 if (uninstalledAppStates != null) {
739 if (mUninstalledInstantApps == null) {
740 mUninstalledInstantApps = new SparseArray<>();
741 }
742 mUninstalledInstantApps.put(userId, uninstalledAppStates);
743 }
744
745 return uninstalledAppStates;
746 }
747
748 private static @Nullable UninstalledInstantAppState parseMetadataFile(
749 @NonNull File metadataFile) {
750 if (!metadataFile.exists()) {
751 return null;
752 }
753 FileInputStream in;
754 try {
755 in = new AtomicFile(metadataFile).openRead();
756 } catch (FileNotFoundException fnfe) {
757 Slog.i(LOG_TAG, "No instant metadata file");
758 return null;
759 }
760
761 final File instantDir = metadataFile.getParentFile();
762 final long timestamp = metadataFile.lastModified();
763 final String packageName = instantDir.getName();
764
765 try {
766 XmlPullParser parser = Xml.newPullParser();
767 parser.setInput(in, StandardCharsets.UTF_8.name());
768 return new UninstalledInstantAppState(
769 parseMetadata(parser, packageName), timestamp);
770 } catch (XmlPullParserException | IOException e) {
771 throw new IllegalStateException("Failed parsing instant"
772 + " metadata file: " + metadataFile, e);
773 } finally {
774 IoUtils.closeQuietly(in);
775 }
776 }
777
778 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
779 @UserIdInt int userId) {
780 File appDir = getInstantApplicationDir(pkg.packageName, userId);
781 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
782 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
783 return new File(appDir, cookieFile);
784 }
785
786 private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
787 @UserIdInt int userId) {
788 File appDir = getInstantApplicationDir(packageName, userId);
789 if (!appDir.exists()) {
790 return null;
791 }
792 File[] files = appDir.listFiles();
793 if (files == null) {
794 return null;
795 }
796 for (File file : files) {
797 if (!file.isDirectory()
798 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
799 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
800 return file;
801 }
802 }
803 return null;
804 }
805
806 private static @Nullable
807 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
808 @NonNull String packageName)
809 throws IOException, XmlPullParserException {
810 final int outerDepth = parser.getDepth();
811 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
812 if (TAG_PACKAGE.equals(parser.getName())) {
813 return parsePackage(parser, packageName);
814 }
815 }
816 return null;
817 }
818
819 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
820 @NonNull String packageName)
821 throws IOException, XmlPullParserException {
822 String label = parser.getAttributeValue(null, ATTR_LABEL);
823
824 List<String> outRequestedPermissions = new ArrayList<>();
825 List<String> outGrantedPermissions = new ArrayList<>();
826
827 final int outerDepth = parser.getDepth();
828 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
829 if (TAG_PERMISSIONS.equals(parser.getName())) {
830 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
831 }
832 }
833
834 String[] requestedPermissions = new String[outRequestedPermissions.size()];
835 outRequestedPermissions.toArray(requestedPermissions);
836
837 String[] grantedPermissions = new String[outGrantedPermissions.size()];
838 outGrantedPermissions.toArray(grantedPermissions);
839
840 return new InstantAppInfo(packageName, label,
841 requestedPermissions, grantedPermissions);
842 }
843
844 private static void parsePermissions(@NonNull XmlPullParser parser,
845 @NonNull List<String> outRequestedPermissions,
846 @NonNull List<String> outGrantedPermissions)
847 throws IOException, XmlPullParserException {
848 final int outerDepth = parser.getDepth();
849 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
850 if (TAG_PERMISSION.equals(parser.getName())) {
851 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
852 outRequestedPermissions.add(permission);
853 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
854 outGrantedPermissions.add(permission);
855 }
856 }
857 }
858 }
859
860 private void writeUninstalledInstantAppMetadata(
861 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
862 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
863 if (!appDir.exists() && !appDir.mkdirs()) {
864 return;
865 }
866
867 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
868
869 AtomicFile destination = new AtomicFile(metadataFile);
870 FileOutputStream out = null;
871 try {
872 out = destination.startWrite();
873
874 XmlSerializer serializer = Xml.newSerializer();
875 serializer.setOutput(out, StandardCharsets.UTF_8.name());
876 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
877
878 serializer.startDocument(null, true);
879
880 serializer.startTag(null, TAG_PACKAGE);
881 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
882 mService.mContext.getPackageManager()).toString());
883
884 serializer.startTag(null, TAG_PERMISSIONS);
885 for (String permission : instantApp.getRequestedPermissions()) {
886 serializer.startTag(null, TAG_PERMISSION);
887 serializer.attribute(null, ATTR_NAME, permission);
888 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
889 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
890 }
891 serializer.endTag(null, TAG_PERMISSION);
892 }
893 serializer.endTag(null, TAG_PERMISSIONS);
894
895 serializer.endTag(null, TAG_PACKAGE);
896
897 serializer.endDocument();
898 destination.finishWrite(out);
899 } catch (Throwable t) {
900 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
901 destination.failWrite(out);
902 } finally {
903 IoUtils.closeQuietly(out);
904 }
905 }
906
907 private static @NonNull File getInstantApplicationsDir(int userId) {
908 return new File(Environment.getUserSystemDirectory(userId),
909 INSTANT_APPS_FOLDER);
910 }
911
912 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
913 return new File (getInstantApplicationsDir(userId), packageName);
914 }
915
916 private static void deleteDir(@NonNull File dir) {
917 File[] files = dir.listFiles();
918 if (files != null) {
919 for (File file : files) {
920 deleteDir(file);
921 }
922 }
923 dir.delete();
924 }
925
926 private static final class UninstalledInstantAppState {
927 final InstantAppInfo mInstantAppInfo;
928 final long mTimestamp;
929
930 public UninstalledInstantAppState(InstantAppInfo instantApp,
931 long timestamp) {
932 mInstantAppInfo = instantApp;
933 mTimestamp = timestamp;
934 }
935 }
936
937 private final class CookiePersistence extends Handler {
938 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
939
940 // In case you wonder why we stash the cookies aside, we use
941 // the user id for the message id and the package for the payload.
942 // Handler allows removing messages by id and tag where the
Svet Ganovee2028c2017-02-17 17:23:29 -0800943 // tag is compared using ==. So to allow cancelling the
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800944 // pending persistence for an app under a given user we use
Svet Ganov312c6cc2017-02-17 20:48:24 -0800945 // the fact that package are cached by the system so the ==
946 // comparison would match and we end up with a way to cancel
947 // persisting the cookie for a user and package.
948 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
949 = new SparseArray<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800950
951 public CookiePersistence(Looper looper) {
952 super(looper);
953 }
954
Svet Ganov312c6cc2017-02-17 20:48:24 -0800955 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
956 @NonNull byte[] cookie) {
957 File cookieFile = computeInstantCookieFile(pkg, userId);
958 cancelPendingPersistLPw(pkg, userId);
959 addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
960 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800961 PERSIST_COOKIE_DELAY_MILLIS);
962 }
963
Svet Ganov312c6cc2017-02-17 20:48:24 -0800964 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
965 @UserIdInt int userId) {
966 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
967 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800968 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800969 SomeArgs state = pendingWorkForUser.get(pkg);
970 if (state != null) {
971 return (byte[]) state.arg1;
972 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800973 }
974 return null;
975 }
976
Svet Ganov312c6cc2017-02-17 20:48:24 -0800977 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
978 @UserIdInt int userId) {
979 removeMessages(userId, pkg);
980 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
981 if (state != null) {
982 state.recycle();
983 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800984 }
985
Svet Ganov312c6cc2017-02-17 20:48:24 -0800986 private void addPendingPersistCookieLPw(@UserIdInt int userId,
987 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
988 @NonNull File cookieFile) {
989 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
990 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800991 if (pendingWorkForUser == null) {
992 pendingWorkForUser = new ArrayMap<>();
993 mPendingPersistCookies.put(userId, pendingWorkForUser);
994 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800995 SomeArgs args = SomeArgs.obtain();
996 args.arg1 = cookie;
997 args.arg2 = cookieFile;
998 pendingWorkForUser.put(pkg, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800999 }
1000
Svet Ganov312c6cc2017-02-17 20:48:24 -08001001 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1002 @UserIdInt int userId) {
1003 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1004 mPendingPersistCookies.get(userId);
1005 SomeArgs state = null;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001006 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001007 state = pendingWorkForUser.remove(pkg);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001008 if (pendingWorkForUser.isEmpty()) {
1009 mPendingPersistCookies.remove(userId);
1010 }
1011 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001012 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001013 }
1014
1015 @Override
1016 public void handleMessage(Message message) {
1017 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -08001018 PackageParser.Package pkg = (PackageParser.Package) message.obj;
1019 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1020 if (state == null) {
1021 return;
1022 }
1023 byte[] cookie = (byte[]) state.arg1;
1024 File cookieFile = (File) state.arg2;
1025 state.recycle();
1026 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001027 }
1028 }
1029}