ManageApplications.java revision 3b7dbcecf70788df4950340cbf750dbe524dca96
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        }
742        updateOptionsMenu();
743        return true;
744    }
745
746    public void onItemClick(AdapterView<?> parent, View view, int position,
747            long id) {
748        ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position);
749        mCurrentPkgName = entry.info.packageName;
750        startApplicationDetailsActivity();
751    }
752
753    public View createTabContent(String tag) {
754        return mRootView;
755    }
756
757    static final int VIEW_NOTHING = 0;
758    static final int VIEW_LIST = 1;
759    static final int VIEW_RUNNING = 2;
760
761    void updateStorageUsage() {
762        // Fragment view not yet created?
763        if (mRootView == null) return;
764
765        if (mCurView == VIEW_RUNNING) {
766            return;
767        }
768
769        long freeStorage = 0;
770        long appStorage = 0;
771        long totalStorage = 0;
772        CharSequence newLabel = null;
773
774        if (mFilterApps == FILTER_APPS_SDCARD) {
775            if (mLastShowedInternalStorage) {
776                mLastShowedInternalStorage = false;
777            }
778            newLabel = getActivity().getText(R.string.sd_card_storage);
779
780            if (mContainerService != null) {
781                try {
782                    final long[] stats = mContainerService.getFileSystemStats(
783                            Environment.getExternalStorageDirectory().getPath());
784                    totalStorage = stats[0];
785                    freeStorage = stats[1];
786                } catch (RemoteException e) {
787                    Log.w(TAG, "Problem in container service", e);
788                }
789            }
790
791            final int N = mApplicationsAdapter.getCount();
792            for (int i=0; i<N; i++) {
793                ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
794                appStorage += ae.externalCodeSize + ae.externalDataSize;
795            }
796        } else {
797            if (!mLastShowedInternalStorage) {
798                mLastShowedInternalStorage = true;
799            }
800            newLabel = getActivity().getText(R.string.internal_storage);
801
802            if (mContainerService != null) {
803                try {
804                    final long[] stats = mContainerService.getFileSystemStats(
805                            Environment.getDataDirectory().getPath());
806                    totalStorage = stats[0];
807                    freeStorage = stats[1];
808                } catch (RemoteException e) {
809                    Log.w(TAG, "Problem in container service", e);
810                }
811            }
812
813            final boolean emulatedStorage = Environment.isExternalStorageEmulated();
814            final int N = mApplicationsAdapter.getCount();
815            for (int i=0; i<N; i++) {
816                ApplicationsState.AppEntry ae = mApplicationsAdapter.getAppEntry(i);
817                appStorage += ae.codeSize + ae.dataSize;
818                if (emulatedStorage) {
819                    appStorage += ae.externalCodeSize + ae.externalDataSize;
820                }
821            }
822            freeStorage += mApplicationsState.sumCacheSizes();
823        }
824        if (newLabel != null) {
825            mStorageChartLabel.setText(newLabel);
826        }
827        if (totalStorage > 0) {
828            mColorBar.setRatios((totalStorage-freeStorage-appStorage)/(float)totalStorage,
829                    appStorage/(float)totalStorage, freeStorage/(float)totalStorage);
830            long usedStorage = totalStorage - freeStorage;
831            if (mLastUsedStorage != usedStorage) {
832                mLastUsedStorage = usedStorage;
833                String sizeStr = Formatter.formatShortFileSize(getActivity(), usedStorage);
834                mUsedStorageText.setText(getActivity().getResources().getString(
835                        R.string.service_foreground_processes, sizeStr));
836            }
837            if (mLastFreeStorage != freeStorage) {
838                mLastFreeStorage = freeStorage;
839                String sizeStr = Formatter.formatShortFileSize(getActivity(), freeStorage);
840                mFreeStorageText.setText(getActivity().getResources().getString(
841                        R.string.service_background_processes, sizeStr));
842            }
843        } else {
844            mColorBar.setRatios(0, 0, 0);
845            if (mLastUsedStorage != -1) {
846                mLastUsedStorage = -1;
847                mUsedStorageText.setText("");
848            }
849            if (mLastFreeStorage != -1) {
850                mLastFreeStorage = -1;
851                mFreeStorageText.setText("");
852            }
853        }
854    }
855
856    private void selectView(int which) {
857        if (which == VIEW_LIST) {
858            if (mResumedRunning) {
859                mRunningProcessesView.doPause();
860                mResumedRunning = false;
861            }
862            if (mCurView != which) {
863                mRunningProcessesView.setVisibility(View.GONE);
864                mListContainer.setVisibility(View.VISIBLE);
865                mLoadingContainer.setVisibility(View.GONE);
866            }
867            if (mActivityResumed) {
868                mApplicationsAdapter.resume(mFilterApps, mSortOrder);
869            }
870        } else if (which == VIEW_RUNNING) {
871            if (!mCreatedRunning) {
872                mRunningProcessesView.doCreate(null);
873                mRunningProcessesView.mAdapter.setShowBackground(mShowBackground);
874                mCreatedRunning = true;
875            }
876            boolean haveData = true;
877            if (mActivityResumed && !mResumedRunning) {
878                haveData = mRunningProcessesView.doResume(this, mRunningProcessesAvail);
879                mResumedRunning = true;
880            }
881            mApplicationsAdapter.pause();
882            if (mCurView != which) {
883                if (haveData) {
884                    mRunningProcessesView.setVisibility(View.VISIBLE);
885                } else {
886                    mLoadingContainer.setVisibility(View.VISIBLE);
887                }
888                mListContainer.setVisibility(View.GONE);
889            }
890        }
891        mCurView = which;
892        final Activity host = getActivity();
893        if (host != null) {
894            host.invalidateOptionsMenu();
895        }
896    }
897
898    void handleRunningProcessesAvail() {
899        if (mCurView == VIEW_RUNNING) {
900            mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
901                    getActivity(), android.R.anim.fade_out));
902            mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation(
903                    getActivity(), android.R.anim.fade_in));
904            mRunningProcessesView.setVisibility(View.VISIBLE);
905            mLoadingContainer.setVisibility(View.GONE);
906        }
907    }
908
909    public void showCurrentTab() {
910        String tabId = mDefaultTab = mTabHost.getCurrentTabTag();
911        int newOption;
912        if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) {
913            newOption = FILTER_APPS_THIRD_PARTY;
914        } else if (TAB_ALL.equalsIgnoreCase(tabId)) {
915            newOption = FILTER_APPS_ALL;
916        } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) {
917            newOption = FILTER_APPS_SDCARD;
918        } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) {
919            ((InputMethodManager)getActivity().getSystemService(Context.INPUT_METHOD_SERVICE))
920                    .hideSoftInputFromWindow(
921                            getActivity().getWindow().getDecorView().getWindowToken(), 0);
922            selectView(VIEW_RUNNING);
923            return;
924        } else {
925            // Invalid option. Do nothing
926            return;
927        }
928
929        mFilterApps = newOption;
930        selectView(VIEW_LIST);
931        updateStorageUsage();
932        updateOptionsMenu();
933    }
934
935    public void onTabChanged(String tabId) {
936        showCurrentTab();
937    }
938
939    private volatile IMediaContainerService mContainerService;
940
941    private final ServiceConnection mContainerConnection = new ServiceConnection() {
942        @Override
943        public void onServiceConnected(ComponentName name, IBinder service) {
944            mContainerService = IMediaContainerService.Stub.asInterface(service);
945            // Make sure this callback didn't come at an inopportune time.
946            if (getActivity() == null) return;
947            updateStorageUsage();
948        }
949
950        @Override
951        public void onServiceDisconnected(ComponentName name) {
952            mContainerService = null;
953        }
954    };
955}
956