diff options
author | Dianne Hackborn <hackbod@google.com> | 2010-07-27 14:41:34 -0700 |
---|---|---|
committer | Dianne Hackborn <hackbod@google.com> | 2010-07-27 15:23:51 -0700 |
commit | e7623f775cb37a8de049dae8c03b5b396526fdd1 (patch) | |
tree | c8c96fcddf6d9951ed5f27037194cbee6fe2e868 /src | |
parent | 938079c9b36f0a45e58b5b086393647d71aebeba (diff) | |
download | packages_apps_Settings-e7623f775cb37a8de049dae8c03b5b396526fdd1.zip packages_apps_Settings-e7623f775cb37a8de049dae8c03b5b396526fdd1.tar.gz packages_apps_Settings-e7623f775cb37a8de049dae8c03b5b396526fdd1.tar.bz2 |
Rewrite Manage Applications to be faster and simpler.
Change-Id: I4cf405e83a369cb935bccf375798557ffd93e14e
Diffstat (limited to 'src')
3 files changed, 796 insertions, 1717 deletions
diff --git a/src/com/android/settings/applications/ApplicationsState.java b/src/com/android/settings/applications/ApplicationsState.java new file mode 100644 index 0000000..d59aadd --- /dev/null +++ b/src/com/android/settings/applications/ApplicationsState.java @@ -0,0 +1,567 @@ +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.text.format.Formatter; +import android.util.Log; + +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; + + public static interface Callbacks { + public void onRunningStateChanged(boolean running); + public void onPackageListChanged(); + public void onPackageIconChanged(); + public void onPackageSizeChanged(String packageName); + public void onAllSizesComputed(); + } + + public static interface AppFilter { + 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 AppEntry { + final String label; + final long id; + long size; + + 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; + boolean sizeStale; + long sizeLoadStart; + + String normalizedLabel; + + AppEntry(Context context, ApplicationInfo info, long id) { + CharSequence label = info.loadLabel(context.getPackageManager()); + this.label = label != null ? label.toString() : info.packageName; + this.id = id; + this.info = info; + this.size = SIZE_UNKNOWN; + this.sizeStale = true; + } + } + + public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() { + private final Collator sCollator = Collator.getInstance(); + @Override + public int compare(AppEntry object1, AppEntry object2) { + 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 AppFilter THIRD_PARTY_FILTER = new AppFilter() { + @Override + public boolean filterApp(ApplicationInfo info) { + if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { + return true; + } + return false; + } + }; + + public static final AppFilter ON_SD_CARD_FILTER = new AppFilter() { + @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; + } + }; + + final Context mContext; + final PackageManager mPm; + PackageIntentReceiver mPackageIntentReceiver; + + boolean mResumed; + Callbacks mCurCallbacks; + + // Information about all applications. Synchronize on mAppEntries + // to protect access to these. + 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; + + /** + * 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); + } + @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(); + removePackage(pkgName); + addPackage(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; + } + for (String pkgName : pkgList) { + removePackage(pkgName); + addPackage(pkgName); + } + } + } + } + + class MainHandler extends Handler { + static final int MSG_PACKAGE_LIST_CHANGED = 1; + static final int MSG_PACKAGE_ICON_CHANGED = 2; + 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) { + switch (msg.what) { + case MSG_PACKAGE_LIST_CHANGED: { + if (mCurCallbacks != null) { + mCurCallbacks.onPackageListChanged(); + } + } break; + case MSG_PACKAGE_ICON_CHANGED: { + if (mCurCallbacks != null) { + mCurCallbacks.onPackageIconChanged(); + } + } break; + case MSG_PACKAGE_SIZE_CHANGED: { + if (mCurCallbacks != null) { + mCurCallbacks.onPackageSizeChanged((String)msg.obj); + } + } break; + case MSG_ALL_SIZES_COMPUTED: { + if (mCurCallbacks != null) { + mCurCallbacks.onAllSizesComputed(); + } + } break; + case MSG_RUNNING_STATE_CHANGED: { + if (mCurCallbacks != null) { + mCurCallbacks.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()); + } + + void resume(Callbacks callbacks) { + synchronized (mEntriesMap) { + mCurCallbacks = callbacks; + mResumed = true; + if (mPackageIntentReceiver == null) { + mPackageIntentReceiver = new PackageIntentReceiver(); + mPackageIntentReceiver.registerReceiver(); + } + mApplications = mPm.getInstalledApplications( + PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS); + if (mApplications == null) { + mApplications = new ArrayList<ApplicationInfo>(); + } + for (int i=0; i<mAppEntries.size(); i++) { + mAppEntries.get(i).sizeStale = true; + } + mCurComputingSizePkg = null; + if (!mBackgroundHandler.hasMessages(BackgroundHandler.MSG_LOAD_ENTRIES)) { + mBackgroundHandler.sendEmptyMessage(BackgroundHandler.MSG_LOAD_ENTRIES); + } + } + } + + void pause() { + synchronized (mEntriesMap) { + mCurCallbacks = null; + mResumed = false; + } + } + + // Creates a new list of app entries with the given filter and comparator. + ArrayList<AppEntry> rebuild(AppFilter filter, Comparator<AppEntry> comparator) { + ArrayList<AppEntry> filteredApps = new ArrayList<AppEntry>(); + synchronized (mEntriesMap) { + if (DEBUG) Log.i(TAG, "Rebuilding..."); + for (int i=0; i<mApplications.size(); i++) { + ApplicationInfo info = mApplications.get(i); + if (filter == null || filter.filterApp(info)) { + AppEntry entry = getEntryLocked(info); + if (DEBUG) Log.i(TAG, "Using " + info.packageName + ": " + entry); + filteredApps.add(entry); + } + } + } + Collections.sort(filteredApps, comparator); + return filteredApps; + } + + void ensureIcon(AppEntry entry) { + if (entry.icon != null) { + return; + } + synchronized (mEntriesMap) { + if (entry.icon == null) { + entry.icon = entry.info.loadIcon(mPm); + } + } + } + + void requestSize(String packageName) { + synchronized (mEntriesMap) { + AppEntry entry = mEntriesMap.get(packageName); + if (entry != null) { + mPm.getPackageSizeInfo(packageName, mBackgroundHandler.mStatsObserver); + } + } + } + + 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) 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. + return; + } + if (indexOfApplicationInfoLocked(pkgName) >= 0) { + if (DEBUG) Log.i(TAG, "Package already exists!"); + return; + } + ApplicationInfo info = mPm.getApplicationInfo(pkgName, + PackageManager.GET_UNINSTALLED_PACKAGES | + PackageManager.GET_DISABLED_COMPONENTS); + 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); + } + } + } catch (NameNotFoundException e) { + } + } + + void removePackage(String pkgName) { + synchronized (mEntriesMap) { + 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); + } + } + } + } + + 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 getTotalSize(PackageStats ps) { + if (ps != null) { + return ps.cacheSize+ps.codeSize+ps.dataSize; + } + 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_LOAD_ENTRIES = 1; + static final int MSG_LOAD_ICONS = 2; + static final int MSG_LOAD_SIZES = 3; + + boolean mRunning; + + final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() { + public void onGetStatsCompleted(PackageStats stats, boolean succeeded) { + boolean sizeChanged = false; + synchronized (mEntriesMap) { + AppEntry entry = mEntriesMap.get(stats.packageName); + if (entry != null) { + synchronized (entry) { + entry.sizeStale = false; + entry.sizeLoadStart = 0; + long newSize = getTotalSize(stats); + if (entry.size != newSize) { + entry.size = newSize; + entry.sizeStr = getSizeStr(entry.size); + 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); + } + } + } + }; + + BackgroundHandler(Looper looper) { + super(looper); + } + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case MSG_LOAD_ENTRIES: { + int numDone = 0; + synchronized (mEntriesMap) { + 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 (numDone >= 6) { + sendEmptyMessage(MSG_LOAD_ENTRIES); + } else { + sendEmptyMessage(MSG_LOAD_ICONS); + } + } break; + case MSG_LOAD_ICONS: { + int numDone = 0; + synchronized (mEntriesMap) { + for (int i=0; i<mAppEntries.size() && numDone<2; i++) { + AppEntry entry = mAppEntries.get(i); + if (entry.icon == null) { + if (!mRunning) { + mRunning = true; + Message m = mMainHandler.obtainMessage( + MainHandler.MSG_RUNNING_STATE_CHANGED, 1); + mMainHandler.sendMessage(m); + } + numDone++; + synchronized (entry) { + entry.icon = entry.info.loadIcon(mPm); + } + } + } + } + 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 (mCurComputingSizePkg != null) { + 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); + } + 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); + } + } + } break; + } + } + + } +} diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java index 03ae68a..c2d8774 100644 --- a/src/com/android/settings/applications/ManageApplications.java +++ b/src/com/android/settings/applications/ManageApplications.java @@ -17,32 +17,15 @@ package com.android.settings.applications; import com.android.settings.R; +import com.android.settings.applications.ApplicationsState.AppEntry; -import android.app.ActivityManager; -import android.app.Dialog; -import android.app.ProgressDialog; import android.app.TabActivity; -import android.content.BroadcastReceiver; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; -import android.content.IntentFilter; -import android.content.SharedPreferences; -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.content.res.Configuration; -import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; -import android.os.SystemClock; import android.provider.Settings; -import android.text.format.Formatter; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; @@ -50,6 +33,7 @@ import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.Window; +import android.widget.AbsListView; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.Filter; @@ -60,78 +44,24 @@ import android.widget.TabHost; import android.widget.TextView; import android.widget.AdapterView.OnItemClickListener; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileNotFoundException; -import java.io.FileOutputStream; -import java.io.IOException; -import java.text.Collator; import java.util.ArrayList; -import java.util.Collections; import java.util.Comparator; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Set; -import java.util.TreeMap; -import java.util.concurrent.CountDownLatch; /** * Activity to pick an application that will be used to display installation information and * options to uninstall/delete user data for system applications. This activity * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE * intent. - * - * Initially a compute in progress message is displayed while the application retrieves - * the list of application information from the PackageManager. The size information - * for each package is refreshed to the screen. The resource (app description and - * icon) information for each package is not available yet, so some default values for size - * icon and descriptions are used initially. Later the resource information for each - * application is retrieved and dynamically updated on the screen. - * - * A Broadcast receiver registers for package additions or deletions when the activity is - * in focus. If the user installs or deletes packages when the activity has focus, the receiver - * gets notified and proceeds to add/delete these packages from the list on the screen. - * This is an unlikely scenario but could happen. The entire list gets created every time - * the activity's onStart gets invoked. This is to avoid having the receiver for the entire - * life cycle of the application. - * - * The applications can be sorted either alphabetically or - * based on size (descending). If this activity gets launched under low memory - * situations (a low memory notification dispatches intent - * ACTION_MANAGE_PACKAGE_STORAGE) the list is sorted per size. - * - * If the user selects an application, extended info (like size, uninstall/clear data options, - * permissions info etc.,) is displayed via the InstalledAppDetails activity. */ public class ManageApplications extends TabActivity implements OnItemClickListener, DialogInterface.OnCancelListener, - TabHost.TabContentFactory, - TabHost.OnTabChangeListener { - // TAG for this activity - private static final String TAG = "ManageApplications"; - private static final String PREFS_NAME = "ManageAppsInfo.prefs"; - private static final String PREF_DISABLE_CACHE = "disableCache"; - - // Log information boolean - private boolean localLOGV = false; - private static final boolean DEBUG_SIZE = false; - private static final boolean DEBUG_TIME = false; + TabHost.TabContentFactory, TabHost.OnTabChangeListener { + static final String TAG = "ManageApplications"; + static final boolean DEBUG = false; // attributes used as keys when passing values to InstalledAppDetails activity public static final String APP_CHG = "chg"; - // attribute name used in receiver for tagging names of added/deleted packages - private static final String ATTR_PKG_NAME="p"; - private static final String ATTR_PKGS="ps"; - private static final String ATTR_STATS="ss"; - private static final String ATTR_SIZE_STRS="fs"; - - private static final String ATTR_GET_SIZE_STATUS="passed"; - private static final String ATTR_PKG_STATS="s"; - private static final String ATTR_PKG_SIZE_STR="f"; - // constant value that can be used to check return code from sub activity. private static final int INSTALLED_APP_DETAILS = 1; @@ -150,83 +80,22 @@ public class ManageApplications extends TabActivity implements // Filter value private int mFilterApps = FILTER_APPS_THIRD_PARTY; - // Custom Adapter used for managing items in the list - private AppInfoAdapter mAppInfoAdapter; - - // messages posted to the handler - private static final int HANDLER_MESSAGE_BASE = 0; - private static final int INIT_PKG_INFO = HANDLER_MESSAGE_BASE+1; - private static final int COMPUTE_BULK_SIZE = HANDLER_MESSAGE_BASE+2; - private static final int REMOVE_PKG = HANDLER_MESSAGE_BASE+3; - private static final int REORDER_LIST = HANDLER_MESSAGE_BASE+4; - private static final int ADD_PKG_START = HANDLER_MESSAGE_BASE+5; - private static final int ADD_PKG_DONE = HANDLER_MESSAGE_BASE+6; - private static final int REFRESH_LABELS = HANDLER_MESSAGE_BASE+7; - private static final int REFRESH_DONE = HANDLER_MESSAGE_BASE+8; - private static final int NEXT_LOAD_STEP = HANDLER_MESSAGE_BASE+9; - private static final int COMPUTE_END = HANDLER_MESSAGE_BASE+10; - private static final int REFRESH_ICONS = HANDLER_MESSAGE_BASE+11; - - // observer object used for computing pkg sizes - private PkgSizeObserver mObserver; - // local handle to PackageManager - private PackageManager mPm; - // Broadcast Receiver object that receives notifications for added/deleted - // packages - private PackageIntentReceiver mReceiver; - // atomic variable used to track if computing pkg sizes is in progress. should be volatile? - - private boolean mComputeSizesFinished = false; - // default icon thats used when displaying applications initially before resource info is - // retrieved - private static Drawable mDefaultAppIcon; - - // temporary dialog displayed while the application info loads - private static final int DLG_BASE = 0; - private static final int DLG_LOADING = DLG_BASE + 1; + private ApplicationsState mApplicationsState; + private ApplicationsAdapter mApplicationsAdapter; // Size resource used for packages whose size computation failed for some reason private CharSequence mInvalidSizeStr; private CharSequence mComputingSizeStr; - // map used to store list of added and removed packages. Immutable Boolean - // variables indicate if a package has been added or removed. If a package is - // added or deleted multiple times a single entry with the latest operation will - // be recorded in the map. - private Map<String, Boolean> mAddRemoveMap; - // layout inflater object used to inflate views private LayoutInflater mInflater; - // invalid size value used initially and also when size retrieval through PackageManager - // fails for whatever reason - private static final int SIZE_INVALID = -1; - - // debug boolean variable to test delays from PackageManager API's - private boolean DEBUG_PKG_DELAY = false; - - // Thread to load resources - ResourceLoaderThread mResourceThread; - private TaskRunner mSizeComputor; - private String mCurrentPkgName; - // Cache application attributes - private AppInfoCache mCache = new AppInfoCache(); - - // Boolean variables indicating state - private boolean mLoadLabelsFinished = false; - private boolean mSizesFirst = false; // ListView used to display list private ListView mListView; // Custom view used to display running processes private RunningProcessesView mRunningProcessesView; - // State variables used to figure out menu options and also - // initiate the first computation and loading of resources - private boolean mJustCreated = true; - private boolean mFirst = false; - private long mLoadTimeStart; - private boolean mSetListViewLater = true; // These are for keeping track of activity and tab switch state. private int mCurView; @@ -236,832 +105,205 @@ public class ManageApplications extends TabActivity implements private boolean mActivityResumed; private Object mNonConfigInstance; - /* - * Handler class to handle messages for various operations. - * Most of the operations that effect Application related data - * are posted as messages to the handler to avoid synchronization - * when accessing these structures. - * - * When the size retrieval gets kicked off for the first time, a COMPUTE_PKG_SIZE_START - * message is posted to the handler which invokes the getSizeInfo for the pkg at index 0. - * - * When the PackageManager's asynchronous call back through - * PkgSizeObserver.onGetStatsCompleted gets invoked, the application resources like - * label, description, icon etc., are loaded in the same thread and these values are - * set on the observer. The observer then posts a COMPUTE_PKG_SIZE_DONE message - * to the handler. This information is updated on the AppInfoAdapter associated with - * the list view of this activity and size info retrieval is initiated for the next package as - * indicated by mComputeIndex. - * - * When a package gets added while the activity has focus, the PkgSizeObserver posts - * ADD_PKG_START message to the handler. If the computation is not in progress, the size - * is retrieved for the newly added package through the observer object and the newly - * installed app info is updated on the screen. If the computation is still in progress - * the package is added to an internal structure and action deferred till the computation - * is done for all the packages. - * - * When a package gets deleted, REMOVE_PKG is posted to the handler - * if computation is not in progress (as indicated by - * mDoneIniting), the package is deleted from the displayed list of apps. If computation is - * still in progress the package is added to an internal structure and action deferred till - * the computation is done for all packages. - * - * When the sizes of all packages is computed, the newly - * added or removed packages are processed in order. - * If the user changes the order in which these applications are viewed by hitting the - * menu key, REORDER_LIST message is posted to the handler. this sorts the list - * of items based on the sort order. - */ - private Handler mHandler = new Handler() { - public void handleMessage(Message msg) { - boolean status; - long size; - String formattedSize; - Bundle data; - String pkgName = null; - data = msg.getData(); - if(data != null) { - pkgName = data.getString(ATTR_PKG_NAME); - } - switch (msg.what) { - case INIT_PKG_INFO: - if(localLOGV) Log.i(TAG, "Message INIT_PKG_INFO, justCreated = " + mJustCreated); - List<ApplicationInfo> newList = null; - if (!mJustCreated) { - if (localLOGV) Log.i(TAG, "List already created"); - // Add or delete newly created packages by comparing lists - newList = getInstalledApps(FILTER_APPS_ALL); - updateAppList(newList); - } - // Retrieve the package list and init some structures - initAppList(newList, mFilterApps); - mHandler.sendEmptyMessage(NEXT_LOAD_STEP); - break; - case COMPUTE_BULK_SIZE: - if(localLOGV) Log.i(TAG, "Message COMPUTE_BULK_PKG_SIZE"); - String[] pkgs = data.getStringArray(ATTR_PKGS); - long[] sizes = data.getLongArray(ATTR_STATS); - String[] formatted = data.getStringArray(ATTR_SIZE_STRS); - if(pkgs == null || sizes == null || formatted == null) { - Log.w(TAG, "Ignoring message"); - break; - } - mAppInfoAdapter.bulkUpdateSizes(pkgs, sizes, formatted); - break; - case COMPUTE_END: - mComputeSizesFinished = true; - mFirst = true; - mHandler.sendEmptyMessage(NEXT_LOAD_STEP); - break; - case REMOVE_PKG: - if(localLOGV) Log.i(TAG, "Message REMOVE_PKG"); - if(pkgName == null) { - Log.w(TAG, "Ignoring message:REMOVE_PKG for null pkgName"); - break; - } - if (!mComputeSizesFinished) { - Boolean currB = mAddRemoveMap.get(pkgName); - if (currB == null || (currB.equals(Boolean.TRUE))) { - mAddRemoveMap.put(pkgName, Boolean.FALSE); - } - break; - } - List<String> pkgList = new ArrayList<String>(); - pkgList.add(pkgName); - mAppInfoAdapter.removeFromList(pkgList); - break; - case REORDER_LIST: - if(localLOGV) Log.i(TAG, "Message REORDER_LIST"); - int menuOption = msg.arg1; - if((menuOption == SORT_ORDER_ALPHA) || - (menuOption == SORT_ORDER_SIZE)) { - // Option to sort list - if (menuOption != mSortOrder) { - mSortOrder = menuOption; - if (localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder); - mAppInfoAdapter.sortList(mSortOrder); - } - } else if(menuOption != mFilterApps) { - // Option to filter list - mFilterApps = menuOption; - boolean ret = mAppInfoAdapter.resetAppList(mFilterApps); - if(!ret) { - // Reset cache - mFilterApps = FILTER_APPS_ALL; - mHandler.sendEmptyMessage(INIT_PKG_INFO); - sendMessageToHandler(REORDER_LIST, menuOption); - } - } - break; - case ADD_PKG_START: - if(localLOGV) Log.i(TAG, "Message ADD_PKG_START"); - if(pkgName == null) { - Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName"); - break; - } - if (!mComputeSizesFinished || !mLoadLabelsFinished) { - Boolean currB = mAddRemoveMap.get(pkgName); - if (currB == null || (currB.equals(Boolean.FALSE))) { - mAddRemoveMap.put(pkgName, Boolean.TRUE); - } - break; - } - mObserver.invokeGetSizeInfo(pkgName); - break; - case ADD_PKG_DONE: - if(localLOGV) Log.i(TAG, "Message ADD_PKG_DONE"); - if(pkgName == null) { - Log.w(TAG, "Ignoring message:ADD_PKG_START for null pkgName"); - break; - } - status = data.getBoolean(ATTR_GET_SIZE_STATUS); - if (status) { - size = data.getLong(ATTR_PKG_STATS); - formattedSize = data.getString(ATTR_PKG_SIZE_STR); - if (!mAppInfoAdapter.isInstalled(pkgName)) { - mAppInfoAdapter.addToList(pkgName, size, formattedSize); - } else { - mAppInfoAdapter.updatePackage(pkgName, size, formattedSize); - } - } - break; - case REFRESH_LABELS: - Map<String, CharSequence> labelMap = (Map<String, CharSequence>) msg.obj; - if (labelMap != null) { - mAppInfoAdapter.bulkUpdateLabels(labelMap); - } - break; - case REFRESH_ICONS: - Map<String, Drawable> iconMap = (Map<String, Drawable>) msg.obj; - if (iconMap != null) { - mAppInfoAdapter.bulkUpdateIcons(iconMap); - } - break; - case REFRESH_DONE: - mLoadLabelsFinished = true; - mHandler.sendEmptyMessage(NEXT_LOAD_STEP); - break; - case NEXT_LOAD_STEP: - if (!mCache.isEmpty() && mSetListViewLater) { - if (localLOGV) Log.i(TAG, "Using cache to populate list view"); - initListView(); - mSetListViewLater = false; - mFirst = true; - } - if (mComputeSizesFinished && mLoadLabelsFinished) { - doneLoadingData(); - // Check for added/removed packages - Set<String> keys = mAddRemoveMap.keySet(); - for (String key : keys) { - if (mAddRemoveMap.get(key) == Boolean.TRUE) { - // Add the package - updatePackageList(Intent.ACTION_PACKAGE_ADDED, key); - } else { - // Remove the package - updatePackageList(Intent.ACTION_PACKAGE_REMOVED, key); - } - } - mAddRemoveMap.clear(); - } else if (!mComputeSizesFinished && !mLoadLabelsFinished) { - // Either load the package labels or initiate get size info - if (mSizesFirst) { - initComputeSizes(); - } else { - initResourceThread(); - } - } else { - if (mSetListViewLater) { - if (localLOGV) Log.i(TAG, "Initing list view for very first time"); - initListView(); - mSetListViewLater = false; - } - if (!mComputeSizesFinished) { - initComputeSizes(); - } else if (!mLoadLabelsFinished) { - initResourceThread(); - } - } - break; - default: - break; - } - } - }; - - private void initListView() { - // Create list view from the adapter here. Wait till the sort order - // of list is defined. its either by label or by size. So atleast one of the - // first steps should have been completed before the list gets filled. - mAppInfoAdapter.sortBaseList(mSortOrder); - if (mJustCreated) { - // Set the adapter here. - mJustCreated = false; - mListView.setAdapter(mAppInfoAdapter); - } - } - - class SizeObserver extends IPackageStatsObserver.Stub { - private CountDownLatch mCount; - PackageStats stats; - boolean succeeded; - - public void invokeGetSize(String packageName, CountDownLatch count) { - mCount = count; - mPm.getPackageSizeInfo(packageName, this); - } - - public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) { - succeeded = pSucceeded; - stats = pStats; - mCount.countDown(); - } - } - - class TaskRunner extends Thread { - private List<ApplicationInfo> mPkgList; - private SizeObserver mSizeObserver; - private static final int END_MSG = COMPUTE_END; - private static final int SEND_PKG_SIZES = COMPUTE_BULK_SIZE; - volatile boolean abort = false; - static final int MSG_PKG_SIZE = 8; - - TaskRunner(List<ApplicationInfo> appList) { - mPkgList = appList; - mSizeObserver = new SizeObserver(); - start(); - } + // View Holder used when displaying views + static class AppViewHolder { + ApplicationsState.AppEntry entry; + TextView appName; + ImageView appIcon; + TextView appSize; + TextView disabled; - public void setAbort() { - abort = true; - } - - public void run() { - long startTime; - if (DEBUG_SIZE || DEBUG_TIME) { - startTime = SystemClock.elapsedRealtime(); + void updateSizeText(ManageApplications ma) { + if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry + + ": " + entry.sizeStr); + if (entry.sizeStr != null) { + appSize.setText(entry.sizeStr); + } else if (entry.size == ApplicationsState.SIZE_INVALID) { + appSize.setText(ma.mInvalidSizeStr); } - int size = mPkgList.size(); - int numMsgs = size / MSG_PKG_SIZE; - if (size > (numMsgs * MSG_PKG_SIZE)) { - numMsgs++; - } - int endi = 0; - for (int j = 0; j < size; j += MSG_PKG_SIZE) { - long sizes[]; - String formatted[]; - String packages[]; - endi += MSG_PKG_SIZE; - if (endi > size) { - endi = size; - } - sizes = new long[endi-j]; - formatted = new String[endi-j]; - packages = new String[endi-j]; - for (int i = j; i < endi; i++) { - if (abort) { - // Exit if abort has been set. - break; - } - CountDownLatch count = new CountDownLatch(1); - String packageName = mPkgList.get(i).packageName; - mSizeObserver.invokeGetSize(packageName, count); - try { - count.await(); - } catch (InterruptedException e) { - Log.i(TAG, "Failed computing size for pkg : "+packageName); - } - // Process the package statistics - PackageStats pStats = mSizeObserver.stats; - boolean succeeded = mSizeObserver.succeeded; - long total; - if(succeeded && pStats != null) { - total = getTotalSize(pStats); - } else { - total = SIZE_INVALID; - } - sizes[i-j] = total; - formatted[i-j] = getSizeStr(total).toString(); - packages[i-j] = packageName; - } - // Post update message - Bundle data = new Bundle(); - data.putStringArray(ATTR_PKGS, packages); - data.putLongArray(ATTR_STATS, sizes); - data.putStringArray(ATTR_SIZE_STRS, formatted); - Message msg = mHandler.obtainMessage(SEND_PKG_SIZES, data); - msg.setData(data); - mHandler.sendMessage(msg); - } - if (DEBUG_SIZE || DEBUG_TIME) Log.i(TAG, "Took "+ - (SystemClock.elapsedRealtime() - startTime)+ - " ms to compute sizes of all packages "); - mHandler.sendEmptyMessage(END_MSG); } } /* - * This method compares the current cache against a new list of - * installed applications and tries to update the list with add or remove - * messages. + * Custom adapter implementation for the ListView + * This adapter maintains a map for each displayed application and its properties + * An index value on each AppInfo object indicates the correct position or index + * in the list. If the list gets updated dynamically when the user is viewing the list of + * applications, we need to return the correct index of position. This is done by mapping + * the getId methods via the package name into the internal maps and indices. + * The order of applications in the list is mirrored in mAppLocalList */ - private boolean updateAppList(List<ApplicationInfo> newList) { - if ((newList == null) || mCache.isEmpty()) { - return false; - } - Set<String> existingList = new HashSet<String>(); - boolean ret = false; - // Loop over new list and find out common elements between old and new lists - int N = newList.size(); - for (int i = (N-1); i >= 0; i--) { - ApplicationInfo info = newList.get(i); - String pkgName = info.packageName; - AppInfo aInfo = mCache.getEntry(pkgName); - if (aInfo != null) { - existingList.add(pkgName); - } else { - // New package. update info by refreshing - if (localLOGV) Log.i(TAG, "New pkg :"+pkgName+" installed when paused"); - updatePackageList(Intent.ACTION_PACKAGE_ADDED, pkgName); - // Remove from current list so that the newly added package can - // be handled later - newList.remove(i); - ret = true; + class ApplicationsAdapter extends BaseAdapter implements Filterable, + ApplicationsState.Callbacks, AbsListView.RecyclerListener { + private final ApplicationsState mState; + private final ArrayList<View> mActive = new ArrayList<View>(); + private ArrayList<ApplicationsState.AppEntry> mBaseEntries; + private ArrayList<ApplicationsState.AppEntry> mEntries; + private boolean mResumed; + private int mLastFilterMode=-1, mLastSortMode=-1; + CharSequence mCurFilterPrefix; + + private Filter mFilter = new Filter() { + @Override + protected FilterResults performFiltering(CharSequence constraint) { + ArrayList<ApplicationsState.AppEntry> entries + = applyPrefixFilter(constraint, mBaseEntries); + FilterResults fr = new FilterResults(); + fr.values = entries; + fr.count = entries.size(); + return fr; } - } - // Loop over old list and figure out stale entries - List<String> deletedList = null; - Set<String> staleList = mCache.getPkgList(); - for (String pkgName : staleList) { - if (!existingList.contains(pkgName)) { - if (localLOGV) Log.i(TAG, "Pkg :"+pkgName+" deleted when paused"); - if (deletedList == null) { - deletedList = new ArrayList<String>(); - deletedList.add(pkgName); - } - ret = true; - } - } - // Delete right away - if (deletedList != null) { - if (localLOGV) Log.i(TAG, "Deleting right away"); - mAppInfoAdapter.removeFromList(deletedList); - } - return ret; - } - - private void doneLoadingData() { - setProgressBarIndeterminateVisibility(false); - } - - List<ApplicationInfo> getInstalledApps(int filterOption) { - List<ApplicationInfo> installedAppList = mPm.getInstalledApplications( - PackageManager.GET_UNINSTALLED_PACKAGES | - PackageManager.GET_DISABLED_COMPONENTS); - if (installedAppList == null) { - return new ArrayList<ApplicationInfo> (); - } - if (filterOption == FILTER_APPS_SDCARD) { - List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> (); - for (ApplicationInfo appInfo : installedAppList) { - if ((appInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - // App on sdcard - appList.add(appInfo); - } - } - return appList; - } else if (filterOption == FILTER_APPS_THIRD_PARTY) { - List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> (); - for (ApplicationInfo appInfo : installedAppList) { - boolean flag = false; - if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - // Updated system app - flag = true; - } else if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - // Non-system app - flag = true; - } - if (flag) { - appList.add(appInfo); - } + @Override + protected void publishResults(CharSequence constraint, FilterResults results) { + mCurFilterPrefix = constraint; + mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values; + notifyDataSetChanged(); } - return appList; - } else { - return installedAppList; - } - } + }; - private static boolean matchFilter(boolean filter, Map<String, String> filterMap, String pkg) { - boolean add = true; - if (filter) { - if (filterMap == null || !filterMap.containsKey(pkg)) { - add = false; - } + public ApplicationsAdapter(ApplicationsState state) { + mState = state; } - return add; - } - - /* - * Utility method used to figure out list of apps based on filterOption - * If the framework supports an additional flag to indicate running apps - * we can get away with some code here. - */ - List<ApplicationInfo> getFilteredApps(List<ApplicationInfo> pAppList, int filterOption, boolean filter, - Map<String, String> filterMap) { - List<ApplicationInfo> retList = new ArrayList<ApplicationInfo>(); - if(pAppList == null) { - return retList; - } - if (filterOption == FILTER_APPS_SDCARD) { - for (ApplicationInfo appInfo : pAppList) { - boolean flag = false; - if ((appInfo.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - // App on sdcard - flag = true; - } - if (flag) { - if (matchFilter(filter, filterMap, appInfo.packageName)) { - retList.add(appInfo); - } - } - } - return retList; - } else if (filterOption == FILTER_APPS_THIRD_PARTY) { - for (ApplicationInfo appInfo : pAppList) { - boolean flag = false; - if ((appInfo.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - // Updated system app - flag = true; - } else if ((appInfo.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - // Non-system app - flag = true; - } - if (flag) { - if (matchFilter(filter, filterMap, appInfo.packageName)) { - retList.add(appInfo); - } - } - } - return retList; - } else { - for (ApplicationInfo appInfo : pAppList) { - if (matchFilter(filter, filterMap, appInfo.packageName)) { - retList.add(appInfo); - } + + public void resume(int filter, int sort) { + if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); + if (!mResumed) { + mResumed = true; + mState.resume(this); + mLastFilterMode = filter; + mLastSortMode = sort; + rebuild(); + } else { + rebuild(filter, sort); } - return retList; } - } - // Some initialization code used when kicking off the size computation - private void initAppList(List<ApplicationInfo> appList, int filterOption) { - setProgressBarIndeterminateVisibility(true); - mComputeSizesFinished = false; - mLoadLabelsFinished = false; - // Initialize lists - mAddRemoveMap = new TreeMap<String, Boolean>(); - mAppInfoAdapter.initMapFromList(appList, filterOption); - } - - // Utility method to start a thread to read application labels and icons - private void initResourceThread() { - if ((mResourceThread != null) && mResourceThread.isAlive()) { - mResourceThread.setAbort(); - } - mResourceThread = new ResourceLoaderThread(); - List<ApplicationInfo> appList = mAppInfoAdapter.getBaseAppList(); - if ((appList != null) && (appList.size()) > 0) { - mResourceThread.loadAllResources(appList); + public void pause() { + if (mResumed) { + mResumed = false; + mState.pause(); + } } - } - private void initComputeSizes() { - // Initiate compute package sizes - if (localLOGV) Log.i(TAG, "Initiating compute sizes for first time"); - if ((mSizeComputor != null) && (mSizeComputor.isAlive())) { - mSizeComputor.setAbort(); - } - List<ApplicationInfo> appList = mAppInfoAdapter.getBaseAppList(); - if ((appList != null) && (appList.size()) > 0) { - mSizeComputor = new TaskRunner(appList); - } else { - mComputeSizesFinished = true; - } - } - - // internal structure used to track added and deleted packages when - // the activity has focus - static class AddRemoveInfo { - String pkgName; - boolean add; - public AddRemoveInfo(String pPkgName, boolean pAdd) { - pkgName = pPkgName; - add = pAdd; + public void rebuild(int filter, int sort) { + if (filter == mLastFilterMode && sort == mLastSortMode) { + return; + } + mLastFilterMode = filter; + mLastSortMode = sort; + rebuild(); } - } - - class ResourceLoaderThread extends Thread { - List<ApplicationInfo> mAppList; - volatile boolean abort = false; - static final int MSG_PKG_SIZE = 8; - public void setAbort() { - abort = true; - } - void loadAllResources(List<ApplicationInfo> appList) { - mAppList = appList; - start(); + public void rebuild() { + if (DEBUG) Log.i(TAG, "Rebuilding app list..."); + ApplicationsState.AppFilter filterObj; + Comparator<AppEntry> comparatorObj; + switch (mLastFilterMode) { + case FILTER_APPS_THIRD_PARTY: + filterObj = ApplicationsState.THIRD_PARTY_FILTER; + break; + case FILTER_APPS_SDCARD: + filterObj = ApplicationsState.ON_SD_CARD_FILTER; + break; + default: + filterObj = null; + break; + } + switch (mLastSortMode) { + case SORT_ORDER_SIZE: + comparatorObj = ApplicationsState.SIZE_COMPARATOR; + break; + default: + comparatorObj = ApplicationsState.ALPHA_COMPARATOR; + break; + } + mBaseEntries = mState.rebuild(filterObj, comparatorObj); + mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); + notifyDataSetChanged(); } - public void run() { - long start; - if (DEBUG_TIME) { - start = SystemClock.elapsedRealtime(); - } - int imax; - if(mAppList == null || (imax = mAppList.size()) <= 0) { - Log.w(TAG, "Empty or null application list"); + ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, + ArrayList<ApplicationsState.AppEntry> origEntries) { + if (prefix == null || prefix.length() == 0) { + return origEntries; } else { - int size = mAppList.size(); - int numMsgs = size / MSG_PKG_SIZE; - if (size > (numMsgs * MSG_PKG_SIZE)) { - numMsgs++; - } - int endi = 0; - for (int j = 0; j < size; j += MSG_PKG_SIZE) { - Map<String, CharSequence> map = new HashMap<String, CharSequence>(); - endi += MSG_PKG_SIZE; - if (endi > size) { - endi = size; + String prefixStr = ApplicationsState.normalize(prefix.toString()); + final String spacePrefixStr = " " + prefixStr; + ArrayList<ApplicationsState.AppEntry> newEntries + = new ArrayList<ApplicationsState.AppEntry>(); + for (int i=0; i<origEntries.size(); i++) { + ApplicationsState.AppEntry entry = origEntries.get(i); + String nlabel = entry.getNormalizedLabel(); + if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) { + newEntries.add(entry); } - for (int i = j; i < endi; i++) { - if (abort) { - // Exit if abort has been set. - break; - } - ApplicationInfo appInfo = mAppList.get(i); - map.put(appInfo.packageName, appInfo.loadLabel(mPm)); - } - // Post update message - Message msg = mHandler.obtainMessage(REFRESH_LABELS); - msg.obj = map; - mHandler.sendMessage(msg); - } - Message doneMsg = mHandler.obtainMessage(REFRESH_DONE); - mHandler.sendMessage(doneMsg); - if (DEBUG_TIME) Log.i(TAG, "Took "+(SystemClock.elapsedRealtime()-start)+ - " ms to load app labels"); - long startIcons; - if (DEBUG_TIME) { - startIcons = SystemClock.elapsedRealtime(); } - Map<String, Drawable> map = new HashMap<String, Drawable>(); - for (int i = (imax-1); i >= 0; i--) { - if (abort) { - return; - } - ApplicationInfo appInfo = mAppList.get(i); - map.put(appInfo.packageName, appInfo.loadIcon(mPm)); - } - Message msg = mHandler.obtainMessage(REFRESH_ICONS); - msg.obj = map; - mHandler.sendMessage(msg); - if (DEBUG_TIME) Log.i(TAG, "Took "+(SystemClock.elapsedRealtime()-startIcons)+" ms to load app icons"); + return newEntries; } - if (DEBUG_TIME) Log.i(TAG, "Took "+(SystemClock.elapsedRealtime()-start)+" ms to load app resources"); } - } - - /* Internal class representing an application or packages displayable attributes - * - */ - static private class AppInfo { - public String pkgName; - int index; - public CharSequence appName; - public Drawable appIcon; - public CharSequence appSize; - long size; - public void refreshIcon(Drawable icon) { - if (icon == null) { - return; - } - appIcon = icon; - } - public void refreshLabel(CharSequence label) { - if (label == null) { - return; - } - appName = label; + @Override + public void onRunningStateChanged(boolean running) { + setProgressBarIndeterminateVisibility(running); } - public AppInfo(String pName, int pIndex, CharSequence aName, - long pSize, - CharSequence pSizeStr) { - this(pName, pIndex, aName, mDefaultAppIcon, pSize, pSizeStr); - } - - public AppInfo(String pName, int pIndex, CharSequence aName, Drawable aIcon, - long pSize, - CharSequence pSizeStr) { - index = pIndex; - pkgName = pName; - appName = aName; - appIcon = aIcon; - size = pSize; - appSize = pSizeStr; + @Override + public void onPackageListChanged() { + rebuild(); } - - public boolean setSize(long newSize, String formattedSize) { - if (size != newSize) { - size = newSize; - appSize = formattedSize; - return true; - } - return false; - } - } - - private long getTotalSize(PackageStats ps) { - if (ps != null) { - return ps.cacheSize+ps.codeSize+ps.dataSize; - } - return SIZE_INVALID; - } - private CharSequence getSizeStr(long size) { - CharSequence appSize = null; - if (size == SIZE_INVALID) { - return mInvalidSizeStr; + @Override + public void onPackageIconChanged() { + // We ensure icons are loaded when their item is displayed, so + // don't care about icons loaded in the background. } - appSize = Formatter.formatFileSize(ManageApplications.this, size); - return appSize; - } - // View Holder used when displaying views - static class AppViewHolder { - TextView appName; - ImageView appIcon; - TextView appSize; - TextView disabled; - } - - /* - * Custom adapter implementation for the ListView - * This adapter maintains a map for each displayed application and its properties - * An index value on each AppInfo object indicates the correct position or index - * in the list. If the list gets updated dynamically when the user is viewing the list of - * applications, we need to return the correct index of position. This is done by mapping - * the getId methods via the package name into the internal maps and indices. - * The order of applications in the list is mirrored in mAppLocalList - */ - class AppInfoAdapter extends BaseAdapter implements Filterable { - private List<ApplicationInfo> mAppList; - private List<ApplicationInfo> mAppLocalList; - private Map<String, String> mFilterMap = new HashMap<String, String>(); - AlphaComparator mAlphaComparator = new AlphaComparator(); - SizeComparator mSizeComparator = new SizeComparator(); - private Filter mAppFilter = new AppFilter(); - final private Object mFilterLock = new Object(); - private Map<String, String> mCurrentFilterMap = null; - - private void generateFilterListLocked(List<ApplicationInfo> list) { - mAppLocalList = new ArrayList<ApplicationInfo>(list); - synchronized(mFilterLock) { - for (ApplicationInfo info : mAppLocalList) { - String label = info.packageName; - AppInfo aInfo = mCache.getEntry(info.packageName); - if ((aInfo != null) && (aInfo.appName != null)) { - label = aInfo.appName.toString(); + @Override + public void onPackageSizeChanged(String packageName) { + for (int i=0; i<mActive.size(); i++) { + AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag(); + if (holder.entry.info.packageName.equals(packageName)) { + synchronized (holder.entry) { + holder.updateSizeText(ManageApplications.this); } - mFilterMap.put(info.packageName, label.toLowerCase()); - } - } - } - - private void addFilterListLocked(int newIdx, ApplicationInfo info, CharSequence pLabel) { - mAppLocalList.add(newIdx, info); - synchronized (mFilterLock) { - String label = info.packageName; - if (pLabel != null) { - label = pLabel.toString(); - } - mFilterMap.put(info.packageName, label.toLowerCase()); - } - } - - private boolean removeFilterListLocked(String removePkg) { - // Remove from filtered list - int N = mAppLocalList.size(); - int i; - for (i = (N-1); i >= 0; i--) { - ApplicationInfo info = mAppLocalList.get(i); - if (info.packageName.equalsIgnoreCase(removePkg)) { - if (localLOGV) Log.i(TAG, "Removing " + removePkg + " from local list"); - mAppLocalList.remove(i); - synchronized (mFilterLock) { - mFilterMap.remove(removePkg); + if (holder.entry.info.packageName.equals(mCurrentPkgName) + && mLastSortMode == SORT_ORDER_SIZE) { + // We got the size information for the last app the + // user viewed, and are sorting by size... they may + // have cleared data, so we immediately want to resort + // the list with the new size to reflect it to the user. + rebuild(); } - return true; + return; } } - return false; } - private void reverseGenerateList() { - generateFilterListLocked(getFilteredApps(mAppList, mFilterApps, mCurrentFilterMap!= null, mCurrentFilterMap)); - sortListInner(mSortOrder); - } - - // Make sure the cache or map contains entries for all elements - // in appList for a valid sort. - public void initMapFromList(List<ApplicationInfo> pAppList, int filterOption) { - boolean notify = false; - List<ApplicationInfo> appList = null; - if (pAppList == null) { - // Just refresh the list - appList = mAppList; - } else { - mAppList = new ArrayList<ApplicationInfo>(pAppList); - appList = pAppList; - notify = true; - } - generateFilterListLocked(getFilteredApps(appList, filterOption, mCurrentFilterMap!= null, mCurrentFilterMap)); - // This loop verifies and creates new entries for new packages in list - int imax = appList.size(); - for (int i = 0; i < imax; i++) { - ApplicationInfo info = appList.get(i); - AppInfo aInfo = mCache.getEntry(info.packageName); - if(aInfo == null){ - aInfo = new AppInfo(info.packageName, i, - info.packageName, -1, mComputingSizeStr); - if (localLOGV) Log.i(TAG, "Creating entry pkg:"+info.packageName+" to map"); - mCache.addEntry(aInfo); - } - } - sortListInner(mSortOrder); - if (notify) { - notifyDataSetChanged(); + @Override + public void onAllSizesComputed() { + if (mLastSortMode == SORT_ORDER_SIZE) { + rebuild(); } } - public AppInfoAdapter(Context c, List<ApplicationInfo> appList) { - mAppList = appList; - } - public int getCount() { - return mAppLocalList.size(); + return mEntries != null ? mEntries.size() : 0; } public Object getItem(int position) { - return mAppLocalList.get(position); + return mEntries.get(position); } - public boolean isInstalled(String pkgName) { - if(pkgName == null) { - if (localLOGV) Log.w(TAG, "Null pkg name when checking if installed"); - return false; - } - for (ApplicationInfo info : mAppList) { - if (info.packageName.equalsIgnoreCase(pkgName)) { - return true; - } - } - return false; - } - - public ApplicationInfo getApplicationInfo(int position) { - int imax = mAppLocalList.size(); - if( (position < 0) || (position >= imax)) { - Log.w(TAG, "Position out of bounds in List Adapter"); - return null; - } - return mAppLocalList.get(position); + public ApplicationsState.AppEntry getAppEntry(int position) { + return mEntries.get(position); } public long getItemId(int position) { - int imax = mAppLocalList.size(); - if( (position < 0) || (position >= imax)) { - Log.w(TAG, "Position out of bounds in List Adapter"); - return -1; - } - AppInfo aInfo = mCache.getEntry(mAppLocalList.get(position).packageName); - if (aInfo == null) { - return -1; - } - return aInfo.index; - } - - public List<ApplicationInfo> getBaseAppList() { - return mAppList; + return mEntries.get(position).id; } public View getView(int position, View convertView, ViewGroup parent) { - if (position >= mAppLocalList.size()) { - Log.w(TAG, "Invalid view position:"+position+", actual size is:"+mAppLocalList.size()); - return null; - } // A ViewHolder keeps references to children views to avoid unnecessary calls // to findViewById() on each row. AppViewHolder holder; @@ -1087,503 +329,35 @@ public class ManageApplications extends TabActivity implements } // Bind the data efficiently with the holder - ApplicationInfo appInfo = mAppLocalList.get(position); - AppInfo mInfo = mCache.getEntry(appInfo.packageName); - if(mInfo != null) { - if (mInfo.appName != null) { - holder.appName.setText(mInfo.appName); + ApplicationsState.AppEntry entry = mEntries.get(position); + synchronized (entry) { + holder.entry = entry; + if (entry.label != null) { + holder.appName.setText(entry.label); holder.appName.setTextColor(getResources().getColorStateList( - appInfo.enabled ? android.R.color.primary_text_dark + entry.info.enabled ? android.R.color.primary_text_dark : android.R.color.secondary_text_dark)); } - if (mInfo.appIcon != null) { - holder.appIcon.setImageDrawable(mInfo.appIcon); - } - if (mInfo.appSize != null) { - holder.appSize.setText(mInfo.appSize); + mState.ensureIcon(entry); + if (entry.icon != null) { + holder.appIcon.setImageDrawable(entry.icon); } - holder.disabled.setVisibility(appInfo.enabled ? View.GONE : View.VISIBLE); - } else { - Log.w(TAG, "No info for package:"+appInfo.packageName+" in property map"); + holder.updateSizeText(ManageApplications.this); + holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE); } + mActive.remove(convertView); + mActive.add(convertView); return convertView; } - - private void adjustIndex() { - int imax = mAppLocalList.size(); - for (int i = 0; i < imax; i++) { - ApplicationInfo info = mAppLocalList.get(i); - mCache.getEntry(info.packageName).index = i; - } - } - - public void sortAppList(List<ApplicationInfo> appList, int sortOrder) { - Collections.sort(appList, getAppComparator(sortOrder)); - } - - public void sortBaseList(int sortOrder) { - if (localLOGV) Log.i(TAG, "Sorting base list based on sortOrder = "+sortOrder); - sortAppList(mAppList, sortOrder); - generateFilterListLocked(getFilteredApps(mAppList, mFilterApps, mCurrentFilterMap!= null, mCurrentFilterMap)); - adjustIndex(); - } - - private void sortListInner(int sortOrder) { - sortAppList(mAppLocalList, sortOrder); - adjustIndex(); - } - - public void sortList(int sortOrder) { - if (localLOGV) Log.i(TAG, "sortOrder = "+sortOrder); - sortListInner(sortOrder); - notifyDataSetChanged(); - } - - /* - * Reset the application list associated with this adapter. - * @param filterOption Sort the list based on this value - * @param appList the actual application list that is used to reset - * @return Return a boolean value to indicate inconsistency - */ - public boolean resetAppList(int filterOption) { - // Change application list based on filter option - generateFilterListLocked(getFilteredApps(mAppList, filterOption, mCurrentFilterMap!= null, mCurrentFilterMap)); - // Check for all properties in map before sorting. Populate values from cache - for(ApplicationInfo applicationInfo : mAppLocalList) { - AppInfo appInfo = mCache.getEntry(applicationInfo.packageName); - if(appInfo == null) { - Log.i(TAG, " Entry does not exist for pkg: " + applicationInfo.packageName); - } - } - if (mAppLocalList.size() > 0) { - sortList(mSortOrder); - } else { - notifyDataSetChanged(); - } - return true; - } - - private Comparator<ApplicationInfo> getAppComparator(int sortOrder) { - if (sortOrder == SORT_ORDER_ALPHA) { - return mAlphaComparator; - } - return mSizeComparator; - } - - public void bulkUpdateIcons(Map<String, Drawable> icons) { - if (icons == null) { - return; - } - Set<String> keys = icons.keySet(); - boolean changed = false; - for (String key : keys) { - Drawable ic = icons.get(key); - if (ic != null) { - AppInfo aInfo = mCache.getEntry(key); - if (aInfo != null) { - aInfo.refreshIcon(ic); - changed = true; - } - } - } - if (changed) { - notifyDataSetChanged(); - } - } - - public void bulkUpdateLabels(Map<String, CharSequence> map) { - if (map == null) { - return; - } - Set<String> keys = map.keySet(); - boolean changed = false; - for (String key : keys) { - CharSequence label = map.get(key); - AppInfo aInfo = mCache.getEntry(key); - if (aInfo != null) { - aInfo.refreshLabel(label); - changed = true; - } - } - if (changed) { - notifyDataSetChanged(); - } - } - - private boolean shouldBeInList(int filterOption, ApplicationInfo info) { - // Match filter here - if (filterOption == FILTER_APPS_THIRD_PARTY) { - if ((info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { - return true; - } else if ((info.flags & ApplicationInfo.FLAG_UPDATED_SYSTEM_APP) != 0) { - return true; - } - } else if (filterOption == FILTER_APPS_SDCARD) { - if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { - return true; - } - } else { - return true; - } - return false; - } - - /* - * Add a package to the current list. - * The package is only added to the displayed list - * based on the filter value. The package is always added to the property map. - * @param pkgName name of package to be added - * @param ps PackageStats of new package - */ - public void addToList(String pkgName, long size, String formattedSize) { - if (pkgName == null) { - return; - } - // Get ApplicationInfo - ApplicationInfo info = null; - try { - info = mPm.getApplicationInfo(pkgName, 0); - } catch (NameNotFoundException e) { - Log.w(TAG, "Ignoring non-existent package:"+pkgName); - return; - } - if(info == null) { - // Nothing to do log error message and return - Log.i(TAG, "Null ApplicationInfo for package:"+pkgName); - return; - } - // Add entry to base list - mAppList.add(info); - // Add entry to map. Note that the index gets adjusted later on based on - // whether the newly added package is part of displayed list - CharSequence label = info.loadLabel(mPm); - mCache.addEntry(new AppInfo(pkgName, -1, - label, info.loadIcon(mPm), size, formattedSize)); - if (addLocalEntry(info, label)) { - notifyDataSetChanged(); - } - } - - private boolean addLocalEntry(ApplicationInfo info, CharSequence label) { - String pkgName = info.packageName; - // Add to list - if (shouldBeInList(mFilterApps, info)) { - // Binary search returns a negative index (ie -index) of the position where - // this might be inserted. - int newIdx = Collections.binarySearch(mAppLocalList, info, - getAppComparator(mSortOrder)); - if(newIdx >= 0) { - if (localLOGV) Log.i(TAG, "Strange. Package:" + pkgName + " is not new"); - return false; - } - // New entry - newIdx = -newIdx-1; - addFilterListLocked(newIdx, info, label); - // Adjust index - adjustIndex(); - return true; - } - return false; - } - - public void updatePackage(String pkgName, - long size, String formattedSize) { - ApplicationInfo info = null; - try { - info = mPm.getApplicationInfo(pkgName, - PackageManager.GET_UNINSTALLED_PACKAGES); - } catch (NameNotFoundException e) { - return; - } - AppInfo aInfo = mCache.getEntry(pkgName); - if (aInfo != null) { - CharSequence label = info.loadLabel(mPm); - aInfo.refreshLabel(label); - aInfo.refreshIcon(info.loadIcon(mPm)); - aInfo.setSize(size, formattedSize); - // Check if the entry has to be added to the displayed list - addLocalEntry(info, label); - // Refresh list since size might have changed - notifyDataSetChanged(); - } - } - - private void removePkgBase(String pkgName) { - int imax = mAppList.size(); - for (int i = 0; i < imax; i++) { - ApplicationInfo app = mAppList.get(i); - if (app.packageName.equalsIgnoreCase(pkgName)) { - if (localLOGV) Log.i(TAG, "Removing pkg: "+pkgName+" from base list"); - mAppList.remove(i); - return; - } - } - } - - public void removeFromList(List<String> pkgNames) { - if(pkgNames == null) { - return; - } - if(pkgNames.size() <= 0) { - return; - } - boolean found = false; - for (String pkg : pkgNames) { - // Remove from the base application list - removePkgBase(pkg); - // Remove from cache - if (localLOGV) Log.i(TAG, "Removing " + pkg + " from cache"); - mCache.removeEntry(pkg); - // Remove from filtered list - if (removeFilterListLocked(pkg)) { - found = true; - } - } - // Adjust indices of list entries - if (found) { - adjustIndex(); - if (localLOGV) Log.i(TAG, "adjusting index and notifying list view"); - notifyDataSetChanged(); - } - } - - public void bulkUpdateSizes(String pkgs[], long sizes[], String formatted[]) { - if(pkgs == null || sizes == null || formatted == null) { - return; - } - boolean changed = false; - for (int i = 0; i < pkgs.length; i++) { - AppInfo entry = mCache.getEntry(pkgs[i]); - if (entry == null) { - if (localLOGV) Log.w(TAG, "Entry for package:"+ pkgs[i] +"doesn't exist in map"); - continue; - } - if (entry.setSize(sizes[i], formatted[i])) { - changed = true; - } - } - if (changed) { - notifyDataSetChanged(); - } - } + @Override public Filter getFilter() { - return mAppFilter; + return mFilter; } - private class AppFilter extends Filter { - @Override - protected FilterResults performFiltering(CharSequence prefix) { - FilterResults results = new FilterResults(); - if (prefix == null || prefix.length() == 0) { - synchronized (mFilterLock) { - results.values = new HashMap<String, String>(mFilterMap); - results.count = mFilterMap.size(); - } - } else { - final String prefixString = prefix.toString().toLowerCase(); - final String spacePrefixString = " " + prefixString; - Map<String, String> newMap = new HashMap<String, String>(); - synchronized (mFilterLock) { - Map<String, String> localMap = mFilterMap; - Set<String> keys = mFilterMap.keySet(); - for (String key : keys) { - String label = localMap.get(key); - if (label == null) continue; - label = label.toLowerCase(); - if (label.startsWith(prefixString) - || label.indexOf(spacePrefixString) != -1) { - newMap.put(key, label); - } - } - } - results.values = newMap; - results.count = newMap.size(); - } - return results; - } - - @Override - protected void publishResults(CharSequence constraint, FilterResults results) { - mCurrentFilterMap = (Map<String, String>) results.values; - reverseGenerateList(); - if (results.count > 0) { - notifyDataSetChanged(); - } else { - notifyDataSetInvalidated(); - } - } - } - } - - /* - * Utility method to clear messages to Handler - * We need'nt synchronize on the Handler since posting messages is guaranteed - * to be thread safe. Even if the other thread that retrieves package sizes - * posts a message, we do a cursory check of validity on mAppInfoAdapter's applist - */ - private void clearMessagesInHandler() { - mHandler.removeMessages(INIT_PKG_INFO); - mHandler.removeMessages(COMPUTE_BULK_SIZE); - mHandler.removeMessages(REMOVE_PKG); - mHandler.removeMessages(REORDER_LIST); - mHandler.removeMessages(ADD_PKG_START); - mHandler.removeMessages(ADD_PKG_DONE); - mHandler.removeMessages(REFRESH_LABELS); - mHandler.removeMessages(REFRESH_DONE); - mHandler.removeMessages(NEXT_LOAD_STEP); - mHandler.removeMessages(COMPUTE_END); - } - - private void sendMessageToHandler(int msgId, int arg1) { - Message msg = mHandler.obtainMessage(msgId); - msg.arg1 = arg1; - mHandler.sendMessage(msg); - } - - private void sendMessageToHandler(int msgId, Bundle data) { - Message msg = mHandler.obtainMessage(msgId); - msg.setData(data); - mHandler.sendMessage(msg); - } - - private void sendMessageToHandler(int msgId) { - mHandler.sendEmptyMessage(msgId); - } - - /* - * Stats Observer class used to compute package sizes and retrieve size information - * PkgSizeOberver is the call back thats used when invoking getPackageSizeInfo on - * PackageManager. The values in call back onGetStatsCompleted are validated - * and the specified message is passed to mHandler. The package name - * and the AppInfo object corresponding to the package name are set on the message - */ - class PkgSizeObserver extends IPackageStatsObserver.Stub { - String pkgName; - public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) { - if(DEBUG_PKG_DELAY) { - try { - Thread.sleep(10*1000); - } catch (InterruptedException e) { - } - } - Bundle data = new Bundle(); - data.putString(ATTR_PKG_NAME, pkgName); - data.putBoolean(ATTR_GET_SIZE_STATUS, pSucceeded); - if(pSucceeded && pStats != null) { - if (localLOGV) Log.i(TAG, "onGetStatsCompleted::"+pkgName+", ("+ - pStats.cacheSize+","+ - pStats.codeSize+", "+pStats.dataSize); - long total = getTotalSize(pStats); - data.putLong(ATTR_PKG_STATS, total); - CharSequence sizeStr = getSizeStr(total); - data.putString(ATTR_PKG_SIZE_STR, sizeStr.toString()); - } else { - Log.w(TAG, "Invalid package stats from PackageManager"); - } - // Post message to Handler - Message msg = mHandler.obtainMessage(ADD_PKG_DONE, data); - msg.setData(data); - mHandler.sendMessage(msg); - } - - public void invokeGetSizeInfo(String packageName) { - if (packageName == null) { - return; - } - pkgName = packageName; - if(localLOGV) Log.i(TAG, "Invoking getPackageSizeInfo for package:"+ - packageName); - mPm.getPackageSizeInfo(packageName, this); - } - } - - /** - * 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"); - ManageApplications.this.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); - ManageApplications.this.registerReceiver(this, sdFilter); - } - @Override - public void onReceive(Context context, Intent intent) { - String actionStr = intent.getAction(); - if (Intent.ACTION_PACKAGE_ADDED.equals(actionStr) || - Intent.ACTION_PACKAGE_REMOVED.equals(actionStr)) { - Uri data = intent.getData(); - String pkgName = data.getEncodedSchemeSpecificPart(); - updatePackageList(actionStr, 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; - } - for (String pkgName : pkgList) { - updatePackageList(Intent.ACTION_PACKAGE_REMOVED, pkgName); - updatePackageList(Intent.ACTION_PACKAGE_ADDED, pkgName); - } - } - } - } - - private void updatePackageList(String actionStr, String pkgName) { - if (Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) { - Bundle data = new Bundle(); - data.putString(ATTR_PKG_NAME, pkgName); - sendMessageToHandler(ADD_PKG_START, data); - } else if (Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) { - Bundle data = new Bundle(); - data.putString(ATTR_PKG_NAME, pkgName); - sendMessageToHandler(REMOVE_PKG, data); - } - } - - static final int VIEW_NOTHING = 0; - static final int VIEW_LIST = 1; - static final int VIEW_RUNNING = 2; - - private void selectView(int which) { - if (mCurView == which) { - return; - } - - mCurView = which; - - if (which == VIEW_LIST) { - if (mResumedRunning) { - mRunningProcessesView.doPause(); - mResumedRunning = false; - } - mRunningProcessesView.setVisibility(View.GONE); - mListView.setVisibility(View.VISIBLE); - } else if (which == VIEW_RUNNING) { - if (!mCreatedRunning) { - mRunningProcessesView.doCreate(null, mNonConfigInstance); - mCreatedRunning = true; - } - if (mActivityResumed && !mResumedRunning) { - mRunningProcessesView.doResume(); - mResumedRunning = true; - } - mRunningProcessesView.setVisibility(View.VISIBLE); - mListView.setVisibility(View.GONE); + @Override + public void onMovedToScrapHeap(View view) { + mActive.remove(view); } } @@ -1596,11 +370,8 @@ public class ManageApplications extends TabActivity implements @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - if(localLOGV) Log.i(TAG, "Activity created"); - long sCreate; - if (DEBUG_TIME) { - sCreate = SystemClock.elapsedRealtime(); - } + mApplicationsState = ApplicationsState.getInstance(getApplication()); + mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState); Intent intent = getIntent(); String action = intent.getAction(); String defaultTabTag = TAB_DOWNLOADED; @@ -1612,7 +383,6 @@ public class ManageApplications extends TabActivity implements mSortOrder = SORT_ORDER_SIZE; mFilterApps = FILTER_APPS_ALL; defaultTabTag = TAB_ALL; - mSizesFirst = true; } if (savedInstanceState != null) { @@ -1620,27 +390,19 @@ public class ManageApplications extends TabActivity implements mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps); String tmp = savedInstanceState.getString("defaultTabTag"); if (tmp != null) defaultTabTag = tmp; - mSizesFirst = savedInstanceState.getBoolean("sizesFirst", mSizesFirst); } mNonConfigInstance = getLastNonConfigurationInstance(); - mPm = getPackageManager(); // initialize some window features requestWindowFeature(Window.FEATURE_RIGHT_ICON); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); - mDefaultAppIcon = Resources.getSystem().getDrawable( - com.android.internal.R.drawable.sym_def_app_icon); mInvalidSizeStr = getText(R.string.invalid_size_value); mComputingSizeStr = getText(R.string.computing_size); // initialize the inflater mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); mRootView = mInflater.inflate(R.layout.compute_sizes, null); - mReceiver = new PackageIntentReceiver(); - mObserver = new PkgSizeObserver(); // Create adapter and list view here - List<ApplicationInfo> appList = getInstalledApps(FILTER_APPS_ALL); - mAppInfoAdapter = new AppInfoAdapter(this, appList); ListView lv = (ListView) mRootView.findViewById(android.R.id.list); lv.setOnItemClickListener(this); lv.setSaveEnabled(true); @@ -1648,21 +410,10 @@ public class ManageApplications extends TabActivity implements lv.setOnItemClickListener(this); lv.setTextFilterEnabled(true); mListView = lv; + lv.setRecyclerListener(mApplicationsAdapter); + mListView.setAdapter(mApplicationsAdapter); mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( R.id.running_processes); - if (DEBUG_TIME) { - Log.i(TAG, "Total time in Activity.create:: " + - (SystemClock.elapsedRealtime() - sCreate)+ " ms"); - } - // Get initial info from file for the very first time this activity started - long sStart; - if (DEBUG_TIME) { - sStart = SystemClock.elapsedRealtime(); - } - mCache.loadCache(); - if (DEBUG_TIME) { - Log.i(TAG, "Took " + (SystemClock.elapsedRealtime()-sStart) + " ms to init cache"); - } final TabHost tabHost = getTabHost(); tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED) @@ -1683,26 +434,18 @@ public class ManageApplications extends TabActivity implements .setContent(this)); tabHost.setCurrentTabByTag(defaultTabTag); tabHost.setOnTabChangedListener(this); - - selectView(TAB_RUNNING.equals(defaultTabTag) ? VIEW_RUNNING : VIEW_LIST); } @Override public void onStart() { super.onStart(); - // Register receiver - mReceiver.registerReceiver(); - sendMessageToHandler(INIT_PKG_INFO); } @Override protected void onResume() { super.onResume(); mActivityResumed = true; - if (mCurView == VIEW_RUNNING) { - mRunningProcessesView.doResume(); - mResumedRunning = true; - } + showCurrentTab(); } @Override @@ -1711,7 +454,6 @@ public class ManageApplications extends TabActivity implements outState.putInt("sortOrder", mSortOrder); outState.putInt("filterApps", mFilterApps); outState.putString("defautTabTag", getTabHost().getCurrentTabTag()); - outState.putBoolean("sizesFirst", mSizesFirst); } @Override @@ -1723,6 +465,7 @@ public class ManageApplications extends TabActivity implements protected void onPause() { super.onPause(); mActivityResumed = false; + mApplicationsAdapter.pause(); if (mResumedRunning) { mRunningProcessesView.doPause(); mResumedRunning = false; @@ -1730,284 +473,13 @@ public class ManageApplications extends TabActivity implements } @Override - public void onStop() { - super.onStop(); - // Stop the background threads - if (mResourceThread != null) { - mResourceThread.setAbort(); - } - if (mSizeComputor != null) { - mSizeComputor.setAbort(); - } - // clear all messages related to application list - clearMessagesInHandler(); - // register receiver here - unregisterReceiver(mReceiver); - } - - @Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { - // Refresh package attributes - try { - ApplicationInfo info = mPm.getApplicationInfo(mCurrentPkgName, - PackageManager.GET_UNINSTALLED_PACKAGES); - } catch (NameNotFoundException e) { - Bundle rData = new Bundle(); - rData.putString(ATTR_PKG_NAME, mCurrentPkgName); - sendMessageToHandler(REMOVE_PKG, rData); - mCurrentPkgName = null; - } + mApplicationsState.requestSize(mCurrentPkgName); } } - // Avoid the restart and pause when orientation changes - @Override - public void onConfigurationChanged(Configuration newConfig) { - super.onConfigurationChanged(newConfig); - } - - @Override - protected void onDestroy() { - // Persist values in cache - mCache.updateCache(); - super.onDestroy(); - } - - class AppInfoCache { - final static boolean FILE_CACHE = true; - private static final String mFileCacheName="ManageAppsInfo.txt"; - private static final int FILE_BUFFER_SIZE = 1024; - private static final boolean DEBUG_CACHE = false; - private static final boolean DEBUG_CACHE_TIME = false; - private Map<String, AppInfo> mAppPropCache = new HashMap<String, AppInfo>(); - - private boolean isEmpty() { - return (mAppPropCache.size() == 0); - } - - private AppInfo getEntry(String pkgName) { - return mAppPropCache.get(pkgName); - } - - private Set<String> getPkgList() { - return mAppPropCache.keySet(); - } - - public void addEntry(AppInfo aInfo) { - if ((aInfo != null) && (aInfo.pkgName != null)) { - mAppPropCache.put(aInfo.pkgName, aInfo); - } - } - - public void removeEntry(String pkgName) { - if (pkgName != null) { - mAppPropCache.remove(pkgName); - } - } - - private void readFromFile() { - File cacheFile = new File(getFilesDir(), mFileCacheName); - if (!cacheFile.exists()) { - return; - } - FileInputStream fis = null; - boolean err = false; - try { - fis = new FileInputStream(cacheFile); - } catch (FileNotFoundException e) { - Log.w(TAG, "Error opening file for read operation : " + cacheFile - + " with exception " + e); - return; - } - try { - byte[] byteBuff = new byte[FILE_BUFFER_SIZE]; - byte[] lenBytes = new byte[2]; - mAppPropCache.clear(); - while(fis.available() > 0) { - fis.read(lenBytes, 0, 2); - int buffLen = (lenBytes[0] << 8) | lenBytes[1]; - if ((buffLen <= 0) || (buffLen > byteBuff.length)) { - err = true; - break; - } - // Buffer length cannot be greater than max. - fis.read(byteBuff, 0, buffLen); - String buffStr = new String(byteBuff); - if (DEBUG_CACHE) { - Log.i(TAG, "Read string of len= " + buffLen + " :: " + buffStr + " from file"); - } - // Parse string for sizes - String substrs[] = buffStr.split(","); - if (substrs.length < 4) { - // Something wrong. Bail out and let recomputation proceed. - err = true; - break; - } - long size = -1; - int idx = -1; - try { - size = Long.parseLong(substrs[1]); - } catch (NumberFormatException e) { - err = true; - break; - } - if (DEBUG_CACHE) { - Log.i(TAG, "Creating entry(" + substrs[0] + ", " + idx+"," + size + ", " + substrs[2] + ")"); - } - AppInfo aInfo = new AppInfo(substrs[0], idx, substrs[3], size, substrs[2]); - mAppPropCache.put(aInfo.pkgName, aInfo); - } - } catch (IOException e) { - Log.w(TAG, "Failed reading from file : " + cacheFile + " with exception : " + e); - err = true; - } finally { - if (fis != null) { - try { - fis.close(); - } catch (IOException e) { - Log.w(TAG, "Failed to close file " + cacheFile + " with exception : " +e); - err = true; - } - } - if (err) { - Log.i(TAG, "Failed to load cache. Not using cache for now."); - // Clear cache and bail out - mAppPropCache.clear(); - } - } - } - - boolean writeToFile() { - File cacheFile = new File(getFilesDir(), mFileCacheName); - FileOutputStream fos = null; - try { - long opStartTime = SystemClock.uptimeMillis(); - fos = new FileOutputStream(cacheFile); - Set<String> keys = mAppPropCache.keySet(); - byte[] lenBytes = new byte[2]; - for (String key : keys) { - AppInfo aInfo = mAppPropCache.get(key); - StringBuilder buff = new StringBuilder(aInfo.pkgName); - buff.append(","); - buff.append(aInfo.size); - buff.append(","); - buff.append(aInfo.appSize); - buff.append(","); - buff.append(aInfo.appName); - if (DEBUG_CACHE) { - Log.i(TAG, "Writing str : " + buff.toString() + " to file of length:" + - buff.toString().length()); - } - try { - byte[] byteBuff = buff.toString().getBytes(); - int len = byteBuff.length; - if (byteBuff.length >= FILE_BUFFER_SIZE) { - // Truncate the output - len = FILE_BUFFER_SIZE; - } - // Use 2 bytes to write length - lenBytes[1] = (byte) (len & 0x00ff); - lenBytes[0] = (byte) ((len & 0x00ff00) >> 8); - fos.write(lenBytes, 0, 2); - fos.write(byteBuff, 0, len); - } catch (IOException e) { - Log.w(TAG, "Failed to write to file : " + cacheFile + " with exception : " + e); - return false; - } - } - if (DEBUG_CACHE_TIME) { - Log.i(TAG, "Took " + (SystemClock.uptimeMillis() - opStartTime) + " ms to write and process from file"); - } - return true; - } catch (FileNotFoundException e) { - Log.w(TAG, "Error opening file for write operation : " + cacheFile+ - " with exception : " + e); - return false; - } finally { - if (fos != null) { - try { - fos.close(); - } catch (IOException e) { - Log.w(TAG, "Failed closing file : " + cacheFile + " with exception : " + e); - return false; - } - } - } - } - private void loadCache() { - // Restore preferences - SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); - boolean disable = settings.getBoolean(PREF_DISABLE_CACHE, true); - if (disable) Log.w(TAG, "Cache has been disabled"); - // Disable cache till the data is loaded successfully - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(PREF_DISABLE_CACHE, true); - editor.commit(); - if (FILE_CACHE && !disable) { - readFromFile(); - // Enable cache since the file has been read successfully - editor.putBoolean(PREF_DISABLE_CACHE, false); - editor.commit(); - } - } - - private void updateCache() { - SharedPreferences settings = getSharedPreferences(PREFS_NAME, 0); - SharedPreferences.Editor editor = settings.edit(); - editor.putBoolean(PREF_DISABLE_CACHE, true); - editor.commit(); - if (FILE_CACHE) { - boolean writeStatus = writeToFile(); - mAppPropCache.clear(); - if (writeStatus) { - // Enable cache since the file has been read successfully - editor.putBoolean(PREF_DISABLE_CACHE, false); - editor.commit(); - } - } - } - } - - /* - * comparator class used to sort AppInfo objects based on size - */ - class SizeComparator implements Comparator<ApplicationInfo> { - public final int compare(ApplicationInfo a, ApplicationInfo b) { - AppInfo ainfo = mCache.getEntry(a.packageName); - AppInfo binfo = mCache.getEntry(b.packageName); - long atotal = ainfo.size; - long btotal = binfo.size; - long ret = atotal - btotal; - // negate result to sort in descending order - if (ret < 0) { - return 1; - } - if (ret == 0) { - return 0; - } - return -1; - } - } - - /* - * Customized comparator class to compare labels. - * Don't use the one defined in ApplicationInfo since that loads the labels again. - */ - class AlphaComparator implements Comparator<ApplicationInfo> { - private final Collator sCollator = Collator.getInstance(); - - public final int compare(ApplicationInfo a, ApplicationInfo b) { - AppInfo ainfo = mCache.getEntry(a.packageName); - AppInfo binfo = mCache.getEntry(b.packageName); - // Check for null app names, to avoid NPE in rare cases - if (ainfo == null || ainfo.appName == null) return -1; - if (binfo == null || binfo.appName == null) return 1; - return sCollator.compare(ainfo.appName.toString(), binfo.appName.toString()); - } - } - // utility method used to start sub activity private void startApplicationDetailsActivity() { // Create intent to start new activity @@ -2028,27 +500,25 @@ public class ManageApplications extends TabActivity implements @Override public boolean onPrepareOptionsMenu(Menu menu) { - if (mFirst) { - menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); - menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); - return true; - } - return false; + menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); + menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); + return true; } @Override public boolean onOptionsItemSelected(MenuItem item) { int menuId = item.getItemId(); if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) { - sendMessageToHandler(REORDER_LIST, menuId); + mSortOrder = menuId; + mApplicationsAdapter.rebuild(mFilterApps, mSortOrder); } return true; } public void onItemClick(AdapterView<?> parent, View view, int position, long id) { - ApplicationInfo info = (ApplicationInfo)mAppInfoAdapter.getItem(position); - mCurrentPkgName = info.packageName; + ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position); + mCurrentPkgName = entry.info.packageName; startApplicationDetailsActivity(); } @@ -2061,7 +531,43 @@ public class ManageApplications extends TabActivity implements return mRootView; } - public void onTabChanged(String tabId) { + static final int VIEW_NOTHING = 0; + static final int VIEW_LIST = 1; + static final int VIEW_RUNNING = 2; + + private void selectView(int which) { + if (which == VIEW_LIST) { + if (mResumedRunning) { + mRunningProcessesView.doPause(); + mResumedRunning = false; + } + if (mCurView != which) { + mRunningProcessesView.setVisibility(View.GONE); + mListView.setVisibility(View.VISIBLE); + } + if (mActivityResumed) { + mApplicationsAdapter.resume(mFilterApps, mSortOrder); + } + } else if (which == VIEW_RUNNING) { + if (!mCreatedRunning) { + mRunningProcessesView.doCreate(null, mNonConfigInstance); + mCreatedRunning = true; + } + if (mActivityResumed && !mResumedRunning) { + mRunningProcessesView.doResume(); + mResumedRunning = true; + } + mApplicationsAdapter.pause(); + if (mCurView != which) { + mRunningProcessesView.setVisibility(View.VISIBLE); + mListView.setVisibility(View.GONE); + } + } + mCurView = which; + } + + public void showCurrentTab() { + String tabId = getTabHost().getCurrentTabTag(); int newOption; if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { newOption = FILTER_APPS_THIRD_PARTY; @@ -2077,7 +583,11 @@ public class ManageApplications extends TabActivity implements return; } + mFilterApps = newOption; selectView(VIEW_LIST); - sendMessageToHandler(REORDER_LIST, newOption); + } + + public void onTabChanged(String tabId) { + showCurrentTab(); } } diff --git a/src/com/android/settings/applications/RunningProcessesView.java b/src/com/android/settings/applications/RunningProcessesView.java index d63cc88..580ae05 100644 --- a/src/com/android/settings/applications/RunningProcessesView.java +++ b/src/com/android/settings/applications/RunningProcessesView.java @@ -45,6 +45,7 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; +import android.widget.AbsListView.RecyclerListener; import java.io.FileInputStream; import java.util.ArrayList; @@ -52,7 +53,7 @@ import java.util.HashMap; import java.util.Iterator; public class RunningProcessesView extends FrameLayout - implements AdapterView.OnItemClickListener { + implements AdapterView.OnItemClickListener, RecyclerListener { /** Maximum number of services to retrieve */ static final int MAX_SERVICES = 100; @@ -518,6 +519,7 @@ public class RunningProcessesView extends FrameLayout mListView.setEmptyView(emptyView); } mListView.setOnItemClickListener(this); + mListView.setRecyclerListener(this); mListView.setAdapter(new ServiceListAdapter(mState)); mColorBar = (LinearColorBar)findViewById(R.id.color_bar); mBackgroundProcessText = (TextView)findViewById(R.id.backgroundText); |