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