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