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