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