ManageApplications.java revision 25f9e326ab65e5298737721e959b22aea68148f1
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 android.app.Activity;
20import android.content.Context;
21import android.content.Intent;
22import android.content.pm.ApplicationInfo;
23import android.content.pm.IntentFilterVerificationInfo;
24import android.content.pm.PackageManager;
25import android.os.Bundle;
26import android.os.Environment;
27import android.os.UserHandle;
28import android.os.UserManager;
29import android.preference.PreferenceFrameLayout;
30import android.provider.Settings;
31import android.util.ArraySet;
32import android.util.Log;
33import android.view.LayoutInflater;
34import android.view.Menu;
35import android.view.MenuInflater;
36import android.view.MenuItem;
37import android.view.View;
38import android.view.ViewGroup;
39import android.view.animation.AnimationUtils;
40import android.widget.AbsListView;
41import android.widget.AdapterView;
42import android.widget.AdapterView.OnItemClickListener;
43import android.widget.AdapterView.OnItemSelectedListener;
44import android.widget.ArrayAdapter;
45import android.widget.BaseAdapter;
46import android.widget.Filter;
47import android.widget.Filterable;
48import android.widget.ListView;
49import android.widget.Spinner;
50
51import com.android.internal.logging.MetricsLogger;
52import com.android.settings.AppHeader;
53import com.android.settings.HelpUtils;
54import com.android.settings.InstrumentedFragment;
55import com.android.settings.R;
56import com.android.settings.Settings.AllApplicationsActivity;
57import com.android.settings.Settings.DomainsURLsAppListActivity;
58import com.android.settings.Settings.NotificationAppListActivity;
59import com.android.settings.Settings.StorageUseActivity;
60import com.android.settings.Settings.UsageAccessSettingsActivity;
61import com.android.settings.SettingsActivity;
62import com.android.settings.Utils;
63import com.android.settings.applications.AppStateUsageBridge.UsageState;
64import com.android.settings.applications.ApplicationsState.AppEntry;
65import com.android.settings.applications.ApplicationsState.AppFilter;
66import com.android.settings.applications.ApplicationsState.CompoundFilter;
67import com.android.settings.applications.ApplicationsState.VolumeFilter;
68import com.android.settings.notification.NotificationBackend;
69import com.android.settings.notification.NotificationBackend.AppRow;
70
71import java.util.ArrayList;
72import java.util.Collections;
73import java.util.Comparator;
74import java.util.List;
75
76/**
77 * Activity to pick an application that will be used to display installation information and
78 * options to uninstall/delete user data for system applications. This activity
79 * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
80 * intent.
81 */
82public class ManageApplications extends InstrumentedFragment
83        implements OnItemClickListener, OnItemSelectedListener {
84
85    static final String TAG = "ManageApplications";
86    static final boolean DEBUG = Log.isLoggable(TAG, Log.DEBUG);
87
88    // Intent extras.
89    public static final String EXTRA_CLASSNAME = "classname";
90    // Used for storage only.
91    public static final String EXTRA_VOLUME_UUID = "volumeUuid";
92    public static final String EXTRA_VOLUME_NAME = "volumeName";
93
94    private static final String EXTRA_SORT_ORDER = "sortOrder";
95
96    // attributes used as keys when passing values to InstalledAppDetails activity
97    public static final String APP_CHG = "chg";
98
99    // constant value that can be used to check return code from sub activity.
100    private static final int INSTALLED_APP_DETAILS = 1;
101    private static final int ADVANCED_SETTINGS = 2;
102
103    public static final int SIZE_TOTAL = 0;
104    public static final int SIZE_INTERNAL = 1;
105    public static final int SIZE_EXTERNAL = 2;
106
107    // Filter options used for displayed list of applications
108    // The order which they appear is the order they will show when spinner is present.
109    public static final int FILTER_APPS_ALL                     = 0;
110    public static final int FILTER_APPS_ENABLED                 = 1;
111    public static final int FILTER_APPS_DISABLED                = 2;
112    public static final int FILTER_APPS_BLOCKED                 = 3;
113    public static final int FILTER_APPS_PRIORITY                = 4;
114    public static final int FILTER_APPS_NO_PEEKING              = 5;
115    public static final int FILTER_APPS_SENSITIVE               = 6;
116    public static final int FILTER_APPS_PERSONAL                = 7;
117    public static final int FILTER_APPS_WORK                    = 8;
118    public static final int FILTER_APPS_WITH_DOMAIN_URLS        = 9;
119    public static final int FILTER_APPS_USAGE_ACCESS            = 10;
120
121    // This is the string labels for the filter modes above, the order must be kept in sync.
122    public static final int[] FILTER_LABELS = new int[] {
123        R.string.filter_all_apps,      // All apps
124        R.string.filter_enabled_apps,  // Enabled
125        R.string.filter_apps_disabled, // Disabled
126        R.string.filter_notif_blocked_apps,   // Blocked Notifications
127        R.string.filter_notif_priority_apps,  // Priority Notifications
128        R.string.filter_notif_no_peeking,     // No peeking Notifications
129        R.string.filter_notif_sensitive_apps, // Sensitive Notifications
130        R.string.filter_personal_apps, // Personal
131        R.string.filter_work_apps,     // Work
132        R.string.filter_with_domain_urls_apps,     // Domain URLs
133        R.string.filter_all_apps,      // Usage access screen, never displayed
134    };
135    // This is the actual mapping to filters from FILTER_ constants above, the order must
136    // be kept in sync.
137    public static final AppFilter[] FILTERS = new AppFilter[] {
138        ApplicationsState.FILTER_EVERYTHING,  // All apps
139        ApplicationsState.FILTER_ALL_ENABLED, // Enabled
140        ApplicationsState.FILTER_DISABLED,    // Disabled
141        AppStateNotificationBridge.FILTER_APP_NOTIFICATION_BLOCKED,   // Blocked Notifications
142        AppStateNotificationBridge.FILTER_APP_NOTIFICATION_PRIORITY,  // Priority Notifications
143        AppStateNotificationBridge.FILTER_APP_NOTIFICATION_NO_PEEK,   // No peeking Notifications
144        AppStateNotificationBridge.FILTER_APP_NOTIFICATION_SENSITIVE, // Sensitive Notifications
145        ApplicationsState.FILTER_PERSONAL,    // Personal
146        ApplicationsState.FILTER_WORK,        // Work
147        ApplicationsState.FILTER_WITH_DOMAIN_URLS,   // Apps with Domain URLs
148        AppStateUsageBridge.FILTER_APP_USAGE, // Apps with Domain URLs
149    };
150
151    // sort order
152    private int mSortOrder = R.id.sort_order_alpha;
153
154    // whether showing system apps.
155    private boolean mShowSystem;
156    private boolean mHasDisabledApps;
157
158    private ApplicationsState mApplicationsState;
159
160    public int mListType;
161    public int mFilter;
162
163    public ApplicationsAdapter mApplications;
164
165    private View mLoadingContainer;
166
167    private View mListContainer;
168
169    // ListView used to display list
170    private ListView mListView;
171
172    // Size resource used for packages whose size computation failed for some reason
173    CharSequence mInvalidSizeStr;
174
175    // layout inflater object used to inflate views
176    private LayoutInflater mInflater;
177
178    private String mCurrentPkgName;
179    private int mCurrentUid;
180
181    private Menu mOptionsMenu;
182
183    public static final int LIST_TYPE_MAIN = 0;
184    public static final int LIST_TYPE_NOTIFICATION = 1;
185    public static final int LIST_TYPE_DOMAINS_URLS = 2;
186    public static final int LIST_TYPE_STORAGE = 3;
187    public static final int LIST_TYPE_USAGE_ACCESS = 4;
188
189    private View mRootView;
190
191    private View mSpinnerHeader;
192    private Spinner mFilterSpinner;
193    private FilterSpinnerAdapter mFilterAdapter;
194    private NotificationBackend mNotifBackend;
195    private ResetAppsHelper mResetAppsHelper;
196    private String mVolumeUuid;
197    private String mVolumeName;
198
199    @Override
200    public void onCreate(Bundle savedInstanceState) {
201        super.onCreate(savedInstanceState);
202        setHasOptionsMenu(true);
203        mApplicationsState = ApplicationsState.getInstance(getActivity().getApplication());
204
205        Intent intent = getActivity().getIntent();
206        Bundle args = getArguments();
207        String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
208        if (className == null) {
209            className = intent.getComponent().getClassName();
210        }
211        if (className.equals(AllApplicationsActivity.class.getName())) {
212            mShowSystem = true;
213        } else if (className.equals(NotificationAppListActivity.class.getName())) {
214            mListType = LIST_TYPE_NOTIFICATION;
215            mNotifBackend = new NotificationBackend();
216        } else if (className.equals(DomainsURLsAppListActivity.class.getName())) {
217            mListType = LIST_TYPE_DOMAINS_URLS;
218        } else if (className.equals(StorageUseActivity.class.getName())) {
219            if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
220                mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
221                mVolumeName = args.getString(EXTRA_VOLUME_NAME);
222                mListType = LIST_TYPE_STORAGE;
223            } else {
224                // No volume selected, display a normal list, sorted by size.
225                mListType = LIST_TYPE_MAIN;
226                mSortOrder = R.id.sort_order_size;
227            }
228        } else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
229            mListType = LIST_TYPE_USAGE_ACCESS;
230            getActivity().getActionBar().setTitle(R.string.usage_access_title);
231        } else {
232            mListType = LIST_TYPE_MAIN;
233        }
234        mFilter = getDefaultFilter();
235
236        if (savedInstanceState != null) {
237            mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
238        }
239
240        mInvalidSizeStr = getActivity().getText(R.string.invalid_size_value);
241
242        mResetAppsHelper = new ResetAppsHelper(getActivity());
243    }
244
245
246    @Override
247    public View onCreateView(LayoutInflater inflater, ViewGroup container,
248            Bundle savedInstanceState) {
249        // initialize the inflater
250        mInflater = inflater;
251
252        mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
253        mLoadingContainer = mRootView.findViewById(R.id.loading_container);
254        mLoadingContainer.setVisibility(View.VISIBLE);
255        mListContainer = mRootView.findViewById(R.id.list_container);
256        if (mListContainer != null) {
257            // Create adapter and list view here
258            View emptyView = mListContainer.findViewById(com.android.internal.R.id.empty);
259            ListView lv = (ListView) mListContainer.findViewById(android.R.id.list);
260            if (emptyView != null) {
261                lv.setEmptyView(emptyView);
262            }
263            lv.setOnItemClickListener(this);
264            lv.setSaveEnabled(true);
265            lv.setItemsCanFocus(true);
266            lv.setTextFilterEnabled(true);
267            mListView = lv;
268            mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter);
269            mListView.setAdapter(mApplications);
270            mListView.setRecyclerListener(mApplications);
271
272            Utils.prepareCustomPreferencesList(container, mRootView, mListView, false);
273        }
274
275        // We have to do this now because PreferenceFrameLayout looks at it
276        // only when the view is added.
277        if (container instanceof PreferenceFrameLayout) {
278            ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true;
279        }
280
281        createHeader();
282
283        mResetAppsHelper.onRestoreInstanceState(savedInstanceState);
284
285        return mRootView;
286    }
287
288    private void createHeader() {
289        Activity activity = getActivity();
290        View content = activity.findViewById(R.id.main_content);
291        ViewGroup contentParent = (ViewGroup) content.getParent();
292        mSpinnerHeader = (ViewGroup) activity.getLayoutInflater()
293                .inflate(R.layout.apps_filter_spinner, contentParent, false);
294        mFilterSpinner = (Spinner) mSpinnerHeader.findViewById(R.id.filter_spinner);
295        mFilterAdapter = new FilterSpinnerAdapter(this);
296        mFilterSpinner.setAdapter(mFilterAdapter);
297        mFilterSpinner.setOnItemSelectedListener(this);
298        contentParent.addView(mSpinnerHeader, 0);
299
300        mFilterAdapter.enableFilter(getDefaultFilter());
301        if (mListType == LIST_TYPE_MAIN || mListType == LIST_TYPE_NOTIFICATION) {
302            if (UserManager.get(getActivity()).getUserProfiles().size() > 1) {
303                mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
304                mFilterAdapter.enableFilter(FILTER_APPS_WORK);
305            }
306        }
307        if (mListType == LIST_TYPE_NOTIFICATION) {
308            mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
309            mFilterAdapter.enableFilter(FILTER_APPS_PRIORITY);
310            mFilterAdapter.enableFilter(FILTER_APPS_SENSITIVE);
311            mFilterAdapter.enableFilter(FILTER_APPS_NO_PEEKING);
312        }
313        if (mListType == LIST_TYPE_STORAGE) {
314            mApplications.setOverrideFilter(new VolumeFilter(mVolumeUuid));
315        }
316    }
317
318    @Override
319    public void onViewCreated(View view, Bundle savedInstanceState) {
320        super.onViewCreated(view, savedInstanceState);
321        if (mListType == LIST_TYPE_STORAGE) {
322            AppHeader.createAppHeader(getActivity(), null, mVolumeName, null);
323        }
324    }
325
326    private int getDefaultFilter() {
327        switch (mListType) {
328            case LIST_TYPE_MAIN:
329                return FILTER_APPS_ALL;
330            case LIST_TYPE_DOMAINS_URLS:
331                return FILTER_APPS_WITH_DOMAIN_URLS;
332            case LIST_TYPE_USAGE_ACCESS:
333                return FILTER_APPS_USAGE_ACCESS;
334            default:
335                return FILTER_APPS_ALL;
336        }
337    }
338
339    @Override
340    protected int getMetricsCategory() {
341        switch (mListType) {
342            case LIST_TYPE_MAIN:
343                return MetricsLogger.MANAGE_APPLICATIONS;
344            case LIST_TYPE_NOTIFICATION:
345                return MetricsLogger.MANAGE_APPLICATIONS_NOTIFICATIONS;
346            case LIST_TYPE_DOMAINS_URLS:
347                return MetricsLogger.MANAGE_DOMAIN_URLS;
348            case LIST_TYPE_STORAGE:
349                return InstrumentedFragment.VIEW_CATEGORY_STORAGE_APPS;
350            case LIST_TYPE_USAGE_ACCESS:
351                return MetricsLogger.USAGE_ACCESS;
352            default:
353                return MetricsLogger.VIEW_UNKNOWN;
354        }
355    }
356
357    @Override
358    public void onResume() {
359        super.onResume();
360        updateView();
361        updateOptionsMenu();
362        if (mApplications != null) {
363            mApplications.resume(mSortOrder);
364            mApplications.updateLoading();
365        }
366    }
367
368    @Override
369    public void onSaveInstanceState(Bundle outState) {
370        super.onSaveInstanceState(outState);
371        mResetAppsHelper.onSaveInstanceState(outState);
372        outState.putInt(EXTRA_SORT_ORDER, mSortOrder);
373    }
374
375    @Override
376    public void onPause() {
377        super.onPause();
378        if (mApplications != null) {
379            mApplications.pause();
380        }
381    }
382
383    @Override
384    public void onStop() {
385        super.onStop();
386        mResetAppsHelper.stop();
387    }
388
389    @Override
390    public void onDestroyView() {
391        super.onDestroyView();
392
393        if (mApplications != null) {
394            mApplications.release();
395        }
396        mRootView = null;
397    }
398
399    @Override
400    public void onActivityResult(int requestCode, int resultCode, Intent data) {
401        if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
402            if (mListType == LIST_TYPE_NOTIFICATION) {
403                mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
404            } else {
405                mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
406            }
407        }
408    }
409
410    // utility method used to start sub activity
411    private void startApplicationDetailsActivity() {
412        Activity activity = getActivity();
413        switch (mListType) {
414            case LIST_TYPE_NOTIFICATION:
415                activity.startActivity(new Intent(Settings.ACTION_APP_NOTIFICATION_SETTINGS)
416                        .addFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP)
417                        .putExtra(Settings.EXTRA_APP_PACKAGE, mCurrentPkgName)
418                        .putExtra(Settings.EXTRA_APP_UID, mCurrentUid));
419                break;
420            case LIST_TYPE_DOMAINS_URLS:
421                startAppInfoFragment(AppLaunchSettings.class, R.string.auto_launch_label);
422                break;
423            case LIST_TYPE_USAGE_ACCESS:
424                startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
425                break;
426            case LIST_TYPE_STORAGE:
427                startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
428                break;
429            // TODO: Figure out if there is a way where we can spin up the profile's settings
430            // process ahead of time, to avoid a long load of data when user clicks on a managed app.
431            // Maybe when they load the list of apps that contains managed profile apps.
432            default:
433                startAppInfoFragment(InstalledAppDetails.class, R.string.application_info_label);
434                break;
435        }
436    }
437
438    private void startAppInfoFragment(Class<? extends AppInfoBase> fragment, int titleRes) {
439        Bundle args = new Bundle();
440        args.putString(AppInfoBase.ARG_PACKAGE_NAME, mCurrentPkgName);
441
442        Intent intent = Utils.onBuildStartFragmentIntent(getActivity(), fragment.getName(), args,
443                null, titleRes, null, false);
444        getActivity().startActivityForResultAsUser(intent, INSTALLED_APP_DETAILS,
445                new UserHandle(UserHandle.getUserId(mCurrentUid)));
446    }
447
448    @Override
449    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
450        if (mListType == LIST_TYPE_DOMAINS_URLS) {
451            return;
452        }
453        HelpUtils.prepareHelpMenuItem(getActivity(), menu, mListType == LIST_TYPE_MAIN
454                ? R.string.help_uri_apps : R.string.help_uri_notifications);
455        mOptionsMenu = menu;
456        inflater.inflate(R.menu.manage_apps, menu);
457        updateOptionsMenu();
458    }
459
460    @Override
461    public void onPrepareOptionsMenu(Menu menu) {
462        updateOptionsMenu();
463    }
464
465    @Override
466    public void onDestroyOptionsMenu() {
467        mOptionsMenu = null;
468    }
469
470    void updateOptionsMenu() {
471        if (mOptionsMenu == null) {
472            return;
473        }
474        mOptionsMenu.findItem(R.id.advanced).setVisible(mListType == LIST_TYPE_MAIN);
475
476        mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE
477                && mSortOrder != R.id.sort_order_alpha);
478        mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE
479                && mSortOrder != R.id.sort_order_size);
480
481        mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem);
482        mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem);
483    }
484
485    @Override
486    public boolean onOptionsItemSelected(MenuItem item) {
487        int menuId = item.getItemId();
488        switch(item.getItemId()) {
489            case R.id.sort_order_alpha:
490            case R.id.sort_order_size:
491                mSortOrder = menuId;
492                if (mApplications != null) {
493                    mApplications.rebuild(mSortOrder);
494                }
495                break;
496            case R.id.show_system:
497            case R.id.hide_system:
498                mShowSystem = !mShowSystem;
499                mApplications.rebuild(false);
500                break;
501            case R.id.reset_app_preferences:
502                mResetAppsHelper.buildResetDialog();
503                return true;
504            case R.id.advanced:
505                ((SettingsActivity) getActivity()).startPreferencePanel(
506                        AdvancedAppSettings.class.getName(), null, R.string.advanced_apps,
507                        null, this, ADVANCED_SETTINGS);
508                return true;
509            default:
510                // Handle the home button
511                return false;
512        }
513        updateOptionsMenu();
514        return true;
515    }
516
517    @Override
518    public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
519        if (mApplications != null && mApplications.getCount() > position) {
520            ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
521            mCurrentPkgName = entry.info.packageName;
522            mCurrentUid = entry.info.uid;
523            startApplicationDetailsActivity();
524        }
525    }
526
527    @Override
528    public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
529        mFilter = mFilterAdapter.getFilter(position);
530        mApplications.setFilter(mFilter);
531        if (DEBUG) Log.d(TAG, "Selecting filter " + mFilter);
532    }
533
534    @Override
535    public void onNothingSelected(AdapterView<?> parent) {
536    }
537
538    public void updateView() {
539        updateOptionsMenu();
540        final Activity host = getActivity();
541        if (host != null) {
542            host.invalidateOptionsMenu();
543        }
544    }
545
546    public void setHasDisabled(boolean hasDisabledApps) {
547        mHasDisabledApps = hasDisabledApps;
548        mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps);
549        mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps);
550    }
551
552    static class FilterSpinnerAdapter extends ArrayAdapter<CharSequence> {
553
554        private final ManageApplications mManageApplications;
555
556        // Use ArrayAdapter for view logic, but have our own list for managing
557        // the options available.
558        private final ArrayList<Integer> mFilterOptions = new ArrayList<>();
559
560        public FilterSpinnerAdapter(ManageApplications manageApplications) {
561            super(manageApplications.getActivity(), R.layout.filter_spinner_item);
562            setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
563            mManageApplications = manageApplications;
564        }
565
566        public int getFilter(int position) {
567            return mFilterOptions.get(position);
568        }
569
570        public void setFilterEnabled(int filter, boolean enabled) {
571            if (enabled) {
572                enableFilter(filter);
573            } else {
574                disableFilter(filter);
575            }
576        }
577
578        public void enableFilter(int filter) {
579            if (mFilterOptions.contains(filter)) return;
580            if (DEBUG) Log.d(TAG, "Enabling filter " + filter);
581            mFilterOptions.add(filter);
582            Collections.sort(mFilterOptions);
583            mManageApplications.mSpinnerHeader.setVisibility(
584                    mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
585            notifyDataSetChanged();
586            if (mFilterOptions.size() == 1) {
587                if (DEBUG) Log.d(TAG, "Auto selecting filter " + filter);
588                mManageApplications.mFilterSpinner.setSelection(0);
589                mManageApplications.onItemSelected(null, null, 0, 0);
590            }
591        }
592
593        public void disableFilter(int filter) {
594            if (!mFilterOptions.remove((Integer) filter)) {
595                return;
596            }
597            if (DEBUG) Log.d(TAG, "Disabling filter " + filter);
598            Collections.sort(mFilterOptions);
599            mManageApplications.mSpinnerHeader.setVisibility(
600                    mFilterOptions.size() > 1 ? View.VISIBLE : View.GONE);
601            notifyDataSetChanged();
602            if (mManageApplications.mFilter == filter) {
603                if (mFilterOptions.size() > 0) {
604                    if (DEBUG) Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0));
605                    mManageApplications.mFilterSpinner.setSelection(0);
606                    mManageApplications.onItemSelected(null, null, 0, 0);
607                }
608            }
609        }
610
611        @Override
612        public int getCount() {
613            return mFilterOptions.size();
614        }
615
616        @Override
617        public CharSequence getItem(int position) {
618            return getFilterString(mFilterOptions.get(position));
619        }
620
621        private CharSequence getFilterString(int filter) {
622            return mManageApplications.getString(FILTER_LABELS[filter]);
623        }
624
625    }
626
627    /*
628     * Custom adapter implementation for the ListView
629     * This adapter maintains a map for each displayed application and its properties
630     * An index value on each AppInfo object indicates the correct position or index
631     * in the list. If the list gets updated dynamically when the user is viewing the list of
632     * applications, we need to return the correct index of position. This is done by mapping
633     * the getId methods via the package name into the internal maps and indices.
634     * The order of applications in the list is mirrored in mAppLocalList
635     */
636    static class ApplicationsAdapter extends BaseAdapter implements Filterable,
637            ApplicationsState.Callbacks, AppStateBaseBridge.Callback,
638            AbsListView.RecyclerListener {
639        private final ApplicationsState mState;
640        private final ApplicationsState.Session mSession;
641        private final ManageApplications mManageApplications;
642        private final Context mContext;
643        private final ArrayList<View> mActive = new ArrayList<View>();
644        private final AppStateBaseBridge mExtraInfoBridge;
645        private int mFilterMode;
646        private ArrayList<ApplicationsState.AppEntry> mBaseEntries;
647        private ArrayList<ApplicationsState.AppEntry> mEntries;
648        private boolean mResumed;
649        private int mLastSortMode=-1;
650        private int mWhichSize = SIZE_TOTAL;
651        CharSequence mCurFilterPrefix;
652        private PackageManager mPm;
653        private AppFilter mOverrideFilter;
654
655        private Filter mFilter = new Filter() {
656            @Override
657            protected FilterResults performFiltering(CharSequence constraint) {
658                ArrayList<ApplicationsState.AppEntry> entries
659                        = applyPrefixFilter(constraint, mBaseEntries);
660                FilterResults fr = new FilterResults();
661                fr.values = entries;
662                fr.count = entries.size();
663                return fr;
664            }
665
666            @Override
667            @SuppressWarnings("unchecked")
668            protected void publishResults(CharSequence constraint, FilterResults results) {
669                mCurFilterPrefix = constraint;
670                mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
671                notifyDataSetChanged();
672            }
673        };
674
675        public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
676                int filterMode) {
677            mState = state;
678            mSession = state.newSession(this);
679            mManageApplications = manageApplications;
680            mContext = manageApplications.getActivity();
681            mPm = mContext.getPackageManager();
682            mFilterMode = filterMode;
683            if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
684                mExtraInfoBridge = new AppStateNotificationBridge(mContext.getPackageManager(),
685                        mState, this, manageApplications.mNotifBackend);
686            } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
687                mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
688            } else {
689                mExtraInfoBridge = null;
690            }
691        }
692
693        public void setOverrideFilter(AppFilter overrideFilter) {
694            mOverrideFilter = overrideFilter;
695            rebuild(true);
696        }
697
698        public void setFilter(int filter) {
699            mFilterMode = filter;
700            rebuild(true);
701        }
702
703        public void resume(int sort) {
704            if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
705            if (!mResumed) {
706                mResumed = true;
707                mSession.resume();
708                mLastSortMode = sort;
709                if (mExtraInfoBridge != null) {
710                    mExtraInfoBridge.resume();
711                }
712                rebuild(true);
713            } else {
714                rebuild(sort);
715            }
716        }
717
718        public void pause() {
719            if (mResumed) {
720                mResumed = false;
721                mSession.pause();
722                if (mExtraInfoBridge != null) {
723                    mExtraInfoBridge.pause();
724                }
725            }
726        }
727
728        public void release() {
729            mSession.release();
730            if (mExtraInfoBridge != null) {
731                mExtraInfoBridge.release();
732            }
733        }
734
735        public void rebuild(int sort) {
736            if (sort == mLastSortMode) {
737                return;
738            }
739            mLastSortMode = sort;
740            rebuild(true);
741        }
742
743        public void rebuild(boolean eraseold) {
744            if (DEBUG) Log.i(TAG, "Rebuilding app list...");
745            ApplicationsState.AppFilter filterObj;
746            Comparator<AppEntry> comparatorObj;
747            boolean emulated = Environment.isExternalStorageEmulated();
748            if (emulated) {
749                mWhichSize = SIZE_TOTAL;
750            } else {
751                mWhichSize = SIZE_INTERNAL;
752            }
753            filterObj = FILTERS[mFilterMode];
754            if (mOverrideFilter != null) {
755                filterObj = mOverrideFilter;
756            }
757            if (!mManageApplications.mShowSystem) {
758                filterObj = new CompoundFilter(filterObj,
759                        ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
760            }
761            switch (mLastSortMode) {
762                case R.id.sort_order_size:
763                    switch (mWhichSize) {
764                        case SIZE_INTERNAL:
765                            comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
766                            break;
767                        case SIZE_EXTERNAL:
768                            comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
769                            break;
770                        default:
771                            comparatorObj = ApplicationsState.SIZE_COMPARATOR;
772                            break;
773                    }
774                    break;
775                default:
776                    comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
777                    break;
778            }
779            ArrayList<ApplicationsState.AppEntry> entries
780                    = mSession.rebuild(filterObj, comparatorObj);
781            if (entries == null && !eraseold) {
782                // Don't have new list yet, but can continue using the old one.
783                return;
784            }
785            mBaseEntries = entries;
786            if (mBaseEntries != null) {
787                mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
788            } else {
789                mEntries = null;
790            }
791            notifyDataSetChanged();
792
793            if (mSession.getAllApps().size() != 0
794                    && mManageApplications.mListContainer.getVisibility() != View.VISIBLE) {
795                Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
796                        mManageApplications.mListContainer, true, true);
797            }
798            if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
799                // No enabled or disabled filters for usage access.
800                return;
801            }
802
803            mManageApplications.setHasDisabled(hasDisabledApps());
804        }
805
806        private void updateLoading() {
807            Utils.handleLoadingContainer(mManageApplications.mLoadingContainer,
808                    mManageApplications.mListContainer, mSession.getAllApps().size() != 0, false);
809        }
810
811        private boolean hasDisabledApps() {
812            ArrayList<AppEntry> allApps = mSession.getAllApps();
813            for (int i = 0; i < allApps.size(); i++) {
814                if (!allApps.get(i).info.enabled) {
815                    return true;
816                }
817            }
818            return false;
819        }
820
821        ArrayList<ApplicationsState.AppEntry> applyPrefixFilter(CharSequence prefix,
822                ArrayList<ApplicationsState.AppEntry> origEntries) {
823            if (prefix == null || prefix.length() == 0) {
824                return origEntries;
825            } else {
826                String prefixStr = ApplicationsState.normalize(prefix.toString());
827                final String spacePrefixStr = " " + prefixStr;
828                ArrayList<ApplicationsState.AppEntry> newEntries
829                        = new ArrayList<ApplicationsState.AppEntry>();
830                for (int i=0; i<origEntries.size(); i++) {
831                    ApplicationsState.AppEntry entry = origEntries.get(i);
832                    String nlabel = entry.getNormalizedLabel();
833                    if (nlabel.startsWith(prefixStr) || nlabel.indexOf(spacePrefixStr) != -1) {
834                        newEntries.add(entry);
835                    }
836                }
837                return newEntries;
838            }
839        }
840
841        @Override
842        public void onExtraInfoUpdated() {
843            rebuild(false);
844        }
845
846        @Override
847        public void onRunningStateChanged(boolean running) {
848            mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running);
849        }
850
851        @Override
852        public void onRebuildComplete(ArrayList<AppEntry> apps) {
853            if (mManageApplications.mLoadingContainer.getVisibility() == View.VISIBLE) {
854                mManageApplications.mLoadingContainer.startAnimation(AnimationUtils.loadAnimation(
855                        mContext, android.R.anim.fade_out));
856                mManageApplications.mListContainer.startAnimation(AnimationUtils.loadAnimation(
857                        mContext, android.R.anim.fade_in));
858            }
859            mManageApplications.mListContainer.setVisibility(View.VISIBLE);
860            mManageApplications.mLoadingContainer.setVisibility(View.GONE);
861            mBaseEntries = apps;
862            mEntries = applyPrefixFilter(mCurFilterPrefix, mBaseEntries);
863            notifyDataSetChanged();
864        }
865
866        @Override
867        public void onPackageListChanged() {
868            rebuild(false);
869        }
870
871        @Override
872        public void onPackageIconChanged() {
873            // We ensure icons are loaded when their item is displayed, so
874            // don't care about icons loaded in the background.
875        }
876
877        @Override
878        public void onLoadEntriesCompleted() {
879            // No op.
880        }
881
882        @Override
883        public void onPackageSizeChanged(String packageName) {
884            for (int i=0; i<mActive.size(); i++) {
885                AppViewHolder holder = (AppViewHolder)mActive.get(i).getTag();
886                if (holder.entry.info.packageName.equals(packageName)) {
887                    synchronized (holder.entry) {
888                        updateSummary(holder);
889                    }
890                    if (holder.entry.info.packageName.equals(mManageApplications.mCurrentPkgName)
891                            && mLastSortMode == R.id.sort_order_size) {
892                        // We got the size information for the last app the
893                        // user viewed, and are sorting by size...  they may
894                        // have cleared data, so we immediately want to resort
895                        // the list with the new size to reflect it to the user.
896                        rebuild(false);
897                    }
898                    return;
899                }
900            }
901        }
902
903        @Override
904        public void onLauncherInfoChanged() {
905            if (!mManageApplications.mShowSystem) {
906                rebuild(false);
907            }
908        }
909
910        @Override
911        public void onAllSizesComputed() {
912            if (mLastSortMode == R.id.sort_order_size) {
913                rebuild(false);
914            }
915        }
916
917        public int getCount() {
918            return mEntries != null ? mEntries.size() : 0;
919        }
920
921        public Object getItem(int position) {
922            return mEntries.get(position);
923        }
924
925        public ApplicationsState.AppEntry getAppEntry(int position) {
926            return mEntries.get(position);
927        }
928
929        public long getItemId(int position) {
930            return mEntries.get(position).id;
931        }
932
933        @Override
934        public boolean areAllItemsEnabled() {
935            return false;
936        }
937
938        public View getView(int position, View convertView, ViewGroup parent) {
939            // A ViewHolder keeps references to children views to avoid unnecessary calls
940            // to findViewById() on each row.
941            AppViewHolder holder = AppViewHolder.createOrRecycle(mManageApplications.mInflater,
942                    convertView);
943            convertView = holder.rootView;
944
945            // Bind the data efficiently with the holder
946            ApplicationsState.AppEntry entry = mEntries.get(position);
947            synchronized (entry) {
948                holder.entry = entry;
949                if (entry.label != null) {
950                    holder.appName.setText(entry.label);
951                }
952                mState.ensureIcon(entry);
953                if (entry.icon != null) {
954                    holder.appIcon.setImageDrawable(entry.icon);
955                }
956                updateSummary(holder);
957                if ((entry.info.flags&ApplicationInfo.FLAG_INSTALLED) == 0) {
958                    holder.disabled.setVisibility(View.VISIBLE);
959                    holder.disabled.setText(R.string.not_installed);
960                } else if (!entry.info.enabled) {
961                    holder.disabled.setVisibility(View.VISIBLE);
962                    holder.disabled.setText(R.string.disabled);
963                } else {
964                    holder.disabled.setVisibility(View.GONE);
965                }
966                holder.checkBox.setVisibility(View.GONE);
967            }
968            mActive.remove(convertView);
969            mActive.add(convertView);
970            return convertView;
971        }
972
973        private void updateSummary(AppViewHolder holder) {
974            switch (mManageApplications.mListType) {
975                case LIST_TYPE_NOTIFICATION:
976                    if (holder.entry.extraInfo != null) {
977                        holder.summary.setText(InstalledAppDetails.getNotificationSummary(
978                                (AppRow) holder.entry.extraInfo, mContext));
979                    } else {
980                        holder.summary.setText(null);
981                    }
982                    break;
983
984                case LIST_TYPE_DOMAINS_URLS:
985                    holder.summary.setText(getDomainsSummary(holder.entry.info.packageName));
986                    break;
987
988                case LIST_TYPE_USAGE_ACCESS:
989                    if (holder.entry.extraInfo != null) {
990                        holder.summary.setText(((UsageState) holder.entry.extraInfo).hasAccess() ?
991                                R.string.switch_on_text : R.string.switch_off_text);
992                    } else {
993                        holder.summary.setText(null);
994                    }
995                    break;
996
997                default:
998                    holder.updateSizeText(mManageApplications.mInvalidSizeStr, mWhichSize);
999                    break;
1000            }
1001        }
1002
1003        @Override
1004        public Filter getFilter() {
1005            return mFilter;
1006        }
1007
1008        @Override
1009        public void onMovedToScrapHeap(View view) {
1010            mActive.remove(view);
1011        }
1012
1013        private CharSequence getDomainsSummary(String packageName) {
1014            ArraySet<String> result = new ArraySet<>();
1015            List<IntentFilterVerificationInfo> list =
1016                    mPm.getIntentFilterVerifications(packageName);
1017            for (IntentFilterVerificationInfo ivi : list) {
1018                for (String host : ivi.getDomains()) {
1019                    result.add(host);
1020                }
1021            }
1022            if (result.size() == 0) {
1023                return mContext.getString(R.string.domain_urls_summary_none);
1024            } else if (result.size() == 1) {
1025                return mContext.getString(R.string.domain_urls_summary_one, result.valueAt(0));
1026            } else {
1027                return mContext.getString(R.string.domain_urls_summary_some, result.valueAt(0));
1028            }
1029        }
1030    }
1031}
1032