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