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