InstantAppRegistry.java revision 0d277a7b189c8807d142b69dd8d00b17978a49a5
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.annotation.NonNull;
20import android.annotation.Nullable;
21import android.annotation.UserIdInt;
22import android.content.Intent;
23import android.content.pm.InstantAppInfo;
24import android.content.pm.PackageParser;
25import android.graphics.Bitmap;
26import android.graphics.BitmapFactory;
27import android.graphics.Canvas;
28import android.graphics.drawable.BitmapDrawable;
29import android.graphics.drawable.Drawable;
30import android.os.Binder;
31import android.os.Environment;
32import android.os.Handler;
33import android.os.Looper;
34import android.os.Message;
35import android.provider.Settings;
36import android.util.ArrayMap;
37import android.util.AtomicFile;
38import android.util.ByteStringUtils;
39import android.util.PackageUtils;
40import android.util.Slog;
41import android.util.SparseArray;
42import android.util.SparseBooleanArray;
43import android.util.Xml;
44import com.android.internal.annotations.GuardedBy;
45import com.android.internal.os.BackgroundThread;
46import com.android.internal.os.SomeArgs;
47import com.android.internal.util.ArrayUtils;
48import com.android.internal.util.XmlUtils;
49import libcore.io.IoUtils;
50import org.xmlpull.v1.XmlPullParser;
51import org.xmlpull.v1.XmlPullParserException;
52import org.xmlpull.v1.XmlSerializer;
53
54import java.io.File;
55import java.io.FileInputStream;
56import java.io.FileNotFoundException;
57import java.io.FileOutputStream;
58import java.io.IOException;
59import java.nio.charset.StandardCharsets;
60import java.security.SecureRandom;
61import java.util.ArrayList;
62import java.util.List;
63import java.util.Locale;
64import java.util.Set;
65import java.util.function.Predicate;
66
67/**
68 * This class is a part of the package manager service that is responsible
69 * for managing data associated with instant apps such as cached uninstalled
70 * instant apps and instant apps' cookies. In addition it is responsible for
71 * pruning installed instant apps and meta-data for uninstalled instant apps
72 * when free space is needed.
73 */
74class InstantAppRegistry {
75    private static final boolean DEBUG = false;
76
77    private static final String LOG_TAG = "InstantAppRegistry";
78
79    private static final long DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS =
80            DEBUG ? 60 * 1000L /* one min */ : 6 * 30 * 24 * 60 * 60 * 1000L; /* six months */
81
82    private static final String INSTANT_APPS_FOLDER = "instant";
83    private static final String INSTANT_APP_ICON_FILE = "icon.png";
84    private static final String INSTANT_APP_COOKIE_FILE_PREFIX = "cookie_";
85    private static final String INSTANT_APP_COOKIE_FILE_SIFFIX = ".dat";
86    private static final String INSTANT_APP_METADATA_FILE = "metadata.xml";
87    private static final String INSTANT_APP_ANDROID_ID_FILE = "android_id";
88
89    private static final String TAG_PACKAGE = "package";
90    private static final String TAG_PERMISSIONS = "permissions";
91    private static final String TAG_PERMISSION = "permission";
92
93    private static final String ATTR_LABEL = "label";
94    private static final String ATTR_NAME = "name";
95    private static final String ATTR_GRANTED = "granted";
96
97    private final PackageManagerService mService;
98    private final CookiePersistence mCookiePersistence;
99
100    /** State for uninstalled instant apps */
101    @GuardedBy("mService.mPackages")
102    private SparseArray<List<UninstalledInstantAppState>> mUninstalledInstantApps;
103
104    /**
105     * Automatic grants for access to instant app metadata.
106     * The key is the target application UID.
107     * The value is a set of instant app UIDs.
108     * UserID -> TargetAppId -> InstantAppId
109     */
110    @GuardedBy("mService.mPackages")
111    private SparseArray<SparseArray<SparseBooleanArray>> mInstantGrants;
112
113    /** The set of all installed instant apps. UserID -> AppID */
114    @GuardedBy("mService.mPackages")
115    private SparseArray<SparseBooleanArray> mInstalledInstantAppUids;
116
117    public InstantAppRegistry(PackageManagerService service) {
118        mService = service;
119        mCookiePersistence = new CookiePersistence(BackgroundThread.getHandler().getLooper());
120    }
121
122    public byte[] getInstantAppCookieLPw(@NonNull String packageName,
123            @UserIdInt int userId) {
124        // Only installed packages can get their own cookie
125        PackageParser.Package pkg = mService.mPackages.get(packageName);
126        if (pkg == null) {
127            return null;
128        }
129
130        byte[] pendingCookie = mCookiePersistence.getPendingPersistCookieLPr(pkg, userId);
131        if (pendingCookie != null) {
132            return pendingCookie;
133        }
134        File cookieFile = peekInstantCookieFile(packageName, userId);
135        if (cookieFile != null && cookieFile.exists()) {
136            try {
137                return IoUtils.readFileAsByteArray(cookieFile.toString());
138            } catch (IOException e) {
139                Slog.w(LOG_TAG, "Error reading cookie file: " + cookieFile);
140            }
141        }
142        return null;
143    }
144
145    public boolean setInstantAppCookieLPw(@NonNull String packageName,
146            @Nullable byte[] cookie, @UserIdInt int userId) {
147        if (cookie != null && cookie.length > 0) {
148            final int maxCookieSize = mService.mContext.getPackageManager()
149                    .getInstantAppCookieMaxSize();
150            if (cookie.length > maxCookieSize) {
151                Slog.e(LOG_TAG, "Instant app cookie for package " + packageName + " size "
152                        + cookie.length + " bytes while max size is " + maxCookieSize);
153                return false;
154            }
155        }
156
157        // Only an installed package can set its own cookie
158        PackageParser.Package pkg = mService.mPackages.get(packageName);
159        if (pkg == null) {
160            return false;
161        }
162
163        mCookiePersistence.schedulePersistLPw(userId, pkg, cookie);
164        return true;
165    }
166
167    private void persistInstantApplicationCookie(@Nullable byte[] cookie,
168            @NonNull String packageName, @NonNull File cookieFile, @UserIdInt int userId) {
169        synchronized (mService.mPackages) {
170            File appDir = getInstantApplicationDir(packageName, userId);
171            if (!appDir.exists() && !appDir.mkdirs()) {
172                Slog.e(LOG_TAG, "Cannot create instant app cookie directory");
173                return;
174            }
175
176            if (cookieFile.exists() && !cookieFile.delete()) {
177                Slog.e(LOG_TAG, "Cannot delete instant app cookie file");
178            }
179
180            // No cookie or an empty one means delete - done
181            if (cookie == null || cookie.length <= 0) {
182                return;
183            }
184        }
185        try (FileOutputStream fos = new FileOutputStream(cookieFile)) {
186            fos.write(cookie, 0, cookie.length);
187        } catch (IOException e) {
188            Slog.e(LOG_TAG, "Error writing instant app cookie file: " + cookieFile, e);
189        }
190    }
191
192    public Bitmap getInstantAppIconLPw(@NonNull String packageName,
193                                       @UserIdInt int userId) {
194        File iconFile = new File(getInstantApplicationDir(packageName, userId),
195                INSTANT_APP_ICON_FILE);
196        if (iconFile.exists()) {
197            return BitmapFactory.decodeFile(iconFile.toString());
198        }
199        return null;
200    }
201
202    public String getInstantAppAndroidIdLPw(@NonNull String packageName,
203                                            @UserIdInt int userId) {
204        File idFile = new File(getInstantApplicationDir(packageName, userId),
205                INSTANT_APP_ANDROID_ID_FILE);
206        if (idFile.exists()) {
207            try {
208                return IoUtils.readFileAsString(idFile.getAbsolutePath());
209            } catch (IOException e) {
210                Slog.e(LOG_TAG, "Failed to read instant app android id file: " + idFile, e);
211            }
212        }
213        return generateInstantAppAndroidIdLPw(packageName, userId);
214    }
215
216    private String generateInstantAppAndroidIdLPw(@NonNull String packageName,
217                                                @UserIdInt int userId) {
218        byte[] randomBytes = new byte[8];
219        new SecureRandom().nextBytes(randomBytes);
220        String id = ByteStringUtils.toHexString(randomBytes).toLowerCase(Locale.US);
221        File idFile = new File(getInstantApplicationDir(packageName, userId),
222                INSTANT_APP_ANDROID_ID_FILE);
223        try (FileOutputStream fos = new FileOutputStream(idFile)) {
224            fos.write(id.getBytes());
225        } catch (IOException e) {
226            Slog.e(LOG_TAG, "Error writing instant app android id file: " + idFile, e);
227        }
228        return id;
229
230    }
231
232    public @Nullable List<InstantAppInfo> getInstantAppsLPr(@UserIdInt int userId) {
233        List<InstantAppInfo> installedApps = getInstalledInstantApplicationsLPr(userId);
234        List<InstantAppInfo> uninstalledApps = getUninstalledInstantApplicationsLPr(userId);
235        if (installedApps != null) {
236            if (uninstalledApps != null) {
237                installedApps.addAll(uninstalledApps);
238            }
239            return installedApps;
240        }
241        return uninstalledApps;
242    }
243
244    public void onPackageInstalledLPw(@NonNull PackageParser.Package pkg, @NonNull int[] userIds) {
245        PackageSetting ps = (PackageSetting) pkg.mExtras;
246        if (ps == null) {
247            return;
248        }
249
250        for (int userId : userIds) {
251            // Ignore not installed apps
252            if (mService.mPackages.get(pkg.packageName) == null || !ps.getInstalled(userId)) {
253                continue;
254            }
255
256            // Propagate permissions before removing any state
257            propagateInstantAppPermissionsIfNeeded(pkg.packageName, userId);
258
259            // Track instant apps
260            if (ps.getInstantApp(userId)) {
261                addInstantAppLPw(userId, ps.appId);
262            }
263
264            // Remove the in-memory state
265            removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
266                            state.mInstantAppInfo.getPackageName().equals(pkg.packageName),
267                    userId);
268
269            // Remove the on-disk state except the cookie
270            File instantAppDir = getInstantApplicationDir(pkg.packageName, userId);
271            new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
272            new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
273
274            // If app signature changed - wipe the cookie
275            File currentCookieFile = peekInstantCookieFile(pkg.packageName, userId);
276            if (currentCookieFile == null) {
277                continue;
278            }
279            File expectedCookeFile = computeInstantCookieFile(pkg, userId);
280            if (!currentCookieFile.equals(expectedCookeFile)) {
281                Slog.i(LOG_TAG, "Signature for package " + pkg.packageName
282                        + " changed - dropping cookie");
283                // Make sure a pending write for the old signed app is cancelled
284                mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
285                currentCookieFile.delete();
286            }
287        }
288    }
289
290    public void onPackageUninstalledLPw(@NonNull PackageParser.Package pkg,
291            @NonNull int[] userIds) {
292        PackageSetting ps = (PackageSetting) pkg.mExtras;
293        if (ps == null) {
294            return;
295        }
296
297        for (int userId : userIds) {
298            if (mService.mPackages.get(pkg.packageName) != null && ps.getInstalled(userId)) {
299                continue;
300            }
301
302            if (ps.getInstantApp(userId)) {
303                // Add a record for an uninstalled instant app
304                addUninstalledInstantAppLPw(pkg, userId);
305                removeInstantAppLPw(userId, ps.appId);
306            } else {
307                // Deleting an app prunes all instant state such as cookie
308                deleteDir(getInstantApplicationDir(pkg.packageName, userId));
309                mCookiePersistence.cancelPendingPersistLPw(pkg, userId);
310                removeAppLPw(userId, ps.appId);
311            }
312        }
313    }
314
315    public void onUserRemovedLPw(int userId) {
316        if (mUninstalledInstantApps != null) {
317            mUninstalledInstantApps.remove(userId);
318            if (mUninstalledInstantApps.size() <= 0) {
319                mUninstalledInstantApps = null;
320            }
321        }
322        if (mInstalledInstantAppUids != null) {
323            mInstalledInstantAppUids.remove(userId);
324            if (mInstalledInstantAppUids.size() <= 0) {
325                mInstalledInstantAppUids = null;
326            }
327        }
328        if (mInstantGrants != null) {
329            mInstantGrants.remove(userId);
330            if (mInstantGrants.size() <= 0) {
331                mInstantGrants = null;
332            }
333        }
334        deleteDir(getInstantApplicationsDir(userId));
335    }
336
337    public boolean isInstantAccessGranted(@UserIdInt int userId, int targetAppId,
338            int instantAppId) {
339        if (mInstantGrants == null) {
340            return false;
341        }
342        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
343        if (targetAppList == null) {
344            return false;
345        }
346        final SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
347        if (instantGrantList == null) {
348            return false;
349        }
350        return instantGrantList.get(instantAppId);
351    }
352
353    public void grantInstantAccessLPw(@UserIdInt int userId, @Nullable Intent intent,
354            int targetAppId, int instantAppId) {
355        if (mInstalledInstantAppUids == null) {
356            return;     // no instant apps installed; no need to grant
357        }
358        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
359        if (instantAppList == null || !instantAppList.get(instantAppId)) {
360            return;     // instant app id isn't installed; no need to grant
361        }
362        if (instantAppList.get(targetAppId)) {
363            return;     // target app id is an instant app; no need to grant
364        }
365        if (intent != null && Intent.ACTION_VIEW.equals(intent.getAction())) {
366            final Set<String> categories = intent.getCategories();
367            if (categories != null && categories.contains(Intent.CATEGORY_BROWSABLE)) {
368                return;  // launched via VIEW/BROWSABLE intent; no need to grant
369            }
370        }
371        if (mInstantGrants == null) {
372            mInstantGrants = new SparseArray<>();
373        }
374        SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
375        if (targetAppList == null) {
376            targetAppList = new SparseArray<>();
377            mInstantGrants.put(userId, targetAppList);
378        }
379        SparseBooleanArray instantGrantList = targetAppList.get(targetAppId);
380        if (instantGrantList == null) {
381            instantGrantList = new SparseBooleanArray();
382            targetAppList.put(targetAppId, instantGrantList);
383        }
384        instantGrantList.put(instantAppId, true /*granted*/);
385    }
386
387    public void addInstantAppLPw(@UserIdInt int userId, int instantAppId) {
388        if (mInstalledInstantAppUids == null) {
389            mInstalledInstantAppUids = new SparseArray<>();
390        }
391        SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
392        if (instantAppList == null) {
393            instantAppList = new SparseBooleanArray();
394            mInstalledInstantAppUids.put(userId, instantAppList);
395        }
396        instantAppList.put(instantAppId, true /*installed*/);
397    }
398
399    private void removeInstantAppLPw(@UserIdInt int userId, int instantAppId) {
400        // remove from the installed list
401        if (mInstalledInstantAppUids == null) {
402            return; // no instant apps on the system
403        }
404        final SparseBooleanArray instantAppList = mInstalledInstantAppUids.get(userId);
405        if (instantAppList == null) {
406            return;
407        }
408
409        instantAppList.delete(instantAppId);
410
411        // remove any grants
412        if (mInstantGrants == null) {
413            return; // no grants on the system
414        }
415        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
416        if (targetAppList == null) {
417            return; // no grants for this user
418        }
419        for (int i = targetAppList.size() - 1; i >= 0; --i) {
420            targetAppList.valueAt(i).delete(instantAppId);
421        }
422    }
423
424    private void removeAppLPw(@UserIdInt int userId, int targetAppId) {
425        // remove from the installed list
426        if (mInstantGrants == null) {
427            return; // no grants on the system
428        }
429        final SparseArray<SparseBooleanArray> targetAppList = mInstantGrants.get(userId);
430        if (targetAppList == null) {
431            return; // no grants for this user
432        }
433        targetAppList.delete(targetAppId);
434    }
435
436    private void addUninstalledInstantAppLPw(@NonNull PackageParser.Package pkg,
437            @UserIdInt int userId) {
438        InstantAppInfo uninstalledApp = createInstantAppInfoForPackage(
439                pkg, userId, false);
440        if (uninstalledApp == null) {
441            return;
442        }
443        if (mUninstalledInstantApps == null) {
444            mUninstalledInstantApps = new SparseArray<>();
445        }
446        List<UninstalledInstantAppState> uninstalledAppStates =
447                mUninstalledInstantApps.get(userId);
448        if (uninstalledAppStates == null) {
449            uninstalledAppStates = new ArrayList<>();
450            mUninstalledInstantApps.put(userId, uninstalledAppStates);
451        }
452        UninstalledInstantAppState uninstalledAppState = new UninstalledInstantAppState(
453                uninstalledApp, System.currentTimeMillis());
454        uninstalledAppStates.add(uninstalledAppState);
455
456        writeUninstalledInstantAppMetadata(uninstalledApp, userId);
457        writeInstantApplicationIconLPw(pkg, userId);
458    }
459
460    private void writeInstantApplicationIconLPw(@NonNull PackageParser.Package pkg,
461            @UserIdInt int userId) {
462        File appDir = getInstantApplicationDir(pkg.packageName, userId);
463        if (!appDir.exists()) {
464            return;
465        }
466
467        Drawable icon = pkg.applicationInfo.loadIcon(mService.mContext.getPackageManager());
468
469        final Bitmap bitmap;
470        if (icon instanceof BitmapDrawable) {
471            bitmap = ((BitmapDrawable) icon).getBitmap();
472        } else  {
473            bitmap = Bitmap.createBitmap(icon.getIntrinsicWidth(),
474                    icon.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);
475            Canvas canvas = new Canvas(bitmap);
476            icon.setBounds(0, 0, icon.getIntrinsicWidth(), icon.getIntrinsicHeight());
477            icon.draw(canvas);
478        }
479
480        File iconFile = new File(getInstantApplicationDir(pkg.packageName, userId),
481                INSTANT_APP_ICON_FILE);
482
483        try (FileOutputStream out = new FileOutputStream(iconFile)) {
484            bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
485        } catch (Exception e) {
486            Slog.e(LOG_TAG, "Error writing instant app icon", e);
487        }
488    }
489
490    public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
491            @UserIdInt int userId) {
492        removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
493                state.mInstantAppInfo.getPackageName().equals(packageName),
494                userId);
495
496        File instantAppDir = getInstantApplicationDir(packageName, userId);
497        new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
498        new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
499        new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
500        File cookie = peekInstantCookieFile(packageName, userId);
501        if (cookie != null) {
502            cookie.delete();
503        }
504    }
505
506    private void removeUninstalledInstantAppStateLPw(
507            @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
508        if (mUninstalledInstantApps == null) {
509            return;
510        }
511        List<UninstalledInstantAppState> uninstalledAppStates =
512                mUninstalledInstantApps.get(userId);
513        if (uninstalledAppStates == null) {
514            return;
515        }
516        final int appCount = uninstalledAppStates.size();
517        for (int i = appCount - 1; i >= 0; --i) {
518            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
519            if (!criteria.test(uninstalledAppState)) {
520                continue;
521            }
522            uninstalledAppStates.remove(i);
523            if (uninstalledAppStates.isEmpty()) {
524                mUninstalledInstantApps.remove(userId);
525                if (mUninstalledInstantApps.size() <= 0) {
526                    mUninstalledInstantApps = null;
527                }
528                return;
529            }
530        }
531    }
532
533    public void pruneInstantAppsLPw() {
534        // For now we prune only state for uninstalled instant apps
535        final long maxCacheDurationMillis = Settings.Global.getLong(
536                mService.mContext.getContentResolver(),
537                Settings.Global.UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS,
538                DEFAULT_UNINSTALLED_INSTANT_APP_CACHE_DURATION_MILLIS);
539
540        for (int userId : UserManagerService.getInstance().getUserIds()) {
541            // Prune in-memory state
542            removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
543                final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
544                return (elapsedCachingMillis > maxCacheDurationMillis);
545            }, userId);
546
547            // Prune on-disk state
548            File instantAppsDir = getInstantApplicationsDir(userId);
549            if (!instantAppsDir.exists()) {
550                continue;
551            }
552            File[] files = instantAppsDir.listFiles();
553            if (files == null) {
554                continue;
555            }
556            for (File instantDir : files) {
557                if (!instantDir.isDirectory()) {
558                    continue;
559                }
560
561                File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
562                if (!metadataFile.exists()) {
563                    continue;
564                }
565
566                final long elapsedCachingMillis = System.currentTimeMillis()
567                        - metadataFile.lastModified();
568                if (elapsedCachingMillis > maxCacheDurationMillis) {
569                    deleteDir(instantDir);
570                }
571            }
572        }
573    }
574
575    private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
576            @UserIdInt int userId) {
577        List<InstantAppInfo> result = null;
578
579        final int packageCount = mService.mPackages.size();
580        for (int i = 0; i < packageCount; i++) {
581            final PackageParser.Package pkg = mService.mPackages.valueAt(i);
582            final PackageSetting ps = (PackageSetting) pkg.mExtras;
583            if (ps == null || !ps.getInstantApp(userId)) {
584                continue;
585            }
586            final InstantAppInfo info = createInstantAppInfoForPackage(
587                    pkg, userId, true);
588            if (info == null) {
589                continue;
590            }
591            if (result == null) {
592                result = new ArrayList<>();
593            }
594            result.add(info);
595        }
596
597        return result;
598    }
599
600    private @NonNull
601    InstantAppInfo createInstantAppInfoForPackage(
602            @NonNull PackageParser.Package pkg, @UserIdInt int userId,
603            boolean addApplicationInfo) {
604        PackageSetting ps = (PackageSetting) pkg.mExtras;
605        if (ps == null) {
606            return null;
607        }
608        if (!ps.getInstalled(userId)) {
609            return null;
610        }
611
612        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
613        pkg.requestedPermissions.toArray(requestedPermissions);
614
615        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
616        String[] grantedPermissions = new String[permissions.size()];
617        permissions.toArray(grantedPermissions);
618
619        if (addApplicationInfo) {
620            return new InstantAppInfo(pkg.applicationInfo,
621                    requestedPermissions, grantedPermissions);
622        } else {
623            return new InstantAppInfo(pkg.applicationInfo.packageName,
624                    pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
625                    requestedPermissions, grantedPermissions);
626        }
627    }
628
629    private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
630            @UserIdInt int userId) {
631        List<UninstalledInstantAppState> uninstalledAppStates =
632                getUninstalledInstantAppStatesLPr(userId);
633        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
634            return null;
635        }
636
637        List<InstantAppInfo> uninstalledApps = null;
638        final int stateCount = uninstalledAppStates.size();
639        for (int i = 0; i < stateCount; i++) {
640            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
641            if (uninstalledApps == null) {
642                uninstalledApps = new ArrayList<>();
643            }
644            uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
645        }
646        return uninstalledApps;
647    }
648
649    private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
650            @UserIdInt int userId) {
651        InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
652                packageName, userId);
653        if (appInfo == null) {
654            return;
655        }
656        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
657            return;
658        }
659        final long identity = Binder.clearCallingIdentity();
660        try {
661            for (String grantedPermission : appInfo.getGrantedPermissions()) {
662                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
663                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
664                    mService.grantRuntimePermission(packageName, grantedPermission, userId);
665                }
666            }
667        } finally {
668            Binder.restoreCallingIdentity(identity);
669        }
670    }
671
672    private @NonNull
673    InstantAppInfo peekOrParseUninstalledInstantAppInfo(
674            @NonNull String packageName, @UserIdInt int userId) {
675        if (mUninstalledInstantApps != null) {
676            List<UninstalledInstantAppState> uninstalledAppStates =
677                    mUninstalledInstantApps.get(userId);
678            if (uninstalledAppStates != null) {
679                final int appCount = uninstalledAppStates.size();
680                for (int i = 0; i < appCount; i++) {
681                    UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
682                    if (uninstalledAppState.mInstantAppInfo
683                            .getPackageName().equals(packageName)) {
684                        return uninstalledAppState.mInstantAppInfo;
685                    }
686                }
687            }
688        }
689
690        File metadataFile = new File(getInstantApplicationDir(packageName, userId),
691                INSTANT_APP_METADATA_FILE);
692        UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
693        if (uninstalledAppState == null) {
694            return null;
695        }
696
697        return uninstalledAppState.mInstantAppInfo;
698    }
699
700    private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
701            @UserIdInt int userId) {
702        List<UninstalledInstantAppState> uninstalledAppStates = null;
703        if (mUninstalledInstantApps != null) {
704            uninstalledAppStates = mUninstalledInstantApps.get(userId);
705            if (uninstalledAppStates != null) {
706                return uninstalledAppStates;
707            }
708        }
709
710        File instantAppsDir = getInstantApplicationsDir(userId);
711        if (instantAppsDir.exists()) {
712            File[] files = instantAppsDir.listFiles();
713            if (files != null) {
714                for (File instantDir : files) {
715                    if (!instantDir.isDirectory()) {
716                        continue;
717                    }
718                    File metadataFile = new File(instantDir,
719                            INSTANT_APP_METADATA_FILE);
720                    UninstalledInstantAppState uninstalledAppState =
721                            parseMetadataFile(metadataFile);
722                    if (uninstalledAppState == null) {
723                        continue;
724                    }
725                    if (uninstalledAppStates == null) {
726                        uninstalledAppStates = new ArrayList<>();
727                    }
728                    uninstalledAppStates.add(uninstalledAppState);
729                }
730            }
731        }
732
733        if (uninstalledAppStates != null) {
734            if (mUninstalledInstantApps == null) {
735                mUninstalledInstantApps = new SparseArray<>();
736            }
737            mUninstalledInstantApps.put(userId, uninstalledAppStates);
738        }
739
740        return uninstalledAppStates;
741    }
742
743    private static @Nullable UninstalledInstantAppState parseMetadataFile(
744            @NonNull File metadataFile) {
745        if (!metadataFile.exists()) {
746            return null;
747        }
748        FileInputStream in;
749        try {
750            in = new AtomicFile(metadataFile).openRead();
751        } catch (FileNotFoundException fnfe) {
752            Slog.i(LOG_TAG, "No instant metadata file");
753            return null;
754        }
755
756        final File instantDir = metadataFile.getParentFile();
757        final long timestamp = metadataFile.lastModified();
758        final String packageName = instantDir.getName();
759
760        try {
761            XmlPullParser parser = Xml.newPullParser();
762            parser.setInput(in, StandardCharsets.UTF_8.name());
763            return new UninstalledInstantAppState(
764                    parseMetadata(parser, packageName), timestamp);
765        } catch (XmlPullParserException | IOException e) {
766            throw new IllegalStateException("Failed parsing instant"
767                    + " metadata file: " + metadataFile, e);
768        } finally {
769            IoUtils.closeQuietly(in);
770        }
771    }
772
773    private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
774            @UserIdInt int userId) {
775        File appDir = getInstantApplicationDir(pkg.packageName, userId);
776        String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
777                pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
778        return new File(appDir, cookieFile);
779    }
780
781    private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
782            @UserIdInt int userId) {
783        File appDir = getInstantApplicationDir(packageName, userId);
784        if (!appDir.exists()) {
785            return null;
786        }
787        File[] files = appDir.listFiles();
788        if (files == null) {
789            return null;
790        }
791        for (File file : files) {
792            if (!file.isDirectory()
793                    && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
794                    && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
795                return file;
796            }
797        }
798        return null;
799    }
800
801    private static @Nullable
802    InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
803                                 @NonNull String packageName)
804            throws IOException, XmlPullParserException {
805        final int outerDepth = parser.getDepth();
806        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
807            if (TAG_PACKAGE.equals(parser.getName())) {
808                return parsePackage(parser, packageName);
809            }
810        }
811        return null;
812    }
813
814    private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
815                                               @NonNull String packageName)
816            throws IOException, XmlPullParserException {
817        String label = parser.getAttributeValue(null, ATTR_LABEL);
818
819        List<String> outRequestedPermissions = new ArrayList<>();
820        List<String> outGrantedPermissions = new ArrayList<>();
821
822        final int outerDepth = parser.getDepth();
823        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
824            if (TAG_PERMISSIONS.equals(parser.getName())) {
825                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
826            }
827        }
828
829        String[] requestedPermissions = new String[outRequestedPermissions.size()];
830        outRequestedPermissions.toArray(requestedPermissions);
831
832        String[] grantedPermissions = new String[outGrantedPermissions.size()];
833        outGrantedPermissions.toArray(grantedPermissions);
834
835        return new InstantAppInfo(packageName, label,
836                requestedPermissions, grantedPermissions);
837    }
838
839    private static void parsePermissions(@NonNull XmlPullParser parser,
840            @NonNull List<String> outRequestedPermissions,
841            @NonNull List<String> outGrantedPermissions)
842            throws IOException, XmlPullParserException {
843        final int outerDepth = parser.getDepth();
844        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
845            if (TAG_PERMISSION.equals(parser.getName())) {
846                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
847                outRequestedPermissions.add(permission);
848                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
849                    outGrantedPermissions.add(permission);
850                }
851            }
852        }
853    }
854
855    private void writeUninstalledInstantAppMetadata(
856            @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
857        File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
858        if (!appDir.exists() && !appDir.mkdirs()) {
859            return;
860        }
861
862        File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
863
864        AtomicFile destination = new AtomicFile(metadataFile);
865        FileOutputStream out = null;
866        try {
867            out = destination.startWrite();
868
869            XmlSerializer serializer = Xml.newSerializer();
870            serializer.setOutput(out, StandardCharsets.UTF_8.name());
871            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
872
873            serializer.startDocument(null, true);
874
875            serializer.startTag(null, TAG_PACKAGE);
876            serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
877                    mService.mContext.getPackageManager()).toString());
878
879            serializer.startTag(null, TAG_PERMISSIONS);
880            for (String permission : instantApp.getRequestedPermissions()) {
881                serializer.startTag(null, TAG_PERMISSION);
882                serializer.attribute(null, ATTR_NAME, permission);
883                if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
884                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
885                }
886                serializer.endTag(null, TAG_PERMISSION);
887            }
888            serializer.endTag(null, TAG_PERMISSIONS);
889
890            serializer.endTag(null, TAG_PACKAGE);
891
892            serializer.endDocument();
893            destination.finishWrite(out);
894        } catch (Throwable t) {
895            Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
896            destination.failWrite(out);
897        } finally {
898            IoUtils.closeQuietly(out);
899        }
900    }
901
902    private static @NonNull File getInstantApplicationsDir(int userId) {
903        return new File(Environment.getUserSystemDirectory(userId),
904                INSTANT_APPS_FOLDER);
905    }
906
907    private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
908        return new File (getInstantApplicationsDir(userId), packageName);
909    }
910
911    private static void deleteDir(@NonNull File dir) {
912        File[] files = dir.listFiles();
913        if (files != null) {
914            for (File file : files) {
915                deleteDir(file);
916            }
917        }
918        dir.delete();
919    }
920
921    private static final class UninstalledInstantAppState {
922        final InstantAppInfo mInstantAppInfo;
923        final long mTimestamp;
924
925        public UninstalledInstantAppState(InstantAppInfo instantApp,
926                long timestamp) {
927            mInstantAppInfo = instantApp;
928            mTimestamp = timestamp;
929        }
930    }
931
932    private final class CookiePersistence extends Handler {
933        private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
934
935        // In case you wonder why we stash the cookies aside, we use
936        // the user id for the message id and the package for the payload.
937        // Handler allows removing messages by id and tag where the
938        // tag is compared using ==. So to allow cancelling the
939        // pending persistence for an app under a given user we use
940        // the fact that package are cached by the system so the ==
941        // comparison would match and we end up with a way to cancel
942        // persisting the cookie for a user and package.
943        private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
944                = new SparseArray<>();
945
946        public CookiePersistence(Looper looper) {
947            super(looper);
948        }
949
950        public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
951                @NonNull byte[] cookie) {
952            File cookieFile = computeInstantCookieFile(pkg, userId);
953            cancelPendingPersistLPw(pkg, userId);
954            addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
955            sendMessageDelayed(obtainMessage(userId, pkg),
956                    PERSIST_COOKIE_DELAY_MILLIS);
957        }
958
959        public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
960                @UserIdInt int userId) {
961            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
962                    mPendingPersistCookies.get(userId);
963            if (pendingWorkForUser != null) {
964                SomeArgs state = pendingWorkForUser.get(pkg);
965                if (state != null) {
966                    return (byte[]) state.arg1;
967                }
968            }
969            return null;
970        }
971
972        public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
973                @UserIdInt int userId) {
974            removeMessages(userId, pkg);
975            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
976            if (state != null) {
977                state.recycle();
978            }
979        }
980
981        private void addPendingPersistCookieLPw(@UserIdInt int userId,
982                @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
983                @NonNull File cookieFile) {
984            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
985                    mPendingPersistCookies.get(userId);
986            if (pendingWorkForUser == null) {
987                pendingWorkForUser = new ArrayMap<>();
988                mPendingPersistCookies.put(userId, pendingWorkForUser);
989            }
990            SomeArgs args = SomeArgs.obtain();
991            args.arg1 = cookie;
992            args.arg2 = cookieFile;
993            pendingWorkForUser.put(pkg, args);
994        }
995
996        private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
997                @UserIdInt int userId) {
998            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
999                    mPendingPersistCookies.get(userId);
1000            SomeArgs state = null;
1001            if (pendingWorkForUser != null) {
1002                state = pendingWorkForUser.remove(pkg);
1003                if (pendingWorkForUser.isEmpty()) {
1004                    mPendingPersistCookies.remove(userId);
1005                }
1006            }
1007            return state;
1008        }
1009
1010        @Override
1011        public void handleMessage(Message message) {
1012            int userId = message.what;
1013            PackageParser.Package pkg = (PackageParser.Package) message.obj;
1014            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1015            if (state == null) {
1016                return;
1017            }
1018            byte[] cookie = (byte[]) state.arg1;
1019            File cookieFile = (File) state.arg2;
1020            state.recycle();
1021            persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
1022        }
1023    }
1024}
1025