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