Home | History | Annotate | Download | only in pm
      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 
     93     public EphemeralApplicationRegistry(PackageManagerService service) {
     94         mService = service;
     95     }
     96 
     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 
    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 
    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 
    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 
    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 
    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 
    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 
    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 }
    726