blob: 46bc69b8d71248277993967f08663adfeec7b567 [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()
149 .getInstantAppCookieMaxSize();
150 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);
221 File idFile = new File(getInstantApplicationDir(packageName, userId),
222 INSTANT_APP_ANDROID_ID_FILE);
223 try (FileOutputStream fos = new FileOutputStream(idFile)) {
224 fos.write(id.getBytes());
225 } catch (IOException e) {
226 Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
227 }
228 return id;
229
230 }
231
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800232 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
233 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
234 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
235 if (installedApps != null) {
236 if (uninstalledApps != null) {
237 installedApps.addAll(uninstalledApps);
238 }
239 return installedApps;
240 }
241 return uninstalledApps;
242 }
243
244 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
245 PackageSetting ps = (PackageSetting) pkg.mExtras;
246 if (ps == null) {
247 return;
248 }
249
250 for (int userId : userIds) {
251 // Ignore not installed apps
252 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
253 continue;
254 }
255
256 // Propagate permissions before removing any state
257 propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
258
259 // Track instant apps
Todd Kennedybe0b8892017-02-15 14:13:52 -0800260 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800261 addInstantAppLPw(userId, ps.appId);
262 }
263
264 // Remove the in-memory state
265 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
266 state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
267 userId);
268
269 // Remove the on-disk state except the cookie
270 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
271 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
272 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
273
274 // If app signature changed - wipe the cookie
275 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
276 if (currentCookieFile == null) {
277 continue;
278 }
279 File expectedCookeFile = computeInstantCookieFile(pkg, userId);
280 if (!currentCookieFile.equals(expectedCookeFile)) {
281 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
282 + " changed - dropping cookie");
Svet Ganov312c6cc2017-02-17 20:48:24 -0800283 // Make sure a pending write for the old signed app is cancelled
284 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800285 currentCookieFile.delete();
286 }
287 }
288 }
289
290 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
291 @NonNull int[] userIds) {
292 PackageSetting ps = (PackageSetting) pkg.mExtras;
293 if (ps == null) {
294 return;
295 }
296
297 for (int userId : userIds) {
298 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
299 continue;
300 }
301
Todd Kennedybe0b8892017-02-15 14:13:52 -0800302 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800303 // Add a record for an uninstalled instant app
304 addUninstalledInstantAppLPw(pkg, userId);
305 removeInstantAppLPw(userId, ps.appId);
306 } else {
307 // Deleting an app prunes all instant state such as cookie
308 deleteDir(getInstantApplicationDir(pkg.packageName, userId));
Svetoslav Ganov8aa379e2017-04-04 18:12:27 -0700309 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800310 removeAppLPw(userId, ps.appId);
311 }
312 }
313 }
314
315 public void onUserRemovedLPw(int userId) {
316 if (mUninstalledInstantApps != null) {
317 mUninstalledInstantApps.remove(userId);
318 if (mUninstalledInstantApps.size() <= 0) {
319 mUninstalledInstantApps = null;
320 }
321 }
322 if (mInstalledInstantAppUids != null) {
323 mInstalledInstantAppUids.remove(userId);
324 if (mInstalledInstantAppUids.size() <= 0) {
325 mInstalledInstantAppUids = null;
326 }
327 }
328 if (mInstantGrants != null) {
329 mInstantGrants.remove(userId);
330 if (mInstantGrants.size() <= 0) {
331 mInstantGrants = null;
332 }
333 }
334 deleteDir(getInstantApplicationsDir(userId));
335 }
336
337 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
338 int instantAppId) {
339 if (mInstantGrants == null) {
340 return false;
341 }
342 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
343 if (targetAppList == null) {
344 return false;
345 }
346 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
347 if (instantGrantList == null) {
348 return false;
349 }
350 return instantGrantList.get(instantAppId);
351 }
352
353 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
354 int targetAppId, int instantAppId) {
355 if (mInstalledInstantAppUids == null) {
356 return; // no instant apps installed; no need to grant
357 }
358 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
359 if (instantAppList == null || !instantAppList.get(instantAppId)) {
360 return; // instant app id isn't installed; no need to grant
361 }
362 if (instantAppList.get(targetAppId)) {
363 return; // target app id is an instant app; no need to grant
364 }
365 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
366 final Set<String> categories = intent.getCategories();
367 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
368 return; // launched via VIEW/BROWSABLE intent; no need to grant
369 }
370 }
371 if (mInstantGrants == null) {
372 mInstantGrants = new SparseArray<>();
373 }
374 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
375 if (targetAppList == null) {
376 targetAppList = new SparseArray<>();
377 mInstantGrants.put(userId, targetAppList);
378 }
379 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
380 if (instantGrantList == null) {
381 instantGrantList = new SparseBooleanArray();
382 targetAppList.put(targetAppId, instantGrantList);
383 }
384 instantGrantList.put(instantAppId, true /*granted*/);
385 }
386
387 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
388 if (mInstalledInstantAppUids == null) {
389 mInstalledInstantAppUids = new SparseArray<>();
390 }
391 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
392 if (instantAppList == null) {
393 instantAppList = new SparseBooleanArray();
394 mInstalledInstantAppUids.put(userId, instantAppList);
395 }
396 instantAppList.put(instantAppId, true /*installed*/);
397 }
398
399 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
400 // remove from the installed list
401 if (mInstalledInstantAppUids == null) {
402 return; // no instant apps on the system
403 }
404 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
405 if (instantAppList == null) {
406 return;
407 }
408
409 instantAppList.delete(instantAppId);
410
411 // remove any grants
412 if (mInstantGrants == null) {
413 return; // no grants on the system
414 }
415 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
416 if (targetAppList == null) {
417 return; // no grants for this user
418 }
419 for (int i = targetAppList.size() - 1; i >= 0; --i) {
420 targetAppList.valueAt(i).delete(instantAppId);
421 }
422 }
423
424 private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
425 // remove from the installed list
426 if (mInstantGrants == null) {
427 return; // no grants on the system
428 }
429 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
430 if (targetAppList == null) {
431 return; // no grants for this user
432 }
433 targetAppList.delete(targetAppId);
434 }
435
436 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
437 @UserIdInt int userId) {
438 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
439 pkg, userId, false);
440 if (uninstalledApp == null) {
441 return;
442 }
443 if (mUninstalledInstantApps == null) {
444 mUninstalledInstantApps = new SparseArray<>();
445 }
446 List<UninstalledInstantAppState> uninstalledAppStates =
447 mUninstalledInstantApps.get(userId);
448 if (uninstalledAppStates == null) {
449 uninstalledAppStates = new ArrayList<>();
450 mUninstalledInstantApps.put(userId, uninstalledAppStates);
451 }
452 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
453 uninstalledApp, System.currentTimeMillis());
454 uninstalledAppStates.add(uninstalledAppState);
455
456 writeUninstalledInstantAppMetadata(uninstalledApp, userId);
457 writeInstantApplicationIconLPw(pkg, userId);
458 }
459
460 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
461 @UserIdInt int userId) {
462 File appDir = getInstantApplicationDir(pkg.packageName, userId);
463 if (!appDir.exists()) {
464 return;
465 }
466
467 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
468
469 final Bitmap bitmap;
470 if (icon instanceof BitmapDrawable) {
471 bitmap = ((BitmapDrawable) icon).getBitmap();
472 } else {
473 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
474 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
475 Canvas canvas = new Canvas(bitmap);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800476 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800477 icon.draw(canvas);
478 }
479
480 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
481 INSTANT_APP_ICON_FILE);
482
483 try (FileOutputStream out = new FileOutputStream(iconFile)) {
484 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
485 } catch (Exception e) {
486 Slog.e(LOG_TAG, "Error writing instant app icon", e);
487 }
488 }
489
490 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
491 @UserIdInt int userId) {
492 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
493 state.mInstantAppInfo.getPackageName().equals(packageName),
494 userId);
495
496 File instantAppDir = getInstantApplicationDir(packageName, userId);
497 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
498 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
Chad Brubaker0d277a72017-04-12 16:56:53 -0700499 new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800500 File cookie = peekInstantCookieFile(packageName, userId);
501 if (cookie != null) {
502 cookie.delete();
503 }
504 }
505
506 private void removeUninstalledInstantAppStateLPw(
507 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
508 if (mUninstalledInstantApps == null) {
509 return;
510 }
511 List<UninstalledInstantAppState> uninstalledAppStates =
512 mUninstalledInstantApps.get(userId);
513 if (uninstalledAppStates == null) {
514 return;
515 }
516 final int appCount = uninstalledAppStates.size();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800517 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800518 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
519 if (!criteria.test(uninstalledAppState)) {
520 continue;
521 }
522 uninstalledAppStates.remove(i);
523 if (uninstalledAppStates.isEmpty()) {
524 mUninstalledInstantApps.remove(userId);
525 if (mUninstalledInstantApps.size() <= 0) {
526 mUninstalledInstantApps = null;
527 }
528 return;
529 }
530 }
531 }
532
533 public void pruneInstantAppsLPw() {
534 // For now we prune only state for uninstalled instant apps
535 final long maxCacheDurationMillis = Settings.Global.getLong(
536 mService.mContext.getContentResolver(),
537 Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS,
538 DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS);
539
540 for (int userId : UserManagerService.getInstance().getUserIds()) {
541 // Prune in-memory state
542 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
543 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
544 return (elapsedCachingMillis > maxCacheDurationMillis);
545 }, userId);
546
547 // Prune on-disk state
548 File instantAppsDir = getInstantApplicationsDir(userId);
549 if (!instantAppsDir.exists()) {
550 continue;
551 }
552 File[] files = instantAppsDir.listFiles();
553 if (files == null) {
554 continue;
555 }
556 for (File instantDir : files) {
557 if (!instantDir.isDirectory()) {
558 continue;
559 }
560
561 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
562 if (!metadataFile.exists()) {
563 continue;
564 }
565
566 final long elapsedCachingMillis = System.currentTimeMillis()
567 - metadataFile.lastModified();
568 if (elapsedCachingMillis > maxCacheDurationMillis) {
569 deleteDir(instantDir);
570 }
571 }
572 }
573 }
574
575 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
576 @UserIdInt int userId) {
577 List<InstantAppInfo> result = null;
578
579 final int packageCount = mService.mPackages.size();
580 for (int i = 0; i < packageCount; i++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800581 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
582 final PackageSetting ps = (PackageSetting) pkg.mExtras;
583 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800584 continue;
585 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800586 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800587 pkg, userId, true);
588 if (info == null) {
589 continue;
590 }
591 if (result == null) {
592 result = new ArrayList<>();
593 }
594 result.add(info);
595 }
596
597 return result;
598 }
599
600 private @NonNull
601 InstantAppInfo createInstantAppInfoForPackage(
602 @NonNull PackageParser.Package pkg, @UserIdInt int userId,
603 boolean addApplicationInfo) {
604 PackageSetting ps = (PackageSetting) pkg.mExtras;
605 if (ps == null) {
606 return null;
607 }
608 if (!ps.getInstalled(userId)) {
609 return null;
610 }
611
612 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
613 pkg.requestedPermissions.toArray(requestedPermissions);
614
615 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
616 String[] grantedPermissions = new String[permissions.size()];
617 permissions.toArray(grantedPermissions);
618
619 if (addApplicationInfo) {
620 return new InstantAppInfo(pkg.applicationInfo,
621 requestedPermissions, grantedPermissions);
622 } else {
623 return new InstantAppInfo(pkg.applicationInfo.packageName,
624 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
625 requestedPermissions, grantedPermissions);
626 }
627 }
628
629 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
630 @UserIdInt int userId) {
631 List<UninstalledInstantAppState> uninstalledAppStates =
632 getUninstalledInstantAppStatesLPr(userId);
633 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
634 return null;
635 }
636
637 List<InstantAppInfo> uninstalledApps = null;
638 final int stateCount = uninstalledAppStates.size();
639 for (int i = 0; i < stateCount; i++) {
640 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
641 if (uninstalledApps == null) {
642 uninstalledApps = new ArrayList<>();
643 }
644 uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
645 }
646 return uninstalledApps;
647 }
648
649 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
650 @UserIdInt int userId) {
651 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
652 packageName, userId);
653 if (appInfo == null) {
654 return;
655 }
656 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
657 return;
658 }
659 final long identity = Binder.clearCallingIdentity();
660 try {
661 for (String grantedPermission : appInfo.getGrantedPermissions()) {
662 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
663 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
664 mService.grantRuntimePermission(packageName, grantedPermission, userId);
665 }
666 }
667 } finally {
668 Binder.restoreCallingIdentity(identity);
669 }
670 }
671
672 private @NonNull
673 InstantAppInfo peekOrParseUninstalledInstantAppInfo(
674 @NonNull String packageName, @UserIdInt int userId) {
675 if (mUninstalledInstantApps != null) {
676 List<UninstalledInstantAppState> uninstalledAppStates =
677 mUninstalledInstantApps.get(userId);
678 if (uninstalledAppStates != null) {
679 final int appCount = uninstalledAppStates.size();
680 for (int i = 0; i < appCount; i++) {
681 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
682 if (uninstalledAppState.mInstantAppInfo
683 .getPackageName().equals(packageName)) {
684 return uninstalledAppState.mInstantAppInfo;
685 }
686 }
687 }
688 }
689
690 File metadataFile = new File(getInstantApplicationDir(packageName, userId),
691 INSTANT_APP_METADATA_FILE);
692 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
693 if (uninstalledAppState == null) {
694 return null;
695 }
696
697 return uninstalledAppState.mInstantAppInfo;
698 }
699
700 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
701 @UserIdInt int userId) {
702 List<UninstalledInstantAppState> uninstalledAppStates = null;
703 if (mUninstalledInstantApps != null) {
704 uninstalledAppStates = mUninstalledInstantApps.get(userId);
705 if (uninstalledAppStates != null) {
706 return uninstalledAppStates;
707 }
708 }
709
710 File instantAppsDir = getInstantApplicationsDir(userId);
711 if (instantAppsDir.exists()) {
712 File[] files = instantAppsDir.listFiles();
713 if (files != null) {
714 for (File instantDir : files) {
715 if (!instantDir.isDirectory()) {
716 continue;
717 }
718 File metadataFile = new File(instantDir,
719 INSTANT_APP_METADATA_FILE);
720 UninstalledInstantAppState uninstalledAppState =
721 parseMetadataFile(metadataFile);
722 if (uninstalledAppState == null) {
723 continue;
724 }
725 if (uninstalledAppStates == null) {
726 uninstalledAppStates = new ArrayList<>();
727 }
728 uninstalledAppStates.add(uninstalledAppState);
729 }
730 }
731 }
732
733 if (uninstalledAppStates != null) {
734 if (mUninstalledInstantApps == null) {
735 mUninstalledInstantApps = new SparseArray<>();
736 }
737 mUninstalledInstantApps.put(userId, uninstalledAppStates);
738 }
739
740 return uninstalledAppStates;
741 }
742
743 private static @Nullable UninstalledInstantAppState parseMetadataFile(
744 @NonNull File metadataFile) {
745 if (!metadataFile.exists()) {
746 return null;
747 }
748 FileInputStream in;
749 try {
750 in = new AtomicFile(metadataFile).openRead();
751 } catch (FileNotFoundException fnfe) {
752 Slog.i(LOG_TAG, "No instant metadata file");
753 return null;
754 }
755
756 final File instantDir = metadataFile.getParentFile();
757 final long timestamp = metadataFile.lastModified();
758 final String packageName = instantDir.getName();
759
760 try {
761 XmlPullParser parser = Xml.newPullParser();
762 parser.setInput(in, StandardCharsets.UTF_8.name());
763 return new UninstalledInstantAppState(
764 parseMetadata(parser, packageName), timestamp);
765 } catch (XmlPullParserException | IOException e) {
766 throw new IllegalStateException("Failed parsing instant"
767 + " metadata file: " + metadataFile, e);
768 } finally {
769 IoUtils.closeQuietly(in);
770 }
771 }
772
773 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
774 @UserIdInt int userId) {
775 File appDir = getInstantApplicationDir(pkg.packageName, userId);
776 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
777 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
778 return new File(appDir, cookieFile);
779 }
780
781 private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
782 @UserIdInt int userId) {
783 File appDir = getInstantApplicationDir(packageName, userId);
784 if (!appDir.exists()) {
785 return null;
786 }
787 File[] files = appDir.listFiles();
788 if (files == null) {
789 return null;
790 }
791 for (File file : files) {
792 if (!file.isDirectory()
793 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
794 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
795 return file;
796 }
797 }
798 return null;
799 }
800
801 private static @Nullable
802 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
803 @NonNull String packageName)
804 throws IOException, XmlPullParserException {
805 final int outerDepth = parser.getDepth();
806 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
807 if (TAG_PACKAGE.equals(parser.getName())) {
808 return parsePackage(parser, packageName);
809 }
810 }
811 return null;
812 }
813
814 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
815 @NonNull String packageName)
816 throws IOException, XmlPullParserException {
817 String label = parser.getAttributeValue(null, ATTR_LABEL);
818
819 List<String> outRequestedPermissions = new ArrayList<>();
820 List<String> outGrantedPermissions = new ArrayList<>();
821
822 final int outerDepth = parser.getDepth();
823 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
824 if (TAG_PERMISSIONS.equals(parser.getName())) {
825 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
826 }
827 }
828
829 String[] requestedPermissions = new String[outRequestedPermissions.size()];
830 outRequestedPermissions.toArray(requestedPermissions);
831
832 String[] grantedPermissions = new String[outGrantedPermissions.size()];
833 outGrantedPermissions.toArray(grantedPermissions);
834
835 return new InstantAppInfo(packageName, label,
836 requestedPermissions, grantedPermissions);
837 }
838
839 private static void parsePermissions(@NonNull XmlPullParser parser,
840 @NonNull List<String> outRequestedPermissions,
841 @NonNull List<String> outGrantedPermissions)
842 throws IOException, XmlPullParserException {
843 final int outerDepth = parser.getDepth();
844 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
845 if (TAG_PERMISSION.equals(parser.getName())) {
846 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
847 outRequestedPermissions.add(permission);
848 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
849 outGrantedPermissions.add(permission);
850 }
851 }
852 }
853 }
854
855 private void writeUninstalledInstantAppMetadata(
856 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
857 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
858 if (!appDir.exists() && !appDir.mkdirs()) {
859 return;
860 }
861
862 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
863
864 AtomicFile destination = new AtomicFile(metadataFile);
865 FileOutputStream out = null;
866 try {
867 out = destination.startWrite();
868
869 XmlSerializer serializer = Xml.newSerializer();
870 serializer.setOutput(out, StandardCharsets.UTF_8.name());
871 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
872
873 serializer.startDocument(null, true);
874
875 serializer.startTag(null, TAG_PACKAGE);
876 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
877 mService.mContext.getPackageManager()).toString());
878
879 serializer.startTag(null, TAG_PERMISSIONS);
880 for (String permission : instantApp.getRequestedPermissions()) {
881 serializer.startTag(null, TAG_PERMISSION);
882 serializer.attribute(null, ATTR_NAME, permission);
883 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
884 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
885 }
886 serializer.endTag(null, TAG_PERMISSION);
887 }
888 serializer.endTag(null, TAG_PERMISSIONS);
889
890 serializer.endTag(null, TAG_PACKAGE);
891
892 serializer.endDocument();
893 destination.finishWrite(out);
894 } catch (Throwable t) {
895 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
896 destination.failWrite(out);
897 } finally {
898 IoUtils.closeQuietly(out);
899 }
900 }
901
902 private static @NonNull File getInstantApplicationsDir(int userId) {
903 return new File(Environment.getUserSystemDirectory(userId),
904 INSTANT_APPS_FOLDER);
905 }
906
907 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
908 return new File (getInstantApplicationsDir(userId), packageName);
909 }
910
911 private static void deleteDir(@NonNull File dir) {
912 File[] files = dir.listFiles();
913 if (files != null) {
914 for (File file : files) {
915 deleteDir(file);
916 }
917 }
918 dir.delete();
919 }
920
921 private static final class UninstalledInstantAppState {
922 final InstantAppInfo mInstantAppInfo;
923 final long mTimestamp;
924
925 public UninstalledInstantAppState(InstantAppInfo instantApp,
926 long timestamp) {
927 mInstantAppInfo = instantApp;
928 mTimestamp = timestamp;
929 }
930 }
931
932 private final class CookiePersistence extends Handler {
933 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
934
935 // In case you wonder why we stash the cookies aside, we use
936 // the user id for the message id and the package for the payload.
937 // Handler allows removing messages by id and tag where the
Svet Ganovee2028c2017-02-17 17:23:29 -0800938 // tag is compared using ==. So to allow cancelling the
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800939 // pending persistence for an app under a given user we use
Svet Ganov312c6cc2017-02-17 20:48:24 -0800940 // the fact that package are cached by the system so the ==
941 // comparison would match and we end up with a way to cancel
942 // persisting the cookie for a user and package.
943 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
944 = new SparseArray<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800945
946 public CookiePersistence(Looper looper) {
947 super(looper);
948 }
949
Svet Ganov312c6cc2017-02-17 20:48:24 -0800950 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
951 @NonNull byte[] cookie) {
952 File cookieFile = computeInstantCookieFile(pkg, userId);
953 cancelPendingPersistLPw(pkg, userId);
954 addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
955 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800956 PERSIST_COOKIE_DELAY_MILLIS);
957 }
958
Svet Ganov312c6cc2017-02-17 20:48:24 -0800959 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
960 @UserIdInt int userId) {
961 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
962 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800963 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800964 SomeArgs state = pendingWorkForUser.get(pkg);
965 if (state != null) {
966 return (byte[]) state.arg1;
967 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800968 }
969 return null;
970 }
971
Svet Ganov312c6cc2017-02-17 20:48:24 -0800972 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
973 @UserIdInt int userId) {
974 removeMessages(userId, pkg);
975 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
976 if (state != null) {
977 state.recycle();
978 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800979 }
980
Svet Ganov312c6cc2017-02-17 20:48:24 -0800981 private void addPendingPersistCookieLPw(@UserIdInt int userId,
982 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
983 @NonNull File cookieFile) {
984 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
985 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800986 if (pendingWorkForUser == null) {
987 pendingWorkForUser = new ArrayMap<>();
988 mPendingPersistCookies.put(userId, pendingWorkForUser);
989 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800990 SomeArgs args = SomeArgs.obtain();
991 args.arg1 = cookie;
992 args.arg2 = cookieFile;
993 pendingWorkForUser.put(pkg, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800994 }
995
Svet Ganov312c6cc2017-02-17 20:48:24 -0800996 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
997 @UserIdInt int userId) {
998 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
999 mPendingPersistCookies.get(userId);
1000 SomeArgs state = null;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001001 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -08001002 state = pendingWorkForUser.remove(pkg);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001003 if (pendingWorkForUser.isEmpty()) {
1004 mPendingPersistCookies.remove(userId);
1005 }
1006 }
Svet Ganov312c6cc2017-02-17 20:48:24 -08001007 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001008 }
1009
1010 @Override
1011 public void handleMessage(Message message) {
1012 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -08001013 PackageParser.Package pkg = (PackageParser.Package) message.obj;
1014 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1015 if (state == null) {
1016 return;
1017 }
1018 byte[] cookie = (byte[]) state.arg1;
1019 File cookieFile = (File) state.arg2;
1020 state.recycle();
1021 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -08001022 }
1023 }
1024}