ManageApplications.java revision 44178e2801c013e60defb4b5f390d893e7344a71
1/*
2 * Copyright (C) 2006 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.settings.applications;
18
19import com.android.settings.R;
20import com.android.settings.applications.ApplicationsState.AppEntry;
21
22import android.app.TabActivity;
23import android.content.Context;
24import android.content.DialogInterface;
25import android.content.Intent;
26import android.net.Uri;
27import android.os.Bundle;
28import android.provider.Settings;
29import android.util.Log;
30import android.view.LayoutInflater;
31import android.view.Menu;
32import android.view.MenuItem;
33import android.view.View;
34import android.view.ViewGroup;
35import android.view.Window;
36import android.view.animation.AnimationUtils;
37import android.widget.AbsListView;
38import android.widget.AdapterView;
39import android.widget.BaseAdapter;
40import android.widget.Filter;
41import android.widget.Filterable;
42import android.widget.ImageView;
43import android.widget.ListView;
44import android.widget.TabHost;
45import android.widget.TextView;
46import android.widget.AdapterView.OnItemClickListener;
47
48import java.util.ArrayList;
49import java.util.Comparator;
50
51/**
52 * Activity to pick an application that will be used to display installation information and
53 * options to uninstall/delete user data for system applications. This activity
54 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
55 * intent.
56 */
57public class ManageApplications extends TabActivity implements
58        OnItemClickListener, DialogInterface.OnCancelListener,
59        TabHost.TabContentFactory, TabHost.OnTabChangeListener {
60    static final String TAG = "ManageApplications";
61    static final boolean DEBUG = false;
62
63    // attributes used as keys when passing values to InstalledAppDetails activity
64    public static final String APP_CHG = "chg";
65
66    // constant value that can be used to check return code from sub activity.
67    private static final int INSTALLED_APP_DETAILS = 1;
68
69    // sort order that can be changed through the menu can be sorted alphabetically
70    // or size(descending)
71    private static final int MENU_OPTIONS_BASE = 0;
72    // Filter options used for displayed list of applications
73    public static final int FILTER_APPS_ALL = MENU_OPTIONS_BASE + 0;
74    public static final int FILTER_APPS_THIRD_PARTY = MENU_OPTIONS_BASE + 1;
75    public static final int FILTER_APPS_SDCARD = MENU_OPTIONS_BASE + 2;
76
77    public static final int SORT_ORDER_ALPHA = MENU_OPTIONS_BASE + 4;
78    public static final int SORT_ORDER_SIZE = MENU_OPTIONS_BASE + 5;
79    // sort order
80    private int mSortOrder = SORT_ORDER_ALPHA;
81    // Filter value
82    private int mFilterApps = FILTER_APPS_THIRD_PARTY;
83
84    private ApplicationsState mApplicationsState;
85    private ApplicationsAdapter mApplicationsAdapter;
86
87    // Size resource used for packages whose size computation failed for some reason
88    private CharSequence mInvalidSizeStr;
89    private CharSequence mComputingSizeStr;
90
91    // layout inflater object used to inflate views
92    private LayoutInflater mInflater;
93
94    private String mCurrentPkgName;
95
96    private View mLoadingContainer;
97
98    private View mListContainer;
99
100    // ListView used to display list
101    private ListView mListView;
102    // Custom view used to display running processes
103    private RunningProcessesView mRunningProcessesView;
104
105    // These are for keeping track of activity and tab switch state.
106    private int mCurView;
107    private boolean mCreatedRunning;
108
109    private boolean mResumedRunning;
110    private boolean mActivityResumed;
111    private Object mNonConfigInstance;
112
113    final Runnable mRunningProcessesAvail = new Runnable() {
114        public void run() {
115            handleRunningProcessesAvail();
116        }
117    };
118
119    // View Holder used when displaying views
120    static class AppViewHolder {
121        ApplicationsState.AppEntry entry;
122        TextView appName;
123        ImageView appIcon;
124        TextView appSize;
125        TextView disabled;
126
127        void updateSizeText(ManageApplications ma) {
128            if (DEBUG) Log.i(TAG, "updateSizeText of " + entry.label + " " + entry
129                    + ": " + entry.sizeStr);
130            if (entry.sizeStr != null) {
131                appSize.setText(entry.sizeStr);
132            } else if (entry.size == ApplicationsState.SIZE_INVALID) {
133                appSize.setText(ma.mInvalidSizeStr);
134            }
135        }
136    }
137
138    /*
139     * Custom adapter implementation for the ListView
140     * This adapter maintains a map for each displayed application and its properties
141     * An index value on each AppInfo object indicates the correct position or index
142     * in the list. If the list gets updated dynamically when the user is viewing the list of
143     * applications, we need to return the correct index of position. This is done by mapping
144     * the getId methods via the package name into the internal maps and indices.
145     * The order of applications in the list is mirrored in mAppLocalList
146     */
147    class ApplicationsAdapter extends BaseAdapter implements Filterable,
148            ApplicationsState.Callbacks, AbsListView.RecyclerListener {
149        private final ApplicationsState mState;
150        private final ArrayList<View> mActive = new ArrayList<View>();
151        private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
152        private ArrayList<ApplicationsState.AppEntry> mEntries;
153        private boolean mResumed;
154        private int mLastFilterMode=-1, mLastSortMode=-1;
155        CharSequence mCurFilterPrefix;
156
157        private Filter mFilter = new Filter() {
158            @Override
159            protected FilterResults performFiltering(CharSequence constraint) {
160                ArrayList<ApplicationsState.AppEntry> entries
161                        = applyPrefixFilter(constraint, mBaseEntries);
162                FilterResults fr = new FilterResults();
163                fr.values = entries;
164                fr.count = entries.size();
165                return fr;
166            }
167
168            @Override
169            protected void publishResults(CharSequence constraint, FilterResults results) {
170                mCurFilterPrefix = constraint;
171                mEntries = (ArrayList<ApplicationsState.AppEntry>)results.values;
172                notifyDataSetChanged();
173            }
174        };
175
176        public ApplicationsAdapter(ApplicationsState state) {
177            mState = state;
178        }
179
180        public void resume(int filter, int sort) {
181            if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
182            if (!mResumed) {
183                mResumed = true;
184                mState.resume(this);
185                mLastFilterMode = filter;
186                mLastSortMode = sort;
187                rebuild();
188            } else {
189                rebuild(filter, sort);
190            }
191        }
192
193        public void pause() {
194            if (mResumed) {
195                mResumed = false;
196                mState.pause();
197            }
198        }
199
200        public void rebuild(int filter, int sort) {
201            if (filter == mLastFilterMode && sort == mLastSortMode) {
202                return;
203            }
204            mLastFilterMode = filter;
205            mLastSortMode = sort;
206            rebuild();
207        }
208
209        public void rebuild() {
210            if (DEBUG) Log.i(TAG, "Rebuilding app list...");
211            ApplicationsState.AppFilter filterObj;
212            Comparator<AppEntry> comparatorObj;
213            switch (mLastFilterMode) {
214                case FILTER_APPS_THIRD_PARTY:
215                    filterObj = ApplicationsState.THIRD_PARTY_FILTER;
216                    break;
217                case FILTER_APPS_SDCARD:
218                    filterObj = ApplicationsState.ON_SD_CARD_FILTER;
219                    break;
220                default:
221                    filterObj = null;
222                    break;
223            }
224            switch (mLastSortMode) {
225                case SORT_ORDER_SIZE:
226                    comparatorObj = ApplicationsState.SIZE_COMPARATOR;
227                    break;
228                default:
229                    comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
230                    break;
231            }
232            mBaseEntries = mState.rebuild(filterObj, comparatorObj);
233            mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
234            notifyDataSetChanged();
235        }
236
237        ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
238                ArrayList<ApplicationsState.AppEntry> origEntries) {
239            if (prefix == null || prefix.length() == 0) {
240                return origEntries;
241            } else {
242                String prefixStr = ApplicationsState.normalize(prefix.toString());
243                final String spacePrefixStr = " " + prefixStr;
244                ArrayList<ApplicationsState.AppEntry> newEntries
245                        = new ArrayList<ApplicationsState.AppEntry>();
246                for (int i=0; i<origEntries.size(); i++) {
247                    ApplicationsState.AppEntry entry = origEntries.get(i);
248                    String nlabel = entry.getNormalizedLabel();
249                    if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
250                        newEntries.add(entry);
251                    }
252                }
253                return newEntries;
254            }
255        }
256
257        @Override
258        public void onRunningStateChanged(boolean running) {
259            setProgressBarIndeterminateVisibility(running);
260        }
261
262        @Override
263        public void onPackageListChanged() {
264            rebuild();
265        }
266
267        @Override
268        public void onPackageIconChanged() {
269            // We ensure icons are loaded when their item is displayed, so
270            // don't care about icons loaded in the background.
271        }
272
273        @Override
274        public void onPackageSizeChanged(String packageName) {
275            for (int i=0; i<mActive.size(); i++) {
276                AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
277                if (holder.entry.info.packageName.equals(packageName)) {
278                    synchronized (holder.entry) {
279                        holder.updateSizeText(ManageApplications.this);
280                    }
281                    if (holder.entry.info.packageName.equals(mCurrentPkgName)
282                            && mLastSortMode == SORT_ORDER_SIZE) {
283                        // We got the size information for the last app the
284                        // user viewed, and are sorting by size...  they may
285                        // have cleared data, so we immediately want to resort
286                        // the list with the new size to reflect it to the user.
287                        rebuild();
288                    }
289                    return;
290                }
291            }
292        }
293
294        @Override
295        public void onAllSizesComputed() {
296            if (mLastSortMode == SORT_ORDER_SIZE) {
297                rebuild();
298            }
299        }
300
301        public int getCount() {
302            return mEntries != null ? mEntries.size() : 0;
303        }
304
305        public Object getItem(int position) {
306            return mEntries.get(position);
307        }
308
309        public ApplicationsState.AppEntry getAppEntry(int position) {
310            return mEntries.get(position);
311        }
312
313        public long getItemId(int position) {
314            return mEntries.get(position).id;
315        }
316
317        public View getView(int position, View convertView, ViewGroup parent) {
318            // A ViewHolder keeps references to children views to avoid unnecessary calls
319            // to findViewById() on each row.
320            AppViewHolder holder;
321
322            // When convertView is not null, we can reuse it directly, there is no need
323            // to reinflate it. We only inflate a new View when the convertView supplied
324            // by ListView is null.
325            if (convertView == null) {
326                convertView = mInflater.inflate(R.layout.manage_applications_item, null);
327
328                // Creates a ViewHolder and store references to the two children views
329                // we want to bind data to.
330                holder = new AppViewHolder();
331                holder.appName = (TextView) convertView.findViewById(R.id.app_name);
332                holder.appIcon = (ImageView) convertView.findViewById(R.id.app_icon);
333                holder.appSize = (TextView) convertView.findViewById(R.id.app_size);
334                holder.disabled = (TextView) convertView.findViewById(R.id.app_disabled);
335                convertView.setTag(holder);
336            } else {
337                // Get the ViewHolder back to get fast access to the TextView
338                // and the ImageView.
339                holder = (AppViewHolder) convertView.getTag();
340            }
341
342            // Bind the data efficiently with the holder
343            ApplicationsState.AppEntry entry = mEntries.get(position);
344            synchronized (entry) {
345                holder.entry = entry;
346                if (entry.label != null) {
347                    holder.appName.setText(entry.label);
348                    holder.appName.setTextColor(getResources().getColorStateList(
349                            entry.info.enabled ? android.R.color.primary_text_dark
350                                    : android.R.color.secondary_text_dark));
351                }
352                mState.ensureIcon(entry);
353                if (entry.icon != null) {
354                    holder.appIcon.setImageDrawable(entry.icon);
355                }
356                holder.updateSizeText(ManageApplications.this);
357                if (InstalledAppDetails.SUPPORT_DISABLE_APPS) {
358                    holder.disabled.setVisibility(entry.info.enabled ? View.GONE : View.VISIBLE);
359                } else {
360                    holder.disabled.setVisibility(View.GONE);
361                }
362            }
363            mActive.remove(convertView);
364            mActive.add(convertView);
365            return convertView;
366        }
367
368        @Override
369        public Filter getFilter() {
370            return mFilter;
371        }
372
373        @Override
374        public void onMovedToScrapHeap(View view) {
375            mActive.remove(view);
376        }
377    }
378
379    static final String TAB_DOWNLOADED = "Downloaded";
380    static final String TAB_RUNNING = "Running";
381    static final String TAB_ALL = "All";
382    static final String TAB_SDCARD = "OnSdCard";
383    private View mRootView;
384
385    @Override
386    protected void onCreate(Bundle savedInstanceState) {
387        super.onCreate(savedInstanceState);
388        mApplicationsState = ApplicationsState.getInstance(getApplication());
389        mApplicationsAdapter = new ApplicationsAdapter(mApplicationsState);
390        Intent intent = getIntent();
391        String action = intent.getAction();
392        String defaultTabTag = TAB_DOWNLOADED;
393        if (intent.getComponent().getClassName().equals(
394                "com.android.settings.RunningServices")) {
395            defaultTabTag = TAB_RUNNING;
396        } else if (intent.getComponent().getClassName().equals(
397                "com.android.settings.applications.StorageUse")
398                || action.equals(Intent.ACTION_MANAGE_PACKAGE_STORAGE)) {
399            mSortOrder = SORT_ORDER_SIZE;
400            mFilterApps = FILTER_APPS_ALL;
401            defaultTabTag = TAB_ALL;
402        }
403
404        if (savedInstanceState != null) {
405            mSortOrder = savedInstanceState.getInt("sortOrder", mSortOrder);
406            mFilterApps = savedInstanceState.getInt("filterApps", mFilterApps);
407            String tmp = savedInstanceState.getString("defaultTabTag");
408            if (tmp != null) defaultTabTag = tmp;
409        }
410
411        mNonConfigInstance = getLastNonConfigurationInstance();
412
413        // initialize some window features
414        requestWindowFeature(Window.FEATURE_RIGHT_ICON);
415        requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
416        mInvalidSizeStr = getText(R.string.invalid_size_value);
417        mComputingSizeStr = getText(R.string.computing_size);
418        // initialize the inflater
419        mInflater = (LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE);
420        mRootView = mInflater.inflate(R.layout.manage_applications, null);
421        mLoadingContainer = mRootView.findViewById(R.id.loading_container);
422        mListContainer = mRootView.findViewById(R.id.list_container);
423        // Create adapter and list view here
424        ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
425        View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
426        if (emptyView != null) {
427            lv.setEmptyView(emptyView);
428        }
429        lv.setOnItemClickListener(this);
430        lv.setSaveEnabled(true);
431        lv.setItemsCanFocus(true);
432        lv.setOnItemClickListener(this);
433        lv.setTextFilterEnabled(true);
434        mListView = lv;
435        lv.setRecyclerListener(mApplicationsAdapter);
436        mListView.setAdapter(mApplicationsAdapter);
437        mRunningProcessesView = (RunningProcessesView)mRootView.findViewById(
438                R.id.running_processes);
439
440        final TabHost tabHost = getTabHost();
441        tabHost.addTab(tabHost.newTabSpec(TAB_DOWNLOADED)
442                .setIndicator(getString(R.string.filter_apps_third_party),
443                        getResources().getDrawable(R.drawable.ic_tab_download))
444                .setContent(this));
445        tabHost.addTab(tabHost.newTabSpec(TAB_ALL)
446                .setIndicator(getString(R.string.filter_apps_all),
447                        getResources().getDrawable(R.drawable.ic_tab_all))
448                .setContent(this));
449        tabHost.addTab(tabHost.newTabSpec(TAB_SDCARD)
450                .setIndicator(getString(R.string.filter_apps_onsdcard),
451                        getResources().getDrawable(R.drawable.ic_tab_sdcard))
452                .setContent(this));
453        tabHost.addTab(tabHost.newTabSpec(TAB_RUNNING)
454                .setIndicator(getString(R.string.filter_apps_running),
455                        getResources().getDrawable(R.drawable.ic_tab_running))
456                .setContent(this));
457        tabHost.setCurrentTabByTag(defaultTabTag);
458        tabHost.setOnTabChangedListener(this);
459    }
460
461    @Override
462    public void onStart() {
463        super.onStart();
464    }
465
466    @Override
467    protected void onResume() {
468        super.onResume();
469        mActivityResumed = true;
470        showCurrentTab();
471    }
472
473    @Override
474    protected void onSaveInstanceState(Bundle outState) {
475        super.onSaveInstanceState(outState);
476        outState.putInt("sortOrder", mSortOrder);
477        outState.putInt("filterApps", mFilterApps);
478        outState.putString("defautTabTag", getTabHost().getCurrentTabTag());
479    }
480
481    @Override
482    public Object onRetainNonConfigurationInstance() {
483        return mRunningProcessesView.doRetainNonConfigurationInstance();
484    }
485
486    @Override
487    protected void onPause() {
488        super.onPause();
489        mActivityResumed = false;
490        mApplicationsAdapter.pause();
491        if (mResumedRunning) {
492            mRunningProcessesView.doPause();
493            mResumedRunning = false;
494        }
495    }
496
497    @Override
498    protected void onActivityResult(int requestCode, int resultCode,
499            Intent data) {
500        if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
501            mApplicationsState.requestSize(mCurrentPkgName);
502        }
503    }
504
505    // utility method used to start sub activity
506    private void startApplicationDetailsActivity() {
507        // Create intent to start new activity
508        Intent intent = new Intent(Settings.ACTION_APPLICATION_DETAILS_SETTINGS,
509                Uri.fromParts("package", mCurrentPkgName, null));
510        // start new activity to display extended information
511        startActivityForResult(intent, INSTALLED_APP_DETAILS);
512    }
513
514    @Override
515    public boolean onCreateOptionsMenu(Menu menu) {
516        menu.add(0, SORT_ORDER_ALPHA, 1, R.string.sort_order_alpha)
517                .setIcon(android.R.drawable.ic_menu_sort_alphabetically);
518        menu.add(0, SORT_ORDER_SIZE, 2, R.string.sort_order_size)
519                .setIcon(android.R.drawable.ic_menu_sort_by_size);
520        return true;
521    }
522
523    @Override
524    public boolean onPrepareOptionsMenu(Menu menu) {
525        menu.findItem(SORT_ORDER_ALPHA).setVisible(mSortOrder != SORT_ORDER_ALPHA);
526        menu.findItem(SORT_ORDER_SIZE).setVisible(mSortOrder != SORT_ORDER_SIZE);
527        return true;
528    }
529
530    @Override
531    public boolean onOptionsItemSelected(MenuItem item) {
532        int menuId = item.getItemId();
533        if ((menuId == SORT_ORDER_ALPHA) || (menuId == SORT_ORDER_SIZE)) {
534            mSortOrder = menuId;
535            mApplicationsAdapter.rebuild(mFilterApps, mSortOrder);
536        }
537        return true;
538    }
539
540    public void onItemClick(AdapterView<?> parent, View view, int position,
541            long id) {
542        ApplicationsState.AppEntry entry = mApplicationsAdapter.getAppEntry(position);
543        mCurrentPkgName = entry.info.packageName;
544        startApplicationDetailsActivity();
545    }
546
547    // Finish the activity if the user presses the back button to cancel the activity
548    public void onCancel(DialogInterface dialog) {
549        finish();
550    }
551
552    public View createTabContent(String tag) {
553        return mRootView;
554    }
555
556    static final int VIEW_NOTHING = 0;
557    static final int VIEW_LIST = 1;
558    static final int VIEW_RUNNING = 2;
559
560    private void selectView(int which) {
561        if (which == VIEW_LIST) {
562            if (mResumedRunning) {
563                mRunningProcessesView.doPause();
564                mResumedRunning = false;
565            }
566            if (mCurView != which) {
567                mRunningProcessesView.setVisibility(View.GONE);
568                mListContainer.setVisibility(View.VISIBLE);
569            }
570            if (mActivityResumed) {
571                mApplicationsAdapter.resume(mFilterApps, mSortOrder);
572            }
573        } else if (which == VIEW_RUNNING) {
574            if (!mCreatedRunning) {
575                mRunningProcessesView.doCreate(null, mNonConfigInstance);
576                mCreatedRunning = true;
577            }
578            boolean haveData = true;
579            if (mActivityResumed && !mResumedRunning) {
580                haveData = mRunningProcessesView.doResume(mRunningProcessesAvail);
581                mResumedRunning = true;
582            }
583            mApplicationsAdapter.pause();
584            if (mCurView != which) {
585                if (haveData) {
586                    mRunningProcessesView.setVisibility(View.VISIBLE);
587                } else {
588                    mLoadingContainer.setVisibility(View.VISIBLE);
589                }
590                mListContainer.setVisibility(View.GONE);
591            }
592        }
593        mCurView = which;
594    }
595
596    void handleRunningProcessesAvail() {
597        if (mCurView == VIEW_RUNNING) {
598            mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
599                    this, android.R.anim.fade_out));
600            mRunningProcessesView.startAnimation(AnimationUtils.loadAnimation(
601                    this, android.R.anim.fade_in));
602            mRunningProcessesView.setVisibility(View.VISIBLE);
603            mLoadingContainer.setVisibility(View.GONE);
604        }
605    }
606
607    public void showCurrentTab() {
608        String tabId = getTabHost().getCurrentTabTag();
609        int newOption;
610        if (TAB_DOWNLOADED.equalsIgnoreCase(tabId)) {
611            newOption = FILTER_APPS_THIRD_PARTY;
612        } else if (TAB_ALL.equalsIgnoreCase(tabId)) {
613            newOption = FILTER_APPS_ALL;
614        } else if (TAB_SDCARD.equalsIgnoreCase(tabId)) {
615            newOption = FILTER_APPS_SDCARD;
616        } else if (TAB_RUNNING.equalsIgnoreCase(tabId)) {
617            selectView(VIEW_RUNNING);
618            return;
619        } else {
620            // Invalid option. Do nothing
621            return;
622        }
623
624        mFilterApps = newOption;
625        selectView(VIEW_LIST);
626    }
627
628    public void onTabChanged(String tabId) {
629        showCurrentTab();
630    }
631}
632