blob: e8be6294891556cbe61f5ca5787f28f0d15c9e02 [file] [log] [blame]
Svet Ganov2acf0632015-11-24 19:10:59 -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.content.Context;
Todd Kennedy0e989d02017-01-13 14:15:36 -080020import android.content.Intent;
Svet Ganov2acf0632015-11-24 19:10:59 -080021import android.content.pm.EphemeralApplicationInfo;
22import android.content.pm.PackageParser;
23import android.content.pm.PackageUserState;
24import android.graphics.Bitmap;
25import android.graphics.BitmapFactory;
26import android.graphics.Canvas;
27import android.graphics.drawable.BitmapDrawable;
28import android.graphics.drawable.Drawable;
29import android.os.Binder;
30import android.os.Environment;
Todd Kennedy0e989d02017-01-13 14:15:36 -080031import android.os.UserHandle;
Svet Ganov2acf0632015-11-24 19:10:59 -080032import android.provider.Settings;
33import android.util.AtomicFile;
34import android.util.Slog;
35import android.util.SparseArray;
Todd Kennedy0e989d02017-01-13 14:15:36 -080036import android.util.SparseBooleanArray;
37import android.util.SparseIntArray;
Svet Ganov2acf0632015-11-24 19:10:59 -080038import android.util.Xml;
39import com.android.internal.annotations.GuardedBy;
40import com.android.internal.util.ArrayUtils;
41import com.android.internal.util.XmlUtils;
42import libcore.io.IoUtils;
Svet Ganova7532cf2016-05-02 08:13:17 -070043import libcore.util.EmptyArray;
Svet Ganov2acf0632015-11-24 19:10:59 -080044import org.xmlpull.v1.XmlPullParser;
45import org.xmlpull.v1.XmlPullParserException;
46import org.xmlpull.v1.XmlSerializer;
47
48import java.io.File;
49import java.io.FileInputStream;
50import java.io.FileNotFoundException;
51import java.io.FileOutputStream;
52import java.io.IOException;
53import java.nio.charset.StandardCharsets;
54import java.security.MessageDigest;
55import java.security.NoSuchAlgorithmException;
56import java.util.ArrayList;
57import java.util.Collections;
Todd Kennedy0e989d02017-01-13 14:15:36 -080058import java.util.HashSet;
Svet Ganov2acf0632015-11-24 19:10:59 -080059import java.util.List;
60import java.util.Set;
61
62/**
63 * This class is a part of the package manager service that is responsible
64 * for managing data associated with ephemeral apps such as cached uninstalled
65 * ephemeral apps and ephemeral apps' cookies.
66 */
67class EphemeralApplicationRegistry {
68 private static final boolean DEBUG = false;
69
Todd Kennedy0e989d02017-01-13 14:15:36 -080070 private static final boolean ENABLED = true;
Svet Ganova7532cf2016-05-02 08:13:17 -070071
Svet Ganov2acf0632015-11-24 19:10:59 -080072 private static final String LOG_TAG = "EphemeralAppRegistry";
73
74 private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
75 DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */
76
77 private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
78
79 private static final String EPHEMERAL_APPS_FOLDER = "ephemeral";
80 private static final String EPHEMERAL_APP_ICON_FILE = "icon.png";
81 private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_";
82 private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat";
83 private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml";
84
85 private static final String TAG_PACKAGE = "package";
86 private static final String TAG_PERMS = "perms";
87 private static final String TAG_PERM = "perm";
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
95 @GuardedBy("mService.mPackages")
96 private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
97
Todd Kennedy0e989d02017-01-13 14:15:36 -080098 /**
99 * Automatic grants for access to instant app metadata.
100 * The key is the target application UID.
101 * The value is a set of instant app UIDs.
102 * UserID -> TargetAppId -> InstantAppId
103 */
104 private SparseArray<SparseArray<SparseBooleanArray>> mEphemeralGrants;
105 /** The set of all installed instant apps. UserID -> AppID */
106 private SparseArray<SparseBooleanArray> mInstalledEphemeralAppUids;
107
Svet Ganov2acf0632015-11-24 19:10:59 -0800108 public EphemeralApplicationRegistry(PackageManagerService service) {
109 mService = service;
110 }
111
112 public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700113 if (!ENABLED) {
114 return EmptyArray.BYTE;
115 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800116 pruneUninstalledEphemeralAppsLPw(userId);
117
118 File cookieFile = peekEphemeralCookieFile(packageName, userId);
119 if (cookieFile != null && cookieFile.exists()) {
120 try {
121 return IoUtils.readFileAsByteArray(cookieFile.toString());
122 } catch (IOException e) {
123 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
124 }
125 }
126 return null;
127 }
128
129 public boolean setEphemeralApplicationCookieLPw(String packageName,
130 byte[] cookie, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700131 if (!ENABLED) {
132 return false;
133 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800134 pruneUninstalledEphemeralAppsLPw(userId);
135
136 PackageParser.Package pkg = mService.mPackages.get(packageName);
137 if (pkg == null) {
138 return false;
139 }
140
141 if (!isValidCookie(mService.mContext, cookie)) {
142 return false;
143 }
144
145 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
146 if (!appDir.exists() && !appDir.mkdirs()) {
147 return false;
148 }
149
150 File cookieFile = computeEphemeralCookieFile(pkg, userId);
151 if (cookieFile.exists() && !cookieFile.delete()) {
152 return false;
153 }
154
155 try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
156 fos.write(cookie, 0, cookie.length);
157 } catch (IOException e) {
158 Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile);
159 return false;
160 }
161 return true;
162 }
163
164 public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700165 if (!ENABLED) {
166 return null;
167 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800168 pruneUninstalledEphemeralAppsLPw(userId);
169
170 File iconFile = new File(getEphemeralApplicationDir(packageName, userId),
171 EPHEMERAL_APP_ICON_FILE);
172 if (iconFile.exists()) {
173 return BitmapFactory.decodeFile(iconFile.toString());
174 }
175 return null;
176 }
177
178 public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700179 if (!ENABLED) {
180 return Collections.emptyList();
181 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800182 pruneUninstalledEphemeralAppsLPw(userId);
183
184 List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
185 result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
186 return result;
187 }
188
189 public void onPackageInstalledLPw(PackageParser.Package pkg) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700190 if (!ENABLED) {
191 return;
192 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800193 PackageSetting ps = (PackageSetting) pkg.mExtras;
194 if (ps == null) {
195 return;
196 }
197 for (int userId : UserManagerService.getInstance().getUserIds()) {
198 pruneUninstalledEphemeralAppsLPw(userId);
199
200 // Ignore not installed apps
201 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
202 continue;
203 }
204
205 // Propagate permissions before removing any state
206 propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
Todd Kennedy0e989d02017-01-13 14:15:36 -0800207 if (pkg.applicationInfo.isEphemeralApp()) {
208 addEphemeralAppLPw(userId, ps.appId);
209 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800210
211 // Remove the in-memory state
212 if (mUninstalledEphemeralApps != null) {
213 List<UninstalledEphemeralAppState> uninstalledAppStates =
214 mUninstalledEphemeralApps.get(userId);
215 if (uninstalledAppStates != null) {
216 final int appCount = uninstalledAppStates.size();
217 for (int i = 0; i < appCount; i++) {
218 UninstalledEphemeralAppState uninstalledAppState =
219 uninstalledAppStates.get(i);
220 if (uninstalledAppState.mEphemeralApplicationInfo
221 .getPackageName().equals(pkg.packageName)) {
222 uninstalledAppStates.remove(i);
223 break;
224 }
225 }
226 }
227 }
228
229 // Remove the on-disk state except the cookie
230 File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId);
231 new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete();
232 new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete();
233
234 // If app signature changed - wipe the cookie
235 File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId);
236 if (currentCookieFile == null) {
237 continue;
238 }
239 File expectedCookeFile = computeEphemeralCookieFile(pkg, userId);
240 if (!currentCookieFile.equals(expectedCookeFile)) {
241 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
242 + " changed - dropping cookie");
243 currentCookieFile.delete();
244 }
245 }
246 }
247
248 public void onPackageUninstalledLPw(PackageParser.Package pkg) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700249 if (!ENABLED) {
250 return;
251 }
Svet Ganov6ac25c52016-02-29 09:52:56 -0800252 if (pkg == null) {
253 return;
254 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800255 PackageSetting ps = (PackageSetting) pkg.mExtras;
256 if (ps == null) {
257 return;
258 }
259 for (int userId : UserManagerService.getInstance().getUserIds()) {
260 pruneUninstalledEphemeralAppsLPw(userId);
261
262 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
263 continue;
264 }
265
266 if (pkg.applicationInfo.isEphemeralApp()) {
267 // Add a record for an uninstalled ephemeral app
268 addUninstalledEphemeralAppLPw(pkg, userId);
Todd Kennedy0e989d02017-01-13 14:15:36 -0800269 removeEphemeralAppLPw(userId, ps.appId);
Svet Ganov2acf0632015-11-24 19:10:59 -0800270 } else {
271 // Deleting an app prunes all ephemeral state such as cookie
272 deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
Todd Kennedy0e989d02017-01-13 14:15:36 -0800273 removeAppLPw(userId, ps.appId);
Svet Ganov2acf0632015-11-24 19:10:59 -0800274 }
275 }
276 }
277
278 public void onUserRemovedLPw(int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700279 if (!ENABLED) {
280 return;
281 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800282 if (mUninstalledEphemeralApps != null) {
283 mUninstalledEphemeralApps.remove(userId);
284 }
Todd Kennedy0e989d02017-01-13 14:15:36 -0800285 if (mInstalledEphemeralAppUids != null) {
286 mInstalledEphemeralAppUids.remove(userId);
287 }
288 if (mEphemeralGrants != null) {
289 mEphemeralGrants.remove(userId);
290 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800291 deleteDir(getEphemeralApplicationsDir(userId));
292 }
293
Todd Kennedy0e989d02017-01-13 14:15:36 -0800294 public boolean isEphemeralAccessGranted(int userId, int targetAppId, int ephemeralAppId) {
295 if (mEphemeralGrants == null) {
296 return false;
297 }
298 final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
299 if (targetAppList == null) {
300 return false;
301 }
302 final SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
303 if (ephemeralGrantList == null) {
304 return false;
305 }
306 return ephemeralGrantList.get(ephemeralAppId);
307 }
308
309 public void grantEphemeralAccessLPw(int userId, Intent intent,
310 int targetAppId, int ephemeralAppId) {
311 if (mInstalledEphemeralAppUids == null) {
312 return; // no ephemeral apps installed; no need to grant
313 }
314 SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
315 if (ephemeralAppList == null || !ephemeralAppList.get(ephemeralAppId)) {
316 return; // ephemeral app id isn't installed; no need to grant
317 }
318 if (ephemeralAppList.get(targetAppId)) {
319 return; // target app id is an ephemeral app; no need to grant
320 }
321 if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
322 final Set<String> categories = intent.getCategories();
323 if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
324 return; // launched via VIEW/BROWSABLE intent; no need to grant
325 }
326 }
327 if (mEphemeralGrants == null) {
328 mEphemeralGrants = new SparseArray<>();
329 }
330 SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
331 if (targetAppList == null) {
332 targetAppList = new SparseArray<>();
333 mEphemeralGrants.put(userId, targetAppList);
334 }
335 SparseBooleanArray ephemeralGrantList = targetAppList.get(targetAppId);
336 if (ephemeralGrantList == null) {
337 ephemeralGrantList = new SparseBooleanArray();
338 targetAppList.put(targetAppId, ephemeralGrantList);
339 }
340 ephemeralGrantList.put(ephemeralAppId, true /*granted*/);
341 }
342
343 public void addEphemeralAppLPw(int userId, int ephemeralAppId) {
344 if (mInstalledEphemeralAppUids == null) {
345 mInstalledEphemeralAppUids = new SparseArray<>();
346 }
347 SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
348 if (ephemeralAppList == null) {
349 ephemeralAppList = new SparseBooleanArray();
350 mInstalledEphemeralAppUids.put(userId, ephemeralAppList);
351 }
352 ephemeralAppList.put(ephemeralAppId, true /*installed*/);
353 }
354
355 private void removeEphemeralAppLPw(int userId, int ephemeralAppId) {
356 // remove from the installed list
357 if (mInstalledEphemeralAppUids == null) {
358 return; // no ephemeral apps on the system
359 }
360 final SparseBooleanArray ephemeralAppList = mInstalledEphemeralAppUids.get(userId);
361 if (ephemeralAppList == null) {
362 Slog.w(LOG_TAG, "Remove ephemeral not in install list");
363 return;
364 } else {
365 ephemeralAppList.delete(ephemeralAppId);
366 }
367 // remove any grants
368 if (mEphemeralGrants == null) {
369 return; // no grants on the system
370 }
371 final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
372 if (targetAppList == null) {
373 return; // no grants for this user
374 }
375 final int numApps = targetAppList.size();
376 for (int i = targetAppList.size() - 1; i >= 0; --i) {
377 targetAppList.valueAt(i).delete(ephemeralAppId);
378 }
379 }
380
381 private void removeAppLPw(int userId, int targetAppId) {
382 // remove from the installed list
383 if (mEphemeralGrants == null) {
384 return; // no grants on the system
385 }
386 final SparseArray<SparseBooleanArray> targetAppList = mEphemeralGrants.get(userId);
387 if (targetAppList == null) {
388 return; // no grants for this user
389 }
390 targetAppList.delete(targetAppId);
391 }
392
Svet Ganov2acf0632015-11-24 19:10:59 -0800393 private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
394 EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
395 if (uninstalledApp == null) {
396 return;
397 }
398 if (mUninstalledEphemeralApps == null) {
399 mUninstalledEphemeralApps = new SparseArray<>();
400 }
401 List<UninstalledEphemeralAppState> uninstalledAppStates =
402 mUninstalledEphemeralApps.get(userId);
403 if (uninstalledAppStates == null) {
404 uninstalledAppStates = new ArrayList<>();
405 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
406 }
407 UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
408 uninstalledApp, System.currentTimeMillis());
409 uninstalledAppStates.add(uninstalledAppState);
410
411 writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
412 writeEphemeralApplicationIconLPw(pkg, userId);
413 }
414
415 private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
416 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
417 if (!appDir.exists()) {
418 return;
419 }
420
421 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
422
423 final Bitmap bitmap;
424 if (icon instanceof BitmapDrawable) {
425 bitmap = ((BitmapDrawable) icon).getBitmap();
426 } else {
427 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
428 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
429 Canvas canvas = new Canvas(bitmap);
430 icon.draw(canvas);
431 }
432
433 File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
434 EPHEMERAL_APP_ICON_FILE);
435
436 try (FileOutputStream out = new FileOutputStream(iconFile)) {
437 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
438 } catch (Exception e) {
439 Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
440 }
441 }
442
443 private void pruneUninstalledEphemeralAppsLPw(int userId) {
444 final long maxCacheDurationMillis = Settings.Global.getLong(
445 mService.mContext.getContentResolver(),
446 Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
447 DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
448
449 // Prune in-memory state
450 if (mUninstalledEphemeralApps != null) {
451 List<UninstalledEphemeralAppState> uninstalledAppStates =
452 mUninstalledEphemeralApps.get(userId);
453 if (uninstalledAppStates != null) {
454 final int appCount = uninstalledAppStates.size();
455 for (int j = appCount - 1; j >= 0; j--) {
456 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
457 final long elapsedCachingMillis = System.currentTimeMillis()
458 - uninstalledAppState.mTimestamp;
459 if (elapsedCachingMillis > maxCacheDurationMillis) {
460 uninstalledAppStates.remove(j);
461 }
462 }
463 if (uninstalledAppStates.isEmpty()) {
464 mUninstalledEphemeralApps.remove(userId);
465 }
466 }
467 }
468
469 // Prune on-disk state
470 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
471 if (!ephemeralAppsDir.exists()) {
472 return;
473 }
474 File[] files = ephemeralAppsDir.listFiles();
475 if (files == null) {
476 return;
477 }
478 for (File ephemeralDir : files) {
479 if (!ephemeralDir.isDirectory()) {
480 continue;
481 }
482
483 File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
484 if (!metadataFile.exists()) {
485 continue;
486 }
487
488 final long elapsedCachingMillis = System.currentTimeMillis()
489 - metadataFile.lastModified();
490 if (elapsedCachingMillis > maxCacheDurationMillis) {
491 deleteDir(ephemeralDir);
492 }
493 }
494 }
495
496 private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
497 List<EphemeralApplicationInfo> result = null;
498
499 final int packageCount = mService.mPackages.size();
500 for (int i = 0; i < packageCount; i++) {
501 PackageParser.Package pkg = mService.mPackages.valueAt(i);
502 if (!pkg.applicationInfo.isEphemeralApp()) {
503 continue;
504 }
505 EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
506 if (info == null) {
507 continue;
508 }
509 if (result == null) {
510 result = new ArrayList<>();
511 }
512 result.add(info);
513 }
514
515 return result;
516 }
517
518 private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
519 PackageParser.Package pkg, int userId) {
520 PackageSetting ps = (PackageSetting) pkg.mExtras;
521 if (ps == null) {
522 return null;
523 }
524 PackageUserState userState = ps.readUserState(userId);
525 if (userState == null || !userState.installed || userState.hidden) {
526 return null;
527 }
528
529 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
530 pkg.requestedPermissions.toArray(requestedPermissions);
531
532 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
533 String[] grantedPermissions = new String[permissions.size()];
534 permissions.toArray(grantedPermissions);
535
536 return new EphemeralApplicationInfo(pkg.applicationInfo,
537 requestedPermissions, grantedPermissions);
538 }
539
540 private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
541 List<UninstalledEphemeralAppState> uninstalledAppStates =
542 getUninstalledEphemeralAppStatesLPr(userId);
543 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
544 return Collections.emptyList();
545 }
546
547 List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
548 final int stateCount = uninstalledAppStates.size();
549 for (int i = 0; i < stateCount; i++) {
550 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
551 uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
552 }
553 return uninstalledApps;
554 }
555
556 private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
557 EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
558 if (appInfo == null) {
559 return;
560 }
561 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
562 return;
563 }
564 final long identity = Binder.clearCallingIdentity();
565 try {
566 for (String grantedPermission : appInfo.getGrantedPermissions()) {
567 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
568 }
569 } finally {
570 Binder.restoreCallingIdentity(identity);
571 }
572 }
573
574 private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
575 int userId) {
576 if (mUninstalledEphemeralApps != null) {
577 List<UninstalledEphemeralAppState> uninstalledAppStates =
578 mUninstalledEphemeralApps.get(userId);
579 if (uninstalledAppStates != null) {
580 final int appCount = uninstalledAppStates.size();
581 for (int i = 0; i < appCount; i++) {
582 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
583 if (uninstalledAppState.mEphemeralApplicationInfo
584 .getPackageName().equals(packageName)) {
585 return uninstalledAppState.mEphemeralApplicationInfo;
586 }
587 }
588 }
589 }
590
591 File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
592 EPHEMERAL_APP_METADATA_FILE);
593 UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
594 if (uninstalledAppState == null) {
595 return null;
596 }
597
598 return uninstalledAppState.mEphemeralApplicationInfo;
599 }
600
601 private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
602 List<UninstalledEphemeralAppState> uninstalledAppStates = null;
603 if (mUninstalledEphemeralApps != null) {
604 uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
605 if (uninstalledAppStates != null) {
606 return uninstalledAppStates;
607 }
608 }
609
610 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
611 if (ephemeralAppsDir.exists()) {
612 File[] files = ephemeralAppsDir.listFiles();
613 if (files != null) {
614 for (File ephemeralDir : files) {
615 if (!ephemeralDir.isDirectory()) {
616 continue;
617 }
618 File metadataFile = new File(ephemeralDir,
619 EPHEMERAL_APP_METADATA_FILE);
620 UninstalledEphemeralAppState uninstalledAppState =
621 parseMetadataFile(metadataFile);
622 if (uninstalledAppState == null) {
623 continue;
624 }
625 if (uninstalledAppStates == null) {
626 uninstalledAppStates = new ArrayList<>();
627 }
628 uninstalledAppStates.add(uninstalledAppState);
629 }
630 }
631 }
632
633 if (uninstalledAppStates != null) {
634 if (mUninstalledEphemeralApps == null) {
635 mUninstalledEphemeralApps = new SparseArray<>();
636 }
637 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
638 }
639
640 return uninstalledAppStates;
641 }
642
643 private static boolean isValidCookie(Context context, byte[] cookie) {
644 if (ArrayUtils.isEmpty(cookie)) {
645 return true;
646 }
647 return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
648 }
649
650 private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
651 if (!metadataFile.exists()) {
652 return null;
653 }
654 FileInputStream in;
655 try {
656 in = new AtomicFile(metadataFile).openRead();
657 } catch (FileNotFoundException fnfe) {
658 Slog.i(LOG_TAG, "No ephemeral metadata file");
659 return null;
660 }
661
662 final File ephemeralDir = metadataFile.getParentFile();
663 final long timestamp = metadataFile.lastModified();
664 final String packageName = ephemeralDir.getName();
665
666 try {
667 XmlPullParser parser = Xml.newPullParser();
668 parser.setInput(in, StandardCharsets.UTF_8.name());
669 return new UninstalledEphemeralAppState(
670 parseMetadata(parser, packageName), timestamp);
671 } catch (XmlPullParserException | IOException e) {
672 throw new IllegalStateException("Failed parsing ephemeral"
673 + " metadata file: " + metadataFile, e);
674 } finally {
675 IoUtils.closeQuietly(in);
676 }
677 }
678
679 private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
680 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
681 String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
682 + EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
683 return new File(appDir, cookieFile);
684 }
685
686 private static File peekEphemeralCookieFile(String packageName, int userId) {
687 File appDir = getEphemeralApplicationDir(packageName, userId);
688 if (!appDir.exists()) {
689 return null;
690 }
691 for (File file : appDir.listFiles()) {
692 if (!file.isDirectory()
693 && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
694 && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
695 return file;
696 }
697 }
698 return null;
699 }
700
701 private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
702 throws IOException, XmlPullParserException {
703 final int outerDepth = parser.getDepth();
704 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
705 if (TAG_PACKAGE.equals(parser.getName())) {
706 return parsePackage(parser, packageName);
707 }
708 }
709 return null;
710 }
711
712 private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
713 throws IOException, XmlPullParserException {
714 String label = parser.getAttributeValue(null, ATTR_LABEL);
715
716 List<String> outRequestedPermissions = new ArrayList<>();
717 List<String> outGrantedPermissions = new ArrayList<>();
718
719 final int outerDepth = parser.getDepth();
720 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
721 if (TAG_PERMS.equals(parser.getName())) {
722 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
723 }
724 }
725
726 String[] requestedPermissions = new String[outRequestedPermissions.size()];
727 outRequestedPermissions.toArray(requestedPermissions);
728
729 String[] grantedPermissions = new String[outGrantedPermissions.size()];
730 outGrantedPermissions.toArray(grantedPermissions);
731
732 return new EphemeralApplicationInfo(packageName, label,
733 requestedPermissions, grantedPermissions);
734 }
735
736 private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
737 List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
738 final int outerDepth = parser.getDepth();
739 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
740 if (TAG_PERM.equals(parser.getName())) {
741 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
742 outRequestedPermissions.add(permission);
743 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
744 outGrantedPermissions.add(permission);
745 }
746 }
747 }
748 }
749
750 private void writeUninstalledEphemeralAppMetadata(
751 EphemeralApplicationInfo ephemeralApp, int userId) {
752 File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
753 if (!appDir.exists() && !appDir.mkdirs()) {
754 return;
755 }
756
757 File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
758
759 AtomicFile destination = new AtomicFile(metadataFile);
760 FileOutputStream out = null;
761 try {
762 out = destination.startWrite();
763
764 XmlSerializer serializer = Xml.newSerializer();
765 serializer.setOutput(out, StandardCharsets.UTF_8.name());
766 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
767
768 serializer.startDocument(null, true);
769
770 serializer.startTag(null, TAG_PACKAGE);
771 serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
772 mService.mContext.getPackageManager()).toString());
773
774 serializer.startTag(null, TAG_PERMS);
775 for (String permission : ephemeralApp.getRequestedPermissions()) {
776 serializer.startTag(null, TAG_PERM);
777 serializer.attribute(null, ATTR_NAME, permission);
778 if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
779 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
780 }
781 serializer.endTag(null, TAG_PERM);
782 }
783 serializer.endTag(null, TAG_PERMS);
784
785 serializer.endTag(null, TAG_PACKAGE);
786
787 serializer.endDocument();
788 destination.finishWrite(out);
789 } catch (Throwable t) {
790 Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
791 destination.failWrite(out);
792 } finally {
793 IoUtils.closeQuietly(out);
794 }
795 }
796
797 private static String computePackageCertDigest(PackageParser.Package pkg) {
798 MessageDigest messageDigest;
799 try {
800 messageDigest = MessageDigest.getInstance("SHA256");
801 } catch (NoSuchAlgorithmException e) {
802 /* can't happen */
803 return null;
804 }
805
806 messageDigest.update(pkg.mSignatures[0].toByteArray());
807
808 final byte[] digest = messageDigest.digest();
809 final int digestLength = digest.length;
810 final int charCount = 2 * digestLength;
811
812 final char[] chars = new char[charCount];
813 for (int i = 0; i < digestLength; i++) {
814 final int byteHex = digest[i] & 0xFF;
815 chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
816 chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
817 }
818 return new String(chars);
819 }
820
821 private static File getEphemeralApplicationsDir(int userId) {
822 return new File(Environment.getUserSystemDirectory(userId),
823 EPHEMERAL_APPS_FOLDER);
824 }
825
826 private static File getEphemeralApplicationDir(String packageName, int userId) {
827 return new File (getEphemeralApplicationsDir(userId), packageName);
828 }
829
830 private static void deleteDir(File dir) {
831 File[] files = dir.listFiles();
832 if (files != null) {
833 for (File file : dir.listFiles()) {
834 deleteDir(file);
835 }
836 }
837 dir.delete();
838 }
839
840 private static final class UninstalledEphemeralAppState {
841 final EphemeralApplicationInfo mEphemeralApplicationInfo;
842 final long mTimestamp;
843
844 public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
845 long timestamp) {
846 mEphemeralApplicationInfo = ephemeralApp;
847 mTimestamp = timestamp;
848 }
849 }
850}