1 /*
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 
17 package com.android.server.pm;
18 
19 import android.content.Context;
20 import android.content.pm.EphemeralApplicationInfo;
21 import android.content.pm.PackageParser;
22 import android.content.pm.PackageUserState;
23 import android.graphics.Bitmap;
24 import android.graphics.BitmapFactory;
25 import android.graphics.Canvas;
26 import android.graphics.drawable.BitmapDrawable;
27 import android.graphics.drawable.Drawable;
28 import android.os.Binder;
29 import android.os.Environment;
30 import android.provider.Settings;
31 import android.util.AtomicFile;
32 import android.util.Slog;
33 import android.util.SparseArray;
34 import android.util.Xml;
35 import com.android.internal.annotations.GuardedBy;
36 import com.android.internal.util.ArrayUtils;
37 import com.android.internal.util.XmlUtils;
38 import libcore.io.IoUtils;
39 import libcore.util.EmptyArray;
40 import org.xmlpull.v1.XmlPullParser;
41 import org.xmlpull.v1.XmlPullParserException;
42 import org.xmlpull.v1.XmlSerializer;
43 
44 import java.io.File;
45 import java.io.FileInputStream;
46 import java.io.FileNotFoundException;
47 import java.io.FileOutputStream;
48 import java.io.IOException;
49 import java.nio.charset.StandardCharsets;
50 import java.security.MessageDigest;
51 import java.security.NoSuchAlgorithmException;
52 import java.util.ArrayList;
53 import java.util.Collections;
54 import java.util.List;
55 import 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  */
62 class EphemeralApplicationRegistry {
63     private static final boolean DEBUG = false;
64 
65     private static final boolean ENABLED = false;
66 
67     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 
EphemeralApplicationRegistry(PackageManagerService service)93     public EphemeralApplicationRegistry(PackageManagerService service) {
94         mService = service;
95     }
96 
getEphemeralApplicationCookieLPw(String packageName, int userId)97     public byte[] getEphemeralApplicationCookieLPw(String packageName, int userId) {
98         if (!ENABLED) {
99             return EmptyArray.BYTE;
100         }
101         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 
setEphemeralApplicationCookieLPw(String packageName, byte[] cookie, int userId)114     public boolean setEphemeralApplicationCookieLPw(String packageName,
115             byte[] cookie, int userId) {
116         if (!ENABLED) {
117             return false;
118         }
119         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 
getEphemeralApplicationIconLPw(String packageName, int userId)149     public Bitmap getEphemeralApplicationIconLPw(String packageName, int userId) {
150         if (!ENABLED) {
151             return null;
152         }
153         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 
getEphemeralApplicationsLPw(int userId)163     public List<EphemeralApplicationInfo> getEphemeralApplicationsLPw(int userId) {
164         if (!ENABLED) {
165             return Collections.emptyList();
166         }
167         pruneUninstalledEphemeralAppsLPw(userId);
168 
169         List<EphemeralApplicationInfo> result = getInstalledEphemeralApplicationsLPr(userId);
170         result.addAll(getUninstalledEphemeralApplicationsLPr(userId));
171         return result;
172     }
173 
onPackageInstalledLPw(PackageParser.Package pkg)174     public void onPackageInstalledLPw(PackageParser.Package pkg) {
175         if (!ENABLED) {
176             return;
177         }
178         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 
onPackageUninstalledLPw(PackageParser.Package pkg)230     public void onPackageUninstalledLPw(PackageParser.Package pkg) {
231         if (!ENABLED) {
232             return;
233         }
234         if (pkg == null) {
235             return;
236         }
237         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 
onUserRemovedLPw(int userId)258     public void onUserRemovedLPw(int userId) {
259         if (!ENABLED) {
260             return;
261         }
262         if (mUninstalledEphemeralApps != null) {
263             mUninstalledEphemeralApps.remove(userId);
264         }
265         deleteDir(getEphemeralApplicationsDir(userId));
266     }
267 
addUninstalledEphemeralAppLPw(PackageParser.Package pkg, int userId)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 
writeEphemeralApplicationIconLPw(PackageParser.Package pkg, int userId)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 
pruneUninstalledEphemeralAppsLPw(int userId)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 
getInstalledEphemeralApplicationsLPr(int userId)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 
createEphemeralAppInfoForPackage( PackageParser.Package pkg, int userId)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 
getUninstalledEphemeralApplicationsLPr(int userId)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 
propagateEphemeralAppPermissionsIfNeeded(PackageParser.Package pkg, int userId)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 
getOrParseUninstalledEphemeralAppInfo(String packageName, int userId)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 
getUninstalledEphemeralAppStatesLPr(int userId)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 
isValidCookie(Context context, byte[] cookie)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 
parseMetadataFile(File metadataFile)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 
computeEphemeralCookieFile(PackageParser.Package pkg, int userId)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 
peekEphemeralCookieFile(String packageName, int userId)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 
parseMetadata(XmlPullParser parser, String packageName)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 
parsePackage(XmlPullParser parser, String packageName)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 
parsePermissions(XmlPullParser parser, List<String> outRequestedPermissions, List<String> outGrantedPermissions)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 
writeUninstalledEphemeralAppMetadata( EphemeralApplicationInfo ephemeralApp, int userId)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 
computePackageCertDigest(PackageParser.Package pkg)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 
getEphemeralApplicationsDir(int userId)696     private static File getEphemeralApplicationsDir(int userId) {
697         return new File(Environment.getUserSystemDirectory(userId),
698                 EPHEMERAL_APPS_FOLDER);
699     }
700 
getEphemeralApplicationDir(String packageName, int userId)701     private static File getEphemeralApplicationDir(String packageName, int userId) {
702         return new File (getEphemeralApplicationsDir(userId), packageName);
703     }
704 
deleteDir(File dir)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 
UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp, long timestamp)719         public UninstalledEphemeralAppState(EphemeralApplicationInfo ephemeralApp,
720                 long timestamp) {
721             mEphemeralApplicationInfo = ephemeralApp;
722             mTimestamp = timestamp;
723         }
724     }
725 }
726