blob: 8389502518fefbc6d487f578490856cb530feaff [file] [log] [blame]
The Android Open Source Projectde2d9f52008-10-21 07:00:00 -07001/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings;
18
19import com.android.settings.R;
20import android.app.Activity;
21import android.content.BroadcastReceiver;
22import android.content.Context;
23import android.content.Intent;
24import android.content.IntentFilter;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.IPackageStatsObserver;
27import android.content.pm.PackageManager;
28import android.content.pm.PackageStats;
29import android.content.pm.PackageManager.NameNotFoundException;
30import android.graphics.drawable.Drawable;
31import android.net.Uri;
32import android.os.Bundle;
33import android.os.Handler;
34import android.os.Message;
35import android.util.Config;
36import android.util.Log;
37import android.view.Menu;
38import android.view.MenuItem;
39import android.view.View;
40import android.widget.AdapterView;
41import android.widget.ImageView;
42import android.widget.ListView;
43import android.widget.SimpleAdapter;
44import android.widget.TextView;
45import android.widget.AdapterView.OnItemClickListener;
46import java.util.ArrayList;
47import java.util.Collections;
48import java.util.Comparator;
49import java.util.HashMap;
50import java.util.List;
51import java.util.Map;
52import java.util.TreeMap;
53
54/**
55 * Activity to pick an application that will be used to display installation information and
56 * options to upgrade/uninstall/delete user data for system applications.
57 * Initially a compute in progress message is displayed while the application retrieves
58 * the size information of installed packages which is done asynchronously through a
59 * handler. Once the computation is done package resource information is retrieved
60 * and then the information is displayed on the screen. All
61 * messages are passed through a Handler object.
62 * Known issue: There could be some ordering issues when installing/uninstalling
63 * applications when the application list is being scanned.
64 */
65public class ManageApplications extends Activity implements SimpleAdapter.ViewBinder, OnItemClickListener {
66 private static final String TAG = "ManageApplications";
67 //Application prefix information
68 public static final String APP_PKG_PREFIX="com.android.settings.";
69 public static final String APP_PKG_NAME=APP_PKG_PREFIX+"ApplicationPkgName";
70 public static final String APP_PKG_SIZE= APP_PKG_PREFIX+"size";
71 public static final String APP_CHG=APP_PKG_PREFIX+"changed";
72
73 //constant value that can be used to check return code from sub activity.
74 private static final int INSTALLED_APP_DETAILS = 1;
75 //application attributes passed to sub activity that displays more app info
76 private static final String KEY_APP_NAME = "ApplicationName";
77 private static final String KEY_APP_ICON = "ApplicationIcon";
78 private static final String KEY_APP_DESC = "ApplicationDescription";
79 private static final String KEY_APP_SIZE= "ApplicationSize";
80 //sort order that can be changed through the menu
81 public static final int SORT_ORDER_ALPHA = 0;
82 public static final int SORT_ORDER_SIZE = 1;
83 //key and resource values used in constructing map for SimpleAdapter
84 private static final String sKeys[] = new String[] { KEY_APP_NAME, KEY_APP_ICON,
85 KEY_APP_DESC, KEY_APP_SIZE};
86 private static final int sResourceIds[] = new int[] { R.id.app_name, R.id.app_icon,
87 R.id.app_description, R.id.app_size};
88 //List of ApplicationInfo objects for various applications
89 private List<ApplicationInfo> mAppList;
90 //SimpleAdapter used for managing items in the list
91 private SimpleAdapter mAppAdapter;
92 //map used to store size information which is used for displaying size information
93 //in this activity as well as the subactivity. this is to avoid invoking package manager
94 //api to retrieve size information
95 private HashMap<String, PackageStats> mSizeMap;
96 private HashMap<String, Map<String, ?> > mAppAdapterMap;
97 //sort order
98 private int mSortOrder = SORT_ORDER_ALPHA;
99 //log information boolean
100 private boolean localLOGV = Config.LOGV || false;
101 private ApplicationInfo mCurrentPkg;
102 private int mCurrentPkgIdx = 0;
103 private static final int COMPUTE_PKG_SIZE_START = 1;
104 private static final int COMPUTE_PKG_SIZE_DONE = 2;
105 private static final int REMOVE_PKG=3;
106 private static final int REORDER_LIST=4;
107 private static final int ADD_PKG=5;
108 private static final String ATTR_APP_IDX="ApplicationIndex";
109 private static final String ATTR_CHAINED="Chained";
110 private static final String ATTR_PKG_NAME="PackageName";
111 private PkgSizeObserver mObserver;
112 private PackageManager mPm;
113 private PackageIntentReceiver mReceiver;
114 private boolean mDoneIniting = false;
115 private String mKbStr;
116 private String mMbStr;
117 private String mBStr;
118
119 /*
120 * Handler class to handle messages for various operations
121 */
122 private Handler mHandler = new Handler() {
123 public void handleMessage(Message msg) {
124 PackageStats ps;
125 ApplicationInfo info;
126 Bundle data;
127 String pkgName;
128 int idx;
129 int size;
130 boolean chained = false;
131 data = msg.getData();
132 switch (msg.what) {
133 case COMPUTE_PKG_SIZE_START:
134 mDoneIniting = false;
135 //initialize lists
136 mAppList = new ArrayList<ApplicationInfo>();
137 mSizeMap = new HashMap<String, PackageStats>();
138 mAppAdapterMap = new HashMap<String, Map<String, ?> >();
139 //update application list from PackageManager
140 mAppList = mPm.getInstalledApplications(0);
141 if(mAppList.size() == 0) {
142 return;
143 }
144 mCurrentPkgIdx = 0;
145 mCurrentPkg = mAppList.get(0);
146 if(localLOGV) Log.i(TAG, "Initiating compute sizes for first time");
147 //register receiver
148 mReceiver = new PackageIntentReceiver();
149 mReceiver.registerReceiver();
150 pkgName = mCurrentPkg.packageName;
151 mObserver = new PkgSizeObserver(0);
152 mObserver.invokeGetSizeInfo(pkgName, true);
153 break;
154 case COMPUTE_PKG_SIZE_DONE:
155 ps = mObserver.ps;
156 info = mObserver.appInfo;
157 chained = data.getBoolean(ATTR_CHAINED);
158 if(!mObserver.succeeded) {
159 if(chained) {
160 removePackageFromAppList(ps.packageName);
161 } else {
162 //do not go to adding phase
163 break;
164 }
165 } else {
166 //insert size value
167 mSizeMap.put(ps.packageName, ps);
168 Map<String, Object> entry = createMapEntry(mPm.getApplicationLabel(info),
169 mPm.getApplicationIcon(info),
170 info.loadDescription(mPm),
171 getSizeStr(ps));
172 mAppAdapterMap.put(ps.packageName, entry);
173 }
174 if(chained) {
175 //here app list is precomputed
176 idx = data.getInt(ATTR_APP_IDX);
177 //increment only if succeded
178 if(mObserver.succeeded) {
179 idx++;
180 }
181 if(idx < mAppList.size()) {
182 pkgName = mAppList.get(idx).packageName;
183 //increment record index and invoke getSizeInfo for next record
184 mObserver.invokeGetSizeInfo(pkgName, true);
185 } else {
186 sortAppList();
187 createListFromValues();
188 mDoneIniting = true;
189 }
190 } else {
191 //add app info object as well
192 mAppList.add(info);
193 sortAppList();
194 size = mAppList.size();
195 int i;
196 for(i = 0; i < size; i++) {
197 if(mAppList.get(i).packageName.equalsIgnoreCase(mCurrentPkg.packageName)) {
198 if(i > mCurrentPkgIdx) {
199 mCurrentPkgIdx = i;
200 }
201 break;
202 }
203 }
204 createListFromValues();
205 }
206 break;
207 case REMOVE_PKG:
208 if(!mDoneIniting) {
209 //insert message again after some delay
210 sendMessageToHandler(REMOVE_PKG, data, 10*1000);
211 break;
212 }
213 pkgName = data.getString(ATTR_PKG_NAME);
214 removePackageFromAppList(pkgName);
215 if(mSizeMap.remove(pkgName) == null) {
216 Log.i(TAG, "Coudnt remove from size map package:"+pkgName);
217 }
218 if(mAppAdapterMap.remove(pkgName) == null) {
219 Log.i(TAG, "Coudnt remove from app adapter map package:"+pkgName);
220 }
221 if(mCurrentPkg.packageName.equalsIgnoreCase(pkgName)) {
222 if(mCurrentPkgIdx == (mAppList.size()-1)) {
223 mCurrentPkgIdx--;
224 }
225 mCurrentPkg = mAppList.get(mCurrentPkgIdx);
226 }
227 createListFromValues();
228 break;
229 case REORDER_LIST:
230 int sortOrder = msg.arg1;
231 if(sortOrder != mSortOrder) {
232 mSortOrder = sortOrder;
233 if(localLOGV) Log.i(TAG, "Changing sort order to "+mSortOrder);
234 sortAppList();
235 mCurrentPkgIdx = 0;
236 mCurrentPkg = mAppList.get(mCurrentPkgIdx);
237 createListFromValues();
238 }
239 break;
240 case ADD_PKG:
241 pkgName = data.getString(ATTR_PKG_NAME);
242 if(!mDoneIniting) {
243 //insert message again after some delay
244 sendMessageToHandler(ADD_PKG, data, 10*1000);
245 break;
246 }
247 mObserver.invokeGetSizeInfo(pkgName, false);
248 break;
249 default:
250 break;
251 }
252 }
253 };
254
255 private void removePackageFromAppList(String pkgName) {
256 int size = mAppList.size();
257 for(int i = 0; i < size; i++) {
258 if(mAppList.get(i).packageName.equalsIgnoreCase(pkgName)) {
259 mAppList.remove(i);
260 break;
261 }
262 }
263 }
264
265 private void clearMessages() {
266 synchronized(mHandler) {
267 mHandler.removeMessages(COMPUTE_PKG_SIZE_START);
268 mHandler.removeMessages(COMPUTE_PKG_SIZE_DONE);
269 mHandler.removeMessages(REMOVE_PKG);
270 mHandler.removeMessages(REORDER_LIST);
271 mHandler.removeMessages(ADD_PKG);
272 }
273 }
274
275 private void sendMessageToHandler(int msgId, Bundle data, long delayMillis) {
276 synchronized(mHandler) {
277 Message msg = mHandler.obtainMessage(msgId);
278 msg.setData(data);
279 if(delayMillis == 0) {
280 mHandler.sendMessage(msg);
281 } else {
282 mHandler.sendMessageDelayed(msg, delayMillis);
283 }
284 }
285 }
286
287 private void sendMessageToHandler(int msgId, int arg1) {
288 synchronized(mHandler) {
289 Message msg = mHandler.obtainMessage(msgId);
290 msg.arg1 = arg1;
291 mHandler.sendMessage(msg);
292 }
293 }
294
295 private void sendMessageToHandler(int msgId) {
296 synchronized(mHandler) {
297 mHandler.sendEmptyMessage(msgId);
298 }
299 }
300
301 class PkgSizeObserver extends IPackageStatsObserver.Stub {
302 public PackageStats ps;
303 public ApplicationInfo appInfo;
304 public Drawable appIcon;
305 public CharSequence appName;
306 public CharSequence appDesc = "";
307 private int mIdx = 0;
308 private boolean mChained = false;
309 public boolean succeeded;
310 PkgSizeObserver(int i) {
311 mIdx = i;
312 }
313
314 private void getAppDetails() {
315 try {
316 appInfo = mPm.getApplicationInfo(ps.packageName, 0);
317 } catch (NameNotFoundException e) {
318 return;
319 }
320 appName = appInfo.loadLabel(mPm);
321 appIcon = appInfo.loadIcon(mPm);
322 }
323
324 public void onGetStatsCompleted(PackageStats pStats, boolean pSucceeded) {
325 Bundle data = new Bundle();
326 ps = pStats;
327 succeeded = pSucceeded;
328 if(mChained) {
329 data.putInt(ATTR_APP_IDX, mIdx);
330 if(succeeded) {
331 mIdx++;
332 }
333 }
334 data.putBoolean(ATTR_CHAINED, mChained);
335 getAppDetails();
336 if(localLOGV) Log.i(TAG, "onGetStatsCompleted::"+appInfo.packageName+", ("+ps.cacheSize+","+
337 ps.codeSize+", "+ps.dataSize);
338 sendMessageToHandler(COMPUTE_PKG_SIZE_DONE, data, 0);
339 }
340
341 public void invokeGetSizeInfo(String packageName, boolean chained) {
342 mChained = chained;
343 mPm.getPackageSizeInfo(packageName, this);
344 }
345 }
346
347 /**
348 * Receives notifications when applications are added/removed.
349 */
350 private class PackageIntentReceiver extends BroadcastReceiver {
351 void registerReceiver() {
352 IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
353 filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
354 filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
355 filter.addDataScheme("package");
356 ManageApplications.this.registerReceiver(this, filter);
357 }
358 @Override
359 public void onReceive(Context context, Intent intent) {
360 String actionStr = intent.getAction();
361 Uri data = intent.getData();
362 String pkgName = data.getEncodedSchemeSpecificPart();
363 if(localLOGV) Log.i(TAG, "action:"+actionStr+", for package:"+pkgName);
364 updatePackageList(actionStr, pkgName);
365 }
366 }
367
368 private void updatePackageList(String actionStr, String pkgName) {
369 //technically we dont have to invoke handler since onReceive is invoked on
370 //the main thread but doing it here for better clarity
371 if(Intent.ACTION_PACKAGE_ADDED.equalsIgnoreCase(actionStr)) {
372 Bundle data = new Bundle();
373 data.putString(ATTR_PKG_NAME, pkgName);
374 sendMessageToHandler(ADD_PKG, data, 0);
375 } else if(Intent.ACTION_PACKAGE_REMOVED.equalsIgnoreCase(actionStr)) {
376 Bundle data = new Bundle();
377 data.putString(ATTR_PKG_NAME, pkgName);
378 sendMessageToHandler(REMOVE_PKG, data, 0);
379 } else if(Intent.ACTION_PACKAGE_CHANGED.equalsIgnoreCase(actionStr)) {
380 //force adapter to draw the list again. TODO derive from SimpleAdapter
381 //to avoid this
382
383 }
384 }
385
386 /*
387 * Utility method to create an array of map objects from a map of map objects
388 * for displaying list items to be used in SimpleAdapter.
389 */
390 private void createListFromValues() {
391 findViewById(R.id.center_text).setVisibility(View.GONE);
392 populateAdapterList();
393 mAppAdapter.setViewBinder(this);
394 ListView lv= (ListView) findViewById(android.R.id.list);
395 lv.setOnItemClickListener(this);
396 lv.setAdapter(mAppAdapter);
397 if(mCurrentPkgIdx != -1) {
398 lv.setSelection(mCurrentPkgIdx);
399 }
400 }
401
402 @Override
403 protected void onCreate(Bundle savedInstanceState) {
404 super.onCreate(savedInstanceState);
405 String action = getIntent().getAction();
406 if(action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
407 mSortOrder = SORT_ORDER_SIZE;
408 }
409 mPm = getPackageManager();
410 //load strings from resources
411 mBStr = getString(R.string.b_text);
412 mKbStr = getString(R.string.kb_text);
413 mMbStr = getString(R.string.mb_text);
414 }
415
416 @Override
417 public void onStart() {
418 super.onStart();
419 setContentView(R.layout.compute_sizes);
420 //clear all messages related to application list
421 clearMessages();
422 sendMessageToHandler(COMPUTE_PKG_SIZE_START);
423 }
424
425 @Override
426 public void onStop() {
427 super.onStop();
428 //register receiver here
429 unregisterReceiver(mReceiver);
430 }
431
432 public static class AppInfoComparator implements Comparator<ApplicationInfo> {
433 public AppInfoComparator(HashMap<String, PackageStats> pSizeMap) {
434 mSizeMap= pSizeMap;
435 }
436
437 public final int compare(ApplicationInfo a, ApplicationInfo b) {
438 PackageStats aps, bps;
439 aps = mSizeMap.get(a.packageName);
440 bps = mSizeMap.get(b.packageName);
441 if (aps == null && bps == null) {
442 return 0;
443 } else if (aps == null) {
444 return 1;
445 } else if (bps == null) {
446 return -1;
447 }
448 long atotal = aps.dataSize+aps.codeSize+aps.cacheSize;
449 long btotal = bps.dataSize+bps.codeSize+bps.cacheSize;
450 long ret = atotal-btotal;
451 //negate result to sort in descending order
452 if(ret < 0) {
453 return 1;
454 }
455 if(ret == 0) {
456 return 0;
457 }
458 return -1;
459 }
460 private HashMap<String, PackageStats> mSizeMap;
461 }
462
463 /*
464 * Have to extract elements form map and populate a list ot be used by
465 * SimpleAdapter when displaying list elements. The sort order has to follow
466 * the order of elements in mAppList.
467 */
468 private List<Map<String, ?>> createAdapterListFromMap() {
469 //get the index from mAppInfo which gives the correct sort position
470 int imax = mAppList.size();
471 if(localLOGV) Log.i(TAG, "Creating new adapter list");
472 List<Map<String, ?>> adapterList = new ArrayList<Map<String, ?>>();
473 ApplicationInfo tmpInfo;
474 for(int i = 0; i < imax; i++) {
475 tmpInfo = mAppList.get(i);
476 Map<String, Object>newObj = new TreeMap<String, Object>(
477 mAppAdapterMap.get(tmpInfo.packageName));
478 adapterList.add(newObj);
479 }
480 return adapterList;
481 }
482
483 private void populateAdapterList() {
484 mAppAdapter = new SimpleAdapter(this, createAdapterListFromMap(),
485 R.layout.manage_applications_item, sKeys, sResourceIds);
486 }
487
488 private String getSizeStr(PackageStats ps) {
489 String retStr = "";
490 //insert total size information into map to display in view
491 //at this point its guaranteed that ps is not null. but checking anyway
492 if(ps != null) {
493 long size = ps.cacheSize+ps.codeSize+ps.dataSize;
494 if(size < 1024) {
495 return String.valueOf(size)+mBStr;
496 }
497 long kb, mb, rem;
498 kb = size >> 10;
499 rem = size - (kb << 10);
500 if(kb < 1024) {
501 if(rem > 512) {
502 kb++;
503 }
504 retStr += String.valueOf(kb)+mKbStr;
505 return retStr;
506 }
507 mb = kb >> 10;
508 if(kb >= 512) {
509 //round off
510 mb++;
511 }
512 retStr += String.valueOf(mb)+ mMbStr;
513 return retStr;
514 } else {
515 Log.w(TAG, "Something fishy, cannot find size info for package:"+ps.packageName);
516 }
517 return retStr;
518 }
519
520 public void sortAppList() {
521 // Sort application list
522 if(mSortOrder == SORT_ORDER_ALPHA) {
523 Collections.sort(mAppList, new ApplicationInfo.DisplayNameComparator(mPm));
524 } else if(mSortOrder == SORT_ORDER_SIZE) {
525 Collections.sort(mAppList, new AppInfoComparator(mSizeMap));
526 }
527 }
528
529 private Map<String, Object> createMapEntry(CharSequence appName,
530 Drawable appIcon, CharSequence appDesc, String sizeStr) {
531 Map<String, Object> map = new TreeMap<String, Object>();
532 map.put(KEY_APP_NAME, appName);
533 //the icon cannot be null. if the application hasnt set it, the default icon is returned.
534 map.put(KEY_APP_ICON, appIcon);
535 if(appDesc == null) {
536 appDesc="";
537 }
538 map.put(KEY_APP_DESC, appDesc);
539 map.put(KEY_APP_SIZE, sizeStr);
540 return map;
541 }
542
543 private void startApplicationDetailsActivity(ApplicationInfo info, PackageStats ps) {
544 //Create intent to start new activity
545 Intent intent = new Intent(Intent.ACTION_VIEW);
546 intent.setClass(this, InstalledAppDetails.class);
547 intent.putExtra(APP_PKG_NAME, info.packageName);
548 if(localLOGV) Log.i(TAG, "code="+ps.codeSize+", cache="+ps.cacheSize+", data="+ps.dataSize);
549 intent.putExtra(APP_PKG_SIZE, ps);
550 if(localLOGV) Log.i(TAG, "Starting sub activity to display info for app:"+info
551 +" with intent:"+intent);
552 //start new activity to display extended information
553 if ((info.flags&ApplicationInfo.FLAG_SYSTEM) != 0) {
554 }
555 startActivityForResult(intent, INSTALLED_APP_DETAILS);
556 }
557
558 public boolean setViewValue(View view, Object data, String textRepresentation) {
559 if(data == null) {
560 return false;
561 }
562 int id = view.getId();
563 switch(id) {
564 case R.id.app_name:
565 ((TextView)view).setText((String)data);
566 break;
567 case R.id.app_icon:
568 ((ImageView)view).setImageDrawable((Drawable)data);
569 break;
570 case R.id.app_description:
571 ((TextView)view).setText((String)data);
572 break;
573 case R.id.app_size:
574 ((TextView)view).setText((String)data);
575 break;
576 default:
577 break;
578 }
579 return true;
580 }
581
582 @Override
583 public boolean onCreateOptionsMenu(Menu menu) {
584 menu.add(0, SORT_ORDER_ALPHA, 0, R.string.sort_order_alpha)
585 .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
586 menu.add(0, SORT_ORDER_SIZE, 0, R.string.sort_order_size)
587 .setIcon(android.R.drawable.ic_menu_sort_by_size);
588 return true;
589 }
590
591 @Override
592 public boolean onPrepareOptionsMenu(Menu menu) {
593 if(mDoneIniting) {
594 menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
595 menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder!= SORT_ORDER_SIZE);
596 return true;
597 }
598 return false;
599 }
600
601 @Override
602 public boolean onOptionsItemSelected(MenuItem item) {
603 int menuId = item.getItemId();
604 sendMessageToHandler(REORDER_LIST, menuId);
605 return true;
606 }
607
608 public void onItemClick(AdapterView<?> parent, View view, int position,
609 long id) {
610 mCurrentPkgIdx=position;
611 ApplicationInfo info = mAppList.get(position);
612 mCurrentPkg = info;
613 PackageStats ps = mSizeMap.get(info.packageName);
614 startApplicationDetailsActivity(info, ps);
615 }
616}