Initial Contribution

This commit is contained in:
The Android Open Source Project
2008-10-21 07:00:00 -07:00
commit de2d9f5f10
215 changed files with 26261 additions and 0 deletions

View File

@@ -0,0 +1,616 @@
/*
* 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.Activity;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.ApplicationInfo;
import android.content.pm.IPackageStatsObserver;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.content.pm.PackageManager.NameNotFoundException;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.util.Config;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.TextView;
import android.widget.AdapterView.OnItemClickListener;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;
/**
* Activity to pick an application that will be used to display installation information and
* options to upgrade/uninstall/delete user data for system applications.
* Initially a compute in progress message is displayed while the application retrieves
* the size information of installed packages which is done asynchronously through a
* handler. Once the computation is done package resource information is retrieved
* and then the information is displayed on the screen. All
* messages are passed through a Handler object.
* Known issue: There could be some ordering issues when installing/uninstalling
* applications when the application list is being scanned.
*/
public class ManageApplications extends Activity implements SimpleAdapter.ViewBinder, OnItemClickListener {
private static final String TAG = "ManageApplications";
//Application prefix information
public static final String APP_PKG_PREFIX="com.android.settings.";
public static final String APP_PKG_NAME=APP_PKG_PREFIX+"ApplicationPkgName";
public static final String APP_PKG_SIZE= APP_PKG_PREFIX+"size";
public static final String APP_CHG=APP_PKG_PREFIX+"changed";
//constant value that can be used to check return code from sub activity.
private static final int INSTALLED_APP_DETAILS = 1;
//application attributes passed to sub activity that displays more app info
private static final String KEY_APP_NAME = "ApplicationName";
private static final String KEY_APP_ICON = "ApplicationIcon";
private static final String KEY_APP_DESC = "ApplicationDescription";
private static final String KEY_APP_SIZE= "ApplicationSize";
//sort order that can be changed through the menu
public static final int SORT_ORDER_ALPHA = 0;
public static final int SORT_ORDER_SIZE = 1;
//key and resource values used in constructing map for SimpleAdapter
private static final String sKeys[] = new String[] { KEY_APP_NAME, KEY_APP_ICON,
KEY_APP_DESC, KEY_APP_SIZE};
private static final int sResourceIds[] = new int[] { R.id.app_name, R.id.app_icon,
R.id.app_description, R.id.app_size};
//List of ApplicationInfo objects for various applications
private List<ApplicationInfo> mAppList;
//SimpleAdapter used for managing items in the list
private SimpleAdapter mAppAdapter;
//map used to store size information which is used for displaying size information
//in this activity as well as the subactivity. this is to avoid invoking package manager
//api to retrieve size information
private HashMap<String, PackageStats> mSizeMap;
private HashMap<String, Map<String, ?> > mAppAdapterMap;
//sort order
private int mSortOrder = SORT_ORDER_ALPHA;
//log information boolean
private boolean localLOGV = Config.LOGV || false;
private ApplicationInfo mCurrentPkg;
private int mCurrentPkgIdx = 0;
private static final int COMPUTE_PKG_SIZE_START = 1;
private static final int COMPUTE_PKG_SIZE_DONE = 2;
private static final int REMOVE_PKG=3;
private static final int REORDER_LIST=4;
private static final int ADD_PKG=5;
private static final String ATTR_APP_IDX="ApplicationIndex";
private static final String ATTR_CHAINED="Chained";
private static final String ATTR_PKG_NAME="PackageName";
private PkgSizeObserver mObserver;
private PackageManager mPm;
private PackageIntentReceiver mReceiver;
private boolean mDoneIniting = false;
private String mKbStr;
private String mMbStr;
private String mBStr;
/*
* Handler class to handle messages for various operations
*/
private Handler mHandler = new Handler() {
public void handleMessage(Message msg) {
PackageStats ps;
ApplicationInfo info;
Bundle data;
String pkgName;
int idx;
int size;
boolean chained = false;
data = msg.getData();
switch (msg.what) {
case COMPUTE_PKG_SIZE_START:
mDoneIniting = false;
//initialize lists
mAppList = new ArrayList<ApplicationInfo>();
mSizeMap = new HashMap<String, PackageStats>();
mAppAdapterMap = new HashMap<String, Map<String, ?> >();
//update application list from PackageManager
mAppList = mPm.getInstalledApplications(0);
if(mAppList.size() == 0) {
return;
}
mCurrentPkgIdx = 0;
mCurrentPkg = mAppList.get(0);
if(localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
//register receiver
mReceiver = new PackageIntentReceiver();
mReceiver.registerReceiver();
pkgName = mCurrentPkg.packageName;
mObserver = new PkgSizeObserver(0);
mObserver.invokeGetSizeInfo(pkgName, true);
break;
case COMPUTE_PKG_SIZE_DONE:
ps = mObserver.ps;
info = mObserver.appInfo;
chained = data.getBoolean(ATTR_CHAINED);
if(!mObserver.succeeded) {
if(chained) {
removePackageFromAppList(ps.packageName);
} else {
//do not go to adding phase
break;
}
} else {
//insert size value
mSizeMap.put(ps.packageName, ps);
Map<String, Object> entry = createMapEntry(mPm.getApplicationLabel(info),
mPm.getApplicationIcon(info),
info.loadDescription(mPm),
getSizeStr(ps));
mAppAdapterMap.put(ps.packageName, entry);
}
if(chained) {
//here app list is precomputed
idx = data.getInt(ATTR_APP_IDX);
//increment only if succeded
if(mObserver.succeeded) {
idx++;
}
if(idx < mAppList.size()) {
pkgName = mAppList.get(idx).packageName;
//increment record index and invoke getSizeInfo for next record
mObserver.invokeGetSizeInfo(pkgName, true);
} else {
sortAppList();
createListFromValues();
mDoneIniting = true;
}
} else {
//add app info object as well
mAppList.add(info);
sortAppList();
size = mAppList.size();
int i;
for(i = 0; i < size; i++) {
if(mAppList.get(i).packageName.equalsIgnoreCase(mCurrentPkg.packageName)) {
if(i > mCurrentPkgIdx) {
mCurrentPkgIdx = i;
}
break;
}
}
createListFromValues();
}
break;
case REMOVE_PKG:
if(!mDoneIniting) {
//insert message again after some delay
sendMessageToHandler(REMOVE_PKG, data, 10*1000);
break;
}
pkgName = data.getString(ATTR_PKG_NAME);
removePackageFromAppList(pkgName);
if(mSizeMap.remove(pkgName) == null) {
Log.i(TAG, "Coudnt remove from size map package:"+pkgName);
}
if(mAppAdapterMap.remove(pkgName) == null) {
Log.i(TAG, "Coudnt remove from app adapter map package:"+pkgName);
}
if(mCurrentPkg.packageName.equalsIgnoreCase(pkgName)) {
if(mCurrentPkgIdx == (mAppList.size()-1)) {
mCurrentPkgIdx--;
}
mCurrentPkg = mAppList.get(mCurrentPkgIdx);
}
createListFromValues();
break;
case REORDER_LIST:
int sortOrder = msg.arg1;
if(sortOrder != mSortOrder) {
mSortOrder = sortOrder;
if(localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
sortAppList();
mCurrentPkgIdx = 0;
mCurrentPkg = mAppList.get(mCurrentPkgIdx);
createListFromValues();
}
break;
case ADD_PKG:
pkgName = data.getString(ATTR_PKG_NAME);
if(!mDoneIniting) {
//insert message again after some delay
sendMessageToHandler(ADD_PKG, data, 10*1000);
break;
}
mObserver.invokeGetSizeInfo(pkgName, false);
break;
default:
break;
}
}
};
private void removePackageFromAppList(String pkgName) {
int size = mAppList.size();
for(int i = 0; i < size; i++) {
if(mAppList.get(i).packageName.equalsIgnoreCase(pkgName)) {
mAppList.remove(i);
break;
}
}
}
private void clearMessages() {
synchronized(mHandler) {
mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
mHandler.removeMessages(REMOVE_PKG);
mHandler.removeMessages(REORDER_LIST);
mHandler.removeMessages(ADD_PKG);
}
}
private void sendMessageToHandler(int msgId, Bundle data, long delayMillis) {
synchronized(mHandler) {
Message msg = mHandler.obtainMessage(msgId);
msg.setData(data);
if(delayMillis == 0) {
mHandler.sendMessage(msg);
} else {
mHandler.sendMessageDelayed(msg, delayMillis);
}
}
}
private void sendMessageToHandler(int msgId, int arg1) {
synchronized(mHandler) {
Message msg = mHandler.obtainMessage(msgId);
msg.arg1 = arg1;
mHandler.sendMessage(msg);
}
}
private void sendMessageToHandler(int msgId) {
synchronized(mHandler) {
mHandler.sendEmptyMessage(msgId);
}
}
class PkgSizeObserver extends IPackageStatsObserver.Stub {
public PackageStats ps;
public ApplicationInfo appInfo;
public Drawable appIcon;
public CharSequence appName;
public CharSequence appDesc = "";
private int mIdx = 0;
private boolean mChained = false;
public boolean succeeded;
PkgSizeObserver(int i) {
mIdx = i;
}
private void getAppDetails() {
try {
appInfo = mPm.getApplicationInfo(ps.packageName, 0);
} catch (NameNotFoundException e) {
return;
}
appName = appInfo.loadLabel(mPm);
appIcon = appInfo.loadIcon(mPm);
}
public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) {
Bundle data = new Bundle();
ps = pStats;
succeeded = pSucceeded;
if(mChained) {
data.putInt(ATTR_APP_IDX, mIdx);
if(succeeded) {
mIdx++;
}
}
data.putBoolean(ATTR_CHAINED, mChained);
getAppDetails();
if(localLOGV) Log.i(TAG, "onGetStatsCompleted::"+appInfo.packageName+", ("+ps.cacheSize+","+
ps.codeSize+", "+ps.dataSize);
sendMessageToHandler(COMPUTE_PKG_SIZE_DONE, data, 0);
}
public void invokeGetSizeInfo(String packageName, boolean chained) {
mChained = chained;
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);
}
@Override
public void onReceive(Context context, Intent intent) {
String actionStr = intent.getAction();
Uri data = intent.getData();
String pkgName = data.getEncodedSchemeSpecificPart();
if(localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
updatePackageList(actionStr, pkgName);
}
}
private void updatePackageList(String actionStr, String pkgName) {
//technically we dont have to invoke handler since onReceive is invoked on
//the main thread but doing it here for better clarity
if(Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
Bundle data = new Bundle();
data.putString(ATTR_PKG_NAME, pkgName);
sendMessageToHandler(ADD_PKG, data, 0);
} else if(Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
Bundle data = new Bundle();
data.putString(ATTR_PKG_NAME, pkgName);
sendMessageToHandler(REMOVE_PKG, data, 0);
} else if(Intent.ACTION_PACKAGE_CHANGED.equalsIgnoreCase(actionStr)) {
//force adapter to draw the list again. TODO derive from SimpleAdapter
//to avoid this
}
}
/*
* Utility method to create an array of map objects from a map of map objects
* for displaying list items to be used in SimpleAdapter.
*/
private void createListFromValues() {
findViewById(R.id.center_text).setVisibility(View.GONE);
populateAdapterList();
mAppAdapter.setViewBinder(this);
ListView lv= (ListView) findViewById(android.R.id.list);
lv.setOnItemClickListener(this);
lv.setAdapter(mAppAdapter);
if(mCurrentPkgIdx != -1) {
lv.setSelection(mCurrentPkgIdx);
}
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
String action = getIntent().getAction();
if(action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
mSortOrder = SORT_ORDER_SIZE;
}
mPm = getPackageManager();
//load strings from resources
mBStr = getString(R.string.b_text);
mKbStr = getString(R.string.kb_text);
mMbStr = getString(R.string.mb_text);
}
@Override
public void onStart() {
super.onStart();
setContentView(R.layout.compute_sizes);
//clear all messages related to application list
clearMessages();
sendMessageToHandler(COMPUTE_PKG_SIZE_START);
}
@Override
public void onStop() {
super.onStop();
//register receiver here
unregisterReceiver(mReceiver);
}
public static class AppInfoComparator implements Comparator<ApplicationInfo> {
public AppInfoComparator(HashMap<String, PackageStats> pSizeMap) {
mSizeMap= pSizeMap;
}
public final int compare(ApplicationInfo a, ApplicationInfo b) {
PackageStats aps, bps;
aps = mSizeMap.get(a.packageName);
bps = mSizeMap.get(b.packageName);
if (aps == null && bps == null) {
return 0;
} else if (aps == null) {
return 1;
} else if (bps == null) {
return -1;
}
long atotal = aps.dataSize+aps.codeSize+aps.cacheSize;
long btotal = bps.dataSize+bps.codeSize+bps.cacheSize;
long ret = atotal-btotal;
//negate result to sort in descending order
if(ret < 0) {
return 1;
}
if(ret == 0) {
return 0;
}
return -1;
}
private HashMap<String, PackageStats> mSizeMap;
}
/*
* Have to extract elements form map and populate a list ot be used by
* SimpleAdapter when displaying list elements. The sort order has to follow
* the order of elements in mAppList.
*/
private List<Map<String, ?>> createAdapterListFromMap() {
//get the index from mAppInfo which gives the correct sort position
int imax = mAppList.size();
if(localLOGV) Log.i(TAG, "Creating new adapter list");
List<Map<String, ?>> adapterList = new ArrayList<Map<String, ?>>();
ApplicationInfo tmpInfo;
for(int i = 0; i < imax; i++) {
tmpInfo = mAppList.get(i);
Map<String, Object>newObj = new TreeMap<String, Object>(
mAppAdapterMap.get(tmpInfo.packageName));
adapterList.add(newObj);
}
return adapterList;
}
private void populateAdapterList() {
mAppAdapter = new SimpleAdapter(this, createAdapterListFromMap(),
R.layout.manage_applications_item, sKeys, sResourceIds);
}
private String getSizeStr(PackageStats ps) {
String retStr = "";
//insert total size information into map to display in view
//at this point its guaranteed that ps is not null. but checking anyway
if(ps != null) {
long size = ps.cacheSize+ps.codeSize+ps.dataSize;
if(size < 1024) {
return String.valueOf(size)+mBStr;
}
long kb, mb, rem;
kb = size >> 10;
rem = size - (kb << 10);
if(kb < 1024) {
if(rem > 512) {
kb++;
}
retStr += String.valueOf(kb)+mKbStr;
return retStr;
}
mb = kb >> 10;
if(kb >= 512) {
//round off
mb++;
}
retStr += String.valueOf(mb)+ mMbStr;
return retStr;
} else {
Log.w(TAG, "Something fishy, cannot find size info for package:"+ps.packageName);
}
return retStr;
}
public void sortAppList() {
// Sort application list
if(mSortOrder == SORT_ORDER_ALPHA) {
Collections.sort(mAppList, new ApplicationInfo.DisplayNameComparator(mPm));
} else if(mSortOrder == SORT_ORDER_SIZE) {
Collections.sort(mAppList, new AppInfoComparator(mSizeMap));
}
}
private Map<String, Object> createMapEntry(CharSequence appName,
Drawable appIcon, CharSequence appDesc, String sizeStr) {
Map<String, Object> map = new TreeMap<String, Object>();
map.put(KEY_APP_NAME, appName);
//the icon cannot be null. if the application hasnt set it, the default icon is returned.
map.put(KEY_APP_ICON, appIcon);
if(appDesc == null) {
appDesc="";
}
map.put(KEY_APP_DESC, appDesc);
map.put(KEY_APP_SIZE, sizeStr);
return map;
}
private void startApplicationDetailsActivity(ApplicationInfo info, PackageStats ps) {
//Create intent to start new activity
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setClass(this, InstalledAppDetails.class);
intent.putExtra(APP_PKG_NAME, info.packageName);
if(localLOGV) Log.i(TAG, "code="+ps.codeSize+", cache="+ps.cacheSize+", data="+ps.dataSize);
intent.putExtra(APP_PKG_SIZE, ps);
if(localLOGV) Log.i(TAG, "Starting sub activity to display info for app:"+info
+" with intent:"+intent);
//start new activity to display extended information
if ((info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
}
startActivityForResult(intent, INSTALLED_APP_DETAILS);
}
public boolean setViewValue(View view, Object data, String textRepresentation) {
if(data == null) {
return false;
}
int id = view.getId();
switch(id) {
case R.id.app_name:
((TextView)view).setText((String)data);
break;
case R.id.app_icon:
((ImageView)view).setImageDrawable((Drawable)data);
break;
case R.id.app_description:
((TextView)view).setText((String)data);
break;
case R.id.app_size:
((TextView)view).setText((String)data);
break;
default:
break;
}
return true;
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add(0, SORT_ORDER_ALPHA, 0, R.string.sort_order_alpha)
.setIcon(android.R.drawable.ic_menu_sort_alphabetically);
menu.add(0, SORT_ORDER_SIZE, 0, R.string.sort_order_size)
.setIcon(android.R.drawable.ic_menu_sort_by_size);
return true;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
if(mDoneIniting) {
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();
sendMessageToHandler(REORDER_LIST, menuId);
return true;
}
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
mCurrentPkgIdx=position;
ApplicationInfo info = mAppList.get(position);
mCurrentPkg = info;
PackageStats ps = mSizeMap.get(info.packageName);
startApplicationDetailsActivity(info, ps);
}
}