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 
17 package com.android.settings.applications.manageapplications;
18 
19 import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
20 
21 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ALL;
22 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BLOCKED;
23 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_DISABLED;
24 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ENABLED;
25 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_FREQUENT;
26 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_INSTANT;
27 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_PERSONAL;
28 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_POWER_ALLOWLIST;
29 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_POWER_ALLOWLIST_ALL;
30 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_RECENT;
31 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WORK;
32 import static com.android.settings.search.actionbar.SearchMenuController.MENU_SEARCH;
33 
34 import android.annotation.Nullable;
35 import android.annotation.StringRes;
36 import android.app.Activity;
37 import android.app.ActivityManager;
38 import android.app.settings.SettingsEnums;
39 import android.app.usage.IUsageStatsManager;
40 import android.compat.annotation.ChangeId;
41 import android.compat.annotation.LoggingOnly;
42 import android.content.Context;
43 import android.content.Intent;
44 import android.content.pm.ApplicationInfo;
45 import android.content.pm.PackageItemInfo;
46 import android.net.Uri;
47 import android.os.Build;
48 import android.os.Bundle;
49 import android.os.Environment;
50 import android.os.IBinder;
51 import android.os.RemoteException;
52 import android.os.ServiceManager;
53 import android.os.UserHandle;
54 import android.os.UserManager;
55 import android.preference.PreferenceFrameLayout;
56 import android.text.TextUtils;
57 import android.util.ArraySet;
58 import android.util.IconDrawableFactory;
59 import android.util.Log;
60 import android.view.LayoutInflater;
61 import android.view.Menu;
62 import android.view.MenuInflater;
63 import android.view.MenuItem;
64 import android.view.View;
65 import android.view.ViewGroup;
66 import android.widget.AdapterView;
67 import android.widget.AdapterView.OnItemSelectedListener;
68 import android.widget.Filter;
69 import android.widget.FrameLayout;
70 import android.widget.SearchView;
71 import android.widget.Spinner;
72 
73 import androidx.annotation.NonNull;
74 import androidx.annotation.VisibleForTesting;
75 import androidx.annotation.WorkerThread;
76 import androidx.coordinatorlayout.widget.CoordinatorLayout;
77 import androidx.core.view.ViewCompat;
78 import androidx.recyclerview.widget.LinearLayoutManager;
79 import androidx.recyclerview.widget.RecyclerView;
80 
81 import com.android.internal.compat.IPlatformCompat;
82 import com.android.settings.R;
83 import com.android.settings.Settings;
84 import com.android.settings.Settings.GamesStorageActivity;
85 import com.android.settings.Settings.HighPowerApplicationsActivity;
86 import com.android.settings.Settings.ManageExternalSourcesActivity;
87 import com.android.settings.Settings.OverlaySettingsActivity;
88 import com.android.settings.Settings.StorageUseActivity;
89 import com.android.settings.Settings.UsageAccessSettingsActivity;
90 import com.android.settings.Settings.WriteSettingsActivity;
91 import com.android.settings.SettingsActivity;
92 import com.android.settings.Utils;
93 import com.android.settings.applications.AppInfoBase;
94 import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
95 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
96 import com.android.settings.applications.AppStateBaseBridge;
97 import com.android.settings.applications.AppStateInstallAppsBridge;
98 import com.android.settings.applications.AppStateManageExternalStorageBridge;
99 import com.android.settings.applications.AppStateMediaManagementAppsBridge;
100 import com.android.settings.applications.AppStateNotificationBridge;
101 import com.android.settings.applications.AppStateNotificationBridge.NotificationsSentState;
102 import com.android.settings.applications.AppStateOverlayBridge;
103 import com.android.settings.applications.AppStatePowerBridge;
104 import com.android.settings.applications.AppStateUsageBridge;
105 import com.android.settings.applications.AppStateUsageBridge.UsageState;
106 import com.android.settings.applications.AppStateWriteSettingsBridge;
107 import com.android.settings.applications.AppStorageSettings;
108 import com.android.settings.applications.UsageAccessDetails;
109 import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails;
110 import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
111 import com.android.settings.applications.appinfo.DrawOverlayDetails;
112 import com.android.settings.applications.appinfo.ExternalSourcesDetails;
113 import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
114 import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
115 import com.android.settings.applications.appinfo.WriteSettingsDetails;
116 import com.android.settings.core.InstrumentedFragment;
117 import com.android.settings.core.SubSettingLauncher;
118 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
119 import com.android.settings.fuelgauge.HighPowerDetail;
120 import com.android.settings.notification.ConfigureNotificationSettings;
121 import com.android.settings.notification.NotificationBackend;
122 import com.android.settings.notification.app.AppNotificationSettings;
123 import com.android.settings.widget.LoadingViewController;
124 import com.android.settings.wifi.AppStateChangeWifiStateBridge;
125 import com.android.settings.wifi.ChangeWifiStateDetails;
126 import com.android.settingslib.applications.ApplicationsState;
127 import com.android.settingslib.applications.ApplicationsState.AppEntry;
128 import com.android.settingslib.applications.ApplicationsState.AppFilter;
129 import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
130 import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
131 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
132 import com.android.settingslib.utils.ThreadUtils;
133 import com.android.settingslib.widget.settingsspinner.SettingsSpinnerAdapter;
134 
135 import com.google.android.material.appbar.AppBarLayout;
136 
137 import java.util.ArrayList;
138 import java.util.Arrays;
139 import java.util.Collections;
140 import java.util.Comparator;
141 import java.util.Set;
142 
143 /**
144  * Activity to pick an application that will be used to display installation information and
145  * options to uninstall/delete user data for system applications. This activity
146  * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
147  * intent.
148  */
149 public class ManageApplications extends InstrumentedFragment
150         implements View.OnClickListener, OnItemSelectedListener, SearchView.OnQueryTextListener,
151         MenuItem.OnActionExpandListener {
152 
153     static final String TAG = "ManageApplications";
154     static final boolean DEBUG = Build.IS_DEBUGGABLE;
155 
156     // Intent extras.
157     public static final String EXTRA_CLASSNAME = "classname";
158     // Used for storage only.
159     public static final String EXTRA_VOLUME_UUID = "volumeUuid";
160     public static final String EXTRA_VOLUME_NAME = "volumeName";
161     public static final String EXTRA_STORAGE_TYPE = "storageType";
162     public static final String EXTRA_WORK_ID = "workId";
163 
164     private static final String EXTRA_SORT_ORDER = "sortOrder";
165     private static final String EXTRA_SHOW_SYSTEM = "showSystem";
166     private static final String EXTRA_HAS_ENTRIES = "hasEntries";
167     private static final String EXTRA_HAS_BRIDGE = "hasBridge";
168     private static final String EXTRA_FILTER_TYPE = "filterType";
169     @VisibleForTesting
170     static final String EXTRA_EXPAND_SEARCH_VIEW = "expand_search_view";
171 
172     // attributes used as keys when passing values to AppInfoDashboardFragment activity
173     public static final String APP_CHG = "chg";
174 
175     // constant value that can be used to check return code from sub activity.
176     private static final int INSTALLED_APP_DETAILS = 1;
177     private static final int ADVANCED_SETTINGS = 2;
178 
179     public static final int SIZE_TOTAL = 0;
180     public static final int SIZE_INTERNAL = 1;
181     public static final int SIZE_EXTERNAL = 2;
182 
183     // Storage types. Used to determine what the extra item in the list of preferences is.
184     public static final int STORAGE_TYPE_DEFAULT = 0; // Show all apps that are not categorized.
185     public static final int STORAGE_TYPE_LEGACY = 1;  // Show apps even if they can be categorized.
186 
187     /**
188      * Intents with action {@code android.settings.MANAGE_APP_OVERLAY_PERMISSION}
189      * and data URI scheme {@code package} don't go to the app-specific screen for managing the
190      * permission anymore. Instead, they redirect to this screen for managing all the apps that have
191      * requested such permission.
192      */
193     @ChangeId
194     @LoggingOnly
195     private static final long CHANGE_RESTRICT_SAW_INTENT = 135920175L;
196 
197     // sort order
198     @VisibleForTesting
199     int mSortOrder = R.id.sort_order_alpha;
200 
201     // whether showing system apps.
202     private boolean mShowSystem;
203 
204     private ApplicationsState mApplicationsState;
205 
206     public int mListType;
207     private AppFilterItem mFilter;
208     private ApplicationsAdapter mApplications;
209 
210     private View mLoadingContainer;
211     private SearchView mSearchView;
212 
213     // Size resource used for packages whose size computation failed for some reason
214     CharSequence mInvalidSizeStr;
215 
216     private String mCurrentPkgName;
217     private int mCurrentUid;
218 
219     private Menu mOptionsMenu;
220 
221     public static final int LIST_TYPE_MAIN = 0;
222     public static final int LIST_TYPE_NOTIFICATION = 1;
223     public static final int LIST_TYPE_STORAGE = 3;
224     public static final int LIST_TYPE_USAGE_ACCESS = 4;
225     public static final int LIST_TYPE_HIGH_POWER = 5;
226     public static final int LIST_TYPE_OVERLAY = 6;
227     public static final int LIST_TYPE_WRITE_SETTINGS = 7;
228     public static final int LIST_TYPE_MANAGE_SOURCES = 8;
229     public static final int LIST_TYPE_GAMES = 9;
230     public static final int LIST_TYPE_WIFI_ACCESS = 10;
231     public static final int LIST_MANAGE_EXTERNAL_STORAGE = 11;
232     public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12;
233     public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13;
234 
235     // List types that should show instant apps.
236     public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
237             LIST_TYPE_MAIN,
238             LIST_TYPE_STORAGE));
239 
240     @VisibleForTesting
241     View mSpinnerHeader;
242     @VisibleForTesting
243     FilterSpinnerAdapter mFilterAdapter;
244     @VisibleForTesting
245     RecyclerView mRecyclerView;
246     // Whether or not search view is expanded.
247     @VisibleForTesting
248     boolean mExpandSearch;
249 
250     private View mRootView;
251     private Spinner mFilterSpinner;
252     private IUsageStatsManager mUsageStatsManager;
253     private UserManager mUserManager;
254     private NotificationBackend mNotificationBackend;
255     private ResetAppsHelper mResetAppsHelper;
256     private String mVolumeUuid;
257     private int mStorageType;
258     private boolean mIsWorkOnly;
259     private int mWorkUserId;
260     private boolean mIsPersonalOnly;
261     private View mEmptyView;
262     private int mFilterType;
263     private AppBarLayout mAppBarLayout;
264 
265     @Override
onCreate(Bundle savedInstanceState)266     public void onCreate(Bundle savedInstanceState) {
267         super.onCreate(savedInstanceState);
268         setHasOptionsMenu(true);
269         final Activity activity = getActivity();
270         mUserManager = activity.getSystemService(UserManager.class);
271         mApplicationsState = ApplicationsState.getInstance(activity.getApplication());
272 
273         Intent intent = activity.getIntent();
274         Bundle args = getArguments();
275         final int screenTitle = getTitleResId(intent, args);
276 
277         String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
278         if (className == null) {
279             className = intent.getComponent().getClassName();
280         }
281         if (className.equals(StorageUseActivity.class.getName())) {
282             if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
283                 mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
284                 mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);
285                 mListType = LIST_TYPE_STORAGE;
286             } else {
287                 // No volume selected, display a normal list, sorted by size.
288                 mListType = LIST_TYPE_MAIN;
289             }
290             mSortOrder = R.id.sort_order_size;
291         } else if (className.equals(UsageAccessSettingsActivity.class.getName())) {
292             mListType = LIST_TYPE_USAGE_ACCESS;
293         } else if (className.equals(HighPowerApplicationsActivity.class.getName())) {
294             mListType = LIST_TYPE_HIGH_POWER;
295             // Default to showing system.
296             mShowSystem = true;
297         } else if (className.equals(OverlaySettingsActivity.class.getName())) {
298             mListType = LIST_TYPE_OVERLAY;
299 
300             reportIfRestrictedSawIntent(intent);
301         } else if (className.equals(WriteSettingsActivity.class.getName())) {
302             mListType = LIST_TYPE_WRITE_SETTINGS;
303         } else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
304             mListType = LIST_TYPE_MANAGE_SOURCES;
305         } else if (className.equals(GamesStorageActivity.class.getName())) {
306             mListType = LIST_TYPE_GAMES;
307             mSortOrder = R.id.sort_order_size;
308         } else if (className.equals(Settings.ChangeWifiStateActivity.class.getName())) {
309             mListType = LIST_TYPE_WIFI_ACCESS;
310         } else if (className.equals(Settings.ManageExternalStorageActivity.class.getName())) {
311             mListType = LIST_MANAGE_EXTERNAL_STORAGE;
312         }  else if (className.equals(Settings.MediaManagementAppsActivity.class.getName())) {
313             mListType = LIST_TYPE_MEDIA_MANAGEMENT_APPS;
314         } else if (className.equals(Settings.AlarmsAndRemindersActivity.class.getName())) {
315             mListType = LIST_TYPE_ALARMS_AND_REMINDERS;
316         } else if (className.equals(Settings.NotificationAppListActivity.class.getName())) {
317             mListType = LIST_TYPE_NOTIFICATION;
318             mUsageStatsManager = IUsageStatsManager.Stub.asInterface(
319                     ServiceManager.getService(Context.USAGE_STATS_SERVICE));
320             mNotificationBackend = new NotificationBackend();
321             mSortOrder = R.id.sort_order_recent_notification;
322         } else {
323             mListType = LIST_TYPE_MAIN;
324         }
325         final AppFilterRegistry appFilterRegistry = AppFilterRegistry.getInstance();
326         mFilter = appFilterRegistry.get(appFilterRegistry.getDefaultFilterType(mListType));
327         mIsPersonalOnly = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
328                 == ProfileSelectFragment.ProfileType.PERSONAL : false;
329         mIsWorkOnly = args != null ? args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
330                 == ProfileSelectFragment.ProfileType.WORK : false;
331         mWorkUserId = args != null ? args.getInt(EXTRA_WORK_ID) : UserHandle.myUserId();
332         if (mIsWorkOnly && mWorkUserId == UserHandle.myUserId()) {
333             mWorkUserId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId());
334         }
335 
336         mExpandSearch = activity.getIntent().getBooleanExtra(EXTRA_EXPAND_SEARCH_VIEW, false);
337 
338         if (savedInstanceState != null) {
339             mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
340             mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
341             mFilterType =
342                     savedInstanceState.getInt(EXTRA_FILTER_TYPE, AppFilterRegistry.FILTER_APPS_ALL);
343             mExpandSearch = savedInstanceState.getBoolean(EXTRA_EXPAND_SEARCH_VIEW);
344         }
345 
346         mInvalidSizeStr = activity.getText(R.string.invalid_size_value);
347 
348         mResetAppsHelper = new ResetAppsHelper(activity);
349 
350         if (screenTitle > 0) {
351             activity.setTitle(screenTitle);
352         }
353     }
354 
reportIfRestrictedSawIntent(Intent intent)355     private void reportIfRestrictedSawIntent(Intent intent) {
356         try {
357             Uri data = intent.getData();
358             if (data == null || !TextUtils.equals("package", data.getScheme())) {
359                 // Not a restricted intent
360                 return;
361             }
362             IBinder activityToken = getActivity().getActivityToken();
363             int callingUid = ActivityManager.getService().getLaunchedFromUid(activityToken);
364             if (callingUid == -1) {
365                 Log.w(TAG, "Error obtaining calling uid");
366                 return;
367             }
368             IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
369                     ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
370             if (platformCompat == null) {
371                 Log.w(TAG, "Error obtaining IPlatformCompat service");
372                 return;
373             }
374             platformCompat.reportChangeByUid(CHANGE_RESTRICT_SAW_INTENT, callingUid);
375         } catch (RemoteException e) {
376             Log.w(TAG, "Error reporting SAW intent restriction", e);
377         }
378     }
379 
380     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)381     public View onCreateView(LayoutInflater inflater, ViewGroup container,
382             Bundle savedInstanceState) {
383         if (mListType == LIST_TYPE_OVERLAY && !Utils.isSystemAlertWindowEnabled(getContext())) {
384             mRootView = inflater.inflate(R.layout.manage_applications_apps_unsupported, null);
385             setHasOptionsMenu(false);
386             return mRootView;
387         }
388 
389         mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
390         mLoadingContainer = mRootView.findViewById(R.id.loading_container);
391         mEmptyView = mRootView.findViewById(android.R.id.empty);
392         mRecyclerView = mRootView.findViewById(R.id.apps_list);
393 
394         mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
395                 savedInstanceState);
396         if (savedInstanceState != null) {
397             mApplications.mHasReceivedLoadEntries =
398                     savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
399             mApplications.mHasReceivedBridgeCallback =
400                     savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
401         }
402         mRecyclerView.setItemAnimator(null);
403         mRecyclerView.setLayoutManager(new LinearLayoutManager(
404                 getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
405         mRecyclerView.setAdapter(mApplications);
406 
407         // We have to do this now because PreferenceFrameLayout looks at it
408         // only when the view is added.
409         if (container instanceof PreferenceFrameLayout) {
410             ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true;
411         }
412 
413         createHeader();
414 
415         mResetAppsHelper.onRestoreInstanceState(savedInstanceState);
416 
417         mAppBarLayout = getActivity().findViewById(R.id.app_bar);
418         disableToolBarScrollableBehavior();
419 
420         return mRootView;
421     }
422 
423     @VisibleForTesting
createHeader()424     void createHeader() {
425         final Activity activity = getActivity();
426         final FrameLayout pinnedHeader = mRootView.findViewById(R.id.pinned_header);
427         mSpinnerHeader = activity.getLayoutInflater()
428                 .inflate(R.layout.manage_apps_filter_spinner, pinnedHeader, false);
429         mFilterSpinner = mSpinnerHeader.findViewById(R.id.filter_spinner);
430         mFilterAdapter = new FilterSpinnerAdapter(this);
431         mFilterSpinner.setAdapter(mFilterAdapter);
432         mFilterSpinner.setOnItemSelectedListener(this);
433         pinnedHeader.addView(mSpinnerHeader, 0);
434 
435         final AppFilterRegistry appFilterRegistry = AppFilterRegistry.getInstance();
436         final int filterType = appFilterRegistry.getDefaultFilterType(mListType);
437         mFilterAdapter.enableFilter(filterType);
438 
439         if (mListType == LIST_TYPE_MAIN) {
440             if (UserManager.get(getActivity()).getUserProfiles().size() > 1 && !mIsWorkOnly
441                     && !mIsPersonalOnly) {
442                 mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
443                 mFilterAdapter.enableFilter(FILTER_APPS_WORK);
444             }
445         }
446         if (mListType == LIST_TYPE_NOTIFICATION) {
447             mFilterAdapter.enableFilter(FILTER_APPS_RECENT);
448             mFilterAdapter.enableFilter(FILTER_APPS_FREQUENT);
449             mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
450             mFilterAdapter.enableFilter(FILTER_APPS_ALL);
451         }
452         if (mListType == LIST_TYPE_HIGH_POWER) {
453             mFilterAdapter.enableFilter(FILTER_APPS_POWER_ALLOWLIST_ALL);
454         }
455 
456         setCompositeFilter();
457     }
458 
459     @VisibleForTesting
460     @Nullable
getCompositeFilter(int listType, int storageType, String volumeUuid)461     static AppFilter getCompositeFilter(int listType, int storageType, String volumeUuid) {
462         AppFilter filter = new VolumeFilter(volumeUuid);
463         if (listType == LIST_TYPE_STORAGE) {
464             if (storageType == STORAGE_TYPE_DEFAULT) {
465                 filter = new CompoundFilter(ApplicationsState.FILTER_APPS_EXCEPT_GAMES, filter);
466             }
467             return filter;
468         }
469         if (listType == LIST_TYPE_GAMES) {
470             return new CompoundFilter(ApplicationsState.FILTER_GAMES, filter);
471         }
472         return null;
473     }
474 
475     @Override
getMetricsCategory()476     public int getMetricsCategory() {
477         switch (mListType) {
478             case LIST_TYPE_MAIN:
479                 return SettingsEnums.MANAGE_APPLICATIONS;
480             case LIST_TYPE_NOTIFICATION:
481                 return SettingsEnums.MANAGE_APPLICATIONS_NOTIFICATIONS;
482             case LIST_TYPE_STORAGE:
483                 return SettingsEnums.APPLICATIONS_STORAGE_APPS;
484             case LIST_TYPE_GAMES:
485                 return SettingsEnums.APPLICATIONS_STORAGE_GAMES;
486             case LIST_TYPE_USAGE_ACCESS:
487                 return SettingsEnums.USAGE_ACCESS;
488             case LIST_TYPE_HIGH_POWER:
489                 return SettingsEnums.APPLICATIONS_HIGH_POWER_APPS;
490             case LIST_TYPE_OVERLAY:
491                 return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
492             case LIST_TYPE_WRITE_SETTINGS:
493                 return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
494             case LIST_TYPE_MANAGE_SOURCES:
495                 return SettingsEnums.MANAGE_EXTERNAL_SOURCES;
496             case LIST_TYPE_WIFI_ACCESS:
497                 return SettingsEnums.CONFIGURE_WIFI;
498             case LIST_MANAGE_EXTERNAL_STORAGE:
499                 return SettingsEnums.MANAGE_EXTERNAL_STORAGE;
500             case LIST_TYPE_ALARMS_AND_REMINDERS:
501                 return SettingsEnums.ALARMS_AND_REMINDERS;
502             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
503                 return SettingsEnums.MEDIA_MANAGEMENT_APPS;
504             default:
505                 return SettingsEnums.PAGE_UNKNOWN;
506         }
507     }
508 
509     @Override
onStart()510     public void onStart() {
511         super.onStart();
512         updateView();
513         if (mApplications != null) {
514             mApplications.resume(mSortOrder);
515             mApplications.updateLoading();
516         }
517     }
518 
519     @Override
onSaveInstanceState(Bundle outState)520     public void onSaveInstanceState(Bundle outState) {
521         super.onSaveInstanceState(outState);
522         mResetAppsHelper.onSaveInstanceState(outState);
523         outState.putInt(EXTRA_SORT_ORDER, mSortOrder);
524         outState.putInt(EXTRA_FILTER_TYPE, mFilter.getFilterType());
525         outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
526         outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
527         outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback);
528         if (mSearchView != null) {
529             outState.putBoolean(EXTRA_EXPAND_SEARCH_VIEW, !mSearchView.isIconified());
530         }
531         if (mApplications != null) {
532             mApplications.onSaveInstanceState(outState);
533         }
534     }
535 
536     @Override
onStop()537     public void onStop() {
538         super.onStop();
539         if (mApplications != null) {
540             mApplications.pause();
541         }
542         mResetAppsHelper.stop();
543     }
544 
545     @Override
onDestroyView()546     public void onDestroyView() {
547         super.onDestroyView();
548 
549         if (mApplications != null) {
550             mApplications.release();
551         }
552         mRootView = null;
553     }
554 
555     @Override
onActivityResult(int requestCode, int resultCode, Intent data)556     public void onActivityResult(int requestCode, int resultCode, Intent data) {
557         if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
558             if (mListType == LIST_TYPE_NOTIFICATION) {
559                 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
560             } else if (mListType == LIST_TYPE_HIGH_POWER || mListType == LIST_TYPE_OVERLAY
561                     || mListType == LIST_TYPE_WRITE_SETTINGS) {
562                 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
563             } else {
564                 mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
565             }
566         }
567     }
568 
setCompositeFilter()569     private void setCompositeFilter() {
570         AppFilter compositeFilter = getCompositeFilter(mListType, mStorageType, mVolumeUuid);
571         if (compositeFilter == null) {
572             compositeFilter = mFilter.getFilter();
573         }
574         if (mIsWorkOnly) {
575             compositeFilter = new CompoundFilter(compositeFilter, ApplicationsState.FILTER_WORK);
576         }
577         if (mIsPersonalOnly) {
578             compositeFilter = new CompoundFilter(compositeFilter,
579                     ApplicationsState.FILTER_PERSONAL);
580         }
581         mApplications.setCompositeFilter(compositeFilter);
582     }
583 
584     // utility method used to start sub activity
startApplicationDetailsActivity()585     private void startApplicationDetailsActivity() {
586         switch (mListType) {
587             case LIST_TYPE_NOTIFICATION:
588                 startAppInfoFragment(AppNotificationSettings.class, R.string.notifications_title);
589                 break;
590             case LIST_TYPE_USAGE_ACCESS:
591                 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
592                 break;
593             case LIST_TYPE_STORAGE:
594                 startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
595                 break;
596             case LIST_TYPE_HIGH_POWER:
597                 HighPowerDetail.show(this, mCurrentUid, mCurrentPkgName, INSTALLED_APP_DETAILS);
598                 break;
599             case LIST_TYPE_OVERLAY:
600                 startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings);
601                 break;
602             case LIST_TYPE_WRITE_SETTINGS:
603                 startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
604                 break;
605             case LIST_TYPE_MANAGE_SOURCES:
606                 startAppInfoFragment(ExternalSourcesDetails.class, R.string.install_other_apps);
607                 break;
608             case LIST_TYPE_GAMES:
609                 startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings);
610                 break;
611             case LIST_TYPE_WIFI_ACCESS:
612                 startAppInfoFragment(ChangeWifiStateDetails.class,
613                         R.string.change_wifi_state_title);
614                 break;
615             case LIST_MANAGE_EXTERNAL_STORAGE:
616                 startAppInfoFragment(ManageExternalStorageDetails.class,
617                         R.string.manage_external_storage_title);
618                 break;
619             case LIST_TYPE_ALARMS_AND_REMINDERS:
620                 startAppInfoFragment(AlarmsAndRemindersDetails.class,
621                         R.string.alarms_and_reminders_label);
622                 break;
623             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
624                 startAppInfoFragment(MediaManagementAppsDetails.class,
625                         R.string.media_management_apps_title);
626                 break;
627             // TODO: Figure out if there is a way where we can spin up the profile's settings
628             // process ahead of time, to avoid a long load of data when user clicks on a managed
629             // app. Maybe when they load the list of apps that contains managed profile apps.
630             default:
631                 startAppInfoFragment(
632                         AppInfoDashboardFragment.class, R.string.application_info_label);
633                 break;
634         }
635     }
636 
startAppInfoFragment(Class<?> fragment, int titleRes)637     private void startAppInfoFragment(Class<?> fragment, int titleRes) {
638         AppInfoBase.startAppInfoFragment(fragment, titleRes, mCurrentPkgName, mCurrentUid, this,
639                 INSTALLED_APP_DETAILS, getMetricsCategory());
640     }
641 
642     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)643     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
644         final Activity activity = getActivity();
645         if (activity == null) {
646             return;
647         }
648         mOptionsMenu = menu;
649         inflater.inflate(R.menu.manage_apps, menu);
650 
651         final MenuItem searchMenuItem = menu.findItem(R.id.search_app_list_menu);
652         if (searchMenuItem != null) {
653             searchMenuItem.setOnActionExpandListener(this);
654             mSearchView = (SearchView) searchMenuItem.getActionView();
655             mSearchView.setQueryHint(getText(R.string.search_settings));
656             mSearchView.setOnQueryTextListener(this);
657             if (mExpandSearch) {
658                 searchMenuItem.expandActionView();
659             }
660         }
661 
662         updateOptionsMenu();
663     }
664 
665     @Override
onMenuItemActionExpand(MenuItem item)666     public boolean onMenuItemActionExpand(MenuItem item) {
667         // To prevent a large space on tool bar.
668         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
669         // To prevent user can expand the collapsing tool bar view.
670         ViewCompat.setNestedScrollingEnabled(mRecyclerView, false);
671         return true;
672     }
673 
674     @Override
onMenuItemActionCollapse(MenuItem item)675     public boolean onMenuItemActionCollapse(MenuItem item) {
676         // We keep the collapsed status after user cancel the search function.
677         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
678         ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
679         return true;
680     }
681 
682     @Override
onPrepareOptionsMenu(Menu menu)683     public void onPrepareOptionsMenu(Menu menu) {
684         updateOptionsMenu();
685     }
686 
687     @Override
onDestroyOptionsMenu()688     public void onDestroyOptionsMenu() {
689         mOptionsMenu = null;
690     }
691 
692     @StringRes
getHelpResource()693     int getHelpResource() {
694         switch (mListType) {
695             case LIST_TYPE_NOTIFICATION:
696                 return R.string.help_uri_notifications;
697             case LIST_TYPE_USAGE_ACCESS:
698                 return R.string.help_url_usage_access;
699             case LIST_TYPE_STORAGE:
700                 return R.string.help_uri_apps_storage;
701             case LIST_TYPE_HIGH_POWER:
702                 return R.string.help_uri_apps_high_power;
703             case LIST_TYPE_OVERLAY:
704                 return R.string.help_uri_apps_overlay;
705             case LIST_TYPE_WRITE_SETTINGS:
706                 return R.string.help_uri_apps_write_settings;
707             case LIST_TYPE_MANAGE_SOURCES:
708                 return R.string.help_uri_apps_manage_sources;
709             case LIST_TYPE_GAMES:
710                 return R.string.help_uri_apps_overlay;
711             case LIST_TYPE_WIFI_ACCESS:
712                 return R.string.help_uri_apps_wifi_access;
713             case LIST_MANAGE_EXTERNAL_STORAGE:
714                 return R.string.help_uri_manage_external_storage;
715             case LIST_TYPE_ALARMS_AND_REMINDERS:
716                 return R.string.help_uri_alarms_and_reminders;
717             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
718                 return R.string.help_uri_media_management_apps;
719             default:
720             case LIST_TYPE_MAIN:
721                 return R.string.help_uri_apps;
722         }
723     }
724 
updateOptionsMenu()725     void updateOptionsMenu() {
726         if (mOptionsMenu == null) {
727             return;
728         }
729         mOptionsMenu.findItem(R.id.advanced).setVisible(false);
730 
731         mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE
732                 && mSortOrder != R.id.sort_order_alpha);
733         mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE
734                 && mSortOrder != R.id.sort_order_size);
735 
736         mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem
737                 && mListType != LIST_TYPE_HIGH_POWER);
738         mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem
739                 && mListType != LIST_TYPE_HIGH_POWER);
740 
741         mOptionsMenu.findItem(R.id.reset_app_preferences).setVisible(mListType == LIST_TYPE_MAIN);
742 
743         // Hide notification menu items, because sorting happens when filtering
744         mOptionsMenu.findItem(R.id.sort_order_recent_notification).setVisible(false);
745         mOptionsMenu.findItem(R.id.sort_order_frequent_notification).setVisible(false);
746         final MenuItem searchItem = mOptionsMenu.findItem(MENU_SEARCH);
747         if (searchItem != null) {
748             searchItem.setVisible(false);
749         }
750     }
751 
752     @Override
onOptionsItemSelected(MenuItem item)753     public boolean onOptionsItemSelected(MenuItem item) {
754         int menuId = item.getItemId();
755         int i = item.getItemId();
756         if (i == R.id.sort_order_alpha || i == R.id.sort_order_size) {
757             if (mApplications != null) {
758                 mApplications.rebuild(menuId);
759             }
760         } else if (i == R.id.show_system || i == R.id.hide_system) {
761             mShowSystem = !mShowSystem;
762             mApplications.rebuild();
763         } else if (i == R.id.reset_app_preferences) {
764             mResetAppsHelper.buildResetDialog();
765             return true;
766         } else if (i == R.id.advanced) {
767             if (mListType == LIST_TYPE_NOTIFICATION) {
768                 new SubSettingLauncher(getContext())
769                         .setDestination(ConfigureNotificationSettings.class.getName())
770                         .setTitleRes(R.string.configure_notification_settings)
771                         .setSourceMetricsCategory(getMetricsCategory())
772                         .setResultListener(this, ADVANCED_SETTINGS)
773                         .launch();
774             } else {
775                 Intent intent = new Intent(
776                         android.provider.Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
777                 startActivityForResult(intent, ADVANCED_SETTINGS);
778             }
779             return true;
780         } else {// Handle the home button
781             return false;
782         }
783         updateOptionsMenu();
784         return true;
785     }
786 
787     @Override
onClick(View view)788     public void onClick(View view) {
789         if (mApplications == null) {
790             return;
791         }
792         final int position = mRecyclerView.getChildAdapterPosition(view);
793 
794         if (position == RecyclerView.NO_POSITION) {
795             Log.w(TAG, "Cannot find position for child, skipping onClick handling");
796             return;
797         }
798         if (mApplications.getApplicationCount() > position) {
799             ApplicationsState.AppEntry entry = mApplications.getAppEntry(position);
800             mCurrentPkgName = entry.info.packageName;
801             mCurrentUid = entry.info.uid;
802             startApplicationDetailsActivity();
803             // We disable the scrolling ability in onMenuItemActionCollapse, we should recover it
804             // if user selects any app item.
805             ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
806         }
807     }
808 
809     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)810     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
811         mFilter = mFilterAdapter.getFilter(position);
812         setCompositeFilter();
813         mApplications.setFilter(mFilter);
814 
815         if (DEBUG) {
816             Log.d(TAG, "Selecting filter " + getContext().getText(mFilter.getTitle()));
817         }
818     }
819 
820     @Override
onNothingSelected(AdapterView<?> parent)821     public void onNothingSelected(AdapterView<?> parent) {
822     }
823 
824     @Override
onQueryTextSubmit(String query)825     public boolean onQueryTextSubmit(String query) {
826         return false;
827     }
828 
829     @Override
onQueryTextChange(String newText)830     public boolean onQueryTextChange(String newText) {
831         mApplications.filterSearch(newText);
832         return false;
833     }
834 
updateView()835     public void updateView() {
836         updateOptionsMenu();
837         final Activity host = getActivity();
838         if (host != null) {
839             host.invalidateOptionsMenu();
840         }
841     }
842 
setHasDisabled(boolean hasDisabledApps)843     public void setHasDisabled(boolean hasDisabledApps) {
844         if (mListType != LIST_TYPE_MAIN) {
845             return;
846         }
847         mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps);
848         mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps);
849     }
850 
setHasInstant(boolean haveInstantApps)851     public void setHasInstant(boolean haveInstantApps) {
852         if (LIST_TYPES_WITH_INSTANT.contains(mListType)) {
853             mFilterAdapter.setFilterEnabled(FILTER_APPS_INSTANT, haveInstantApps);
854         }
855     }
856 
disableToolBarScrollableBehavior()857     private void disableToolBarScrollableBehavior() {
858         final CoordinatorLayout.LayoutParams params =
859                 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
860         final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
861         behavior.setDragCallback(
862                 new AppBarLayout.Behavior.DragCallback() {
863                     @Override
864                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
865                         return false;
866                     }
867                 });
868         params.setBehavior(behavior);
869     }
870 
871     /**
872      * Returns a resource ID of title based on what type of app list is
873      * @param intent the intent of the activity that might include a specified title
874      * @param args the args that includes a class name of app list
875      */
getTitleResId(@onNull Intent intent, Bundle args)876     public static int getTitleResId(@NonNull Intent intent, Bundle args) {
877         int screenTitle = intent.getIntExtra(
878                 SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.all_apps);
879         String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
880         if (className == null) {
881             className = intent.getComponent().getClassName();
882         }
883         if (className.equals(Settings.UsageAccessSettingsActivity.class.getName())) {
884             screenTitle = R.string.usage_access;
885         } else if (className.equals(Settings.HighPowerApplicationsActivity.class.getName())) {
886             screenTitle = R.string.high_power_apps;
887         } else if (className.equals(Settings.OverlaySettingsActivity.class.getName())) {
888             screenTitle = R.string.system_alert_window_settings;
889         } else if (className.equals(Settings.WriteSettingsActivity.class.getName())) {
890             screenTitle = R.string.write_settings;
891         } else if (className.equals(Settings.ManageExternalSourcesActivity.class.getName())) {
892             screenTitle = R.string.install_other_apps;
893         } else if (className.equals(Settings.ChangeWifiStateActivity.class.getName())) {
894             screenTitle = R.string.change_wifi_state_title;
895         } else if (className.equals(Settings.ManageExternalStorageActivity.class.getName())) {
896             screenTitle = R.string.manage_external_storage_title;
897         }  else if (className.equals(Settings.MediaManagementAppsActivity.class.getName())) {
898             screenTitle = R.string.media_management_apps_title;
899         } else if (className.equals(Settings.AlarmsAndRemindersActivity.class.getName())) {
900             screenTitle = R.string.alarms_and_reminders_title;
901         } else if (className.equals(Settings.NotificationAppListActivity.class.getName())) {
902             screenTitle = R.string.app_notifications_title;
903         } else {
904             if (screenTitle == -1) {
905                 screenTitle = R.string.all_apps;
906             }
907         }
908         return screenTitle;
909     }
910 
911     static class FilterSpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> {
912 
913         private final ManageApplications mManageApplications;
914         private final Context mContext;
915 
916         // Use ArrayAdapter for view logic, but have our own list for managing
917         // the options available.
918         private final ArrayList<AppFilterItem> mFilterOptions = new ArrayList<>();
919 
FilterSpinnerAdapter(ManageApplications manageApplications)920         public FilterSpinnerAdapter(ManageApplications manageApplications) {
921             super(manageApplications.getContext());
922             mContext = manageApplications.getContext();
923             mManageApplications = manageApplications;
924         }
925 
getFilter(int position)926         public AppFilterItem getFilter(int position) {
927             return mFilterOptions.get(position);
928         }
929 
setFilterEnabled(@ppFilterRegistry.FilterType int filter, boolean enabled)930         public void setFilterEnabled(@AppFilterRegistry.FilterType int filter, boolean enabled) {
931             if (enabled) {
932                 enableFilter(filter);
933             } else {
934                 disableFilter(filter);
935             }
936         }
937 
enableFilter(@ppFilterRegistry.FilterType int filterType)938         public void enableFilter(@AppFilterRegistry.FilterType int filterType) {
939             final AppFilterItem filter = AppFilterRegistry.getInstance().get(filterType);
940             if (mFilterOptions.contains(filter)) {
941                 return;
942             }
943             if (DEBUG) {
944                 Log.d(TAG, "Enabling filter " + mContext.getText(filter.getTitle()));
945             }
946             mFilterOptions.add(filter);
947             Collections.sort(mFilterOptions);
948             updateFilterView(mFilterOptions.size() > 1);
949             notifyDataSetChanged();
950             if (mFilterOptions.size() == 1) {
951                 if (DEBUG) {
952                     Log.d(TAG, "Auto selecting filter " + filter + " " + mContext.getText(
953                             filter.getTitle()));
954                 }
955                 mManageApplications.mFilterSpinner.setSelection(0);
956                 mManageApplications.onItemSelected(null, null, 0, 0);
957             }
958             if (mFilterOptions.size() > 1) {
959                 final AppFilterItem previousFilter = AppFilterRegistry.getInstance().get(
960                         mManageApplications.mFilterType);
961                 final int index = mFilterOptions.indexOf(previousFilter);
962                 if (index != -1) {
963                     mManageApplications.mFilterSpinner.setSelection(index);
964                     mManageApplications.onItemSelected(null, null, index, 0);
965                 }
966             }
967         }
968 
disableFilter(@ppFilterRegistry.FilterType int filterType)969         public void disableFilter(@AppFilterRegistry.FilterType int filterType) {
970             final AppFilterItem filter = AppFilterRegistry.getInstance().get(filterType);
971             if (!mFilterOptions.remove(filter)) {
972                 return;
973             }
974             if (DEBUG) {
975                 Log.d(TAG, "Disabling filter " + filter + " " + mContext.getText(
976                         filter.getTitle()));
977             }
978             Collections.sort(mFilterOptions);
979             updateFilterView(mFilterOptions.size() > 1);
980             notifyDataSetChanged();
981             if (mManageApplications.mFilter == filter) {
982                 if (mFilterOptions.size() > 0) {
983                     if (DEBUG) {
984                         Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0)
985                                 + mContext.getText(mFilterOptions.get(0).getTitle()));
986                     }
987                     mManageApplications.mFilterSpinner.setSelection(0);
988                     mManageApplications.onItemSelected(null, null, 0, 0);
989                 }
990             }
991         }
992 
993         @Override
getCount()994         public int getCount() {
995             return mFilterOptions.size();
996         }
997 
998         @Override
getItem(int position)999         public CharSequence getItem(int position) {
1000             return mContext.getText(mFilterOptions.get(position).getTitle());
1001         }
1002 
1003         @VisibleForTesting
updateFilterView(boolean hasFilter)1004         void updateFilterView(boolean hasFilter) {
1005             // If we need to add a floating filter in this screen, we should have an extra top
1006             // padding for putting floating filter view. Otherwise, the content of list will be
1007             // overlapped by floating filter.
1008             if (hasFilter) {
1009                 mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE);
1010             } else {
1011                 mManageApplications.mSpinnerHeader.setVisibility(View.GONE);
1012             }
1013         }
1014     }
1015 
1016     static class ApplicationsAdapter extends RecyclerView.Adapter<ApplicationViewHolder>
1017             implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback {
1018 
1019         private static final String STATE_LAST_SCROLL_INDEX = "state_last_scroll_index";
1020         private static final int VIEW_TYPE_APP = 0;
1021         private static final int VIEW_TYPE_EXTRA_VIEW = 1;
1022 
1023         private final ApplicationsState mState;
1024         private final ApplicationsState.Session mSession;
1025         private final ManageApplications mManageApplications;
1026         private final Context mContext;
1027         private final AppStateBaseBridge mExtraInfoBridge;
1028         private final LoadingViewController mLoadingViewController;
1029         private final IconDrawableFactory mIconDrawableFactory;
1030 
1031         private AppFilterItem mAppFilter;
1032         private ArrayList<ApplicationsState.AppEntry> mEntries;
1033         private ArrayList<ApplicationsState.AppEntry> mOriginalEntries;
1034         private boolean mResumed;
1035         private int mLastSortMode = -1;
1036         private int mWhichSize = SIZE_TOTAL;
1037         private AppFilter mCompositeFilter;
1038         private boolean mHasReceivedLoadEntries;
1039         private boolean mHasReceivedBridgeCallback;
1040         private SearchFilter mSearchFilter;
1041         private PowerAllowlistBackend mBackend;
1042 
1043         // This is to remember and restore the last scroll position when this
1044         // fragment is paused. We need this special handling because app entries are added gradually
1045         // when we rebuild the list after the user made some changes, like uninstalling an app.
1046         private int mLastIndex = -1;
1047 
1048         @VisibleForTesting
1049         OnScrollListener mOnScrollListener;
1050         private RecyclerView mRecyclerView;
1051 
1052 
ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, AppFilterItem appFilter, Bundle savedInstanceState)1053         public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
1054                 AppFilterItem appFilter, Bundle savedInstanceState) {
1055             setHasStableIds(true);
1056             mState = state;
1057             mSession = state.newSession(this);
1058             mManageApplications = manageApplications;
1059             mLoadingViewController = new LoadingViewController(
1060                     mManageApplications.mLoadingContainer,
1061                     mManageApplications.mRecyclerView,
1062                     mManageApplications.mEmptyView
1063             );
1064             mContext = manageApplications.getActivity();
1065             mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
1066             mAppFilter = appFilter;
1067             mBackend = PowerAllowlistBackend.getInstance(mContext);
1068             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1069                 mExtraInfoBridge = new AppStateNotificationBridge(mContext, mState, this,
1070                         manageApplications.mUsageStatsManager,
1071                         manageApplications.mUserManager,
1072                         manageApplications.mNotificationBackend);
1073             } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
1074                 mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
1075             } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
1076                 mBackend.refreshList();
1077                 mExtraInfoBridge = new AppStatePowerBridge(mContext, mState, this);
1078             } else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) {
1079                 mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
1080             } else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
1081                 mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
1082             } else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) {
1083                 mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
1084             } else if (mManageApplications.mListType == LIST_TYPE_WIFI_ACCESS) {
1085                 mExtraInfoBridge = new AppStateChangeWifiStateBridge(mContext, mState, this);
1086             } else if (mManageApplications.mListType == LIST_MANAGE_EXTERNAL_STORAGE) {
1087                 mExtraInfoBridge = new AppStateManageExternalStorageBridge(mContext, mState, this);
1088             } else if (mManageApplications.mListType == LIST_TYPE_ALARMS_AND_REMINDERS) {
1089                 mExtraInfoBridge = new AppStateAlarmsAndRemindersBridge(mContext, mState, this);
1090             } else if (mManageApplications.mListType == LIST_TYPE_MEDIA_MANAGEMENT_APPS) {
1091                 mExtraInfoBridge = new AppStateMediaManagementAppsBridge(mContext, mState, this);
1092             } else {
1093                 mExtraInfoBridge = null;
1094             }
1095             if (savedInstanceState != null) {
1096                 mLastIndex = savedInstanceState.getInt(STATE_LAST_SCROLL_INDEX);
1097             }
1098         }
1099 
1100         @Override
onAttachedToRecyclerView(@onNull RecyclerView recyclerView)1101         public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
1102             super.onAttachedToRecyclerView(recyclerView);
1103             mRecyclerView = recyclerView;
1104             mOnScrollListener = new OnScrollListener(this);
1105             mRecyclerView.addOnScrollListener(mOnScrollListener);
1106         }
1107 
1108         @Override
onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)1109         public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
1110             super.onDetachedFromRecyclerView(recyclerView);
1111             mRecyclerView.removeOnScrollListener(mOnScrollListener);
1112             mOnScrollListener = null;
1113             mRecyclerView = null;
1114         }
1115 
setCompositeFilter(AppFilter compositeFilter)1116         public void setCompositeFilter(AppFilter compositeFilter) {
1117             mCompositeFilter = compositeFilter;
1118             rebuild();
1119         }
1120 
setFilter(AppFilterItem appFilter)1121         public void setFilter(AppFilterItem appFilter) {
1122             mAppFilter = appFilter;
1123 
1124             // Notification filters require resorting the list
1125             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1126                 if (FILTER_APPS_FREQUENT == appFilter.getFilterType()) {
1127                     rebuild(R.id.sort_order_frequent_notification);
1128                 } else if (FILTER_APPS_RECENT == appFilter.getFilterType()) {
1129                     rebuild(R.id.sort_order_recent_notification);
1130                 } else if (FILTER_APPS_BLOCKED == appFilter.getFilterType()) {
1131                     rebuild(R.id.sort_order_alpha);
1132                 } else {
1133                     rebuild(R.id.sort_order_alpha);
1134                 }
1135             } else {
1136                 rebuild();
1137             }
1138         }
1139 
resume(int sort)1140         public void resume(int sort) {
1141             if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
1142             if (!mResumed) {
1143                 mResumed = true;
1144                 mSession.onResume();
1145                 mLastSortMode = sort;
1146                 if (mExtraInfoBridge != null) {
1147                     mExtraInfoBridge.resume();
1148                 }
1149                 rebuild();
1150             } else {
1151                 rebuild(sort);
1152             }
1153         }
1154 
pause()1155         public void pause() {
1156             if (mResumed) {
1157                 mResumed = false;
1158                 mSession.onPause();
1159                 if (mExtraInfoBridge != null) {
1160                     mExtraInfoBridge.pause();
1161                 }
1162             }
1163         }
1164 
onSaveInstanceState(Bundle outState)1165         public void onSaveInstanceState(Bundle outState) {
1166             // Record the current scroll position before pausing.
1167             final LinearLayoutManager layoutManager =
1168                     (LinearLayoutManager) mManageApplications.mRecyclerView.getLayoutManager();
1169             outState.putInt(STATE_LAST_SCROLL_INDEX, layoutManager.findFirstVisibleItemPosition());
1170         }
1171 
release()1172         public void release() {
1173             mSession.onDestroy();
1174             if (mExtraInfoBridge != null) {
1175                 mExtraInfoBridge.release();
1176             }
1177         }
1178 
rebuild(int sort)1179         public void rebuild(int sort) {
1180             if (sort == mLastSortMode) {
1181                 return;
1182             }
1183             mManageApplications.mSortOrder = sort;
1184             mLastSortMode = sort;
1185             rebuild();
1186         }
1187 
1188         @Override
onCreateViewHolder(ViewGroup parent, int viewType)1189         public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1190             final View view;
1191             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1192                 view = ApplicationViewHolder.newView(parent, true /* twoTarget */);
1193             } else {
1194                 view = ApplicationViewHolder.newView(parent, false /* twoTarget */);
1195             }
1196             return new ApplicationViewHolder(view);
1197         }
1198 
1199         @Override
getItemViewType(int position)1200         public int getItemViewType(int position) {
1201             return VIEW_TYPE_APP;
1202         }
1203 
rebuild()1204         public void rebuild() {
1205             if (!mHasReceivedLoadEntries
1206                     || (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) {
1207                 // Don't rebuild the list until all the app entries are loaded.
1208                 if (DEBUG) {
1209                     Log.d(TAG, "Not rebuilding until all the app entries loaded."
1210                             + " !mHasReceivedLoadEntries=" + !mHasReceivedLoadEntries
1211                             + " !mExtraInfoBridgeNull=" + (mExtraInfoBridge != null)
1212                             + " !mHasReceivedBridgeCallback=" + !mHasReceivedBridgeCallback);
1213                 }
1214                 return;
1215             }
1216             ApplicationsState.AppFilter filterObj;
1217             Comparator<AppEntry> comparatorObj;
1218             boolean emulated = Environment.isExternalStorageEmulated();
1219             if (emulated) {
1220                 mWhichSize = SIZE_TOTAL;
1221             } else {
1222                 mWhichSize = SIZE_INTERNAL;
1223             }
1224             filterObj = mAppFilter.getFilter();
1225             if (mCompositeFilter != null) {
1226                 filterObj = new CompoundFilter(filterObj, mCompositeFilter);
1227             }
1228             if (!mManageApplications.mShowSystem) {
1229                 if (LIST_TYPES_WITH_INSTANT.contains(mManageApplications.mListType)) {
1230                     filterObj = new CompoundFilter(filterObj,
1231                             ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT);
1232                 } else {
1233                     filterObj = new CompoundFilter(filterObj,
1234                             ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
1235                 }
1236             }
1237             if (mLastSortMode == R.id.sort_order_size) {
1238                 switch (mWhichSize) {
1239                     case SIZE_INTERNAL:
1240                         comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
1241                         break;
1242                     case SIZE_EXTERNAL:
1243                         comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
1244                         break;
1245                     default:
1246                         comparatorObj = ApplicationsState.SIZE_COMPARATOR;
1247                         break;
1248                 }
1249             } else if (mLastSortMode == R.id.sort_order_recent_notification) {
1250                 comparatorObj = AppStateNotificationBridge.RECENT_NOTIFICATION_COMPARATOR;
1251             } else if (mLastSortMode == R.id.sort_order_frequent_notification) {
1252                 comparatorObj = AppStateNotificationBridge.FREQUENCY_NOTIFICATION_COMPARATOR;
1253             } else {
1254                 comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
1255             }
1256 
1257             final AppFilter finalFilterObj = new CompoundFilter(filterObj,
1258                     ApplicationsState.FILTER_NOT_HIDE);
1259             ThreadUtils.postOnBackgroundThread(() -> {
1260                 mSession.rebuild(finalFilterObj, comparatorObj, false);
1261             });
1262         }
1263 
1264         @VisibleForTesting
filterSearch(String query)1265         void filterSearch(String query) {
1266             if (mSearchFilter == null) {
1267                 mSearchFilter = new SearchFilter();
1268             }
1269             // If we haven't load apps list completely, don't filter anything.
1270             if (mOriginalEntries == null) {
1271                 Log.w(TAG, "Apps haven't loaded completely yet, so nothing can be filtered");
1272                 return;
1273             }
1274             mSearchFilter.filter(query);
1275         }
1276 
packageNameEquals(PackageItemInfo info1, PackageItemInfo info2)1277         private static boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) {
1278             if (info1 == null || info2 == null) {
1279                 return false;
1280             }
1281             if (info1.packageName == null || info2.packageName == null) {
1282                 return false;
1283             }
1284             return info1.packageName.equals(info2.packageName);
1285         }
1286 
removeDuplicateIgnoringUser( ArrayList<ApplicationsState.AppEntry> entries)1287         private ArrayList<ApplicationsState.AppEntry> removeDuplicateIgnoringUser(
1288                 ArrayList<ApplicationsState.AppEntry> entries) {
1289             int size = entries.size();
1290             // returnList will not have more entries than entries
1291             ArrayList<ApplicationsState.AppEntry> returnEntries = new ArrayList<>(size);
1292 
1293             // assume appinfo of same package but different users are grouped together
1294             PackageItemInfo lastInfo = null;
1295             for (int i = 0; i < size; i++) {
1296                 AppEntry appEntry = entries.get(i);
1297                 PackageItemInfo info = appEntry.info;
1298                 if (!packageNameEquals(lastInfo, appEntry.info)) {
1299                     returnEntries.add(appEntry);
1300                 }
1301                 lastInfo = info;
1302             }
1303             returnEntries.trimToSize();
1304             return returnEntries;
1305         }
1306 
1307         @Override
onRebuildComplete(ArrayList<AppEntry> entries)1308         public void onRebuildComplete(ArrayList<AppEntry> entries) {
1309             if (DEBUG) {
1310                 Log.d(TAG, "onRebuildComplete size=" + entries.size());
1311             }
1312             final int filterType = mAppFilter.getFilterType();
1313             if (filterType == FILTER_APPS_POWER_ALLOWLIST
1314                     || filterType == FILTER_APPS_POWER_ALLOWLIST_ALL) {
1315                 entries = removeDuplicateIgnoringUser(entries);
1316             }
1317             mEntries = entries;
1318             mOriginalEntries = entries;
1319             notifyDataSetChanged();
1320             if (getItemCount() == 0) {
1321                 mLoadingViewController.showEmpty(false /* animate */);
1322             } else {
1323                 mLoadingViewController.showContent(false /* animate */);
1324 
1325                 if (mManageApplications.mSearchView != null
1326                         && mManageApplications.mSearchView.isVisibleToUser()) {
1327                     final CharSequence query = mManageApplications.mSearchView.getQuery();
1328                     if (!TextUtils.isEmpty(query)) {
1329                         filterSearch(query.toString());
1330                     }
1331                 }
1332             }
1333             // Restore the last scroll position if the number of entries added so far is bigger than
1334             // it.
1335             if (mLastIndex != -1 && getItemCount() > mLastIndex) {
1336                 mManageApplications.mRecyclerView.getLayoutManager().scrollToPosition(mLastIndex);
1337                 mLastIndex = -1;
1338             }
1339 
1340             if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
1341                 // No enabled or disabled filters for usage access.
1342                 return;
1343             }
1344 
1345             mManageApplications.setHasDisabled(mState.haveDisabledApps());
1346             mManageApplications.setHasInstant(mState.haveInstantApps());
1347         }
1348 
1349         @VisibleForTesting
updateLoading()1350         void updateLoading() {
1351             final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
1352             if (appLoaded) {
1353                 mLoadingViewController.showContent(false /* animate */);
1354             } else {
1355                 mLoadingViewController.showLoadingViewDelayed();
1356             }
1357         }
1358 
1359         @Override
onExtraInfoUpdated()1360         public void onExtraInfoUpdated() {
1361             mHasReceivedBridgeCallback = true;
1362             rebuild();
1363         }
1364 
1365         @Override
onRunningStateChanged(boolean running)1366         public void onRunningStateChanged(boolean running) {
1367             mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running);
1368         }
1369 
1370         @Override
onPackageListChanged()1371         public void onPackageListChanged() {
1372             rebuild();
1373         }
1374 
1375         @Override
onPackageIconChanged()1376         public void onPackageIconChanged() {
1377             // We ensure icons are loaded when their item is displayed, so
1378             // don't care about icons loaded in the background.
1379         }
1380 
1381         @Override
onLoadEntriesCompleted()1382         public void onLoadEntriesCompleted() {
1383             mHasReceivedLoadEntries = true;
1384             // We may have been skipping rebuilds until this came in, trigger one now.
1385             rebuild();
1386         }
1387 
1388         @Override
onPackageSizeChanged(String packageName)1389         public void onPackageSizeChanged(String packageName) {
1390             if (mEntries == null) {
1391                 return;
1392             }
1393             final int size = mEntries.size();
1394             for (int i = 0; i < size; i++) {
1395                 final AppEntry entry = mEntries.get(i);
1396                 final ApplicationInfo info = entry.info;
1397                 if (info == null && !TextUtils.equals(packageName, info.packageName)) {
1398                     continue;
1399                 }
1400                 if (TextUtils.equals(mManageApplications.mCurrentPkgName, info.packageName)) {
1401                     // We got the size information for the last app the
1402                     // user viewed, and are sorting by size...  they may
1403                     // have cleared data, so we immediately want to resort
1404                     // the list with the new size to reflect it to the user.
1405                     rebuild();
1406                     return;
1407                 } else {
1408                     mOnScrollListener.postNotifyItemChange(i);
1409                 }
1410             }
1411         }
1412 
1413         @Override
onLauncherInfoChanged()1414         public void onLauncherInfoChanged() {
1415             if (!mManageApplications.mShowSystem) {
1416                 rebuild();
1417             }
1418         }
1419 
1420         @Override
onAllSizesComputed()1421         public void onAllSizesComputed() {
1422             if (mLastSortMode == R.id.sort_order_size) {
1423                 rebuild();
1424             }
1425         }
1426 
1427         @Override
getItemCount()1428         public int getItemCount() {
1429             if (mEntries == null) {
1430                 return 0;
1431             }
1432             return mEntries.size();
1433         }
1434 
getApplicationCount()1435         public int getApplicationCount() {
1436             return mEntries != null ? mEntries.size() : 0;
1437         }
1438 
getAppEntry(int position)1439         public AppEntry getAppEntry(int position) {
1440             return mEntries.get(position);
1441         }
1442 
1443         @Override
getItemId(int position)1444         public long getItemId(int position) {
1445             if (position == mEntries.size()) {
1446                 return -1;
1447             }
1448             return mEntries.get(position).id;
1449         }
1450 
isEnabled(int position)1451         public boolean isEnabled(int position) {
1452             if (getItemViewType(position) == VIEW_TYPE_EXTRA_VIEW
1453                     || mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
1454                 return true;
1455             }
1456             ApplicationsState.AppEntry entry = mEntries.get(position);
1457 
1458             return !mBackend.isSysAllowlisted(entry.info.packageName)
1459                     && !mBackend.isDefaultActiveApp(entry.info.packageName);
1460         }
1461 
1462         @Override
onBindViewHolder(ApplicationViewHolder holder, int position)1463         public void onBindViewHolder(ApplicationViewHolder holder, int position) {
1464             // Bind the data efficiently with the holder
1465             final ApplicationsState.AppEntry entry = mEntries.get(position);
1466             synchronized (entry) {
1467                 mState.ensureLabelDescription(entry);
1468                 holder.setTitle(entry.label, entry.labelDescription);
1469                 mState.ensureIcon(entry);
1470                 holder.setIcon(entry.icon);
1471                 updateSummary(holder, entry);
1472                 updateSwitch(holder, entry);
1473                 holder.updateDisableView(entry.info);
1474             }
1475             holder.setEnabled(isEnabled(position));
1476 
1477             holder.itemView.setOnClickListener(mManageApplications);
1478         }
1479 
updateSummary(ApplicationViewHolder holder, AppEntry entry)1480         private void updateSummary(ApplicationViewHolder holder, AppEntry entry) {
1481             switch (mManageApplications.mListType) {
1482                 case LIST_TYPE_NOTIFICATION:
1483                     if (entry.extraInfo != null
1484                             && entry.extraInfo instanceof NotificationsSentState) {
1485                         holder.setSummary(AppStateNotificationBridge.getSummary(mContext,
1486                                 (NotificationsSentState) entry.extraInfo, mLastSortMode));
1487                     } else {
1488                         holder.setSummary(null);
1489                     }
1490                     break;
1491                 case LIST_TYPE_USAGE_ACCESS:
1492                     if (entry.extraInfo != null) {
1493                         holder.setSummary(
1494                                 (new UsageState((PermissionState) entry.extraInfo)).isPermissible()
1495                                         ? R.string.app_permission_summary_allowed
1496                                         : R.string.app_permission_summary_not_allowed);
1497                     } else {
1498                         holder.setSummary(null);
1499                     }
1500                     break;
1501                 case LIST_TYPE_HIGH_POWER:
1502                     holder.setSummary(HighPowerDetail.getSummary(mContext, entry));
1503                     break;
1504                 case LIST_TYPE_OVERLAY:
1505                     holder.setSummary(DrawOverlayDetails.getSummary(mContext, entry));
1506                     break;
1507                 case LIST_TYPE_WRITE_SETTINGS:
1508                     holder.setSummary(WriteSettingsDetails.getSummary(mContext, entry));
1509                     break;
1510                 case LIST_TYPE_MANAGE_SOURCES:
1511                     holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry));
1512                     break;
1513                 case LIST_TYPE_WIFI_ACCESS:
1514                     holder.setSummary(ChangeWifiStateDetails.getSummary(mContext, entry));
1515                     break;
1516                 case LIST_MANAGE_EXTERNAL_STORAGE:
1517                     holder.setSummary(ManageExternalStorageDetails.getSummary(mContext, entry));
1518                     break;
1519                 case LIST_TYPE_ALARMS_AND_REMINDERS:
1520                     holder.setSummary(AlarmsAndRemindersDetails.getSummary(mContext, entry));
1521                     break;
1522                 case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
1523                     holder.setSummary(MediaManagementAppsDetails.getSummary(mContext, entry));
1524                     break;
1525                 default:
1526                     holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
1527                     break;
1528             }
1529         }
1530 
updateSwitch(ApplicationViewHolder holder, AppEntry entry)1531         private void updateSwitch(ApplicationViewHolder holder, AppEntry entry) {
1532             switch (mManageApplications.mListType) {
1533                 case LIST_TYPE_NOTIFICATION:
1534                     holder.updateSwitch(((AppStateNotificationBridge) mExtraInfoBridge)
1535                                     .getSwitchOnCheckedListener(entry),
1536                             AppStateNotificationBridge.enableSwitch(entry),
1537                             AppStateNotificationBridge.checkSwitch(entry));
1538                     if (entry.extraInfo != null
1539                             && entry.extraInfo instanceof NotificationsSentState) {
1540                         holder.setSummary(AppStateNotificationBridge.getSummary(mContext,
1541                                 (NotificationsSentState) entry.extraInfo, mLastSortMode));
1542                     } else {
1543                         holder.setSummary(null);
1544                     }
1545                     break;
1546             }
1547         }
1548 
1549         public static class OnScrollListener extends RecyclerView.OnScrollListener {
1550             private int mScrollState = SCROLL_STATE_IDLE;
1551             private boolean mDelayNotifyDataChange;
1552             private ApplicationsAdapter mAdapter;
1553 
OnScrollListener(ApplicationsAdapter adapter)1554             public OnScrollListener(ApplicationsAdapter adapter) {
1555                 mAdapter = adapter;
1556             }
1557 
1558             @Override
onScrollStateChanged(@onNull RecyclerView recyclerView, int newState)1559             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
1560                 mScrollState = newState;
1561                 if (mScrollState == SCROLL_STATE_IDLE && mDelayNotifyDataChange) {
1562                     mDelayNotifyDataChange = false;
1563                     mAdapter.notifyDataSetChanged();
1564                 }
1565             }
1566 
postNotifyItemChange(int index)1567             public void postNotifyItemChange(int index) {
1568                 if (mScrollState == SCROLL_STATE_IDLE) {
1569                     mAdapter.notifyItemChanged(index);
1570                 } else {
1571                     mDelayNotifyDataChange = true;
1572                 }
1573             }
1574         }
1575 
1576         /**
1577          * An array filter that constrains the content of the array adapter with a substring.
1578          * Item that does not contains the specified substring will be removed from the list.</p>
1579          */
1580         private class SearchFilter extends Filter {
1581             @WorkerThread
1582             @Override
performFiltering(CharSequence query)1583             protected FilterResults performFiltering(CharSequence query) {
1584                 final ArrayList<ApplicationsState.AppEntry> matchedEntries;
1585                 if (TextUtils.isEmpty(query)) {
1586                     matchedEntries = mOriginalEntries;
1587                 } else {
1588                     matchedEntries = new ArrayList<>();
1589                     for (ApplicationsState.AppEntry entry : mOriginalEntries) {
1590                         if (entry.label.toLowerCase().contains(query.toString().toLowerCase())) {
1591                             matchedEntries.add(entry);
1592                         }
1593                     }
1594                 }
1595                 final FilterResults results = new FilterResults();
1596                 results.values = matchedEntries;
1597                 results.count = matchedEntries.size();
1598                 return results;
1599             }
1600 
1601             @Override
publishResults(CharSequence constraint, FilterResults results)1602             protected void publishResults(CharSequence constraint, FilterResults results) {
1603                 mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
1604                 notifyDataSetChanged();
1605             }
1606         }
1607     }
1608 }
1609