2 * Copyright (C) 2015 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settingslib.applications;
19 import android.app.ActivityManager;
20 import android.app.AppGlobals;
21 import android.app.Application;
22 import android.content.BroadcastReceiver;
23 import android.content.Context;
24 import android.content.Intent;
25 import android.content.IntentFilter;
26 import android.content.pm.ApplicationInfo;
27 import android.content.pm.IPackageManager;
28 import android.content.pm.IPackageStatsObserver;
29 import android.content.pm.PackageManager;
30 import android.content.pm.PackageStats;
31 import android.content.pm.ParceledListSlice;
32 import android.content.pm.ResolveInfo;
33 import android.content.pm.UserInfo;
34 import android.graphics.drawable.Drawable;
35 import android.net.Uri;
36 import android.os.Handler;
37 import android.os.HandlerThread;
38 import android.os.Looper;
39 import android.os.Message;
40 import android.os.Process;
41 import android.os.RemoteException;
42 import android.os.SystemClock;
43 import android.os.UserHandle;
44 import android.os.UserManager;
45 import android.text.format.Formatter;
46 import android.util.Log;
47 import android.util.SparseArray;
49 import com.android.internal.util.ArrayUtils;
52 import java.text.Collator;
53 import java.text.Normalizer;
54 import java.text.Normalizer.Form;
55 import java.util.ArrayList;
56 import java.util.Collections;
57 import java.util.Comparator;
58 import java.util.HashMap;
59 import java.util.List;
60 import java.util.Objects;
61 import java.util.regex.Pattern;
64 * Keeps track of information about all installed applications, lazy-loading
67 public class ApplicationsState {
68 static final String TAG = "ApplicationsState";
69 static final boolean DEBUG = false;
70 static final boolean DEBUG_LOCKING = false;
72 public static final int SIZE_UNKNOWN = -1;
73 public static final int SIZE_INVALID = -2;
75 static final Pattern REMOVE_DIACRITICALS_PATTERN
76 = Pattern.compile("\\p{InCombiningDiacriticalMarks}+");
78 static final Object sLock = new Object();
79 static ApplicationsState sInstance;
81 public static ApplicationsState getInstance(Application app) {
82 synchronized (sLock) {
83 if (sInstance == null) {
84 sInstance = new ApplicationsState(app);
90 final Context mContext;
91 final PackageManager mPm;
92 final IPackageManager mIpm;
93 final UserManager mUm;
94 final int mAdminRetrieveFlags;
95 final int mRetrieveFlags;
96 PackageIntentReceiver mPackageIntentReceiver;
99 boolean mHaveDisabledApps;
101 // Information about all applications. Synchronize on mEntriesMap
102 // to protect access to these.
103 final ArrayList<Session> mSessions = new ArrayList<Session>();
104 final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>();
105 final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges();
106 // Map: userid => (Map: package name => AppEntry)
107 final SparseArray<HashMap<String, AppEntry>> mEntriesMap =
108 new SparseArray<HashMap<String, AppEntry>>();
109 final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>();
110 List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>();
112 String mCurComputingSizePkg;
113 int mCurComputingSizeUserId;
114 boolean mSessionsChanged;
116 // Temporary for dispatching session callbacks. Only touched by main thread.
117 final ArrayList<Session> mActiveSessions = new ArrayList<Session>();
119 final HandlerThread mThread;
120 final BackgroundHandler mBackgroundHandler;
121 final MainHandler mMainHandler = new MainHandler(Looper.getMainLooper());
123 private ApplicationsState(Application app) {
125 mPm = mContext.getPackageManager();
126 mIpm = AppGlobals.getPackageManager();
127 mUm = (UserManager) app.getSystemService(Context.USER_SERVICE);
128 for (int userId : mUm.getProfileIdsWithDisabled(UserHandle.myUserId())) {
129 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
131 mThread = new HandlerThread("ApplicationsState.Loader",
132 Process.THREAD_PRIORITY_BACKGROUND);
134 mBackgroundHandler = new BackgroundHandler(mThread.getLooper());
136 // Only the owner can see all apps.
137 mAdminRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES |
138 PackageManager.GET_DISABLED_COMPONENTS |
139 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
140 mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS |
141 PackageManager.GET_DISABLED_UNTIL_USED_COMPONENTS;
144 * This is a trick to prevent the foreground thread from being delayed.
145 * The problem is that Dalvik monitors are initially spin locks, to keep
146 * them lightweight. This leads to unfair contention -- Even though the
147 * background thread only holds the lock for a short amount of time, if
148 * it keeps running and locking again it can prevent the main thread from
149 * acquiring its lock for a long time... sometimes even > 5 seconds
150 * (leading to an ANR).
152 * Dalvik will promote a monitor to a "real" lock if it detects enough
153 * contention on it. It doesn't figure this out fast enough for us
154 * here, though, so this little trick will force it to turn into a real
157 synchronized (mEntriesMap) {
160 } catch (InterruptedException e) {
165 public Looper getBackgroundLooper() {
166 return mThread.getLooper();
169 public Session newSession(Callbacks callbacks) {
170 Session s = new Session(callbacks);
171 synchronized (mEntriesMap) {
177 void doResumeIfNeededLocked() {
182 if (mPackageIntentReceiver == null) {
183 mPackageIntentReceiver = new PackageIntentReceiver();
184 mPackageIntentReceiver.registerReceiver();
186 mApplications = new ArrayList<ApplicationInfo>();
187 for (UserInfo user : mUm.getProfiles(UserHandle.myUserId())) {
189 // If this user is new, it needs a map created.
190 if (mEntriesMap.indexOfKey(user.id) < 0) {
191 mEntriesMap.put(user.id, new HashMap<String, AppEntry>());
193 @SuppressWarnings("unchecked")
194 ParceledListSlice<ApplicationInfo> list =
195 mIpm.getInstalledApplications(
196 user.isAdmin() ? mAdminRetrieveFlags : mRetrieveFlags,
198 mApplications.addAll(list.getList());
199 } catch (RemoteException e) {
203 if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) {
204 // If an interesting part of the configuration has changed, we
205 // should completely reload the app entries.
208 for (int i=0; i<mAppEntries.size(); i++) {
209 mAppEntries.get(i).sizeStale = true;
213 mHaveDisabledApps = false;
214 for (int i=0; i<mApplications.size(); i++) {
215 final ApplicationInfo info = mApplications.get(i);
216 // Need to trim out any applications that are disabled by
217 // something different than the user.
219 if (info.enabledSetting != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
220 mApplications.remove(i);
224 mHaveDisabledApps = true;
226 int userId = UserHandle.getUserId(info.uid);
227 final AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
232 if (mAppEntries.size() > mApplications.size()) {
233 // There are less apps now, some must have been uninstalled.
236 mCurComputingSizePkg = null;
237 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
238 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
242 private void clearEntries() {
243 for (int i = 0; i < mEntriesMap.size(); i++) {
244 mEntriesMap.valueAt(i).clear();
249 public boolean haveDisabledApps() {
250 return mHaveDisabledApps;
253 void doPauseIfNeededLocked() {
257 for (int i=0; i<mSessions.size(); i++) {
258 if (mSessions.get(i).mResumed) {
265 void doPauseLocked() {
267 if (mPackageIntentReceiver != null) {
268 mPackageIntentReceiver.unregisterReceiver();
269 mPackageIntentReceiver = null;
273 public AppEntry getEntry(String packageName, int userId) {
274 if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock...");
275 synchronized (mEntriesMap) {
276 AppEntry entry = mEntriesMap.get(userId).get(packageName);
278 ApplicationInfo info = getAppInfoLocked(packageName, userId);
281 info = mIpm.getApplicationInfo(packageName, 0, userId);
282 } catch (RemoteException e) {
283 Log.w(TAG, "getEntry couldn't reach PackageManager", e);
288 entry = getEntryLocked(info);
291 if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock");
296 private ApplicationInfo getAppInfoLocked(String pkg, int userId) {
297 for (int i = 0; i < mApplications.size(); i++) {
298 ApplicationInfo info = mApplications.get(i);
299 if (pkg.equals(info.packageName)
300 && userId == UserHandle.getUserId(info.uid)) {
307 public void ensureIcon(AppEntry entry) {
308 if (entry.icon != null) {
311 synchronized (entry) {
312 entry.ensureIconLocked(mContext, mPm);
316 public void requestSize(String packageName, int userId) {
317 if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock...");
318 synchronized (mEntriesMap) {
319 AppEntry entry = mEntriesMap.get(userId).get(packageName);
321 mPm.getPackageSizeInfoAsUser(packageName, userId, mBackgroundHandler.mStatsObserver);
323 if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock");
327 long sumCacheSizes() {
329 if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock...");
330 synchronized (mEntriesMap) {
331 if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock");
332 for (int i=mAppEntries.size()-1; i>=0; i--) {
333 sum += mAppEntries.get(i).cacheSize;
335 if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock");
340 int indexOfApplicationInfoLocked(String pkgName, int userId) {
341 for (int i=mApplications.size()-1; i>=0; i--) {
342 ApplicationInfo appInfo = mApplications.get(i);
343 if (appInfo.packageName.equals(pkgName)
344 && UserHandle.getUserId(appInfo.uid) == userId) {
351 void addPackage(String pkgName, int userId) {
353 synchronized (mEntriesMap) {
354 if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock");
355 if (DEBUG) Log.i(TAG, "Adding package " + pkgName);
357 // If we are not resumed, we will do a full query the
358 // next time we resume, so there is no reason to do work
360 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed");
363 if (indexOfApplicationInfoLocked(pkgName, userId) >= 0) {
364 if (DEBUG) Log.i(TAG, "Package already exists!");
365 if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists");
368 ApplicationInfo info = mIpm.getApplicationInfo(pkgName,
369 mUm.isUserAdmin(userId) ? mAdminRetrieveFlags : mRetrieveFlags,
375 if (info.enabledSetting
376 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) {
379 mHaveDisabledApps = true;
381 mApplications.add(info);
382 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) {
383 mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES);
385 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
386 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
388 if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock");
390 } catch (RemoteException e) {
394 public void removePackage(String pkgName, int userId) {
395 synchronized (mEntriesMap) {
396 if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock");
397 int idx = indexOfApplicationInfoLocked(pkgName, userId);
398 if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx);
400 AppEntry entry = mEntriesMap.get(userId).get(pkgName);
401 if (DEBUG) Log.i(TAG, "removePackage: " + entry);
403 mEntriesMap.get(userId).remove(pkgName);
404 mAppEntries.remove(entry);
406 ApplicationInfo info = mApplications.get(idx);
407 mApplications.remove(idx);
409 mHaveDisabledApps = false;
410 for (int i=0; i<mApplications.size(); i++) {
411 if (!mApplications.get(i).enabled) {
412 mHaveDisabledApps = true;
417 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
418 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
421 if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock");
425 public void invalidatePackage(String pkgName, int userId) {
426 removePackage(pkgName, userId);
427 addPackage(pkgName, userId);
430 private void addUser(int userId) {
431 final int profileIds[] = mUm.getProfileIdsWithDisabled(UserHandle.myUserId());
432 if (ArrayUtils.contains(profileIds, userId)) {
433 synchronized (mEntriesMap) {
434 mEntriesMap.put(userId, new HashMap<String, AppEntry>());
436 // If resumed, Manually pause, then cause a resume to repopulate the app list.
437 // This is the simplest way to reload the packages so that the new user
438 // is included. Otherwise the list will be repopulated on next resume.
440 doResumeIfNeededLocked();
442 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
443 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
449 private void removeUser(int userId) {
450 synchronized (mEntriesMap) {
451 HashMap<String, AppEntry> userMap = mEntriesMap.get(userId);
452 if (userMap != null) {
453 for (AppEntry appEntry : userMap.values()) {
454 mAppEntries.remove(appEntry);
455 mApplications.remove(appEntry.info);
457 mEntriesMap.remove(userId);
458 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) {
459 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED);
465 private AppEntry getEntryLocked(ApplicationInfo info) {
466 int userId = UserHandle.getUserId(info.uid);
467 AppEntry entry = mEntriesMap.get(userId).get(info.packageName);
468 if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry);
470 if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName);
471 entry = new AppEntry(mContext, info, mCurId++);
472 mEntriesMap.get(userId).put(info.packageName, entry);
473 mAppEntries.add(entry);
474 } else if (entry.info != info) {
480 // --------------------------------------------------------------
482 private long getTotalInternalSize(PackageStats ps) {
484 return ps.codeSize + ps.dataSize;
489 private long getTotalExternalSize(PackageStats ps) {
491 // We also include the cache size here because for non-emulated
492 // we don't automtically clean cache files.
493 return ps.externalCodeSize + ps.externalDataSize
494 + ps.externalCacheSize
495 + ps.externalMediaSize + ps.externalObbSize;
500 private String getSizeStr(long size) {
502 return Formatter.formatFileSize(mContext, size);
507 void rebuildActiveSessions() {
508 synchronized (mEntriesMap) {
509 if (!mSessionsChanged) {
512 mActiveSessions.clear();
513 for (int i=0; i<mSessions.size(); i++) {
514 Session s = mSessions.get(i);
516 mActiveSessions.add(s);
522 public static String normalize(String str) {
523 String tmp = Normalizer.normalize(str, Form.NFD);
524 return REMOVE_DIACRITICALS_PATTERN.matcher(tmp)
525 .replaceAll("").toLowerCase();
528 public class Session {
529 final Callbacks mCallbacks;
532 // Rebuilding of app list. Synchronized on mRebuildSync.
533 final Object mRebuildSync = new Object();
534 boolean mRebuildRequested;
535 boolean mRebuildAsync;
536 AppFilter mRebuildFilter;
537 Comparator<AppEntry> mRebuildComparator;
538 ArrayList<AppEntry> mRebuildResult;
539 ArrayList<AppEntry> mLastAppList;
540 boolean mRebuildForeground;
542 Session(Callbacks callbacks) {
543 mCallbacks = callbacks;
546 public void resume() {
547 if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock...");
548 synchronized (mEntriesMap) {
551 mSessionsChanged = true;
552 doResumeIfNeededLocked();
555 if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock");
558 public void pause() {
559 if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock...");
560 synchronized (mEntriesMap) {
563 mSessionsChanged = true;
564 mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this);
565 doPauseIfNeededLocked();
567 if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock");
571 public ArrayList<AppEntry> getAllApps() {
572 synchronized (mEntriesMap) {
573 return new ArrayList<>(mAppEntries);
577 // Creates a new list of app entries with the given filter and comparator.
578 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) {
579 return rebuild(filter, comparator, true);
582 public ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator,
583 boolean foreground) {
584 synchronized (mRebuildSync) {
585 synchronized (mEntriesMap) {
586 mRebuildingSessions.add(this);
587 mRebuildRequested = true;
588 mRebuildAsync = false;
589 mRebuildFilter = filter;
590 mRebuildComparator = comparator;
591 mRebuildForeground = foreground;
592 mRebuildResult = null;
593 if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) {
594 Message msg = mBackgroundHandler.obtainMessage(
595 BackgroundHandler.MSG_REBUILD_LIST);
596 mBackgroundHandler.sendMessage(msg);
600 // We will wait for .25s for the list to be built.
601 long waitend = SystemClock.uptimeMillis()+250;
603 while (mRebuildResult == null) {
604 long now = SystemClock.uptimeMillis();
605 if (now >= waitend) {
609 mRebuildSync.wait(waitend - now);
610 } catch (InterruptedException e) {
614 mRebuildAsync = true;
616 return mRebuildResult;
620 void handleRebuildList() {
622 Comparator<AppEntry> comparator;
623 synchronized (mRebuildSync) {
624 if (!mRebuildRequested) {
628 filter = mRebuildFilter;
629 comparator = mRebuildComparator;
630 mRebuildRequested = false;
631 mRebuildFilter = null;
632 mRebuildComparator = null;
633 if (mRebuildForeground) {
634 Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND);
635 mRebuildForeground = false;
639 if (filter != null) {
644 synchronized (mEntriesMap) {
645 apps = new ArrayList<>(mAppEntries);
648 ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>();
649 if (DEBUG) Log.i(TAG, "Rebuilding...");
650 for (int i=0; i<apps.size(); i++) {
651 AppEntry entry = apps.get(i);
652 if (entry != null && (filter == null || filter.filterApp(entry))) {
653 synchronized (mEntriesMap) {
654 if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock");
655 if (comparator != null) {
656 // Only need the label if we are going to be sorting.
657 entry.ensureLabel(mContext);
659 if (DEBUG) Log.i(TAG, "Using " + entry.info.packageName + ": " + entry);
660 filteredApps.add(entry);
661 if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock");
666 if (comparator != null) {
667 Collections.sort(filteredApps, comparator);
670 synchronized (mRebuildSync) {
671 if (!mRebuildRequested) {
672 mLastAppList = filteredApps;
673 if (!mRebuildAsync) {
674 mRebuildResult = filteredApps;
675 mRebuildSync.notifyAll();
677 if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) {
678 Message msg = mMainHandler.obtainMessage(
679 MainHandler.MSG_REBUILD_COMPLETE, this);
680 mMainHandler.sendMessage(msg);
686 Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND);
689 public void release() {
691 synchronized (mEntriesMap) {
692 mSessions.remove(this);
697 class MainHandler extends Handler {
698 static final int MSG_REBUILD_COMPLETE = 1;
699 static final int MSG_PACKAGE_LIST_CHANGED = 2;
700 static final int MSG_PACKAGE_ICON_CHANGED = 3;
701 static final int MSG_PACKAGE_SIZE_CHANGED = 4;
702 static final int MSG_ALL_SIZES_COMPUTED = 5;
703 static final int MSG_RUNNING_STATE_CHANGED = 6;
704 static final int MSG_LAUNCHER_INFO_CHANGED = 7;
705 static final int MSG_LOAD_ENTRIES_COMPLETE = 8;
707 public MainHandler(Looper looper) {
712 public void handleMessage(Message msg) {
713 rebuildActiveSessions();
715 case MSG_REBUILD_COMPLETE: {
716 Session s = (Session)msg.obj;
717 if (mActiveSessions.contains(s)) {
718 s.mCallbacks.onRebuildComplete(s.mLastAppList);
721 case MSG_PACKAGE_LIST_CHANGED: {
722 for (int i=0; i<mActiveSessions.size(); i++) {
723 mActiveSessions.get(i).mCallbacks.onPackageListChanged();
726 case MSG_PACKAGE_ICON_CHANGED: {
727 for (int i=0; i<mActiveSessions.size(); i++) {
728 mActiveSessions.get(i).mCallbacks.onPackageIconChanged();
731 case MSG_PACKAGE_SIZE_CHANGED: {
732 for (int i=0; i<mActiveSessions.size(); i++) {
733 mActiveSessions.get(i).mCallbacks.onPackageSizeChanged(
737 case MSG_ALL_SIZES_COMPUTED: {
738 for (int i=0; i<mActiveSessions.size(); i++) {
739 mActiveSessions.get(i).mCallbacks.onAllSizesComputed();
742 case MSG_RUNNING_STATE_CHANGED: {
743 for (int i=0; i<mActiveSessions.size(); i++) {
744 mActiveSessions.get(i).mCallbacks.onRunningStateChanged(
748 case MSG_LAUNCHER_INFO_CHANGED: {
749 for (int i=0; i<mActiveSessions.size(); i++) {
750 mActiveSessions.get(i).mCallbacks.onLauncherInfoChanged();
753 case MSG_LOAD_ENTRIES_COMPLETE: {
754 for (int i=0; i<mActiveSessions.size(); i++) {
755 mActiveSessions.get(i).mCallbacks.onLoadEntriesCompleted();
762 private class BackgroundHandler extends Handler {
763 static final int MSG_REBUILD_LIST = 1;
764 static final int MSG_LOAD_ENTRIES = 2;
765 static final int MSG_LOAD_ICONS = 3;
766 static final int MSG_LOAD_SIZES = 4;
767 static final int MSG_LOAD_LAUNCHER = 5;
771 BackgroundHandler(Looper looper) {
776 public void handleMessage(Message msg) {
777 // Always try rebuilding list first thing, if needed.
778 ArrayList<Session> rebuildingSessions = null;
779 synchronized (mEntriesMap) {
780 if (mRebuildingSessions.size() > 0) {
781 rebuildingSessions = new ArrayList<Session>(mRebuildingSessions);
782 mRebuildingSessions.clear();
785 if (rebuildingSessions != null) {
786 for (int i=0; i<rebuildingSessions.size(); i++) {
787 rebuildingSessions.get(i).handleRebuildList();
792 case MSG_REBUILD_LIST: {
794 case MSG_LOAD_ENTRIES: {
796 synchronized (mEntriesMap) {
797 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock");
798 for (int i = 0; i < mApplications.size() && numDone < 6; i++) {
801 Message m = mMainHandler.obtainMessage(
802 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
803 mMainHandler.sendMessage(m);
805 ApplicationInfo info = mApplications.get(i);
806 int userId = UserHandle.getUserId(info.uid);
807 if (mEntriesMap.get(userId).get(info.packageName) == null) {
809 getEntryLocked(info);
811 if (userId != 0 && mEntriesMap.indexOfKey(0) >= 0) {
812 // If this app is for a profile and we are on the owner, remove
813 // the owner entry if it isn't installed. This will prevent
814 // duplicates of work only apps showing up as 'not installed
816 // Note: This depends on us traversing the users in order, which
817 // happens because of the way we generate the list in
818 // doResumeIfNeededLocked.
819 AppEntry entry = mEntriesMap.get(0).get(info.packageName);
821 (entry.info.flags & ApplicationInfo.FLAG_INSTALLED) == 0) {
822 mEntriesMap.get(0).remove(info.packageName);
823 mAppEntries.remove(entry);
827 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock");
831 sendEmptyMessage(MSG_LOAD_ENTRIES);
833 if (!mMainHandler.hasMessages(MainHandler.MSG_LOAD_ENTRIES_COMPLETE)) {
834 mMainHandler.sendEmptyMessage(MainHandler.MSG_LOAD_ENTRIES_COMPLETE);
836 sendEmptyMessage(MSG_LOAD_LAUNCHER);
839 case MSG_LOAD_LAUNCHER: {
840 Intent launchIntent = new Intent(Intent.ACTION_MAIN, null)
841 .addCategory(Intent.CATEGORY_LAUNCHER);
843 for (int i = 0; i < mEntriesMap.size(); i++) {
844 int userId = mEntriesMap.keyAt(i);
845 // If we do not specify MATCH_DIRECT_BOOT_AWARE or
846 // MATCH_DIRECT_BOOT_UNAWARE, system will derive and update the flags
847 // according to the user's lock state. When the user is locked, components
848 // with ComponentInfo#directBootAware == false will be filtered. We should
849 // explicitly include both direct boot aware and unaware components here.
850 List<ResolveInfo> intents = mPm.queryIntentActivitiesAsUser(
852 PackageManager.GET_DISABLED_COMPONENTS
853 | PackageManager.MATCH_DIRECT_BOOT_AWARE
854 | PackageManager.MATCH_DIRECT_BOOT_UNAWARE,
857 synchronized (mEntriesMap) {
858 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER acquired lock");
859 HashMap<String, AppEntry> userEntries = mEntriesMap.valueAt(i);
860 final int N = intents.size();
861 for (int j = 0; j < N; j++) {
862 String packageName = intents.get(j).activityInfo.packageName;
863 AppEntry entry = userEntries.get(packageName);
865 entry.hasLauncherEntry = true;
867 Log.w(TAG, "Cannot find pkg: " + packageName
868 + " on user " + userId);
871 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_LAUNCHER releasing lock");
875 if (!mMainHandler.hasMessages(MainHandler.MSG_LAUNCHER_INFO_CHANGED)) {
876 mMainHandler.sendEmptyMessage(MainHandler.MSG_LAUNCHER_INFO_CHANGED);
878 sendEmptyMessage(MSG_LOAD_ICONS);
880 case MSG_LOAD_ICONS: {
882 synchronized (mEntriesMap) {
883 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock");
884 for (int i=0; i<mAppEntries.size() && numDone<2; i++) {
885 AppEntry entry = mAppEntries.get(i);
886 if (entry.icon == null || !entry.mounted) {
887 synchronized (entry) {
888 if (entry.ensureIconLocked(mContext, mPm)) {
891 Message m = mMainHandler.obtainMessage(
892 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
893 mMainHandler.sendMessage(m);
900 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock");
903 if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) {
904 mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED);
908 sendEmptyMessage(MSG_LOAD_ICONS);
910 sendEmptyMessage(MSG_LOAD_SIZES);
913 case MSG_LOAD_SIZES: {
914 synchronized (mEntriesMap) {
915 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock");
916 if (mCurComputingSizePkg != null) {
917 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing");
921 long now = SystemClock.uptimeMillis();
922 for (int i=0; i<mAppEntries.size(); i++) {
923 AppEntry entry = mAppEntries.get(i);
924 if (entry.size == SIZE_UNKNOWN || entry.sizeStale) {
925 if (entry.sizeLoadStart == 0 ||
926 (entry.sizeLoadStart < (now-20*1000))) {
929 Message m = mMainHandler.obtainMessage(
930 MainHandler.MSG_RUNNING_STATE_CHANGED, 1);
931 mMainHandler.sendMessage(m);
933 entry.sizeLoadStart = now;
934 mCurComputingSizePkg = entry.info.packageName;
935 mCurComputingSizeUserId = UserHandle.getUserId(entry.info.uid);
936 mPm.getPackageSizeInfoAsUser(mCurComputingSizePkg,
937 mCurComputingSizeUserId, mStatsObserver);
939 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing");
943 if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) {
944 mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED);
946 Message m = mMainHandler.obtainMessage(
947 MainHandler.MSG_RUNNING_STATE_CHANGED, 0);
948 mMainHandler.sendMessage(m);
950 if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock");
956 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
957 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
958 boolean sizeChanged = false;
959 synchronized (mEntriesMap) {
960 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock");
961 HashMap<String, AppEntry> userMap = mEntriesMap.get(stats.userHandle);
962 if (userMap == null) {
963 // The user must have been removed.
966 AppEntry entry = userMap.get(stats.packageName);
968 synchronized (entry) {
969 entry.sizeStale = false;
970 entry.sizeLoadStart = 0;
971 long externalCodeSize = stats.externalCodeSize
972 + stats.externalObbSize;
973 long externalDataSize = stats.externalDataSize
974 + stats.externalMediaSize;
975 long newSize = externalCodeSize + externalDataSize
976 + getTotalInternalSize(stats);
977 if (entry.size != newSize ||
978 entry.cacheSize != stats.cacheSize ||
979 entry.codeSize != stats.codeSize ||
980 entry.dataSize != stats.dataSize ||
981 entry.externalCodeSize != externalCodeSize ||
982 entry.externalDataSize != externalDataSize ||
983 entry.externalCacheSize != stats.externalCacheSize) {
984 entry.size = newSize;
985 entry.cacheSize = stats.cacheSize;
986 entry.codeSize = stats.codeSize;
987 entry.dataSize = stats.dataSize;
988 entry.externalCodeSize = externalCodeSize;
989 entry.externalDataSize = externalDataSize;
990 entry.externalCacheSize = stats.externalCacheSize;
991 entry.sizeStr = getSizeStr(entry.size);
992 entry.internalSize = getTotalInternalSize(stats);
993 entry.internalSizeStr = getSizeStr(entry.internalSize);
994 entry.externalSize = getTotalExternalSize(stats);
995 entry.externalSizeStr = getSizeStr(entry.externalSize);
996 if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry
997 + ": " + entry.sizeStr);
1002 Message msg = mMainHandler.obtainMessage(
1003 MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName);
1004 mMainHandler.sendMessage(msg);
1007 if (mCurComputingSizePkg != null
1008 && (mCurComputingSizePkg.equals(stats.packageName)
1009 && mCurComputingSizeUserId == stats.userHandle)) {
1010 mCurComputingSizePkg = null;
1011 sendEmptyMessage(MSG_LOAD_SIZES);
1013 if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock");
1020 * Receives notifications when applications are added/removed.
1022 private class PackageIntentReceiver extends BroadcastReceiver {
1023 void registerReceiver() {
1024 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
1025 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
1026 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
1027 filter.addDataScheme("package");
1028 mContext.registerReceiver(this, filter);
1029 // Register for events related to sdcard installation.
1030 IntentFilter sdFilter = new IntentFilter();
1031 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
1032 sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
1033 mContext.registerReceiver(this, sdFilter);
1034 // Register for events related to user creation/deletion.
1035 IntentFilter userFilter = new IntentFilter();
1036 userFilter.addAction(Intent.ACTION_USER_ADDED);
1037 userFilter.addAction(Intent.ACTION_USER_REMOVED);
1038 mContext.registerReceiver(this, userFilter);
1040 void unregisterReceiver() {
1041 mContext.unregisterReceiver(this);
1044 public void onReceive(Context context, Intent intent) {
1045 String actionStr = intent.getAction();
1046 if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) {
1047 Uri data = intent.getData();
1048 String pkgName = data.getEncodedSchemeSpecificPart();
1049 for (int i = 0; i < mEntriesMap.size(); i++) {
1050 addPackage(pkgName, mEntriesMap.keyAt(i));
1052 } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) {
1053 Uri data = intent.getData();
1054 String pkgName = data.getEncodedSchemeSpecificPart();
1055 for (int i = 0; i < mEntriesMap.size(); i++) {
1056 removePackage(pkgName, mEntriesMap.keyAt(i));
1058 } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) {
1059 Uri data = intent.getData();
1060 String pkgName = data.getEncodedSchemeSpecificPart();
1061 for (int i = 0; i < mEntriesMap.size(); i++) {
1062 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1064 } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) ||
1065 Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) {
1066 // When applications become available or unavailable (perhaps because
1067 // the SD card was inserted or ejected) we need to refresh the
1068 // AppInfo with new label, icon and size information as appropriate
1069 // given the newfound (un)availability of the application.
1070 // A simple way to do that is to treat the refresh as a package
1071 // removal followed by a package addition.
1072 String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST);
1073 if (pkgList == null || pkgList.length == 0) {
1077 boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr);
1079 for (String pkgName : pkgList) {
1080 for (int i = 0; i < mEntriesMap.size(); i++) {
1081 invalidatePackage(pkgName, mEntriesMap.keyAt(i));
1085 } else if (Intent.ACTION_USER_ADDED.equals(actionStr)) {
1086 addUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1087 } else if (Intent.ACTION_USER_REMOVED.equals(actionStr)) {
1088 removeUser(intent.getIntExtra(Intent.EXTRA_USER_HANDLE, UserHandle.USER_NULL));
1093 public interface Callbacks {
1094 void onRunningStateChanged(boolean running);
1095 void onPackageListChanged();
1096 void onRebuildComplete(ArrayList<AppEntry> apps);
1097 void onPackageIconChanged();
1098 void onPackageSizeChanged(String packageName);
1099 void onAllSizesComputed();
1100 void onLauncherInfoChanged();
1101 void onLoadEntriesCompleted();
1104 public static class SizeInfo {
1105 public long cacheSize;
1106 public long codeSize;
1107 public long dataSize;
1108 public long externalCodeSize;
1109 public long externalDataSize;
1111 // This is the part of externalDataSize that is in the cache
1112 // section of external storage. Note that we don't just combine
1113 // this with cacheSize because currently the platform can't
1114 // automatically trim this data when needed, so it is something
1115 // the user may need to manage. The externalDataSize also includes
1116 // this value, since what this is here is really the part of
1117 // externalDataSize that we can just consider to be "cache" files
1118 // for purposes of cleaning them up in the app details UI.
1119 public long externalCacheSize;
1122 public static class AppEntry extends SizeInfo {
1123 public final File apkFile;
1124 public final long id;
1125 public String label;
1127 public long internalSize;
1128 public long externalSize;
1130 public boolean mounted;
1133 * Setting this to {@code true} prevents the entry to be filtered by
1134 * {@link #FILTER_DOWNLOADED_AND_LAUNCHER}.
1136 public boolean hasLauncherEntry;
1138 public String getNormalizedLabel() {
1139 if (normalizedLabel != null) {
1140 return normalizedLabel;
1142 normalizedLabel = normalize(label);
1143 return normalizedLabel;
1146 // Need to synchronize on 'this' for the following.
1147 public ApplicationInfo info;
1148 public Drawable icon;
1149 public String sizeStr;
1150 public String internalSizeStr;
1151 public String externalSizeStr;
1152 public boolean sizeStale;
1153 public long sizeLoadStart;
1155 public String normalizedLabel;
1157 // A location where extra info can be placed to be used by custom filters.
1158 public Object extraInfo;
1160 AppEntry(Context context, ApplicationInfo info, long id) {
1161 apkFile = new File(info.sourceDir);
1164 this.size = SIZE_UNKNOWN;
1165 this.sizeStale = true;
1166 ensureLabel(context);
1169 public void ensureLabel(Context context) {
1170 if (this.label == null || !this.mounted) {
1171 if (!this.apkFile.exists()) {
1172 this.mounted = false;
1173 this.label = info.packageName;
1175 this.mounted = true;
1176 CharSequence label = info.loadLabel(context.getPackageManager());
1177 this.label = label != null ? label.toString() : info.packageName;
1182 boolean ensureIconLocked(Context context, PackageManager pm) {
1183 if (this.icon == null) {
1184 if (this.apkFile.exists()) {
1185 this.icon = getBadgedIcon(pm);
1188 this.mounted = false;
1189 this.icon = context.getDrawable(
1190 com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon);
1192 } else if (!this.mounted) {
1193 // If the app wasn't mounted but is now mounted, reload
1195 if (this.apkFile.exists()) {
1196 this.mounted = true;
1197 this.icon = getBadgedIcon(pm);
1204 private Drawable getBadgedIcon(PackageManager pm) {
1205 // Do badging ourself so that it comes from the user of the app not the current user.
1206 return pm.getUserBadgedIcon(pm.loadUnbadgedItemIcon(info, info),
1207 new UserHandle(UserHandle.getUserId(info.uid)));
1210 public String getVersion(Context context) {
1212 return context.getPackageManager().getPackageInfo(info.packageName, 0).versionName;
1213 } catch (PackageManager.NameNotFoundException e) {
1220 * Compare by label, then package name, then uid.
1222 public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
1223 private final Collator sCollator = Collator.getInstance();
1225 public int compare(AppEntry object1, AppEntry object2) {
1226 int compareResult = sCollator.compare(object1.label, object2.label);
1227 if (compareResult != 0) {
1228 return compareResult;
1230 if (object1.info != null && object2.info != null) {
1232 sCollator.compare(object1.info.packageName, object2.info.packageName);
1233 if (compareResult != 0) {
1234 return compareResult;
1237 return object1.info.uid - object2.info.uid;
1241 public static final Comparator<AppEntry> SIZE_COMPARATOR
1242 = new Comparator<AppEntry>() {
1244 public int compare(AppEntry object1, AppEntry object2) {
1245 if (object1.size < object2.size) return 1;
1246 if (object1.size > object2.size) return -1;
1247 return ALPHA_COMPARATOR.compare(object1, object2);
1251 public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR
1252 = new Comparator<AppEntry>() {
1254 public int compare(AppEntry object1, AppEntry object2) {
1255 if (object1.internalSize < object2.internalSize) return 1;
1256 if (object1.internalSize > object2.internalSize) return -1;
1257 return ALPHA_COMPARATOR.compare(object1, object2);
1261 public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR
1262 = new Comparator<AppEntry>() {
1264 public int compare(AppEntry object1, AppEntry object2) {
1265 if (object1.externalSize < object2.externalSize) return 1;
1266 if (object1.externalSize > object2.externalSize) return -1;
1267 return ALPHA_COMPARATOR.compare(object1, object2);
1271 public interface AppFilter {
1273 boolean filterApp(AppEntry info);
1276 public static final AppFilter FILTER_PERSONAL = new AppFilter() {
1277 private int mCurrentUser;
1279 public void init() {
1280 mCurrentUser = ActivityManager.getCurrentUser();
1284 public boolean filterApp(AppEntry entry) {
1285 return UserHandle.getUserId(entry.info.uid) == mCurrentUser;
1289 public static final AppFilter FILTER_WITHOUT_DISABLED_UNTIL_USED = new AppFilter() {
1290 public void init() {
1295 public boolean filterApp(AppEntry entry) {
1296 return entry.info.enabledSetting
1297 != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
1301 public static final AppFilter FILTER_WORK = new AppFilter() {
1302 private int mCurrentUser;
1304 public void init() {
1305 mCurrentUser = ActivityManager.getCurrentUser();
1309 public boolean filterApp(AppEntry entry) {
1310 return UserHandle.getUserId(entry.info.uid) != mCurrentUser;
1315 * Displays a combined list with "downloaded" and "visible in launcher" apps only.
1317 public static final AppFilter FILTER_DOWNLOADED_AND_LAUNCHER = new AppFilter() {
1318 public void init() {
1322 public boolean filterApp(AppEntry entry) {
1323 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1325 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1327 } else if (entry.hasLauncherEntry) {
1334 public static final AppFilter FILTER_THIRD_PARTY = new AppFilter() {
1335 public void init() {
1339 public boolean filterApp(AppEntry entry) {
1340 if ((entry.info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) {
1342 } else if ((entry.info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) {
1349 public static final AppFilter FILTER_DISABLED = new AppFilter() {
1350 public void init() {
1354 public boolean filterApp(AppEntry entry) {
1355 return !entry.info.enabled;
1359 public static final AppFilter FILTER_ALL_ENABLED = new AppFilter() {
1360 public void init() {
1364 public boolean filterApp(AppEntry entry) {
1365 return entry.info.enabled;
1369 public static final AppFilter FILTER_EVERYTHING = new AppFilter() {
1370 public void init() {
1374 public boolean filterApp(AppEntry entry) {
1379 public static final AppFilter FILTER_WITH_DOMAIN_URLS = new AppFilter() {
1380 public void init() {
1384 public boolean filterApp(AppEntry entry) {
1385 return (entry.info.privateFlags & ApplicationInfo.PRIVATE_FLAG_HAS_DOMAIN_URLS) != 0;
1389 public static class VolumeFilter implements AppFilter {
1390 private final String mVolumeUuid;
1392 public VolumeFilter(String volumeUuid) {
1393 mVolumeUuid = volumeUuid;
1397 public void init() {
1401 public boolean filterApp(AppEntry info) {
1402 return Objects.equals(info.info.volumeUuid, mVolumeUuid);
1406 public static class CompoundFilter implements AppFilter {
1407 private final AppFilter mFirstFilter;
1408 private final AppFilter mSecondFilter;
1410 public CompoundFilter(AppFilter first, AppFilter second) {
1411 mFirstFilter = first;
1412 mSecondFilter = second;
1416 public void init() {
1417 mFirstFilter.init();
1418 mSecondFilter.init();
1422 public boolean filterApp(AppEntry info) {
1423 return mFirstFilter.filterApp(info) && mSecondFilter.filterApp(info);