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