| package com.android.settings.applications; |
| |
| import android.app.Application; |
| import android.content.BroadcastReceiver; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.IntentFilter; |
| import android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageStatsObserver; |
| import android.content.pm.PackageManager; |
| import android.content.pm.PackageStats; |
| import android.content.pm.PackageManager.NameNotFoundException; |
| import android.graphics.drawable.Drawable; |
| import android.net.Uri; |
| import android.os.Handler; |
| import android.os.HandlerThread; |
| import android.os.Looper; |
| import android.os.Message; |
| import android.os.Process; |
| import android.os.SystemClock; |
| import android.os.UserHandle; |
| import android.text.format.Formatter; |
| import android.util.Log; |
| |
| import java.io.File; |
| import java.text.Collator; |
| import java.text.Normalizer; |
| import java.text.Normalizer.Form; |
| import java.util.ArrayList; |
| import java.util.Collections; |
| import java.util.Comparator; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.regex.Pattern; |
| |
| /** |
| * Keeps track of information about all installed applications, lazy-loading |
| * as needed. |
| */ |
| public class ApplicationsState { |
| static final String TAG = "ApplicationsState"; |
| static final boolean DEBUG = false; |
| static final boolean DEBUG_LOCKING = false; |
| |
| public static interface Callbacks { |
| public void onRunningStateChanged(boolean running); |
| public void onPackageListChanged(); |
| public void onRebuildComplete(ArrayList<AppEntry> apps); |
| public void onPackageIconChanged(); |
| public void onPackageSizeChanged(String packageName); |
| public void onAllSizesComputed(); |
| } |
| |
| public static interface AppFilter { |
| public void init(); |
| public boolean filterApp(ApplicationInfo info); |
| } |
| |
| static final int SIZE_UNKNOWN = -1; |
| static final int SIZE_INVALID = -2; |
| |
| static final Pattern REMOVE_DIACRITICALS_PATTERN |
| = Pattern.compile("\\p{InCombiningDiacriticalMarks}+"); |
| |
| public static String normalize(String str) { |
| String tmp = Normalizer.normalize(str, Form.NFD); |
| return REMOVE_DIACRITICALS_PATTERN.matcher(tmp) |
| .replaceAll("").toLowerCase(); |
| } |
| |
| public static class SizeInfo { |
| long cacheSize; |
| long codeSize; |
| long dataSize; |
| long externalCodeSize; |
| long externalDataSize; |
| |
| // This is the part of externalDataSize that is in the cache |
| // section of external storage. Note that we don't just combine |
| // this with cacheSize because currently the platform can't |
| // automatically trim this data when needed, so it is something |
| // the user may need to manage. The externalDataSize also includes |
| // this value, since what this is here is really the part of |
| // externalDataSize that we can just consider to be "cache" files |
| // for purposes of cleaning them up in the app details UI. |
| long externalCacheSize; |
| } |
| |
| public static class AppEntry extends SizeInfo { |
| final File apkFile; |
| final long id; |
| String label; |
| long size; |
| long internalSize; |
| long externalSize; |
| |
| boolean mounted; |
| |
| String getNormalizedLabel() { |
| if (normalizedLabel != null) { |
| return normalizedLabel; |
| } |
| normalizedLabel = normalize(label); |
| return normalizedLabel; |
| } |
| |
| // Need to synchronize on 'this' for the following. |
| ApplicationInfo info; |
| Drawable icon; |
| String sizeStr; |
| String internalSizeStr; |
| String externalSizeStr; |
| boolean sizeStale; |
| long sizeLoadStart; |
| |
| String normalizedLabel; |
| |
| AppEntry(Context context, ApplicationInfo info, long id) { |
| apkFile = new File(info.sourceDir); |
| this.id = id; |
| this.info = info; |
| this.size = SIZE_UNKNOWN; |
| this.sizeStale = true; |
| ensureLabel(context); |
| } |
| |
| void ensureLabel(Context context) { |
| if (this.label == null || !this.mounted) { |
| if (!this.apkFile.exists()) { |
| this.mounted = false; |
| this.label = info.packageName; |
| } else { |
| this.mounted = true; |
| CharSequence label = info.loadLabel(context.getPackageManager()); |
| this.label = label != null ? label.toString() : info.packageName; |
| } |
| } |
| } |
| |
| boolean ensureIconLocked(Context context, PackageManager pm) { |
| if (this.icon == null) { |
| if (this.apkFile.exists()) { |
| this.icon = this.info.loadIcon(pm); |
| return true; |
| } else { |
| this.mounted = false; |
| this.icon = context.getResources().getDrawable( |
| com.android.internal.R.drawable.sym_app_on_sd_unavailable_icon); |
| } |
| } else if (!this.mounted) { |
| // If the app wasn't mounted but is now mounted, reload |
| // its icon. |
| if (this.apkFile.exists()) { |
| this.mounted = true; |
| this.icon = this.info.loadIcon(pm); |
| return true; |
| } |
| } |
| return false; |
| } |
| } |
| |
| public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { |
| private final Collator sCollator = Collator.getInstance(); |
| @Override |
| public int compare(AppEntry object1, AppEntry object2) { |
| final boolean normal1 = object1.info.enabled |
| && (object1.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0; |
| final boolean normal2 = object2.info.enabled |
| && (object2.info.flags&ApplicationInfo.FLAG_INSTALLED) != 0; |
| if (normal1 != normal2) { |
| return normal1 ? -1 : 1; |
| } |
| return sCollator.compare(object1.label, object2.label); |
| } |
| }; |
| |
| public static final Comparator<AppEntry> SIZE_COMPARATOR |
| = new Comparator<AppEntry>() { |
| private final Collator sCollator = Collator.getInstance(); |
| @Override |
| public int compare(AppEntry object1, AppEntry object2) { |
| if (object1.size < object2.size) return 1; |
| if (object1.size > object2.size) return -1; |
| return sCollator.compare(object1.label, object2.label); |
| } |
| }; |
| |
| public static final Comparator<AppEntry> INTERNAL_SIZE_COMPARATOR |
| = new Comparator<AppEntry>() { |
| private final Collator sCollator = Collator.getInstance(); |
| @Override |
| public int compare(AppEntry object1, AppEntry object2) { |
| if (object1.internalSize < object2.internalSize) return 1; |
| if (object1.internalSize > object2.internalSize) return -1; |
| return sCollator.compare(object1.label, object2.label); |
| } |
| }; |
| |
| public static final Comparator<AppEntry> EXTERNAL_SIZE_COMPARATOR |
| = new Comparator<AppEntry>() { |
| private final Collator sCollator = Collator.getInstance(); |
| @Override |
| public int compare(AppEntry object1, AppEntry object2) { |
| if (object1.externalSize < object2.externalSize) return 1; |
| if (object1.externalSize > object2.externalSize) return -1; |
| return sCollator.compare(object1.label, object2.label); |
| } |
| }; |
| |
| public static final AppFilter THIRD_PARTY_FILTER = new AppFilter() { |
| public void init() { |
| } |
| |
| @Override |
| public boolean filterApp(ApplicationInfo info) { |
| if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { |
| return true; |
| } else if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { |
| return true; |
| } |
| return false; |
| } |
| }; |
| |
| public static final AppFilter ON_SD_CARD_FILTER = new AppFilter() { |
| final CanBeOnSdCardChecker mCanBeOnSdCardChecker |
| = new CanBeOnSdCardChecker(); |
| |
| public void init() { |
| mCanBeOnSdCardChecker.init(); |
| } |
| |
| @Override |
| public boolean filterApp(ApplicationInfo info) { |
| return mCanBeOnSdCardChecker.check(info); |
| } |
| }; |
| |
| final Context mContext; |
| final PackageManager mPm; |
| final int mRetrieveFlags; |
| PackageIntentReceiver mPackageIntentReceiver; |
| |
| boolean mResumed; |
| |
| // Information about all applications. Synchronize on mEntriesMap |
| // to protect access to these. |
| final ArrayList<Session> mSessions = new ArrayList<Session>(); |
| final ArrayList<Session> mRebuildingSessions = new ArrayList<Session>(); |
| final InterestingConfigChanges mInterestingConfigChanges = new InterestingConfigChanges(); |
| final HashMap<String, AppEntry> mEntriesMap = new HashMap<String, AppEntry>(); |
| final ArrayList<AppEntry> mAppEntries = new ArrayList<AppEntry>(); |
| List<ApplicationInfo> mApplications = new ArrayList<ApplicationInfo>(); |
| long mCurId = 1; |
| String mCurComputingSizePkg; |
| boolean mSessionsChanged; |
| |
| // Temporary for dispatching session callbacks. Only touched by main thread. |
| final ArrayList<Session> mActiveSessions = new ArrayList<Session>(); |
| |
| /** |
| * Receives notifications when applications are added/removed. |
| */ |
| private class PackageIntentReceiver extends BroadcastReceiver { |
| void registerReceiver() { |
| IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED); |
| filter.addAction(Intent.ACTION_PACKAGE_REMOVED); |
| filter.addAction(Intent.ACTION_PACKAGE_CHANGED); |
| filter.addDataScheme("package"); |
| mContext.registerReceiver(this, filter); |
| // Register for events related to sdcard installation. |
| IntentFilter sdFilter = new IntentFilter(); |
| sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE); |
| sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE); |
| mContext.registerReceiver(this, sdFilter); |
| } |
| void unregisterReceiver() { |
| mContext.unregisterReceiver(this); |
| } |
| @Override |
| public void onReceive(Context context, Intent intent) { |
| String actionStr = intent.getAction(); |
| if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr)) { |
| Uri data = intent.getData(); |
| String pkgName = data.getEncodedSchemeSpecificPart(); |
| addPackage(pkgName); |
| } else if (Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { |
| Uri data = intent.getData(); |
| String pkgName = data.getEncodedSchemeSpecificPart(); |
| removePackage(pkgName); |
| } else if (Intent.ACTION_PACKAGE_CHANGED.equals(actionStr)) { |
| Uri data = intent.getData(); |
| String pkgName = data.getEncodedSchemeSpecificPart(); |
| invalidatePackage(pkgName); |
| } else if (Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr) || |
| Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE.equals(actionStr)) { |
| // When applications become available or unavailable (perhaps because |
| // the SD card was inserted or ejected) we need to refresh the |
| // AppInfo with new label, icon and size information as appropriate |
| // given the newfound (un)availability of the application. |
| // A simple way to do that is to treat the refresh as a package |
| // removal followed by a package addition. |
| String pkgList[] = intent.getStringArrayExtra(Intent.EXTRA_CHANGED_PACKAGE_LIST); |
| if (pkgList == null || pkgList.length == 0) { |
| // Ignore |
| return; |
| } |
| boolean avail = Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE.equals(actionStr); |
| if (avail) { |
| for (String pkgName : pkgList) { |
| invalidatePackage(pkgName); |
| } |
| } |
| } |
| } |
| } |
| |
| void rebuildActiveSessions() { |
| synchronized (mEntriesMap) { |
| if (!mSessionsChanged) { |
| return; |
| } |
| mActiveSessions.clear(); |
| for (int i=0; i<mSessions.size(); i++) { |
| Session s = mSessions.get(i); |
| if (s.mResumed) { |
| mActiveSessions.add(s); |
| } |
| } |
| } |
| } |
| |
| class MainHandler extends Handler { |
| static final int MSG_REBUILD_COMPLETE = 1; |
| static final int MSG_PACKAGE_LIST_CHANGED = 2; |
| static final int MSG_PACKAGE_ICON_CHANGED = 3; |
| static final int MSG_PACKAGE_SIZE_CHANGED = 4; |
| static final int MSG_ALL_SIZES_COMPUTED = 5; |
| static final int MSG_RUNNING_STATE_CHANGED = 6; |
| |
| @Override |
| public void handleMessage(Message msg) { |
| rebuildActiveSessions(); |
| switch (msg.what) { |
| case MSG_REBUILD_COMPLETE: { |
| Session s = (Session)msg.obj; |
| if (mActiveSessions.contains(s)) { |
| s.mCallbacks.onRebuildComplete(s.mLastAppList); |
| } |
| } break; |
| case MSG_PACKAGE_LIST_CHANGED: { |
| for (int i=0; i<mActiveSessions.size(); i++) { |
| mActiveSessions.get(i).mCallbacks.onPackageListChanged(); |
| } |
| } break; |
| case MSG_PACKAGE_ICON_CHANGED: { |
| for (int i=0; i<mActiveSessions.size(); i++) { |
| mActiveSessions.get(i).mCallbacks.onPackageIconChanged(); |
| } |
| } break; |
| case MSG_PACKAGE_SIZE_CHANGED: { |
| for (int i=0; i<mActiveSessions.size(); i++) { |
| mActiveSessions.get(i).mCallbacks.onPackageSizeChanged( |
| (String)msg.obj); |
| } |
| } break; |
| case MSG_ALL_SIZES_COMPUTED: { |
| for (int i=0; i<mActiveSessions.size(); i++) { |
| mActiveSessions.get(i).mCallbacks.onAllSizesComputed(); |
| } |
| } break; |
| case MSG_RUNNING_STATE_CHANGED: { |
| for (int i=0; i<mActiveSessions.size(); i++) { |
| mActiveSessions.get(i).mCallbacks.onRunningStateChanged( |
| msg.arg1 != 0); |
| } |
| } break; |
| } |
| } |
| } |
| |
| final MainHandler mMainHandler = new MainHandler(); |
| |
| // -------------------------------------------------------------- |
| |
| static final Object sLock = new Object(); |
| static ApplicationsState sInstance; |
| |
| static ApplicationsState getInstance(Application app) { |
| synchronized (sLock) { |
| if (sInstance == null) { |
| sInstance = new ApplicationsState(app); |
| } |
| return sInstance; |
| } |
| } |
| |
| private ApplicationsState(Application app) { |
| mContext = app; |
| mPm = mContext.getPackageManager(); |
| mThread = new HandlerThread("ApplicationsState.Loader", |
| Process.THREAD_PRIORITY_BACKGROUND); |
| mThread.start(); |
| mBackgroundHandler = new BackgroundHandler(mThread.getLooper()); |
| |
| // Only the owner can see all apps. |
| if (UserHandle.myUserId() == 0) { |
| mRetrieveFlags = PackageManager.GET_UNINSTALLED_PACKAGES | |
| PackageManager.GET_DISABLED_COMPONENTS; |
| } else { |
| mRetrieveFlags = PackageManager.GET_DISABLED_COMPONENTS; |
| } |
| |
| /** |
| * This is a trick to prevent the foreground thread from being delayed. |
| * The problem is that Dalvik monitors are initially spin locks, to keep |
| * them lightweight. This leads to unfair contention -- Even though the |
| * background thread only holds the lock for a short amount of time, if |
| * it keeps running and locking again it can prevent the main thread from |
| * acquiring its lock for a long time... sometimes even > 5 seconds |
| * (leading to an ANR). |
| * |
| * Dalvik will promote a monitor to a "real" lock if it detects enough |
| * contention on it. It doesn't figure this out fast enough for us |
| * here, though, so this little trick will force it to turn into a real |
| * lock immediately. |
| */ |
| synchronized (mEntriesMap) { |
| try { |
| mEntriesMap.wait(1); |
| } catch (InterruptedException e) { |
| } |
| } |
| } |
| |
| public class Session { |
| final Callbacks mCallbacks; |
| boolean mResumed; |
| |
| // Rebuilding of app list. Synchronized on mRebuildSync. |
| final Object mRebuildSync = new Object(); |
| boolean mRebuildRequested; |
| boolean mRebuildAsync; |
| AppFilter mRebuildFilter; |
| Comparator<AppEntry> mRebuildComparator; |
| ArrayList<AppEntry> mRebuildResult; |
| ArrayList<AppEntry> mLastAppList; |
| |
| Session(Callbacks callbacks) { |
| mCallbacks = callbacks; |
| } |
| |
| public void resume() { |
| if (DEBUG_LOCKING) Log.v(TAG, "resume about to acquire lock..."); |
| synchronized (mEntriesMap) { |
| if (!mResumed) { |
| mResumed = true; |
| mSessionsChanged = true; |
| doResumeIfNeededLocked(); |
| } |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "...resume releasing lock"); |
| } |
| |
| public void pause() { |
| if (DEBUG_LOCKING) Log.v(TAG, "pause about to acquire lock..."); |
| synchronized (mEntriesMap) { |
| if (mResumed) { |
| mResumed = false; |
| mSessionsChanged = true; |
| mBackgroundHandler.removeMessages(BackgroundHandler.MSG_REBUILD_LIST, this); |
| doPauseIfNeededLocked(); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "...pause releasing lock"); |
| } |
| } |
| |
| // Creates a new list of app entries with the given filter and comparator. |
| ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { |
| synchronized (mRebuildSync) { |
| synchronized (mEntriesMap) { |
| mRebuildingSessions.add(this); |
| mRebuildRequested = true; |
| mRebuildAsync = false; |
| mRebuildFilter = filter; |
| mRebuildComparator = comparator; |
| mRebuildResult = null; |
| if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_REBUILD_LIST)) { |
| Message msg = mBackgroundHandler.obtainMessage( |
| BackgroundHandler.MSG_REBUILD_LIST); |
| mBackgroundHandler.sendMessage(msg); |
| } |
| } |
| |
| // We will wait for .25s for the list to be built. |
| long waitend = SystemClock.uptimeMillis()+250; |
| |
| while (mRebuildResult == null) { |
| long now = SystemClock.uptimeMillis(); |
| if (now >= waitend) { |
| break; |
| } |
| try { |
| mRebuildSync.wait(waitend - now); |
| } catch (InterruptedException e) { |
| } |
| } |
| |
| mRebuildAsync = true; |
| |
| return mRebuildResult; |
| } |
| } |
| |
| void handleRebuildList() { |
| AppFilter filter; |
| Comparator<AppEntry> comparator; |
| synchronized (mRebuildSync) { |
| if (!mRebuildRequested) { |
| return; |
| } |
| |
| filter = mRebuildFilter; |
| comparator = mRebuildComparator; |
| mRebuildRequested = false; |
| mRebuildFilter = null; |
| mRebuildComparator = null; |
| } |
| |
| Process.setThreadPriority(Process.THREAD_PRIORITY_FOREGROUND); |
| |
| if (filter != null) { |
| filter.init(); |
| } |
| |
| List<ApplicationInfo> apps; |
| synchronized (mEntriesMap) { |
| apps = new ArrayList<ApplicationInfo>(mApplications); |
| } |
| |
| ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); |
| if (DEBUG) Log.i(TAG, "Rebuilding..."); |
| for (int i=0; i<apps.size(); i++) { |
| ApplicationInfo info = apps.get(i); |
| if (filter == null || filter.filterApp(info)) { |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "rebuild acquired lock"); |
| AppEntry entry = getEntryLocked(info); |
| entry.ensureLabel(mContext); |
| if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry); |
| filteredApps.add(entry); |
| if (DEBUG_LOCKING) Log.v(TAG, "rebuild releasing lock"); |
| } |
| } |
| } |
| |
| Collections.sort(filteredApps, comparator); |
| |
| synchronized (mRebuildSync) { |
| if (!mRebuildRequested) { |
| mLastAppList = filteredApps; |
| if (!mRebuildAsync) { |
| mRebuildResult = filteredApps; |
| mRebuildSync.notifyAll(); |
| } else { |
| if (!mMainHandler.hasMessages(MainHandler.MSG_REBUILD_COMPLETE, this)) { |
| Message msg = mMainHandler.obtainMessage( |
| MainHandler.MSG_REBUILD_COMPLETE, this); |
| mMainHandler.sendMessage(msg); |
| } |
| } |
| } |
| } |
| |
| Process.setThreadPriority(Process.THREAD_PRIORITY_BACKGROUND); |
| } |
| |
| public void release() { |
| pause(); |
| synchronized (mEntriesMap) { |
| mSessions.remove(this); |
| } |
| } |
| } |
| |
| public Session newSession(Callbacks callbacks) { |
| Session s = new Session(callbacks); |
| synchronized (mEntriesMap) { |
| mSessions.add(s); |
| } |
| return s; |
| } |
| |
| void doResumeIfNeededLocked() { |
| if (mResumed) { |
| return; |
| } |
| mResumed = true; |
| if (mPackageIntentReceiver == null) { |
| mPackageIntentReceiver = new PackageIntentReceiver(); |
| mPackageIntentReceiver.registerReceiver(); |
| } |
| mApplications = mPm.getInstalledApplications(mRetrieveFlags); |
| if (mApplications == null) { |
| mApplications = new ArrayList<ApplicationInfo>(); |
| } |
| |
| if (mInterestingConfigChanges.applyNewConfig(mContext.getResources())) { |
| // If an interesting part of the configuration has changed, we |
| // should completely reload the app entries. |
| mEntriesMap.clear(); |
| mAppEntries.clear(); |
| } else { |
| for (int i=0; i<mAppEntries.size(); i++) { |
| mAppEntries.get(i).sizeStale = true; |
| } |
| } |
| |
| for (int i=0; i<mApplications.size(); i++) { |
| final ApplicationInfo info = mApplications.get(i); |
| // Need to trim out any applications that are disabled by |
| // something different than the user. |
| if (!info.enabled && info.enabledSetting |
| != PackageManager.COMPONENT_ENABLED_STATE_DISABLED_USER) { |
| mApplications.remove(i); |
| i--; |
| continue; |
| } |
| final AppEntry entry = mEntriesMap.get(info.packageName); |
| if (entry != null) { |
| entry.info = info; |
| } |
| } |
| mCurComputingSizePkg = null; |
| if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { |
| mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); |
| } |
| } |
| |
| void doPauseIfNeededLocked() { |
| if (!mResumed) { |
| return; |
| } |
| for (int i=0; i<mSessions.size(); i++) { |
| if (mSessions.get(i).mResumed) { |
| return; |
| } |
| } |
| mResumed = false; |
| if (mPackageIntentReceiver != null) { |
| mPackageIntentReceiver.unregisterReceiver(); |
| mPackageIntentReceiver = null; |
| } |
| } |
| |
| AppEntry getEntry(String packageName) { |
| if (DEBUG_LOCKING) Log.v(TAG, "getEntry about to acquire lock..."); |
| synchronized (mEntriesMap) { |
| AppEntry entry = mEntriesMap.get(packageName); |
| if (entry == null) { |
| for (int i=0; i<mApplications.size(); i++) { |
| ApplicationInfo info = mApplications.get(i); |
| if (packageName.equals(info.packageName)) { |
| entry = getEntryLocked(info); |
| break; |
| } |
| } |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "...getEntry releasing lock"); |
| return entry; |
| } |
| } |
| |
| void ensureIcon(AppEntry entry) { |
| if (entry.icon != null) { |
| return; |
| } |
| synchronized (entry) { |
| entry.ensureIconLocked(mContext, mPm); |
| } |
| } |
| |
| void requestSize(String packageName) { |
| if (DEBUG_LOCKING) Log.v(TAG, "requestSize about to acquire lock..."); |
| synchronized (mEntriesMap) { |
| AppEntry entry = mEntriesMap.get(packageName); |
| if (entry != null) { |
| mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "...requestSize releasing lock"); |
| } |
| } |
| |
| long sumCacheSizes() { |
| long sum = 0; |
| if (DEBUG_LOCKING) Log.v(TAG, "sumCacheSizes about to acquire lock..."); |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "-> sumCacheSizes now has lock"); |
| for (int i=mAppEntries.size()-1; i>=0; i--) { |
| sum += mAppEntries.get(i).cacheSize; |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "...sumCacheSizes releasing lock"); |
| } |
| return sum; |
| } |
| |
| int indexOfApplicationInfoLocked(String pkgName) { |
| for (int i=mApplications.size()-1; i>=0; i--) { |
| if (mApplications.get(i).packageName.equals(pkgName)) { |
| return i; |
| } |
| } |
| return -1; |
| } |
| |
| void addPackage(String pkgName) { |
| try { |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "addPackage acquired lock"); |
| if (DEBUG) Log.i(TAG, "Adding package " + pkgName); |
| if (!mResumed) { |
| // If we are not resumed, we will do a full query the |
| // next time we resume, so there is no reason to do work |
| // here. |
| if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: not resumed"); |
| return; |
| } |
| if (indexOfApplicationInfoLocked(pkgName) >= 0) { |
| if (DEBUG) Log.i(TAG, "Package already exists!"); |
| if (DEBUG_LOCKING) Log.v(TAG, "addPackage release lock: already exists"); |
| return; |
| } |
| ApplicationInfo info = mPm.getApplicationInfo(pkgName, mRetrieveFlags); |
| mApplications.add(info); |
| if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { |
| mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); |
| } |
| if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { |
| mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "addPackage releasing lock"); |
| } |
| } catch (NameNotFoundException e) { |
| } |
| } |
| |
| void removePackage(String pkgName) { |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "removePackage acquired lock"); |
| int idx = indexOfApplicationInfoLocked(pkgName); |
| if (DEBUG) Log.i(TAG, "removePackage: " + pkgName + " @ " + idx); |
| if (idx >= 0) { |
| AppEntry entry = mEntriesMap.get(pkgName); |
| if (DEBUG) Log.i(TAG, "removePackage: " + entry); |
| if (entry != null) { |
| mEntriesMap.remove(pkgName); |
| mAppEntries.remove(entry); |
| } |
| mApplications.remove(idx); |
| if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_LIST_CHANGED)) { |
| mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_LIST_CHANGED); |
| } |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "removePackage releasing lock"); |
| } |
| } |
| |
| void invalidatePackage(String pkgName) { |
| removePackage(pkgName); |
| addPackage(pkgName); |
| } |
| |
| AppEntry getEntryLocked(ApplicationInfo info) { |
| AppEntry entry = mEntriesMap.get(info.packageName); |
| if (DEBUG) Log.i(TAG, "Looking up entry of pkg " + info.packageName + ": " + entry); |
| if (entry == null) { |
| if (DEBUG) Log.i(TAG, "Creating AppEntry for " + info.packageName); |
| entry = new AppEntry(mContext, info, mCurId++); |
| mEntriesMap.put(info.packageName, entry); |
| mAppEntries.add(entry); |
| } else if (entry.info != info) { |
| entry.info = info; |
| } |
| return entry; |
| } |
| |
| // -------------------------------------------------------------- |
| |
| private long getTotalInternalSize(PackageStats ps) { |
| if (ps != null) { |
| return ps.codeSize + ps.dataSize; |
| } |
| return SIZE_INVALID; |
| } |
| |
| private long getTotalExternalSize(PackageStats ps) { |
| if (ps != null) { |
| // We also include the cache size here because for non-emulated |
| // we don't automtically clean cache files. |
| return ps.externalCodeSize + ps.externalDataSize |
| + ps.externalCacheSize |
| + ps.externalMediaSize + ps.externalObbSize; |
| } |
| return SIZE_INVALID; |
| } |
| |
| private String getSizeStr(long size) { |
| if (size >= 0) { |
| return Formatter.formatFileSize(mContext, size); |
| } |
| return null; |
| } |
| |
| final HandlerThread mThread; |
| final BackgroundHandler mBackgroundHandler; |
| class BackgroundHandler extends Handler { |
| static final int MSG_REBUILD_LIST = 1; |
| static final int MSG_LOAD_ENTRIES = 2; |
| static final int MSG_LOAD_ICONS = 3; |
| static final int MSG_LOAD_SIZES = 4; |
| |
| boolean mRunning; |
| |
| final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { |
| public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { |
| boolean sizeChanged = false; |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted acquired lock"); |
| AppEntry entry = mEntriesMap.get(stats.packageName); |
| if (entry != null) { |
| synchronized (entry) { |
| entry.sizeStale = false; |
| entry.sizeLoadStart = 0; |
| long externalCodeSize = stats.externalCodeSize |
| + stats.externalObbSize; |
| long externalDataSize = stats.externalDataSize |
| + stats.externalMediaSize; |
| long newSize = externalCodeSize + externalDataSize |
| + getTotalInternalSize(stats); |
| if (entry.size != newSize || |
| entry.cacheSize != stats.cacheSize || |
| entry.codeSize != stats.codeSize || |
| entry.dataSize != stats.dataSize || |
| entry.externalCodeSize != externalCodeSize || |
| entry.externalDataSize != externalDataSize || |
| entry.externalCacheSize != stats.externalCacheSize) { |
| entry.size = newSize; |
| entry.cacheSize = stats.cacheSize; |
| entry.codeSize = stats.codeSize; |
| entry.dataSize = stats.dataSize; |
| entry.externalCodeSize = externalCodeSize; |
| entry.externalDataSize = externalDataSize; |
| entry.externalCacheSize = stats.externalCacheSize; |
| entry.sizeStr = getSizeStr(entry.size); |
| entry.internalSize = getTotalInternalSize(stats); |
| entry.internalSizeStr = getSizeStr(entry.internalSize); |
| entry.externalSize = getTotalExternalSize(stats); |
| entry.externalSizeStr = getSizeStr(entry.externalSize); |
| if (DEBUG) Log.i(TAG, "Set size of " + entry.label + " " + entry |
| + ": " + entry.sizeStr); |
| sizeChanged = true; |
| } |
| } |
| if (sizeChanged) { |
| Message msg = mMainHandler.obtainMessage( |
| MainHandler.MSG_PACKAGE_SIZE_CHANGED, stats.packageName); |
| mMainHandler.sendMessage(msg); |
| } |
| } |
| if (mCurComputingSizePkg == null |
| || mCurComputingSizePkg.equals(stats.packageName)) { |
| mCurComputingSizePkg = null; |
| sendEmptyMessage(MSG_LOAD_SIZES); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "onGetStatsCompleted releasing lock"); |
| } |
| } |
| }; |
| |
| BackgroundHandler(Looper looper) { |
| super(looper); |
| } |
| |
| @Override |
| public void handleMessage(Message msg) { |
| // Always try rebuilding list first thing, if needed. |
| ArrayList<Session> rebuildingSessions = null; |
| synchronized (mEntriesMap) { |
| if (mRebuildingSessions.size() > 0) { |
| rebuildingSessions = new ArrayList<Session>(mRebuildingSessions); |
| mRebuildingSessions.clear(); |
| } |
| } |
| if (rebuildingSessions != null) { |
| for (int i=0; i<rebuildingSessions.size(); i++) { |
| rebuildingSessions.get(i).handleRebuildList(); |
| } |
| } |
| |
| switch (msg.what) { |
| case MSG_REBUILD_LIST: { |
| } break; |
| case MSG_LOAD_ENTRIES: { |
| int numDone = 0; |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES acquired lock"); |
| for (int i=0; i<mApplications.size() && numDone<6; i++) { |
| if (!mRunning) { |
| mRunning = true; |
| Message m = mMainHandler.obtainMessage( |
| MainHandler.MSG_RUNNING_STATE_CHANGED, 1); |
| mMainHandler.sendMessage(m); |
| } |
| ApplicationInfo info = mApplications.get(i); |
| if (mEntriesMap.get(info.packageName) == null) { |
| numDone++; |
| getEntryLocked(info); |
| } |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ENTRIES releasing lock"); |
| } |
| |
| if (numDone >= 6) { |
| sendEmptyMessage(MSG_LOAD_ENTRIES); |
| } else { |
| sendEmptyMessage(MSG_LOAD_ICONS); |
| } |
| } break; |
| case MSG_LOAD_ICONS: { |
| int numDone = 0; |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS acquired lock"); |
| for (int i=0; i<mAppEntries.size() && numDone<2; i++) { |
| AppEntry entry = mAppEntries.get(i); |
| if (entry.icon == null || !entry.mounted) { |
| synchronized (entry) { |
| if (entry.ensureIconLocked(mContext, mPm)) { |
| if (!mRunning) { |
| mRunning = true; |
| Message m = mMainHandler.obtainMessage( |
| MainHandler.MSG_RUNNING_STATE_CHANGED, 1); |
| mMainHandler.sendMessage(m); |
| } |
| numDone++; |
| } |
| } |
| } |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_ICONS releasing lock"); |
| } |
| if (numDone > 0) { |
| if (!mMainHandler.hasMessages(MainHandler.MSG_PACKAGE_ICON_CHANGED)) { |
| mMainHandler.sendEmptyMessage(MainHandler.MSG_PACKAGE_ICON_CHANGED); |
| } |
| } |
| if (numDone >= 2) { |
| sendEmptyMessage(MSG_LOAD_ICONS); |
| } else { |
| sendEmptyMessage(MSG_LOAD_SIZES); |
| } |
| } break; |
| case MSG_LOAD_SIZES: { |
| synchronized (mEntriesMap) { |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES acquired lock"); |
| if (mCurComputingSizePkg != null) { |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: currently computing"); |
| return; |
| } |
| |
| long now = SystemClock.uptimeMillis(); |
| for (int i=0; i<mAppEntries.size(); i++) { |
| AppEntry entry = mAppEntries.get(i); |
| if (entry.size == SIZE_UNKNOWN || entry.sizeStale) { |
| if (entry.sizeLoadStart == 0 || |
| (entry.sizeLoadStart < (now-20*1000))) { |
| if (!mRunning) { |
| mRunning = true; |
| Message m = mMainHandler.obtainMessage( |
| MainHandler.MSG_RUNNING_STATE_CHANGED, 1); |
| mMainHandler.sendMessage(m); |
| } |
| entry.sizeLoadStart = now; |
| mCurComputingSizePkg = entry.info.packageName; |
| mPm.getPackageSizeInfo(mCurComputingSizePkg, mStatsObserver); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing: now computing"); |
| return; |
| } |
| } |
| if (!mMainHandler.hasMessages(MainHandler.MSG_ALL_SIZES_COMPUTED)) { |
| mMainHandler.sendEmptyMessage(MainHandler.MSG_ALL_SIZES_COMPUTED); |
| mRunning = false; |
| Message m = mMainHandler.obtainMessage( |
| MainHandler.MSG_RUNNING_STATE_CHANGED, 0); |
| mMainHandler.sendMessage(m); |
| } |
| if (DEBUG_LOCKING) Log.v(TAG, "MSG_LOAD_SIZES releasing lock"); |
| } |
| } break; |
| } |
| } |
| |
| } |
| } |