blob: 1e3e0caa1ece85ed791ca94d1a63190a6a9b4cb3 [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;
20import android.content.pm.EphemeralApplicationInfo;
21import android.content.pm.PackageParser;
22import android.content.pm.PackageUserState;
23import android.graphics.Bitmap;
24import android.graphics.BitmapFactory;
25import android.graphics.Canvas;
26import android.graphics.drawable.BitmapDrawable;
27import android.graphics.drawable.Drawable;
28import android.os.Binder;
29import android.os.Environment;
30import android.provider.Settings;
31import android.util.AtomicFile;
32import android.util.Slog;
33import android.util.SparseArray;
34import android.util.Xml;
35import com.android.internal.annotations.GuardedBy;
36import com.android.internal.util.ArrayUtils;
37import com.android.internal.util.XmlUtils;
38import libcore.io.IoUtils;
Svet Ganova7532cf2016-05-02 08:13:17 -070039import libcore.util.EmptyArray;
Svet Ganov2acf0632015-11-24 19:10:59 -080040import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.nio.charset.StandardCharsets;
50import java.security.MessageDigest;
51import java.security.NoSuchAlgorithmException;
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.List;
55import java.util.Set;
56
57/**
58 * This class is a part of the package manager service that is responsible
59 * for managing data associated with ephemeral apps such as cached uninstalled
60 * ephemeral apps and ephemeral apps' cookies.
61 */
62class EphemeralApplicationRegistry {
63 private static final boolean DEBUG = false;
64
Svet Ganova7532cf2016-05-02 08:13:17 -070065 private static final boolean ENABLED = false;
66
Svet Ganov2acf0632015-11-24 19:10:59 -080067 private static final String LOG_TAG = "EphemeralAppRegistry";
68
69 private static final long DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS =
70 DEBUG ? 60 * 1000L /* one min */ : 30 * 24 * 60 * 60 * 1000L; /* one month */
71
72 private final static char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
73
74 private static final String EPHEMERAL_APPS_FOLDER = "ephemeral";
75 private static final String EPHEMERAL_APP_ICON_FILE = "icon.png";
76 private static final String EPHEMERAL_APP_COOKIE_FILE_PREFIX = "cookie_";
77 private static final String EPHEMERAL_APP_COOKIE_FILE_SIFFIX = ".dat";
78 private static final String EPHEMERAL_APP_METADATA_FILE = "metadata.xml";
79
80 private static final String TAG_PACKAGE = "package";
81 private static final String TAG_PERMS = "perms";
82 private static final String TAG_PERM = "perm";
83
84 private static final String ATTR_LABEL = "label";
85 private static final String ATTR_NAME = "name";
86 private static final String ATTR_GRANTED = "granted";
87
88 private final PackageManagerService mService;
89
90 @GuardedBy("mService.mPackages")
91 private SparseArray<List<UninstalledEphemeralAppState>> mUninstalledEphemeralApps;
92
93 public EphemeralApplicationRegistry(PackageManagerService service) {
94 mService = service;
95 }
96
97 public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -070098 if (!ENABLED) {
99 return EmptyArray.BYTE;
100 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800101 pruneUninstalledEphemeralAppsLPw(userId);
102
103 File cookieFile = peekEphemeralCookieFile(packageName, userId);
104 if (cookieFile != null && cookieFile.exists()) {
105 try {
106 return IoUtils.readFileAsByteArray(cookieFile.toString());
107 } catch (IOException e) {
108 Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
109 }
110 }
111 return null;
112 }
113
114 public boolean setEphemeralApplicationCookieLPw(String packageName,
115 byte[] cookie, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700116 if (!ENABLED) {
117 return false;
118 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800119 pruneUninstalledEphemeralAppsLPw(userId);
120
121 PackageParser.Package pkg = mService.mPackages.get(packageName);
122 if (pkg == null) {
123 return false;
124 }
125
126 if (!isValidCookie(mService.mContext, cookie)) {
127 return false;
128 }
129
130 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
131 if (!appDir.exists() && !appDir.mkdirs()) {
132 return false;
133 }
134
135 File cookieFile = computeEphemeralCookieFile(pkg, userId);
136 if (cookieFile.exists() && !cookieFile.delete()) {
137 return false;
138 }
139
140 try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
141 fos.write(cookie, 0, cookie.length);
142 } catch (IOException e) {
143 Slog.w(LOG_TAG, "Error writing cookie file: " + cookieFile);
144 return false;
145 }
146 return true;
147 }
148
149 public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700150 if (!ENABLED) {
151 return null;
152 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800153 pruneUninstalledEphemeralAppsLPw(userId);
154
155 File iconFile = new File(getEphemeralApplicationDir(packageName, userId),
156 EPHEMERAL_APP_ICON_FILE);
157 if (iconFile.exists()) {
158 return BitmapFactory.decodeFile(iconFile.toString());
159 }
160 return null;
161 }
162
163 public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700164 if (!ENABLED) {
165 return Collections.emptyList();
166 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800167 pruneUninstalledEphemeralAppsLPw(userId);
168
169 List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
170 result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
171 return result;
172 }
173
174 public void onPackageInstalledLPw(PackageParser.Package pkg) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700175 if (!ENABLED) {
176 return;
177 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800178 PackageSetting ps = (PackageSetting) pkg.mExtras;
179 if (ps == null) {
180 return;
181 }
182 for (int userId : UserManagerService.getInstance().getUserIds()) {
183 pruneUninstalledEphemeralAppsLPw(userId);
184
185 // Ignore not installed apps
186 if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
187 continue;
188 }
189
190 // Propagate permissions before removing any state
191 propagateEphemeralAppPermissionsIfNeeded(pkg, userId);
192
193 // Remove the in-memory state
194 if (mUninstalledEphemeralApps != null) {
195 List<UninstalledEphemeralAppState> uninstalledAppStates =
196 mUninstalledEphemeralApps.get(userId);
197 if (uninstalledAppStates != null) {
198 final int appCount = uninstalledAppStates.size();
199 for (int i = 0; i < appCount; i++) {
200 UninstalledEphemeralAppState uninstalledAppState =
201 uninstalledAppStates.get(i);
202 if (uninstalledAppState.mEphemeralApplicationInfo
203 .getPackageName().equals(pkg.packageName)) {
204 uninstalledAppStates.remove(i);
205 break;
206 }
207 }
208 }
209 }
210
211 // Remove the on-disk state except the cookie
212 File ephemeralAppDir = getEphemeralApplicationDir(pkg.packageName, userId);
213 new File(ephemeralAppDir, EPHEMERAL_APP_METADATA_FILE).delete();
214 new File(ephemeralAppDir, EPHEMERAL_APP_ICON_FILE).delete();
215
216 // If app signature changed - wipe the cookie
217 File currentCookieFile = peekEphemeralCookieFile(pkg.packageName, userId);
218 if (currentCookieFile == null) {
219 continue;
220 }
221 File expectedCookeFile = computeEphemeralCookieFile(pkg, userId);
222 if (!currentCookieFile.equals(expectedCookeFile)) {
223 Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
224 + " changed - dropping cookie");
225 currentCookieFile.delete();
226 }
227 }
228 }
229
230 public void onPackageUninstalledLPw(PackageParser.Package pkg) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700231 if (!ENABLED) {
232 return;
233 }
Svet Ganov6ac25c52016-02-29 09:52:56 -0800234 if (pkg == null) {
235 return;
236 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800237 PackageSetting ps = (PackageSetting) pkg.mExtras;
238 if (ps == null) {
239 return;
240 }
241 for (int userId : UserManagerService.getInstance().getUserIds()) {
242 pruneUninstalledEphemeralAppsLPw(userId);
243
244 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
245 continue;
246 }
247
248 if (pkg.applicationInfo.isEphemeralApp()) {
249 // Add a record for an uninstalled ephemeral app
250 addUninstalledEphemeralAppLPw(pkg, userId);
251 } else {
252 // Deleting an app prunes all ephemeral state such as cookie
253 deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
254 }
255 }
256 }
257
258 public void onUserRemovedLPw(int userId) {
Svet Ganova7532cf2016-05-02 08:13:17 -0700259 if (!ENABLED) {
260 return;
261 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800262 if (mUninstalledEphemeralApps != null) {
263 mUninstalledEphemeralApps.remove(userId);
264 }
265 deleteDir(getEphemeralApplicationsDir(userId));
266 }
267
268 private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
269 EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
270 if (uninstalledApp == null) {
271 return;
272 }
273 if (mUninstalledEphemeralApps == null) {
274 mUninstalledEphemeralApps = new SparseArray<>();
275 }
276 List<UninstalledEphemeralAppState> uninstalledAppStates =
277 mUninstalledEphemeralApps.get(userId);
278 if (uninstalledAppStates == null) {
279 uninstalledAppStates = new ArrayList<>();
280 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
281 }
282 UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
283 uninstalledApp, System.currentTimeMillis());
284 uninstalledAppStates.add(uninstalledAppState);
285
286 writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
287 writeEphemeralApplicationIconLPw(pkg, userId);
288 }
289
290 private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
291 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
292 if (!appDir.exists()) {
293 return;
294 }
295
296 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
297
298 final Bitmap bitmap;
299 if (icon instanceof BitmapDrawable) {
300 bitmap = ((BitmapDrawable) icon).getBitmap();
301 } else {
302 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
303 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
304 Canvas canvas = new Canvas(bitmap);
305 icon.draw(canvas);
306 }
307
308 File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
309 EPHEMERAL_APP_ICON_FILE);
310
311 try (FileOutputStream out = new FileOutputStream(iconFile)) {
312 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
313 } catch (Exception e) {
314 Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
315 }
316 }
317
318 private void pruneUninstalledEphemeralAppsLPw(int userId) {
319 final long maxCacheDurationMillis = Settings.Global.getLong(
320 mService.mContext.getContentResolver(),
321 Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
322 DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
323
324 // Prune in-memory state
325 if (mUninstalledEphemeralApps != null) {
326 List<UninstalledEphemeralAppState> uninstalledAppStates =
327 mUninstalledEphemeralApps.get(userId);
328 if (uninstalledAppStates != null) {
329 final int appCount = uninstalledAppStates.size();
330 for (int j = appCount - 1; j >= 0; j--) {
331 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
332 final long elapsedCachingMillis = System.currentTimeMillis()
333 - uninstalledAppState.mTimestamp;
334 if (elapsedCachingMillis > maxCacheDurationMillis) {
335 uninstalledAppStates.remove(j);
336 }
337 }
338 if (uninstalledAppStates.isEmpty()) {
339 mUninstalledEphemeralApps.remove(userId);
340 }
341 }
342 }
343
344 // Prune on-disk state
345 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
346 if (!ephemeralAppsDir.exists()) {
347 return;
348 }
349 File[] files = ephemeralAppsDir.listFiles();
350 if (files == null) {
351 return;
352 }
353 for (File ephemeralDir : files) {
354 if (!ephemeralDir.isDirectory()) {
355 continue;
356 }
357
358 File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
359 if (!metadataFile.exists()) {
360 continue;
361 }
362
363 final long elapsedCachingMillis = System.currentTimeMillis()
364 - metadataFile.lastModified();
365 if (elapsedCachingMillis > maxCacheDurationMillis) {
366 deleteDir(ephemeralDir);
367 }
368 }
369 }
370
371 private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
372 List<EphemeralApplicationInfo> result = null;
373
374 final int packageCount = mService.mPackages.size();
375 for (int i = 0; i < packageCount; i++) {
376 PackageParser.Package pkg = mService.mPackages.valueAt(i);
377 if (!pkg.applicationInfo.isEphemeralApp()) {
378 continue;
379 }
380 EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
381 if (info == null) {
382 continue;
383 }
384 if (result == null) {
385 result = new ArrayList<>();
386 }
387 result.add(info);
388 }
389
390 return result;
391 }
392
393 private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
394 PackageParser.Package pkg, int userId) {
395 PackageSetting ps = (PackageSetting) pkg.mExtras;
396 if (ps == null) {
397 return null;
398 }
399 PackageUserState userState = ps.readUserState(userId);
400 if (userState == null || !userState.installed || userState.hidden) {
401 return null;
402 }
403
404 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
405 pkg.requestedPermissions.toArray(requestedPermissions);
406
407 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
408 String[] grantedPermissions = new String[permissions.size()];
409 permissions.toArray(grantedPermissions);
410
411 return new EphemeralApplicationInfo(pkg.applicationInfo,
412 requestedPermissions, grantedPermissions);
413 }
414
415 private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
416 List<UninstalledEphemeralAppState> uninstalledAppStates =
417 getUninstalledEphemeralAppStatesLPr(userId);
418 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
419 return Collections.emptyList();
420 }
421
422 List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
423 final int stateCount = uninstalledAppStates.size();
424 for (int i = 0; i < stateCount; i++) {
425 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
426 uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
427 }
428 return uninstalledApps;
429 }
430
431 private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
432 EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
433 if (appInfo == null) {
434 return;
435 }
436 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
437 return;
438 }
439 final long identity = Binder.clearCallingIdentity();
440 try {
441 for (String grantedPermission : appInfo.getGrantedPermissions()) {
442 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
443 }
444 } finally {
445 Binder.restoreCallingIdentity(identity);
446 }
447 }
448
449 private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
450 int userId) {
451 if (mUninstalledEphemeralApps != null) {
452 List<UninstalledEphemeralAppState> uninstalledAppStates =
453 mUninstalledEphemeralApps.get(userId);
454 if (uninstalledAppStates != null) {
455 final int appCount = uninstalledAppStates.size();
456 for (int i = 0; i < appCount; i++) {
457 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
458 if (uninstalledAppState.mEphemeralApplicationInfo
459 .getPackageName().equals(packageName)) {
460 return uninstalledAppState.mEphemeralApplicationInfo;
461 }
462 }
463 }
464 }
465
466 File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
467 EPHEMERAL_APP_METADATA_FILE);
468 UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
469 if (uninstalledAppState == null) {
470 return null;
471 }
472
473 return uninstalledAppState.mEphemeralApplicationInfo;
474 }
475
476 private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
477 List<UninstalledEphemeralAppState> uninstalledAppStates = null;
478 if (mUninstalledEphemeralApps != null) {
479 uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
480 if (uninstalledAppStates != null) {
481 return uninstalledAppStates;
482 }
483 }
484
485 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
486 if (ephemeralAppsDir.exists()) {
487 File[] files = ephemeralAppsDir.listFiles();
488 if (files != null) {
489 for (File ephemeralDir : files) {
490 if (!ephemeralDir.isDirectory()) {
491 continue;
492 }
493 File metadataFile = new File(ephemeralDir,
494 EPHEMERAL_APP_METADATA_FILE);
495 UninstalledEphemeralAppState uninstalledAppState =
496 parseMetadataFile(metadataFile);
497 if (uninstalledAppState == null) {
498 continue;
499 }
500 if (uninstalledAppStates == null) {
501 uninstalledAppStates = new ArrayList<>();
502 }
503 uninstalledAppStates.add(uninstalledAppState);
504 }
505 }
506 }
507
508 if (uninstalledAppStates != null) {
509 if (mUninstalledEphemeralApps == null) {
510 mUninstalledEphemeralApps = new SparseArray<>();
511 }
512 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
513 }
514
515 return uninstalledAppStates;
516 }
517
518 private static boolean isValidCookie(Context context, byte[] cookie) {
519 if (ArrayUtils.isEmpty(cookie)) {
520 return true;
521 }
522 return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
523 }
524
525 private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
526 if (!metadataFile.exists()) {
527 return null;
528 }
529 FileInputStream in;
530 try {
531 in = new AtomicFile(metadataFile).openRead();
532 } catch (FileNotFoundException fnfe) {
533 Slog.i(LOG_TAG, "No ephemeral metadata file");
534 return null;
535 }
536
537 final File ephemeralDir = metadataFile.getParentFile();
538 final long timestamp = metadataFile.lastModified();
539 final String packageName = ephemeralDir.getName();
540
541 try {
542 XmlPullParser parser = Xml.newPullParser();
543 parser.setInput(in, StandardCharsets.UTF_8.name());
544 return new UninstalledEphemeralAppState(
545 parseMetadata(parser, packageName), timestamp);
546 } catch (XmlPullParserException | IOException e) {
547 throw new IllegalStateException("Failed parsing ephemeral"
548 + " metadata file: " + metadataFile, e);
549 } finally {
550 IoUtils.closeQuietly(in);
551 }
552 }
553
554 private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
555 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
556 String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
557 + EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
558 return new File(appDir, cookieFile);
559 }
560
561 private static File peekEphemeralCookieFile(String packageName, int userId) {
562 File appDir = getEphemeralApplicationDir(packageName, userId);
563 if (!appDir.exists()) {
564 return null;
565 }
566 for (File file : appDir.listFiles()) {
567 if (!file.isDirectory()
568 && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
569 && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
570 return file;
571 }
572 }
573 return null;
574 }
575
576 private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
577 throws IOException, XmlPullParserException {
578 final int outerDepth = parser.getDepth();
579 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
580 if (TAG_PACKAGE.equals(parser.getName())) {
581 return parsePackage(parser, packageName);
582 }
583 }
584 return null;
585 }
586
587 private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
588 throws IOException, XmlPullParserException {
589 String label = parser.getAttributeValue(null, ATTR_LABEL);
590
591 List<String> outRequestedPermissions = new ArrayList<>();
592 List<String> outGrantedPermissions = new ArrayList<>();
593
594 final int outerDepth = parser.getDepth();
595 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
596 if (TAG_PERMS.equals(parser.getName())) {
597 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
598 }
599 }
600
601 String[] requestedPermissions = new String[outRequestedPermissions.size()];
602 outRequestedPermissions.toArray(requestedPermissions);
603
604 String[] grantedPermissions = new String[outGrantedPermissions.size()];
605 outGrantedPermissions.toArray(grantedPermissions);
606
607 return new EphemeralApplicationInfo(packageName, label,
608 requestedPermissions, grantedPermissions);
609 }
610
611 private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
612 List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
613 final int outerDepth = parser.getDepth();
614 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
615 if (TAG_PERM.equals(parser.getName())) {
616 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
617 outRequestedPermissions.add(permission);
618 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
619 outGrantedPermissions.add(permission);
620 }
621 }
622 }
623 }
624
625 private void writeUninstalledEphemeralAppMetadata(
626 EphemeralApplicationInfo ephemeralApp, int userId) {
627 File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
628 if (!appDir.exists() && !appDir.mkdirs()) {
629 return;
630 }
631
632 File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
633
634 AtomicFile destination = new AtomicFile(metadataFile);
635 FileOutputStream out = null;
636 try {
637 out = destination.startWrite();
638
639 XmlSerializer serializer = Xml.newSerializer();
640 serializer.setOutput(out, StandardCharsets.UTF_8.name());
641 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
642
643 serializer.startDocument(null, true);
644
645 serializer.startTag(null, TAG_PACKAGE);
646 serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
647 mService.mContext.getPackageManager()).toString());
648
649 serializer.startTag(null, TAG_PERMS);
650 for (String permission : ephemeralApp.getRequestedPermissions()) {
651 serializer.startTag(null, TAG_PERM);
652 serializer.attribute(null, ATTR_NAME, permission);
653 if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
654 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
655 }
656 serializer.endTag(null, TAG_PERM);
657 }
658 serializer.endTag(null, TAG_PERMS);
659
660 serializer.endTag(null, TAG_PACKAGE);
661
662 serializer.endDocument();
663 destination.finishWrite(out);
664 } catch (Throwable t) {
665 Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
666 destination.failWrite(out);
667 } finally {
668 IoUtils.closeQuietly(out);
669 }
670 }
671
672 private static String computePackageCertDigest(PackageParser.Package pkg) {
673 MessageDigest messageDigest;
674 try {
675 messageDigest = MessageDigest.getInstance("SHA256");
676 } catch (NoSuchAlgorithmException e) {
677 /* can't happen */
678 return null;
679 }
680
681 messageDigest.update(pkg.mSignatures[0].toByteArray());
682
683 final byte[] digest = messageDigest.digest();
684 final int digestLength = digest.length;
685 final int charCount = 2 * digestLength;
686
687 final char[] chars = new char[charCount];
688 for (int i = 0; i < digestLength; i++) {
689 final int byteHex = digest[i] & 0xFF;
690 chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
691 chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
692 }
693 return new String(chars);
694 }
695
696 private static File getEphemeralApplicationsDir(int userId) {
697 return new File(Environment.getUserSystemDirectory(userId),
698 EPHEMERAL_APPS_FOLDER);
699 }
700
701 private static File getEphemeralApplicationDir(String packageName, int userId) {
702 return new File (getEphemeralApplicationsDir(userId), packageName);
703 }
704
705 private static void deleteDir(File dir) {
706 File[] files = dir.listFiles();
707 if (files != null) {
708 for (File file : dir.listFiles()) {
709 deleteDir(file);
710 }
711 }
712 dir.delete();
713 }
714
715 private static final class UninstalledEphemeralAppState {
716 final EphemeralApplicationInfo mEphemeralApplicationInfo;
717 final long mTimestamp;
718
719 public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
720 long timestamp) {
721 mEphemeralApplicationInfo = ephemeralApp;
722 mTimestamp = timestamp;
723 }
724 }
725}