ApplicationsState.java revision 8089d48968cb2c5a9023842fb6d955a15580b69f
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 (mRebuildingSessions) {
586                    mRebuildingSessions.add(this);
587                    mRebuildRequested = true;
588                    mRebuildAsync = true;
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                return null;
601            }
602        }
603
604        void handleRebuildList() {
605            AppFilter filter;
606            Comparator<AppEntry> comparator;
607            synchronized (mRebuildSync) {
608                if (!mRebuildRequested) {
609                    return;
610                }
611
612                filter = mRebuildFilter;
613                comparator = mRebuildComparator;
614                mRebuildRequested = false;
615                mRebuildFilter = null;
616                mRebuildComparator = null;
617                if (mRebuildForeground) {
618                    Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
619                    mRebuildForeground = false;
620                }
621            }
622
623            if (filter != null) {
624                filter.init();
625            }
626
627            List<AppEntry> apps;
628            synchronized (mEntriesMap) {
629                apps = new ArrayList<>(mAppEntries);
630            }
631
632            ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
633            if (DEBUG) Log.i(TAG, "Rebuilding...");
634            for (int i=0; i<apps.size(); i++) {
635                AppEntry entry = apps.get(i);
636                if (entry != null && (filter == null || filter.filterApp(entry))) {
637                    synchronized (mEntriesMap) {
638                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
639                        if (comparator != null) {
640                            // Only need the label if we are going to be sorting.
641                            entry.ensureLabel(mContext);
642                        }
643                        if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
644                        filteredApps.add(entry);
645                        if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
646                    }
647                }
648            }
649
650            if (comparator != null) {
651                Collections.sort(filteredApps, comparator);
652            }
653
654            synchronized (mRebuildSync) {
655                if (!mRebuildRequested) {
656                    mLastAppList = filteredApps;
657                    if (!mRebuildAsync) {
658                        mRebuildResult = filteredApps;
659                        mRebuildSync.notifyAll();
660                    } else {
661                        if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
662                            Message msg = mMainHandler.obtainMessage(
663                                    MainHandler.MSG_REBUILD_COMPLETE, this);
664                            mMainHandler.sendMessage(msg);
665                        }
666                    }
667                }
668            }
669
670            Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
671        }
672
673        public void release() {
674            pause();
675            synchronized (mEntriesMap) {
676                mSessions.remove(this);
677            }
678        }
679    }
680
681    class MainHandler extends Handler {
682        static final int MSG_REBUILD_COMPLETE = 1;
683        static final int MSG_PACKAGE_LIST_CHANGED = 2;
684        static final int MSG_PACKAGE_ICON_CHANGED = 3;
685        static final int MSG_PACKAGE_SIZE_CHANGED = 4;
686        static final int MSG_ALL_SIZES_COMPUTED = 5;
687        static final int MSG_RUNNING_STATE_CHANGED = 6;
688        static final int MSG_LAUNCHER_INFO_CHANGED = 7;
689        static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
690
691        public MainHandler(Looper looper) {
692            super(looper);
693        }
694
695        @Override
696        public void handleMessage(Message msg) {
697            rebuildActiveSessions();
698            switch (msg.what) {
699                case MSG_REBUILD_COMPLETE: {
700                    Session s = (Session)msg.obj;
701                    if (mActiveSessions.contains(s)) {
702                        s.mCallbacks.onRebuildComplete(s.mLastAppList);
703                    }
704                } break;
705                case MSG_PACKAGE_LIST_CHANGED: {
706                    for (int i=0; i<mActiveSessions.size(); i++) {
707                        mActiveSessions.get(i).mCallbacks.onPackageListChanged();
708                    }
709                } break;
710                case MSG_PACKAGE_ICON_CHANGED: {
711                    for (int i=0; i<mActiveSessions.size(); i++) {
712                        mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
713                    }
714                } break;
715                case MSG_PACKAGE_SIZE_CHANGED: {
716                    for (int i=0; i<mActiveSessions.size(); i++) {
717                        mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
718                                (String)msg.obj);
719                    }
720                } break;
721                case MSG_ALL_SIZES_COMPUTED: {
722                    for (int i=0; i<mActiveSessions.size(); i++) {
723                        mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
724                    }
725                } break;
726                case MSG_RUNNING_STATE_CHANGED: {
727                    for (int i=0; i<mActiveSessions.size(); i++) {
728                        mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
729                                msg.arg1 != 0);
730                    }
731                } break;
732                case MSG_LAUNCHER_INFO_CHANGED: {
733                    for (int i=0; i<mActiveSessions.size(); i++) {
734                        mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
735                    }
736                } break;
737                case MSG_LOAD_ENTRIES_COMPLETE: {
738                    for (int i=0; i<mActiveSessions.size(); i++) {
739                        mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
740                    }
741                } break;
742            }
743        }
744    }
745
746    private class BackgroundHandler extends Handler {
747        static final int MSG_REBUILD_LIST = 1;
748        static final int MSG_LOAD_ENTRIES = 2;
749        static final int MSG_LOAD_ICONS = 3;
750        static final int MSG_LOAD_SIZES = 4;
751        static final int MSG_LOAD_LAUNCHER = 5;
752        static final int MSG_LOAD_HOME_APP = 6;
753
754        boolean mRunning;
755
756        BackgroundHandler(Looper looper) {
757            super(looper);
758        }
759
760        @Override
761        public void handleMessage(Message msg) {
762            // Always try rebuilding list first thing, if needed.
763            ArrayList<Session> rebuildingSessions = null;
764            synchronized (mRebuildingSessions) {
765                if (mRebuildingSessions.size() > 0) {
766                    rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
767                    mRebuildingSessions.clear();
768                }
769            }
770            if (rebuildingSessions != null) {
771                for (int i=0; i<rebuildingSessions.size(); i++) {
772                    rebuildingSessions.get(i).handleRebuildList();
773                }
774            }
775
776            switch (msg.what) {
777                case MSG_REBUILD_LIST: {
778                } break;
779                case MSG_LOAD_ENTRIES: {
780                    int numDone = 0;
781                    synchronized (mEntriesMap) {
782                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
783                        for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
784                            if (!mRunning) {
785                                mRunning = true;
786                                Message m = mMainHandler.obtainMessage(
787                                        MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
788                                mMainHandler.sendMessage(m);
789                            }
790                            ApplicationInfo info = mApplications.get(i);
791                            int userId = UserHandle.getUserId(info.uid);
792                            if (mEntriesMap.get(userId).get(info.packageName) == null) {
793                                numDone++;
794                                getEntryLocked(info);
795                            }
796                            if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
797                                // If this app is for a profile and we are on the owner, remove
798                                // the owner entry if it isn't installed.  This will prevent
799                                // duplicates of work only apps showing up as 'not installed
800                                // for this user'.
801                                // Note: This depends on us traversing the users in order, which
802                                // happens because of the way we generate the list in
803                                // doResumeIfNeededLocked.
804                                AppEntry entry = mEntriesMap.get(0).get(info.packageName);
805                                if (entry != null &&
806                                        (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
807                                    mEntriesMap.get(0).remove(info.packageName);
808                                    mAppEntries.remove(entry);
809                                }
810                            }
811                        }
812                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
813                    }
814
815                    if (numDone >= 6) {
816                        sendEmptyMessage(MSG_LOAD_ENTRIES);
817                    } else {
818                        if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
819                            mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
820                        }
821                        sendEmptyMessage(MSG_LOAD_HOME_APP);
822                    }
823                } break;
824                case MSG_LOAD_HOME_APP: {
825                    final List<ResolveInfo> homeActivities = new ArrayList<>();
826                    mPm.getHomeActivities(homeActivities);
827                    synchronized (mEntriesMap) {
828                        final int entryCount = mEntriesMap.size();
829                        for (int i = 0; i < entryCount; i++) {
830                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
831                            final HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
832                            for (ResolveInfo activity : homeActivities) {
833                                String packageName = activity.activityInfo.packageName;
834                                AppEntry entry = userEntries.get(packageName);
835                                if (entry != null) {
836                                    entry.isHomeApp = true;
837                                }
838                            }
839                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
840                        }
841                    }
842                    sendEmptyMessage(MSG_LOAD_LAUNCHER);
843                }
844                break;
845                case MSG_LOAD_LAUNCHER: {
846                    Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
847                            .addCategory(Intent.CATEGORY_LAUNCHER);
848                    for (int i = 0; i < mEntriesMap.size(); i++) {
849                        int userId = mEntriesMap.keyAt(i);
850                        // If we do not specify MATCH_DIRECT_BOOT_AWARE or
851                        // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
852                        // according to the user's lock state. When the user is locked, components
853                        // with ComponentInfo#directBootAware == false will be filtered. We should
854                        // explicitly include both direct boot aware and unaware components here.
855                        List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
856                                launchIntent,
857                                PackageManager.GET_DISABLED_COMPONENTS
858                                        | PackageManager.MATCH_DIRECT_BOOT_AWARE
859                                        | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
860                                userId
861                        );
862                        synchronized (mEntriesMap) {
863                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
864                            HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
865                            final int N = intents.size();
866                            for (int j = 0; j < N; j++) {
867                                String packageName = intents.get(j).activityInfo.packageName;
868                                AppEntry entry = userEntries.get(packageName);
869                                if (entry != null) {
870                                    entry.hasLauncherEntry = true;
871                                } else {
872                                    Log.w(TAG, "Cannot find pkg: " + packageName
873                                            + " on user " + userId);
874                                }
875                            }
876                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
877                        }
878                    }
879
880                    if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
881                        mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
882                    }
883                    sendEmptyMessage(MSG_LOAD_ICONS);
884                } break;
885                case MSG_LOAD_ICONS: {
886                    int numDone = 0;
887                    synchronized (mEntriesMap) {
888                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
889                        for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
890                            AppEntry entry = mAppEntries.get(i);
891                            if (entry.icon == null || !entry.mounted) {
892                                synchronized (entry) {
893                                    if (entry.ensureIconLocked(mContext, mPm)) {
894                                        if (!mRunning) {
895                                            mRunning = true;
896                                            Message m = mMainHandler.obtainMessage(
897                                                    MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
898                                            mMainHandler.sendMessage(m);
899                                        }
900                                        numDone++;
901                                    }
902                                }
903                            }
904                        }
905                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
906                    }
907                    if (numDone > 0) {
908                        if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
909                            mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
910                        }
911                    }
912                    if (numDone >= 2) {
913                        sendEmptyMessage(MSG_LOAD_ICONS);
914                    } else {
915                        sendEmptyMessage(MSG_LOAD_SIZES);
916                    }
917                } break;
918                case MSG_LOAD_SIZES: {
919                    synchronized (mEntriesMap) {
920                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
921                        if (mCurComputingSizePkg != null) {
922                            if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
923                            return;
924                        }
925
926                        long now = SystemClock.uptimeMillis();
927                        for (int i=0; i<mAppEntries.size(); i++) {
928                            AppEntry entry = mAppEntries.get(i);
929                            if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
930                                if (entry.sizeLoadStart == 0 ||
931                                        (entry.sizeLoadStart < (now-20*1000))) {
932                                    if (!mRunning) {
933                                        mRunning = true;
934                                        Message m = mMainHandler.obtainMessage(
935                                                MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
936                                        mMainHandler.sendMessage(m);
937                                    }
938                                    entry.sizeLoadStart = now;
939                                    mCurComputingSizePkg = entry.info.packageName;
940                                    mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
941                                    mPm.getPackageSizeInfoAsUser(mCurComputingSizePkg,
942                                            mCurComputingSizeUserId, mStatsObserver);
943                                }
944                                if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
945                                return;
946                            }
947                        }
948                        if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
949                            mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
950                            mRunning = false;
951                            Message m = mMainHandler.obtainMessage(
952                                    MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
953                            mMainHandler.sendMessage(m);
954                        }
955                        if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
956                    }
957                } break;
958            }
959        }
960
961        final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
962            public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
963                boolean sizeChanged = false;
964                synchronized (mEntriesMap) {
965                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
966                    HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
967                    if (userMap == null) {
968                        // The user must have been removed.
969                        return;
970                    }
971                    AppEntry entry = userMap.get(stats.packageName);
972                    if (entry != null) {
973                        synchronized (entry) {
974                            entry.sizeStale = false;
975                            entry.sizeLoadStart = 0;
976                            long externalCodeSize = stats.externalCodeSize
977                                    + stats.externalObbSize;
978                            long externalDataSize = stats.externalDataSize
979                                    + stats.externalMediaSize;
980                            long newSize = externalCodeSize + externalDataSize
981                                    + getTotalInternalSize(stats);
982                            if (entry.size != newSize ||
983                                    entry.cacheSize != stats.cacheSize ||
984                                    entry.codeSize != stats.codeSize ||
985                                    entry.dataSize != stats.dataSize ||
986                                    entry.externalCodeSize != externalCodeSize ||
987                                    entry.externalDataSize != externalDataSize ||
988                                    entry.externalCacheSize != stats.externalCacheSize) {
989                                entry.size = newSize;
990                                entry.cacheSize = stats.cacheSize;
991                                entry.codeSize = stats.codeSize;
992                                entry.dataSize = stats.dataSize;
993                                entry.externalCodeSize = externalCodeSize;
994                                entry.externalDataSize = externalDataSize;
995                                entry.externalCacheSize = stats.externalCacheSize;
996                                entry.sizeStr = getSizeStr(entry.size);
997                                entry.internalSize = getTotalInternalSize(stats);
998                                entry.internalSizeStr = getSizeStr(entry.internalSize);
999                                entry.externalSize = getTotalExternalSize(stats);
1000                                entry.externalSizeStr = getSizeStr(entry.externalSize);
1001                                if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
1002                                        + ": " + entry.sizeStr);
1003                                sizeChanged = true;
1004                            }
1005                        }
1006                        if (sizeChanged) {
1007                            Message msg = mMainHandler.obtainMessage(
1008                                    MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
1009                            mMainHandler.sendMessage(msg);
1010                        }
1011                    }
1012                    if (mCurComputingSizePkg != null
1013                            && (mCurComputingSizePkg.equals(stats.packageName)
1014                            && mCurComputingSizeUserId == stats.userHandle)) {
1015                        mCurComputingSizePkg = null;
1016                        sendEmptyMessage(MSG_LOAD_SIZES);
1017                    }
1018                    if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1019                }
1020            }
1021        };
1022    }
1023
1024    /**
1025     * Receives notifications when applications are added/removed.
1026     */
1027    private class PackageIntentReceiver extends BroadcastReceiver {
1028        void registerReceiver() {
1029            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1030            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1031            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1032            filter.addDataScheme("package");
1033            mContext.registerReceiver(this, filter);
1034            // Register for events related to sdcard installation.
1035            IntentFilter sdFilter = new IntentFilter();
1036            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1037            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1038            mContext.registerReceiver(this, sdFilter);
1039            // Register for events related to user creation/deletion.
1040            IntentFilter userFilter = new IntentFilter();
1041            userFilter.addAction(Intent.ACTION_USER_ADDED);
1042            userFilter.addAction(Intent.ACTION_USER_REMOVED);
1043            mContext.registerReceiver(this, userFilter);
1044        }
1045        void unregisterReceiver() {
1046            mContext.unregisterReceiver(this);
1047        }
1048        @Override
1049        public void onReceive(Context context, Intent intent) {
1050            String actionStr = intent.getAction();
1051            if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1052                Uri data = intent.getData();
1053                String pkgName = data.getEncodedSchemeSpecificPart();
1054                for (int i = 0; i < mEntriesMap.size(); i++) {
1055                    addPackage(pkgName, mEntriesMap.keyAt(i));
1056                }
1057            } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1058                Uri data = intent.getData();
1059                String pkgName = data.getEncodedSchemeSpecificPart();
1060                for (int i = 0; i < mEntriesMap.size(); i++) {
1061                    removePackage(pkgName, mEntriesMap.keyAt(i));
1062                }
1063            } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1064                Uri data = intent.getData();
1065                String pkgName = data.getEncodedSchemeSpecificPart();
1066                for (int i = 0; i < mEntriesMap.size(); i++) {
1067                    invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1068                }
1069            } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1070                    Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1071                // When applications become available or unavailable (perhaps because
1072                // the SD card was inserted or ejected) we need to refresh the
1073                // AppInfo with new label, icon and size information as appropriate
1074                // given the newfound (un)availability of the application.
1075                // A simple way to do that is to treat the refresh as a package
1076                // removal followed by a package addition.
1077                String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1078                if (pkgList == null || pkgList.length == 0) {
1079                    // Ignore
1080                    return;
1081                }
1082                boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1083                if (avail) {
1084                    for (String pkgName : pkgList) {
1085                        for (int i = 0; i < mEntriesMap.size(); i++) {
1086                            invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1087                        }
1088                    }
1089                }
1090            } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1091                addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1092            } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1093                removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1094            }
1095        }
1096    }
1097
1098    public interface Callbacks {
1099        void onRunningStateChanged(boolean running);
1100        void onPackageListChanged();
1101        void onRebuildComplete(ArrayList<AppEntry> apps);
1102        void onPackageIconChanged();
1103        void onPackageSizeChanged(String packageName);
1104        void onAllSizesComputed();
1105        void onLauncherInfoChanged();
1106        void onLoadEntriesCompleted();
1107    }
1108
1109    public static class SizeInfo {
1110        public long cacheSize;
1111        public long codeSize;
1112        public long dataSize;
1113        public long externalCodeSize;
1114        public long externalDataSize;
1115
1116        // This is the part of externalDataSize that is in the cache
1117        // section of external storage.  Note that we don't just combine
1118        // this with cacheSize because currently the platform can't
1119        // automatically trim this data when needed, so it is something
1120        // the user may need to manage.  The externalDataSize also includes
1121        // this value, since what this is here is really the part of
1122        // externalDataSize that we can just consider to be "cache" files
1123        // for purposes of cleaning them up in the app details UI.
1124        public long externalCacheSize;
1125    }
1126
1127    public static class AppEntry extends SizeInfo {
1128        public final File apkFile;
1129        public final long id;
1130        public String label;
1131        public long size;
1132        public long internalSize;
1133        public long externalSize;
1134
1135        public boolean mounted;
1136
1137        /**
1138         * Setting this to {@code true} prevents the entry to be filtered by
1139         * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1140         */
1141        public boolean hasLauncherEntry;
1142
1143        /**
1144         * Whether or not it's a Home app.
1145         */
1146        public boolean isHomeApp;
1147
1148        public String getNormalizedLabel() {
1149            if (normalizedLabel != null) {
1150                return normalizedLabel;
1151            }
1152            normalizedLabel = normalize(label);
1153            return normalizedLabel;
1154        }
1155
1156        // Need to synchronize on 'this' for the following.
1157        public ApplicationInfo info;
1158        public Drawable icon;
1159        public String sizeStr;
1160        public String internalSizeStr;
1161        public String externalSizeStr;
1162        public boolean sizeStale;
1163        public long sizeLoadStart;
1164
1165        public String normalizedLabel;
1166
1167        // A location where extra info can be placed to be used by custom filters.
1168        public Object extraInfo;
1169
1170        AppEntry(Context context, ApplicationInfo info, long id) {
1171            apkFile = new File(info.sourceDir);
1172            this.id = id;
1173            this.info = info;
1174            this.size = SIZE_UNKNOWN;
1175            this.sizeStale = true;
1176            ensureLabel(context);
1177        }
1178
1179        public void ensureLabel(Context context) {
1180            if (this.label == null || !this.mounted) {
1181                if (!this.apkFile.exists()) {
1182                    this.mounted = false;
1183                    this.label = info.packageName;
1184                } else {
1185                    this.mounted = true;
1186                    CharSequence label = info.loadLabel(context.getPackageManager());
1187                    this.label = label != null ? label.toString() : info.packageName;
1188                }
1189            }
1190        }
1191
1192        boolean ensureIconLocked(Context context, PackageManager pm) {
1193            if (this.icon == null) {
1194                if (this.apkFile.exists()) {
1195                    this.icon = getBadgedIcon(pm);
1196                    return true;
1197                } else {
1198                    this.mounted = false;
1199                    this.icon = context.getDrawable(
1200                            com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
1201                }
1202            } else if (!this.mounted) {
1203                // If the app wasn't mounted but is now mounted, reload
1204                // its icon.
1205                if (this.apkFile.exists()) {
1206                    this.mounted = true;
1207                    this.icon = getBadgedIcon(pm);
1208                    return true;
1209                }
1210            }
1211            return false;
1212        }
1213
1214        private Drawable getBadgedIcon(PackageManager pm) {
1215            // Do badging ourself so that it comes from the user of the app not the current user.
1216            return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
1217                    new UserHandle(UserHandle.getUserId(info.uid)));
1218        }
1219
1220        public String getVersion(Context context) {
1221            try {
1222                return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1223            } catch (PackageManager.NameNotFoundException e) {
1224                return "";
1225            }
1226        }
1227    }
1228
1229    /**
1230     * Compare by label, then package name, then uid.
1231     */
1232    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
1233        private final Collator sCollator = Collator.getInstance();
1234        @Override
1235        public int compare(AppEntry object1, AppEntry object2) {
1236            int compareResult = sCollator.compare(object1.label, object2.label);
1237            if (compareResult != 0) {
1238                return compareResult;
1239            }
1240            if (object1.info != null && object2.info != null) {
1241                compareResult =
1242                    sCollator.compare(object1.info.packageName, object2.info.packageName);
1243                if (compareResult != 0) {
1244                    return compareResult;
1245                }
1246            }
1247            return object1.info.uid - object2.info.uid;
1248        }
1249    };
1250
1251    public static final Comparator<AppEntry> SIZE_COMPARATOR
1252            = new Comparator<AppEntry>() {
1253        @Override
1254        public int compare(AppEntry object1, AppEntry object2) {
1255            if (object1.size < object2.size) return 1;
1256            if (object1.size > object2.size) return -1;
1257            return ALPHA_COMPARATOR.compare(object1, object2);
1258        }
1259    };
1260
1261    public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
1262            = new Comparator<AppEntry>() {
1263        @Override
1264        public int compare(AppEntry object1, AppEntry object2) {
1265            if (object1.internalSize < object2.internalSize) return 1;
1266            if (object1.internalSize > object2.internalSize) return -1;
1267            return ALPHA_COMPARATOR.compare(object1, object2);
1268        }
1269    };
1270
1271    public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
1272            = new Comparator<AppEntry>() {
1273        @Override
1274        public int compare(AppEntry object1, AppEntry object2) {
1275            if (object1.externalSize < object2.externalSize) return 1;
1276            if (object1.externalSize > object2.externalSize) return -1;
1277            return ALPHA_COMPARATOR.compare(object1, object2);
1278        }
1279    };
1280
1281    public interface AppFilter {
1282        void init();
1283        boolean filterApp(AppEntry info);
1284    }
1285
1286    public static final AppFilter FILTER_PERSONAL = new AppFilter() {
1287        private int mCurrentUser;
1288
1289        public void init() {
1290            mCurrentUser = ActivityManager.getCurrentUser();
1291        }
1292
1293        @Override
1294        public boolean filterApp(AppEntry entry) {
1295            return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1296        }
1297    };
1298
1299    public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
1300        public void init() {
1301            // do nothings
1302        }
1303
1304        @Override
1305        public boolean filterApp(AppEntry entry) {
1306            return entry.info.enabledSetting
1307                    != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1308        }
1309    };
1310
1311    public static final AppFilter FILTER_WORK = new AppFilter() {
1312        private int mCurrentUser;
1313
1314        public void init() {
1315            mCurrentUser = ActivityManager.getCurrentUser();
1316        }
1317
1318        @Override
1319        public boolean filterApp(AppEntry entry) {
1320            return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
1321        }
1322    };
1323
1324    /**
1325     * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1326     */
1327    public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
1328        public void init() {
1329        }
1330
1331        @Override
1332        public boolean filterApp(AppEntry entry) {
1333            if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1334                return true;
1335            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1336                return true;
1337            } else if (entry.hasLauncherEntry) {
1338                return true;
1339            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) != 0 && entry.isHomeApp) {
1340                return true;
1341            }
1342            return false;
1343        }
1344    };
1345
1346    public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
1347        public void init() {
1348        }
1349
1350        @Override
1351        public boolean filterApp(AppEntry entry) {
1352            if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1353                return true;
1354            } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1355                return true;
1356            }
1357            return false;
1358        }
1359    };
1360
1361    public static final AppFilter FILTER_DISABLED = new AppFilter() {
1362        public void init() {
1363        }
1364
1365        @Override
1366        public boolean filterApp(AppEntry entry) {
1367            return !entry.info.enabled;
1368        }
1369    };
1370
1371    public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
1372        public void init() {
1373        }
1374
1375        @Override
1376        public boolean filterApp(AppEntry entry) {
1377            return entry.info.enabled;
1378        }
1379    };
1380
1381    public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
1382        public void init() {
1383        }
1384
1385        @Override
1386        public boolean filterApp(AppEntry entry) {
1387            return true;
1388        }
1389    };
1390
1391    public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
1392        public void init() {
1393        }
1394
1395        @Override
1396        public boolean filterApp(AppEntry entry) {
1397            return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
1398        }
1399    };
1400
1401    public static class VolumeFilter implements AppFilter {
1402        private final String mVolumeUuid;
1403
1404        public VolumeFilter(String volumeUuid) {
1405            mVolumeUuid = volumeUuid;
1406        }
1407
1408        @Override
1409        public void init() {
1410        }
1411
1412        @Override
1413        public boolean filterApp(AppEntry info) {
1414            return Objects.equals(info.info.volumeUuid, mVolumeUuid);
1415        }
1416    }
1417
1418    public static class CompoundFilter implements AppFilter {
1419        private final AppFilter mFirstFilter;
1420        private final AppFilter mSecondFilter;
1421
1422        public CompoundFilter(AppFilter first, AppFilter second) {
1423            mFirstFilter = first;
1424            mSecondFilter = second;
1425        }
1426
1427        @Override
1428        public void init() {
1429            mFirstFilter.init();
1430            mSecondFilter.init();
1431        }
1432
1433        @Override
1434        public boolean filterApp(AppEntry info) {
1435            return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);
1436        }
1437    }
1438}
1439