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