ManageApplications.java revision 762eaa77e3e12d5337ddc9cb87b473c47af5524a
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 static com.android.settings.Utils.prepareCustomPreferencesList;
20
21import android.app.Activity;
22import android.app.Fragment;
23import android.content.Context;
24import android.content.Intent;
25import android.content.pm.ApplicationInfo;
26import android.content.pm.IPackageManager;
27import android.content.pm.PackageInfo;
28import android.os.Bundle;
29import android.os.Environment;
30import android.os.RemoteException;
31import android.os.ServiceManager;
32import android.os.StatFs;
33import android.preference.PreferenceActivity;
34import android.provider.Settings;
35import android.text.format.Formatter;
36import android.util.Log;
37import android.view.LayoutInflater;
38import android.view.Menu;
39import android.view.MenuInflater;
40import android.view.MenuItem;
41import android.view.View;
42import android.view.ViewGroup;
43import android.view.animation.AnimationUtils;
44import android.view.inputmethod.InputMethodManager;
45import android.widget.AbsListView;
46import android.widget.AdapterView;
47import android.widget.AdapterView.OnItemClickListener;
48import android.widget.BaseAdapter;
49import android.widget.CheckBox;
50import android.widget.Filter;
51import android.widget.Filterable;
52import android.widget.ImageView;
53import android.widget.ListView;
54import android.widget.TabHost;
55import android.widget.TextView;
56
57import com.android.internal.content.PackageHelper;
58import com.android.settings.R;
59import com.android.settings.Settings.RunningServicesActivity;
60import com.android.settings.Settings.StorageUseActivity;
61import com.android.settings.applications.ApplicationsState.AppEntry;
62
63import java.util.ArrayList;
64import java.util.Comparator;
65
66final 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 */
114public 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            defaultTabTag = TAB_RUNNING;
565        } else if (className.equals(StorageUseActivity.class.getName())
566                || Intent.ACTION_MANAGE_PACKAGE_STORAGE.equals(action)) {
567            mSortOrder = SORT_ORDER_SIZE;
568            mFilterApps = FILTER_APPS_ALL;
569            defaultTabTag = TAB_ALL;
570        } else if (Settings.ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS.equals(action)) {
571            // Select the all-apps tab, with the default sorting
572            defaultTabTag = TAB_ALL;
573        }
574
575        if (savedInstanceState != null) {
576            mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder);
577            mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps);
578            String tmp = savedInstanceState.getString("defaultTabTag");
579            if (tmp != null) defaultTabTag = tmp;
580            mShowBackground = savedInstanceState.getBoolean("showBackground", false);
581        }
582
583        mDefaultTab = defaultTabTag;
584
585        mDataFileStats = new StatFs("/data");
586        mSDCardFileStats = new StatFs(Environment.getExternalStorageDirectory().toString());
587
588        mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
589        mComputingSizeStr = getActivity().getText(R.string.computing_size);
590    }
591
592
593    @Override
594    public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
595        // initialize the inflater
596        mInflater = inflater;
597        mRootView = inflater.inflate(R.layout.manage_applications, null);
598        mLoadingContainer = mRootView.findViewById(R.id.loading_container);
599        mListContainer = mRootView.findViewById(R.id.list_container);
600        // Create adapter and list view here
601        ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
602        View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
603        if (emptyView != null) {
604            lv.setEmptyView(emptyView);
605        }
606        lv.setOnItemClickListener(this);
607        lv.setSaveEnabled(true);
608        lv.setItemsCanFocus(true);
609        lv.setOnItemClickListener(this);
610        lv.setTextFilterEnabled(true);
611        mListView = lv;
612        lv.setRecyclerListener(mApplicationsAdapter);
613        mListView.setAdapter(mApplicationsAdapter);
614        mColorBar = (LinearColorBar)mListContainer.findViewById(R.id.storage_color_bar);
615        mStorageChartLabel = (TextView)mListContainer.findViewById(R.id.storageChartLabel);
616        mUsedStorageText = (TextView)mListContainer.findViewById(R.id.usedStorageText);
617        mFreeStorageText = (TextView)mListContainer.findViewById(R.id.freeStorageText);
618        mRunningProcessesView = (RunningProcessesView)mRootView.findViewById(
619                R.id.running_processes);
620
621        mCreatedRunning = mResumedRunning = false;
622        mCurView = VIEW_NOTHING;
623
624        mTabHost = (TabHost) mInflater.inflate(R.layout.manage_apps_tab_content, container, false);
625        mTabHost.setup();
626        final TabHost tabHost = mTabHost;
627        tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED)
628                .setIndicator(getActivity().getString(R.string.filter_apps_third_party),
629                        getActivity().getResources().getDrawable(R.drawable.ic_tab_download))
630                .setContent(this));
631        if (!Environment.isExternalStorageEmulated()) {
632            tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD)
633                    .setIndicator(getActivity().getString(R.string.filter_apps_onsdcard),
634                            getActivity().getResources().getDrawable(R.drawable.ic_tab_sdcard))
635                    .setContent(this));
636        }
637        tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING)
638                .setIndicator(getActivity().getString(R.string.filter_apps_running),
639                        getActivity().getResources().getDrawable(R.drawable.ic_tab_running))
640                .setContent(this));
641        tabHost.addTab(tabHost.newTabSpec(TAB_ALL)
642                .setIndicator(getActivity().getString(R.string.filter_apps_all),
643                        getActivity().getResources().getDrawable(R.drawable.ic_tab_all))
644                .setContent(this));
645        tabHost.setCurrentTabByTag(mDefaultTab);
646        tabHost.setOnTabChangedListener(this);
647
648        // adjust padding around tabwidget as needed
649        prepareCustomPreferencesList(container, mTabHost, mListView);
650
651        return mTabHost;
652    }
653
654    @Override
655    public void onStart() {
656        super.onStart();
657    }
658
659    @Override
660    public void onResume() {
661        super.onResume();
662        mActivityResumed = true;
663        showCurrentTab();
664        updateOptionsMenu();
665    }
666
667    @Override
668    public void onSaveInstanceState(Bundle outState) {
669        super.onSaveInstanceState(outState);
670        outState.putInt("sortOrder", mSortOrder);
671        outState.putInt("filterApps", mFilterApps);
672        if (mDefaultTab != null) {
673            outState.putString("defautTabTag", mDefaultTab);
674        }
675        outState.putBoolean("showBackground", mShowBackground);
676    }
677
678    @Override
679    public void onPause() {
680        super.onPause();
681        mActivityResumed = false;
682        mApplicationsAdapter.pause();
683        if (mResumedRunning) {
684            mRunningProcessesView.doPause();
685            mResumedRunning = false;
686        }
687    }
688
689    @Override
690    public void onActivityResult(int requestCode, int resultCode, Intent data) {
691        if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
692            mApplicationsState.requestSize(mCurrentPkgName);
693        }
694    }
695
696    // utility method used to start sub activity
697    private void startApplicationDetailsActivity() {
698        // start new fragment to display extended information
699        Bundle args = new Bundle();
700        args.putString(InstalledAppDetails.ARG_PACKAGE_NAME, mCurrentPkgName);
701
702        PreferenceActivity pa = (PreferenceActivity)getActivity();
703        pa.startPreferencePanel(InstalledAppDetails.class.getName(), args,
704                R.string.application_info_label, null, this, INSTALLED_APP_DETAILS);
705    }
706
707    @Override
708    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
709        Log.i(TAG, "onCreateOptionsMenu in " + this + ": " + menu);
710        mOptionsMenu = menu;
711        // note: icons removed for now because the cause the new action
712        // bar UI to be very confusing.
713        menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
714                //.setIcon(android.R.drawable.ic_menu_sort_alphabetically)
715                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
716        menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
717                //.setIcon(android.R.drawable.ic_menu_sort_by_size)
718                .setShowAsAction(MenuItem.SHOW_AS_ACTION_NEVER);
719        menu.add(0, SHOW_RUNNING_SERVICES, 3, R.string.show_running_services)
720                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
721        menu.add(0, SHOW_BACKGROUND_PROCESSES, 3, R.string.show_background_processes)
722                .setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
723        updateOptionsMenu();
724    }
725
726    @Override
727    public void onPrepareOptionsMenu(Menu menu) {
728        updateOptionsMenu();
729    }
730
731    @Override
732    public void onDestroyOptionsMenu() {
733        mOptionsMenu = null;
734    }
735
736    void updateOptionsMenu() {
737        if (mOptionsMenu == null) {
738            return;
739        }
740
741        /*
742         * The running processes screen doesn't use the mApplicationsAdapter
743         * so bringing up this menu in that case doesn't make any sense.
744         */
745        if (mCurView == VIEW_RUNNING) {
746            boolean showingBackground = mRunningProcessesView != null
747                    ? mRunningProcessesView.mAdapter.getShowBackground() : false;
748            mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(false);
749            mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(false);
750            mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(showingBackground);
751            mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(!showingBackground);
752        } else {
753            mOptionsMenu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
754            mOptionsMenu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
755            mOptionsMenu.findItem(SHOW_RUNNING_SERVICES).setVisible(false);
756            mOptionsMenu.findItem(SHOW_BACKGROUND_PROCESSES).setVisible(false);
757        }
758    }
759
760    @Override
761    public boolean onOptionsItemSelected(MenuItem item) {
762        int menuId = item.getItemId();
763        if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) {
764            mSortOrder = menuId;
765            if (mCurView != VIEW_RUNNING) {
766                mApplicationsAdapter.rebuild(mFilterApps, mSortOrder);
767            }
768        } else if (menuId == SHOW_RUNNING_SERVICES) {
769            mShowBackground = false;
770            mRunningProcessesView.mAdapter.setShowBackground(false);
771        } else if (menuId == SHOW_BACKGROUND_PROCESSES) {
772            mShowBackground = true;
773            mRunningProcessesView.mAdapter.setShowBackground(true);
774        }
775        updateOptionsMenu();
776        return true;
777    }
778
779    public void onItemClick(AdapterView<?> parent, View view, int position,
780            long id) {
781        ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position);
782        mCurrentPkgName = entry.info.packageName;
783        startApplicationDetailsActivity();
784    }
785
786    public View createTabContent(String tag) {
787        return mRootView;
788    }
789
790    static final int VIEW_NOTHING = 0;
791    static final int VIEW_LIST = 1;
792    static final int VIEW_RUNNING = 2;
793
794    void updateStorageUsage() {
795        if (mCurView == VIEW_RUNNING) {
796            return;
797        }
798
799        long freeStorage = 0;
800        long appStorage = 0;
801        long totalStorage = 0;
802        CharSequence newLabel = null;
803
804        if (mFilterApps == FILTER_APPS_SDCARD) {
805            if (mLastShowedInternalStorage) {
806                mLastShowedInternalStorage = false;
807            }
808            newLabel = getActivity().getText(R.string.sd_card_storage);
809            mSDCardFileStats.restat(Environment.getExternalStorageDirectory().toString());
810            try {
811                totalStorage = (long)mSDCardFileStats.getBlockCount() *
812                        mSDCardFileStats.getBlockSize();
813                freeStorage = (long) mSDCardFileStats.getAvailableBlocks() *
814                mSDCardFileStats.getBlockSize();
815            } catch (IllegalArgumentException e) {
816                // use the old value of mFreeMem
817            }
818            final int N = mApplicationsAdapter.getCount();
819            for (int i=0; i<N; i++) {
820                ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
821                appStorage += ae.externalCodeSize + ae.externalDataSize;
822            }
823        } else {
824            if (!mLastShowedInternalStorage) {
825                mLastShowedInternalStorage = true;
826            }
827            newLabel = getActivity().getText(R.string.internal_storage);
828            mDataFileStats.restat("/data");
829            try {
830                totalStorage = (long)mDataFileStats.getBlockCount() *
831                        mDataFileStats.getBlockSize();
832                freeStorage = (long) mDataFileStats.getAvailableBlocks() *
833                    mDataFileStats.getBlockSize();
834            } catch (IllegalArgumentException e) {
835            }
836            final boolean emulatedStorage = Environment.isExternalStorageEmulated();
837            final int N = mApplicationsAdapter.getCount();
838            for (int i=0; i<N; i++) {
839                ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
840                appStorage += ae.codeSize + ae.dataSize;
841                if (emulatedStorage) {
842                    appStorage += ae.externalCodeSize + ae.externalDataSize;
843                }
844            }
845            freeStorage += mApplicationsState.sumCacheSizes();
846        }
847        if (newLabel != null) {
848            mStorageChartLabel.setText(newLabel);
849        }
850        if (totalStorage > 0) {
851            mColorBar.setRatios((totalStorage-freeStorage-appStorage)/(float)totalStorage,
852                    appStorage/(float)totalStorage, freeStorage/(float)totalStorage);
853            long usedStorage = totalStorage - freeStorage;
854            if (mLastUsedStorage != usedStorage) {
855                mLastUsedStorage = usedStorage;
856                String sizeStr = Formatter.formatShortFileSize(getActivity(), usedStorage);
857                mUsedStorageText.setText(getActivity().getResources().getString(
858                        R.string.service_foreground_processes, sizeStr));
859            }
860            if (mLastFreeStorage != freeStorage) {
861                mLastFreeStorage = freeStorage;
862                String sizeStr = Formatter.formatShortFileSize(getActivity(), freeStorage);
863                mFreeStorageText.setText(getActivity().getResources().getString(
864                        R.string.service_background_processes, sizeStr));
865            }
866        } else {
867            mColorBar.setRatios(0, 0, 0);
868            if (mLastUsedStorage != -1) {
869                mLastUsedStorage = -1;
870                mUsedStorageText.setText("");
871            }
872            if (mLastFreeStorage != -1) {
873                mLastFreeStorage = -1;
874                mFreeStorageText.setText("");
875            }
876        }
877    }
878
879    private void selectView(int which) {
880        if (which == VIEW_LIST) {
881            if (mResumedRunning) {
882                mRunningProcessesView.doPause();
883                mResumedRunning = false;
884            }
885            if (mCurView != which) {
886                mRunningProcessesView.setVisibility(View.GONE);
887                mListContainer.setVisibility(View.VISIBLE);
888                mLoadingContainer.setVisibility(View.GONE);
889            }
890            if (mActivityResumed) {
891                mApplicationsAdapter.resume(mFilterApps, mSortOrder);
892            }
893        } else if (which == VIEW_RUNNING) {
894            if (!mCreatedRunning) {
895                mRunningProcessesView.doCreate(null);
896                mRunningProcessesView.mAdapter.setShowBackground(mShowBackground);
897                mCreatedRunning = true;
898            }
899            boolean haveData = true;
900            if (mActivityResumed && !mResumedRunning) {
901                haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
902                mResumedRunning = true;
903            }
904            mApplicationsAdapter.pause();
905            if (mCurView != which) {
906                if (haveData) {
907                    mRunningProcessesView.setVisibility(View.VISIBLE);
908                } else {
909                    mLoadingContainer.setVisibility(View.VISIBLE);
910                }
911                mListContainer.setVisibility(View.GONE);
912            }
913        }
914        mCurView = which;
915        final Activity host = getActivity();
916        if (host != null) {
917            host.invalidateOptionsMenu();
918        }
919    }
920
921    void handleRunningProcessesAvail() {
922        if (mCurView == VIEW_RUNNING) {
923            mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
924                    getActivity(), android.R.anim.fade_out));
925            mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation(
926                    getActivity(), android.R.anim.fade_in));
927            mRunningProcessesView.setVisibility(View.VISIBLE);
928            mLoadingContainer.setVisibility(View.GONE);
929        }
930    }
931
932    public void showCurrentTab() {
933        String tabId = mDefaultTab = mTabHost.getCurrentTabTag();
934        int newOption;
935        if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) {
936            newOption = FILTER_APPS_THIRD_PARTY;
937        } else if (TAB_ALL.equalsIgnoreCase(tabId)) {
938            newOption = FILTER_APPS_ALL;
939        } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) {
940            newOption = FILTER_APPS_SDCARD;
941        } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) {
942            ((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE))
943                    .hideSoftInputFromWindow(
944                            getActivity().getWindow().getDecorView().getWindowToken(), 0);
945            selectView(VIEW_RUNNING);
946            return;
947        } else {
948            // Invalid option. Do nothing
949            return;
950        }
951
952        mFilterApps = newOption;
953        selectView(VIEW_LIST);
954        updateStorageUsage();
955        updateOptionsMenu();
956    }
957
958    public void onTabChanged(String tabId) {
959        showCurrentTab();
960    }
961}
962