• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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.tv.settings.library.device.apps;
18 
19 import android.annotation.IntDef;
20 import android.app.ActivityManager;
21 import android.app.AppGlobals;
22 import android.app.Application;
23 import android.app.usage.StorageStats;
24 import android.app.usage.StorageStatsManager;
25 import android.content.BroadcastReceiver;
26 import android.content.Context;
27 import android.content.Intent;
28 import android.content.IntentFilter;
29 import android.content.pm.ApplicationInfo;
30 import android.content.pm.IPackageManager;
31 import android.content.pm.IPackageStatsObserver;
32 import android.content.pm.ModuleInfo;
33 import android.content.pm.PackageManager;
34 import android.content.pm.PackageManager.NameNotFoundException;
35 import android.content.pm.PackageStats;
36 import android.content.pm.ParceledListSlice;
37 import android.content.pm.ResolveInfo;
38 import android.content.pm.UserInfo;
39 import android.graphics.drawable.Drawable;
40 import android.net.Uri;
41 import android.os.Handler;
42 import android.os.HandlerThread;
43 import android.os.Looper;
44 import android.os.Message;
45 import android.os.Process;
46 import android.os.RemoteException;
47 import android.os.SystemClock;
48 import android.os.UserHandle;
49 import android.os.UserManager;
50 import android.text.format.Formatter;
51 import android.util.Log;
52 import android.util.SparseArray;
53 
54 import com.android.internal.annotations.VisibleForTesting;
55 import com.android.internal.util.ArrayUtils;
56 import com.android.tv.settings.library.util.LibUtils;
57 import com.android.tv.settings.library.util.ThreadUtils;
58 import com.android.tv.settings.library.util.lifecycle.Lifecycle;
59 import com.android.tv.settings.library.util.lifecycle.LifecycleObserver;
60 import com.android.tv.settings.library.util.lifecycle.events.OnDestroy;
61 import com.android.tv.settings.library.util.lifecycle.events.OnPause;
62 import com.android.tv.settings.library.util.lifecycle.events.OnResume;
63 
64 import java.io.File;
65 import java.io.IOException;
66 import java.lang.annotation.Retention;
67 import java.lang.annotation.RetentionPolicy;
68 import java.lang.ref.WeakReference;
69 import java.text.Collator;
70 import java.text.Normalizer;
71 import java.text.Normalizer.Form;
72 import java.util.ArrayList;
73 import java.util.Collections;
74 import java.util.Comparator;
75 import java.util.HashMap;
76 import java.util.HashSet;
77 import java.util.List;
78 import java.util.Objects;
79 import java.util.UUID;
80 import java.util.regex.Pattern;
81 
82 /**
83  * Keeps track of information about all installed applications, lazy-loading
84  * as needed.
85  */
86 public class ApplicationsState {
87     private static final String TAG = "ApplicationsState";
88 
89     public static final int SIZE_UNKNOWN = -1;
90     public static final int SIZE_INVALID = -2;
91 
92     // TODO(b/187728742): Migrate to use one flag.
93     private static final boolean DEBUG = false;
94     private static final boolean DEBUG_LOCKING = false;
95     private static final Object sLock = new Object();
96     private static final Pattern REMOVE_DIACRITICALS_PATTERN
97             = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
98 
99     @VisibleForTesting
100     static ApplicationsState sInstance;
101 
getInstance(Application app)102     public static ApplicationsState getInstance(Application app) {
103         return getInstance(app, AppGlobals.getPackageManager());
104     }
105 
106     @VisibleForTesting
getInstance(Application app, IPackageManager iPackageManager)107     static ApplicationsState getInstance(Application app, IPackageManager iPackageManager) {
108         synchronized (sLock) {
109             if (sInstance == null) {
110                 sInstance = new ApplicationsState(app, iPackageManager);
111             }
112             return sInstance;
113         }
114     }
115 
116     final Context mContext;
117     final PackageManager mPm;
118     final IPackageManager mIpm;
119     final UserManager mUm;
120     final StorageStatsManager mStats;
121     final int mAdminRetrieveFlags;
122     final int mRetrieveFlags;
123     ApplicationsState.PackageIntentReceiver
124             mPackageIntentReceiver;
125 
126     boolean mResumed;
127     boolean mHaveDisabledApps;
128     boolean mHaveInstantApps;
129 
130     // Information about all applications.  Synchronize on mEntriesMap
131     // to protect access to these.
132     final ArrayList<ApplicationsState.Session> mSessions = new ArrayList<>();
133     final ArrayList<ApplicationsState.Session> mRebuildingSessions = new ArrayList<>();
134     private InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
135     // Map: userid => (Map: package name => AppEntry)
136     final SparseArray<HashMap<String, ApplicationsState.AppEntry>> mEntriesMap =
137             new SparseArray<>();
138     final ArrayList<ApplicationsState.AppEntry> mAppEntries = new ArrayList<>();
139     List<ApplicationInfo> mApplications = new ArrayList<>();
140     long mCurId = 1;
141     UUID mCurComputingSizeUuid;
142     String mCurComputingSizePkg;
143     int mCurComputingSizeUserId;
144     boolean mSessionsChanged;
145     // Maps all installed modules on the system to whether they're hidden or not.
146     // TODO(b/382016780): to be removed after flag cleanup.
147     final HashMap<String, Boolean> mSystemModules = new HashMap<>();
148 
149     // Temporary for dispatching session callbacks.  Only touched by main thread.
150     final ArrayList<WeakReference<ApplicationsState.Session>> mActiveSessions = new ArrayList<>();
151 
152     final HandlerThread mThread;
153     final ApplicationsState.BackgroundHandler
154             mBackgroundHandler;
155     final ApplicationsState.MainHandler
156             mMainHandler = new ApplicationsState.MainHandler(Looper.getMainLooper());
157 
158     /** Requests that the home app is loaded. */
159     public static final int FLAG_SESSION_REQUEST_HOME_APP = 1 << 0;
160 
161     /** Requests that icons are loaded. */
162     public static final int FLAG_SESSION_REQUEST_ICONS = 1 << 1;
163 
164     /** Requests that sizes are loaded. */
165     public static final int FLAG_SESSION_REQUEST_SIZES = 1 << 2;
166 
167     /** Requests that launcher intents are resolved. */
168     public static final int FLAG_SESSION_REQUEST_LAUNCHER = 1 << 3;
169 
170     /** Requests that leanback launcher intents are resolved. */
171     public static final int FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER = 1 << 4;
172 
173     /**
174      * Flags to configure the session to request various types of info.
175      */
176     @IntDef( value = {
177             FLAG_SESSION_REQUEST_HOME_APP,
178             FLAG_SESSION_REQUEST_ICONS,
179             FLAG_SESSION_REQUEST_SIZES,
180             FLAG_SESSION_REQUEST_LAUNCHER,
181             FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER
182     })
183     @Retention(RetentionPolicy.SOURCE)
184     public @interface SessionFlags {
185     }
186 
187     @VisibleForTesting
setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges)188     void setInterestingConfigChanges(InterestingConfigChanges interestingConfigChanges) {
189         mInterestingConfigChanges = interestingConfigChanges;
190     }
191 
192     @ApplicationsState.SessionFlags
193     public static final int DEFAULT_SESSION_FLAGS =
194             FLAG_SESSION_REQUEST_HOME_APP | FLAG_SESSION_REQUEST_ICONS |
195                     FLAG_SESSION_REQUEST_SIZES | FLAG_SESSION_REQUEST_LAUNCHER;
196 
ApplicationsState(Application app, IPackageManager iPackageManager)197     private ApplicationsState(Application app, IPackageManager iPackageManager) {
198         mContext = app;
199         mPm = mContext.getPackageManager();
200         mIpm = iPackageManager;
201         mUm = mContext.getSystemService(UserManager.class);
202         mStats = mContext.getSystemService(StorageStatsManager.class);
203         for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
204             mEntriesMap.put(userId, new HashMap<>());
205         }
206 
207         mThread = new HandlerThread("ApplicationsState.Loader");
208         mThread.start();
209         mBackgroundHandler = new ApplicationsState.BackgroundHandler(mThread.getLooper());
210 
211         // Only the owner can see all apps.
212         mAdminRetrieveFlags = PackageManager.MATCH_ANY_USER |
213                 PackageManager.MATCH_DISABLED_COMPONENTS |
214                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
215         mRetrieveFlags = PackageManager.MATCH_DISABLED_COMPONENTS |
216                 PackageManager.MATCH_DISABLED_UNTIL_USED_COMPONENTS;
217 
218         if (!android.content.pm.Flags.removeHiddenModuleUsage()) {
219             final List<ModuleInfo> moduleInfos = mPm.getInstalledModules(0 /* flags */);
220             for (ModuleInfo info : moduleInfos) {
221                 mSystemModules.put(info.getPackageName(), info.isHidden());
222             }
223         }
224 
225         /**
226          * This is a trick to prevent the foreground thread from being delayed.
227          * The problem is that Dalvik monitors are initially spin locks, to keep
228          * them lightweight.  This leads to unfair contention -- Even though the
229          * background thread only holds the lock for a short amount of time, if
230          * it keeps running and locking again it can prevent the main thread from
231          * acquiring its lock for a long time...  sometimes even > 5 seconds
232          * (leading to an ANR).
233          *
234          * Dalvik will promote a monitor to a "real" lock if it detects enough
235          * contention on it.  It doesn't figure this out fast enough for us
236          * here, though, so this little trick will force it to turn into a real
237          * lock immediately.
238          */
239         synchronized (mEntriesMap) {
240             try {
241                 mEntriesMap.wait(1);
242             } catch (InterruptedException e) {
243             }
244         }
245     }
246 
getBackgroundLooper()247     public Looper getBackgroundLooper() {
248         return mThread.getLooper();
249     }
250 
newSession( ApplicationsState.Callbacks callbacks)251     public ApplicationsState.Session newSession(
252             ApplicationsState.Callbacks callbacks) {
253         return newSession(callbacks, null);
254     }
255 
newSession( ApplicationsState.Callbacks callbacks, Lifecycle lifecycle)256     public ApplicationsState.Session newSession(
257             ApplicationsState.Callbacks callbacks, Lifecycle lifecycle) {
258         ApplicationsState.Session
259                 s = new ApplicationsState.Session(callbacks, lifecycle);
260         synchronized (mEntriesMap) {
261             mSessions.add(s);
262         }
263         return s;
264     }
265 
doResumeIfNeededLocked()266     void doResumeIfNeededLocked() {
267         if (mResumed) {
268             return;
269         }
270         mResumed = true;
271         if (mPackageIntentReceiver == null) {
272             mPackageIntentReceiver = new ApplicationsState.PackageIntentReceiver();
273             mPackageIntentReceiver.registerReceiver();
274         }
275 
276         final List<ApplicationInfo> prevApplications = mApplications;
277         mApplications = new ArrayList<>();
278         for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
279             try {
280                 // If this user is new, it needs a map created.
281                 if (mEntriesMap.indexOfKey(user.id) < 0) {
282                     mEntriesMap.put(user.id, new HashMap<>());
283                 }
284                 @SuppressWarnings("unchecked")
285                 ParceledListSlice<ApplicationInfo> list =
286                         mIpm.getInstalledApplications(
287                                 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
288                                 user.id);
289                 mApplications.addAll(list.getList());
290             } catch (Exception e) {
291                 Log.e(TAG, "Error during doResumeIfNeededLocked", e);
292             }
293         }
294 
295         if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
296             // If an interesting part of the configuration has changed, we
297             // should completely reload the app entries.
298             clearEntries();
299         } else {
300             for (int i = 0; i < mAppEntries.size(); i++) {
301                 mAppEntries.get(i).sizeStale = true;
302             }
303         }
304 
305         mHaveDisabledApps = false;
306         mHaveInstantApps = false;
307         for (int i = 0; i < mApplications.size(); i++) {
308             final ApplicationInfo info = mApplications.get(i);
309             // Need to trim out any applications that are disabled by
310             // something different than the user.
311             if (!info.enabled) {
312                 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
313                     mApplications.remove(i);
314                     i--;
315                     continue;
316                 }
317                 mHaveDisabledApps = true;
318             }
319             if (!android.content.pm.Flags.removeHiddenModuleUsage()) {
320                 if (isHiddenModule(info.packageName)) {
321                     mApplications.remove(i--);
322                     continue;
323                 }
324             }
325             if (!mHaveInstantApps && AppUtils.isInstant(info)) {
326                 mHaveInstantApps = true;
327             }
328 
329             int userId = UserHandle.getUserId(info.uid);
330             final ApplicationsState.AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
331             if (entry != null) {
332                 entry.info = info;
333             }
334         }
335 
336         if (anyAppIsRemoved(prevApplications, mApplications)) {
337             // some apps have been uninstalled.
338             clearEntries();
339         }
340         mCurComputingSizePkg = null;
341         if (!mBackgroundHandler.hasMessages(
342                 ApplicationsState.BackgroundHandler.MSG_LOAD_ENTRIES)) {
343             mBackgroundHandler.sendEmptyMessage(
344                     ApplicationsState.BackgroundHandler.MSG_LOAD_ENTRIES);
345         }
346     }
347 
348     /* The original design is mAppEntries.size() > mApplications.size().
349        It's correct if there is only the owner user and only one app is removed.
350        Problem 1:
351        If there is a user profile, the size of mAppEntries < mApplications is normal because
352        the number of app entries on UI (mAppEntries) should be equal to the number of apps got
353        from PMS (mApplications).
354 
355        owner only case:
356        mApplications: user 0: 191
357        mAppEntries  : user 0: 191
358        total mAppEntries: 191, mApplications: 191
359        If an app is removed, cached mAppEntries: 191 , mApplications: 191 -> 190, it is detected
360        as the number of apps becomes less.
361 
362        If there is a work profile, mAppEntries removes some apps that are not installed for the
363        owner user.
364 
365        For example, in the following case, 6 apps are removed from mAppEntries for the owner.
366        mApplications: user 0: 197, user 10: 189 => total 386
367        mAppEntries  : user 0: 191, user 10: 189 => total 380
368        If an app is removed, cached mAppEntries: 380 , mApplications: 386 -> 385, the size of
369        mAppEntries is still not larger than mApplications, then does not clear mAppEntries.
370 
371        Problem 2:
372        If remove an app and add another app outside Settings (e.g. Play Store) and back to
373        Settings, the amount of apps are not changed, it causes the entries keep the removed app.
374 
375        Another case, if adding more apps than removing apps (e.g. add 2 apps and remove 1 app),
376        the final number of apps (mApplications) is even increased,
377 
378        Therefore, should not only count on number of apps to determine any app is removed.
379        Compare the change of applications instead.
380     */
anyAppIsRemoved(List<ApplicationInfo> prevApplications, List<ApplicationInfo> applications)381     private static boolean anyAppIsRemoved(List<ApplicationInfo> prevApplications,
382             List<ApplicationInfo> applications) {
383 
384         // No cache
385         if (prevApplications.size() == 0) {
386             return false;
387         }
388 
389         if (applications.size() < prevApplications.size()) {
390             return true;
391         }
392 
393         // build package sets of all applications <userId, HashSet of packages>
394         final HashMap<String, HashSet<String>> packageMap = new HashMap<>();
395         for (ApplicationInfo application : applications) {
396             final String userId = String.valueOf(UserHandle.getUserId(application.uid));
397 
398             HashSet<String> appPackages = packageMap.get(userId);
399             if (appPackages == null) {
400                 appPackages = new HashSet<>();
401                 packageMap.put(userId, appPackages);
402             }
403             if (hasFlag(application.flags, ApplicationInfo.FLAG_INSTALLED)) {
404                 appPackages.add(application.packageName);
405             }
406         }
407 
408         // detect any previous app is removed
409         for (ApplicationInfo prevApplication : prevApplications) {
410             if (!hasFlag(prevApplication.flags, ApplicationInfo.FLAG_INSTALLED)) {
411                 continue;
412             }
413             final String userId = String.valueOf(UserHandle.getUserId(prevApplication.uid));
414 
415             final HashSet<String> packagesSet = packageMap.get(userId);
416             if (packagesSet == null || !packagesSet.remove(prevApplication.packageName)) {
417                 return true;
418             }
419         }
420 
421         return false;
422     }
423 
424     @VisibleForTesting
clearEntries()425     void clearEntries() {
426         for (int i = 0; i < mEntriesMap.size(); i++) {
427             mEntriesMap.valueAt(i).clear();
428         }
429         mAppEntries.clear();
430     }
431 
haveDisabledApps()432     public boolean haveDisabledApps() {
433         return mHaveDisabledApps;
434     }
435 
haveInstantApps()436     public boolean haveInstantApps() {
437         return mHaveInstantApps;
438     }
439 
440     // TODO(b/382016780): to be removed after flag cleanup.
isHiddenModule(String packageName)441     boolean isHiddenModule(String packageName) {
442         Boolean isHidden = mSystemModules.get(packageName);
443         if (isHidden == null) {
444             return false;
445         }
446 
447         return isHidden;
448     }
449 
450     // TODO(b/382016780): to be removed after flag cleanup.
isSystemModule(String packageName)451     boolean isSystemModule(String packageName) {
452         return mSystemModules.containsKey(packageName);
453     }
454 
doPauseIfNeededLocked()455     void doPauseIfNeededLocked() {
456         if (!mResumed) {
457             return;
458         }
459         for (int i = 0; i < mSessions.size(); i++) {
460             if (mSessions.get(i).mResumed) {
461                 return;
462             }
463         }
464         doPauseLocked();
465     }
466 
doPauseLocked()467     void doPauseLocked() {
468         mResumed = false;
469         if (mPackageIntentReceiver != null) {
470             mPackageIntentReceiver.unregisterReceiver();
471             mPackageIntentReceiver = null;
472         }
473     }
474 
getEntry(String packageName, int userId)475     public ApplicationsState.AppEntry getEntry(String packageName, int userId) {
476         if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
477         synchronized (mEntriesMap) {
478             ApplicationsState.AppEntry entry = mEntriesMap.get(userId).get(packageName);
479             if (entry == null) {
480                 ApplicationInfo info = getAppInfoLocked(packageName, userId);
481                 if (info == null) {
482                     try {
483                         info = mIpm.getApplicationInfo(packageName, 0, userId);
484                     } catch (RemoteException e) {
485                         Log.w(TAG, "getEntry couldn't reach PackageManager", e);
486                         return null;
487                     }
488                 }
489                 if (info != null) {
490                     entry = getEntryLocked(info);
491                 }
492             }
493             if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
494             return entry;
495         }
496     }
497 
getAppInfoLocked(String pkg, int userId)498     private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
499         for (int i = 0; i < mApplications.size(); i++) {
500             ApplicationInfo info = mApplications.get(i);
501             if (pkg.equals(info.packageName)
502                     && userId == UserHandle.getUserId(info.uid)) {
503                 return info;
504             }
505         }
506         return null;
507     }
508 
ensureIcon(ApplicationsState.AppEntry entry)509     public void ensureIcon(ApplicationsState.AppEntry entry) {
510         if (entry.icon != null) {
511             return;
512         }
513         synchronized (entry) {
514             entry.ensureIconLocked(mContext);
515         }
516     }
517 
518     /**
519      * To generate and cache the label description.
520      *
521      * @param entry contain the entries of an app
522      */
ensureLabelDescription( ApplicationsState.AppEntry entry)523     public void ensureLabelDescription(
524             ApplicationsState.AppEntry entry) {
525         if (entry.labelDescription != null) {
526             return;
527         }
528         synchronized (entry) {
529             entry.ensureLabelDescriptionLocked(mContext);
530         }
531     }
532 
requestSize(String packageName, int userId)533     public void requestSize(String packageName, int userId) {
534         if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
535         synchronized (mEntriesMap) {
536             ApplicationsState.AppEntry entry = mEntriesMap.get(userId).get(packageName);
537             if (entry != null && hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)) {
538                 mBackgroundHandler.post(
539                         () -> {
540                             try {
541                                 final StorageStats stats =
542                                         mStats.queryStatsForPackage(
543                                                 entry.info.storageUuid,
544                                                 packageName,
545                                                 UserHandle.of(userId));
546                                 final long cacheQuota =
547                                         mStats.getCacheQuotaBytes(
548                                                 entry.info.storageUuid.toString(), entry.info.uid);
549                                 final PackageStats legacy = new PackageStats(packageName, userId);
550                                 legacy.codeSize = stats.getAppBytes();
551                                 legacy.dataSize = stats.getDataBytes();
552                                 legacy.cacheSize = Math.min(stats.getCacheBytes(), cacheQuota);
553                                 try {
554                                     mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
555                                             legacy, true);
556                                 } catch (RemoteException ignored) {
557                                 }
558                             } catch (NameNotFoundException | IOException e) {
559                                 Log.w(TAG, "Failed to query stats: " + e);
560                                 try {
561                                     mBackgroundHandler.mStatsObserver.onGetStatsCompleted(
562                                             null, false);
563                                 } catch (RemoteException ignored) {
564                                 }
565                             }
566                         });
567             }
568             if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
569         }
570     }
571 
sumCacheSizes()572     long sumCacheSizes() {
573         long sum = 0;
574         if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
575         synchronized (mEntriesMap) {
576             if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
577             for (int i = mAppEntries.size() - 1; i >= 0; i--) {
578                 sum += mAppEntries.get(i).cacheSize;
579             }
580             if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
581         }
582         return sum;
583     }
584 
indexOfApplicationInfoLocked(String pkgName, int userId)585     int indexOfApplicationInfoLocked(String pkgName, int userId) {
586         for (int i = mApplications.size() - 1; i >= 0; i--) {
587             ApplicationInfo appInfo = mApplications.get(i);
588             if (appInfo.packageName.equals(pkgName)
589                     && UserHandle.getUserId(appInfo.uid) == userId) {
590                 return i;
591             }
592         }
593         return -1;
594     }
595 
addPackage(String pkgName, int userId)596     void addPackage(String pkgName, int userId) {
597         try {
598             synchronized (mEntriesMap) {
599                 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
600                 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
601                 if (!mResumed) {
602                     // If we are not resumed, we will do a full query the
603                     // next time we resume, so there is no reason to do work
604                     // here.
605                     if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
606                     return;
607                 }
608                 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
609                     if (DEBUG) Log.i(TAG, "Package already exists!");
610                     if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
611                     return;
612                 }
613                 ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
614                         mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
615                         userId);
616                 if (info == null) {
617                     return;
618                 }
619                 if (!info.enabled) {
620                     if (info.enabledSetting
621                             != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
622                         return;
623                     }
624                     mHaveDisabledApps = true;
625                 }
626                 if (AppUtils.isInstant(info)) {
627                     mHaveInstantApps = true;
628                 }
629                 mApplications.add(info);
630                 if (!mBackgroundHandler.hasMessages(
631                         ApplicationsState.BackgroundHandler.MSG_LOAD_ENTRIES)) {
632                     mBackgroundHandler.sendEmptyMessage(
633                             ApplicationsState.BackgroundHandler.MSG_LOAD_ENTRIES);
634                 }
635                 if (!mMainHandler.hasMessages(
636                         ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
637                     mMainHandler.sendEmptyMessage(
638                             ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED);
639                 }
640                 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
641             }
642         } catch (RemoteException e) {
643         }
644     }
645 
removePackage(String pkgName, int userId)646     public void removePackage(String pkgName, int userId) {
647         synchronized (mEntriesMap) {
648             if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
649             int idx = indexOfApplicationInfoLocked(pkgName, userId);
650             if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
651             if (idx >= 0) {
652                 ApplicationsState.AppEntry entry = mEntriesMap.get(userId).get(pkgName);
653                 if (DEBUG) Log.i(TAG, "removePackage: " + entry);
654                 if (entry != null) {
655                     mEntriesMap.get(userId).remove(pkgName);
656                     mAppEntries.remove(entry);
657                 }
658                 ApplicationInfo info = mApplications.get(idx);
659                 mApplications.remove(idx);
660                 if (!info.enabled) {
661                     mHaveDisabledApps = false;
662                     for (ApplicationInfo otherInfo : mApplications) {
663                         if (!otherInfo.enabled) {
664                             mHaveDisabledApps = true;
665                             break;
666                         }
667                     }
668                 }
669                 if (AppUtils.isInstant(info)) {
670                     mHaveInstantApps = false;
671                     for (ApplicationInfo otherInfo : mApplications) {
672                         if (AppUtils.isInstant(otherInfo)) {
673                             mHaveInstantApps = true;
674                             break;
675                         }
676                     }
677                 }
678                 if (!mMainHandler.hasMessages(
679                         ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
680                     mMainHandler.sendEmptyMessage(
681                             ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED);
682                 }
683             }
684             if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
685         }
686     }
687 
invalidatePackage(String pkgName, int userId)688     public void invalidatePackage(String pkgName, int userId) {
689         removePackage(pkgName, userId);
690         addPackage(pkgName, userId);
691     }
692 
addUser(int userId)693     private void addUser(int userId) {
694         final int[] profileIds = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
695         if (ArrayUtils.contains(profileIds, userId)) {
696             synchronized (mEntriesMap) {
697                 mEntriesMap.put(userId, new HashMap<String, ApplicationsState.AppEntry>());
698                 if (mResumed) {
699                     // If resumed, Manually pause, then cause a resume to repopulate the app list.
700                     // This is the simplest way to reload the packages so that the new user
701                     // is included.  Otherwise the list will be repopulated on next resume.
702                     doPauseLocked();
703                     doResumeIfNeededLocked();
704                 }
705                 if (!mMainHandler.hasMessages(
706                         ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
707                     mMainHandler.sendEmptyMessage(
708                             ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED);
709                 }
710             }
711         }
712     }
713 
removeUser(int userId)714     private void removeUser(int userId) {
715         synchronized (mEntriesMap) {
716             HashMap<String, ApplicationsState.AppEntry> userMap = mEntriesMap.get(userId);
717             if (userMap != null) {
718                 for (ApplicationsState.AppEntry appEntry : userMap.values()) {
719                     mAppEntries.remove(appEntry);
720                     mApplications.remove(appEntry.info);
721                 }
722                 mEntriesMap.remove(userId);
723                 if (!mMainHandler.hasMessages(
724                         ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
725                     mMainHandler.sendEmptyMessage(
726                             ApplicationsState.MainHandler.MSG_PACKAGE_LIST_CHANGED);
727                 }
728             }
729         }
730     }
731 
getEntryLocked(ApplicationInfo info)732     private ApplicationsState.AppEntry getEntryLocked(ApplicationInfo info) {
733         int userId = UserHandle.getUserId(info.uid);
734         ApplicationsState.AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
735         if (DEBUG) {
736             Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
737         }
738         if (entry == null) {
739             if (!android.content.pm.Flags.removeHiddenModuleUsage()) {
740                 if (isHiddenModule(info.packageName)) {
741                     if (DEBUG) {
742                         Log.i(TAG, "No AppEntry for " + info.packageName + " (hidden module)");
743                     }
744                     return null;
745                 }
746             }
747             if (DEBUG) {
748                 Log.i(TAG, "Creating AppEntry for " + info.packageName);
749             }
750             entry = new ApplicationsState.AppEntry(mContext, info, mCurId++);
751             mEntriesMap.get(userId).put(info.packageName, entry);
752             mAppEntries.add(entry);
753         } else if (entry.info != info) {
754             entry.info = info;
755         }
756         return entry;
757     }
758 
759     // --------------------------------------------------------------
760 
getTotalInternalSize(PackageStats ps)761     private long getTotalInternalSize(PackageStats ps) {
762         if (ps != null) {
763             // We subtract the cache size because the system can clear it automatically and
764             // |dataSize| is a superset of |cacheSize|.
765             return ps.codeSize + ps.dataSize - ps.cacheSize;
766         }
767         return SIZE_INVALID;
768     }
769 
getTotalExternalSize(PackageStats ps)770     private long getTotalExternalSize(PackageStats ps) {
771         if (ps != null) {
772             // We also include the cache size here because for non-emulated
773             // we don't automatically clean cache files.
774             return ps.externalCodeSize + ps.externalDataSize
775                     + ps.externalCacheSize
776                     + ps.externalMediaSize + ps.externalObbSize;
777         }
778         return SIZE_INVALID;
779     }
780 
getSizeStr(long size)781     private String getSizeStr(long size) {
782         if (size >= 0) {
783             return Formatter.formatFileSize(mContext, size);
784         }
785         return null;
786     }
787 
rebuildActiveSessions()788     void rebuildActiveSessions() {
789         synchronized (mEntriesMap) {
790             if (!mSessionsChanged) {
791                 return;
792             }
793             mActiveSessions.clear();
794             for (int i = 0; i < mSessions.size(); i++) {
795                 ApplicationsState.Session s = mSessions.get(i);
796                 if (s.mResumed) {
797                     mActiveSessions.add(new WeakReference<>(s));
798                 }
799             }
800         }
801     }
802 
normalize(String str)803     public static String normalize(String str) {
804         String tmp = Normalizer.normalize(str, Form.NFD);
805         return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
806                 .replaceAll("").toLowerCase();
807     }
808 
809     public class Session implements LifecycleObserver, OnResume, OnPause, OnDestroy {
810 
811         final ApplicationsState.Callbacks mCallbacks;
812         boolean mResumed;
813 
814         // Rebuilding of app list.  Synchronized on mRebuildSync.
815         final Object mRebuildSync = new Object();
816         boolean mRebuildRequested;
817         boolean mRebuildAsync;
818         ApplicationsState.AppFilter mRebuildFilter;
819         Comparator<ApplicationsState.AppEntry> mRebuildComparator;
820         ArrayList<ApplicationsState.AppEntry> mRebuildResult;
821         ArrayList<ApplicationsState.AppEntry> mLastAppList;
822         boolean mRebuildForeground;
823 
824         private final boolean mHasLifecycle;
825         @ApplicationsState.SessionFlags
826         private int mFlags = DEFAULT_SESSION_FLAGS;
827 
Session(ApplicationsState.Callbacks callbacks, Lifecycle lifecycle)828         Session(ApplicationsState.Callbacks callbacks, Lifecycle lifecycle) {
829             mCallbacks = callbacks;
830             if (lifecycle != null) {
831                 lifecycle.addObserver(this);
832                 mHasLifecycle = true;
833             } else {
834                 mHasLifecycle = false;
835             }
836         }
837 
838         @ApplicationsState.SessionFlags
getSessionFlags()839         public int getSessionFlags() {
840             return mFlags;
841         }
842 
setSessionFlags(@pplicationsState.SessionFlags int flags)843         public void setSessionFlags(@ApplicationsState.SessionFlags int flags) {
844             mFlags = flags;
845         }
846 
onResume()847         public void onResume() {
848             if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
849             synchronized (mEntriesMap) {
850                 if (!mResumed) {
851                     mResumed = true;
852                     mSessionsChanged = true;
853                     doPauseLocked();
854                     doResumeIfNeededLocked();
855                 }
856             }
857             if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
858         }
859 
onPause()860         public void onPause() {
861             if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
862             synchronized (mEntriesMap) {
863                 if (mResumed) {
864                     mResumed = false;
865                     mSessionsChanged = true;
866                     mBackgroundHandler.removeMessages(
867                             ApplicationsState.BackgroundHandler.MSG_REBUILD_LIST, this);
868                     doPauseIfNeededLocked();
869                 }
870                 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
871             }
872         }
873 
getAllApps()874         public ArrayList<ApplicationsState.AppEntry> getAllApps() {
875             synchronized (mEntriesMap) {
876                 return new ArrayList<>(mAppEntries);
877             }
878         }
879 
880         // Creates a new list of app entries with the given filter and comparator.
rebuild( ApplicationsState.AppFilter filter, Comparator<ApplicationsState.AppEntry> comparator)881         public ArrayList<ApplicationsState.AppEntry> rebuild(
882                 ApplicationsState.AppFilter filter,
883                 Comparator<ApplicationsState.AppEntry> comparator) {
884             return rebuild(filter, comparator, true);
885         }
886 
rebuild( ApplicationsState.AppFilter filter, Comparator<ApplicationsState.AppEntry> comparator, boolean foreground)887         public ArrayList<ApplicationsState.AppEntry> rebuild(
888                 ApplicationsState.AppFilter filter,
889                 Comparator<ApplicationsState.AppEntry> comparator,
890                 boolean foreground) {
891             synchronized (mRebuildSync) {
892                 synchronized (mRebuildingSessions) {
893                     mRebuildingSessions.add(this);
894                     mRebuildRequested = true;
895                     mRebuildAsync = true;
896                     mRebuildFilter = filter;
897                     mRebuildComparator = comparator;
898                     mRebuildForeground = foreground;
899                     mRebuildResult = null;
900                     if (!mBackgroundHandler.hasMessages(
901                             ApplicationsState.BackgroundHandler.MSG_REBUILD_LIST)) {
902                         Message msg = mBackgroundHandler.obtainMessage(
903                                 ApplicationsState.BackgroundHandler.MSG_REBUILD_LIST);
904                         mBackgroundHandler.sendMessage(msg);
905                     }
906                 }
907 
908                 return null;
909             }
910         }
911 
handleRebuildList()912         void handleRebuildList() {
913             ApplicationsState.AppFilter filter;
914             Comparator<ApplicationsState.AppEntry> comparator;
915 
916             if (!mResumed) {
917                 return;
918             }
919             synchronized (mRebuildSync) {
920                 if (!mRebuildRequested) {
921                     return;
922                 }
923 
924                 filter = mRebuildFilter;
925                 comparator = mRebuildComparator;
926                 mRebuildRequested = false;
927                 mRebuildFilter = null;
928                 mRebuildComparator = null;
929                 if (mRebuildForeground) {
930                     Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
931                     mRebuildForeground = false;
932                 }
933             }
934 
935             if (filter != null) {
936                 filter.init(mContext);
937             }
938 
939             final List<ApplicationsState.AppEntry> apps;
940             synchronized (mEntriesMap) {
941                 apps = new ArrayList<>(mAppEntries);
942             }
943 
944             ArrayList<ApplicationsState.AppEntry> filteredApps = new ArrayList<>();
945             if (DEBUG) {
946                 Log.i(TAG, "Rebuilding...");
947             }
948             for (ApplicationsState.AppEntry entry : apps) {
949                 if (entry != null && (filter == null || filter.filterApp(entry))) {
950                     synchronized (mEntriesMap) {
951                         if (DEBUG_LOCKING) {
952                             Log.v(TAG, "rebuild acquired lock");
953                         }
954                         if (comparator != null) {
955                             // Only need the label if we are going to be sorting.
956                             entry.ensureLabel(mContext);
957                         }
958                         if (DEBUG) {
959                             Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
960                         }
961                         filteredApps.add(entry);
962                         if (DEBUG_LOCKING) {
963                             Log.v(TAG, "rebuild releasing lock");
964                         }
965                     }
966                 }
967             }
968 
969             if (comparator != null) {
970                 synchronized (mEntriesMap) {
971                     // Locking to ensure that the background handler does not mutate
972                     // the size of AppEntries used for ordering while sorting.
973                     Collections.sort(filteredApps, comparator);
974                 }
975             }
976 
977             synchronized (mRebuildSync) {
978                 if (!mRebuildRequested) {
979                     mLastAppList = filteredApps;
980                     if (!mRebuildAsync) {
981                         mRebuildResult = filteredApps;
982                         mRebuildSync.notifyAll();
983                     } else {
984                         if (!mMainHandler.hasMessages(
985                                 ApplicationsState.MainHandler.MSG_REBUILD_COMPLETE, this)) {
986                             Message msg = mMainHandler.obtainMessage(
987                                     ApplicationsState.MainHandler.MSG_REBUILD_COMPLETE, this);
988                             mMainHandler.sendMessage(msg);
989                         }
990                     }
991                 }
992             }
993 
994             Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
995         }
996 
onDestroy()997         public void onDestroy() {
998             if (!mHasLifecycle) {
999                 // TODO: Legacy, remove this later once all usages are switched to Lifecycle
1000                 onPause();
1001             }
1002             synchronized (mEntriesMap) {
1003                 mSessions.remove(this);
1004             }
1005         }
1006     }
1007 
1008     class MainHandler extends Handler {
1009         static final int MSG_REBUILD_COMPLETE = 1;
1010         static final int MSG_PACKAGE_LIST_CHANGED = 2;
1011         static final int MSG_PACKAGE_ICON_CHANGED = 3;
1012         static final int MSG_PACKAGE_SIZE_CHANGED = 4;
1013         static final int MSG_ALL_SIZES_COMPUTED = 5;
1014         static final int MSG_RUNNING_STATE_CHANGED = 6;
1015         static final int MSG_LAUNCHER_INFO_CHANGED = 7;
1016         static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
1017 
MainHandler(Looper looper)1018         public MainHandler(Looper looper) {
1019             super(looper);
1020         }
1021 
1022         @Override
handleMessage(Message msg)1023         public void handleMessage(Message msg) {
1024             rebuildActiveSessions();
1025             switch (msg.what) {
1026                 case MSG_REBUILD_COMPLETE: {
1027                     ApplicationsState.Session
1028                             s = (ApplicationsState.Session) msg.obj;
1029                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1030                         final ApplicationsState.Session session = sessionRef.get();
1031                         if (session != null && session == s) {
1032                             s.mCallbacks.onRebuildComplete(s.mLastAppList);
1033                         }
1034                     }
1035                 }
1036                 break;
1037                 case MSG_PACKAGE_LIST_CHANGED: {
1038                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1039                         final ApplicationsState.Session session = sessionRef.get();
1040                         if (session != null) {
1041                             session.mCallbacks.onPackageListChanged();
1042                         }
1043                     }
1044                 }
1045                 break;
1046                 case MSG_PACKAGE_ICON_CHANGED: {
1047                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1048                         final ApplicationsState.Session session = sessionRef.get();
1049                         if (session != null) {
1050                             session.mCallbacks.onPackageIconChanged();
1051                         }
1052                     }
1053                 }
1054                 break;
1055                 case MSG_PACKAGE_SIZE_CHANGED: {
1056                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1057                         final ApplicationsState.Session session = sessionRef.get();
1058                         if (session != null) {
1059                             session.mCallbacks.onPackageSizeChanged(
1060                                     (String) msg.obj);
1061                         }
1062                     }
1063                 }
1064                 break;
1065                 case MSG_ALL_SIZES_COMPUTED: {
1066                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1067                         final ApplicationsState.Session session = sessionRef.get();
1068                         if (session != null) {
1069                             session.mCallbacks.onAllSizesComputed();
1070                         }
1071                     }
1072                 }
1073                 break;
1074                 case MSG_RUNNING_STATE_CHANGED: {
1075                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1076                         final ApplicationsState.Session session = sessionRef.get();
1077                         if (session != null) {
1078                             session.mCallbacks.onRunningStateChanged(
1079                                     msg.arg1 != 0);
1080                         }
1081                     }
1082                 }
1083                 break;
1084                 case MSG_LAUNCHER_INFO_CHANGED: {
1085                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1086                         final ApplicationsState.Session session = sessionRef.get();
1087                         if (session != null) {
1088                             session.mCallbacks.onLauncherInfoChanged();
1089                         }
1090                     }
1091                 }
1092                 break;
1093                 case MSG_LOAD_ENTRIES_COMPLETE: {
1094                     for (WeakReference<ApplicationsState.Session> sessionRef : mActiveSessions) {
1095                         final ApplicationsState.Session session = sessionRef.get();
1096                         if (session != null) {
1097                             session.mCallbacks.onLoadEntriesCompleted();
1098                         }
1099                     }
1100                 }
1101                 break;
1102             }
1103         }
1104     }
1105 
1106     private class BackgroundHandler extends Handler {
1107         static final int MSG_REBUILD_LIST = 1;
1108         static final int MSG_LOAD_ENTRIES = 2;
1109         static final int MSG_LOAD_HOME_APP = 3;
1110         static final int MSG_LOAD_LAUNCHER = 4;
1111         static final int MSG_LOAD_LEANBACK_LAUNCHER = 5;
1112         static final int MSG_LOAD_ICONS = 6;
1113         static final int MSG_LOAD_SIZES = 7;
1114 
1115         boolean mRunning;
1116 
BackgroundHandler(Looper looper)1117         BackgroundHandler(Looper looper) {
1118             super(looper);
1119         }
1120 
1121         @Override
handleMessage(Message msg)1122         public void handleMessage(Message msg) {
1123             // Always try rebuilding list first thing, if needed.
1124             ArrayList<ApplicationsState.Session> rebuildingSessions = null;
1125             synchronized (mRebuildingSessions) {
1126                 if (mRebuildingSessions.size() > 0) {
1127                     rebuildingSessions = new ArrayList<ApplicationsState.Session>(
1128                             mRebuildingSessions);
1129                     mRebuildingSessions.clear();
1130                 }
1131             }
1132             if (rebuildingSessions != null) {
1133                 for (ApplicationsState.Session session : rebuildingSessions) {
1134                     session.handleRebuildList();
1135                 }
1136             }
1137 
1138             int flags = getCombinedSessionFlags(mSessions);
1139 
1140             switch (msg.what) {
1141                 case MSG_REBUILD_LIST: {
1142                 }
1143                 break;
1144                 case MSG_LOAD_ENTRIES: {
1145                     int numDone = 0;
1146                     synchronized (mEntriesMap) {
1147                         if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
1148                         for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
1149                             if (!mRunning) {
1150                                 mRunning = true;
1151                                 Message m = mMainHandler.obtainMessage(
1152                                         ApplicationsState.MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
1153                                 mMainHandler.sendMessage(m);
1154                             }
1155                             ApplicationInfo info = mApplications.get(i);
1156                             int userId = UserHandle.getUserId(info.uid);
1157                             if (mEntriesMap.get(userId).get(info.packageName) == null) {
1158                                 numDone++;
1159                                 getEntryLocked(info);
1160                             }
1161                             if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
1162                                 // If this app is for a profile and we are on the owner, remove
1163                                 // the owner entry if it isn't installed.  This will prevent
1164                                 // duplicates of work only apps showing up as 'not installed
1165                                 // for this user'.
1166                                 // Note: This depends on us traversing the users in order, which
1167                                 // happens because of the way we generate the list in
1168                                 // doResumeIfNeededLocked.
1169                                 ApplicationsState.AppEntry
1170                                         entry = mEntriesMap.get(0).get(info.packageName);
1171                                 if (entry != null && !hasFlag(entry.info.flags,
1172                                         ApplicationInfo.FLAG_INSTALLED)) {
1173                                     mEntriesMap.get(0).remove(info.packageName);
1174                                     mAppEntries.remove(entry);
1175                                 }
1176                             }
1177                         }
1178                         if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
1179                     }
1180 
1181                     if (numDone >= 6) {
1182                         sendEmptyMessage(MSG_LOAD_ENTRIES);
1183                     } else {
1184                         if (!mMainHandler.hasMessages(
1185                                 ApplicationsState.MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
1186                             mMainHandler.sendEmptyMessage(
1187                                     ApplicationsState.MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
1188                         }
1189                         sendEmptyMessage(MSG_LOAD_HOME_APP);
1190                     }
1191                 }
1192                 break;
1193                 case MSG_LOAD_HOME_APP: {
1194                     if (hasFlag(flags, FLAG_SESSION_REQUEST_HOME_APP)) {
1195                         final List<ResolveInfo> homeActivities = new ArrayList<>();
1196                         mPm.getHomeActivities(homeActivities);
1197                         synchronized (mEntriesMap) {
1198                             final int entryCount = mEntriesMap.size();
1199                             for (int i = 0; i < entryCount; i++) {
1200                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP acquired lock");
1201                                 final HashMap<String, ApplicationsState.AppEntry> userEntries =
1202                                         mEntriesMap.valueAt(
1203                                                 i);
1204                                 for (ResolveInfo activity : homeActivities) {
1205                                     String packageName = activity.activityInfo.packageName;
1206                                     ApplicationsState.AppEntry
1207                                             entry = userEntries.get(packageName);
1208                                     if (entry != null) {
1209                                         entry.isHomeApp = true;
1210                                     }
1211                                 }
1212                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_HOME_APP releasing lock");
1213                             }
1214                         }
1215                     }
1216                     sendEmptyMessage(MSG_LOAD_LAUNCHER);
1217                 }
1218                 break;
1219                 case MSG_LOAD_LAUNCHER:
1220                 case MSG_LOAD_LEANBACK_LAUNCHER: {
1221                     if ((msg.what == MSG_LOAD_LAUNCHER &&
1222                             hasFlag(flags, FLAG_SESSION_REQUEST_LAUNCHER))
1223                             || (msg.what == MSG_LOAD_LEANBACK_LAUNCHER &&
1224                             hasFlag(flags, FLAG_SESSION_REQUEST_LEANBACK_LAUNCHER))) {
1225 
1226                         Intent launchIntent = new Intent(Intent.ACTION_MAIN, null);
1227                         launchIntent.addCategory(msg.what == MSG_LOAD_LAUNCHER
1228                                 ? Intent.CATEGORY_LAUNCHER : Intent.CATEGORY_LEANBACK_LAUNCHER);
1229                         for (int i = 0; i < mEntriesMap.size(); i++) {
1230                             int userId = mEntriesMap.keyAt(i);
1231                             // If we do not specify MATCH_DIRECT_BOOT_AWARE or
1232                             // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
1233                             // according to the user's lock state. When the user is locked,
1234                             // components with ComponentInfo#directBootAware == false will be
1235                             // filtered. W should explicitly include both direct boot aware and
1236                             // unaware component here.
1237                             List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
1238                                     launchIntent,
1239                                     PackageManager.MATCH_DISABLED_COMPONENTS
1240                                             | PackageManager.MATCH_DIRECT_BOOT_AWARE
1241                                             | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
1242                                     userId
1243                             );
1244                             synchronized (mEntriesMap) {
1245                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
1246                                 HashMap<String, ApplicationsState.AppEntry> userEntries =
1247                                         mEntriesMap.valueAt(i);
1248                                 final int N = intents.size();
1249                                 for (int j = 0; j < N; j++) {
1250                                     ResolveInfo resolveInfo = intents.get(j);
1251                                     String packageName = resolveInfo.activityInfo.packageName;
1252                                     ApplicationsState.AppEntry
1253                                             entry = userEntries.get(packageName);
1254                                     if (entry != null) {
1255                                         entry.hasLauncherEntry = true;
1256                                         entry.launcherEntryEnabled |=
1257                                                 resolveInfo.activityInfo.enabled;
1258                                     } else {
1259                                         Log.w(TAG, "Cannot find pkg: " + packageName
1260                                                 + " on user " + userId);
1261                                     }
1262                                 }
1263                                 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
1264                             }
1265                         }
1266 
1267                         if (!mMainHandler.hasMessages(
1268                                 ApplicationsState.MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
1269                             mMainHandler.sendEmptyMessage(
1270                                     ApplicationsState.MainHandler.MSG_LAUNCHER_INFO_CHANGED);
1271                         }
1272                     }
1273                     if (msg.what == MSG_LOAD_LAUNCHER) {
1274                         sendEmptyMessage(MSG_LOAD_LEANBACK_LAUNCHER);
1275                     } else {
1276                         sendEmptyMessage(MSG_LOAD_ICONS);
1277                     }
1278                 }
1279                 break;
1280                 case MSG_LOAD_ICONS: {
1281                     if (hasFlag(flags, FLAG_SESSION_REQUEST_ICONS)) {
1282                         int numDone = 0;
1283                         synchronized (mEntriesMap) {
1284                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
1285                             for (int i = 0; i < mAppEntries.size() && numDone < 2; i++) {
1286                                 ApplicationsState.AppEntry
1287                                         entry = mAppEntries.get(i);
1288                                 if (entry.icon == null || !entry.mounted) {
1289                                     synchronized (entry) {
1290                                         if (entry.ensureIconLocked(mContext)) {
1291                                             if (!mRunning) {
1292                                                 mRunning = true;
1293                                                 Message m = mMainHandler.obtainMessage(
1294                                                         ApplicationsState.MainHandler
1295                                                                 .MSG_RUNNING_STATE_CHANGED,
1296                                                         1);
1297                                                 mMainHandler.sendMessage(m);
1298                                             }
1299                                             numDone++;
1300                                         }
1301                                     }
1302                                 }
1303                             }
1304                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
1305                         }
1306                         if (numDone > 0) {
1307                             if (!mMainHandler.hasMessages(
1308                                     ApplicationsState.MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
1309                                 mMainHandler.sendEmptyMessage(
1310                                         ApplicationsState.MainHandler.MSG_PACKAGE_ICON_CHANGED);
1311                             }
1312                         }
1313                         if (numDone >= 2) {
1314                             sendEmptyMessage(MSG_LOAD_ICONS);
1315                             break;
1316                         }
1317                     }
1318                     sendEmptyMessage(MSG_LOAD_SIZES);
1319                 }
1320                 break;
1321                 case MSG_LOAD_SIZES: {
1322                     if (hasFlag(flags, FLAG_SESSION_REQUEST_SIZES)) {
1323                         synchronized (mEntriesMap) {
1324                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
1325                             if (mCurComputingSizePkg != null) {
1326                                 if (DEBUG_LOCKING) {
1327                                     Log.v(TAG,
1328                                             "MSG_LOAD_SIZES releasing: currently computing");
1329                                 }
1330                                 return;
1331                             }
1332 
1333                             long now = SystemClock.uptimeMillis();
1334                             for (int i = 0; i < mAppEntries.size(); i++) {
1335                                 ApplicationsState.AppEntry
1336                                         entry = mAppEntries.get(i);
1337                                 if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_INSTALLED)
1338                                         && (entry.size == SIZE_UNKNOWN || entry.sizeStale)) {
1339                                     if (entry.sizeLoadStart == 0 ||
1340                                             (entry.sizeLoadStart < (now - 20 * 1000))) {
1341                                         if (!mRunning) {
1342                                             mRunning = true;
1343                                             Message m = mMainHandler.obtainMessage(
1344                                                     ApplicationsState.MainHandler
1345                                                             .MSG_RUNNING_STATE_CHANGED,
1346                                                     1);
1347                                             mMainHandler.sendMessage(m);
1348                                         }
1349                                         entry.sizeLoadStart = now;
1350                                         mCurComputingSizeUuid = entry.info.storageUuid;
1351                                         mCurComputingSizePkg = entry.info.packageName;
1352                                         mCurComputingSizeUserId = UserHandle.getUserId(
1353                                                 entry.info.uid);
1354 
1355                                         mBackgroundHandler.post(() -> {
1356                                             try {
1357                                                 final StorageStats stats =
1358                                                         mStats.queryStatsForPackage(
1359                                                                 mCurComputingSizeUuid,
1360                                                                 mCurComputingSizePkg,
1361                                                                 UserHandle.of(
1362                                                                         mCurComputingSizeUserId));
1363                                                 final PackageStats legacy = new PackageStats(
1364                                                         mCurComputingSizePkg,
1365                                                         mCurComputingSizeUserId);
1366                                                 legacy.codeSize = stats.getAppBytes();
1367                                                 legacy.dataSize = stats.getDataBytes();
1368                                                 legacy.cacheSize = stats.getCacheBytes();
1369                                                 try {
1370                                                     mStatsObserver.onGetStatsCompleted(legacy,
1371                                                             true);
1372                                                 } catch (RemoteException ignored) {
1373                                                 }
1374                                             } catch (NameNotFoundException | IOException e) {
1375                                                 Log.w(TAG, "Failed to query stats: " + e);
1376                                                 try {
1377                                                     mStatsObserver.onGetStatsCompleted(null, false);
1378                                                 } catch (RemoteException ignored) {
1379                                                 }
1380                                             }
1381 
1382                                         });
1383                                     }
1384                                     if (DEBUG_LOCKING) {
1385                                         Log.v(TAG,
1386                                                 "MSG_LOAD_SIZES releasing: now computing");
1387                                     }
1388                                     return;
1389                                 }
1390                             }
1391                             if (!mMainHandler.hasMessages(
1392                                     ApplicationsState.MainHandler.MSG_ALL_SIZES_COMPUTED)) {
1393                                 mMainHandler.sendEmptyMessage(
1394                                         ApplicationsState.MainHandler.MSG_ALL_SIZES_COMPUTED);
1395                                 mRunning = false;
1396                                 Message m = mMainHandler.obtainMessage(
1397                                         ApplicationsState.MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
1398                                 mMainHandler.sendMessage(m);
1399                             }
1400                             if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
1401                         }
1402                     }
1403                 }
1404                 break;
1405             }
1406         }
1407 
1408         @ApplicationsState.SessionFlags
getCombinedSessionFlags(List<ApplicationsState.Session> sessions)1409         private int getCombinedSessionFlags(List<ApplicationsState.Session> sessions) {
1410             synchronized (mEntriesMap) {
1411                 int flags = 0;
1412                 for (ApplicationsState.Session session : sessions) {
1413                     flags |= session.mFlags;
1414                 }
1415                 return flags;
1416             }
1417         }
1418 
1419         final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
1420             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
1421                 if (!succeeded) {
1422                     // There is no meaningful information in stats if the call failed.
1423                     return;
1424                 }
1425 
1426                 boolean sizeChanged = false;
1427                 synchronized (mEntriesMap) {
1428                     if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
1429                     HashMap<String, ApplicationsState.AppEntry> userMap = mEntriesMap.get(
1430                             stats.userHandle);
1431                     if (userMap == null) {
1432                         // The user must have been removed.
1433                         return;
1434                     }
1435                     ApplicationsState.AppEntry entry = userMap.get(stats.packageName);
1436                     if (entry != null) {
1437                         synchronized (entry) {
1438                             entry.sizeStale = false;
1439                             entry.sizeLoadStart = 0;
1440                             long externalCodeSize = stats.externalCodeSize
1441                                     + stats.externalObbSize;
1442                             long externalDataSize = stats.externalDataSize
1443                                     + stats.externalMediaSize;
1444                             long newSize = externalCodeSize + externalDataSize
1445                                     + getTotalInternalSize(stats);
1446                             if (entry.size != newSize ||
1447                                     entry.cacheSize != stats.cacheSize ||
1448                                     entry.codeSize != stats.codeSize ||
1449                                     entry.dataSize != stats.dataSize ||
1450                                     entry.externalCodeSize != externalCodeSize ||
1451                                     entry.externalDataSize != externalDataSize ||
1452                                     entry.externalCacheSize != stats.externalCacheSize) {
1453                                 entry.size = newSize;
1454                                 entry.cacheSize = stats.cacheSize;
1455                                 entry.codeSize = stats.codeSize;
1456                                 entry.dataSize = stats.dataSize;
1457                                 entry.externalCodeSize = externalCodeSize;
1458                                 entry.externalDataSize = externalDataSize;
1459                                 entry.externalCacheSize = stats.externalCacheSize;
1460                                 entry.sizeStr = getSizeStr(entry.size);
1461                                 entry.internalSize = getTotalInternalSize(stats);
1462                                 entry.internalSizeStr = getSizeStr(entry.internalSize);
1463                                 entry.externalSize = getTotalExternalSize(stats);
1464                                 entry.externalSizeStr = getSizeStr(entry.externalSize);
1465                                 if (DEBUG) {
1466                                     Log.i(TAG, "Set size of " + entry.label + " " + entry
1467                                             + ": " + entry.sizeStr);
1468                                 }
1469                                 sizeChanged = true;
1470                             }
1471                         }
1472                         if (sizeChanged) {
1473                             Message msg = mMainHandler.obtainMessage(
1474                                     ApplicationsState.MainHandler.MSG_PACKAGE_SIZE_CHANGED,
1475                                     stats.packageName);
1476                             mMainHandler.sendMessage(msg);
1477                         }
1478                     }
1479                     if (mCurComputingSizePkg != null
1480                             && (mCurComputingSizePkg.equals(stats.packageName)
1481                             && mCurComputingSizeUserId == stats.userHandle)) {
1482                         mCurComputingSizePkg = null;
1483                         sendEmptyMessage(MSG_LOAD_SIZES);
1484                     }
1485                     if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1486                 }
1487             }
1488         };
1489     }
1490 
1491     /**
1492      * Receives notifications when applications are added/removed.
1493      */
1494     private class PackageIntentReceiver extends BroadcastReceiver {
registerReceiver()1495         void registerReceiver() {
1496             IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1497             filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1498             filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1499             filter.addDataScheme("package");
1500             mContext.registerReceiver(this, filter);
1501             // Register for events related to sdcard installation.
1502             IntentFilter sdFilter = new IntentFilter();
1503             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1504             sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1505             mContext.registerReceiver(this, sdFilter);
1506             // Register for events related to user creation/deletion.
1507             IntentFilter userFilter = new IntentFilter();
1508             userFilter.addAction(Intent.ACTION_USER_ADDED);
1509             userFilter.addAction(Intent.ACTION_USER_REMOVED);
1510             mContext.registerReceiver(this, userFilter);
1511         }
1512 
unregisterReceiver()1513         void unregisterReceiver() {
1514             mContext.unregisterReceiver(this);
1515         }
1516 
1517         @Override
onReceive(Context context, Intent intent)1518         public void onReceive(Context context, Intent intent) {
1519             String actionStr = intent.getAction();
1520             if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1521                 Uri data = intent.getData();
1522                 String pkgName = data.getEncodedSchemeSpecificPart();
1523                 for (int i = 0; i < mEntriesMap.size(); i++) {
1524                     addPackage(pkgName, mEntriesMap.keyAt(i));
1525                 }
1526             } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1527                 Uri data = intent.getData();
1528                 String pkgName = data.getEncodedSchemeSpecificPart();
1529                 for (int i = 0; i < mEntriesMap.size(); i++) {
1530                     removePackage(pkgName, mEntriesMap.keyAt(i));
1531                 }
1532             } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1533                 Uri data = intent.getData();
1534                 String pkgName = data.getEncodedSchemeSpecificPart();
1535                 for (int i = 0; i < mEntriesMap.size(); i++) {
1536                     invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1537                 }
1538             } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1539                     Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1540                 // When applications become available or unavailable (perhaps because
1541                 // the SD card was inserted or ejected) we need to refresh the
1542                 // AppInfo with new label, icon and size information as appropriate
1543                 // given the newfound (un)availability of the application.
1544                 // A simple way to do that is to treat the refresh as a package
1545                 // removal followed by a package addition.
1546                 String[] pkgList = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1547                 if (pkgList == null || pkgList.length == 0) {
1548                     // Ignore
1549                     return;
1550                 }
1551                 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1552                 if (avail) {
1553                     for (String pkgName : pkgList) {
1554                         for (int i = 0; i < mEntriesMap.size(); i++) {
1555                             invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1556                         }
1557                     }
1558                 }
1559             } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1560                 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1561             } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1562                 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1563             }
1564         }
1565     }
1566 
1567     /**
1568      * Whether the packages for the  user have been initialized.
1569      */
isUserAdded(int userId)1570     public boolean isUserAdded(int userId) {
1571         return mEntriesMap.contains(userId);
1572     }
1573 
1574     public interface Callbacks {
onRunningStateChanged(boolean running)1575         void onRunningStateChanged(boolean running);
1576 
onPackageListChanged()1577         void onPackageListChanged();
1578 
onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps)1579         void onRebuildComplete(ArrayList<ApplicationsState.AppEntry> apps);
1580 
onPackageIconChanged()1581         void onPackageIconChanged();
1582 
onPackageSizeChanged(String packageName)1583         void onPackageSizeChanged(String packageName);
1584 
onAllSizesComputed()1585         void onAllSizesComputed();
1586 
onLauncherInfoChanged()1587         void onLauncherInfoChanged();
1588 
onLoadEntriesCompleted()1589         void onLoadEntriesCompleted();
1590     }
1591 
1592     public static class SizeInfo {
1593         public long cacheSize;
1594         public long codeSize;
1595         public long dataSize;
1596         public long externalCodeSize;
1597         public long externalDataSize;
1598 
1599         // This is the part of externalDataSize that is in the cache
1600         // section of external storage.  Note that we don't just combine
1601         // this with cacheSize because currently the platform can't
1602         // automatically trim this data when needed, so it is something
1603         // the user may need to manage.  The externalDataSize also includes
1604         // this value, since what this is here is really the part of
1605         // externalDataSize that we can just consider to be "cache" files
1606         // for purposes of cleaning them up in the app details UI.
1607         public long externalCacheSize;
1608     }
1609 
1610     public static class AppEntry extends
1611             ApplicationsState.SizeInfo {
1612         public final File apkFile;
1613         public final long id;
1614         public String label;
1615         public long size;
1616         public long internalSize;
1617         public long externalSize;
1618         public String labelDescription;
1619 
1620         public boolean mounted;
1621 
1622         /**
1623          * Setting this to {@code true} prevents the entry to be filtered by
1624          * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1625          */
1626         public boolean hasLauncherEntry;
1627 
1628         /**
1629          * Whether the component that has a launcher intent filter is enabled.
1630          */
1631         public boolean launcherEntryEnabled;
1632 
1633         /**
1634          * Whether or not it's a Home app.
1635          */
1636         public boolean isHomeApp;
1637 
getNormalizedLabel()1638         public String getNormalizedLabel() {
1639             if (normalizedLabel != null) {
1640                 return normalizedLabel;
1641             }
1642             normalizedLabel = normalize(label);
1643             return normalizedLabel;
1644         }
1645 
1646         // Need to synchronize on 'this' for the following.
1647         public ApplicationInfo info;
1648         public Drawable icon;
1649         public String sizeStr;
1650         public String internalSizeStr;
1651         public String externalSizeStr;
1652         public boolean sizeStale;
1653         public long sizeLoadStart;
1654 
1655         public String normalizedLabel;
1656 
1657         // A location where extra info can be placed to be used by custom filters.
1658         public Object extraInfo;
1659 
AppEntry(Context context, ApplicationInfo info, long id)1660         public AppEntry(Context context, ApplicationInfo info, long id) {
1661             apkFile = new File(info.sourceDir);
1662             this.id = id;
1663             this.info = info;
1664             this.size = SIZE_UNKNOWN;
1665             this.sizeStale = true;
1666             ensureLabel(context);
1667             // Speed up the cache of the icon and label description if they haven't been created.
1668             ThreadUtils.postOnBackgroundThread(() -> {
1669                 if (this.icon == null) {
1670                     this.ensureIconLocked(context);
1671                 }
1672                 if (this.labelDescription == null) {
1673                     this.ensureLabelDescriptionLocked(context);
1674                 }
1675             });
1676         }
1677 
ensureLabel(Context context)1678         public void ensureLabel(Context context) {
1679             if (this.label == null || !this.mounted) {
1680                 if (!this.apkFile.exists()) {
1681                     this.mounted = false;
1682                     this.label = info.packageName;
1683                 } else {
1684                     this.mounted = true;
1685                     CharSequence label = info.loadLabel(context.getPackageManager());
1686                     this.label = label != null ? label.toString() : info.packageName;
1687                 }
1688             }
1689         }
1690 
ensureIconLocked(Context context)1691         boolean ensureIconLocked(Context context) {
1692             if (this.icon == null) {
1693                 if (this.apkFile.exists()) {
1694                     this.icon = LibUtils.getBadgedIcon(context, info);
1695                     return true;
1696                 } else {
1697                     this.mounted = false;
1698                     this.icon = context.getDrawable(context.getResources().getIdentifier(
1699                             "sym_app_on_sd_unavailable_icon", "drawable", "android"));
1700                 }
1701             } else if (!this.mounted) {
1702                 // If the app wasn't mounted but is now mounted, reload
1703                 // its icon.
1704                 if (this.apkFile.exists()) {
1705                     this.mounted = true;
1706                     this.icon = LibUtils.getBadgedIcon(context, info);
1707                     return true;
1708                 }
1709             }
1710             return false;
1711         }
1712 
getVersion(Context context)1713         public String getVersion(Context context) {
1714             try {
1715                 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1716             } catch (PackageManager.NameNotFoundException e) {
1717                 return "";
1718             }
1719         }
1720 
1721         /**
1722          * Get the label description which distinguishes a personal app from a work app for
1723          * accessibility purpose. If the app is in a work profile, then add a "work" prefix to the
1724          * app label.
1725          *
1726          * @param context The application context
1727          */
ensureLabelDescriptionLocked(Context context)1728         public void ensureLabelDescriptionLocked(Context context) {
1729             final int userId = UserHandle.getUserId(this.info.uid);
1730             if (UserManager.get(context).isManagedProfile(userId)) {
1731                 this.labelDescription = "";
1732             } else {
1733                 this.labelDescription = this.label;
1734             }
1735         }
1736     }
1737 
hasFlag(int flags, int flag)1738     private static boolean hasFlag(int flags, int flag) {
1739         return (flags & flag) != 0;
1740     }
1741 
1742     /**
1743      * Compare by label, then package name, then uid.
1744      */
1745     public static final Comparator<ApplicationsState.AppEntry> ALPHA_COMPARATOR =
1746             new Comparator<ApplicationsState.AppEntry>() {
1747                 private final Collator sCollator = Collator.getInstance();
1748 
1749                 @Override
1750                 public int compare(ApplicationsState.AppEntry object1,
1751                         ApplicationsState.AppEntry object2) {
1752                     int compareResult = sCollator.compare(object1.label, object2.label);
1753                     if (compareResult != 0) {
1754                         return compareResult;
1755                     }
1756                     if (object1.info != null && object2.info != null) {
1757                         compareResult =
1758                                 sCollator.compare(object1.info.packageName,
1759                                         object2.info.packageName);
1760                         if (compareResult != 0) {
1761                             return compareResult;
1762                         }
1763                     }
1764 
1765                     return object1.info.uid - object2.info.uid;
1766                 }
1767             };
1768 
1769     public static final Comparator<ApplicationsState.AppEntry> SIZE_COMPARATOR
1770             = new Comparator<ApplicationsState.AppEntry>() {
1771         @Override
1772         public int compare(ApplicationsState.AppEntry object1, ApplicationsState.AppEntry object2) {
1773             if (object1.size < object2.size) return 1;
1774             if (object1.size > object2.size) return -1;
1775             return ALPHA_COMPARATOR.compare(object1, object2);
1776         }
1777     };
1778 
1779     public static final Comparator<ApplicationsState.AppEntry> INTERNAL_SIZE_COMPARATOR
1780             = new Comparator<ApplicationsState.AppEntry>() {
1781         @Override
1782         public int compare(ApplicationsState.AppEntry object1, ApplicationsState.AppEntry object2) {
1783             if (object1.internalSize < object2.internalSize) return 1;
1784             if (object1.internalSize > object2.internalSize) return -1;
1785             return ALPHA_COMPARATOR.compare(object1, object2);
1786         }
1787     };
1788 
1789     public static final Comparator<ApplicationsState.AppEntry> EXTERNAL_SIZE_COMPARATOR
1790             = new Comparator<ApplicationsState.AppEntry>() {
1791         @Override
1792         public int compare(ApplicationsState.AppEntry object1, ApplicationsState.AppEntry object2) {
1793             if (object1.externalSize < object2.externalSize) return 1;
1794             if (object1.externalSize > object2.externalSize) return -1;
1795             return ALPHA_COMPARATOR.compare(object1, object2);
1796         }
1797     };
1798 
1799     public interface AppFilter {
init()1800         void init();
1801 
init(Context context)1802         default void init(Context context) {
1803             init();
1804         }
1805 
filterApp(ApplicationsState.AppEntry info)1806         boolean filterApp(ApplicationsState.AppEntry info);
1807     }
1808 
1809     public static final ApplicationsState.AppFilter
1810             FILTER_PERSONAL = new ApplicationsState.AppFilter() {
1811         private int mCurrentUser;
1812 
1813         @Override
1814         public void init() {
1815             mCurrentUser = ActivityManager.getCurrentUser();
1816         }
1817 
1818         @Override
1819         public boolean filterApp(
1820                 ApplicationsState.AppEntry entry) {
1821             return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1822         }
1823     };
1824 
1825     public static final ApplicationsState.AppFilter
1826             FILTER_WITHOUT_DISABLED_UNTIL_USED = new ApplicationsState.AppFilter() {
1827         @Override
1828         public void init() {
1829             // do nothing
1830         }
1831 
1832         @Override
1833         public boolean filterApp(
1834                 ApplicationsState.AppEntry entry) {
1835             return entry.info.enabledSetting
1836                     != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1837         }
1838     };
1839 
1840 
1841     /**
1842      * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1843      */
1844     public static final ApplicationsState.AppFilter
1845             FILTER_DOWNLOADED_AND_LAUNCHER = new ApplicationsState.AppFilter() {
1846         @Override
1847         public void init() {
1848         }
1849 
1850         @Override
1851         public boolean filterApp(
1852                 ApplicationsState.AppEntry entry) {
1853             if (AppUtils.isInstant(entry.info)) {
1854                 return false;
1855             } else if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
1856                 return true;
1857             } else if (!hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM)) {
1858                 return true;
1859             } else if (entry.hasLauncherEntry) {
1860                 return true;
1861             } else {
1862                 return hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM) && entry.isHomeApp;
1863             }
1864         }
1865     };
1866 
1867     /**
1868      * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1869      */
1870     public static final ApplicationsState.AppFilter
1871             FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT = new ApplicationsState.AppFilter() {
1872 
1873         @Override
1874         public void init() {
1875         }
1876 
1877         @Override
1878         public boolean filterApp(
1879                 ApplicationsState.AppEntry entry) {
1880             return AppUtils.isInstant(entry.info)
1881                     || FILTER_DOWNLOADED_AND_LAUNCHER.filterApp(entry);
1882         }
1883 
1884     };
1885 
1886     public static final ApplicationsState.AppFilter
1887             FILTER_THIRD_PARTY = new ApplicationsState.AppFilter() {
1888         @Override
1889         public void init() {
1890         }
1891 
1892         @Override
1893         public boolean filterApp(
1894                 ApplicationsState.AppEntry entry) {
1895             if (hasFlag(entry.info.flags, ApplicationInfo.FLAG_UPDATED_SYSTEM_APP)) {
1896                 return true;
1897             } else {
1898                 return !hasFlag(entry.info.flags, ApplicationInfo.FLAG_SYSTEM);
1899             }
1900         }
1901     };
1902 
1903     public static final ApplicationsState.AppFilter
1904             FILTER_DISABLED = new ApplicationsState.AppFilter() {
1905         @Override
1906         public void init() {
1907         }
1908 
1909         @Override
1910         public boolean filterApp(
1911                 ApplicationsState.AppEntry entry) {
1912             return !entry.info.enabled && !AppUtils.isInstant(entry.info);
1913         }
1914     };
1915 
1916     public static final ApplicationsState.AppFilter
1917             FILTER_INSTANT = new ApplicationsState.AppFilter() {
1918         @Override
1919         public void init() {
1920         }
1921 
1922         @Override
1923         public boolean filterApp(
1924                 ApplicationsState.AppEntry entry) {
1925             return AppUtils.isInstant(entry.info);
1926         }
1927     };
1928 
1929     public static final ApplicationsState.AppFilter
1930             FILTER_ALL_ENABLED = new ApplicationsState.AppFilter() {
1931         @Override
1932         public void init() {
1933         }
1934 
1935         @Override
1936         public boolean filterApp(
1937                 ApplicationsState.AppEntry entry) {
1938             return entry.info.enabled && !AppUtils.isInstant(entry.info);
1939         }
1940     };
1941 
1942     public static final ApplicationsState.AppFilter
1943             FILTER_EVERYTHING = new ApplicationsState.AppFilter() {
1944         @Override
1945         public void init() {
1946         }
1947 
1948         @Override
1949         public boolean filterApp(
1950                 ApplicationsState.AppEntry entry) {
1951             return true;
1952         }
1953     };
1954 
1955 
1956     public static final ApplicationsState.AppFilter
1957             FILTER_NOT_HIDE = new ApplicationsState.AppFilter() {
1958         private String[] mHidePackageNames;
1959 
1960         @Override
1961         public void init(Context context) {
1962             mHidePackageNames = context.getResources()
1963                     .getStringArray(context.getResources().getIdentifier(
1964                             "config_hideWhenDisabled_packageNames", "array", "android"));
1965         }
1966 
1967         @Override
1968         public void init() {
1969         }
1970 
1971         @Override
1972         public boolean filterApp(
1973                 ApplicationsState.AppEntry entry) {
1974             if (ArrayUtils.contains(mHidePackageNames, entry.info.packageName)) {
1975                 if (!entry.info.enabled) {
1976                     return false;
1977                 } else {
1978                     return entry.info.enabledSetting
1979                             != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1980                 }
1981             }
1982 
1983             return true;
1984         }
1985     };
1986 
1987     public static final ApplicationsState.AppFilter
1988             FILTER_GAMES = new ApplicationsState.AppFilter() {
1989         @Override
1990         public void init() {
1991         }
1992 
1993         @Override
1994         public boolean filterApp(ApplicationsState.AppEntry info) {
1995             // TODO: Update for the new game category.
1996             boolean isGame;
1997             synchronized (info.info) {
1998                 isGame = hasFlag(info.info.flags, ApplicationInfo.FLAG_IS_GAME)
1999                         || info.info.category == ApplicationInfo.CATEGORY_GAME;
2000             }
2001             return isGame;
2002         }
2003     };
2004 
2005     public static class VolumeFilter implements
2006             ApplicationsState.AppFilter {
2007         private final String mVolumeUuid;
2008 
VolumeFilter(String volumeUuid)2009         public VolumeFilter(String volumeUuid) {
2010             mVolumeUuid = volumeUuid;
2011         }
2012 
2013         @Override
init()2014         public void init() {
2015         }
2016 
2017         @Override
filterApp( ApplicationsState.AppEntry info)2018         public boolean filterApp(
2019                 ApplicationsState.AppEntry info) {
2020             return Objects.equals(info.info.volumeUuid, mVolumeUuid);
2021         }
2022     }
2023 
2024     public static class CompoundFilter implements
2025             ApplicationsState.AppFilter {
2026         private final ApplicationsState.AppFilter mFirstFilter;
2027         private final ApplicationsState.AppFilter mSecondFilter;
2028 
CompoundFilter(ApplicationsState.AppFilter first, ApplicationsState.AppFilter second)2029         public CompoundFilter(ApplicationsState.AppFilter first,
2030                 ApplicationsState.AppFilter second) {
2031             mFirstFilter = first;
2032             mSecondFilter = second;
2033         }
2034 
2035         @Override
init(Context context)2036         public void init(Context context) {
2037             mFirstFilter.init(context);
2038             mSecondFilter.init(context);
2039         }
2040 
2041         @Override
init()2042         public void init() {
2043             mFirstFilter.init();
2044             mSecondFilter.init();
2045         }
2046 
2047         @Override
filterApp( ApplicationsState.AppEntry info)2048         public boolean filterApp(
2049                 ApplicationsState.AppEntry info) {
2050             return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);
2051         }
2052     }
2053 
2054     public static final ApplicationsState.AppFilter
2055             FILTER_AUDIO = new ApplicationsState.AppFilter() {
2056         @Override
2057         public void init() {
2058         }
2059 
2060         @Override
2061         public boolean filterApp(
2062                 ApplicationsState.AppEntry entry) {
2063             boolean isMusicApp;
2064             synchronized (entry) {
2065                 isMusicApp = entry.info.category == ApplicationInfo.CATEGORY_AUDIO;
2066             }
2067             return isMusicApp;
2068         }
2069     };
2070 
2071     public static final ApplicationsState.AppFilter
2072             FILTER_MOVIES = new ApplicationsState.AppFilter() {
2073         @Override
2074         public void init() {
2075         }
2076 
2077         @Override
2078         public boolean filterApp(
2079                 ApplicationsState.AppEntry entry) {
2080             boolean isMovieApp;
2081             synchronized (entry) {
2082                 isMovieApp = entry.info.category == ApplicationInfo.CATEGORY_VIDEO;
2083             }
2084             return isMovieApp;
2085         }
2086     };
2087 
2088     public static final ApplicationsState.AppFilter
2089             FILTER_PHOTOS =
2090             new ApplicationsState.AppFilter() {
2091                 @Override
2092                 public void init() {
2093                 }
2094 
2095                 @Override
2096                 public boolean filterApp(
2097                         ApplicationsState.AppEntry entry) {
2098                     boolean isPhotosApp;
2099                     synchronized (entry) {
2100                         isPhotosApp = entry.info.category == ApplicationInfo.CATEGORY_IMAGE;
2101                     }
2102                     return isPhotosApp;
2103                 }
2104             };
2105 
2106     public static final ApplicationsState.AppFilter
2107             FILTER_OTHER_APPS =
2108             new ApplicationsState.AppFilter() {
2109                 @Override
2110                 public void init() {
2111                 }
2112 
2113                 @Override
2114                 public boolean filterApp(
2115                         ApplicationsState.AppEntry entry) {
2116                     boolean isCategorized;
2117                     synchronized (entry) {
2118                         isCategorized =
2119                                 FILTER_AUDIO.filterApp(entry)
2120                                         || FILTER_GAMES.filterApp(entry)
2121                                         || FILTER_MOVIES.filterApp(entry)
2122                                         || FILTER_PHOTOS.filterApp(entry);
2123                     }
2124                     return !isCategorized;
2125                 }
2126             };
2127 }
2128