InstantAppRegistry.java revision f935a703985a921447df023d6f91dd0bbf967d9b
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    boolean hasInstantApplicationMetadataLPr(String packageName, int userId) {
508        return hasUninstalledInstantAppStateLPr(packageName, userId)
509                || hasInstantAppMetadataLPr(packageName, userId);
510    }
511
512    public void deleteInstantApplicationMetadataLPw(@NonNull String packageName,
513            @UserIdInt int userId) {
514        removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) ->
515                state.mInstantAppInfo.getPackageName().equals(packageName),
516                userId);
517
518        File instantAppDir = getInstantApplicationDir(packageName, userId);
519        new File(instantAppDir, INSTANT_APP_METADATA_FILE).delete();
520        new File(instantAppDir, INSTANT_APP_ICON_FILE).delete();
521        new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).delete();
522        File cookie = peekInstantCookieFile(packageName, userId);
523        if (cookie != null) {
524            cookie.delete();
525        }
526    }
527
528    private void removeUninstalledInstantAppStateLPw(
529            @NonNull Predicate<UninstalledInstantAppState> criteria, @UserIdInt int userId) {
530        if (mUninstalledInstantApps == null) {
531            return;
532        }
533        List<UninstalledInstantAppState> uninstalledAppStates =
534                mUninstalledInstantApps.get(userId);
535        if (uninstalledAppStates == null) {
536            return;
537        }
538        final int appCount = uninstalledAppStates.size();
539        for (int i = appCount - 1; i >= 0; --i) {
540            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
541            if (!criteria.test(uninstalledAppState)) {
542                continue;
543            }
544            uninstalledAppStates.remove(i);
545            if (uninstalledAppStates.isEmpty()) {
546                mUninstalledInstantApps.remove(userId);
547                if (mUninstalledInstantApps.size() <= 0) {
548                    mUninstalledInstantApps = null;
549                }
550                return;
551            }
552        }
553    }
554
555    private boolean hasUninstalledInstantAppStateLPr(String packageName, @UserIdInt int userId) {
556        if (mUninstalledInstantApps == null) {
557            return false;
558        }
559        final List<UninstalledInstantAppState> uninstalledAppStates =
560                mUninstalledInstantApps.get(userId);
561        if (uninstalledAppStates == null) {
562            return false;
563        }
564        final int appCount = uninstalledAppStates.size();
565        for (int i = 0; i < appCount; i++) {
566            final UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
567            if (packageName.equals(uninstalledAppState.mInstantAppInfo.getPackageName())) {
568                return true;
569            }
570        }
571        return false;
572    }
573
574    private boolean hasInstantAppMetadataLPr(String packageName, @UserIdInt int userId) {
575        final File instantAppDir = getInstantApplicationDir(packageName, userId);
576        return new File(instantAppDir, INSTANT_APP_METADATA_FILE).exists()
577                || new File(instantAppDir, INSTANT_APP_ICON_FILE).exists()
578                || new File(instantAppDir, INSTANT_APP_ANDROID_ID_FILE).exists()
579                || peekInstantCookieFile(packageName, userId) != null;
580    }
581
582    void pruneInstantApps() {
583        final long maxInstalledCacheDuration = Settings.Global.getLong(
584                mService.mContext.getContentResolver(),
585                Settings.Global.INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
586                DEFAULT_INSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
587
588        final long maxUninstalledCacheDuration = Settings.Global.getLong(
589                mService.mContext.getContentResolver(),
590                Settings.Global.UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD,
591                DEFAULT_UNINSTALLED_INSTANT_APP_MAX_CACHE_PERIOD);
592
593        try {
594            pruneInstantApps(Long.MAX_VALUE,
595                    maxInstalledCacheDuration, maxUninstalledCacheDuration);
596        } catch (IOException e) {
597            Slog.e(LOG_TAG, "Error pruning installed and uninstalled instant apps", e);
598        }
599    }
600
601    boolean pruneInstalledInstantApps(long neededSpace, long maxInstalledCacheDuration) {
602        try {
603            return pruneInstantApps(neededSpace, maxInstalledCacheDuration, Long.MAX_VALUE);
604        } catch (IOException e) {
605            Slog.e(LOG_TAG, "Error pruning installed instant apps", e);
606            return false;
607        }
608    }
609
610    boolean pruneUninstalledInstantApps(long neededSpace, long maxUninstalledCacheDuration) {
611        try {
612            return pruneInstantApps(neededSpace, Long.MAX_VALUE, maxUninstalledCacheDuration);
613        } catch (IOException e) {
614            Slog.e(LOG_TAG, "Error pruning uninstalled instant apps", e);
615            return false;
616        }
617    }
618
619    /**
620     * Prunes instant apps until there is enough <code>neededSpace</code>. Both
621     * installed and uninstalled instant apps are pruned that are older than
622     * <code>maxInstalledCacheDuration</code> and <code>maxUninstalledCacheDuration</code>
623     * respectively. All times are in milliseconds.
624     *
625     * @param neededSpace The space to ensure is free.
626     * @param maxInstalledCacheDuration The max duration for caching installed apps in millis.
627     * @param maxUninstalledCacheDuration The max duration for caching uninstalled apps in millis.
628     * @return Whether enough space was freed.
629     *
630     * @throws IOException
631     */
632    private boolean pruneInstantApps(long neededSpace, long maxInstalledCacheDuration,
633            long maxUninstalledCacheDuration) throws IOException {
634        final StorageManager storage = mService.mContext.getSystemService(StorageManager.class);
635        final File file = storage.findPathForUuid(StorageManager.UUID_PRIVATE_INTERNAL);
636
637        if (file.getUsableSpace() >= neededSpace) {
638            return true;
639        }
640
641        List<String> packagesToDelete = null;
642
643        final int[] allUsers;
644        final long now = System.currentTimeMillis();
645
646        // Prune first installed instant apps
647        synchronized (mService.mPackages) {
648            allUsers = PackageManagerService.sUserManager.getUserIds();
649
650            final int packageCount = mService.mPackages.size();
651            for (int i = 0; i < packageCount; i++) {
652                final PackageParser.Package pkg = mService.mPackages.valueAt(i);
653                if (now - pkg.getLatestPackageUseTimeInMills() < maxInstalledCacheDuration) {
654                    continue;
655                }
656                if (!(pkg.mExtras instanceof PackageSetting)) {
657                    continue;
658                }
659                final PackageSetting  ps = (PackageSetting) pkg.mExtras;
660                boolean installedOnlyAsInstantApp = false;
661                for (int userId : allUsers) {
662                    if (ps.getInstalled(userId)) {
663                        if (ps.getInstantApp(userId)) {
664                            installedOnlyAsInstantApp = true;
665                        } else {
666                            installedOnlyAsInstantApp = false;
667                            break;
668                        }
669                    }
670                }
671                if (installedOnlyAsInstantApp) {
672                    if (packagesToDelete == null) {
673                        packagesToDelete = new ArrayList<>();
674                    }
675                    packagesToDelete.add(pkg.packageName);
676                }
677            }
678
679            if (packagesToDelete != null) {
680                packagesToDelete.sort((String lhs, String rhs) -> {
681                    final PackageParser.Package lhsPkg = mService.mPackages.get(lhs);
682                    final PackageParser.Package rhsPkg = mService.mPackages.get(rhs);
683                    if (lhsPkg == null && rhsPkg == null) {
684                        return 0;
685                    } else if (lhsPkg == null) {
686                        return -1;
687                    } else if (rhsPkg == null) {
688                        return 1;
689                    } else {
690                        if (lhsPkg.getLatestPackageUseTimeInMills() >
691                                rhsPkg.getLatestPackageUseTimeInMills()) {
692                            return 1;
693                        } else if (lhsPkg.getLatestPackageUseTimeInMills() <
694                                rhsPkg.getLatestPackageUseTimeInMills()) {
695                            return -1;
696                        } else {
697                            if (lhsPkg.mExtras instanceof PackageSetting
698                                    && rhsPkg.mExtras instanceof PackageSetting) {
699                                final PackageSetting lhsPs = (PackageSetting) lhsPkg.mExtras;
700                                final PackageSetting rhsPs = (PackageSetting) rhsPkg.mExtras;
701                                if (lhsPs.firstInstallTime > rhsPs.firstInstallTime) {
702                                    return 1;
703                                } else {
704                                    return -1;
705                                }
706                            } else {
707                                return 0;
708                            }
709                        }
710                    }
711                });
712            }
713        }
714
715        if (packagesToDelete != null) {
716            final int packageCount = packagesToDelete.size();
717            for (int i = 0; i < packageCount; i++) {
718                final String packageToDelete = packagesToDelete.get(i);
719                if (mService.deletePackageX(packageToDelete, PackageManager.VERSION_CODE_HIGHEST,
720                        UserHandle.USER_SYSTEM, PackageManager.DELETE_ALL_USERS)
721                                == PackageManager.DELETE_SUCCEEDED) {
722                    if (file.getUsableSpace() >= neededSpace) {
723                        return true;
724                    }
725                }
726            }
727        }
728
729        // Prune uninstalled instant apps
730        synchronized (mService.mPackages) {
731            // TODO: Track last used time for uninstalled instant apps for better pruning
732            for (int userId : UserManagerService.getInstance().getUserIds()) {
733                // Prune in-memory state
734                removeUninstalledInstantAppStateLPw((UninstalledInstantAppState state) -> {
735                    final long elapsedCachingMillis = System.currentTimeMillis() - state.mTimestamp;
736                    return (elapsedCachingMillis > maxUninstalledCacheDuration);
737                }, userId);
738
739                // Prune on-disk state
740                File instantAppsDir = getInstantApplicationsDir(userId);
741                if (!instantAppsDir.exists()) {
742                    continue;
743                }
744                File[] files = instantAppsDir.listFiles();
745                if (files == null) {
746                    continue;
747                }
748                for (File instantDir : files) {
749                    if (!instantDir.isDirectory()) {
750                        continue;
751                    }
752
753                    File metadataFile = new File(instantDir, INSTANT_APP_METADATA_FILE);
754                    if (!metadataFile.exists()) {
755                        continue;
756                    }
757
758                    final long elapsedCachingMillis = System.currentTimeMillis()
759                            - metadataFile.lastModified();
760                    if (elapsedCachingMillis > maxUninstalledCacheDuration) {
761                        deleteDir(instantDir);
762                        if (file.getUsableSpace() >= neededSpace) {
763                            return true;
764                        }
765                    }
766                }
767            }
768        }
769
770        return false;
771    }
772
773    private @Nullable List<InstantAppInfo> getInstalledInstantApplicationsLPr(
774            @UserIdInt int userId) {
775        List<InstantAppInfo> result = null;
776
777        final int packageCount = mService.mPackages.size();
778        for (int i = 0; i < packageCount; i++) {
779            final PackageParser.Package pkg = mService.mPackages.valueAt(i);
780            final PackageSetting ps = (PackageSetting) pkg.mExtras;
781            if (ps == null || !ps.getInstantApp(userId)) {
782                continue;
783            }
784            final InstantAppInfo info = createInstantAppInfoForPackage(
785                    pkg, userId, true);
786            if (info == null) {
787                continue;
788            }
789            if (result == null) {
790                result = new ArrayList<>();
791            }
792            result.add(info);
793        }
794
795        return result;
796    }
797
798    private @NonNull
799    InstantAppInfo createInstantAppInfoForPackage(
800            @NonNull PackageParser.Package pkg, @UserIdInt int userId,
801            boolean addApplicationInfo) {
802        PackageSetting ps = (PackageSetting) pkg.mExtras;
803        if (ps == null) {
804            return null;
805        }
806        if (!ps.getInstalled(userId)) {
807            return null;
808        }
809
810        String[] requestedPermissions = new String[pkg.requestedPermissions.size()];
811        pkg.requestedPermissions.toArray(requestedPermissions);
812
813        Set<String> permissions = ps.getPermissionsState().getPermissions(userId);
814        String[] grantedPermissions = new String[permissions.size()];
815        permissions.toArray(grantedPermissions);
816
817        if (addApplicationInfo) {
818            return new InstantAppInfo(pkg.applicationInfo,
819                    requestedPermissions, grantedPermissions);
820        } else {
821            return new InstantAppInfo(pkg.applicationInfo.packageName,
822                    pkg.applicationInfo.loadLabel(mService.mContext.getPackageManager()),
823                    requestedPermissions, grantedPermissions);
824        }
825    }
826
827    private @Nullable List<InstantAppInfo> getUninstalledInstantApplicationsLPr(
828            @UserIdInt int userId) {
829        List<UninstalledInstantAppState> uninstalledAppStates =
830                getUninstalledInstantAppStatesLPr(userId);
831        if (uninstalledAppStates == null || uninstalledAppStates.isEmpty()) {
832            return null;
833        }
834
835        List<InstantAppInfo> uninstalledApps = null;
836        final int stateCount = uninstalledAppStates.size();
837        for (int i = 0; i < stateCount; i++) {
838            UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
839            if (uninstalledApps == null) {
840                uninstalledApps = new ArrayList<>();
841            }
842            uninstalledApps.add(uninstalledAppState.mInstantAppInfo);
843        }
844        return uninstalledApps;
845    }
846
847    private void propagateInstantAppPermissionsIfNeeded(@NonNull String packageName,
848            @UserIdInt int userId) {
849        InstantAppInfo appInfo = peekOrParseUninstalledInstantAppInfo(
850                packageName, userId);
851        if (appInfo == null) {
852            return;
853        }
854        if (ArrayUtils.isEmpty(appInfo.getGrantedPermissions())) {
855            return;
856        }
857        final long identity = Binder.clearCallingIdentity();
858        try {
859            for (String grantedPermission : appInfo.getGrantedPermissions()) {
860                BasePermission bp = mService.mSettings.mPermissions.get(grantedPermission);
861                if (bp != null && (bp.isRuntime() || bp.isDevelopment()) && bp.isInstant()) {
862                    mService.grantRuntimePermission(packageName, grantedPermission, userId);
863                }
864            }
865        } finally {
866            Binder.restoreCallingIdentity(identity);
867        }
868    }
869
870    private @NonNull
871    InstantAppInfo peekOrParseUninstalledInstantAppInfo(
872            @NonNull String packageName, @UserIdInt int userId) {
873        if (mUninstalledInstantApps != null) {
874            List<UninstalledInstantAppState> uninstalledAppStates =
875                    mUninstalledInstantApps.get(userId);
876            if (uninstalledAppStates != null) {
877                final int appCount = uninstalledAppStates.size();
878                for (int i = 0; i < appCount; i++) {
879                    UninstalledInstantAppState uninstalledAppState = uninstalledAppStates.get(i);
880                    if (uninstalledAppState.mInstantAppInfo
881                            .getPackageName().equals(packageName)) {
882                        return uninstalledAppState.mInstantAppInfo;
883                    }
884                }
885            }
886        }
887
888        File metadataFile = new File(getInstantApplicationDir(packageName, userId),
889                INSTANT_APP_METADATA_FILE);
890        UninstalledInstantAppState uninstalledAppState = parseMetadataFile(metadataFile);
891        if (uninstalledAppState == null) {
892            return null;
893        }
894
895        return uninstalledAppState.mInstantAppInfo;
896    }
897
898    private @Nullable List<UninstalledInstantAppState> getUninstalledInstantAppStatesLPr(
899            @UserIdInt int userId) {
900        List<UninstalledInstantAppState> uninstalledAppStates = null;
901        if (mUninstalledInstantApps != null) {
902            uninstalledAppStates = mUninstalledInstantApps.get(userId);
903            if (uninstalledAppStates != null) {
904                return uninstalledAppStates;
905            }
906        }
907
908        File instantAppsDir = getInstantApplicationsDir(userId);
909        if (instantAppsDir.exists()) {
910            File[] files = instantAppsDir.listFiles();
911            if (files != null) {
912                for (File instantDir : files) {
913                    if (!instantDir.isDirectory()) {
914                        continue;
915                    }
916                    File metadataFile = new File(instantDir,
917                            INSTANT_APP_METADATA_FILE);
918                    UninstalledInstantAppState uninstalledAppState =
919                            parseMetadataFile(metadataFile);
920                    if (uninstalledAppState == null) {
921                        continue;
922                    }
923                    if (uninstalledAppStates == null) {
924                        uninstalledAppStates = new ArrayList<>();
925                    }
926                    uninstalledAppStates.add(uninstalledAppState);
927                }
928            }
929        }
930
931        if (uninstalledAppStates != null) {
932            if (mUninstalledInstantApps == null) {
933                mUninstalledInstantApps = new SparseArray<>();
934            }
935            mUninstalledInstantApps.put(userId, uninstalledAppStates);
936        }
937
938        return uninstalledAppStates;
939    }
940
941    private static @Nullable UninstalledInstantAppState parseMetadataFile(
942            @NonNull File metadataFile) {
943        if (!metadataFile.exists()) {
944            return null;
945        }
946        FileInputStream in;
947        try {
948            in = new AtomicFile(metadataFile).openRead();
949        } catch (FileNotFoundException fnfe) {
950            Slog.i(LOG_TAG, "No instant metadata file");
951            return null;
952        }
953
954        final File instantDir = metadataFile.getParentFile();
955        final long timestamp = metadataFile.lastModified();
956        final String packageName = instantDir.getName();
957
958        try {
959            XmlPullParser parser = Xml.newPullParser();
960            parser.setInput(in, StandardCharsets.UTF_8.name());
961            return new UninstalledInstantAppState(
962                    parseMetadata(parser, packageName), timestamp);
963        } catch (XmlPullParserException | IOException e) {
964            throw new IllegalStateException("Failed parsing instant"
965                    + " metadata file: " + metadataFile, e);
966        } finally {
967            IoUtils.closeQuietly(in);
968        }
969    }
970
971    private static @NonNull File computeInstantCookieFile(@NonNull PackageParser.Package pkg,
972            @UserIdInt int userId) {
973        File appDir = getInstantApplicationDir(pkg.packageName, userId);
974        String cookieFile = INSTANT_APP_COOKIE_FILE_PREFIX + PackageUtils.computeSha256Digest(
975                pkg.mSignatures[0].toByteArray()) + INSTANT_APP_COOKIE_FILE_SIFFIX;
976        return new File(appDir, cookieFile);
977    }
978
979    private static @Nullable File peekInstantCookieFile(@NonNull String packageName,
980            @UserIdInt int userId) {
981        File appDir = getInstantApplicationDir(packageName, userId);
982        if (!appDir.exists()) {
983            return null;
984        }
985        File[] files = appDir.listFiles();
986        if (files == null) {
987            return null;
988        }
989        for (File file : files) {
990            if (!file.isDirectory()
991                    && file.getName().startsWith(INSTANT_APP_COOKIE_FILE_PREFIX)
992                    && file.getName().endsWith(INSTANT_APP_COOKIE_FILE_SIFFIX)) {
993                return file;
994            }
995        }
996        return null;
997    }
998
999    private static @Nullable
1000    InstantAppInfo parseMetadata(@NonNull XmlPullParser parser,
1001                                 @NonNull String packageName)
1002            throws IOException, XmlPullParserException {
1003        final int outerDepth = parser.getDepth();
1004        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1005            if (TAG_PACKAGE.equals(parser.getName())) {
1006                return parsePackage(parser, packageName);
1007            }
1008        }
1009        return null;
1010    }
1011
1012    private static InstantAppInfo parsePackage(@NonNull XmlPullParser parser,
1013                                               @NonNull String packageName)
1014            throws IOException, XmlPullParserException {
1015        String label = parser.getAttributeValue(null, ATTR_LABEL);
1016
1017        List<String> outRequestedPermissions = new ArrayList<>();
1018        List<String> outGrantedPermissions = new ArrayList<>();
1019
1020        final int outerDepth = parser.getDepth();
1021        while (XmlUtils.nextElementWithin(parser, outerDepth)) {
1022            if (TAG_PERMISSIONS.equals(parser.getName())) {
1023                parsePermissions(parser, outRequestedPermissions, outGrantedPermissions);
1024            }
1025        }
1026
1027        String[] requestedPermissions = new String[outRequestedPermissions.size()];
1028        outRequestedPermissions.toArray(requestedPermissions);
1029
1030        String[] grantedPermissions = new String[outGrantedPermissions.size()];
1031        outGrantedPermissions.toArray(grantedPermissions);
1032
1033        return new InstantAppInfo(packageName, label,
1034                requestedPermissions, grantedPermissions);
1035    }
1036
1037    private static void parsePermissions(@NonNull XmlPullParser parser,
1038            @NonNull List<String> outRequestedPermissions,
1039            @NonNull List<String> outGrantedPermissions)
1040            throws IOException, XmlPullParserException {
1041        final int outerDepth = parser.getDepth();
1042        while (XmlUtils.nextElementWithin(parser,outerDepth)) {
1043            if (TAG_PERMISSION.equals(parser.getName())) {
1044                String permission = XmlUtils.readStringAttribute(parser, ATTR_NAME);
1045                outRequestedPermissions.add(permission);
1046                if (XmlUtils.readBooleanAttribute(parser, ATTR_GRANTED)) {
1047                    outGrantedPermissions.add(permission);
1048                }
1049            }
1050        }
1051    }
1052
1053    private void writeUninstalledInstantAppMetadata(
1054            @NonNull InstantAppInfo instantApp, @UserIdInt int userId) {
1055        File appDir = getInstantApplicationDir(instantApp.getPackageName(), userId);
1056        if (!appDir.exists() && !appDir.mkdirs()) {
1057            return;
1058        }
1059
1060        File metadataFile = new File(appDir, INSTANT_APP_METADATA_FILE);
1061
1062        AtomicFile destination = new AtomicFile(metadataFile);
1063        FileOutputStream out = null;
1064        try {
1065            out = destination.startWrite();
1066
1067            XmlSerializer serializer = Xml.newSerializer();
1068            serializer.setOutput(out, StandardCharsets.UTF_8.name());
1069            serializer.setFeature("http://xmlpull.org/v1/doc/features.html#indent-output", true);
1070
1071            serializer.startDocument(null, true);
1072
1073            serializer.startTag(null, TAG_PACKAGE);
1074            serializer.attribute(null, ATTR_LABEL, instantApp.loadLabel(
1075                    mService.mContext.getPackageManager()).toString());
1076
1077            serializer.startTag(null, TAG_PERMISSIONS);
1078            for (String permission : instantApp.getRequestedPermissions()) {
1079                serializer.startTag(null, TAG_PERMISSION);
1080                serializer.attribute(null, ATTR_NAME, permission);
1081                if (ArrayUtils.contains(instantApp.getGrantedPermissions(), permission)) {
1082                    serializer.attribute(null, ATTR_GRANTED, String.valueOf(true));
1083                }
1084                serializer.endTag(null, TAG_PERMISSION);
1085            }
1086            serializer.endTag(null, TAG_PERMISSIONS);
1087
1088            serializer.endTag(null, TAG_PACKAGE);
1089
1090            serializer.endDocument();
1091            destination.finishWrite(out);
1092        } catch (Throwable t) {
1093            Slog.wtf(LOG_TAG, "Failed to write instant state, restoring backup", t);
1094            destination.failWrite(out);
1095        } finally {
1096            IoUtils.closeQuietly(out);
1097        }
1098    }
1099
1100    private static @NonNull File getInstantApplicationsDir(int userId) {
1101        return new File(Environment.getUserSystemDirectory(userId),
1102                INSTANT_APPS_FOLDER);
1103    }
1104
1105    private static @NonNull File getInstantApplicationDir(String packageName, int userId) {
1106        return new File(getInstantApplicationsDir(userId), packageName);
1107    }
1108
1109    private static void deleteDir(@NonNull File dir) {
1110        File[] files = dir.listFiles();
1111        if (files != null) {
1112            for (File file : files) {
1113                deleteDir(file);
1114            }
1115        }
1116        dir.delete();
1117    }
1118
1119    private static final class UninstalledInstantAppState {
1120        final InstantAppInfo mInstantAppInfo;
1121        final long mTimestamp;
1122
1123        public UninstalledInstantAppState(InstantAppInfo instantApp,
1124                long timestamp) {
1125            mInstantAppInfo = instantApp;
1126            mTimestamp = timestamp;
1127        }
1128    }
1129
1130    private final class CookiePersistence extends Handler {
1131        private static final long PERSIST_COOKIE_DELAY_MILLIS = 1000L; /* one second */
1132
1133        // In case you wonder why we stash the cookies aside, we use
1134        // the user id for the message id and the package for the payload.
1135        // Handler allows removing messages by id and tag where the
1136        // tag is compared using ==. So to allow cancelling the
1137        // pending persistence for an app under a given user we use
1138        // the fact that package are cached by the system so the ==
1139        // comparison would match and we end up with a way to cancel
1140        // persisting the cookie for a user and package.
1141        private final SparseArray<ArrayMap<PackageParser.Package, SomeArgs>> mPendingPersistCookies
1142                = new SparseArray<>();
1143
1144        public CookiePersistence(Looper looper) {
1145            super(looper);
1146        }
1147
1148        public void schedulePersistLPw(@UserIdInt int userId, @NonNull PackageParser.Package pkg,
1149                @NonNull byte[] cookie) {
1150            File cookieFile = computeInstantCookieFile(pkg, userId);
1151            cancelPendingPersistLPw(pkg, userId);
1152            addPendingPersistCookieLPw(userId, pkg, cookie, cookieFile);
1153            sendMessageDelayed(obtainMessage(userId, pkg),
1154                    PERSIST_COOKIE_DELAY_MILLIS);
1155        }
1156
1157        public @Nullable byte[] getPendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1158                @UserIdInt int userId) {
1159            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1160                    mPendingPersistCookies.get(userId);
1161            if (pendingWorkForUser != null) {
1162                SomeArgs state = pendingWorkForUser.get(pkg);
1163                if (state != null) {
1164                    return (byte[]) state.arg1;
1165                }
1166            }
1167            return null;
1168        }
1169
1170        public void cancelPendingPersistLPw(@NonNull PackageParser.Package pkg,
1171                @UserIdInt int userId) {
1172            removeMessages(userId, pkg);
1173            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1174            if (state != null) {
1175                state.recycle();
1176            }
1177        }
1178
1179        private void addPendingPersistCookieLPw(@UserIdInt int userId,
1180                @NonNull PackageParser.Package pkg, @NonNull byte[] cookie,
1181                @NonNull File cookieFile) {
1182            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1183                    mPendingPersistCookies.get(userId);
1184            if (pendingWorkForUser == null) {
1185                pendingWorkForUser = new ArrayMap<>();
1186                mPendingPersistCookies.put(userId, pendingWorkForUser);
1187            }
1188            SomeArgs args = SomeArgs.obtain();
1189            args.arg1 = cookie;
1190            args.arg2 = cookieFile;
1191            pendingWorkForUser.put(pkg, args);
1192        }
1193
1194        private SomeArgs removePendingPersistCookieLPr(@NonNull PackageParser.Package pkg,
1195                @UserIdInt int userId) {
1196            ArrayMap<PackageParser.Package, SomeArgs> pendingWorkForUser =
1197                    mPendingPersistCookies.get(userId);
1198            SomeArgs state = null;
1199            if (pendingWorkForUser != null) {
1200                state = pendingWorkForUser.remove(pkg);
1201                if (pendingWorkForUser.isEmpty()) {
1202                    mPendingPersistCookies.remove(userId);
1203                }
1204            }
1205            return state;
1206        }
1207
1208        @Override
1209        public void handleMessage(Message message) {
1210            int userId = message.what;
1211            PackageParser.Package pkg = (PackageParser.Package) message.obj;
1212            SomeArgs state = removePendingPersistCookieLPr(pkg, userId);
1213            if (state == null) {
1214                return;
1215            }
1216            byte[] cookie = (byte[]) state.arg1;
1217            File cookieFile = (File) state.arg2;
1218            state.recycle();
1219            persistInstantApplicationCookie(cookie, pkg.packageName, cookieFile, userId);
1220        }
1221    }
1222}
1223