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 17 package com.android.settings.applications; 18 19 import static com.android.settings.Utils.prepareCustomPreferencesList; 20 21 import android.app.Activity; 22 import android.app.Fragment; 23 import android.content.Context; 24 import android.content.Intent; 25 import android.content.pm.ApplicationInfo; 26 import android.content.pm.IPackageManager; 27 import android.content.pm.PackageInfo; 28 import android.os.Bundle; 29 import android.os.Environment; 30 import android.os.RemoteException; 31 import android.os.ServiceManager; 32 import android.os.StatFs; 33 import android.preference.PreferenceActivity; 34 import android.provider.Settings; 35 import android.text.format.Formatter; 36 import android.util.Log; 37 import android.view.LayoutInflater; 38 import android.view.Menu; 39 import android.view.MenuInflater; 40 import android.view.MenuItem; 41 import android.view.View; 42 import android.view.ViewGroup; 43 import android.view.animation.AnimationUtils; 44 import android.view.inputmethod.InputMethodManager; 45 import android.widget.AbsListView; 46 import android.widget.AdapterView; 47 import android.widget.AdapterView.OnItemClickListener; 48 import android.widget.BaseAdapter; 49 import android.widget.CheckBox; 50 import android.widget.Filter; 51 import android.widget.Filterable; 52 import android.widget.ImageView; 53 import android.widget.ListView; 54 import android.widget.TabHost; 55 import android.widget.TextView; 56 57 import com.android.internal.content.PackageHelper; 58 import com.android.settings.R; 59 import com.android.settings.Settings.RunningServicesActivity; 60 import com.android.settings.Settings.StorageUseActivity; 61 import com.android.settings.applications.ApplicationsState.AppEntry; 62 63 import java.util.ArrayList; 64 import java.util.Comparator; 65 66 final class CanBeOnSdCardChecker { 67 final IPackageManager mPm; 68 int mInstallLocation; 69 70 CanBeOnSdCardChecker() { 71 mPm = IPackageManager.Stub.asInterface( 72 ServiceManager.getService("package")); 73 } 74 75 void init() { 76 try { 77 mInstallLocation = mPm.getInstallLocation(); 78 } catch (RemoteException e) { 79 Log.e("CanBeOnSdCardChecker", "Is Package Manager running?"); 80 return; 81 } 82 } 83 84 boolean check(ApplicationInfo info) { 85 boolean canBe = false; 86 if ((info.flags & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0) { 87 canBe = true; 88 } else { 89 if ((info.flags & ApplicationInfo.FLAG_FORWARD_LOCK) == 0 && 90 (info.flags & ApplicationInfo.FLAG_SYSTEM) == 0) { 91 if (info.installLocation == PackageInfo.INSTALL_LOCATION_PREFER_EXTERNAL || 92 info.installLocation == PackageInfo.INSTALL_LOCATION_AUTO) { 93 canBe = true; 94 } else if (info.installLocation 95 == PackageInfo.INSTALL_LOCATION_UNSPECIFIED) { 96 if (mInstallLocation == PackageHelper.APP_INSTALL_EXTERNAL) { 97 // For apps with no preference and the default value set 98 // to install on sdcard. 99 canBe = true; 100 } 101 } 102 } 103 } 104 return canBe; 105 } 106 } 107 108 /** 109 * Activity to pick an application that will be used to display installation information and 110 * options to uninstall/delete user data for system applications. This activity 111 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE 112 * intent. 113 */ 114 public class ManageApplications extends Fragment implements 115 OnItemClickListener, 116 TabHost.TabContentFactory, TabHost.OnTabChangeListener { 117 static final String TAG = "ManageApplications"; 118 static final boolean DEBUG = false; 119 120 // attributes used as keys when passing values to InstalledAppDetails activity 121 public static final String APP_CHG = "chg"; 122 123 // constant value that can be used to check return code from sub activity. 124 private static final int INSTALLED_APP_DETAILS = 1; 125 126 public static final int SIZE_TOTAL = 0; 127 public static final int SIZE_INTERNAL = 1; 128 public static final int SIZE_EXTERNAL = 2; 129 130 // sort order that can be changed through the menu can be sorted alphabetically 131 // or size(descending) 132 private static final int MENU_OPTIONS_BASE = 0; 133 // Filter options used for displayed list of applications 134 public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0; 135 public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1; 136 public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2; 137 138 public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4; 139 public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5; 140 public static final int SHOW_RUNNING_SERVICES = MENU_OPTIONS_BASE + 6; 141 public static final int SHOW_BACKGROUND_PROCESSES = MENU_OPTIONS_BASE + 7; 142 // sort order 143 private int mSortOrder = SORT_ORDER_ALPHA; 144 // Filter value 145 private int mFilterApps = FILTER_APPS_THIRD_PARTY; 146 147 private ApplicationsState mApplicationsState; 148 private ApplicationsAdapter mApplicationsAdapter; 149 150 // Size resource used for packages whose size computation failed for some reason 151 private CharSequence mInvalidSizeStr; 152 private CharSequence mComputingSizeStr; 153 154 // layout inflater object used to inflate views 155 private LayoutInflater mInflater; 156 157 private String mCurrentPkgName; 158 159 private View mLoadingContainer; 160 161 private View mListContainer; 162 163 // ListView used to display list 164 private ListView mListView; 165 // Custom view used to display running processes 166 private RunningProcessesView mRunningProcessesView; 167 168 LinearColorBar mColorBar; 169 TextView mStorageChartLabel; 170 TextView mUsedStorageText; 171 TextView mFreeStorageText; 172 173 private Menu mOptionsMenu; 174 175 // These are for keeping track of activity and tab switch state. 176 private int mCurView; 177 private boolean mCreatedRunning; 178 179 private boolean mResumedRunning; 180 private boolean mActivityResumed; 181 182 private StatFs mDataFileStats; 183 private StatFs mSDCardFileStats; 184 private boolean mLastShowedInternalStorage = true; 185 private long mLastUsedStorage, mLastAppStorage, mLastFreeStorage; 186 187 static final String TAB_DOWNLOADED = "Downloaded"; 188 static final String TAB_RUNNING = "Running"; 189 static final String TAB_ALL = "All"; 190 static final String TAB_SDCARD = "OnSdCard"; 191 private View mRootView; 192 193 private boolean mShowBackground = false; 194 195 // -------------- Copied from TabActivity -------------- 196 197 private TabHost mTabHost; 198 private String mDefaultTab = null; 199 200 // -------------- Copied from TabActivity -------------- 201 202 final Runnable mRunningProcessesAvail = new Runnable() { 203 public void run() { 204 handleRunningProcessesAvail(); 205 } 206 }; 207 208 // View Holder used when displaying views 209 static class AppViewHolder { 210 ApplicationsState.AppEntry entry; 211 TextView appName; 212 ImageView appIcon; 213 TextView appSize; 214 TextView disabled; 215 CheckBox checkBox; 216 217 void updateSizeText(ManageApplications ma, int whichSize) { 218 if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry 219 + ": " + entry.sizeStr); 220 if (entry.sizeStr != null) { 221 switch (whichSize) { 222 case SIZE_INTERNAL: 223 appSize.setText(entry.internalSizeStr); 224 break; 225 case SIZE_EXTERNAL: 226 appSize.setText(entry.externalSizeStr); 227 break; 228 default: 229 appSize.setText(entry.sizeStr); 230 break; 231 } 232 } else if (entry.size == ApplicationsState.SIZE_INVALID) { 233 appSize.setText(ma.mInvalidSizeStr); 234 } 235 } 236 } 237 238 /* 239 * Custom adapter implementation for the ListView 240 * This adapter maintains a map for each displayed application and its properties 241 * An index value on each AppInfo object indicates the correct position or index 242 * in the list. If the list gets updated dynamically when the user is viewing the list of 243 * applications, we need to return the correct index of position. This is done by mapping 244 * the getId methods via the package name into the internal maps and indices. 245 * The order of applications in the list is mirrored in mAppLocalList 246 */ 247 class ApplicationsAdapter extends BaseAdapter implements Filterable, 248 ApplicationsState.Callbacks, AbsListView.RecyclerListener { 249 private final ApplicationsState mState; 250 private final ArrayList<View> mActive = new ArrayList<View>(); 251 private ArrayList<ApplicationsState.AppEntry> mBaseEntries; 252 private ArrayList<ApplicationsState.AppEntry> mEntries; 253 private boolean mResumed; 254 private int mLastFilterMode=-1, mLastSortMode=-1; 255 private boolean mWaitingForData; 256 private int mWhichSize = SIZE_TOTAL; 257 CharSequence mCurFilterPrefix; 258 259 private Filter mFilter = new Filter() { 260 @Override 261 protected FilterResults performFiltering(CharSequence constraint) { 262 ArrayList<ApplicationsState.AppEntry> entries 263 = applyPrefixFilter(constraint, mBaseEntries); 264 FilterResults fr = new FilterResults(); 265 fr.values = entries; 266 fr.count = entries.size(); 267 return fr; 268 } 269 270 @Override 271 protected void publishResults(CharSequence constraint, FilterResults results) { 272 mCurFilterPrefix = constraint; 273 mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values; 274 notifyDataSetChanged(); 275 updateStorageUsage(); 276 } 277 }; 278 279 public ApplicationsAdapter(ApplicationsState state) { 280 mState = state; 281 } 282 283 public void resume(int filter, int sort) { 284 if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed); 285 if (!mResumed) { 286 mResumed = true; 287 mState.resume(this); 288 mLastFilterMode = filter; 289 mLastSortMode = sort; 290 rebuild(true); 291 } else { 292 rebuild(filter, sort); 293 } 294 } 295 296 public void pause() { 297 if (mResumed) { 298 mResumed = false; 299 mState.pause(); 300 } 301 } 302 303 public void rebuild(int filter, int sort) { 304 if (filter == mLastFilterMode && sort == mLastSortMode) { 305 return; 306 } 307 mLastFilterMode = filter; 308 mLastSortMode = sort; 309 rebuild(true); 310 } 311 312 public void rebuild(boolean eraseold) { 313 if (DEBUG) Log.i(TAG, "Rebuilding app list..."); 314 ApplicationsState.AppFilter filterObj; 315 Comparator<AppEntry> comparatorObj; 316 boolean emulated = Environment.isExternalStorageEmulated(); 317 if (emulated) { 318 mWhichSize = SIZE_TOTAL; 319 } else { 320 mWhichSize = SIZE_INTERNAL; 321 } 322 switch (mLastFilterMode) { 323 case FILTER_APPS_THIRD_PARTY: 324 filterObj = ApplicationsState.THIRD_PARTY_FILTER; 325 break; 326 case FILTER_APPS_SDCARD: 327 filterObj = ApplicationsState.ON_SD_CARD_FILTER; 328 if (!emulated) { 329 mWhichSize = SIZE_EXTERNAL; 330 } 331 break; 332 default: 333 filterObj = null; 334 break; 335 } 336 switch (mLastSortMode) { 337 case SORT_ORDER_SIZE: 338 switch (mWhichSize) { 339 case SIZE_INTERNAL: 340 comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR; 341 break; 342 case SIZE_EXTERNAL: 343 comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR; 344 break; 345 default: 346 comparatorObj = ApplicationsState.SIZE_COMPARATOR; 347 break; 348 } 349 break; 350 default: 351 comparatorObj = ApplicationsState.ALPHA_COMPARATOR; 352 break; 353 } 354 ArrayList<ApplicationsState.AppEntry> entries 355 = mState.rebuild(filterObj, comparatorObj); 356 if (entries == null && !eraseold) { 357 // Don't have new list yet, but can continue using the old one. 358 return; 359 } 360 mBaseEntries = entries; 361 if (mBaseEntries != null) { 362 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 363 } else { 364 mEntries = null; 365 } 366 notifyDataSetChanged(); 367 updateStorageUsage(); 368 369 if (entries == null) { 370 mWaitingForData = true; 371 mListContainer.setVisibility(View.INVISIBLE); 372 mLoadingContainer.setVisibility(View.VISIBLE); 373 } else { 374 mListContainer.setVisibility(View.VISIBLE); 375 mLoadingContainer.setVisibility(View.GONE); 376 } 377 } 378 379 ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix, 380 ArrayList<ApplicationsState.AppEntry> origEntries) { 381 if (prefix == null || prefix.length() == 0) { 382 return origEntries; 383 } else { 384 String prefixStr = ApplicationsState.normalize(prefix.toString()); 385 final String spacePrefixStr = " " + prefixStr; 386 ArrayList<ApplicationsState.AppEntry> newEntries 387 = new ArrayList<ApplicationsState.AppEntry>(); 388 for (int i=0; i<origEntries.size(); i++) { 389 ApplicationsState.AppEntry entry = origEntries.get(i); 390 String nlabel = entry.getNormalizedLabel(); 391 if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) { 392 newEntries.add(entry); 393 } 394 } 395 return newEntries; 396 } 397 } 398 399 @Override 400 public void onRunningStateChanged(boolean running) { 401 getActivity().setProgressBarIndeterminateVisibility(running); 402 } 403 404 @Override 405 public void onRebuildComplete(ArrayList<AppEntry> apps) { 406 if (mLoadingContainer.getVisibility() == View.VISIBLE) { 407 mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( 408 getActivity(), android.R.anim.fade_out)); 409 mListContainer.startAnimation(AnimationUtils.loadAnimation( 410 getActivity(), android.R.anim.fade_in)); 411 } 412 mListContainer.setVisibility(View.VISIBLE); 413 mLoadingContainer.setVisibility(View.GONE); 414 mWaitingForData = false; 415 mBaseEntries = apps; 416 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries); 417 notifyDataSetChanged(); 418 updateStorageUsage(); 419 } 420 421 @Override 422 public void onPackageListChanged() { 423 rebuild(false); 424 } 425 426 @Override 427 public void onPackageIconChanged() { 428 // We ensure icons are loaded when their item is displayed, so 429 // don't care about icons loaded in the background. 430 } 431 432 @Override 433 public void onPackageSizeChanged(String packageName) { 434 for (int i=0; i<mActive.size(); i++) { 435 AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag(); 436 if (holder.entry.info.packageName.equals(packageName)) { 437 synchronized (holder.entry) { 438 holder.updateSizeText(ManageApplications.this, mWhichSize); 439 } 440 if (holder.entry.info.packageName.equals(mCurrentPkgName) 441 && mLastSortMode == SORT_ORDER_SIZE) { 442 // We got the size information for the last app the 443 // user viewed, and are sorting by size... they may 444 // have cleared data, so we immediately want to resort 445 // the list with the new size to reflect it to the user. 446 rebuild(false); 447 } 448 updateStorageUsage(); 449 return; 450 } 451 } 452 } 453 454 @Override 455 public void onAllSizesComputed() { 456 if (mLastSortMode == SORT_ORDER_SIZE) { 457 rebuild(false); 458 } 459 } 460 461 public int getCount() { 462 return mEntries != null ? mEntries.size() : 0; 463 } 464 465 public Object getItem(int position) { 466 return mEntries.get(position); 467 } 468 469 public ApplicationsState.AppEntry getAppEntry(int position) { 470 return mEntries.get(position); 471 } 472 473 public long getItemId(int position) { 474 return mEntries.get(position).id; 475 } 476 477 public View getView(int position, View convertView, ViewGroup parent) { 478 // A ViewHolder keeps references to children views to avoid unnecessary calls 479 // to findViewById() on each row. 480 AppViewHolder holder; 481 482 // When convertView is not null, we can reuse it directly, there is no need 483 // to reinflate it. We only inflate a new View when the convertView supplied 484 // by ListView is null. 485 if (convertView == null) { 486 convertView = mInflater.inflate(R.layout.manage_applications_item, null); 487 488 // Creates a ViewHolder and store references to the two children views 489 // we want to bind data to. 490 holder = new AppViewHolder(); 491 holder.appName = (TextView) convertView.findViewById(R.id.app_name); 492 holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon); 493 holder.appSize = (TextView) convertView.findViewById(R.id.app_size); 494 holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled); 495 holder.checkBox = (CheckBox) convertView.findViewById(R.id.app_on_sdcard); 496 convertView.setTag(holder); 497 } else { 498 // Get the ViewHolder back to get fast access to the TextView 499 // and the ImageView. 500 holder = (AppViewHolder) convertView.getTag(); 501 } 502 503 // Bind the data efficiently with the holder 504 ApplicationsState.AppEntry entry = mEntries.get(position); 505 synchronized (entry) { 506 holder.entry = entry; 507 if (entry.label != null) { 508 holder.appName.setText(entry.label); 509 holder.appName.setTextColor(getActivity().getResources().getColorStateList( 510 entry.info.enabled ? android.R.color.primary_text_dark 511 : android.R.color.secondary_text_dark)); 512 } 513 mState.ensureIcon(entry); 514 if (entry.icon != null) { 515 holder.appIcon.setImageDrawable(entry.icon); 516 } 517 holder.updateSizeText(ManageApplications.this, mWhichSize); 518 if (InstalledAppDetails.SUPPORT_DISABLE_APPS) { 519 holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE); 520 } else { 521 holder.disabled.setVisibility(View.GONE); 522 } 523 if (mLastFilterMode == FILTER_APPS_SDCARD) { 524 holder.checkBox.setVisibility(View.VISIBLE); 525 holder.checkBox.setChecked((entry.info.flags 526 & ApplicationInfo.FLAG_EXTERNAL_STORAGE) != 0); 527 } else { 528 holder.checkBox.setVisibility(View.GONE); 529 } 530 } 531 mActive.remove(convertView); 532 mActive.add(convertView); 533 return convertView; 534 } 535 536 @Override 537 public Filter getFilter() { 538 return mFilter; 539 } 540 541 @Override 542 public void onMovedToScrapHeap(View view) { 543 mActive.remove(view); 544 } 545 } 546 547 @Override 548 public void onCreate(Bundle savedInstanceState) { 549 super.onCreate(savedInstanceState); 550 551 setHasOptionsMenu(true); 552 553 mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication()); 554 mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState); 555 Intent intent = getActivity().getIntent(); 556 String action = intent.getAction(); 557 String defaultTabTag = TAB_DOWNLOADED; 558 String className = getArguments() != null 559 ? getArguments().getString("classname") : null; 560 if (className == null) { 561 className = intent.getComponent().getClassName(); 562 } 563 if (className.equals(RunningServicesActivity.class.getName()) 564 || className.endsWith(".RunningServices")) { 565 defaultTabTag = TAB_RUNNING; 566 } else if (className.equals(StorageUseActivity.class.getName()) 567 || Intent.ACTION_MANAGE_PACKAGE_STORAGE.equals(action) 568 || className.endsWith(".StorageUse")) { 569 mSortOrder = SORT_ORDER_SIZE; 570 mFilterApps = FILTER_APPS_ALL; 571 defaultTabTag = TAB_ALL; 572 } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) { 573 // Select the all-apps tab, with the default sorting 574 defaultTabTag = TAB_ALL; 575 } 576 577 if (savedInstanceState != null) { 578 mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder); 579 mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps); 580 String tmp = savedInstanceState.getString("defaultTabTag"); 581 if (tmp != null) defaultTabTag = tmp; 582 mShowBackground = savedInstanceState.getBoolean("showBackground", false); 583 } 584 585 mDefaultTab = defaultTabTag; 586 587 mDataFileStats = new StatFs("/data"); 588 mSDCardFileStats = new StatFs(Environment.getExternalStorageDirectory().toString()); 589 590 mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value); 591 mComputingSizeStr = getActivity().getText(R.string.computing_size); 592 } 593 594 595 @Override 596 public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { 597 // initialize the inflater 598 mInflater = inflater; 599 mRootView = inflater.inflate(R.layout.manage_applications, null); 600 mLoadingContainer = mRootView.findViewById(R.id.loading_container); 601 mListContainer = mRootView.findViewById(R.id.list_container); 602 // Create adapter and list view here 603 ListView lv = (ListView) mListContainer.findViewById(android.R.id.list); 604 View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty); 605 if (emptyView != null) { 606 lv.setEmptyView(emptyView); 607 } 608 lv.setOnItemClickListener(this); 609 lv.setSaveEnabled(true); 610 lv.setItemsCanFocus(true); 611 lv.setOnItemClickListener(this); 612 lv.setTextFilterEnabled(true); 613 mListView = lv; 614 lv.setRecyclerListener(mApplicationsAdapter); 615 mListView.setAdapter(mApplicationsAdapter); 616 mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar); 617 mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel); 618 mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText); 619 mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText); 620 mRunningProcessesView = (RunningProcessesView)mRootView.findViewById( 621 R.id.running_processes); 622 623 mCreatedRunning = mResumedRunning = false; 624 mCurView = VIEW_NOTHING; 625 626 mTabHost = (TabHost) mInflater.inflate(R.layout.manage_apps_tab_content, container, false); 627 mTabHost.setup(); 628 final TabHost tabHost = mTabHost; 629 tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED) 630 .setIndicator(getActivity().getString(R.string.filter_apps_third_party), 631 getActivity().getResources().getDrawable(R.drawable.ic_tab_download)) 632 .setContent(this)); 633 if (!Environment.isExternalStorageEmulated()) { 634 tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD) 635 .setIndicator(getActivity().getString(R.string.filter_apps_onsdcard), 636 getActivity().getResources().getDrawable(R.drawable.ic_tab_sdcard)) 637 .setContent(this)); 638 } 639 tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING) 640 .setIndicator(getActivity().getString(R.string.filter_apps_running), 641 getActivity().getResources().getDrawable(R.drawable.ic_tab_running)) 642 .setContent(this)); 643 tabHost.addTab(tabHost.newTabSpec(TAB_ALL) 644 .setIndicator(getActivity().getString(R.string.filter_apps_all), 645 getActivity().getResources().getDrawable(R.drawable.ic_tab_all)) 646 .setContent(this)); 647 tabHost.setCurrentTabByTag(mDefaultTab); 648 tabHost.setOnTabChangedListener(this); 649 650 // adjust padding around tabwidget as needed 651 prepareCustomPreferencesList(container, mTabHost, mListView, false); 652 653 return mTabHost; 654 } 655 656 @Override 657 public void onStart() { 658 super.onStart(); 659 } 660 661 @Override 662 public void onResume() { 663 super.onResume(); 664 mActivityResumed = true; 665 showCurrentTab(); 666 updateOptionsMenu(); 667 mTabHost.getTabWidget().setEnabled(true); 668 } 669 670 @Override 671 public void onSaveInstanceState(Bundle outState) { 672 super.onSaveInstanceState(outState); 673 outState.putInt("sortOrder", mSortOrder); 674 outState.putInt("filterApps", mFilterApps); 675 if (mDefaultTab != null) { 676 outState.putString("defautTabTag", mDefaultTab); 677 } 678 outState.putBoolean("showBackground", mShowBackground); 679 } 680 681 @Override 682 public void onPause() { 683 super.onPause(); 684 mActivityResumed = false; 685 mApplicationsAdapter.pause(); 686 if (mResumedRunning) { 687 mRunningProcessesView.doPause(); 688 mResumedRunning = false; 689 } 690 mTabHost.getTabWidget().setEnabled(false); 691 } 692 693 @Override 694 public void onActivityResult(int requestCode, int resultCode, Intent data) { 695 if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) { 696 mApplicationsState.requestSize(mCurrentPkgName); 697 } 698 } 699 700 // utility method used to start sub activity 701 private void startApplicationDetailsActivity() { 702 // start new fragment to display extended information 703 Bundle args = new Bundle(); 704 args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mCurrentPkgName); 705 706 PreferenceActivity pa = (PreferenceActivity)getActivity(); 707 pa.startPreferencePanel(InstalledAppDetails.class.getName(), args, 708 R.string.application_info_label, null, this, INSTALLED_APP_DETAILS); 709 } 710 711 @Override 712 public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) { 713 Log.i(TAG, "onCreateOptionsMenu in " + this + ": " + menu); 714 mOptionsMenu = menu; 715 // note: icons removed for now because the cause the new action 716 // bar UI to be very confusing. 717 menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha) 718 //.setIcon(android.R.drawable.ic_menu_sort_alphabetically) 719 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 720 menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size) 721 //.setIcon(android.R.drawable.ic_menu_sort_by_size) 722 .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER); 723 menu.add(0, SHOW_RUNNING_SERVICES, 3, R.string.show_running_services) 724 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 725 menu.add(0, SHOW_BACKGROUND_PROCESSES, 3, R.string.show_background_processes) 726 .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM); 727 updateOptionsMenu(); 728 } 729 730 @Override 731 public void onPrepareOptionsMenu(Menu menu) { 732 updateOptionsMenu(); 733 } 734 735 @Override 736 public void onDestroyOptionsMenu() { 737 mOptionsMenu = null; 738 } 739 740 void updateOptionsMenu() { 741 if (mOptionsMenu == null) { 742 return; 743 } 744 745 /* 746 * The running processes screen doesn't use the mApplicationsAdapter 747 * so bringing up this menu in that case doesn't make any sense. 748 */ 749 if (mCurView == VIEW_RUNNING) { 750 boolean showingBackground = mRunningProcessesView != null 751 ? mRunningProcessesView.mAdapter.getShowBackground() : false; 752 mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(false); 753 mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(false); 754 mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(showingBackground); 755 mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(!showingBackground); 756 } else { 757 mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA); 758 mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE); 759 mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(false); 760 mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(false); 761 } 762 } 763 764 @Override 765 public boolean onOptionsItemSelected(MenuItem item) { 766 int menuId = item.getItemId(); 767 if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) { 768 mSortOrder = menuId; 769 if (mCurView != VIEW_RUNNING) { 770 mApplicationsAdapter.rebuild(mFilterApps, mSortOrder); 771 } 772 } else if (menuId == SHOW_RUNNING_SERVICES) { 773 mShowBackground = false; 774 mRunningProcessesView.mAdapter.setShowBackground(false); 775 } else if (menuId == SHOW_BACKGROUND_PROCESSES) { 776 mShowBackground = true; 777 mRunningProcessesView.mAdapter.setShowBackground(true); 778 } 779 updateOptionsMenu(); 780 return true; 781 } 782 783 public void onItemClick(AdapterView<?> parent, View view, int position, 784 long id) { 785 ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position); 786 mCurrentPkgName = entry.info.packageName; 787 startApplicationDetailsActivity(); 788 } 789 790 public View createTabContent(String tag) { 791 return mRootView; 792 } 793 794 static final int VIEW_NOTHING = 0; 795 static final int VIEW_LIST = 1; 796 static final int VIEW_RUNNING = 2; 797 798 void updateStorageUsage() { 799 if (mCurView == VIEW_RUNNING) { 800 return; 801 } 802 803 long freeStorage = 0; 804 long appStorage = 0; 805 long totalStorage = 0; 806 CharSequence newLabel = null; 807 808 if (mFilterApps == FILTER_APPS_SDCARD) { 809 if (mLastShowedInternalStorage) { 810 mLastShowedInternalStorage = false; 811 } 812 newLabel = getActivity().getText(R.string.sd_card_storage); 813 mSDCardFileStats.restat(Environment.getExternalStorageDirectory().toString()); 814 try { 815 totalStorage = (long)mSDCardFileStats.getBlockCount() * 816 mSDCardFileStats.getBlockSize(); 817 freeStorage = (long) mSDCardFileStats.getAvailableBlocks() * 818 mSDCardFileStats.getBlockSize(); 819 } catch (IllegalArgumentException e) { 820 // use the old value of mFreeMem 821 } 822 final int N = mApplicationsAdapter.getCount(); 823 for (int i=0; i<N; i++) { 824 ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i); 825 appStorage += ae.externalCodeSize + ae.externalDataSize; 826 } 827 } else { 828 if (!mLastShowedInternalStorage) { 829 mLastShowedInternalStorage = true; 830 } 831 newLabel = getActivity().getText(R.string.internal_storage); 832 mDataFileStats.restat("/data"); 833 try { 834 totalStorage = (long)mDataFileStats.getBlockCount() * 835 mDataFileStats.getBlockSize(); 836 freeStorage = (long) mDataFileStats.getAvailableBlocks() * 837 mDataFileStats.getBlockSize(); 838 } catch (IllegalArgumentException e) { 839 } 840 final boolean emulatedStorage = Environment.isExternalStorageEmulated(); 841 final int N = mApplicationsAdapter.getCount(); 842 for (int i=0; i<N; i++) { 843 ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i); 844 appStorage += ae.codeSize + ae.dataSize; 845 if (emulatedStorage) { 846 appStorage += ae.externalCodeSize + ae.externalDataSize; 847 } 848 } 849 freeStorage += mApplicationsState.sumCacheSizes(); 850 } 851 if (newLabel != null) { 852 mStorageChartLabel.setText(newLabel); 853 } 854 if (totalStorage > 0) { 855 mColorBar.setRatios((totalStorage-freeStorage-appStorage)/(float)totalStorage, 856 appStorage/(float)totalStorage, freeStorage/(float)totalStorage); 857 long usedStorage = totalStorage - freeStorage; 858 if (mLastUsedStorage != usedStorage) { 859 mLastUsedStorage = usedStorage; 860 String sizeStr = Formatter.formatShortFileSize(getActivity(), usedStorage); 861 mUsedStorageText.setText(getActivity().getResources().getString( 862 R.string.service_foreground_processes, sizeStr)); 863 } 864 if (mLastFreeStorage != freeStorage) { 865 mLastFreeStorage = freeStorage; 866 String sizeStr = Formatter.formatShortFileSize(getActivity(), freeStorage); 867 mFreeStorageText.setText(getActivity().getResources().getString( 868 R.string.service_background_processes, sizeStr)); 869 } 870 } else { 871 mColorBar.setRatios(0, 0, 0); 872 if (mLastUsedStorage != -1) { 873 mLastUsedStorage = -1; 874 mUsedStorageText.setText(""); 875 } 876 if (mLastFreeStorage != -1) { 877 mLastFreeStorage = -1; 878 mFreeStorageText.setText(""); 879 } 880 } 881 } 882 883 private void selectView(int which) { 884 if (which == VIEW_LIST) { 885 if (mResumedRunning) { 886 mRunningProcessesView.doPause(); 887 mResumedRunning = false; 888 } 889 if (mCurView != which) { 890 mRunningProcessesView.setVisibility(View.GONE); 891 mListContainer.setVisibility(View.VISIBLE); 892 mLoadingContainer.setVisibility(View.GONE); 893 } 894 if (mActivityResumed) { 895 mApplicationsAdapter.resume(mFilterApps, mSortOrder); 896 } 897 } else if (which == VIEW_RUNNING) { 898 if (!mCreatedRunning) { 899 mRunningProcessesView.doCreate(null); 900 mRunningProcessesView.mAdapter.setShowBackground(mShowBackground); 901 mCreatedRunning = true; 902 } 903 boolean haveData = true; 904 if (mActivityResumed && !mResumedRunning) { 905 haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail); 906 mResumedRunning = true; 907 } 908 mApplicationsAdapter.pause(); 909 if (mCurView != which) { 910 if (haveData) { 911 mRunningProcessesView.setVisibility(View.VISIBLE); 912 } else { 913 mLoadingContainer.setVisibility(View.VISIBLE); 914 } 915 mListContainer.setVisibility(View.GONE); 916 } 917 } 918 mCurView = which; 919 final Activity host = getActivity(); 920 if (host != null) { 921 host.invalidateOptionsMenu(); 922 } 923 } 924 925 void handleRunningProcessesAvail() { 926 if (mCurView == VIEW_RUNNING) { 927 mLoadingContainer.startAnimation(AnimationUtils.loadAnimation( 928 getActivity(), android.R.anim.fade_out)); 929 mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation( 930 getActivity(), android.R.anim.fade_in)); 931 mRunningProcessesView.setVisibility(View.VISIBLE); 932 mLoadingContainer.setVisibility(View.GONE); 933 } 934 } 935 936 public void showCurrentTab() { 937 String tabId = mDefaultTab = mTabHost.getCurrentTabTag(); 938 int newOption; 939 if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) { 940 newOption = FILTER_APPS_THIRD_PARTY; 941 } else if (TAB_ALL.equalsIgnoreCase(tabId)) { 942 newOption = FILTER_APPS_ALL; 943 } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) { 944 newOption = FILTER_APPS_SDCARD; 945 } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) { 946 ((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE)) 947 .hideSoftInputFromWindow( 948 getActivity().getWindow().getDecorView().getWindowToken(), 0); 949 selectView(VIEW_RUNNING); 950 return; 951 } else { 952 // Invalid option. Do nothing 953 return; 954 } 955 956 mFilterApps = newOption; 957 selectView(VIEW_LIST); 958 updateStorageUsage(); 959 updateOptionsMenu(); 960 } 961 962 public void onTabChanged(String tabId) { 963 showCurrentTab(); 964 } 965 } 966