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