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