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
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 libcore.util.EmptyArray;
40import org.xmlpull.v1.XmlPullParser;
41import org.xmlpull.v1.XmlPullParserException;
42import org.xmlpull.v1.XmlSerializer;
43
44import java.io.File;
45import java.io.FileInputStream;
46import java.io.FileNotFoundException;
47import java.io.FileOutputStream;
48import java.io.IOException;
49import java.nio.charset.StandardCharsets;
50import java.security.MessageDigest;
51import java.security.NoSuchAlgorithmException;
52import java.util.ArrayList;
53import java.util.Collections;
54import java.util.List;
55import java.util.Set;
56
57/**
58 * This class is a part of the package manager service that is responsible
59 * for managing data associated with ephemeral apps such as cached uninstalled
60 * ephemeral apps and ephemeral apps' cookies.
61 */
62class EphemeralApplicationRegistry {
63    private static final boolean DEBUG = false;
64
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