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.settingslib.applications;
18
19import android.app.ActivityManager;
20import android.app.AppGlobals;
21import android.app.Application;
22import android.app.usage.StorageStats;
23import android.app.usage.StorageStatsManager;
24import android.content.BroadcastReceiver;
25import android.content.Context;
26import android.content.Intent;
27import android.content.IntentFilter;
28import android.content.pm.ApplicationInfo;
29import android.content.pm.IPackageManager;
30import android.content.pm.IPackageStatsObserver;
31import android.content.pm.PackageManager;
32import android.content.pm.PackageManager.NameNotFoundException;
33import android.content.pm.PackageStats;
34import android.content.pm.ParceledListSlice;
35import android.content.pm.ResolveInfo;
36import android.content.pm.UserInfo;
37import android.graphics.drawable.Drawable;
38import android.net.Uri;
39import android.os.Handler;
40import android.os.HandlerThread;
41import android.os.Looper;
42import android.os.Message;
43import android.os.Process;
44import android.os.RemoteException;
45import android.os.SystemClock;
46import android.os.UserHandle;
47import android.os.UserManager;
48import android.text.format.Formatter;
49import android.util.IconDrawableFactory;
50import android.util.Log;
51import android.util.SparseArray;
52
53import com.android.internal.R;
54import com.android.internal.util.ArrayUtils;
55
56import java.io.File;
57import java.io.IOException;
58import java.text.Collator;
59import java.text.Normalizer;
60import java.text.Normalizer.Form;
61import java.util.ArrayList;
62import java.util.Collections;
63import java.util.Comparator;
64import java.util.HashMap;
65import java.util.List;
66import java.util.Objects;
67import java.util.UUID;
68import java.util.regex.Pattern;
69
70/**
71 * Keeps track of information about all installed applications, lazy-loading
72 * as needed.
73 */
74public class ApplicationsState {
75    static final String TAG = "ApplicationsState";
76    static final boolean DEBUG = false;
77    static final boolean DEBUG_LOCKING = false;
78
79    public static final int SIZE_UNKNOWN = -1;
80    public static final int SIZE_INVALID = -2;
81
82    static final Pattern REMOVE_DIACRITICALS_PATTERN
83            = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
84
85    static final Object sLock = new Object();
86    static ApplicationsState sInstance;
87
88    public static ApplicationsState getInstance(Application app) {
89        synchronized (sLock) {
90            if (sInstance == null) {
91                sInstance = new ApplicationsState(app);
92            }
93            return sInstance;
94        }
95    }
96
97    final Context mContext;
98    final PackageManager mPm;
99    final IconDrawableFactory mDrawableFactory;
100    final IPackageManager mIpm;
101    final UserManager mUm;
102    final StorageStatsManager mStats;
103    final int mAdminRetrieveFlags;
104    final int mRetrieveFlags;
105    PackageIntentReceiver mPackageIntentReceiver;
106
107    boolean mResumed;
108    boolean mHaveDisabledApps;
109    boolean mHaveInstantApps;
110
111    // Information about all applications.  Synchronize on mEntriesMap
112    // to protect access to these.
113    final ArrayList<Session> mSessions = new ArrayList<Session>();
114    final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
115    final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
116    // Map: userid => (Map: package name => AppEntry)
117    final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
118            new SparseArray<HashMap<String, AppEntry>>();
119    final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
120    List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
121    long mCurId = 1;
122    UUID mCurComputingSizeUuid;
123    String mCurComputingSizePkg;
124    int mCurComputingSizeUserId;
125    boolean mSessionsChanged;
126
127    // Temporary for dispatching session callbacks.  Only touched by main thread.
128    final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
129
130    final HandlerThread mThread;
131    final BackgroundHandler mBackgroundHandler;
132    final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
133
134    private ApplicationsState(Application app) {
135        mContext = app;
136        mPm = mContext.getPackageManager();
137        mDrawableFactory = IconDrawableFactory.newInstance(mContext);
138        mIpm = AppGlobals.getPackageManager();
139        mUm = mContext.getSystemService(UserManager.class);
140        mStats = mContext.getSystemService(StorageStatsManager.class);
141        for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
142            mEntriesMap.put(userId, new HashMap<String, AppEntry>());
143        }
144        mThread = new HandlerThread("ApplicationsState.Loader",
145                Process.THREAD_PRIORITY_BACKGROUND);
146        mThread.start();
147        mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
148
149        // Only the owner can see all apps.
150        mAdminRetrieveFlags = PackageManager.MATCH_ANY_USER |
151                PackageManager.MATCH_DISABLED_COMPONENTS |
152                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
153        mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
154                PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
155
156        /**
157         * This is a trick to prevent the foreground thread from being delayed.
158         * The problem is that Dalvik monitors are initially spin locks, to keep
159         * them lightweight.  This leads to unfair contention -- Even though the
160         * background thread only holds the lock for a short amount of time, if
161         * it keeps running and locking again it can prevent the main thread from
162         * acquiring its lock for a long time...  sometimes even > 5 seconds
163         * (leading to an ANR).
164         *
165         * Dalvik will promote a monitor to a "real" lock if it detects enough
166         * contention on it.  It doesn't figure this out fast enough for us
167         * here, though, so this little trick will force it to turn into a real
168         * lock immediately.
169         */
170        synchronized (mEntriesMap) {
171            try {
172                mEntriesMap.wait(1);
173            } catch (InterruptedException e) {
174            }
175        }
176    }
177
178    public Looper getBackgroundLooper() {
179        return mThread.getLooper();
180    }
181
182    public Session newSession(Callbacks callbacks) {
183        Session s = new Session(callbacks);
184        synchronized (mEntriesMap) {
185            mSessions.add(s);
186        }
187        return s;
188    }
189
190    void doResumeIfNeededLocked() {
191        if (mResumed) {
192            return;
193        }
194        mResumed = true;
195        if (mPackageIntentReceiver == null) {
196            mPackageIntentReceiver = new PackageIntentReceiver();
197            mPackageIntentReceiver.registerReceiver();
198        }
199        mApplications = new ArrayList<ApplicationInfo>();
200        for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
201            try {
202                // If this user is new, it needs a map created.
203                if (mEntriesMap.indexOfKey(user.id) < 0) {
204                    mEntriesMap.put(user.id, new HashMap<String, AppEntry>());
205                }
206                @SuppressWarnings("unchecked")
207                ParceledListSlice<ApplicationInfo> list =
208                        mIpm.getInstalledApplications(
209                                user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
210                                user.id);
211                mApplications.addAll(list.getList());
212            } catch (RemoteException e) {
213            }
214        }
215
216        if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
217            // If an interesting part of the configuration has changed, we
218            // should completely reload the app entries.
219            clearEntries();
220        } else {
221            for (int i=0; i<mAppEntries.size(); i++) {
222                mAppEntries.get(i).sizeStale = true;
223            }
224        }
225
226        mHaveDisabledApps = false;
227        mHaveInstantApps = false;
228        for (int i=0; i<mApplications.size(); i++) {
229            final ApplicationInfo info = mApplications.get(i);
230            // Need to trim out any applications that are disabled by
231            // something different than the user.
232            if (!info.enabled) {
233                if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
234                    mApplications.remove(i);
235                    i--;
236                    continue;
237                }
238                mHaveDisabledApps = true;
239            }
240            if (!mHaveInstantApps && AppUtils.isInstant(info)) {
241                mHaveInstantApps = true;
242            }
243
244            int userId = UserHandle.getUserId(info.uid);
245            final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
246            if (entry != null) {
247                entry.info = info;
248            }
249        }
250        if (mAppEntries.size() > mApplications.size()) {
251            // There are less apps now, some must have been uninstalled.
252            clearEntries();
253        }
254        mCurComputingSizePkg = null;
255        if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
256            mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
257        }
258    }
259
260    private void clearEntries() {
261        for (int i = 0; i < mEntriesMap.size(); i++) {
262            mEntriesMap.valueAt(i).clear();
263        }
264        mAppEntries.clear();
265    }
266
267    public boolean haveDisabledApps() {
268        return mHaveDisabledApps;
269    }
270    public boolean haveInstantApps() {
271        return mHaveInstantApps;
272    }
273
274    void doPauseIfNeededLocked() {
275        if (!mResumed) {
276            return;
277        }
278        for (int i=0; i<mSessions.size(); i++) {
279            if (mSessions.get(i).mResumed) {
280                return;
281            }
282        }
283        doPauseLocked();
284    }
285
286    void doPauseLocked() {
287        mResumed = false;
288        if (mPackageIntentReceiver != null) {
289            mPackageIntentReceiver.unregisterReceiver();
290            mPackageIntentReceiver = null;
291        }
292    }
293
294    public AppEntry getEntry(String packageName, int userId) {
295        if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
296        synchronized (mEntriesMap) {
297            AppEntry entry = mEntriesMap.get(userId).get(packageName);
298            if (entry == null) {
299                ApplicationInfo info = getAppInfoLocked(packageName, userId);
300                if (info == null) {
301                    try {
302                        info = mIpm.getApplicationInfo(packageName, 0, userId);
303                    } catch (RemoteException e) {
304                        Log.w(TAG, "getEntry couldn't reach PackageManager", e);
305                        return null;
306                    }
307                }
308                if (info != null) {
309                    entry = getEntryLocked(info);
310                }
311            }
312            if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
313            return entry;
314        }
315    }
316
317    private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
318        for (int i = 0; i < mApplications.size(); i++) {
319            ApplicationInfo info = mApplications.get(i);
320            if (pkg.equals(info.packageName)
321                    && userId == UserHandle.getUserId(info.uid)) {
322                return info;
323            }
324        }
325        return null;
326    }
327
328    public void ensureIcon(AppEntry entry) {
329        if (entry.icon != null) {
330            return;
331        }
332        synchronized (entry) {
333            entry.ensureIconLocked(mContext, mDrawableFactory);
334        }
335    }
336
337    public void requestSize(String packageName, int userId) {
338        if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
339        synchronized (mEntriesMap) {
340            AppEntry entry = mEntriesMap.get(userId).get(packageName);
341            if (entry != null && (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0) {
342                mBackgroundHandler.post(
343                        () -> {
344                            try {
345                                final StorageStats stats =
346                                        mStats.queryStatsForPackage(
347                                                entry.info.storageUuid,
348                                                packageName,
349                                                UserHandle.of(userId));
350                                final long cacheQuota =
351                                        mStats.getCacheQuotaBytes(
352                                                entry.info.storageUuid.toString(), entry.info.uid);
353                                final PackageStats legacy = new PackageStats(packageName, userId);
354                                legacy.codeSize = stats.getCodeBytes();
355                                legacy.dataSize = stats.getDataBytes();
356                                legacy.cacheSize = Math.min(stats.getCacheBytes(), cacheQuota);
357                                try {
358                                    mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
359                                            legacy, true);
360                                } catch (RemoteException ignored) {
361                                }
362                            } catch (NameNotFoundException | IOException e) {
363                                Log.w(TAG, "Failed to query stats: " + e);
364                                try {
365                                    mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
366                                            null, false);
367                                } catch (RemoteException ignored) {
368                                }
369                            }
370                        });
371            }
372            if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
373        }
374    }
375
376    long sumCacheSizes() {
377        long sum = 0;
378        if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
379        synchronized (mEntriesMap) {
380            if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
381            for (int i=mAppEntries.size()-1; i>=0; i--) {
382                sum += mAppEntries.get(i).cacheSize;
383            }
384            if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
385        }
386        return sum;
387    }
388
389    int indexOfApplicationInfoLocked(String pkgName, int userId) {
390        for (int i=mApplications.size()-1; i>=0; i--) {
391            ApplicationInfo appInfo = mApplications.get(i);
392            if (appInfo.packageName.equals(pkgName)
393                    && UserHandle.getUserId(appInfo.uid) == userId) {
394                return i;
395            }
396        }
397        return -1;
398    }
399
400    void addPackage(String pkgName, int userId) {
401        try {
402            synchronized (mEntriesMap) {
403                if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
404                if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
405                if (!mResumed) {
406                    // If we are not resumed, we will do a full query the
407                    // next time we resume, so there is no reason to do work
408                    // here.
409                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
410                    return;
411                }
412                if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
413                    if (DEBUG) Log.i(TAG, "Package already exists!");
414                    if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
415                    return;
416                }
417                ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
418                        mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
419                        userId);
420                if (info == null) {
421                    return;
422                }
423                if (!info.enabled) {
424                    if (info.enabledSetting
425                            != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
426                        return;
427                    }
428                    mHaveDisabledApps = true;
429                }
430                if (AppUtils.isInstant(info)) {
431                    mHaveInstantApps = true;
432                }
433                mApplications.add(info);
434                if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
435                    mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
436                }
437                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
438                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
439                }
440                if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
441            }
442        } catch (RemoteException e) {
443        }
444    }
445
446    public void removePackage(String pkgName, int userId) {
447        synchronized (mEntriesMap) {
448            if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
449            int idx = indexOfApplicationInfoLocked(pkgName, userId);
450            if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
451            if (idx >= 0) {
452                AppEntry entry = mEntriesMap.get(userId).get(pkgName);
453                if (DEBUG) Log.i(TAG, "removePackage: " + entry);
454                if (entry != null) {
455                    mEntriesMap.get(userId).remove(pkgName);
456                    mAppEntries.remove(entry);
457                }
458                ApplicationInfo info = mApplications.get(idx);
459                mApplications.remove(idx);
460                if (!info.enabled) {
461                    mHaveDisabledApps = false;
462                    for (ApplicationInfo otherInfo : mApplications) {
463                        if (!otherInfo.enabled) {
464                            mHaveDisabledApps = true;
465                            break;
466                        }
467                    }
468                }
469                if (AppUtils.isInstant(info)) {
470                    mHaveInstantApps = false;
471                    for (ApplicationInfo otherInfo : mApplications) {
472                        if (AppUtils.isInstant(otherInfo)) {
473                            mHaveInstantApps = true;
474                            break;
475                        }
476                    }
477                }
478                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
479                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
480                }
481            }
482            if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
483        }
484    }
485
486    public void invalidatePackage(String pkgName, int userId) {
487        removePackage(pkgName, userId);
488        addPackage(pkgName, userId);
489    }
490
491    private void addUser(int userId) {
492        final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
493        if (ArrayUtils.contains(profileIds, userId)) {
494            synchronized (mEntriesMap) {
495                mEntriesMap.put(userId, new HashMap<String, AppEntry>());
496                if (mResumed) {
497                    // If resumed, Manually pause, then cause a resume to repopulate the app list.
498                    // This is the simplest way to reload the packages so that the new user
499                    // is included.  Otherwise the list will be repopulated on next resume.
500                    doPauseLocked();
501                    doResumeIfNeededLocked();
502                }
503                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
504                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
505                }
506            }
507        }
508    }
509
510    private void removeUser(int userId) {
511        synchronized (mEntriesMap) {
512            HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
513            if (userMap != null) {
514                for (AppEntry appEntry : userMap.values()) {
515                    mAppEntries.remove(appEntry);
516                    mApplications.remove(appEntry.info);
517                }
518                mEntriesMap.remove(userId);
519                if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
520                    mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
521                }
522            }
523        }
524    }
525
526    private AppEntry getEntryLocked(ApplicationInfo info) {
527        int userId = UserHandle.getUserId(info.uid);
528        AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
529        if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
530        if (entry == null) {
531            if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
532            entry = new AppEntry(mContext, info, mCurId++);
533            mEntriesMap.get(userId).put(info.packageName, entry);
534            mAppEntries.add(entry);
535        } else if (entry.info != info) {
536            entry.info = info;
537        }
538        return entry;
539    }
540
541    // --------------------------------------------------------------
542
543    private long getTotalInternalSize(PackageStats ps) {
544        if (ps != null) {
545            return ps.codeSize + ps.dataSize;
546        }
547        return SIZE_INVALID;
548    }
549
550    private long getTotalExternalSize(PackageStats ps) {
551        if (ps != null) {
552            // We also include the cache size here because for non-emulated
553            // we don't automtically clean cache files.
554            return ps.externalCodeSize + ps.externalDataSize
555                    + ps.externalCacheSize
556                    + ps.externalMediaSize + ps.externalObbSize;
557        }
558        return SIZE_INVALID;
559    }
560
561    private String getSizeStr(long size) {
562        if (size >= 0) {
563            return Formatter.formatFileSize(mContext, size);
564        }
565        return null;
566    }
567
568    void rebuildActiveSessions() {
569        synchronized (mEntriesMap) {
570            if (!mSessionsChanged) {
571                return;
572            }
573            mActiveSessions.clear();
574            for (int i=0; i<mSessions.size(); i++) {
575                Session s = mSessions.get(i);
576                if (s.mResumed) {
577                    mActiveSessions.add(s);
578                }
579            }
580        }
581    }
582
583    public static String normalize(String str) {
584        String tmp = Normalizer.normalize(str, Form.NFD);
585        return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
586                .replaceAll("").toLowerCase();
587    }
588
589    public class Session {
590        final Callbacks mCallbacks;
591        boolean mResumed;
592
593        // Rebuilding of app list.  Synchronized on mRebuildSync.
594        final Object mRebuildSync = new Object();
595        boolean mRebuildRequested;
596        boolean mRebuildAsync;
597        AppFilter mRebuildFilter;
598        Comparator<AppEntry> mRebuildComparator;
599        ArrayList<AppEntry> mRebuildResult;
600        ArrayList<AppEntry> mLastAppList;
601        boolean mRebuildForeground;
602
603        Session(Callbacks callbacks) {
604            mCallbacks = callbacks;
605        }
606
607        public void resume() {
608            if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
609            synchronized (mEntriesMap) {
610                if (!mResumed) {
611                    mResumed = true;
612                    mSessionsChanged = true;
613                    doResumeIfNeededLocked();
614                }
615            }
616            if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
617        }
618
619        public void pause() {
620            if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
621            synchronized (mEntriesMap) {
622                if (mResumed) {
623                    mResumed = false;
624                    mSessionsChanged = true;
625                    mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
626                    doPauseIfNeededLocked();
627                }
628                if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
629            }
630        }
631
632        public ArrayList<AppEntry> getAllApps() {
633            synchronized (mEntriesMap) {
634                return new ArrayList<>(mAppEntries);
635            }
636        }
637
638        // Creates a new list of app entries with the given filter and comparator.
639        public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
640            return rebuild(filter, comparator, true);
641        }
642
643        public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator,
644                boolean foreground) {
645            synchronized (mRebuildSync) {
646                synchronized (mRebuildingSessions) {
647                    mRebuildingSessions.add(this);
648                    mRebuildRequested = true;
649                    mRebuildAsync = true;
650                    mRebuildFilter = filter;
651                    mRebuildComparator = comparator;
652                    mRebuildForeground = foreground;
653                    mRebuildResult = null;
654                    if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
655                        Message msg = mBackgroundHandler.obtainMessage(
656                                BackgroundHandler.MSG_REBUILD_LIST);
657                        mBackgroundHandler.sendMessage(msg);
658                    }
659                }
660
661                return null;
662            }
663        }
664
665        void handleRebuildList() {
666            AppFilter filter;
667            Comparator<AppEntry> comparator;
668            synchronized (mRebuildSync) {
669                if (!mRebuildRequested) {
670                    return;
671                }
672
673                filter = mRebuildFilter;
674                comparator = mRebuildComparator;
675                mRebuildRequested = false;
676                mRebuildFilter = null;
677                mRebuildComparator = null;
678                if (mRebuildForeground) {
679                    Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
680                    mRebuildForeground = false;
681                }
682            }
683
684            if (filter != null) {
685                filter.init(mContext);
686            }
687
688            List<AppEntry> apps;
689            synchronized (mEntriesMap) {
690                apps = new ArrayList<>(mAppEntries);
691            }
692
693            ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
694            if (DEBUG) Log.i(TAG, "Rebuilding...");
695            for (int i=0; i<apps.size(); i++) {
696                AppEntry entry = apps.get(i);
697                if (entry != null && (filter == null || filter.filterApp(entry))) {
698                    synchronized (mEntriesMap) {
699                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
700                        if (comparator != null) {
701                            // Only need the label if we are going to be sorting.
702                            entry.ensureLabel(mContext);
703                        }
704                        if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
705                        filteredApps.add(entry);
706                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
707                    }
708                }
709            }
710
711            if (comparator != null) {
712                synchronized (mEntriesMap) {
713                    // Locking to ensure that the background handler does not mutate
714                    // the size of AppEntries used for ordering while sorting.
715                    Collections.sort(filteredApps, comparator);
716                }
717            }
718
719            synchronized (mRebuildSync) {
720                if (!mRebuildRequested) {
721                    mLastAppList = filteredApps;
722                    if (!mRebuildAsync) {
723                        mRebuildResult = filteredApps;
724                        mRebuildSync.notifyAll();
725                    } else {
726                        if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
727                            Message msg = mMainHandler.obtainMessage(
728                                    MainHandler.MSG_REBUILD_COMPLETE, this);
729                            mMainHandler.sendMessage(msg);
730                        }
731                    }
732                }
733            }
734
735            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
736        }
737
738        public void release() {
739            pause();
740            synchronized (mEntriesMap) {
741                mSessions.remove(this);
742            }
743        }
744    }
745
746    class MainHandler extends Handler {
747        static final int MSG_REBUILD_COMPLETE = 1;
748        static final int MSG_PACKAGE_LIST_CHANGED = 2;
749        static final int MSG_PACKAGE_ICON_CHANGED = 3;
750        static final int MSG_PACKAGE_SIZE_CHANGED = 4;
751        static final int MSG_ALL_SIZES_COMPUTED = 5;
752        static final int MSG_RUNNING_STATE_CHANGED = 6;
753        static final int MSG_LAUNCHER_INFO_CHANGED = 7;
754        static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
755
756        public MainHandler(Looper looper) {
757            super(looper);
758        }
759
760        @Override
761        public void handleMessage(Message msg) {
762            rebuildActiveSessions();
763            switch (msg.what) {
764                case MSG_REBUILD_COMPLETE: {
765                    Session s = (Session)msg.obj;
766                    if (mActiveSessions.contains(s)) {
767                        s.mCallbacks.onRebuildComplete(s.mLastAppList);
768                    }
769                } break;
770                case MSG_PACKAGE_LIST_CHANGED: {
771                    for (int i=0; i<mActiveSessions.size(); i++) {
772                        mActiveSessions.get(i).mCallbacks.onPackageListChanged();
773                    }
774                } break;
775                case MSG_PACKAGE_ICON_CHANGED: {
776                    for (int i=0; i<mActiveSessions.size(); i++) {
777                        mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
778                    }
779                } break;
780                case MSG_PACKAGE_SIZE_CHANGED: {
781                    for (int i=0; i<mActiveSessions.size(); i++) {
782                        mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
783                                (String)msg.obj);
784                    }
785                } break;
786                case MSG_ALL_SIZES_COMPUTED: {
787                    for (int i=0; i<mActiveSessions.size(); i++) {
788                        mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
789                    }
790                } break;
791                case MSG_RUNNING_STATE_CHANGED: {
792                    for (int i=0; i<mActiveSessions.size(); i++) {
793                        mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
794                                msg.arg1 != 0);
795                    }
796                } break;
797                case MSG_LAUNCHER_INFO_CHANGED: {
798                    for (int i=0; i<mActiveSessions.size(); i++) {
799                        mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
800                    }
801                } break;
802                case MSG_LOAD_ENTRIES_COMPLETE: {
803                    for (int i=0; i<mActiveSessions.size(); i++) {
804                        mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
805                    }
806                } break;
807            }
808        }
809    }
810
811    private class BackgroundHandler extends Handler {
812        static final int MSG_REBUILD_LIST = 1;
813        static final int MSG_LOAD_ENTRIES = 2;
814        static final int MSG_LOAD_ICONS = 3;
815        static final int MSG_LOAD_SIZES = 4;
816        static final int MSG_LOAD_LAUNCHER = 5;
817        static final int MSG_LOAD_HOME_APP = 6;
818
819        boolean mRunning;
820
821        BackgroundHandler(Looper looper) {
822            super(looper);
823        }
824
825        @Override
826        public void handleMessage(Message msg) {
827            // Always try rebuilding list first thing, if needed.
828            ArrayList<Session> rebuildingSessions = null;
829            synchronized (mRebuildingSessions) {
830                if (mRebuildingSessions.size() > 0) {
831                    rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
832                    mRebuildingSessions.clear();
833                }
834            }
835            if (rebuildingSessions != null) {
836                for (int i=0; i<rebuildingSessions.size(); i++) {
837                    rebuildingSessions.get(i).handleRebuildList();
838                }
839            }
840
841            switch (msg.what) {
842                case MSG_REBUILD_LIST: {
843                } break;
844                case MSG_LOAD_ENTRIES: {
845                    int numDone = 0;
846                    synchronized (mEntriesMap) {
847                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
848                        for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
849                            if (!mRunning) {
850                                mRunning = true;
851                                Message m = mMainHandler.obtainMessage(
852                                        MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
853                                mMainHandler.sendMessage(m);
854                            }
855                            ApplicationInfo info = mApplications.get(i);
856                            int userId = UserHandle.getUserId(info.uid);
857                            if (mEntriesMap.get(userId).get(info.packageName) == null) {
858                                numDone++;
859                                getEntryLocked(info);
860                            }
861                            if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
862                                // If this app is for a profile and we are on the owner, remove
863                                // the owner entry if it isn't installed.  This will prevent
864                                // duplicates of work only apps showing up as 'not installed
865                                // for this user'.
866                                // Note: This depends on us traversing the users in order, which
867                                // happens because of the way we generate the list in
868                                // doResumeIfNeededLocked.
869                                AppEntry entry = mEntriesMap.get(0).get(info.packageName);
870                                if (entry != null &&
871                                        (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
872                                    mEntriesMap.get(0).remove(info.packageName);
873                                    mAppEntries.remove(entry);
874                                }
875                            }
876                        }
877                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
878                    }
879
880                    if (numDone >= 6) {
881                        sendEmptyMessage(MSG_LOAD_ENTRIES);
882                    } else {
883                        if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
884                            mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
885                        }
886                        sendEmptyMessage(MSG_LOAD_HOME_APP);
887                    }
888                } break;
889                case MSG_LOAD_HOME_APP: {
890                    final List<ResolveInfo> homeActivities = new ArrayList<>();
891                    mPm.getHomeActivities(homeActivities);
892                    synchronized (mEntriesMap) {
893                        final int entryCount = mEntriesMap.size();
894                        for (int i = 0; i < entryCount; i++) {
895                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
896                            final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
897                            for (ResolveInfo activity : homeActivities) {
898                                String packageName = activity.activityInfo.packageName;
899                                AppEntry entry = userEntries.get(packageName);
900                                if (entry != null) {
901                                    entry.isHomeApp = true;
902                                }
903                            }
904                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
905                        }
906                    }
907                    sendEmptyMessage(MSG_LOAD_LAUNCHER);
908                }
909                break;
910                case MSG_LOAD_LAUNCHER: {
911                    Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
912                            .addCategory(Intent.CATEGORY_LAUNCHER);
913                    for (int i = 0; i < mEntriesMap.size(); i++) {
914                        int userId = mEntriesMap.keyAt(i);
915                        // If we do not specify MATCH_DIRECT_BOOT_AWARE or
916                        // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
917                        // according to the user's lock state. When the user is locked, components
918                        // with ComponentInfo#directBootAware == false will be filtered. We should
919                        // explicitly include both direct boot aware and unaware components here.
920                        List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
921                                launchIntent,
922                                PackageManager.MATCH_DISABLED_COMPONENTS
923                                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
924                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
925                                userId
926                        );
927                        synchronized (mEntriesMap) {
928                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
929                            HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
930                            final int N = intents.size();
931                            for (int j = 0; j < N; j++) {
932                                String packageName = intents.get(j).activityInfo.packageName;
933                                AppEntry entry = userEntries.get(packageName);
934                                if (entry != null) {
935                                    entry.hasLauncherEntry = true;
936                                } else {
937                                    Log.w(TAG, "Cannot find pkg: " + packageName
938                                            + " on user " + userId);
939                                }
940                            }
941                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
942                        }
943                    }
944
945                    if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
946                        mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
947                    }
948                    sendEmptyMessage(MSG_LOAD_ICONS);
949                } break;
950                case MSG_LOAD_ICONS: {
951                    int numDone = 0;
952                    synchronized (mEntriesMap) {
953                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
954                        for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
955                            AppEntry entry = mAppEntries.get(i);
956                            if (entry.icon == null || !entry.mounted) {
957                                synchronized (entry) {
958                                    if (entry.ensureIconLocked(mContext, mDrawableFactory)) {
959                                        if (!mRunning) {
960                                            mRunning = true;
961                                            Message m = mMainHandler.obtainMessage(
962                                                    MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
963                                            mMainHandler.sendMessage(m);
964                                        }
965                                        numDone++;
966                                    }
967                                }
968                            }
969                        }
970                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
971                    }
972                    if (numDone > 0) {
973                        if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
974                            mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
975                        }
976                    }
977                    if (numDone >= 2) {
978                        sendEmptyMessage(MSG_LOAD_ICONS);
979                    } else {
980                        sendEmptyMessage(MSG_LOAD_SIZES);
981                    }
982                } break;
983                case MSG_LOAD_SIZES: {
984                    synchronized (mEntriesMap) {
985                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
986                        if (mCurComputingSizePkg != null) {
987                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
988                            return;
989                        }
990
991                        long now = SystemClock.uptimeMillis();
992                        for (int i=0; i<mAppEntries.size(); i++) {
993                            AppEntry entry = mAppEntries.get(i);
994                            if ((entry.info.flags & ApplicationInfo.FLAG_INSTALLED) != 0
995                                    && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) {
996                                if (entry.sizeLoadStart == 0 ||
997                                        (entry.sizeLoadStart < (now-20*1000))) {
998                                    if (!mRunning) {
999                                        mRunning = true;
1000                                        Message m = mMainHandler.obtainMessage(
1001                                                MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
1002                                        mMainHandler.sendMessage(m);
1003                                    }
1004                                    entry.sizeLoadStart = now;
1005                                    mCurComputingSizeUuid = entry.info.storageUuid;
1006                                    mCurComputingSizePkg = entry.info.packageName;
1007                                    mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
1008
1009                                    mBackgroundHandler.post(() -> {
1010                                        try {
1011                                            final StorageStats stats = mStats.queryStatsForPackage(
1012                                                    mCurComputingSizeUuid, mCurComputingSizePkg,
1013                                                    UserHandle.of(mCurComputingSizeUserId));
1014                                            final PackageStats legacy = new PackageStats(
1015                                                    mCurComputingSizePkg, mCurComputingSizeUserId);
1016                                            legacy.codeSize = stats.getCodeBytes();
1017                                            legacy.dataSize = stats.getDataBytes();
1018                                            legacy.cacheSize = stats.getCacheBytes();
1019                                            try {
1020                                                mStatsObserver.onGetStatsCompleted(legacy, true);
1021                                            } catch (RemoteException ignored) {
1022                                            }
1023                                        } catch (NameNotFoundException | IOException e) {
1024                                            Log.w(TAG, "Failed to query stats: " + e);
1025                                            try {
1026                                                mStatsObserver.onGetStatsCompleted(null, false);
1027                                            } catch (RemoteException ignored) {
1028                                            }
1029                                        }
1030
1031                                    });
1032                                }
1033                                if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
1034                                return;
1035                            }
1036                        }
1037                        if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
1038                            mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
1039                            mRunning = false;
1040                            Message m = mMainHandler.obtainMessage(
1041                                    MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
1042                            mMainHandler.sendMessage(m);
1043                        }
1044                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
1045                    }
1046                } break;
1047            }
1048        }
1049
1050        final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
1051            public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
1052                if (!succeeded) {
1053                    // There is no meaningful information in stats if the call failed.
1054                    return;
1055                }
1056
1057                boolean sizeChanged = false;
1058                synchronized (mEntriesMap) {
1059                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
1060                    HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
1061                    if (userMap == null) {
1062                        // The user must have been removed.
1063                        return;
1064                    }
1065                    AppEntry entry = userMap.get(stats.packageName);
1066                    if (entry != null) {
1067                        synchronized (entry) {
1068                            entry.sizeStale = false;
1069                            entry.sizeLoadStart = 0;
1070                            long externalCodeSize = stats.externalCodeSize
1071                                    + stats.externalObbSize;
1072                            long externalDataSize = stats.externalDataSize
1073                                    + stats.externalMediaSize;
1074                            long newSize = externalCodeSize + externalDataSize
1075                                    + getTotalInternalSize(stats);
1076                            if (entry.size != newSize ||
1077                                    entry.cacheSize != stats.cacheSize ||
1078                                    entry.codeSize != stats.codeSize ||
1079                                    entry.dataSize != stats.dataSize ||
1080                                    entry.externalCodeSize != externalCodeSize ||
1081                                    entry.externalDataSize != externalDataSize ||
1082                                    entry.externalCacheSize != stats.externalCacheSize) {
1083                                entry.size = newSize;
1084                                entry.cacheSize = stats.cacheSize;
1085                                entry.codeSize = stats.codeSize;
1086                                entry.dataSize = stats.dataSize;
1087                                entry.externalCodeSize = externalCodeSize;
1088                                entry.externalDataSize = externalDataSize;
1089                                entry.externalCacheSize = stats.externalCacheSize;
1090                                entry.sizeStr = getSizeStr(entry.size);
1091                                entry.internalSize = getTotalInternalSize(stats);
1092                                entry.internalSizeStr = getSizeStr(entry.internalSize);
1093                                entry.externalSize = getTotalExternalSize(stats);
1094                                entry.externalSizeStr = getSizeStr(entry.externalSize);
1095                                if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
1096                                        + ": " + entry.sizeStr);
1097                                sizeChanged = true;
1098                            }
1099                        }
1100                        if (sizeChanged) {
1101                            Message msg = mMainHandler.obtainMessage(
1102                                    MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
1103                            mMainHandler.sendMessage(msg);
1104                        }
1105                    }
1106                    if (mCurComputingSizePkg != null
1107                            && (mCurComputingSizePkg.equals(stats.packageName)
1108                            && mCurComputingSizeUserId == stats.userHandle)) {
1109                        mCurComputingSizePkg = null;
1110                        sendEmptyMessage(MSG_LOAD_SIZES);
1111                    }
1112                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1113                }
1114            }
1115        };
1116    }
1117
1118    /**
1119     * Receives notifications when applications are added/removed.
1120     */
1121    private class PackageIntentReceiver extends BroadcastReceiver {
1122        void registerReceiver() {
1123            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1124            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1125            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1126            filter.addDataScheme("package");
1127            mContext.registerReceiver(this, filter);
1128            // Register for events related to sdcard installation.
1129            IntentFilter sdFilter = new IntentFilter();
1130            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1131            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1132            mContext.registerReceiver(this, sdFilter);
1133            // Register for events related to user creation/deletion.
1134            IntentFilter userFilter = new IntentFilter();
1135            userFilter.addAction(Intent.ACTION_USER_ADDED);
1136            userFilter.addAction(Intent.ACTION_USER_REMOVED);
1137            mContext.registerReceiver(this, userFilter);
1138        }
1139        void unregisterReceiver() {
1140            mContext.unregisterReceiver(this);
1141        }
1142        @Override
1143        public void onReceive(Context context, Intent intent) {
1144            String actionStr = intent.getAction();
1145            if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1146                Uri data = intent.getData();
1147                String pkgName = data.getEncodedSchemeSpecificPart();
1148                for (int i = 0; i < mEntriesMap.size(); i++) {
1149                    addPackage(pkgName, mEntriesMap.keyAt(i));
1150                }
1151            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1152                Uri data = intent.getData();
1153                String pkgName = data.getEncodedSchemeSpecificPart();
1154                for (int i = 0; i < mEntriesMap.size(); i++) {
1155                    removePackage(pkgName, mEntriesMap.keyAt(i));
1156                }
1157            } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1158                Uri data = intent.getData();
1159                String pkgName = data.getEncodedSchemeSpecificPart();
1160                for (int i = 0; i < mEntriesMap.size(); i++) {
1161                    invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1162                }
1163            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1164                    Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1165                // When applications become available or unavailable (perhaps because
1166                // the SD card was inserted or ejected) we need to refresh the
1167                // AppInfo with new label, icon and size information as appropriate
1168                // given the newfound (un)availability of the application.
1169                // A simple way to do that is to treat the refresh as a package
1170                // removal followed by a package addition.
1171                String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1172                if (pkgList == null || pkgList.length == 0) {
1173                    // Ignore
1174                    return;
1175                }
1176                boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1177                if (avail) {
1178                    for (String pkgName : pkgList) {
1179                        for (int i = 0; i < mEntriesMap.size(); i++) {
1180                            invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1181                        }
1182                    }
1183                }
1184            } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1185                addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1186            } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1187                removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1188            }
1189        }
1190    }
1191
1192    public interface Callbacks {
1193        void onRunningStateChanged(boolean running);
1194        void onPackageListChanged();
1195        void onRebuildComplete(ArrayList<AppEntry> apps);
1196        void onPackageIconChanged();
1197        void onPackageSizeChanged(String packageName);
1198        void onAllSizesComputed();
1199        void onLauncherInfoChanged();
1200        void onLoadEntriesCompleted();
1201    }
1202
1203    public static class SizeInfo {
1204        public long cacheSize;
1205        public long codeSize;
1206        public long dataSize;
1207        public long externalCodeSize;
1208        public long externalDataSize;
1209
1210        // This is the part of externalDataSize that is in the cache
1211        // section of external storage.  Note that we don't just combine
1212        // this with cacheSize because currently the platform can't
1213        // automatically trim this data when needed, so it is something
1214        // the user may need to manage.  The externalDataSize also includes
1215        // this value, since what this is here is really the part of
1216        // externalDataSize that we can just consider to be "cache" files
1217        // for purposes of cleaning them up in the app details UI.
1218        public long externalCacheSize;
1219    }
1220
1221    public static class AppEntry extends SizeInfo {
1222        public final File apkFile;
1223        public final long id;
1224        public String label;
1225        public long size;
1226        public long internalSize;
1227        public long externalSize;
1228
1229        public boolean mounted;
1230
1231        /**
1232         * Setting this to {@code true} prevents the entry to be filtered by
1233         * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1234         */
1235        public boolean hasLauncherEntry;
1236
1237        /**
1238         * Whether or not it's a Home app.
1239         */
1240        public boolean isHomeApp;
1241
1242        public String getNormalizedLabel() {
1243            if (normalizedLabel != null) {
1244                return normalizedLabel;
1245            }
1246            normalizedLabel = normalize(label);
1247            return normalizedLabel;
1248        }
1249
1250        // Need to synchronize on 'this' for the following.
1251        public ApplicationInfo info;
1252        public Drawable icon;
1253        public String sizeStr;
1254        public String internalSizeStr;
1255        public String externalSizeStr;
1256        public boolean sizeStale;
1257        public long sizeLoadStart;
1258
1259        public String normalizedLabel;
1260
1261        // A location where extra info can be placed to be used by custom filters.
1262        public Object extraInfo;
1263
1264        AppEntry(Context context, ApplicationInfo info, long id) {
1265            apkFile = new File(info.sourceDir);
1266            this.id = id;
1267            this.info = info;
1268            this.size = SIZE_UNKNOWN;
1269            this.sizeStale = true;
1270            ensureLabel(context);
1271        }
1272
1273        public void ensureLabel(Context context) {
1274            if (this.label == null || !this.mounted) {
1275                if (!this.apkFile.exists()) {
1276                    this.mounted = false;
1277                    this.label = info.packageName;
1278                } else {
1279                    this.mounted = true;
1280                    CharSequence label = info.loadLabel(context.getPackageManager());
1281                    this.label = label != null ? label.toString() : info.packageName;
1282                }
1283            }
1284        }
1285
1286        boolean ensureIconLocked(Context context, IconDrawableFactory drawableFactory) {
1287            if (this.icon == null) {
1288                if (this.apkFile.exists()) {
1289                    this.icon = drawableFactory.getBadgedIcon(info);
1290                    return true;
1291                } else {
1292                    this.mounted = false;
1293                    this.icon = context.getDrawable(R.drawable.sym_app_on_sd_unavailable_icon);
1294                }
1295            } else if (!this.mounted) {
1296                // If the app wasn't mounted but is now mounted, reload
1297                // its icon.
1298                if (this.apkFile.exists()) {
1299                    this.mounted = true;
1300                    this.icon = drawableFactory.getBadgedIcon(info);
1301                    return true;
1302                }
1303            }
1304            return false;
1305        }
1306
1307        public String getVersion(Context context) {
1308            try {
1309                return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1310            } catch (PackageManager.NameNotFoundException e) {
1311                return "";
1312            }
1313        }
1314    }
1315
1316    /**
1317     * Compare by label, then package name, then uid.
1318     */
1319    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
1320        private final Collator sCollator = Collator.getInstance();
1321        @Override
1322        public int compare(AppEntry object1, AppEntry object2) {
1323            int compareResult = sCollator.compare(object1.label, object2.label);
1324            if (compareResult != 0) {
1325                return compareResult;
1326            }
1327            if (object1.info != null && object2.info != null) {
1328                compareResult =
1329                    sCollator.compare(object1.info.packageName, object2.info.packageName);
1330                if (compareResult != 0) {
1331                    return compareResult;
1332                }
1333            }
1334            return object1.info.uid - object2.info.uid;
1335        }
1336    };
1337
1338    public static final Comparator<AppEntry> SIZE_COMPARATOR
1339            = new Comparator<AppEntry>() {
1340        @Override
1341        public int compare(AppEntry object1, AppEntry object2) {
1342            if (object1.size < object2.size) return 1;
1343            if (object1.size > object2.size) return -1;
1344            return ALPHA_COMPARATOR.compare(object1, object2);
1345        }
1346    };
1347
1348    public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
1349            = new Comparator<AppEntry>() {
1350        @Override
1351        public int compare(AppEntry object1, AppEntry object2) {
1352            if (object1.internalSize < object2.internalSize) return 1;
1353            if (object1.internalSize > object2.internalSize) return -1;
1354            return ALPHA_COMPARATOR.compare(object1, object2);
1355        }
1356    };
1357
1358    public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
1359            = new Comparator<AppEntry>() {
1360        @Override
1361        public int compare(AppEntry object1, AppEntry object2) {
1362            if (object1.externalSize < object2.externalSize) return 1;
1363            if (object1.externalSize > object2.externalSize) return -1;
1364            return ALPHA_COMPARATOR.compare(object1, object2);
1365        }
1366    };
1367
1368    public interface AppFilter {
1369        void init();
1370        default void init(Context context) {
1371            init();
1372        }
1373        boolean filterApp(AppEntry info);
1374    }
1375
1376    public static final AppFilter FILTER_PERSONAL = new AppFilter() {
1377        private int mCurrentUser;
1378
1379        @Override
1380        public void init() {
1381            mCurrentUser = ActivityManager.getCurrentUser();
1382        }
1383
1384        @Override
1385        public boolean filterApp(AppEntry entry) {
1386            return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1387        }
1388    };
1389
1390    public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
1391        @Override
1392        public void init() {
1393            // do nothing
1394        }
1395
1396        @Override
1397        public boolean filterApp(AppEntry entry) {
1398            return entry.info.enabledSetting
1399                    != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1400        }
1401    };
1402
1403    public static final AppFilter FILTER_WORK = new AppFilter() {
1404        private int mCurrentUser;
1405
1406        @Override
1407        public void init() {
1408            mCurrentUser = ActivityManager.getCurrentUser();
1409        }
1410
1411        @Override
1412        public boolean filterApp(AppEntry entry) {
1413            return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
1414        }
1415    };
1416
1417    /**
1418     * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1419     */
1420    public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
1421        @Override
1422        public void init() {
1423        }
1424
1425        @Override
1426        public boolean filterApp(AppEntry entry) {
1427            if (AppUtils.isInstant(entry.info)) {
1428                return false;
1429            } else if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1430                return true;
1431            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1432                return true;
1433            } else if (entry.hasLauncherEntry) {
1434                return true;
1435            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) {
1436                return true;
1437            }
1438            return false;
1439        }
1440    };
1441
1442    /**
1443     * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1444     */
1445    public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new AppFilter() {
1446
1447        @Override
1448        public void init() {
1449        }
1450
1451        @Override
1452        public boolean filterApp(AppEntry entry) {
1453            return AppUtils.isInstant(entry.info)
1454                    || FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry);
1455        }
1456
1457    };
1458
1459    public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
1460        @Override
1461        public void init() {
1462        }
1463
1464        @Override
1465        public boolean filterApp(AppEntry entry) {
1466            if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1467                return true;
1468            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1469                return true;
1470            }
1471            return false;
1472        }
1473    };
1474
1475    public static final AppFilter FILTER_DISABLED = new AppFilter() {
1476        @Override
1477        public void init() {
1478        }
1479
1480        @Override
1481        public boolean filterApp(AppEntry entry) {
1482            return !entry.info.enabled && !AppUtils.isInstant(entry.info);
1483        }
1484    };
1485
1486    public static final AppFilter FILTER_INSTANT = new AppFilter() {
1487        @Override
1488        public void init() {
1489        }
1490
1491        @Override
1492        public boolean filterApp(AppEntry entry) {
1493            return AppUtils.isInstant(entry.info);
1494        }
1495    };
1496
1497    public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
1498        @Override
1499        public void init() {
1500        }
1501
1502        @Override
1503        public boolean filterApp(AppEntry entry) {
1504            return entry.info.enabled && !AppUtils.isInstant(entry.info);
1505        }
1506    };
1507
1508    public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
1509        @Override
1510        public void init() {
1511        }
1512
1513        @Override
1514        public boolean filterApp(AppEntry entry) {
1515            return true;
1516        }
1517    };
1518
1519    public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
1520        @Override
1521        public void init() {
1522        }
1523
1524        @Override
1525        public boolean filterApp(AppEntry entry) {
1526            return !AppUtils.isInstant(entry.info)
1527                && (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
1528        }
1529    };
1530
1531    public static final AppFilter FILTER_NOT_HIDE = new AppFilter() {
1532        private String[] mHidePackageNames;
1533
1534        @Override
1535        public void init(Context context) {
1536            mHidePackageNames = context.getResources()
1537                .getStringArray(R.array.config_hideWhenDisabled_packageNames);
1538        }
1539
1540        @Override
1541        public void init() {
1542        }
1543
1544        @Override
1545        public boolean filterApp(AppEntry entry) {
1546            if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
1547                if (!entry.info.enabled) {
1548                    return false;
1549                } else if (entry.info.enabledSetting ==
1550                    PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED) {
1551                    return false;
1552                }
1553            }
1554
1555            return true;
1556        }
1557    };
1558
1559    public static final AppFilter FILTER_GAMES = new AppFilter() {
1560        @Override
1561        public void init() {
1562        }
1563
1564        @Override
1565        public boolean filterApp(ApplicationsState.AppEntry info) {
1566            // TODO: Update for the new game category.
1567            boolean isGame;
1568            synchronized (info.info) {
1569                isGame = ((info.info.flags & ApplicationInfo.FLAG_IS_GAME) != 0)
1570                        || info.info.category == ApplicationInfo.CATEGORY_GAME;
1571            }
1572            return isGame;
1573        }
1574    };
1575
1576    public static class VolumeFilter implements AppFilter {
1577        private final String mVolumeUuid;
1578
1579        public VolumeFilter(String volumeUuid) {
1580            mVolumeUuid = volumeUuid;
1581        }
1582
1583        @Override
1584        public void init() {
1585        }
1586
1587        @Override
1588        public boolean filterApp(AppEntry info) {
1589            return Objects.equals(info.info.volumeUuid, mVolumeUuid);
1590        }
1591    }
1592
1593    public static class CompoundFilter implements AppFilter {
1594        private final AppFilter mFirstFilter;
1595        private final AppFilter mSecondFilter;
1596
1597        public CompoundFilter(AppFilter first, AppFilter second) {
1598            mFirstFilter = first;
1599            mSecondFilter = second;
1600        }
1601
1602        @Override
1603        public void init(Context context) {
1604            mFirstFilter.init(context);
1605            mSecondFilter.init(context);
1606        }
1607
1608        @Override
1609        public void init() {
1610            mFirstFilter.init();
1611            mSecondFilter.init();
1612        }
1613
1614        @Override
1615        public boolean filterApp(AppEntry info) {
1616            return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);
1617        }
1618    }
1619
1620    public static final AppFilter FILTER_AUDIO = new AppFilter() {
1621        @Override
1622        public void init() {
1623        }
1624
1625        @Override
1626        public boolean filterApp(AppEntry entry) {
1627            boolean isMusicApp;
1628            synchronized(entry) {
1629                isMusicApp = entry.info.category == ApplicationInfo.CATEGORY_AUDIO;
1630            }
1631            return isMusicApp;
1632        }
1633    };
1634
1635    public static final AppFilter FILTER_MOVIES = new AppFilter() {
1636        @Override
1637        public void init() {
1638        }
1639
1640        @Override
1641        public boolean filterApp(AppEntry entry) {
1642            boolean isMovieApp;
1643            synchronized(entry) {
1644                isMovieApp = entry.info.category == ApplicationInfo.CATEGORY_VIDEO;
1645            }
1646            return isMovieApp;
1647        }
1648    };
1649
1650    public static final AppFilter FILTER_PHOTOS =
1651            new AppFilter() {
1652                @Override
1653                public void init() {}
1654
1655                @Override
1656                public boolean filterApp(AppEntry entry) {
1657                    boolean isPhotosApp;
1658                    synchronized (entry) {
1659                        isPhotosApp = entry.info.category == ApplicationInfo.CATEGORY_IMAGE;
1660                    }
1661                    return isPhotosApp;
1662                }
1663            };
1664
1665    public static final AppFilter FILTER_OTHER_APPS =
1666            new AppFilter() {
1667                @Override
1668                public void init() {}
1669
1670                @Override
1671                public boolean filterApp(AppEntry entry) {
1672                    boolean isCategorized;
1673                    synchronized (entry) {
1674                        isCategorized =
1675                                FILTER_AUDIO.filterApp(entry)
1676                                        || FILTER_GAMES.filterApp(entry)
1677                                        || FILTER_MOVIES.filterApp(entry)
1678                                        || FILTER_PHOTOS.filterApp(entry);
1679                    }
1680                    return !isCategorized;
1681                }
1682            };
1683}
1684