| /* |
| * 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; |
| |
| 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.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_PKG_NAME = "pkg"; |
| 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_RUNNING = MENU_OPTIONS_BASE + 1; |
| public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 2; |
| public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 3; |
| |
| 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; |
| // 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; |
| |
| /* |
| * 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; |
| ApplicationInfo info; |
| Bundle data; |
| String pkgName = null; |
| AppInfo appInfo; |
| 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; |
| } |
| try { |
| info = mPm.getApplicationInfo(pkgName, 0); |
| } catch (NameNotFoundException e) { |
| Log.w(TAG, "Couldnt find application info for:"+pkgName); |
| 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); |
| dismissLoadingMsg(); |
| } |
| } |
| |
| 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 if (filterOption == FILTER_APPS_RUNNING) { |
| List<ApplicationInfo> appList =new ArrayList<ApplicationInfo> (); |
| List<ActivityManager.RunningAppProcessInfo> procList = getRunningAppProcessesList(); |
| if ((procList == null) || (procList.size() == 0)) { |
| return appList; |
| } |
| // Retrieve running processes from ActivityManager |
| for (ActivityManager.RunningAppProcessInfo appProcInfo : procList) { |
| if ((appProcInfo != null) && (appProcInfo.pkgList != null)){ |
| int size = appProcInfo.pkgList.length; |
| for (int i = 0; i < size; i++) { |
| ApplicationInfo appInfo = null; |
| try { |
| appInfo = mPm.getApplicationInfo(appProcInfo.pkgList[i], |
| PackageManager.GET_UNINSTALLED_PACKAGES); |
| } catch (NameNotFoundException e) { |
| Log.w(TAG, "Error retrieving ApplicationInfo for pkg:"+appProcInfo.pkgList[i]); |
| continue; |
| } |
| if(appInfo != null) { |
| 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 if (filterOption == FILTER_APPS_RUNNING) { |
| List<ActivityManager.RunningAppProcessInfo> procList = getRunningAppProcessesList(); |
| if ((procList == null) || (procList.size() == 0)) { |
| return retList; |
| } |
| // Retrieve running processes from ActivityManager |
| HashMap<String, ActivityManager.RunningAppProcessInfo> runningMap = |
| new HashMap<String, ActivityManager.RunningAppProcessInfo>(); |
| for (ActivityManager.RunningAppProcessInfo appProcInfo : procList) { |
| if ((appProcInfo != null) && (appProcInfo.pkgList != null)){ |
| int size = appProcInfo.pkgList.length; |
| for (int i = 0; i < size; i++) { |
| runningMap.put(appProcInfo.pkgList[i], appProcInfo); |
| } |
| } |
| } |
| // Query list to find running processes in current list |
| for (ApplicationInfo appInfo : pAppList) { |
| if (runningMap.get(appInfo.packageName) != null) { |
| 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; |
| } |
| } |
| |
| private List<ActivityManager.RunningAppProcessInfo> getRunningAppProcessesList() { |
| ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE); |
| return am.getRunningAppProcesses(); |
| } |
| |
| // 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_RUNNING) { |
| List<ApplicationInfo> runningList = getInstalledApps(FILTER_APPS_RUNNING); |
| for (ApplicationInfo running : runningList) { |
| if (running.packageName.equalsIgnoreCase(info.packageName)) { |
| return true; |
| } |
| } |
| } else 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 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 (action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) { |
| mSortOrder = SORT_ORDER_SIZE; |
| mFilterApps = FILTER_APPS_ALL; |
| defaultTabTag = TAB_ALL; |
| mSizesFirst = true; |
| } |
| mPm = getPackageManager(); |
| // initialize some window features |
| requestWindowFeature(Window.FEATURE_RIGHT_ICON); |
| requestWindowFeature(Window.FEATURE_PROGRESS); |
| requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); |
| showLoadingMsg(); |
| 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; |
| 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_RUNNING) |
| .setIndicator(getString(R.string.filter_apps_running), |
| getResources().getDrawable(R.drawable.ic_tab_running)) |
| .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.setCurrentTabByTag(defaultTabTag); |
| tabHost.setOnTabChangedListener(this); |
| } |
| |
| @Override |
| protected void onDestroy() { |
| // Persist values in cache |
| mCache.updateCache(); |
| super.onDestroy(); |
| } |
| |
| @Override |
| public Dialog onCreateDialog(int id, Bundle args) { |
| if (id == DLG_LOADING) { |
| ProgressDialog dlg = new ProgressDialog(this); |
| dlg.setProgressStyle(ProgressDialog.STYLE_SPINNER); |
| dlg.setMessage(getText(R.string.loading)); |
| dlg.setIndeterminate(true); |
| dlg.setOnCancelListener(this); |
| return dlg; |
| } |
| return null; |
| } |
| |
| private void showLoadingMsg() { |
| if (DEBUG_TIME) { |
| mLoadTimeStart = SystemClock.elapsedRealtime(); |
| } |
| showDialog(DLG_LOADING); |
| if(localLOGV) Log.i(TAG, "Displaying Loading message"); |
| } |
| |
| private void dismissLoadingMsg() { |
| if(localLOGV) Log.i(TAG, "Dismissing Loading message"); |
| dismissDialog(DLG_LOADING); |
| if (DEBUG_TIME) Log.i(TAG, "Displayed loading message for "+ |
| (SystemClock.elapsedRealtime() - mLoadTimeStart) + " ms"); |
| } |
| |
| 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(); |
| } |
| } |
| } |
| } |
| |
| @Override |
| public void onStart() { |
| super.onStart(); |
| // Register receiver |
| mReceiver.registerReceiver(); |
| sendMessageToHandler(INIT_PKG_INFO); |
| } |
| |
| @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); |
| } |
| |
| // Avoid the restart and pause when orientation changes |
| @Override |
| public void onConfigurationChanged(Configuration newConfig) { |
| super.onConfigurationChanged(newConfig); |
| } |
| |
| /* |
| * 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(Intent.ACTION_VIEW); |
| intent.setClass(this, InstalledAppDetails.class); |
| intent.putExtra(APP_PKG_NAME, mCurrentPkgName); |
| // 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_RUNNING.equalsIgnoreCase(tabId)) { |
| newOption = FILTER_APPS_RUNNING; |
| } else if (TAB_ALL.equalsIgnoreCase(tabId)) { |
| newOption = FILTER_APPS_ALL; |
| } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { |
| newOption = FILTER_APPS_SDCARD; |
| } else { |
| // Invalid option. Do nothing |
| return; |
| } |
| sendMessageToHandler(REORDER_LIST, newOption); |
| } |
| |
| @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; |
| } |
| } |
| } |
| } |