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