blob: 0ae5f314d4736569df4313c9caed26d6261b4760 [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) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800120 // Only installed packages can get their own cookie
121 PackageParser.Package pkg = mService.mPackages.get(packageName);
122 if (pkg == null) {
123 return null;
124 }
125
126 byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800127 if (pendingCookie != null) {
128 return pendingCookie;
129 }
130 File cookieFile = peekInstantCookieFile(packageName, userId);
131 if (cookieFile != null && cookieFile.exists()) {
132 try {
133 return IoUtils.readFileAsByteArray(cookieFile.toString());
134 } catch (IOException e) {
135 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
136 }
137 }
138 return null;
139 }
140
141 public boolean setInstantAppCookieLPw(@NonNull String packageName,
Svet Ganovee2028c2017-02-17 17:23:29 -0800142 @Nullable byte[] cookie, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800143 if (cookie != null && cookie.length > 0) {
144 final int maxCookieSize = mService.mContext.getPackageManager()
145 .getInstantAppCookieMaxSize();
146 if (cookie.length > maxCookieSize) {
147 Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
148 + cookie.length + " bytes while max size is " + maxCookieSize);
149 return false;
150 }
151 }
152
Svet Ganov312c6cc2017-02-17 20:48:24 -0800153 // Only an installed package can set its own cookie
Svet Ganovee2028c2017-02-17 17:23:29 -0800154 PackageParser.Package pkg = mService.mPackages.get(packageName);
155 if (pkg == null) {
156 return false;
157 }
158
Svet Ganov312c6cc2017-02-17 20:48:24 -0800159 mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800160 return true;
161 }
162
163 private void persistInstantApplicationCookie(@Nullable byte[] cookie,
Svet Ganovee2028c2017-02-17 17:23:29 -0800164 @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800165 synchronized (mService.mPackages) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800166 File appDir = getInstantApplicationDir(packageName, userId);
167 if (!appDir.exists() && !appDir.mkdirs()) {
168 Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
169 return;
170 }
171
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800172 if (cookieFile.exists() && !cookieFile.delete()) {
173 Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
174 }
175
176 // No cookie or an empty one means delete - done
177 if (cookie == null || cookie.length <= 0) {
178 return;
179 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800180 }
181 try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
182 fos.write(cookie, 0, cookie.length);
183 } catch (IOException e) {
184 Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800185 }
186 }
187
188 public Bitmap getInstantAppIconLPw(@NonNull String packageName,
189 @UserIdInt int userId) {
190 File iconFile = new File(getInstantApplicationDir(packageName, userId),
191 INSTANT_APP_ICON_FILE);
192 if (iconFile.exists()) {
193 return BitmapFactory.decodeFile(iconFile.toString());
194 }
195 return null;
196 }
197
198 public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
199 List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
200 List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
201 if (installedApps != null) {
202 if (uninstalledApps != null) {
203 installedApps.addAll(uninstalledApps);
204 }
205 return installedApps;
206 }
207 return uninstalledApps;
208 }
209
210 public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
211 PackageSetting ps = (PackageSetting) pkg.mExtras;
212 if (ps == null) {
213 return;
214 }
215
216 for (int userId : userIds) {
217 // Ignore not installed apps
218 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
219 continue;
220 }
221
222 // Propagate permissions before removing any state
223 propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
224
225 // Track instant apps
Todd Kennedybe0b8892017-02-15 14:13:52 -0800226 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800227 addInstantAppLPw(userId, ps.appId);
228 }
229
230 // Remove the in-memory state
231 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
232 state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
233 userId);
234
235 // Remove the on-disk state except the cookie
236 File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
237 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
238 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
239
240 // If app signature changed - wipe the cookie
241 File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
242 if (currentCookieFile == null) {
243 continue;
244 }
245 File expectedCookeFile = computeInstantCookieFile(pkg, userId);
246 if (!currentCookieFile.equals(expectedCookeFile)) {
247 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
248 + " changed - dropping cookie");
Svet Ganov312c6cc2017-02-17 20:48:24 -0800249 // Make sure a pending write for the old signed app is cancelled
250 mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800251 currentCookieFile.delete();
252 }
253 }
254 }
255
256 public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
257 @NonNull int[] userIds) {
258 PackageSetting ps = (PackageSetting) pkg.mExtras;
259 if (ps == null) {
260 return;
261 }
262
263 for (int userId : userIds) {
264 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
265 continue;
266 }
267
Todd Kennedybe0b8892017-02-15 14:13:52 -0800268 if (ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800269 // Add a record for an uninstalled instant app
270 addUninstalledInstantAppLPw(pkg, userId);
271 removeInstantAppLPw(userId, ps.appId);
272 } else {
273 // Deleting an app prunes all instant state such as cookie
274 deleteDir(getInstantApplicationDir(pkg.packageName, userId));
275 removeAppLPw(userId, ps.appId);
276 }
277 }
278 }
279
280 public void onUserRemovedLPw(int userId) {
281 if (mUninstalledInstantApps != null) {
282 mUninstalledInstantApps.remove(userId);
283 if (mUninstalledInstantApps.size() <= 0) {
284 mUninstalledInstantApps = null;
285 }
286 }
287 if (mInstalledInstantAppUids != null) {
288 mInstalledInstantAppUids.remove(userId);
289 if (mInstalledInstantAppUids.size() <= 0) {
290 mInstalledInstantAppUids = null;
291 }
292 }
293 if (mInstantGrants != null) {
294 mInstantGrants.remove(userId);
295 if (mInstantGrants.size() <= 0) {
296 mInstantGrants = null;
297 }
298 }
299 deleteDir(getInstantApplicationsDir(userId));
300 }
301
302 public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
303 int instantAppId) {
304 if (mInstantGrants == null) {
305 return false;
306 }
307 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
308 if (targetAppList == null) {
309 return false;
310 }
311 final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
312 if (instantGrantList == null) {
313 return false;
314 }
315 return instantGrantList.get(instantAppId);
316 }
317
318 public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
319 int targetAppId, int instantAppId) {
320 if (mInstalledInstantAppUids == null) {
321 return; // no instant apps installed; no need to grant
322 }
323 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
324 if (instantAppList == null || !instantAppList.get(instantAppId)) {
325 return; // instant app id isn't installed; no need to grant
326 }
327 if (instantAppList.get(targetAppId)) {
328 return; // target app id is an instant app; no need to grant
329 }
330 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
331 final Set<String> categories = intent.getCategories();
332 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
333 return; // launched via VIEW/BROWSABLE intent; no need to grant
334 }
335 }
336 if (mInstantGrants == null) {
337 mInstantGrants = new SparseArray<>();
338 }
339 SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
340 if (targetAppList == null) {
341 targetAppList = new SparseArray<>();
342 mInstantGrants.put(userId, targetAppList);
343 }
344 SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
345 if (instantGrantList == null) {
346 instantGrantList = new SparseBooleanArray();
347 targetAppList.put(targetAppId, instantGrantList);
348 }
349 instantGrantList.put(instantAppId, true /*granted*/);
350 }
351
352 public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
353 if (mInstalledInstantAppUids == null) {
354 mInstalledInstantAppUids = new SparseArray<>();
355 }
356 SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
357 if (instantAppList == null) {
358 instantAppList = new SparseBooleanArray();
359 mInstalledInstantAppUids.put(userId, instantAppList);
360 }
361 instantAppList.put(instantAppId, true /*installed*/);
362 }
363
364 private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
365 // remove from the installed list
366 if (mInstalledInstantAppUids == null) {
367 return; // no instant apps on the system
368 }
369 final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
370 if (instantAppList == null) {
371 return;
372 }
373
374 instantAppList.delete(instantAppId);
375
376 // remove any grants
377 if (mInstantGrants == null) {
378 return; // no grants on the system
379 }
380 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
381 if (targetAppList == null) {
382 return; // no grants for this user
383 }
384 for (int i = targetAppList.size() - 1; i >= 0; --i) {
385 targetAppList.valueAt(i).delete(instantAppId);
386 }
387 }
388
389 private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
390 // remove from the installed list
391 if (mInstantGrants == null) {
392 return; // no grants on the system
393 }
394 final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
395 if (targetAppList == null) {
396 return; // no grants for this user
397 }
398 targetAppList.delete(targetAppId);
399 }
400
401 private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
402 @UserIdInt int userId) {
403 InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
404 pkg, userId, false);
405 if (uninstalledApp == null) {
406 return;
407 }
408 if (mUninstalledInstantApps == null) {
409 mUninstalledInstantApps = new SparseArray<>();
410 }
411 List<UninstalledInstantAppState> uninstalledAppStates =
412 mUninstalledInstantApps.get(userId);
413 if (uninstalledAppStates == null) {
414 uninstalledAppStates = new ArrayList<>();
415 mUninstalledInstantApps.put(userId, uninstalledAppStates);
416 }
417 UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
418 uninstalledApp, System.currentTimeMillis());
419 uninstalledAppStates.add(uninstalledAppState);
420
421 writeUninstalledInstantAppMetadata(uninstalledApp, userId);
422 writeInstantApplicationIconLPw(pkg, userId);
423 }
424
425 private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
426 @UserIdInt int userId) {
427 File appDir = getInstantApplicationDir(pkg.packageName, userId);
428 if (!appDir.exists()) {
429 return;
430 }
431
432 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
433
434 final Bitmap bitmap;
435 if (icon instanceof BitmapDrawable) {
436 bitmap = ((BitmapDrawable) icon).getBitmap();
437 } else {
438 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
439 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
440 Canvas canvas = new Canvas(bitmap);
Todd Kennedy03f336b2017-02-28 15:11:52 -0800441 icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800442 icon.draw(canvas);
443 }
444
445 File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
446 INSTANT_APP_ICON_FILE);
447
448 try (FileOutputStream out = new FileOutputStream(iconFile)) {
449 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
450 } catch (Exception e) {
451 Slog.e(LOG_TAG, "Error writing instant app icon", e);
452 }
453 }
454
455 public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
456 @UserIdInt int userId) {
457 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
458 state.mInstantAppInfo.getPackageName().equals(packageName),
459 userId);
460
461 File instantAppDir = getInstantApplicationDir(packageName, userId);
462 new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
463 new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
464 File cookie = peekInstantCookieFile(packageName, userId);
465 if (cookie != null) {
466 cookie.delete();
467 }
468 }
469
470 private void removeUninstalledInstantAppStateLPw(
471 @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
472 if (mUninstalledInstantApps == null) {
473 return;
474 }
475 List<UninstalledInstantAppState> uninstalledAppStates =
476 mUninstalledInstantApps.get(userId);
477 if (uninstalledAppStates == null) {
478 return;
479 }
480 final int appCount = uninstalledAppStates.size();
Todd Kennedy7a12f9f2017-01-31 12:49:28 -0800481 for (int i = appCount - 1; i >= 0; --i) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800482 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
483 if (!criteria.test(uninstalledAppState)) {
484 continue;
485 }
486 uninstalledAppStates.remove(i);
487 if (uninstalledAppStates.isEmpty()) {
488 mUninstalledInstantApps.remove(userId);
489 if (mUninstalledInstantApps.size() <= 0) {
490 mUninstalledInstantApps = null;
491 }
492 return;
493 }
494 }
495 }
496
497 public void pruneInstantAppsLPw() {
498 // For now we prune only state for uninstalled instant apps
499 final long maxCacheDurationMillis = Settings.Global.getLong(
500 mService.mContext.getContentResolver(),
501 Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS,
502 DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS);
503
504 for (int userId : UserManagerService.getInstance().getUserIds()) {
505 // Prune in-memory state
506 removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
507 final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
508 return (elapsedCachingMillis > maxCacheDurationMillis);
509 }, userId);
510
511 // Prune on-disk state
512 File instantAppsDir = getInstantApplicationsDir(userId);
513 if (!instantAppsDir.exists()) {
514 continue;
515 }
516 File[] files = instantAppsDir.listFiles();
517 if (files == null) {
518 continue;
519 }
520 for (File instantDir : files) {
521 if (!instantDir.isDirectory()) {
522 continue;
523 }
524
525 File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
526 if (!metadataFile.exists()) {
527 continue;
528 }
529
530 final long elapsedCachingMillis = System.currentTimeMillis()
531 - metadataFile.lastModified();
532 if (elapsedCachingMillis > maxCacheDurationMillis) {
533 deleteDir(instantDir);
534 }
535 }
536 }
537 }
538
539 private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
540 @UserIdInt int userId) {
541 List<InstantAppInfo> result = null;
542
543 final int packageCount = mService.mPackages.size();
544 for (int i = 0; i < packageCount; i++) {
Todd Kennedybe0b8892017-02-15 14:13:52 -0800545 final PackageParser.Package pkg = mService.mPackages.valueAt(i);
546 final PackageSetting ps = (PackageSetting) pkg.mExtras;
547 if (ps == null || !ps.getInstantApp(userId)) {
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800548 continue;
549 }
Todd Kennedybe0b8892017-02-15 14:13:52 -0800550 final InstantAppInfo info = createInstantAppInfoForPackage(
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800551 pkg, userId, true);
552 if (info == null) {
553 continue;
554 }
555 if (result == null) {
556 result = new ArrayList<>();
557 }
558 result.add(info);
559 }
560
561 return result;
562 }
563
564 private @NonNull
565 InstantAppInfo createInstantAppInfoForPackage(
566 @NonNull PackageParser.Package pkg, @UserIdInt int userId,
567 boolean addApplicationInfo) {
568 PackageSetting ps = (PackageSetting) pkg.mExtras;
569 if (ps == null) {
570 return null;
571 }
572 if (!ps.getInstalled(userId)) {
573 return null;
574 }
575
576 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
577 pkg.requestedPermissions.toArray(requestedPermissions);
578
579 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
580 String[] grantedPermissions = new String[permissions.size()];
581 permissions.toArray(grantedPermissions);
582
583 if (addApplicationInfo) {
584 return new InstantAppInfo(pkg.applicationInfo,
585 requestedPermissions, grantedPermissions);
586 } else {
587 return new InstantAppInfo(pkg.applicationInfo.packageName,
588 pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
589 requestedPermissions, grantedPermissions);
590 }
591 }
592
593 private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
594 @UserIdInt int userId) {
595 List<UninstalledInstantAppState> uninstalledAppStates =
596 getUninstalledInstantAppStatesLPr(userId);
597 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
598 return null;
599 }
600
601 List<InstantAppInfo> uninstalledApps = null;
602 final int stateCount = uninstalledAppStates.size();
603 for (int i = 0; i < stateCount; i++) {
604 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
605 if (uninstalledApps == null) {
606 uninstalledApps = new ArrayList<>();
607 }
608 uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
609 }
610 return uninstalledApps;
611 }
612
613 private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
614 @UserIdInt int userId) {
615 InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
616 packageName, userId);
617 if (appInfo == null) {
618 return;
619 }
620 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
621 return;
622 }
623 final long identity = Binder.clearCallingIdentity();
624 try {
625 for (String grantedPermission : appInfo.getGrantedPermissions()) {
626 BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
627 if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
628 mService.grantRuntimePermission(packageName, grantedPermission, userId);
629 }
630 }
631 } finally {
632 Binder.restoreCallingIdentity(identity);
633 }
634 }
635
636 private @NonNull
637 InstantAppInfo peekOrParseUninstalledInstantAppInfo(
638 @NonNull String packageName, @UserIdInt int userId) {
639 if (mUninstalledInstantApps != null) {
640 List<UninstalledInstantAppState> uninstalledAppStates =
641 mUninstalledInstantApps.get(userId);
642 if (uninstalledAppStates != null) {
643 final int appCount = uninstalledAppStates.size();
644 for (int i = 0; i < appCount; i++) {
645 UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
646 if (uninstalledAppState.mInstantAppInfo
647 .getPackageName().equals(packageName)) {
648 return uninstalledAppState.mInstantAppInfo;
649 }
650 }
651 }
652 }
653
654 File metadataFile = new File(getInstantApplicationDir(packageName, userId),
655 INSTANT_APP_METADATA_FILE);
656 UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
657 if (uninstalledAppState == null) {
658 return null;
659 }
660
661 return uninstalledAppState.mInstantAppInfo;
662 }
663
664 private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
665 @UserIdInt int userId) {
666 List<UninstalledInstantAppState> uninstalledAppStates = null;
667 if (mUninstalledInstantApps != null) {
668 uninstalledAppStates = mUninstalledInstantApps.get(userId);
669 if (uninstalledAppStates != null) {
670 return uninstalledAppStates;
671 }
672 }
673
674 File instantAppsDir = getInstantApplicationsDir(userId);
675 if (instantAppsDir.exists()) {
676 File[] files = instantAppsDir.listFiles();
677 if (files != null) {
678 for (File instantDir : files) {
679 if (!instantDir.isDirectory()) {
680 continue;
681 }
682 File metadataFile = new File(instantDir,
683 INSTANT_APP_METADATA_FILE);
684 UninstalledInstantAppState uninstalledAppState =
685 parseMetadataFile(metadataFile);
686 if (uninstalledAppState == null) {
687 continue;
688 }
689 if (uninstalledAppStates == null) {
690 uninstalledAppStates = new ArrayList<>();
691 }
692 uninstalledAppStates.add(uninstalledAppState);
693 }
694 }
695 }
696
697 if (uninstalledAppStates != null) {
698 if (mUninstalledInstantApps == null) {
699 mUninstalledInstantApps = new SparseArray<>();
700 }
701 mUninstalledInstantApps.put(userId, uninstalledAppStates);
702 }
703
704 return uninstalledAppStates;
705 }
706
707 private static @Nullable UninstalledInstantAppState parseMetadataFile(
708 @NonNull File metadataFile) {
709 if (!metadataFile.exists()) {
710 return null;
711 }
712 FileInputStream in;
713 try {
714 in = new AtomicFile(metadataFile).openRead();
715 } catch (FileNotFoundException fnfe) {
716 Slog.i(LOG_TAG, "No instant metadata file");
717 return null;
718 }
719
720 final File instantDir = metadataFile.getParentFile();
721 final long timestamp = metadataFile.lastModified();
722 final String packageName = instantDir.getName();
723
724 try {
725 XmlPullParser parser = Xml.newPullParser();
726 parser.setInput(in, StandardCharsets.UTF_8.name());
727 return new UninstalledInstantAppState(
728 parseMetadata(parser, packageName), timestamp);
729 } catch (XmlPullParserException | IOException e) {
730 throw new IllegalStateException("Failed parsing instant"
731 + " metadata file: " + metadataFile, e);
732 } finally {
733 IoUtils.closeQuietly(in);
734 }
735 }
736
737 private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
738 @UserIdInt int userId) {
739 File appDir = getInstantApplicationDir(pkg.packageName, userId);
740 String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
741 pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
742 return new File(appDir, cookieFile);
743 }
744
745 private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
746 @UserIdInt int userId) {
747 File appDir = getInstantApplicationDir(packageName, userId);
748 if (!appDir.exists()) {
749 return null;
750 }
751 File[] files = appDir.listFiles();
752 if (files == null) {
753 return null;
754 }
755 for (File file : files) {
756 if (!file.isDirectory()
757 && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
758 && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
759 return file;
760 }
761 }
762 return null;
763 }
764
765 private static @Nullable
766 InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
767 @NonNull String packageName)
768 throws IOException, XmlPullParserException {
769 final int outerDepth = parser.getDepth();
770 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
771 if (TAG_PACKAGE.equals(parser.getName())) {
772 return parsePackage(parser, packageName);
773 }
774 }
775 return null;
776 }
777
778 private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
779 @NonNull String packageName)
780 throws IOException, XmlPullParserException {
781 String label = parser.getAttributeValue(null, ATTR_LABEL);
782
783 List<String> outRequestedPermissions = new ArrayList<>();
784 List<String> outGrantedPermissions = new ArrayList<>();
785
786 final int outerDepth = parser.getDepth();
787 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
788 if (TAG_PERMISSIONS.equals(parser.getName())) {
789 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
790 }
791 }
792
793 String[] requestedPermissions = new String[outRequestedPermissions.size()];
794 outRequestedPermissions.toArray(requestedPermissions);
795
796 String[] grantedPermissions = new String[outGrantedPermissions.size()];
797 outGrantedPermissions.toArray(grantedPermissions);
798
799 return new InstantAppInfo(packageName, label,
800 requestedPermissions, grantedPermissions);
801 }
802
803 private static void parsePermissions(@NonNull XmlPullParser parser,
804 @NonNull List<String> outRequestedPermissions,
805 @NonNull List<String> outGrantedPermissions)
806 throws IOException, XmlPullParserException {
807 final int outerDepth = parser.getDepth();
808 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
809 if (TAG_PERMISSION.equals(parser.getName())) {
810 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
811 outRequestedPermissions.add(permission);
812 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
813 outGrantedPermissions.add(permission);
814 }
815 }
816 }
817 }
818
819 private void writeUninstalledInstantAppMetadata(
820 @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
821 File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
822 if (!appDir.exists() && !appDir.mkdirs()) {
823 return;
824 }
825
826 File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
827
828 AtomicFile destination = new AtomicFile(metadataFile);
829 FileOutputStream out = null;
830 try {
831 out = destination.startWrite();
832
833 XmlSerializer serializer = Xml.newSerializer();
834 serializer.setOutput(out, StandardCharsets.UTF_8.name());
835 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
836
837 serializer.startDocument(null, true);
838
839 serializer.startTag(null, TAG_PACKAGE);
840 serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
841 mService.mContext.getPackageManager()).toString());
842
843 serializer.startTag(null, TAG_PERMISSIONS);
844 for (String permission : instantApp.getRequestedPermissions()) {
845 serializer.startTag(null, TAG_PERMISSION);
846 serializer.attribute(null, ATTR_NAME, permission);
847 if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
848 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
849 }
850 serializer.endTag(null, TAG_PERMISSION);
851 }
852 serializer.endTag(null, TAG_PERMISSIONS);
853
854 serializer.endTag(null, TAG_PACKAGE);
855
856 serializer.endDocument();
857 destination.finishWrite(out);
858 } catch (Throwable t) {
859 Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
860 destination.failWrite(out);
861 } finally {
862 IoUtils.closeQuietly(out);
863 }
864 }
865
866 private static @NonNull File getInstantApplicationsDir(int userId) {
867 return new File(Environment.getUserSystemDirectory(userId),
868 INSTANT_APPS_FOLDER);
869 }
870
871 private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
872 return new File (getInstantApplicationsDir(userId), packageName);
873 }
874
875 private static void deleteDir(@NonNull File dir) {
876 File[] files = dir.listFiles();
877 if (files != null) {
878 for (File file : files) {
879 deleteDir(file);
880 }
881 }
882 dir.delete();
883 }
884
885 private static final class UninstalledInstantAppState {
886 final InstantAppInfo mInstantAppInfo;
887 final long mTimestamp;
888
889 public UninstalledInstantAppState(InstantAppInfo instantApp,
890 long timestamp) {
891 mInstantAppInfo = instantApp;
892 mTimestamp = timestamp;
893 }
894 }
895
896 private final class CookiePersistence extends Handler {
897 private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
898
899 // In case you wonder why we stash the cookies aside, we use
900 // the user id for the message id and the package for the payload.
901 // Handler allows removing messages by id and tag where the
Svet Ganovee2028c2017-02-17 17:23:29 -0800902 // tag is compared using ==. So to allow cancelling the
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800903 // pending persistence for an app under a given user we use
Svet Ganov312c6cc2017-02-17 20:48:24 -0800904 // the fact that package are cached by the system so the ==
905 // comparison would match and we end up with a way to cancel
906 // persisting the cookie for a user and package.
907 private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
908 = new SparseArray<>();
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800909
910 public CookiePersistence(Looper looper) {
911 super(looper);
912 }
913
Svet Ganov312c6cc2017-02-17 20:48:24 -0800914 public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
915 @NonNull byte[] cookie) {
916 File cookieFile = computeInstantCookieFile(pkg, userId);
917 cancelPendingPersistLPw(pkg, userId);
918 addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
919 sendMessageDelayed(obtainMessage(userId, pkg),
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800920 PERSIST_COOKIE_DELAY_MILLIS);
921 }
922
Svet Ganov312c6cc2017-02-17 20:48:24 -0800923 public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
924 @UserIdInt int userId) {
925 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
926 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800927 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800928 SomeArgs state = pendingWorkForUser.get(pkg);
929 if (state != null) {
930 return (byte[]) state.arg1;
931 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800932 }
933 return null;
934 }
935
Svet Ganov312c6cc2017-02-17 20:48:24 -0800936 public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
937 @UserIdInt int userId) {
938 removeMessages(userId, pkg);
939 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
940 if (state != null) {
941 state.recycle();
942 }
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800943 }
944
Svet Ganov312c6cc2017-02-17 20:48:24 -0800945 private void addPendingPersistCookieLPw(@UserIdInt int userId,
946 @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
947 @NonNull File cookieFile) {
948 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
949 mPendingPersistCookies.get(userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800950 if (pendingWorkForUser == null) {
951 pendingWorkForUser = new ArrayMap<>();
952 mPendingPersistCookies.put(userId, pendingWorkForUser);
953 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800954 SomeArgs args = SomeArgs.obtain();
955 args.arg1 = cookie;
956 args.arg2 = cookieFile;
957 pendingWorkForUser.put(pkg, args);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800958 }
959
Svet Ganov312c6cc2017-02-17 20:48:24 -0800960 private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
961 @UserIdInt int userId) {
962 ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
963 mPendingPersistCookies.get(userId);
964 SomeArgs state = null;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800965 if (pendingWorkForUser != null) {
Svet Ganov312c6cc2017-02-17 20:48:24 -0800966 state = pendingWorkForUser.remove(pkg);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800967 if (pendingWorkForUser.isEmpty()) {
968 mPendingPersistCookies.remove(userId);
969 }
970 }
Svet Ganov312c6cc2017-02-17 20:48:24 -0800971 return state;
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800972 }
973
974 @Override
975 public void handleMessage(Message message) {
976 int userId = message.what;
Svet Ganov312c6cc2017-02-17 20:48:24 -0800977 PackageParser.Package pkg = (PackageParser.Package) message.obj;
978 SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
979 if (state == null) {
980 return;
981 }
982 byte[] cookie = (byte[]) state.arg1;
983 File cookieFile = (File) state.arg2;
984 state.recycle();
985 persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
Svetoslav Ganov096d3042017-01-30 16:34:13 -0800986 }
987 }
988}