[go: nahoru, domu]

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