2 * Copyright (C) 2006 The Android Open Source Project
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
17 package com.android.settings.applications;
19 import com.android.settings.R;
20 import com.android.settings.applications.ApplicationsState.AppEntry;
22 import android.app.TabActivity;
23 import android.content.Context;
24 import android.content.DialogInterface;
25 import android.content.Intent;
26 import android.content.pm.ApplicationInfo;
27 import android.net.Uri;
28 import android.os.Bundle;
29 import android.provider.Settings;
30 import android.util.Log;
31 import android.view.LayoutInflater;
32 import android.view.Menu;
33 import android.view.MenuItem;
34 import android.view.View;
35 import android.view.ViewGroup;
36 import android.view.Window;
37 import android.view.animation.AnimationUtils;
38 import android.widget.AbsListView;
39 import android.widget.AdapterView;
40 import android.widget.BaseAdapter;
41 import android.widget.Filter;
42 import android.widget.Filterable;
43 import android.widget.ImageView;
44 import android.widget.ListView;
45 import android.widget.TabHost;
46 import android.widget.TextView;
47 import android.widget.AdapterView.OnItemClickListener;
49 import java.util.ArrayList;
50 import java.util.Comparator;
51 import java.util.List;
54 * Activity to pick an application that will be used to display installation information and
55 * options to uninstall/delete user data for system applications. This activity
56 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
59 public class ManageApplications extends TabActivity implements
60 OnItemClickListener, DialogInterface.OnCancelListener,
61 TabHost.TabContentFactory, TabHost.OnTabChangeListener {
62 static final String TAG = "ManageApplications";
63 static final boolean DEBUG = false;
65 // attributes used as keys when passing values to InstalledAppDetails activity
66 public static final String APP_CHG = "chg";
68 // constant value that can be used to check return code from sub activity.
69 private static final int INSTALLED_APP_DETAILS = 1;
71 // sort order that can be changed through the menu can be sorted alphabetically
72 // or size(descending)
73 private static final int MENU_OPTIONS_BASE = 0;
74 // Filter options used for displayed list of applications
75 public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0;
76 public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1;
77 public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2;
79 public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4;
80 public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5;
82 private int mSortOrder = SORT_ORDER_ALPHA;
84 private int mFilterApps = FILTER_APPS_THIRD_PARTY;
86 private ApplicationsState mApplicationsState;
87 private ApplicationsAdapter mApplicationsAdapter;
89 // Size resource used for packages whose size computation failed for some reason
90 private CharSequence mInvalidSizeStr;
91 private CharSequence mComputingSizeStr;
93 // layout inflater object used to inflate views
94 private LayoutInflater mInflater;
96 private String mCurrentPkgName;
98 private View mLoadingContainer;
100 private View mListContainer;
102 // ListView used to display list
103 private ListView mListView;
104 // Custom view used to display running processes
105 private RunningProcessesView mRunningProcessesView;
107 // These are for keeping track of activity and tab switch state.
108 private int mCurView;
109 private boolean mCreatedRunning;
111 private boolean mResumedRunning;
112 private boolean mActivityResumed;
113 private Object mNonConfigInstance;
115 final Runnable mRunningProcessesAvail = new Runnable() {
117 handleRunningProcessesAvail();
121 // View Holder used when displaying views
122 static class AppViewHolder {
123 ApplicationsState.AppEntry entry;
129 void updateSizeText(ManageApplications ma) {
130 if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry
131 + ": " + entry.sizeStr);
132 if (entry.sizeStr != null) {
133 appSize.setText(entry.sizeStr);
134 } else if (entry.size == ApplicationsState.SIZE_INVALID) {
135 appSize.setText(ma.mInvalidSizeStr);
141 * Custom adapter implementation for the ListView
142 * This adapter maintains a map for each displayed application and its properties
143 * An index value on each AppInfo object indicates the correct position or index
144 * in the list. If the list gets updated dynamically when the user is viewing the list of
145 * applications, we need to return the correct index of position. This is done by mapping
146 * the getId methods via the package name into the internal maps and indices.
147 * The order of applications in the list is mirrored in mAppLocalList
149 class ApplicationsAdapter extends BaseAdapter implements Filterable,
150 ApplicationsState.Callbacks, AbsListView.RecyclerListener {
151 private final ApplicationsState mState;
152 private final ArrayList<View> mActive = new ArrayList<View>();
153 private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
154 private ArrayList<ApplicationsState.AppEntry> mEntries;
155 private boolean mResumed;
156 private int mLastFilterMode=-1, mLastSortMode=-1;
157 private boolean mWaitingForData;
158 CharSequence mCurFilterPrefix;
160 private Filter mFilter = new Filter() {
162 protected FilterResults performFiltering(CharSequence constraint) {
163 ArrayList<ApplicationsState.AppEntry> entries
164 = applyPrefixFilter(constraint, mBaseEntries);
165 FilterResults fr = new FilterResults();
167 fr.count = entries.size();
172 protected void publishResults(CharSequence constraint, FilterResults results) {
173 mCurFilterPrefix = constraint;
174 mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values;
175 notifyDataSetChanged();
179 public ApplicationsAdapter(ApplicationsState state) {
183 public void resume(int filter, int sort) {
184 if (DEBUG) Log.i(TAG, "Resume! mResumed=" + mResumed);
188 mLastFilterMode = filter;
189 mLastSortMode = sort;
192 rebuild(filter, sort);
196 public void pause() {
203 public void rebuild(int filter, int sort) {
204 if (filter == mLastFilterMode && sort == mLastSortMode) {
207 mLastFilterMode = filter;
208 mLastSortMode = sort;
212 public void rebuild(boolean eraseold) {
213 if (DEBUG) Log.i(TAG, "Rebuilding app list...");
214 ApplicationsState.AppFilter filterObj;
215 Comparator<AppEntry> comparatorObj;
216 switch (mLastFilterMode) {
217 case FILTER_APPS_THIRD_PARTY:
218 filterObj = ApplicationsState.THIRD_PARTY_FILTER;
220 case FILTER_APPS_SDCARD:
221 filterObj = ApplicationsState.ON_SD_CARD_FILTER;
227 switch (mLastSortMode) {
228 case SORT_ORDER_SIZE:
229 comparatorObj = ApplicationsState.SIZE_COMPARATOR;
232 comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
235 ArrayList<ApplicationsState.AppEntry> entries
236 = mState.rebuild(filterObj, comparatorObj);
237 if (entries == null && !eraseold) {
238 // Don't have new list yet, but can continue using the old one.
241 mBaseEntries = entries;
242 if (mBaseEntries != null) {
243 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
247 notifyDataSetChanged();
249 if (entries == null) {
250 mWaitingForData = true;
251 mListContainer.setVisibility(View.INVISIBLE);
252 mLoadingContainer.setVisibility(View.VISIBLE);
254 mListContainer.setVisibility(View.VISIBLE);
255 mLoadingContainer.setVisibility(View.GONE);
259 ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
260 ArrayList<ApplicationsState.AppEntry> origEntries) {
261 if (prefix == null || prefix.length() == 0) {
264 String prefixStr = ApplicationsState.normalize(prefix.toString());
265 final String spacePrefixStr = " " + prefixStr;
266 ArrayList<ApplicationsState.AppEntry> newEntries
267 = new ArrayList<ApplicationsState.AppEntry>();
268 for (int i=0; i<origEntries.size(); i++) {
269 ApplicationsState.AppEntry entry = origEntries.get(i);
270 String nlabel = entry.getNormalizedLabel();
271 if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
272 newEntries.add(entry);
280 public void onRunningStateChanged(boolean running) {
281 setProgressBarIndeterminateVisibility(running);
285 public void onRebuildComplete(ArrayList<AppEntry> apps) {
286 mListContainer.setVisibility(View.VISIBLE);
287 mLoadingContainer.setVisibility(View.GONE);
288 mWaitingForData = false;
290 mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
291 notifyDataSetChanged();
295 public void onPackageListChanged() {
300 public void onPackageIconChanged() {
301 // We ensure icons are loaded when their item is displayed, so
302 // don't care about icons loaded in the background.
306 public void onPackageSizeChanged(String packageName) {
307 for (int i=0; i<mActive.size(); i++) {
308 AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
309 if (holder.entry.info.packageName.equals(packageName)) {
310 synchronized (holder.entry) {
311 holder.updateSizeText(ManageApplications.this);
313 if (holder.entry.info.packageName.equals(mCurrentPkgName)
314 && mLastSortMode == SORT_ORDER_SIZE) {
315 // We got the size information for the last app the
316 // user viewed, and are sorting by size... they may
317 // have cleared data, so we immediately want to resort
318 // the list with the new size to reflect it to the user.
327 public void onAllSizesComputed() {
328 if (mLastSortMode == SORT_ORDER_SIZE) {
333 public int getCount() {
334 return mEntries != null ? mEntries.size() : 0;
337 public Object getItem(int position) {
338 return mEntries.get(position);
341 public ApplicationsState.AppEntry getAppEntry(int position) {
342 return mEntries.get(position);
345 public long getItemId(int position) {
346 return mEntries.get(position).id;
349 public View getView(int position, View convertView, ViewGroup parent) {
350 // A ViewHolder keeps references to children views to avoid unnecessary calls
351 // to findViewById() on each row.
352 AppViewHolder holder;
354 // When convertView is not null, we can reuse it directly, there is no need
355 // to reinflate it. We only inflate a new View when the convertView supplied
356 // by ListView is null.
357 if (convertView == null) {
358 convertView = mInflater.inflate(R.layout.manage_applications_item, null);
360 // Creates a ViewHolder and store references to the two children views
361 // we want to bind data to.
362 holder = new AppViewHolder();
363 holder.appName = (TextView) convertView.findViewById(R.id.app_name);
364 holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
365 holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
366 holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
367 convertView.setTag(holder);
369 // Get the ViewHolder back to get fast access to the TextView
370 // and the ImageView.
371 holder = (AppViewHolder) convertView.getTag();
374 // Bind the data efficiently with the holder
375 ApplicationsState.AppEntry entry = mEntries.get(position);
376 synchronized (entry) {
377 holder.entry = entry;
378 if (entry.label != null) {
379 holder.appName.setText(entry.label);
380 holder.appName.setTextColor(getResources().getColorStateList(
381 entry.info.enabled ? android.R.color.primary_text_dark
382 : android.R.color.secondary_text_dark));
384 mState.ensureIcon(entry);
385 if (entry.icon != null) {
386 holder.appIcon.setImageDrawable(entry.icon);
388 holder.updateSizeText(ManageApplications.this);
389 if (InstalledAppDetails.SUPPORT_DISABLE_APPS) {
390 holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE);
392 holder.disabled.setVisibility(View.GONE);
395 mActive.remove(convertView);
396 mActive.add(convertView);
401 public Filter getFilter() {
406 public void onMovedToScrapHeap(View view) {
407 mActive.remove(view);
411 static final String TAB_DOWNLOADED = "Downloaded";
412 static final String TAB_RUNNING = "Running";
413 static final String TAB_ALL = "All";
414 static final String TAB_SDCARD = "OnSdCard";
415 private View mRootView;
418 protected void onCreate(Bundle savedInstanceState) {
419 super.onCreate(savedInstanceState);
420 mApplicationsState = ApplicationsState.getInstance(getApplication());
421 mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState);
422 Intent intent = getIntent();
423 String action = intent.getAction();
424 String defaultTabTag = TAB_DOWNLOADED;
425 if (intent.getComponent().getClassName().equals(
426 "com.android.settings.RunningServices")) {
427 defaultTabTag = TAB_RUNNING;
428 } else if (intent.getComponent().getClassName().equals(
429 "com.android.settings.applications.StorageUse")
430 || action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
431 mSortOrder = SORT_ORDER_SIZE;
432 mFilterApps = FILTER_APPS_ALL;
433 defaultTabTag = TAB_ALL;
434 } else if (action.equals(Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS)) {
435 // Select the all-apps tab, with the default sorting
436 defaultTabTag = TAB_ALL;
439 if (savedInstanceState != null) {
440 mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder);
441 mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps);
442 String tmp = savedInstanceState.getString("defaultTabTag");
443 if (tmp != null) defaultTabTag = tmp;
446 mNonConfigInstance = getLastNonConfigurationInstance();
448 // initialize some window features
449 requestWindowFeature(Window.FEATURE_RIGHT_ICON);
450 requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
451 mInvalidSizeStr = getText(R.string.invalid_size_value);
452 mComputingSizeStr = getText(R.string.computing_size);
453 // initialize the inflater
454 mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
455 mRootView = mInflater.inflate(R.layout.manage_applications, null);
456 mLoadingContainer = mRootView.findViewById(R.id.loading_container);
457 mListContainer = mRootView.findViewById(R.id.list_container);
458 // Create adapter and list view here
459 ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
460 View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
461 if (emptyView != null) {
462 lv.setEmptyView(emptyView);
464 lv.setOnItemClickListener(this);
465 lv.setSaveEnabled(true);
466 lv.setItemsCanFocus(true);
467 lv.setOnItemClickListener(this);
468 lv.setTextFilterEnabled(true);
470 lv.setRecyclerListener(mApplicationsAdapter);
471 mListView.setAdapter(mApplicationsAdapter);
472 mRunningProcessesView = (RunningProcessesView)mRootView.findViewById(
473 R.id.running_processes);
475 final TabHost tabHost = getTabHost();
476 tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED)
477 .setIndicator(getString(R.string.filter_apps_third_party),
478 getResources().getDrawable(R.drawable.ic_tab_download))
480 tabHost.addTab(tabHost.newTabSpec(TAB_ALL)
481 .setIndicator(getString(R.string.filter_apps_all),
482 getResources().getDrawable(R.drawable.ic_tab_all))
484 tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD)
485 .setIndicator(getString(R.string.filter_apps_onsdcard),
486 getResources().getDrawable(R.drawable.ic_tab_sdcard))
488 tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING)
489 .setIndicator(getString(R.string.filter_apps_running),
490 getResources().getDrawable(R.drawable.ic_tab_running))
492 tabHost.setCurrentTabByTag(defaultTabTag);
493 tabHost.setOnTabChangedListener(this);
497 public void onStart() {
502 protected void onResume() {
504 mActivityResumed = true;
509 protected void onSaveInstanceState(Bundle outState) {
510 super.onSaveInstanceState(outState);
511 outState.putInt("sortOrder", mSortOrder);
512 outState.putInt("filterApps", mFilterApps);
513 outState.putString("defautTabTag", getTabHost().getCurrentTabTag());
517 public Object onRetainNonConfigurationInstance() {
518 return mRunningProcessesView.doRetainNonConfigurationInstance();
522 protected void onPause() {
524 mActivityResumed = false;
525 mApplicationsAdapter.pause();
526 if (mResumedRunning) {
527 mRunningProcessesView.doPause();
528 mResumedRunning = false;
533 protected void onActivityResult(int requestCode, int resultCode,
535 if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
536 mApplicationsState.requestSize(mCurrentPkgName);
540 // utility method used to start sub activity
541 private void startApplicationDetailsActivity() {
542 // Create intent to start new activity
543 Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
544 Uri.fromParts("package", mCurrentPkgName, null));
545 // start new activity to display extended information
546 startActivityForResult(intent, INSTALLED_APP_DETAILS);
550 public boolean onCreateOptionsMenu(Menu menu) {
551 menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
552 .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
553 menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
554 .setIcon(android.R.drawable.ic_menu_sort_by_size);
559 public boolean onPrepareOptionsMenu(Menu menu) {
560 menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
561 menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
566 public boolean onOptionsItemSelected(MenuItem item) {
567 int menuId = item.getItemId();
568 if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) {
570 mApplicationsAdapter.rebuild(mFilterApps, mSortOrder);
575 public void onItemClick(AdapterView<?> parent, View view, int position,
577 ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position);
578 mCurrentPkgName = entry.info.packageName;
579 startApplicationDetailsActivity();
582 // Finish the activity if the user presses the back button to cancel the activity
583 public void onCancel(DialogInterface dialog) {
587 public View createTabContent(String tag) {
591 static final int VIEW_NOTHING = 0;
592 static final int VIEW_LIST = 1;
593 static final int VIEW_RUNNING = 2;
595 private void selectView(int which) {
596 if (which == VIEW_LIST) {
597 if (mResumedRunning) {
598 mRunningProcessesView.doPause();
599 mResumedRunning = false;
601 if (mCurView != which) {
602 mRunningProcessesView.setVisibility(View.GONE);
603 mListContainer.setVisibility(View.VISIBLE);
604 mLoadingContainer.setVisibility(View.GONE);
606 if (mActivityResumed) {
607 mApplicationsAdapter.resume(mFilterApps, mSortOrder);
609 } else if (which == VIEW_RUNNING) {
610 if (!mCreatedRunning) {
611 mRunningProcessesView.doCreate(null, mNonConfigInstance);
612 mCreatedRunning = true;
614 boolean haveData = true;
615 if (mActivityResumed && !mResumedRunning) {
616 haveData = mRunningProcessesView.doResume(mRunningProcessesAvail);
617 mResumedRunning = true;
619 mApplicationsAdapter.pause();
620 if (mCurView != which) {
622 mRunningProcessesView.setVisibility(View.VISIBLE);
624 mLoadingContainer.setVisibility(View.VISIBLE);
626 mListContainer.setVisibility(View.GONE);
632 void handleRunningProcessesAvail() {
633 if (mCurView == VIEW_RUNNING) {
634 mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
635 this, android.R.anim.fade_out));
636 mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation(
637 this, android.R.anim.fade_in));
638 mRunningProcessesView.setVisibility(View.VISIBLE);
639 mLoadingContainer.setVisibility(View.GONE);
643 public void showCurrentTab() {
644 String tabId = getTabHost().getCurrentTabTag();
646 if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) {
647 newOption = FILTER_APPS_THIRD_PARTY;
648 } else if (TAB_ALL.equalsIgnoreCase(tabId)) {
649 newOption = FILTER_APPS_ALL;
650 } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) {
651 newOption = FILTER_APPS_SDCARD;
652 } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) {
653 selectView(VIEW_RUNNING);
656 // Invalid option. Do nothing
660 mFilterApps = newOption;
661 selectView(VIEW_LIST);
664 public void onTabChanged(String tabId) {