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