blob: 389e0a15bfb9c88179d59f3e8a57951dcb44d6aa [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) {
Svet Ganov6ac25c52016-02-29 09:52:56 -0800213 if (pkg == null) {
214 return;
215 }
Svet Ganov2acf0632015-11-24 19:10:59 -0800216 PackageSetting ps = (PackageSetting) pkg.mExtras;
217 if (ps == null) {
218 return;
219 }
220 for (int userId : UserManagerService.getInstance().getUserIds()) {
221 pruneUninstalledEphemeralAppsLPw(userId);
222
223 if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
224 continue;
225 }
226
227 if (pkg.applicationInfo.isEphemeralApp()) {
228 // Add a record for an uninstalled ephemeral app
229 addUninstalledEphemeralAppLPw(pkg, userId);
230 } else {
231 // Deleting an app prunes all ephemeral state such as cookie
232 deleteDir(getEphemeralApplicationDir(pkg.packageName, userId));
233 }
234 }
235 }
236
237 public void onUserRemovedLPw(int userId) {
238 if (mUninstalledEphemeralApps != null) {
239 mUninstalledEphemeralApps.remove(userId);
240 }
241 deleteDir(getEphemeralApplicationsDir(userId));
242 }
243
244 private void addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId) {
245 EphemeralApplicationInfo uninstalledApp = createEphemeralAppInfoForPackage(pkg, userId);
246 if (uninstalledApp == null) {
247 return;
248 }
249 if (mUninstalledEphemeralApps == null) {
250 mUninstalledEphemeralApps = new SparseArray<>();
251 }
252 List<UninstalledEphemeralAppState> uninstalledAppStates =
253 mUninstalledEphemeralApps.get(userId);
254 if (uninstalledAppStates == null) {
255 uninstalledAppStates = new ArrayList<>();
256 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
257 }
258 UninstalledEphemeralAppState uninstalledAppState = new UninstalledEphemeralAppState(
259 uninstalledApp, System.currentTimeMillis());
260 uninstalledAppStates.add(uninstalledAppState);
261
262 writeUninstalledEphemeralAppMetadata(uninstalledApp, userId);
263 writeEphemeralApplicationIconLPw(pkg, userId);
264 }
265
266 private void writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId) {
267 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
268 if (!appDir.exists()) {
269 return;
270 }
271
272 Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
273
274 final Bitmap bitmap;
275 if (icon instanceof BitmapDrawable) {
276 bitmap = ((BitmapDrawable) icon).getBitmap();
277 } else {
278 bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
279 icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
280 Canvas canvas = new Canvas(bitmap);
281 icon.draw(canvas);
282 }
283
284 File iconFile = new File(getEphemeralApplicationDir(pkg.packageName, userId),
285 EPHEMERAL_APP_ICON_FILE);
286
287 try (FileOutputStream out = new FileOutputStream(iconFile)) {
288 bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
289 } catch (Exception e) {
290 Slog.e(LOG_TAG, "Error writing ephemeral app icon", e);
291 }
292 }
293
294 private void pruneUninstalledEphemeralAppsLPw(int userId) {
295 final long maxCacheDurationMillis = Settings.Global.getLong(
296 mService.mContext.getContentResolver(),
297 Settings.Global.UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS,
298 DEFAULT_UNINSTALLED_EPHEMERAL_APP_CACHE_DURATION_MILLIS);
299
300 // Prune in-memory state
301 if (mUninstalledEphemeralApps != null) {
302 List<UninstalledEphemeralAppState> uninstalledAppStates =
303 mUninstalledEphemeralApps.get(userId);
304 if (uninstalledAppStates != null) {
305 final int appCount = uninstalledAppStates.size();
306 for (int j = appCount - 1; j >= 0; j--) {
307 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(j);
308 final long elapsedCachingMillis = System.currentTimeMillis()
309 - uninstalledAppState.mTimestamp;
310 if (elapsedCachingMillis > maxCacheDurationMillis) {
311 uninstalledAppStates.remove(j);
312 }
313 }
314 if (uninstalledAppStates.isEmpty()) {
315 mUninstalledEphemeralApps.remove(userId);
316 }
317 }
318 }
319
320 // Prune on-disk state
321 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
322 if (!ephemeralAppsDir.exists()) {
323 return;
324 }
325 File[] files = ephemeralAppsDir.listFiles();
326 if (files == null) {
327 return;
328 }
329 for (File ephemeralDir : files) {
330 if (!ephemeralDir.isDirectory()) {
331 continue;
332 }
333
334 File metadataFile = new File(ephemeralDir, EPHEMERAL_APP_METADATA_FILE);
335 if (!metadataFile.exists()) {
336 continue;
337 }
338
339 final long elapsedCachingMillis = System.currentTimeMillis()
340 - metadataFile.lastModified();
341 if (elapsedCachingMillis > maxCacheDurationMillis) {
342 deleteDir(ephemeralDir);
343 }
344 }
345 }
346
347 private List<EphemeralApplicationInfo> getInstalledEphemeralApplicationsLPr(int userId) {
348 List<EphemeralApplicationInfo> result = null;
349
350 final int packageCount = mService.mPackages.size();
351 for (int i = 0; i < packageCount; i++) {
352 PackageParser.Package pkg = mService.mPackages.valueAt(i);
353 if (!pkg.applicationInfo.isEphemeralApp()) {
354 continue;
355 }
356 EphemeralApplicationInfo info = createEphemeralAppInfoForPackage(pkg, userId);
357 if (info == null) {
358 continue;
359 }
360 if (result == null) {
361 result = new ArrayList<>();
362 }
363 result.add(info);
364 }
365
366 return result;
367 }
368
369 private EphemeralApplicationInfo createEphemeralAppInfoForPackage(
370 PackageParser.Package pkg, int userId) {
371 PackageSetting ps = (PackageSetting) pkg.mExtras;
372 if (ps == null) {
373 return null;
374 }
375 PackageUserState userState = ps.readUserState(userId);
376 if (userState == null || !userState.installed || userState.hidden) {
377 return null;
378 }
379
380 String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
381 pkg.requestedPermissions.toArray(requestedPermissions);
382
383 Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
384 String[] grantedPermissions = new String[permissions.size()];
385 permissions.toArray(grantedPermissions);
386
387 return new EphemeralApplicationInfo(pkg.applicationInfo,
388 requestedPermissions, grantedPermissions);
389 }
390
391 private List<EphemeralApplicationInfo> getUninstalledEphemeralApplicationsLPr(int userId) {
392 List<UninstalledEphemeralAppState> uninstalledAppStates =
393 getUninstalledEphemeralAppStatesLPr(userId);
394 if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
395 return Collections.emptyList();
396 }
397
398 List<EphemeralApplicationInfo> uninstalledApps = new ArrayList<>();
399 final int stateCount = uninstalledAppStates.size();
400 for (int i = 0; i < stateCount; i++) {
401 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
402 uninstalledApps.add(uninstalledAppState.mEphemeralApplicationInfo);
403 }
404 return uninstalledApps;
405 }
406
407 private void propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId) {
408 EphemeralApplicationInfo appInfo = getOrParseUninstalledEphemeralAppInfo(pkg.packageName, userId);
409 if (appInfo == null) {
410 return;
411 }
412 if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
413 return;
414 }
415 final long identity = Binder.clearCallingIdentity();
416 try {
417 for (String grantedPermission : appInfo.getGrantedPermissions()) {
418 mService.grantRuntimePermission(pkg.packageName, grantedPermission, userId);
419 }
420 } finally {
421 Binder.restoreCallingIdentity(identity);
422 }
423 }
424
425 private EphemeralApplicationInfo getOrParseUninstalledEphemeralAppInfo(String packageName,
426 int userId) {
427 if (mUninstalledEphemeralApps != null) {
428 List<UninstalledEphemeralAppState> uninstalledAppStates =
429 mUninstalledEphemeralApps.get(userId);
430 if (uninstalledAppStates != null) {
431 final int appCount = uninstalledAppStates.size();
432 for (int i = 0; i < appCount; i++) {
433 UninstalledEphemeralAppState uninstalledAppState = uninstalledAppStates.get(i);
434 if (uninstalledAppState.mEphemeralApplicationInfo
435 .getPackageName().equals(packageName)) {
436 return uninstalledAppState.mEphemeralApplicationInfo;
437 }
438 }
439 }
440 }
441
442 File metadataFile = new File(getEphemeralApplicationDir(packageName, userId),
443 EPHEMERAL_APP_METADATA_FILE);
444 UninstalledEphemeralAppState uninstalledAppState = parseMetadataFile(metadataFile);
445 if (uninstalledAppState == null) {
446 return null;
447 }
448
449 return uninstalledAppState.mEphemeralApplicationInfo;
450 }
451
452 private List<UninstalledEphemeralAppState> getUninstalledEphemeralAppStatesLPr(int userId) {
453 List<UninstalledEphemeralAppState> uninstalledAppStates = null;
454 if (mUninstalledEphemeralApps != null) {
455 uninstalledAppStates = mUninstalledEphemeralApps.get(userId);
456 if (uninstalledAppStates != null) {
457 return uninstalledAppStates;
458 }
459 }
460
461 File ephemeralAppsDir = getEphemeralApplicationsDir(userId);
462 if (ephemeralAppsDir.exists()) {
463 File[] files = ephemeralAppsDir.listFiles();
464 if (files != null) {
465 for (File ephemeralDir : files) {
466 if (!ephemeralDir.isDirectory()) {
467 continue;
468 }
469 File metadataFile = new File(ephemeralDir,
470 EPHEMERAL_APP_METADATA_FILE);
471 UninstalledEphemeralAppState uninstalledAppState =
472 parseMetadataFile(metadataFile);
473 if (uninstalledAppState == null) {
474 continue;
475 }
476 if (uninstalledAppStates == null) {
477 uninstalledAppStates = new ArrayList<>();
478 }
479 uninstalledAppStates.add(uninstalledAppState);
480 }
481 }
482 }
483
484 if (uninstalledAppStates != null) {
485 if (mUninstalledEphemeralApps == null) {
486 mUninstalledEphemeralApps = new SparseArray<>();
487 }
488 mUninstalledEphemeralApps.put(userId, uninstalledAppStates);
489 }
490
491 return uninstalledAppStates;
492 }
493
494 private static boolean isValidCookie(Context context, byte[] cookie) {
495 if (ArrayUtils.isEmpty(cookie)) {
496 return true;
497 }
498 return cookie.length <= context.getPackageManager().getEphemeralCookieMaxSizeBytes();
499 }
500
501 private static UninstalledEphemeralAppState parseMetadataFile(File metadataFile) {
502 if (!metadataFile.exists()) {
503 return null;
504 }
505 FileInputStream in;
506 try {
507 in = new AtomicFile(metadataFile).openRead();
508 } catch (FileNotFoundException fnfe) {
509 Slog.i(LOG_TAG, "No ephemeral metadata file");
510 return null;
511 }
512
513 final File ephemeralDir = metadataFile.getParentFile();
514 final long timestamp = metadataFile.lastModified();
515 final String packageName = ephemeralDir.getName();
516
517 try {
518 XmlPullParser parser = Xml.newPullParser();
519 parser.setInput(in, StandardCharsets.UTF_8.name());
520 return new UninstalledEphemeralAppState(
521 parseMetadata(parser, packageName), timestamp);
522 } catch (XmlPullParserException | IOException e) {
523 throw new IllegalStateException("Failed parsing ephemeral"
524 + " metadata file: " + metadataFile, e);
525 } finally {
526 IoUtils.closeQuietly(in);
527 }
528 }
529
530 private static File computeEphemeralCookieFile(PackageParser.Package pkg, int userId) {
531 File appDir = getEphemeralApplicationDir(pkg.packageName, userId);
532 String cookieFile = EPHEMERAL_APP_COOKIE_FILE_PREFIX + computePackageCertDigest(pkg)
533 + EPHEMERAL_APP_COOKIE_FILE_SIFFIX;
534 return new File(appDir, cookieFile);
535 }
536
537 private static File peekEphemeralCookieFile(String packageName, int userId) {
538 File appDir = getEphemeralApplicationDir(packageName, userId);
539 if (!appDir.exists()) {
540 return null;
541 }
542 for (File file : appDir.listFiles()) {
543 if (!file.isDirectory()
544 && file.getName().startsWith(EPHEMERAL_APP_COOKIE_FILE_PREFIX)
545 && file.getName().endsWith(EPHEMERAL_APP_COOKIE_FILE_SIFFIX)) {
546 return file;
547 }
548 }
549 return null;
550 }
551
552 private static EphemeralApplicationInfo parseMetadata(XmlPullParser parser, String packageName)
553 throws IOException, XmlPullParserException {
554 final int outerDepth = parser.getDepth();
555 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
556 if (TAG_PACKAGE.equals(parser.getName())) {
557 return parsePackage(parser, packageName);
558 }
559 }
560 return null;
561 }
562
563 private static EphemeralApplicationInfo parsePackage(XmlPullParser parser, String packageName)
564 throws IOException, XmlPullParserException {
565 String label = parser.getAttributeValue(null, ATTR_LABEL);
566
567 List<String> outRequestedPermissions = new ArrayList<>();
568 List<String> outGrantedPermissions = new ArrayList<>();
569
570 final int outerDepth = parser.getDepth();
571 while (XmlUtils.nextElementWithin(parser, outerDepth)) {
572 if (TAG_PERMS.equals(parser.getName())) {
573 parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
574 }
575 }
576
577 String[] requestedPermissions = new String[outRequestedPermissions.size()];
578 outRequestedPermissions.toArray(requestedPermissions);
579
580 String[] grantedPermissions = new String[outGrantedPermissions.size()];
581 outGrantedPermissions.toArray(grantedPermissions);
582
583 return new EphemeralApplicationInfo(packageName, label,
584 requestedPermissions, grantedPermissions);
585 }
586
587 private static void parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions,
588 List<String> outGrantedPermissions) throws IOException, XmlPullParserException {
589 final int outerDepth = parser.getDepth();
590 while (XmlUtils.nextElementWithin(parser,outerDepth)) {
591 if (TAG_PERM.equals(parser.getName())) {
592 String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
593 outRequestedPermissions.add(permission);
594 if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
595 outGrantedPermissions.add(permission);
596 }
597 }
598 }
599 }
600
601 private void writeUninstalledEphemeralAppMetadata(
602 EphemeralApplicationInfo ephemeralApp, int userId) {
603 File appDir = getEphemeralApplicationDir(ephemeralApp.getPackageName(), userId);
604 if (!appDir.exists() && !appDir.mkdirs()) {
605 return;
606 }
607
608 File metadataFile = new File(appDir, EPHEMERAL_APP_METADATA_FILE);
609
610 AtomicFile destination = new AtomicFile(metadataFile);
611 FileOutputStream out = null;
612 try {
613 out = destination.startWrite();
614
615 XmlSerializer serializer = Xml.newSerializer();
616 serializer.setOutput(out, StandardCharsets.UTF_8.name());
617 serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
618
619 serializer.startDocument(null, true);
620
621 serializer.startTag(null, TAG_PACKAGE);
622 serializer.attribute(null, ATTR_LABEL, ephemeralApp.loadLabel(
623 mService.mContext.getPackageManager()).toString());
624
625 serializer.startTag(null, TAG_PERMS);
626 for (String permission : ephemeralApp.getRequestedPermissions()) {
627 serializer.startTag(null, TAG_PERM);
628 serializer.attribute(null, ATTR_NAME, permission);
629 if (ArrayUtils.contains(ephemeralApp.getGrantedPermissions(), permission)) {
630 serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
631 }
632 serializer.endTag(null, TAG_PERM);
633 }
634 serializer.endTag(null, TAG_PERMS);
635
636 serializer.endTag(null, TAG_PACKAGE);
637
638 serializer.endDocument();
639 destination.finishWrite(out);
640 } catch (Throwable t) {
641 Slog.wtf(LOG_TAG, "Failed to write ephemeral state, restoring backup", t);
642 destination.failWrite(out);
643 } finally {
644 IoUtils.closeQuietly(out);
645 }
646 }
647
648 private static String computePackageCertDigest(PackageParser.Package pkg) {
649 MessageDigest messageDigest;
650 try {
651 messageDigest = MessageDigest.getInstance("SHA256");
652 } catch (NoSuchAlgorithmException e) {
653 /* can't happen */
654 return null;
655 }
656
657 messageDigest.update(pkg.mSignatures[0].toByteArray());
658
659 final byte[] digest = messageDigest.digest();
660 final int digestLength = digest.length;
661 final int charCount = 2 * digestLength;
662
663 final char[] chars = new char[charCount];
664 for (int i = 0; i < digestLength; i++) {
665 final int byteHex = digest[i] & 0xFF;
666 chars[i * 2] = HEX_ARRAY[byteHex >>> 4];
667 chars[i * 2 + 1] = HEX_ARRAY[byteHex & 0x0F];
668 }
669 return new String(chars);
670 }
671
672 private static File getEphemeralApplicationsDir(int userId) {
673 return new File(Environment.getUserSystemDirectory(userId),
674 EPHEMERAL_APPS_FOLDER);
675 }
676
677 private static File getEphemeralApplicationDir(String packageName, int userId) {
678 return new File (getEphemeralApplicationsDir(userId), packageName);
679 }
680
681 private static void deleteDir(File dir) {
682 File[] files = dir.listFiles();
683 if (files != null) {
684 for (File file : dir.listFiles()) {
685 deleteDir(file);
686 }
687 }
688 dir.delete();
689 }
690
691 private static final class UninstalledEphemeralAppState {
692 final EphemeralApplicationInfo mEphemeralApplicationInfo;
693 final long mTimestamp;
694
695 public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
696 long timestamp) {
697 mEphemeralApplicationInfo = ephemeralApp;
698 mTimestamp = timestamp;
699 }
700 }
701}