OSDN Git Service

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