ManageApplications.java revision 44178e2801c013e60defb4b5f390d893e7344a71
1/* 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.applications; 18 19import com.android.settings.R; 20import com.android.settings.applications.ApplicationsState.AppEntry; 21 22import android.app.TabActivity; 23import android.content.Context; 24import android.content.DialogInterface; 25import android.content.Intent; 26import android.net.Uri; 27import android.os.Bundle; 28import android.provider.Settings; 29import android.util.Log; 30import android.view.LayoutInflater; 31import android.view.Menu; 32import android.view.MenuItem; 33import android.view.View; 34import android.view.ViewGroup; 35import android.view.Window; 36import android.view.animation.AnimationUtils; 37import android.widget.AbsListView; 38import android.widget.AdapterView; 39import android.widget.BaseAdapter; 40import android.widget.Filter; 41import android.widget.Filterable; 42import android.widget.ImageView; 43import android.widget.ListView; 44import android.widget.TabHost; 45import android.widget.TextView; 46import android.widget.AdapterView.OnItemClickListener; 47 48import java.util.ArrayList; 49import java.util.Comparator; 50 51/** 52 * Activity to pick an application that will be used to display installation information and 53 * options to uninstall/delete user data for system applications. This activity 54 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE 55 * intent. 56 */ 57public class ManageApplications extends TabActivity implements 58 OnItemClickListener, DialogInterface.OnCancelListener, 59 TabHost.TabContentFactory, TabHost.OnTabChangeListener { 60 static final String TAG = "ManageApplications"; 61 static final boolean DEBUG = false; 62 63 // attributes used as keys when passing values to InstalledAppDetails activity 64 public static final String APP_CHG = "chg"; 65 66 // constant value that can be used to check return code from sub activity. 67 private static final int INSTALLED_APP_DETAILS = 1; 68 69 // sort order that can be changed through the menu can be sorted alphabetically 70 // or size(descending) 71 private static final int MENU_OPTIONS_BASE = 0; 72 // Filter options used for displayed list of applications 73 public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0; 74 public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1; 75 public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2; 76 77 public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4; 78 public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5; 79 // sort order 80 private int mSortOrder = SORT_ORDER_ALPHA; 81 // Filter value 82 private int mFilterApps = FILTER_APPS_THIRD_PARTY; 83 84 private ApplicationsState mApplicationsState; 85 private ApplicationsAdapter mApplicationsAdapter; 86 87 // Size resource used for packages whose size computation failed for some reason 88 private CharSequence mInvalidSizeStr; 89 private CharSequence mComputingSizeStr; 90 91 // layout inflater object used to inflate views 92 private LayoutInflater mInflater; 93 94 private String mCurrentPkgName; 95 96 private View mLoadingContainer; 97 98 private View mListContainer; 99 100 // ListView used to display list 101 private ListView mListView; 102 // Custom view used to display running processes 103 private RunningProcessesView mRunningProcessesView; 104 105 // These are for keeping track of activity and tab switch state. 106 private int mCurView; 107 private boolean mCreatedRunning; 108 109 private boolean mResumedRunning; 110 private boolean mActivityResumed; 111 private Object mNonConfigInstance; 112 113 final Runnable mRunningProcessesAvail = new Runnable() { 114 public void run() { 115 handleRunningProcessesAvail(); 116 } 117 }; 118 119 // View Holder used when displaying views 120 static class AppViewHolder { 121 ApplicationsState.AppEntry entry; 122 TextView appName; 123 ImageView appIcon; 124 TextView appSize; 125 TextView disabled; 126 127 void updateSizeText(ManageApplications ma) { 128 if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry 129 + ": " + entry.sizeStr); 130 if (entry.sizeStr != null) { 131 appSize.setText(entry.sizeStr); 132 } else if (entry.size == ApplicationsState.SIZE_INVALID) { 133 appSize.setText(ma.mInvalidSizeStr); 134 } 135 } 136 } 137 138 /* 139 * Custom adapter implementation for the ListView 140 * This adapter maintains a map for each displayed application and its properties 141 * An index value on each AppInfo object indicates the correct position or index 142 * in the list. If the list gets updated dynamically when the user is viewing the list of 143 * applications, we need to return the correct index of position. This is done by mapping 144 * the getId methods via the package name into the internal maps and indices. 145 * The order of applications in the list is mirrored in mAppLocalList 146 */ 147 class ApplicationsAdapter extends BaseAdapter implements Filterable, 148 ApplicationsState.Callbacks, AbsListView.RecyclerListener { 149 private final ApplicationsState mState; 150 private final ArrayList<View> mActive = new ArrayList<View>(); 151 private ArrayList<ApplicationsState.AppEntry> mBaseEntries; 152 private ArrayList<ApplicationsState.AppEntry> mEntries; 153 private boolean mResumed; 154 private int mLastFilterMode=-1, mLastSortMode=-1; 155 CharSequence mCurFilterPrefix; 156 157 private Filter mFilter = new Filter() { 158 @Override 159 protected FilterResults performFiltering(CharSequence constraint) { 160 ArrayList<ApplicationsState.AppEntry> entries 161 = applyPrefixFilter(constraint, mBaseEntries); 162 FilterResults fr = new FilterResults(); 163 fr.values = entries; 164 fr.count = entries.size(); 165 return fr; 166 } 167 168 @Override 169 protected void publishResults(CharSequence constraint, FilterResults results) { 170 mCurFilterPrefix = constraint; 171 mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values; 172 notifyDataSetChanged(); 173 } 174 }; 175 176 public ApplicationsAdapter(ApplicationsState state) { 177 mState = state; 178 } 179 180 public void resume(int filter, int sort) { 181 if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); 182 if (!mResumed) { 183 mResumed = true; 184 mState.resume(this); 185 mLastFilterMode = filter; 186 mLastSortMode = sort; 187 rebuild(); 188 } else { 189 rebuild(filter, sort); 190 } 191 } 192 193 public void pause() { 194 if (mResumed) { 195 mResumed = false; 196 mState.pause(); 197 } 198 } 199 200 public void rebuild(int filter, int sort) { 201 if (filter == mLastFilterMode && sort == mLastSortMode) { 202 return; 203 } 204 mLastFilterMode = filter; 205 mLastSortMode = sort; 206 rebuild(); 207 } 208 209 public void rebuild() { 210 if (DEBUG) Log.i(TAG, "Rebuilding app list..."); 211 ApplicationsState.AppFilter filterObj; 212 Comparator<AppEntry> comparatorObj; 213 switch (mLastFilterMode) { 214 case FILTER_APPS_THIRD_PARTY: 215 filterObj = ApplicationsState.THIRD_PARTY_FILTER; 216 break; 217 case FILTER_APPS_SDCARD: 218 filterObj = ApplicationsState.ON_SD_CARD_FILTER; 219 break; 220 default: 221 filterObj = null; 222 break; 223 } 224 switch (mLastSortMode) { 225 case SORT_ORDER_SIZE: 226 comparatorObj = ApplicationsState.SIZE_COMPARATOR; 227 break; 228 default: 229 comparatorObj = ApplicationsState.ALPHA_COMPARATOR; 230 break; 231 } 232 mBaseEntries = mState.rebuild(filterObj, comparatorObj); 233 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 234 notifyDataSetChanged(); 235 } 236 237 ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, 238 ArrayList<ApplicationsState.AppEntry> origEntries) { 239 if (prefix == null || prefix.length() == 0) { 240 return origEntries; 241 } else { 242 String prefixStr = ApplicationsState.normalize(prefix.toString()); 243 final String spacePrefixStr = " " + prefixStr; 244 ArrayList<ApplicationsState.AppEntry> newEntries 245 = new ArrayList<ApplicationsState.AppEntry>(); 246 for (int i=0; i<origEntries.size(); i++) { 247 ApplicationsState.AppEntry entry = origEntries.get(i); 248 String nlabel = entry.getNormalizedLabel(); 249 if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) { 250 newEntries.add(entry); 251 } 252 } 253 return newEntries; 254 } 255 } 256 257 @Override 258 public void onRunningStateChanged(boolean running) { 259 setProgressBarIndeterminateVisibility(running); 260 } 261 262 @Override 263 public void onPackageListChanged() { 264 rebuild(); 265 } 266 267 @Override 268 public void onPackageIconChanged() { 269 // We ensure icons are loaded when their item is displayed, so 270 // don't care about icons loaded in the background. 271 } 272 273 @Override 274 public void onPackageSizeChanged(String packageName) { 275 for (int i=0; i<mActive.size(); i++) { 276 AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag(); 277 if (holder.entry.info.packageName.equals(packageName)) { 278 synchronized (holder.entry) { 279 holder.updateSizeText(ManageApplications.this); 280 } 281 if (holder.entry.info.packageName.equals(mCurrentPkgName) 282 && mLastSortMode == SORT_ORDER_SIZE) { 283 // We got the size information for the last app the 284 // user viewed, and are sorting by size... they may 285 // have cleared data, so we immediately want to resort 286 // the list with the new size to reflect it to the user. 287 rebuild(); 288 } 289 return; 290 } 291 } 292 } 293 294 @Override 295 public void onAllSizesComputed() { 296 if (mLastSortMode == SORT_ORDER_SIZE) { 297 rebuild(); 298 } 299 } 300 301 public int getCount() { 302 return mEntries != null ? mEntries.size() : 0; 303 } 304 305 public Object getItem(int position) { 306 return mEntries.get(position); 307 } 308 309 public ApplicationsState.AppEntry getAppEntry(int position) { 310 return mEntries.get(position); 311 } 312 313 public long getItemId(int position) { 314 return mEntries.get(position).id; 315 } 316 317 public View getView(int position, View convertView, ViewGroup parent) { 318 // A ViewHolder keeps references to children views to avoid unnecessary calls 319 // to findViewById() on each row. 320 AppViewHolder holder; 321 322 // When convertView is not null, we can reuse it directly, there is no need 323 // to reinflate it. We only inflate a new View when the convertView supplied 324 // by ListView is null. 325 if (convertView == null) { 326 convertView = mInflater.inflate(R.layout.manage_applications_item, null); 327 328 // Creates a ViewHolder and store references to the two children views 329 // we want to bind data to. 330 holder = new AppViewHolder(); 331 holder.appName = (TextView) convertView.findViewById(R.id.app_name); 332 holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); 333 holder.appSize = (TextView) convertView.findViewById(R.id.app_size); 334 holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); 335 convertView.setTag(holder); 336 } else { 337 // Get the ViewHolder back to get fast access to the TextView 338 // and the ImageView. 339 holder = (AppViewHolder) convertView.getTag(); 340 } 341 342 // Bind the data efficiently with the holder 343 ApplicationsState.AppEntry entry = mEntries.get(position); 344 synchronized (entry) { 345 holder.entry = entry; 346 if (entry.label != null) { 347 holder.appName.setText(entry.label); 348 holder.appName.setTextColor(getResources().getColorStateList( 349 entry.info.enabled ? android.R.color.primary_text_dark 350 : android.R.color.secondary_text_dark)); 351 } 352 mState.ensureIcon(entry); 353 if (entry.icon != null) { 354 holder.appIcon.setImageDrawable(entry.icon); 355 } 356 holder.updateSizeText(ManageApplications.this); 357 if (InstalledAppDetails.SUPPORT_DISABLE_APPS) { 358 holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE); 359 } else { 360 holder.disabled.setVisibility(View.GONE); 361 } 362 } 363 mActive.remove(convertView); 364 mActive.add(convertView); 365 return convertView; 366 } 367 368 @Override 369 public Filter getFilter() { 370 return mFilter; 371 } 372 373 @Override 374 public void onMovedToScrapHeap(View view) { 375 mActive.remove(view); 376 } 377 } 378 379 static final String TAB_DOWNLOADED = "Downloaded"; 380 static final String TAB_RUNNING = "Running"; 381 static final String TAB_ALL = "All"; 382 static final String TAB_SDCARD = "OnSdCard"; 383 private View mRootView; 384 385 @Override 386 protected void onCreate(Bundle savedInstanceState) { 387 super.onCreate(savedInstanceState); 388 mApplicationsState = ApplicationsState.getInstance(getApplication()); 389 mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState); 390 Intent intent = getIntent(); 391 String action = intent.getAction(); 392 String defaultTabTag = TAB_DOWNLOADED; 393 if (intent.getComponent().getClassName().equals( 394 "com.android.settings.RunningServices")) { 395 defaultTabTag = TAB_RUNNING; 396 } else if (intent.getComponent().getClassName().equals( 397 "com.android.settings.applications.StorageUse") 398 || action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) { 399 mSortOrder = SORT_ORDER_SIZE; 400 mFilterApps = FILTER_APPS_ALL; 401 defaultTabTag = TAB_ALL; 402 } 403 404 if (savedInstanceState != null) { 405 mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder); 406 mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps); 407 String tmp = savedInstanceState.getString("defaultTabTag"); 408 if (tmp != null) defaultTabTag = tmp; 409 } 410 411 mNonConfigInstance = getLastNonConfigurationInstance(); 412 413 // initialize some window features 414 requestWindowFeature(Window.FEATURE_RIGHT_ICON); 415 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); 416 mInvalidSizeStr = getText(R.string.invalid_size_value); 417 mComputingSizeStr = getText(R.string.computing_size); 418 // initialize the inflater 419 mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE); 420 mRootView = mInflater.inflate(R.layout.manage_applications, null); 421 mLoadingContainer = mRootView.findViewById(R.id.loading_container); 422 mListContainer = mRootView.findViewById(R.id.list_container); 423 // Create adapter and list view here 424 ListView lv = (ListView) mListContainer.findViewById(android.R.id.list); 425 View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); 426 if (emptyView != null) { 427 lv.setEmptyView(emptyView); 428 } 429 lv.setOnItemClickListener(this); 430 lv.setSaveEnabled(true); 431 lv.setItemsCanFocus(true); 432 lv.setOnItemClickListener(this); 433 lv.setTextFilterEnabled(true); 434 mListView = lv; 435 lv.setRecyclerListener(mApplicationsAdapter); 436 mListView.setAdapter(mApplicationsAdapter); 437 mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( 438 R.id.running_processes); 439 440 final TabHost tabHost = getTabHost(); 441 tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED) 442 .setIndicator(getString(R.string.filter_apps_third_party), 443 getResources().getDrawable(R.drawable.ic_tab_download)) 444 .setContent(this)); 445 tabHost.addTab(tabHost.newTabSpec(TAB_ALL) 446 .setIndicator(getString(R.string.filter_apps_all), 447 getResources().getDrawable(R.drawable.ic_tab_all)) 448 .setContent(this)); 449 tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD) 450 .setIndicator(getString(R.string.filter_apps_onsdcard), 451 getResources().getDrawable(R.drawable.ic_tab_sdcard)) 452 .setContent(this)); 453 tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING) 454 .setIndicator(getString(R.string.filter_apps_running), 455 getResources().getDrawable(R.drawable.ic_tab_running)) 456 .setContent(this)); 457 tabHost.setCurrentTabByTag(defaultTabTag); 458 tabHost.setOnTabChangedListener(this); 459 } 460 461 @Override 462 public void onStart() { 463 super.onStart(); 464 } 465 466 @Override 467 protected void onResume() { 468 super.onResume(); 469 mActivityResumed = true; 470 showCurrentTab(); 471 } 472 473 @Override 474 protected void onSaveInstanceState(Bundle outState) { 475 super.onSaveInstanceState(outState); 476 outState.putInt("sortOrder", mSortOrder); 477 outState.putInt("filterApps", mFilterApps); 478 outState.putString("defautTabTag", getTabHost().getCurrentTabTag()); 479 } 480 481 @Override 482 public Object onRetainNonConfigurationInstance() { 483 return mRunningProcessesView.doRetainNonConfigurationInstance(); 484 } 485 486 @Override 487 protected void onPause() { 488 super.onPause(); 489 mActivityResumed = false; 490 mApplicationsAdapter.pause(); 491 if (mResumedRunning) { 492 mRunningProcessesView.doPause(); 493 mResumedRunning = false; 494 } 495 } 496 497 @Override 498 protected void onActivityResult(int requestCode, int resultCode, 499 Intent data) { 500 if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { 501 mApplicationsState.requestSize(mCurrentPkgName); 502 } 503 } 504 505 // utility method used to start sub activity 506 private void startApplicationDetailsActivity() { 507 // Create intent to start new activity 508 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS, 509 Uri.fromParts("package", mCurrentPkgName, null)); 510 // start new activity to display extended information 511 startActivityForResult(intent, INSTALLED_APP_DETAILS); 512 } 513 514 @Override 515 public boolean onCreateOptionsMenu(Menu menu) { 516 menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha) 517 .setIcon(android.R.drawable.ic_menu_sort_alphabetically); 518 menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size) 519 .setIcon(android.R.drawable.ic_menu_sort_by_size); 520 return true; 521 } 522 523 @Override 524 public boolean onPrepareOptionsMenu(Menu menu) { 525 menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); 526 menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); 527 return true; 528 } 529 530 @Override 531 public boolean onOptionsItemSelected(MenuItem item) { 532 int menuId = item.getItemId(); 533 if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) { 534 mSortOrder = menuId; 535 mApplicationsAdapter.rebuild(mFilterApps, mSortOrder); 536 } 537 return true; 538 } 539 540 public void onItemClick(AdapterView<?> parent, View view, int position, 541 long id) { 542 ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position); 543 mCurrentPkgName = entry.info.packageName; 544 startApplicationDetailsActivity(); 545 } 546 547 // Finish the activity if the user presses the back button to cancel the activity 548 public void onCancel(DialogInterface dialog) { 549 finish(); 550 } 551 552 public View createTabContent(String tag) { 553 return mRootView; 554 } 555 556 static final int VIEW_NOTHING = 0; 557 static final int VIEW_LIST = 1; 558 static final int VIEW_RUNNING = 2; 559 560 private void selectView(int which) { 561 if (which == VIEW_LIST) { 562 if (mResumedRunning) { 563 mRunningProcessesView.doPause(); 564 mResumedRunning = false; 565 } 566 if (mCurView != which) { 567 mRunningProcessesView.setVisibility(View.GONE); 568 mListContainer.setVisibility(View.VISIBLE); 569 } 570 if (mActivityResumed) { 571 mApplicationsAdapter.resume(mFilterApps, mSortOrder); 572 } 573 } else if (which == VIEW_RUNNING) { 574 if (!mCreatedRunning) { 575 mRunningProcessesView.doCreate(null, mNonConfigInstance); 576 mCreatedRunning = true; 577 } 578 boolean haveData = true; 579 if (mActivityResumed && !mResumedRunning) { 580 haveData = mRunningProcessesView.doResume(mRunningProcessesAvail); 581 mResumedRunning = true; 582 } 583 mApplicationsAdapter.pause(); 584 if (mCurView != which) { 585 if (haveData) { 586 mRunningProcessesView.setVisibility(View.VISIBLE); 587 } else { 588 mLoadingContainer.setVisibility(View.VISIBLE); 589 } 590 mListContainer.setVisibility(View.GONE); 591 } 592 } 593 mCurView = which; 594 } 595 596 void handleRunningProcessesAvail() { 597 if (mCurView == VIEW_RUNNING) { 598 mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( 599 this, android.R.anim.fade_out)); 600 mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( 601 this, android.R.anim.fade_in)); 602 mRunningProcessesView.setVisibility(View.VISIBLE); 603 mLoadingContainer.setVisibility(View.GONE); 604 } 605 } 606 607 public void showCurrentTab() { 608 String tabId = getTabHost().getCurrentTabTag(); 609 int newOption; 610 if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { 611 newOption = FILTER_APPS_THIRD_PARTY; 612 } else if (TAB_ALL.equalsIgnoreCase(tabId)) { 613 newOption = FILTER_APPS_ALL; 614 } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { 615 newOption = FILTER_APPS_SDCARD; 616 } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) { 617 selectView(VIEW_RUNNING); 618 return; 619 } else { 620 // Invalid option. Do nothing 621 return; 622 } 623 624 mFilterApps = newOption; 625 selectView(VIEW_LIST); 626 } 627 628 public void onTabChanged(String tabId) { 629 showCurrentTab(); 630 } 631} 632