summaryrefslogtreecommitdiffstats
path: root/src/com/android/settings/applications/ManageApplications.java
diff options
context:
space:
mode:
Diffstat (limited to 'src/com/android/settings/applications/ManageApplications.java')
-rw-r--r--src/com/android/settings/applications/ManageApplications.java2076
1 files changed, 2076 insertions, 0 deletions
diff --git a/src/com/android/settings/applications/ManageApplications.java b/src/com/android/settings/applications/ManageApplications.java
new file mode 100644
index 0000000..52ea376
--- /dev/null
+++ b/src/com/android/settings/applications/ManageApplications.java
@@ -0,0 +1,2076 @@
+/*
+ * Copyright (C) 2006 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settings.applications;
+
+import com.android.settings.R;
+
+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;
+import android.view.MenuItem;
+import android.view.View;
+import android.view.ViewGroup;
+import android.view.Window;
+import android.widget.AdapterView;
+import android.widget.BaseAdapter;
+import android.widget.Filter;
+import android.widget.Filterable;
+import android.widget.ImageView;
+import android.widget.ListView;
+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;
+
+ // 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;
+
+ // sort order that can be changed through the menu can be sorted alphabetically
+ // or size(descending)
+ private static final int MENU_OPTIONS_BASE = 0;
+ // Filter options used for displayed list of applications
+ public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0;
+ public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1;
+ public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2;
+
+ public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4;
+ public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5;
+ // sort order
+ private int mSortOrder = SORT_ORDER_ALPHA;
+ // 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;
+
+ // 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;
+ private boolean mCreatedRunning;
+
+ private boolean mResumedRunning;
+ 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();
+ }
+
+ public void setAbort() {
+ abort = true;
+ }
+
+ public void run() {
+ long startTime;
+ if (DEBUG_SIZE || DEBUG_TIME) {
+ startTime = SystemClock.elapsedRealtime();
+ }
+ 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.
+ */
+ 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;
+ }
+ }
+
+ // 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);
+ 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);
+ }
+ }
+ 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;
+ }
+ }
+ 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);
+ }
+ }
+ 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);
+ }
+ }
+
+ 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;
+ }
+ }
+
+ 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 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");
+ } 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;
+ }
+ 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");
+ }
+ 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;
+ }
+
+ 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;
+ }
+
+ 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;
+ }
+ appSize = Formatter.formatFileSize(ManageApplications.this, size);
+ return appSize;
+ }
+
+ // View Holder used when displaying views
+ static class AppViewHolder {
+ TextView appName;
+ ImageView appIcon;
+ TextView appSize;
+ }
+
+ /*
+ * 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();
+ }
+ 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);
+ }
+ return true;
+ }
+ }
+ 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();
+ }
+ }
+
+ public AppInfoAdapter(Context c, List<ApplicationInfo> appList) {
+ mAppList = appList;
+ }
+
+ public int getCount() {
+ return mAppLocalList.size();
+ }
+
+ public Object getItem(int position) {
+ return mAppLocalList.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 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;
+ }
+
+ 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;
+
+ // When convertView is not null, we can reuse it directly, there is no need
+ // to reinflate it. We only inflate a new View when the convertView supplied
+ // by ListView is null.
+ if (convertView == null) {
+ convertView = mInflater.inflate(R.layout.manage_applications_item, null);
+
+ // Creates a ViewHolder and store references to the two children views
+ // we want to bind data to.
+ holder = new AppViewHolder();
+ holder.appName = (TextView) convertView.findViewById(R.id.app_name);
+ holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
+ holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
+ convertView.setTag(holder);
+ } else {
+ // Get the ViewHolder back to get fast access to the TextView
+ // and the ImageView.
+ holder = (AppViewHolder) convertView.getTag();
+ }
+
+ // 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);
+ }
+ if(mInfo.appIcon != null) {
+ holder.appIcon.setImageDrawable(mInfo.appIcon);
+ }
+ if (mInfo.appSize != null) {
+ holder.appSize.setText(mInfo.appSize);
+ }
+ } else {
+ Log.w(TAG, "No info for package:"+appInfo.packageName+" in property map");
+ }
+ 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();
+ }
+ }
+
+ public Filter getFilter() {
+ return mAppFilter;
+ }
+
+ 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);
+ }
+ }
+
+ static final String TAB_DOWNLOADED = "Downloaded";
+ static final String TAB_RUNNING = "Running";
+ static final String TAB_ALL = "All";
+ static final String TAB_SDCARD = "OnSdCard";
+ private View mRootView;
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ if(localLOGV) Log.i(TAG, "Activity created");
+ long sCreate;
+ if (DEBUG_TIME) {
+ sCreate = SystemClock.elapsedRealtime();
+ }
+ Intent intent = getIntent();
+ String action = intent.getAction();
+ String defaultTabTag = TAB_DOWNLOADED;
+ if (intent.getComponent().getClassName().equals(
+ "com.android.settings.RunningServices")) {
+ defaultTabTag = TAB_RUNNING;
+ }
+ if (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
+ mSortOrder = SORT_ORDER_SIZE;
+ mFilterApps = FILTER_APPS_ALL;
+ defaultTabTag = TAB_ALL;
+ mSizesFirst = true;
+ }
+
+ if (savedInstanceState != null) {
+ mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder);
+ 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);
+ lv.setItemsCanFocus(true);
+ lv.setOnItemClickListener(this);
+ lv.setTextFilterEnabled(true);
+ mListView = lv;
+ 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)
+ .setIndicator(getString(R.string.filter_apps_third_party),
+ getResources().getDrawable(R.drawable.ic_tab_download))
+ .setContent(this));
+ tabHost.addTab(tabHost.newTabSpec(TAB_ALL)
+ .setIndicator(getString(R.string.filter_apps_all),
+ getResources().getDrawable(R.drawable.ic_tab_all))
+ .setContent(this));
+ tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD)
+ .setIndicator(getString(R.string.filter_apps_onsdcard),
+ getResources().getDrawable(R.drawable.ic_tab_sdcard))
+ .setContent(this));
+ tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING)
+ .setIndicator(getString(R.string.filter_apps_running),
+ getResources().getDrawable(R.drawable.ic_tab_running))
+ .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;
+ }
+ }
+
+ @Override
+ protected void onSaveInstanceState(Bundle outState) {
+ super.onSaveInstanceState(outState);
+ outState.putInt("sortOrder", mSortOrder);
+ outState.putInt("filterApps", mFilterApps);
+ outState.putString("defautTabTag", getTabHost().getCurrentTabTag());
+ outState.putBoolean("sizesFirst", mSizesFirst);
+ }
+
+ @Override
+ public Object onRetainNonConfigurationInstance() {
+ return mRunningProcessesView.doRetainNonConfigurationInstance();
+ }
+
+ @Override
+ protected void onPause() {
+ super.onPause();
+ mActivityResumed = false;
+ if (mResumedRunning) {
+ mRunningProcessesView.doPause();
+ mResumedRunning = false;
+ }
+ }
+
+ @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;
+ }
+ }
+ }
+
+ // 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
+ Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
+ Uri.fromParts("package", mCurrentPkgName, null));
+ // start new activity to display extended information
+ startActivityForResult(intent, INSTALLED_APP_DETAILS);
+ }
+
+ @Override
+ public boolean onCreateOptionsMenu(Menu menu) {
+ menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
+ .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
+ menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
+ .setIcon(android.R.drawable.ic_menu_sort_by_size);
+ return true;
+ }
+
+ @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;
+ }
+
+ @Override
+ public boolean onOptionsItemSelected(MenuItem item) {
+ int menuId = item.getItemId();
+ if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) {
+ sendMessageToHandler(REORDER_LIST, menuId);
+ }
+ return true;
+ }
+
+ public void onItemClick(AdapterView<?> parent, View view, int position,
+ long id) {
+ ApplicationInfo info = (ApplicationInfo)mAppInfoAdapter.getItem(position);
+ mCurrentPkgName = info.packageName;
+ startApplicationDetailsActivity();
+ }
+
+ // Finish the activity if the user presses the back button to cancel the activity
+ public void onCancel(DialogInterface dialog) {
+ finish();
+ }
+
+ public View createTabContent(String tag) {
+ return mRootView;
+ }
+
+ public void onTabChanged(String tabId) {
+ int newOption;
+ if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) {
+ newOption = FILTER_APPS_THIRD_PARTY;
+ } else if (TAB_ALL.equalsIgnoreCase(tabId)) {
+ newOption = FILTER_APPS_ALL;
+ } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) {
+ newOption = FILTER_APPS_SDCARD;
+ } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) {
+ selectView(VIEW_RUNNING);
+ return;
+ } else {
+ // Invalid option. Do nothing
+ return;
+ }
+
+ selectView(VIEW_LIST);
+ sendMessageToHandler(REORDER_LIST, newOption);
+ }
+}