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_DRAGGING;
20 import static androidx.recyclerview.widget.RecyclerView.SCROLL_STATE_IDLE;
21 
22 import static com.android.internal.jank.InteractionJankMonitor.CUJ_SETTINGS_PAGE_SCROLL;
23 import static com.android.settings.ChangeIds.CHANGE_RESTRICT_SAW_INTENT;
24 import static com.android.settings.Utils.PROPERTY_DELETE_ALL_APP_CLONES_ENABLED;
25 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ALL;
26 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_OPTIMIZED;
27 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_RESTRICTED;
28 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BATTERY_UNRESTRICTED;
29 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_BLOCKED;
30 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_DISABLED;
31 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_ENABLED;
32 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_FREQUENT;
33 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_INSTANT;
34 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_PERSONAL;
35 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_POWER_ALLOWLIST;
36 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_POWER_ALLOWLIST_ALL;
37 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_RECENT;
38 import static com.android.settings.applications.manageapplications.AppFilterRegistry.FILTER_APPS_WORK;
39 import static com.android.settings.search.actionbar.SearchMenuController.MENU_SEARCH;
40 
41 import android.annotation.StringRes;
42 import android.app.Activity;
43 import android.app.ActivityManager;
44 import android.app.settings.SettingsEnums;
45 import android.app.usage.IUsageStatsManager;
46 import android.content.Context;
47 import android.content.Intent;
48 import android.content.pm.ApplicationInfo;
49 import android.content.pm.PackageItemInfo;
50 import android.content.pm.UserInfo;
51 import android.content.res.Configuration;
52 import android.graphics.drawable.Drawable;
53 import android.net.Uri;
54 import android.os.Build;
55 import android.os.Bundle;
56 import android.os.Environment;
57 import android.os.IBinder;
58 import android.os.IUserManager;
59 import android.os.RemoteException;
60 import android.os.ServiceManager;
61 import android.os.UserHandle;
62 import android.os.UserManager;
63 import android.preference.PreferenceFrameLayout;
64 import android.provider.DeviceConfig;
65 import android.provider.Settings;
66 import android.text.TextUtils;
67 import android.util.ArraySet;
68 import android.util.IconDrawableFactory;
69 import android.util.Log;
70 import android.view.LayoutInflater;
71 import android.view.Menu;
72 import android.view.MenuInflater;
73 import android.view.MenuItem;
74 import android.view.View;
75 import android.view.ViewGroup;
76 import android.view.inputmethod.InputMethodManager;
77 import android.widget.AdapterView;
78 import android.widget.AdapterView.OnItemSelectedListener;
79 import android.widget.Filter;
80 import android.widget.FrameLayout;
81 import android.widget.SearchView;
82 import android.widget.Spinner;
83 import android.widget.Toast;
84 
85 import androidx.annotation.NonNull;
86 import androidx.annotation.Nullable;
87 import androidx.annotation.VisibleForTesting;
88 import androidx.annotation.WorkerThread;
89 import androidx.coordinatorlayout.widget.CoordinatorLayout;
90 import androidx.core.view.ViewCompat;
91 import androidx.recyclerview.widget.LinearLayoutManager;
92 import androidx.recyclerview.widget.RecyclerView;
93 
94 import com.android.internal.compat.IPlatformCompat;
95 import com.android.internal.jank.InteractionJankMonitor;
96 import com.android.settings.R;
97 import com.android.settings.Settings.AlarmsAndRemindersActivity;
98 import com.android.settings.Settings.AppBatteryUsageActivity;
99 import com.android.settings.Settings.ChangeNfcTagAppsActivity;
100 import com.android.settings.Settings.ChangeWifiStateActivity;
101 import com.android.settings.Settings.ClonedAppsListActivity;
102 import com.android.settings.Settings.HighPowerApplicationsActivity;
103 import com.android.settings.Settings.LongBackgroundTasksActivity;
104 import com.android.settings.Settings.ManageExternalSourcesActivity;
105 import com.android.settings.Settings.ManageExternalStorageActivity;
106 import com.android.settings.Settings.MediaManagementAppsActivity;
107 import com.android.settings.Settings.NotificationAppListActivity;
108 import com.android.settings.Settings.NotificationReviewPermissionsActivity;
109 import com.android.settings.Settings.OverlaySettingsActivity;
110 import com.android.settings.Settings.TurnScreenOnSettingsActivity;
111 import com.android.settings.Settings.UsageAccessSettingsActivity;
112 import com.android.settings.Settings.WriteSettingsActivity;
113 import com.android.settings.SettingsActivity;
114 import com.android.settings.Utils;
115 import com.android.settings.applications.AppInfoBase;
116 import com.android.settings.applications.AppStateAlarmsAndRemindersBridge;
117 import com.android.settings.applications.AppStateAppBatteryUsageBridge;
118 import com.android.settings.applications.AppStateAppOpsBridge.PermissionState;
119 import com.android.settings.applications.AppStateBaseBridge;
120 import com.android.settings.applications.AppStateClonedAppsBridge;
121 import com.android.settings.applications.AppStateInstallAppsBridge;
122 import com.android.settings.applications.AppStateLocaleBridge;
123 import com.android.settings.applications.AppStateLongBackgroundTasksBridge;
124 import com.android.settings.applications.AppStateManageExternalStorageBridge;
125 import com.android.settings.applications.AppStateMediaManagementAppsBridge;
126 import com.android.settings.applications.AppStateNotificationBridge;
127 import com.android.settings.applications.AppStateNotificationBridge.NotificationsSentState;
128 import com.android.settings.applications.AppStateOverlayBridge;
129 import com.android.settings.applications.AppStatePowerBridge;
130 import com.android.settings.applications.AppStateTurnScreenOnBridge;
131 import com.android.settings.applications.AppStateUsageBridge;
132 import com.android.settings.applications.AppStateUsageBridge.UsageState;
133 import com.android.settings.applications.AppStateWriteSettingsBridge;
134 import com.android.settings.applications.AppStorageSettings;
135 import com.android.settings.applications.UsageAccessDetails;
136 import com.android.settings.applications.appinfo.AlarmsAndRemindersDetails;
137 import com.android.settings.applications.appinfo.AppInfoDashboardFragment;
138 import com.android.settings.applications.appinfo.AppLocaleDetails;
139 import com.android.settings.applications.appinfo.DrawOverlayDetails;
140 import com.android.settings.applications.appinfo.ExternalSourcesDetails;
141 import com.android.settings.applications.appinfo.LongBackgroundTasksDetails;
142 import com.android.settings.applications.appinfo.ManageExternalStorageDetails;
143 import com.android.settings.applications.appinfo.MediaManagementAppsDetails;
144 import com.android.settings.applications.appinfo.TurnScreenOnDetails;
145 import com.android.settings.applications.appinfo.WriteSettingsDetails;
146 import com.android.settings.core.InstrumentedFragment;
147 import com.android.settings.core.SubSettingLauncher;
148 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
149 import com.android.settings.fuelgauge.AdvancedPowerUsageDetail;
150 import com.android.settings.fuelgauge.HighPowerDetail;
151 import com.android.settings.localepicker.AppLocalePickerActivity;
152 import com.android.settings.nfc.AppStateNfcTagAppsBridge;
153 import com.android.settings.nfc.ChangeNfcTagAppsStateDetails;
154 import com.android.settings.notification.ConfigureNotificationSettings;
155 import com.android.settings.notification.NotificationBackend;
156 import com.android.settings.notification.app.AppNotificationSettings;
157 import com.android.settings.spa.SpaActivity;
158 import com.android.settings.spa.app.appinfo.AppInfoSettingsProvider;
159 import com.android.settings.spa.app.appinfo.CloneAppInfoSettingsProvider;
160 import com.android.settings.widget.LoadingViewController;
161 import com.android.settings.wifi.AppStateChangeWifiStateBridge;
162 import com.android.settings.wifi.ChangeWifiStateDetails;
163 import com.android.settingslib.RestrictedLockUtils;
164 import com.android.settingslib.RestrictedLockUtilsInternal;
165 import com.android.settingslib.applications.AppIconCacheManager;
166 import com.android.settingslib.applications.AppUtils;
167 import com.android.settingslib.applications.ApplicationsState;
168 import com.android.settingslib.applications.ApplicationsState.AppEntry;
169 import com.android.settingslib.applications.ApplicationsState.AppFilter;
170 import com.android.settingslib.applications.ApplicationsState.CompoundFilter;
171 import com.android.settingslib.applications.ApplicationsState.VolumeFilter;
172 import com.android.settingslib.fuelgauge.PowerAllowlistBackend;
173 import com.android.settingslib.utils.ThreadUtils;
174 import com.android.settingslib.widget.SettingsSpinnerAdapter;
175 
176 import com.google.android.material.appbar.AppBarLayout;
177 
178 import java.util.ArrayList;
179 import java.util.Arrays;
180 import java.util.Collections;
181 import java.util.Comparator;
182 import java.util.Set;
183 
184 /**
185  * Activity to pick an application that will be used to display installation information and
186  * options to uninstall/delete user data for system applications. This activity
187  * can be launched through Settings or via the ACTION_MANAGE_PACKAGE_STORAGE
188  * intent.
189  */
190 public class ManageApplications extends InstrumentedFragment
191         implements View.OnClickListener, OnItemSelectedListener, SearchView.OnQueryTextListener,
192         MenuItem.OnActionExpandListener {
193 
194     static final String TAG = "ManageApplications";
195     static final boolean DEBUG = Build.IS_DEBUGGABLE;
196 
197     // Intent extras.
198     public static final String EXTRA_CLASSNAME = "classname";
199     // Used for storage only.
200     public static final String EXTRA_VOLUME_UUID = "volumeUuid";
201     public static final String EXTRA_VOLUME_NAME = "volumeName";
202     public static final String EXTRA_STORAGE_TYPE = "storageType";
203     public static final String EXTRA_WORK_ID = "workId";
204 
205     private static final String EXTRA_SORT_ORDER = "sortOrder";
206     private static final String EXTRA_SHOW_SYSTEM = "showSystem";
207     private static final String EXTRA_HAS_ENTRIES = "hasEntries";
208     private static final String EXTRA_HAS_BRIDGE = "hasBridge";
209     private static final String EXTRA_FILTER_TYPE = "filterType";
210     @VisibleForTesting
211     static final String EXTRA_EXPAND_SEARCH_VIEW = "expand_search_view";
212 
213     // attributes used as keys when passing values to AppInfoDashboardFragment activity
214     public static final String APP_CHG = "chg";
215 
216     // constant value that can be used to check return code from sub activity.
217     private static final int INSTALLED_APP_DETAILS = 1;
218     private static final int ADVANCED_SETTINGS = 2;
219 
220     public static final int SIZE_TOTAL = 0;
221     public static final int SIZE_INTERNAL = 1;
222     public static final int SIZE_EXTERNAL = 2;
223 
224     // Storage types. Used to determine what the extra item in the list of preferences is.
225     public static final int STORAGE_TYPE_DEFAULT = 0; // Show all apps that are not categorized.
226     public static final int STORAGE_TYPE_LEGACY = 1;  // Show apps even if they can be categorized.
227 
228     // sort order
229     @VisibleForTesting
230     int mSortOrder = R.id.sort_order_alpha;
231 
232     // whether showing system apps.
233     private boolean mShowSystem;
234 
235     private ApplicationsState mApplicationsState;
236 
237     public int mListType;
238     private AppFilterItem mFilter;
239     private ApplicationsAdapter mApplications;
240 
241     private View mLoadingContainer;
242     private SearchView mSearchView;
243 
244     // Size resource used for packages whose size computation failed for some reason
245     CharSequence mInvalidSizeStr;
246 
247     private String mCurrentPkgName;
248     private int mCurrentUid;
249 
250     private Menu mOptionsMenu;
251 
252     public static final int LIST_TYPE_NONE = -1;
253     public static final int LIST_TYPE_MAIN = 0;
254     public static final int LIST_TYPE_NOTIFICATION = 1;
255     public static final int LIST_TYPE_STORAGE = 3;
256     public static final int LIST_TYPE_USAGE_ACCESS = 4;
257     public static final int LIST_TYPE_HIGH_POWER = 5;
258     public static final int LIST_TYPE_OVERLAY = 6;
259     public static final int LIST_TYPE_WRITE_SETTINGS = 7;
260     public static final int LIST_TYPE_MANAGE_SOURCES = 8;
261     public static final int LIST_TYPE_GAMES = 9;
262     public static final int LIST_TYPE_WIFI_ACCESS = 10;
263     public static final int LIST_MANAGE_EXTERNAL_STORAGE = 11;
264     public static final int LIST_TYPE_ALARMS_AND_REMINDERS = 12;
265     public static final int LIST_TYPE_MEDIA_MANAGEMENT_APPS = 13;
266     public static final int LIST_TYPE_APPS_LOCALE = 14;
267     public static final int LIST_TYPE_BATTERY_OPTIMIZATION = 15;
268     public static final int LIST_TYPE_LONG_BACKGROUND_TASKS = 16;
269     public static final int LIST_TYPE_CLONED_APPS = 17;
270     public static final int LIST_TYPE_NFC_TAG_APPS = 18;
271     public static final int LIST_TYPE_TURN_SCREEN_ON = 19;
272     public static final int LIST_TYPE_USER_ASPECT_RATIO_APPS = 20;
273 
274     // List types that should show instant apps.
275     public static final Set<Integer> LIST_TYPES_WITH_INSTANT = new ArraySet<>(Arrays.asList(
276             LIST_TYPE_MAIN,
277             LIST_TYPE_STORAGE));
278 
279     @VisibleForTesting
280     View mSpinnerHeader;
281     @VisibleForTesting
282     FilterSpinnerAdapter mFilterAdapter;
283     @VisibleForTesting
284     RecyclerView mRecyclerView;
285     // Whether or not search view is expanded.
286     @VisibleForTesting
287     boolean mExpandSearch;
288 
289     private View mRootView;
290     private Spinner mFilterSpinner;
291     private IUsageStatsManager mUsageStatsManager;
292     private UserManager mUserManager;
293     private NotificationBackend mNotificationBackend;
294     private ResetAppsHelper mResetAppsHelper;
295     private String mVolumeUuid;
296     private int mStorageType;
297     private boolean mIsWorkOnly;
298     private boolean mIsPrivateProfileOnly;
299     private int mWorkUserId;
300     private boolean mIsPersonalOnly;
301     private View mEmptyView;
302     private int mFilterType;
303     private AppBarLayout mAppBarLayout;
304 
305     @Override
onAttach(Context context)306     public void onAttach(Context context) {
307         super.onAttach(context);
308 
309         mListType = getListType();
310         final String spaDestination = ManageApplicationsUtil.getSpaDestination(context, mListType);
311         if (spaDestination != null) {
312             SpaActivity.startSpaActivity(context, spaDestination);
313             getActivity().finish();
314         }
315     }
316 
getListType()317     private int getListType() {
318         Bundle args = getArguments();
319         final String className = getClassName(getActivity().getIntent(), args);
320         return ManageApplicationsUtil.LIST_TYPE_MAP.getOrDefault(className, LIST_TYPE_MAIN);
321     }
322 
323     @Override
onCreate(Bundle savedInstanceState)324     public void onCreate(Bundle savedInstanceState) {
325         super.onCreate(savedInstanceState);
326         final Activity activity = getActivity();
327         if (activity.isFinishing()) {
328             return;
329         }
330         setHasOptionsMenu(true);
331         mUserManager = activity.getSystemService(UserManager.class);
332         mApplicationsState = ApplicationsState.getInstance(activity.getApplication());
333 
334         Intent intent = activity.getIntent();
335         Bundle args = getArguments();
336         final int screenTitle = getTitleResId(intent, args);
337         final String className = getClassName(intent, args);
338         switch (mListType) {
339             case LIST_TYPE_STORAGE:
340                 if (args != null && args.containsKey(EXTRA_VOLUME_UUID)) {
341                     mVolumeUuid = args.getString(EXTRA_VOLUME_UUID);
342                     mStorageType = args.getInt(EXTRA_STORAGE_TYPE, STORAGE_TYPE_DEFAULT);
343                 } else {
344                     // No volume selected, display a normal list, sorted by size.
345                     mListType = LIST_TYPE_MAIN;
346                 }
347                 mSortOrder = R.id.sort_order_size;
348                 break;
349             case LIST_TYPE_HIGH_POWER:
350                 // Default to showing system.
351                 mShowSystem = true;
352                 break;
353             case LIST_TYPE_OVERLAY:
354                 reportIfRestrictedSawIntent(intent);
355                 break;
356             case LIST_TYPE_GAMES:
357                 mSortOrder = R.id.sort_order_size;
358                 break;
359             case LIST_TYPE_NOTIFICATION:
360                 mUsageStatsManager = IUsageStatsManager.Stub.asInterface(
361                         ServiceManager.getService(Context.USAGE_STATS_SERVICE));
362                 mNotificationBackend = new NotificationBackend();
363                 mSortOrder = R.id.sort_order_recent_notification;
364                 if (className.equals(NotificationReviewPermissionsActivity.class.getName())) {
365                     // Special-case for a case where a user is directed to the all apps notification
366                     // preferences page via a notification prompt to review permissions settings.
367                     Settings.Global.putInt(getContext().getContentResolver(),
368                             Settings.Global.REVIEW_PERMISSIONS_NOTIFICATION_STATE,
369                             1);  // USER_INTERACTED
370                 }
371                 break;
372             case LIST_TYPE_NFC_TAG_APPS:
373                 mShowSystem = true;
374                 break;
375         }
376         final AppFilterRegistry appFilterRegistry = AppFilterRegistry.getInstance();
377         mFilter = appFilterRegistry.get(appFilterRegistry.getDefaultFilterType(mListType));
378         mIsPersonalOnly = args != null && args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
379                 == ProfileSelectFragment.ProfileType.PERSONAL;
380         mIsWorkOnly = args != null && args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
381                 == ProfileSelectFragment.ProfileType.WORK;
382         mIsPrivateProfileOnly = args != null && args.getInt(ProfileSelectFragment.EXTRA_PROFILE)
383                 == ProfileSelectFragment.ProfileType.PRIVATE;
384         mWorkUserId = args != null ? args.getInt(EXTRA_WORK_ID) : UserHandle.myUserId();
385         if (mIsWorkOnly && mWorkUserId == UserHandle.myUserId()) {
386             mWorkUserId = Utils.getManagedProfileId(mUserManager, UserHandle.myUserId());
387         }
388 
389         mExpandSearch = activity.getIntent().getBooleanExtra(EXTRA_EXPAND_SEARCH_VIEW, false);
390 
391         if (savedInstanceState != null) {
392             mSortOrder = savedInstanceState.getInt(EXTRA_SORT_ORDER, mSortOrder);
393             mShowSystem = savedInstanceState.getBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
394             mFilterType =
395                     savedInstanceState.getInt(EXTRA_FILTER_TYPE, AppFilterRegistry.FILTER_APPS_ALL);
396             mExpandSearch = savedInstanceState.getBoolean(EXTRA_EXPAND_SEARCH_VIEW);
397         }
398 
399         mInvalidSizeStr = activity.getText(R.string.invalid_size_value);
400 
401         mResetAppsHelper = new ResetAppsHelper(activity);
402 
403         if (screenTitle > 0) {
404             activity.setTitle(screenTitle);
405         }
406     }
407 
reportIfRestrictedSawIntent(Intent intent)408     private void reportIfRestrictedSawIntent(Intent intent) {
409         try {
410             Uri data = intent.getData();
411             if (data == null || !TextUtils.equals("package", data.getScheme())) {
412                 // Not a restricted intent
413                 return;
414             }
415             IBinder activityToken = getActivity().getActivityToken();
416             int callingUid = ActivityManager.getService().getLaunchedFromUid(activityToken);
417             if (callingUid == -1) {
418                 Log.w(TAG, "Error obtaining calling uid");
419                 return;
420             }
421             IPlatformCompat platformCompat = IPlatformCompat.Stub.asInterface(
422                     ServiceManager.getService(Context.PLATFORM_COMPAT_SERVICE));
423             if (platformCompat == null) {
424                 Log.w(TAG, "Error obtaining IPlatformCompat service");
425                 return;
426             }
427             platformCompat.reportChangeByUid(CHANGE_RESTRICT_SAW_INTENT, callingUid);
428         } catch (RemoteException e) {
429             Log.w(TAG, "Error reporting SAW intent restriction", e);
430         }
431     }
432 
433     @Override
onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)434     public View onCreateView(LayoutInflater inflater, ViewGroup container,
435             Bundle savedInstanceState) {
436         if (getActivity().isFinishing()) {
437             return null;
438         }
439         if (mListType == LIST_TYPE_OVERLAY && !Utils.isSystemAlertWindowEnabled(getContext())) {
440             mRootView = inflater.inflate(R.layout.manage_applications_apps_unsupported, null);
441             setHasOptionsMenu(false);
442             return mRootView;
443         }
444 
445         mRootView = inflater.inflate(R.layout.manage_applications_apps, null);
446         mLoadingContainer = mRootView.findViewById(R.id.loading_container);
447         mEmptyView = mRootView.findViewById(android.R.id.empty);
448         mRecyclerView = mRootView.findViewById(R.id.apps_list);
449 
450         mApplications = new ApplicationsAdapter(mApplicationsState, this, mFilter,
451                 savedInstanceState);
452         if (savedInstanceState != null) {
453             mApplications.mHasReceivedLoadEntries =
454                     savedInstanceState.getBoolean(EXTRA_HAS_ENTRIES, false);
455             mApplications.mHasReceivedBridgeCallback =
456                     savedInstanceState.getBoolean(EXTRA_HAS_BRIDGE, false);
457         }
458         mRecyclerView.setItemAnimator(null);
459         mRecyclerView.setLayoutManager(new LinearLayoutManager(
460                 getContext(), RecyclerView.VERTICAL, false /* reverseLayout */));
461         mRecyclerView.setAdapter(mApplications);
462 
463         // We have to do this now because PreferenceFrameLayout looks at it
464         // only when the view is added.
465         if (container instanceof PreferenceFrameLayout) {
466             ((PreferenceFrameLayout.LayoutParams) mRootView.getLayoutParams()).removeBorders = true;
467         }
468 
469         createHeader();
470 
471         mResetAppsHelper.onRestoreInstanceState(savedInstanceState);
472 
473         mAppBarLayout = getActivity().findViewById(R.id.app_bar);
474         autoSetCollapsingToolbarLayoutScrolling();
475 
476         return mRootView;
477     }
478 
479     @VisibleForTesting
createHeader()480     void createHeader() {
481         final Activity activity = getActivity();
482         final FrameLayout pinnedHeader = mRootView.findViewById(R.id.pinned_header);
483         mSpinnerHeader = activity.getLayoutInflater()
484                 .inflate(R.layout.manage_apps_filter_spinner, pinnedHeader, false);
485         mFilterSpinner = mSpinnerHeader.findViewById(R.id.filter_spinner);
486         mFilterAdapter = new FilterSpinnerAdapter(this);
487         mFilterSpinner.setAdapter(mFilterAdapter);
488         mFilterSpinner.setOnItemSelectedListener(this);
489         pinnedHeader.addView(mSpinnerHeader, 0);
490 
491         final AppFilterRegistry appFilterRegistry = AppFilterRegistry.getInstance();
492         final int filterType = appFilterRegistry.getDefaultFilterType(mListType);
493         mFilterAdapter.enableFilter(filterType);
494 
495         if (mListType == LIST_TYPE_MAIN) {
496             // Apply the personal and work filter only if new tab should be added
497             // for a given user profile. Else let it use the default all apps filter.
498             if (Utils.isNewTabNeeded(getActivity()) && !mIsWorkOnly && !mIsPersonalOnly) {
499                 mFilterAdapter.enableFilter(FILTER_APPS_PERSONAL);
500                 mFilterAdapter.enableFilter(FILTER_APPS_WORK);
501             }
502         }
503         if (mListType == LIST_TYPE_NOTIFICATION) {
504             mFilterAdapter.enableFilter(FILTER_APPS_RECENT);
505             mFilterAdapter.enableFilter(FILTER_APPS_FREQUENT);
506             mFilterAdapter.enableFilter(FILTER_APPS_BLOCKED);
507             mFilterAdapter.enableFilter(FILTER_APPS_ALL);
508         }
509         if (mListType == LIST_TYPE_HIGH_POWER) {
510             mFilterAdapter.enableFilter(FILTER_APPS_POWER_ALLOWLIST_ALL);
511         }
512         if (mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
513             mFilterAdapter.enableFilter(FILTER_APPS_ALL);
514             mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_UNRESTRICTED);
515             mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_OPTIMIZED);
516             mFilterAdapter.enableFilter(FILTER_APPS_BATTERY_RESTRICTED);
517         }
518 
519         setCompositeFilter();
520     }
521 
522     @VisibleForTesting
523     @Nullable
getCompositeFilter(int listType, int storageType, String volumeUuid)524     static AppFilter getCompositeFilter(int listType, int storageType, String volumeUuid) {
525         AppFilter filter = new VolumeFilter(volumeUuid);
526         if (listType == LIST_TYPE_STORAGE) {
527             if (storageType == STORAGE_TYPE_DEFAULT) {
528                 filter = new CompoundFilter(ApplicationsState.FILTER_APPS_EXCEPT_GAMES, filter);
529             }
530             return filter;
531         }
532         if (listType == LIST_TYPE_GAMES) {
533             return new CompoundFilter(ApplicationsState.FILTER_GAMES, filter);
534         }
535         return null;
536     }
537 
538     @Override
getMetricsCategory()539     public int getMetricsCategory() {
540         switch (mListType) {
541             case LIST_TYPE_MAIN:
542                 return SettingsEnums.MANAGE_APPLICATIONS;
543             case LIST_TYPE_NOTIFICATION:
544                 return SettingsEnums.MANAGE_APPLICATIONS_NOTIFICATIONS;
545             case LIST_TYPE_STORAGE:
546                 return SettingsEnums.APPLICATIONS_STORAGE_APPS;
547             case LIST_TYPE_GAMES:
548                 return SettingsEnums.APPLICATIONS_STORAGE_GAMES;
549             case LIST_TYPE_USAGE_ACCESS:
550                 return SettingsEnums.USAGE_ACCESS;
551             case LIST_TYPE_HIGH_POWER:
552                 return SettingsEnums.APPLICATIONS_HIGH_POWER_APPS;
553             case LIST_TYPE_OVERLAY:
554                 return SettingsEnums.SYSTEM_ALERT_WINDOW_APPS;
555             case LIST_TYPE_WRITE_SETTINGS:
556                 return SettingsEnums.MODIFY_SYSTEM_SETTINGS;
557             case LIST_TYPE_MANAGE_SOURCES:
558                 return SettingsEnums.MANAGE_EXTERNAL_SOURCES;
559             case LIST_TYPE_WIFI_ACCESS:
560                 return SettingsEnums.CONFIGURE_WIFI;
561             case LIST_MANAGE_EXTERNAL_STORAGE:
562                 return SettingsEnums.MANAGE_EXTERNAL_STORAGE;
563             case LIST_TYPE_ALARMS_AND_REMINDERS:
564                 return SettingsEnums.ALARMS_AND_REMINDERS;
565             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
566                 return SettingsEnums.MEDIA_MANAGEMENT_APPS;
567             case LIST_TYPE_APPS_LOCALE:
568                 return SettingsEnums.APPS_LOCALE_LIST;
569             case LIST_TYPE_BATTERY_OPTIMIZATION:
570                 return SettingsEnums.BATTERY_OPTIMIZED_APPS_LIST;
571             case LIST_TYPE_LONG_BACKGROUND_TASKS:
572                 return SettingsEnums.LONG_BACKGROUND_TASKS;
573             case LIST_TYPE_CLONED_APPS:
574                 return SettingsEnums.CLONED_APPS;
575             case LIST_TYPE_NFC_TAG_APPS:
576                 return SettingsEnums.CONFIG_NFC_TAG_APP_PREF;
577             case LIST_TYPE_TURN_SCREEN_ON:
578                 return SettingsEnums.SETTINGS_TURN_SCREEN_ON_ACCESS;
579             default:
580                 return SettingsEnums.PAGE_UNKNOWN;
581         }
582     }
583 
584     @Override
onStart()585     public void onStart() {
586         super.onStart();
587         updateView();
588         if (mApplications != null) {
589             mApplications.updateLoading();
590         }
591     }
592 
593     @Override
onResume()594     public void onResume() {
595         super.onResume();
596         if (mApplications != null) {
597             mApplications.resume(mSortOrder);
598         }
599     }
600 
601     @Override
onSaveInstanceState(Bundle outState)602     public void onSaveInstanceState(Bundle outState) {
603         super.onSaveInstanceState(outState);
604         mResetAppsHelper.onSaveInstanceState(outState);
605         outState.putInt(EXTRA_SORT_ORDER, mSortOrder);
606         outState.putInt(EXTRA_FILTER_TYPE, mFilter.getFilterType());
607         outState.putBoolean(EXTRA_SHOW_SYSTEM, mShowSystem);
608         if (mSearchView != null) {
609             outState.putBoolean(EXTRA_EXPAND_SEARCH_VIEW, !mSearchView.isIconified());
610         }
611         if (mApplications != null) {
612             outState.putBoolean(EXTRA_HAS_ENTRIES, mApplications.mHasReceivedLoadEntries);
613             outState.putBoolean(EXTRA_HAS_BRIDGE, mApplications.mHasReceivedBridgeCallback);
614             mApplications.onSaveInstanceState(outState);
615         }
616     }
617 
618     @Override
onPause()619     public void onPause() {
620         super.onPause();
621         if (mApplications != null) {
622             mApplications.pause();
623         }
624     }
625 
626     @Override
onStop()627     public void onStop() {
628         super.onStop();
629         if (mResetAppsHelper != null) {
630             mResetAppsHelper.stop();
631         }
632     }
633 
634     @Override
onDestroyView()635     public void onDestroyView() {
636         super.onDestroyView();
637 
638         if (mApplications != null) {
639             mApplications.release();
640         }
641         mRootView = null;
642         AppIconCacheManager.getInstance().release();
643     }
644 
645     @Override
onActivityResult(int requestCode, int resultCode, Intent data)646     public void onActivityResult(int requestCode, int resultCode, Intent data) {
647         if (requestCode == INSTALLED_APP_DETAILS && mCurrentPkgName != null) {
648             if (mListType == LIST_TYPE_NOTIFICATION || mListType == LIST_TYPE_HIGH_POWER
649                     || mListType == LIST_TYPE_OVERLAY || mListType == LIST_TYPE_WRITE_SETTINGS
650                     || mListType == LIST_TYPE_NFC_TAG_APPS) {
651                 mApplications.mExtraInfoBridge.forceUpdate(mCurrentPkgName, mCurrentUid);
652             } else {
653                 mApplicationsState.requestSize(mCurrentPkgName, UserHandle.getUserId(mCurrentUid));
654             }
655         }
656     }
657 
setCompositeFilter()658     private void setCompositeFilter() {
659         AppFilter compositeFilter = getCompositeFilter(mListType, mStorageType, mVolumeUuid);
660         if (compositeFilter == null) {
661             compositeFilter = mFilter.getFilter();
662         }
663         if (mIsWorkOnly) {
664             compositeFilter = new CompoundFilter(compositeFilter, ApplicationsState.FILTER_WORK);
665         }
666         if (mIsPrivateProfileOnly) {
667             compositeFilter =
668                     new CompoundFilter(compositeFilter, ApplicationsState.FILTER_PRIVATE_PROFILE);
669         }
670 
671         // We might not be showing the private tab even when there's a private profile present and
672         // there's only personal profile info to show, in which case we should still apply the
673         // personal filter.
674         if (mIsPersonalOnly || !(mIsWorkOnly || mIsPrivateProfileOnly)) {
675             compositeFilter = new CompoundFilter(compositeFilter,
676                     ApplicationsState.FILTER_PERSONAL);
677         }
678         mApplications.setCompositeFilter(compositeFilter);
679     }
680 
681     // utility method used to start sub activity
startApplicationDetailsActivity()682     private void startApplicationDetailsActivity() {
683         switch (mListType) {
684             case LIST_TYPE_NOTIFICATION:
685                 startAppInfoFragment(AppNotificationSettings.class, R.string.notifications_title);
686                 break;
687             case LIST_TYPE_USAGE_ACCESS:
688                 startAppInfoFragment(UsageAccessDetails.class, R.string.usage_access);
689                 break;
690             case LIST_TYPE_STORAGE:
691                 startAppInfoFragment(AppStorageSettings.class, R.string.storage_settings);
692                 break;
693             case LIST_TYPE_HIGH_POWER:
694                 HighPowerDetail.show(this, mCurrentUid, mCurrentPkgName, INSTALLED_APP_DETAILS);
695                 break;
696             case LIST_TYPE_OVERLAY:
697                 startAppInfoFragment(DrawOverlayDetails.class, R.string.overlay_settings);
698                 break;
699             case LIST_TYPE_WRITE_SETTINGS:
700                 startAppInfoFragment(WriteSettingsDetails.class, R.string.write_system_settings);
701                 break;
702             case LIST_TYPE_MANAGE_SOURCES:
703                 startAppInfoFragment(ExternalSourcesDetails.class,
704                         com.android.settingslib.R.string.install_other_apps);
705                 break;
706             case LIST_TYPE_GAMES:
707                 startAppInfoFragment(AppStorageSettings.class, R.string.game_storage_settings);
708                 break;
709             case LIST_TYPE_WIFI_ACCESS:
710                 startAppInfoFragment(ChangeWifiStateDetails.class,
711                         R.string.change_wifi_state_title);
712                 break;
713             case LIST_MANAGE_EXTERNAL_STORAGE:
714                 startAppInfoFragment(ManageExternalStorageDetails.class,
715                         R.string.manage_external_storage_title);
716                 break;
717             case LIST_TYPE_ALARMS_AND_REMINDERS:
718                 startAppInfoFragment(AlarmsAndRemindersDetails.class,
719                         com.android.settingslib.R.string.alarms_and_reminders_label);
720                 break;
721             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
722                 startAppInfoFragment(MediaManagementAppsDetails.class,
723                         R.string.media_management_apps_title);
724                 break;
725             case LIST_TYPE_APPS_LOCALE:
726                 Intent intent = new Intent(getContext(), AppLocalePickerActivity.class);
727                 intent.setData(Uri.parse("package:" + mCurrentPkgName));
728                 getContext().startActivityAsUser(intent,
729                         UserHandle.getUserHandleForUid(mCurrentUid));
730                 break;
731             case LIST_TYPE_BATTERY_OPTIMIZATION:
732                 AdvancedPowerUsageDetail.startBatteryDetailPage(
733                         getActivity(), this, mCurrentPkgName,
734                         UserHandle.getUserHandleForUid(mCurrentUid));
735                 break;
736             case LIST_TYPE_LONG_BACKGROUND_TASKS:
737                 startAppInfoFragment(LongBackgroundTasksDetails.class,
738                         R.string.long_background_tasks_label);
739                 break;
740             case LIST_TYPE_CLONED_APPS:
741                 int userId = UserHandle.getUserId(mCurrentUid);
742                 UserInfo userInfo = mUserManager.getUserInfo(userId);
743                 if (userInfo != null && !userInfo.isCloneProfile()) {
744                     SpaActivity.startSpaActivity(getContext(), CloneAppInfoSettingsProvider.INSTANCE
745                             .getRoute(mCurrentPkgName, userId));
746                 } else {
747                     SpaActivity.startSpaActivity(getContext(), AppInfoSettingsProvider.INSTANCE
748                             .getRoute(mCurrentPkgName, userId));
749                 }
750                 break;
751             case LIST_TYPE_NFC_TAG_APPS:
752                 startAppInfoFragment(ChangeNfcTagAppsStateDetails.class,
753                         R.string.change_nfc_tag_apps_title);
754                 break;
755             case LIST_TYPE_TURN_SCREEN_ON:
756                 startAppInfoFragment(TurnScreenOnDetails.class,
757                         com.android.settingslib.R.string.turn_screen_on_title);
758                 break;
759             // TODO: Figure out if there is a way where we can spin up the profile's settings
760             // process ahead of time, to avoid a long load of data when user clicks on a managed
761             // app. Maybe when they load the list of apps that contains managed profile apps.
762             default:
763                 startAppInfoFragment(
764                         AppInfoDashboardFragment.class, R.string.application_info_label);
765                 break;
766         }
767     }
768 
startAppInfoFragment(Class<?> fragment, int titleRes)769     private void startAppInfoFragment(Class<?> fragment, int titleRes) {
770         AppInfoBase.startAppInfoFragment(fragment, getString(titleRes), mCurrentPkgName,
771                 mCurrentUid, this, INSTALLED_APP_DETAILS, getMetricsCategory());
772     }
773 
774     @Override
onCreateOptionsMenu(Menu menu, MenuInflater inflater)775     public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
776         final Activity activity = getActivity();
777         if (activity == null) {
778             return;
779         }
780         mOptionsMenu = menu;
781         inflater.inflate(R.menu.manage_apps, menu);
782 
783         final MenuItem searchMenuItem = menu.findItem(R.id.search_app_list_menu);
784         if (searchMenuItem != null) {
785             searchMenuItem.setOnActionExpandListener(this);
786             mSearchView = (SearchView) searchMenuItem.getActionView();
787             mSearchView.setQueryHint(getText(R.string.search_settings));
788             mSearchView.setOnQueryTextListener(this);
789             mSearchView.setMaxWidth(Integer.MAX_VALUE);
790             if (mExpandSearch) {
791                 searchMenuItem.expandActionView();
792             }
793         }
794 
795         updateOptionsMenu();
796     }
797 
798     @Override
onMenuItemActionExpand(MenuItem item)799     public boolean onMenuItemActionExpand(MenuItem item) {
800         // To prevent a large space on tool bar.
801         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
802         // To prevent user can expand the collapsing tool bar view.
803         ViewCompat.setNestedScrollingEnabled(mRecyclerView, false);
804         return true;
805     }
806 
807     @Override
onMenuItemActionCollapse(MenuItem item)808     public boolean onMenuItemActionCollapse(MenuItem item) {
809         // We keep the collapsed status after user cancel the search function.
810         mAppBarLayout.setExpanded(false /*expanded*/, false /*animate*/);
811         ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
812         return true;
813     }
814 
815     @Override
onPrepareOptionsMenu(Menu menu)816     public void onPrepareOptionsMenu(Menu menu) {
817         updateOptionsMenu();
818     }
819 
820     @Override
onDestroyOptionsMenu()821     public void onDestroyOptionsMenu() {
822         mOptionsMenu = null;
823     }
824 
825     @StringRes
getHelpResource()826     int getHelpResource() {
827         switch (mListType) {
828             case LIST_TYPE_NOTIFICATION:
829                 return R.string.help_uri_notifications;
830             case LIST_TYPE_USAGE_ACCESS:
831                 return R.string.help_url_usage_access;
832             case LIST_TYPE_STORAGE:
833                 return R.string.help_uri_apps_storage;
834             case LIST_TYPE_HIGH_POWER:
835                 return R.string.help_uri_apps_high_power;
836             case LIST_TYPE_OVERLAY:
837                 return R.string.help_uri_apps_overlay;
838             case LIST_TYPE_WRITE_SETTINGS:
839                 return R.string.help_uri_apps_write_settings;
840             case LIST_TYPE_MANAGE_SOURCES:
841                 return R.string.help_uri_apps_manage_sources;
842             case LIST_TYPE_GAMES:
843                 return R.string.help_uri_apps_overlay;
844             case LIST_TYPE_WIFI_ACCESS:
845                 return R.string.help_uri_apps_wifi_access;
846             case LIST_MANAGE_EXTERNAL_STORAGE:
847                 return R.string.help_uri_manage_external_storage;
848             case LIST_TYPE_ALARMS_AND_REMINDERS:
849                 return R.string.help_uri_alarms_and_reminders;
850             case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
851                 return R.string.help_uri_media_management_apps;
852             case LIST_TYPE_LONG_BACKGROUND_TASKS:
853                 return R.string.help_uri_long_background_tasks;
854             default:
855             case LIST_TYPE_MAIN:
856                 return R.string.help_uri_apps;
857         }
858     }
859 
updateOptionsMenu()860     void updateOptionsMenu() {
861         if (mOptionsMenu == null) {
862             return;
863         }
864         mOptionsMenu.findItem(R.id.advanced).setVisible(false);
865 
866         mOptionsMenu.findItem(R.id.sort_order_alpha).setVisible(mListType == LIST_TYPE_STORAGE
867                 && mSortOrder != R.id.sort_order_alpha);
868         mOptionsMenu.findItem(R.id.sort_order_size).setVisible(mListType == LIST_TYPE_STORAGE
869                 && mSortOrder != R.id.sort_order_size);
870 
871         mOptionsMenu.findItem(R.id.show_system).setVisible(!mShowSystem
872                 && mListType != LIST_TYPE_HIGH_POWER && mListType != LIST_TYPE_APPS_LOCALE
873                 && mListType != LIST_TYPE_CLONED_APPS);
874         mOptionsMenu.findItem(R.id.hide_system).setVisible(mShowSystem
875                 && mListType != LIST_TYPE_HIGH_POWER && mListType != LIST_TYPE_APPS_LOCALE
876                 && mListType != LIST_TYPE_CLONED_APPS);
877 
878         mOptionsMenu.findItem(R.id.reset_app_preferences).setVisible(mListType == LIST_TYPE_MAIN);
879 
880         // Hide notification menu items, because sorting happens when filtering
881         mOptionsMenu.findItem(R.id.sort_order_recent_notification).setVisible(false);
882         mOptionsMenu.findItem(R.id.sort_order_frequent_notification).setVisible(false);
883         final MenuItem searchItem = mOptionsMenu.findItem(MENU_SEARCH);
884         if (searchItem != null) {
885             searchItem.setVisible(false);
886         }
887 
888         mOptionsMenu.findItem(R.id.delete_all_app_clones)
889                 .setVisible(mListType == LIST_TYPE_CLONED_APPS  && DeviceConfig.getBoolean(
890                         DeviceConfig.NAMESPACE_APP_CLONING, PROPERTY_DELETE_ALL_APP_CLONES_ENABLED,
891                 true) && Utils.getCloneUserId(getContext()) != -1);
892     }
893 
894     @Override
onOptionsItemSelected(MenuItem item)895     public boolean onOptionsItemSelected(MenuItem item) {
896         int menuId = item.getItemId();
897         int i = item.getItemId();
898         if (i == R.id.sort_order_alpha || i == R.id.sort_order_size) {
899             if (mApplications != null) {
900                 mApplications.rebuild(menuId, false);
901             }
902         } else if (i == R.id.show_system || i == R.id.hide_system) {
903             mShowSystem = !mShowSystem;
904             mApplications.rebuild();
905         } else if (i == R.id.reset_app_preferences) {
906             final boolean appsControlDisallowedBySystem =
907                     RestrictedLockUtilsInternal.hasBaseUserRestriction(getActivity(),
908                             UserManager.DISALLOW_APPS_CONTROL, UserHandle.myUserId());
909             final RestrictedLockUtils.EnforcedAdmin appsControlDisallowedAdmin =
910                     RestrictedLockUtilsInternal.checkIfRestrictionEnforced(getActivity(),
911                             UserManager.DISALLOW_APPS_CONTROL, UserHandle.myUserId());
912             if (appsControlDisallowedAdmin != null && !appsControlDisallowedBySystem) {
913                 RestrictedLockUtils.sendShowAdminSupportDetailsIntent(
914                         getActivity(), appsControlDisallowedAdmin);
915             } else {
916                 mResetAppsHelper.buildResetDialog();
917             }
918             return true;
919         } else if (i == R.id.advanced) {
920             if (mListType == LIST_TYPE_NOTIFICATION) {
921                 new SubSettingLauncher(getContext())
922                         .setDestination(ConfigureNotificationSettings.class.getName())
923                         .setTitleRes(R.string.configure_notification_settings)
924                         .setSourceMetricsCategory(getMetricsCategory())
925                         .setResultListener(this, ADVANCED_SETTINGS)
926                         .launch();
927             } else {
928                 Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS)
929                         .setPackage(getContext()
930                                 .getPackageManager().getPermissionControllerPackageName());
931                 startActivityForResult(intent, ADVANCED_SETTINGS);
932             }
933             return true;
934         } else if (i == R.id.delete_all_app_clones) {
935             int clonedUserId = Utils.getCloneUserId(getContext());
936             if (clonedUserId == -1) {
937                 // No Apps Cloned Till now. Do Nothing.
938                 return false;
939             }
940             IUserManager um = IUserManager.Stub.asInterface(
941                     ServiceManager.getService(Context.USER_SERVICE));
942             CloneBackend cloneBackend = CloneBackend.getInstance(getContext());
943             try {
944                 // Warning: This removes all the data, media & images present in cloned user.
945                 if (um.removeUser(clonedUserId)) {
946                     cloneBackend.resetCloneUserId();
947                     mApplications.rebuild();
948                 } else if (ManageApplications.DEBUG) {
949                     Log.e(TAG, "Failed to remove cloned user");
950                 }
951             } catch (RemoteException e) {
952                 Log.e(TAG, "Failed to remove cloned apps", e);
953                 Toast.makeText(getContext(),
954                         getContext().getString(R.string.delete_all_app_clones_failure),
955                         Toast.LENGTH_LONG).show();
956             }
957         } else {// Handle the home button
958             return false;
959         }
960         updateOptionsMenu();
961         return true;
962     }
963 
964     @Override
onClick(View view)965     public void onClick(View view) {
966         if (mApplications == null) {
967             return;
968         }
969         final int applicationPosition =
970                 ApplicationsAdapter.getApplicationPosition(
971                         mListType, mRecyclerView.getChildAdapterPosition(view));
972 
973         if (applicationPosition == RecyclerView.NO_POSITION) {
974             Log.w(TAG, "Cannot find position for child, skipping onClick handling");
975             return;
976         }
977         if (mApplications.getApplicationCount() > applicationPosition) {
978             ApplicationsState.AppEntry entry = mApplications.getAppEntry(applicationPosition);
979             mCurrentPkgName = entry.info.packageName;
980             mCurrentUid = entry.info.uid;
981             startApplicationDetailsActivity();
982             // We disable the scrolling ability in onMenuItemActionCollapse, we should recover it
983             // if user selects any app item.
984             ViewCompat.setNestedScrollingEnabled(mRecyclerView, true);
985         }
986     }
987 
988     @Override
onItemSelected(AdapterView<?> parent, View view, int position, long id)989     public void onItemSelected(AdapterView<?> parent, View view, int position, long id) {
990         mFilter = mFilterAdapter.getFilter(position);
991         setCompositeFilter();
992         mApplications.setFilter(mFilter);
993 
994         if (DEBUG) {
995             Log.d(TAG, "Selecting filter " + getContext().getText(mFilter.getTitle()));
996         }
997     }
998 
999     @Override
onNothingSelected(AdapterView<?> parent)1000     public void onNothingSelected(AdapterView<?> parent) {
1001     }
1002 
1003     @Override
onQueryTextSubmit(String query)1004     public boolean onQueryTextSubmit(String query) {
1005         return false;
1006     }
1007 
1008     @Override
onQueryTextChange(String newText)1009     public boolean onQueryTextChange(String newText) {
1010         mApplications.filterSearch(newText);
1011         return false;
1012     }
1013 
updateView()1014     public void updateView() {
1015         updateOptionsMenu();
1016         final Activity host = getActivity();
1017         if (host != null) {
1018             host.invalidateOptionsMenu();
1019         }
1020     }
1021 
setHasDisabled(boolean hasDisabledApps)1022     public void setHasDisabled(boolean hasDisabledApps) {
1023         if (mListType != LIST_TYPE_MAIN) {
1024             return;
1025         }
1026         mFilterAdapter.setFilterEnabled(FILTER_APPS_ENABLED, hasDisabledApps);
1027         mFilterAdapter.setFilterEnabled(FILTER_APPS_DISABLED, hasDisabledApps);
1028     }
1029 
setHasInstant(boolean haveInstantApps)1030     public void setHasInstant(boolean haveInstantApps) {
1031         if (LIST_TYPES_WITH_INSTANT.contains(mListType)) {
1032             mFilterAdapter.setFilterEnabled(FILTER_APPS_INSTANT, haveInstantApps);
1033         }
1034     }
1035 
autoSetCollapsingToolbarLayoutScrolling()1036     private void autoSetCollapsingToolbarLayoutScrolling() {
1037         final CoordinatorLayout.LayoutParams params =
1038                 (CoordinatorLayout.LayoutParams) mAppBarLayout.getLayoutParams();
1039         final AppBarLayout.Behavior behavior = new AppBarLayout.Behavior();
1040         behavior.setDragCallback(
1041                 new AppBarLayout.Behavior.DragCallback() {
1042                     @Override
1043                     public boolean canDrag(@NonNull AppBarLayout appBarLayout) {
1044                         return appBarLayout.getResources().getConfiguration().orientation
1045                                 == Configuration.ORIENTATION_LANDSCAPE;
1046                     }
1047                 });
1048         params.setBehavior(behavior);
1049     }
1050 
1051     /**
1052      * Returns a resource ID of title based on what type of app list is
1053      *
1054      * @param intent the intent of the activity that might include a specified title
1055      * @param args   the args that includes a class name of app list
1056      */
getTitleResId(@onNull Intent intent, Bundle args)1057     public static int getTitleResId(@NonNull Intent intent, Bundle args) {
1058         int screenTitle = intent.getIntExtra(
1059                 SettingsActivity.EXTRA_SHOW_FRAGMENT_TITLE_RESID, R.string.all_apps);
1060         String className = getClassName(intent, args);
1061         if (className.equals(UsageAccessSettingsActivity.class.getName())) {
1062             screenTitle = R.string.usage_access;
1063         } else if (className.equals(HighPowerApplicationsActivity.class.getName())) {
1064             screenTitle = R.string.high_power_apps;
1065         } else if (className.equals(OverlaySettingsActivity.class.getName())) {
1066             screenTitle = R.string.system_alert_window_settings;
1067         } else if (className.equals(WriteSettingsActivity.class.getName())) {
1068             screenTitle = R.string.write_settings;
1069         } else if (className.equals(ManageExternalSourcesActivity.class.getName())) {
1070             screenTitle = com.android.settingslib.R.string.install_other_apps;
1071         } else if (className.equals(ChangeWifiStateActivity.class.getName())) {
1072             screenTitle = R.string.change_wifi_state_title;
1073         } else if (className.equals(ManageExternalStorageActivity.class.getName())) {
1074             screenTitle = R.string.manage_external_storage_title;
1075         } else if (className.equals(MediaManagementAppsActivity.class.getName())) {
1076             screenTitle = R.string.media_management_apps_title;
1077         } else if (className.equals(AlarmsAndRemindersActivity.class.getName())) {
1078             screenTitle = com.android.settingslib.R.string.alarms_and_reminders_title;
1079         } else if (className.equals(NotificationAppListActivity.class.getName())
1080                 || className.equals(
1081                 NotificationReviewPermissionsActivity.class.getName())) {
1082             screenTitle = R.string.app_notifications_title;
1083         } else if (className.equals(AppLocaleDetails.class.getName())) {
1084             screenTitle = R.string.app_locales_picker_menu_title;
1085         } else if (className.equals(AppBatteryUsageActivity.class.getName())) {
1086             screenTitle = R.string.app_battery_usage_title;
1087         } else if (className.equals(LongBackgroundTasksActivity.class.getName())) {
1088             screenTitle = R.string.long_background_tasks_title;
1089         } else if (className.equals(ClonedAppsListActivity.class.getName())) {
1090             screenTitle = R.string.cloned_apps_dashboard_title;
1091         } else if (className.equals(ChangeNfcTagAppsActivity.class.getName())) {
1092             screenTitle = R.string.change_nfc_tag_apps_title;
1093         } else if (className.equals(TurnScreenOnSettingsActivity.class.getName())) {
1094             screenTitle = com.android.settingslib.R.string.turn_screen_on_title;
1095         } else {
1096             if (screenTitle == -1) {
1097                 screenTitle = R.string.all_apps;
1098             }
1099         }
1100         return screenTitle;
1101     }
1102 
getClassName(@onNull Intent intent, Bundle args)1103     private static String getClassName(@NonNull Intent intent, Bundle args) {
1104         String className = args != null ? args.getString(EXTRA_CLASSNAME) : null;
1105         if (className == null) {
1106             className = intent.getComponent().getClassName();
1107         }
1108         return className;
1109     }
1110 
1111     static class FilterSpinnerAdapter extends SettingsSpinnerAdapter<CharSequence> {
1112 
1113         private final ManageApplications mManageApplications;
1114         private final Context mContext;
1115 
1116         // Use ArrayAdapter for view logic, but have our own list for managing
1117         // the options available.
1118         private final ArrayList<AppFilterItem> mFilterOptions = new ArrayList<>();
1119 
FilterSpinnerAdapter(ManageApplications manageApplications)1120         public FilterSpinnerAdapter(ManageApplications manageApplications) {
1121             super(manageApplications.getContext());
1122             mContext = manageApplications.getContext();
1123             mManageApplications = manageApplications;
1124         }
1125 
getFilter(int position)1126         public AppFilterItem getFilter(int position) {
1127             return mFilterOptions.get(position);
1128         }
1129 
setFilterEnabled(@ppFilterRegistry.FilterType int filter, boolean enabled)1130         public void setFilterEnabled(@AppFilterRegistry.FilterType int filter, boolean enabled) {
1131             if (enabled) {
1132                 enableFilter(filter);
1133             } else {
1134                 disableFilter(filter);
1135             }
1136         }
1137 
enableFilter(@ppFilterRegistry.FilterType int filterType)1138         public void enableFilter(@AppFilterRegistry.FilterType int filterType) {
1139             final AppFilterItem filter = AppFilterRegistry.getInstance().get(filterType);
1140             if (mFilterOptions.contains(filter)) {
1141                 return;
1142             }
1143             if (DEBUG) {
1144                 Log.d(TAG, "Enabling filter " + mContext.getText(filter.getTitle()));
1145             }
1146             mFilterOptions.add(filter);
1147             Collections.sort(mFilterOptions);
1148             updateFilterView(mFilterOptions.size() > 1);
1149             notifyDataSetChanged();
1150             if (mFilterOptions.size() == 1) {
1151                 if (DEBUG) {
1152                     Log.d(TAG, "Auto selecting filter " + filter + " " + mContext.getText(
1153                             filter.getTitle()));
1154                 }
1155                 mManageApplications.mFilterSpinner.setSelection(0);
1156                 mManageApplications.onItemSelected(null, null, 0, 0);
1157             }
1158             if (mFilterOptions.size() > 1) {
1159                 final AppFilterItem previousFilter = AppFilterRegistry.getInstance().get(
1160                         mManageApplications.mFilterType);
1161                 final int index = mFilterOptions.indexOf(previousFilter);
1162                 if (index != -1) {
1163                     mManageApplications.mFilterSpinner.setSelection(index);
1164                     mManageApplications.onItemSelected(null, null, index, 0);
1165                 }
1166             }
1167         }
1168 
disableFilter(@ppFilterRegistry.FilterType int filterType)1169         public void disableFilter(@AppFilterRegistry.FilterType int filterType) {
1170             final AppFilterItem filter = AppFilterRegistry.getInstance().get(filterType);
1171             if (!mFilterOptions.remove(filter)) {
1172                 return;
1173             }
1174             if (DEBUG) {
1175                 Log.d(TAG, "Disabling filter " + filter + " " + mContext.getText(
1176                         filter.getTitle()));
1177             }
1178             Collections.sort(mFilterOptions);
1179             updateFilterView(mFilterOptions.size() > 1);
1180             notifyDataSetChanged();
1181             if (mManageApplications.mFilter == filter) {
1182                 if (mFilterOptions.size() > 0) {
1183                     if (DEBUG) {
1184                         Log.d(TAG, "Auto selecting filter " + mFilterOptions.get(0)
1185                                 + mContext.getText(mFilterOptions.get(0).getTitle()));
1186                     }
1187                     mManageApplications.mFilterSpinner.setSelection(0);
1188                     mManageApplications.onItemSelected(null, null, 0, 0);
1189                 }
1190             }
1191         }
1192 
1193         @Override
getCount()1194         public int getCount() {
1195             return mFilterOptions.size();
1196         }
1197 
1198         @Override
getItem(int position)1199         public CharSequence getItem(int position) {
1200             return mContext.getText(mFilterOptions.get(position).getTitle());
1201         }
1202 
1203         @VisibleForTesting
updateFilterView(boolean hasFilter)1204         void updateFilterView(boolean hasFilter) {
1205             // If we need to add a floating filter in this screen, we should have an extra top
1206             // padding for putting floating filter view. Otherwise, the content of list will be
1207             // overlapped by floating filter.
1208             if (hasFilter) {
1209                 mManageApplications.mSpinnerHeader.setVisibility(View.VISIBLE);
1210             } else {
1211                 mManageApplications.mSpinnerHeader.setVisibility(View.GONE);
1212             }
1213         }
1214     }
1215 
1216     static class ApplicationsAdapter extends RecyclerView.Adapter<ApplicationViewHolder>
1217             implements ApplicationsState.Callbacks, AppStateBaseBridge.Callback {
1218 
1219         private static final String STATE_LAST_SCROLL_INDEX = "state_last_scroll_index";
1220         private static final int VIEW_TYPE_APP = 0;
1221         private static final int VIEW_TYPE_EXTRA_VIEW = 1;
1222         private static final int VIEW_TYPE_APP_HEADER = 2;
1223         private static final int VIEW_TYPE_TWO_TARGET = 3;
1224 
1225         private final ApplicationsState mState;
1226         private final ApplicationsState.Session mSession;
1227         private final ManageApplications mManageApplications;
1228         private final Context mContext;
1229         private final AppStateBaseBridge mExtraInfoBridge;
1230         private final LoadingViewController mLoadingViewController;
1231         private final IconDrawableFactory mIconDrawableFactory;
1232 
1233         private AppFilterItem mAppFilter;
1234         private ArrayList<ApplicationsState.AppEntry> mEntries;
1235         private ArrayList<ApplicationsState.AppEntry> mOriginalEntries;
1236         private boolean mResumed;
1237         private int mLastSortMode = -1;
1238         private int mWhichSize = SIZE_TOTAL;
1239         private AppFilter mCompositeFilter;
1240         private boolean mHasReceivedLoadEntries;
1241         private boolean mHasReceivedBridgeCallback;
1242         private SearchFilter mSearchFilter;
1243         private PowerAllowlistBackend mBackend;
1244 
1245         // This is to remember and restore the last scroll position when this
1246         // fragment is paused. We need this special handling because app entries are added gradually
1247         // when we rebuild the list after the user made some changes, like uninstalling an app.
1248         private int mLastIndex = -1;
1249 
1250         @VisibleForTesting
1251         OnScrollListener mOnScrollListener;
1252         private RecyclerView mRecyclerView;
1253 
1254 
ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications, AppFilterItem appFilter, Bundle savedInstanceState)1255         public ApplicationsAdapter(ApplicationsState state, ManageApplications manageApplications,
1256                 AppFilterItem appFilter, Bundle savedInstanceState) {
1257             setHasStableIds(true);
1258             mState = state;
1259             mSession = state.newSession(this);
1260             mManageApplications = manageApplications;
1261             mLoadingViewController = new LoadingViewController(
1262                     mManageApplications.mLoadingContainer,
1263                     mManageApplications.mRecyclerView,
1264                     mManageApplications.mEmptyView
1265             );
1266             mContext = manageApplications.getActivity();
1267             mIconDrawableFactory = IconDrawableFactory.newInstance(mContext);
1268             mAppFilter = appFilter;
1269             mBackend = PowerAllowlistBackend.getInstance(mContext);
1270             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1271                 mExtraInfoBridge = new AppStateNotificationBridge(mContext, mState, this,
1272                         manageApplications.mUsageStatsManager,
1273                         manageApplications.mUserManager,
1274                         manageApplications.mNotificationBackend);
1275             } else if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
1276                 mExtraInfoBridge = new AppStateUsageBridge(mContext, mState, this);
1277             } else if (mManageApplications.mListType == LIST_TYPE_HIGH_POWER) {
1278                 mBackend.refreshList();
1279                 mExtraInfoBridge = new AppStatePowerBridge(mContext, mState, this);
1280             } else if (mManageApplications.mListType == LIST_TYPE_OVERLAY) {
1281                 mExtraInfoBridge = new AppStateOverlayBridge(mContext, mState, this);
1282             } else if (mManageApplications.mListType == LIST_TYPE_WRITE_SETTINGS) {
1283                 mExtraInfoBridge = new AppStateWriteSettingsBridge(mContext, mState, this);
1284             } else if (mManageApplications.mListType == LIST_TYPE_MANAGE_SOURCES) {
1285                 mExtraInfoBridge = new AppStateInstallAppsBridge(mContext, mState, this);
1286             } else if (mManageApplications.mListType == LIST_TYPE_WIFI_ACCESS) {
1287                 mExtraInfoBridge = new AppStateChangeWifiStateBridge(mContext, mState, this);
1288             } else if (mManageApplications.mListType == LIST_MANAGE_EXTERNAL_STORAGE) {
1289                 mExtraInfoBridge = new AppStateManageExternalStorageBridge(mContext, mState, this);
1290             } else if (mManageApplications.mListType == LIST_TYPE_ALARMS_AND_REMINDERS) {
1291                 mExtraInfoBridge = new AppStateAlarmsAndRemindersBridge(mContext, mState, this);
1292             } else if (mManageApplications.mListType == LIST_TYPE_MEDIA_MANAGEMENT_APPS) {
1293                 mExtraInfoBridge = new AppStateMediaManagementAppsBridge(mContext, mState, this);
1294             } else if (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE) {
1295                 mExtraInfoBridge = new AppStateLocaleBridge(mContext, mState, this,
1296                         mManageApplications.mUserManager);
1297             } else if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
1298                 mExtraInfoBridge = new AppStateAppBatteryUsageBridge(mContext, mState, this);
1299             } else if (mManageApplications.mListType == LIST_TYPE_LONG_BACKGROUND_TASKS) {
1300                 mExtraInfoBridge = new AppStateLongBackgroundTasksBridge(mContext, mState, this);
1301             } else if (mManageApplications.mListType == LIST_TYPE_CLONED_APPS) {
1302                 mExtraInfoBridge = new AppStateClonedAppsBridge(mContext, mState, this);
1303             } else if (mManageApplications.mListType == LIST_TYPE_NFC_TAG_APPS) {
1304                 mExtraInfoBridge = new AppStateNfcTagAppsBridge(mContext, mState, this);
1305             } else if (mManageApplications.mListType == LIST_TYPE_TURN_SCREEN_ON) {
1306                 mExtraInfoBridge = new AppStateTurnScreenOnBridge(mContext, mState, this);
1307             } else {
1308                 mExtraInfoBridge = null;
1309             }
1310             if (savedInstanceState != null) {
1311                 mLastIndex = savedInstanceState.getInt(STATE_LAST_SCROLL_INDEX);
1312             }
1313         }
1314 
1315         @Override
onAttachedToRecyclerView(@onNull RecyclerView recyclerView)1316         public void onAttachedToRecyclerView(@NonNull RecyclerView recyclerView) {
1317             super.onAttachedToRecyclerView(recyclerView);
1318             final String className =
1319                     mManageApplications.getClass().getName() + "_" + mManageApplications.mListType;
1320             mRecyclerView = recyclerView;
1321             mOnScrollListener = new OnScrollListener(this, className);
1322             mRecyclerView.addOnScrollListener(mOnScrollListener);
1323         }
1324 
1325         @Override
onDetachedFromRecyclerView(@onNull RecyclerView recyclerView)1326         public void onDetachedFromRecyclerView(@NonNull RecyclerView recyclerView) {
1327             super.onDetachedFromRecyclerView(recyclerView);
1328             mRecyclerView.removeOnScrollListener(mOnScrollListener);
1329             mOnScrollListener = null;
1330             mRecyclerView = null;
1331         }
1332 
setCompositeFilter(AppFilter compositeFilter)1333         public void setCompositeFilter(AppFilter compositeFilter) {
1334             mCompositeFilter = compositeFilter;
1335             rebuild();
1336         }
1337 
setFilter(AppFilterItem appFilter)1338         public void setFilter(AppFilterItem appFilter) {
1339             mAppFilter = appFilter;
1340             final int filterType = appFilter.getFilterType();
1341 
1342             // Notification filters require resorting the list
1343             if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1344                 if (FILTER_APPS_FREQUENT == filterType) {
1345                     rebuild(R.id.sort_order_frequent_notification, false);
1346                 } else if (FILTER_APPS_RECENT == filterType) {
1347                     rebuild(R.id.sort_order_recent_notification, false);
1348                 } else if (FILTER_APPS_BLOCKED == filterType) {
1349                     rebuild(R.id.sort_order_alpha, true);
1350                 } else {
1351                     rebuild(R.id.sort_order_alpha, true);
1352                 }
1353                 return;
1354             }
1355 
1356             if (mManageApplications.mListType == LIST_TYPE_BATTERY_OPTIMIZATION) {
1357                 logAppBatteryUsage(filterType);
1358             }
1359 
1360             rebuild();
1361         }
1362 
resume(int sort)1363         public void resume(int sort) {
1364             if (DEBUG) Log.i(TAG, "Resume!  mResumed=" + mResumed);
1365             if (!mResumed) {
1366                 mResumed = true;
1367                 mSession.onResume();
1368                 mLastSortMode = sort;
1369                 if (mExtraInfoBridge != null) {
1370                     mExtraInfoBridge.resume(false /* forceLoadAllApps */);
1371                 }
1372                 rebuild();
1373             } else {
1374                 rebuild(sort, false);
1375             }
1376         }
1377 
pause()1378         public void pause() {
1379             if (mResumed) {
1380                 mResumed = false;
1381                 mSession.onPause();
1382                 if (mExtraInfoBridge != null) {
1383                     mExtraInfoBridge.pause();
1384                 }
1385             }
1386         }
1387 
onSaveInstanceState(Bundle outState)1388         public void onSaveInstanceState(Bundle outState) {
1389             // Record the current scroll position before pausing.
1390             final LinearLayoutManager layoutManager =
1391                     (LinearLayoutManager) mManageApplications.mRecyclerView.getLayoutManager();
1392             outState.putInt(STATE_LAST_SCROLL_INDEX, layoutManager.findFirstVisibleItemPosition());
1393         }
1394 
release()1395         public void release() {
1396             mSession.onDestroy();
1397             if (mExtraInfoBridge != null) {
1398                 mExtraInfoBridge.release();
1399             }
1400         }
1401 
rebuild(int sort, boolean force)1402         public void rebuild(int sort, boolean force) {
1403             if (sort == mLastSortMode && !force) {
1404                 return;
1405             }
1406             mManageApplications.mSortOrder = sort;
1407             mLastSortMode = sort;
1408             rebuild();
1409         }
1410 
1411         @Override
onCreateViewHolder(ViewGroup parent, int viewType)1412         public ApplicationViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
1413             final View view;
1414             if (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE
1415                     && viewType == VIEW_TYPE_APP_HEADER) {
1416                 view = ApplicationViewHolder.newHeader(parent,
1417                         R.string.desc_app_locale_selection_supported);
1418             } else if (mManageApplications.mListType == LIST_TYPE_NOTIFICATION) {
1419                 view = ApplicationViewHolder.newView(parent, true /* twoTarget */,
1420                         LIST_TYPE_NOTIFICATION);
1421             } else if (mManageApplications.mListType == LIST_TYPE_CLONED_APPS
1422                     && viewType == VIEW_TYPE_APP_HEADER) {
1423                 view = ApplicationViewHolder.newHeaderWithAnimation(mContext, parent,
1424                         R.string.desc_cloned_apps_intro_text, R.raw.app_cloning,
1425                         R.string.desc_cloneable_app_list_text);
1426             } else if (mManageApplications.mListType == LIST_TYPE_CLONED_APPS
1427                     && viewType == VIEW_TYPE_TWO_TARGET) {
1428                 view = ApplicationViewHolder.newView(
1429                         parent, true, LIST_TYPE_CLONED_APPS);
1430             } else {
1431                 view = ApplicationViewHolder.newView(parent, false /* twoTarget */,
1432                         mManageApplications.mListType);
1433             }
1434             return new ApplicationViewHolder(view);
1435         }
1436 
1437         @Override
getItemViewType(int position)1438         public int getItemViewType(int position) {
1439             if (position == 0 && (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE
1440                     || mManageApplications.mListType == LIST_TYPE_CLONED_APPS)) {
1441                 return VIEW_TYPE_APP_HEADER;
1442             } else if (mManageApplications.mListType == LIST_TYPE_CLONED_APPS) {
1443                 return VIEW_TYPE_TWO_TARGET;
1444             }
1445             return VIEW_TYPE_APP;
1446         }
1447 
rebuild()1448         public void rebuild() {
1449             if (!mHasReceivedLoadEntries
1450                     || (mExtraInfoBridge != null && !mHasReceivedBridgeCallback)) {
1451                 // Don't rebuild the list until all the app entries are loaded.
1452                 if (DEBUG) {
1453                     Log.d(TAG, "Not rebuilding until all the app entries loaded."
1454                             + " !mHasReceivedLoadEntries=" + !mHasReceivedLoadEntries
1455                             + " !mExtraInfoBridgeNull=" + (mExtraInfoBridge != null)
1456                             + " !mHasReceivedBridgeCallback=" + !mHasReceivedBridgeCallback);
1457                 }
1458                 return;
1459             }
1460             ApplicationsState.AppFilter filterObj;
1461             Comparator<AppEntry> comparatorObj;
1462             boolean emulated = Environment.isExternalStorageEmulated();
1463             if (emulated) {
1464                 mWhichSize = SIZE_TOTAL;
1465             } else {
1466                 mWhichSize = SIZE_INTERNAL;
1467             }
1468             filterObj = mAppFilter.getFilter();
1469             if (mCompositeFilter != null) {
1470                 filterObj = new CompoundFilter(filterObj, mCompositeFilter);
1471             }
1472             if (!mManageApplications.mShowSystem) {
1473                 if (LIST_TYPES_WITH_INSTANT.contains(mManageApplications.mListType)) {
1474                     filterObj = new CompoundFilter(filterObj,
1475                             ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER_AND_INSTANT);
1476                 } else {
1477                     filterObj = new CompoundFilter(filterObj,
1478                             ApplicationsState.FILTER_DOWNLOADED_AND_LAUNCHER);
1479                 }
1480             }
1481             if (mLastSortMode == R.id.sort_order_size) {
1482                 switch (mWhichSize) {
1483                     case SIZE_INTERNAL:
1484                         comparatorObj = ApplicationsState.INTERNAL_SIZE_COMPARATOR;
1485                         break;
1486                     case SIZE_EXTERNAL:
1487                         comparatorObj = ApplicationsState.EXTERNAL_SIZE_COMPARATOR;
1488                         break;
1489                     default:
1490                         comparatorObj = ApplicationsState.SIZE_COMPARATOR;
1491                         break;
1492                 }
1493             } else if (mLastSortMode == R.id.sort_order_recent_notification) {
1494                 comparatorObj = AppStateNotificationBridge.RECENT_NOTIFICATION_COMPARATOR;
1495             } else if (mLastSortMode == R.id.sort_order_frequent_notification) {
1496                 comparatorObj = AppStateNotificationBridge.FREQUENCY_NOTIFICATION_COMPARATOR;
1497             } else {
1498                 comparatorObj = ApplicationsState.ALPHA_COMPARATOR;
1499             }
1500 
1501             final AppFilter finalFilterObj = new CompoundFilter(filterObj,
1502                     ApplicationsState.FILTER_NOT_HIDE);
1503             ThreadUtils.postOnBackgroundThread(() -> {
1504                 mSession.rebuild(finalFilterObj, comparatorObj, false);
1505             });
1506         }
1507 
logAppBatteryUsage(int filterType)1508         private void logAppBatteryUsage(int filterType) {
1509             switch (filterType) {
1510                 case FILTER_APPS_BATTERY_UNRESTRICTED:
1511                     logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_UNRESTRICTED);
1512                     break;
1513                 case FILTER_APPS_BATTERY_OPTIMIZED:
1514                     logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_OPTIMIZED);
1515                     break;
1516                 case FILTER_APPS_BATTERY_RESTRICTED:
1517                     logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_RESTRICTED);
1518                     break;
1519                 default:
1520                     logAction(SettingsEnums.ACTION_BATTERY_OPTIMIZED_APPS_FILTER_ALL_APPS);
1521             }
1522         }
1523 
logAction(int action)1524         private void logAction(int action) {
1525             mManageApplications.mMetricsFeatureProvider.action(mContext, action);
1526         }
1527 
1528         @VisibleForTesting
filterSearch(String query)1529         void filterSearch(String query) {
1530             if (mSearchFilter == null) {
1531                 mSearchFilter = new SearchFilter();
1532             }
1533             // If we haven't load apps list completely, don't filter anything.
1534             if (mOriginalEntries == null) {
1535                 Log.w(TAG, "Apps haven't loaded completely yet, so nothing can be filtered");
1536                 return;
1537             }
1538             mSearchFilter.filter(query);
1539         }
1540 
packageNameEquals(PackageItemInfo info1, PackageItemInfo info2)1541         private static boolean packageNameEquals(PackageItemInfo info1, PackageItemInfo info2) {
1542             if (info1 == null || info2 == null) {
1543                 return false;
1544             }
1545             if (info1.packageName == null || info2.packageName == null) {
1546                 return false;
1547             }
1548             return info1.packageName.equals(info2.packageName);
1549         }
1550 
removeDuplicateIgnoringUser( ArrayList<ApplicationsState.AppEntry> entries)1551         private ArrayList<ApplicationsState.AppEntry> removeDuplicateIgnoringUser(
1552                 ArrayList<ApplicationsState.AppEntry> entries) {
1553             int size = entries.size();
1554             // returnList will not have more entries than entries
1555             ArrayList<ApplicationsState.AppEntry> returnEntries = new ArrayList<>(size);
1556 
1557             // assume appinfo of same package but different users are grouped together
1558             PackageItemInfo lastInfo = null;
1559             for (int i = 0; i < size; i++) {
1560                 AppEntry appEntry = entries.get(i);
1561                 PackageItemInfo info = appEntry.info;
1562                 if (!packageNameEquals(lastInfo, appEntry.info)) {
1563                     returnEntries.add(appEntry);
1564                 }
1565                 lastInfo = info;
1566             }
1567             returnEntries.trimToSize();
1568             return returnEntries;
1569         }
1570 
1571         @Override
onRebuildComplete(ArrayList<AppEntry> entries)1572         public void onRebuildComplete(ArrayList<AppEntry> entries) {
1573             if (DEBUG) {
1574                 Log.d(TAG, "onRebuildComplete size=" + entries.size());
1575             }
1576 
1577             // Preload top visible icons of app list.
1578             AppUtils.preloadTopIcons(mContext, entries,
1579                     mContext.getResources().getInteger(R.integer.config_num_visible_app_icons));
1580 
1581             final int filterType = mAppFilter.getFilterType();
1582             if (filterType == FILTER_APPS_POWER_ALLOWLIST
1583                     || filterType == FILTER_APPS_POWER_ALLOWLIST_ALL) {
1584                 entries = removeDuplicateIgnoringUser(entries);
1585             }
1586             mEntries = entries;
1587             mOriginalEntries = entries;
1588             notifyDataSetChanged();
1589             if (getItemCount() == 0) {
1590                 mLoadingViewController.showEmpty(false /* animate */);
1591             } else {
1592                 mLoadingViewController.showContent(false /* animate */);
1593 
1594                 if (mManageApplications.mSearchView != null
1595                         && mManageApplications.mSearchView.isVisibleToUser()) {
1596                     final CharSequence query = mManageApplications.mSearchView.getQuery();
1597                     if (!TextUtils.isEmpty(query)) {
1598                         filterSearch(query.toString());
1599                     }
1600                 }
1601             }
1602             // Restore the last scroll position if the number of entries added so far is bigger than
1603             // it.
1604             if (mLastIndex != -1 && getItemCount() > mLastIndex) {
1605                 mManageApplications.mRecyclerView.getLayoutManager().scrollToPosition(mLastIndex);
1606                 mLastIndex = -1;
1607             }
1608 
1609             if (mManageApplications.mListType == LIST_TYPE_USAGE_ACCESS) {
1610                 // No enabled or disabled filters for usage access.
1611                 return;
1612             }
1613 
1614             mManageApplications.setHasDisabled(mState.haveDisabledApps());
1615             mManageApplications.setHasInstant(mState.haveInstantApps());
1616         }
1617 
1618         @VisibleForTesting
updateLoading()1619         void updateLoading() {
1620             final boolean appLoaded = mHasReceivedLoadEntries && mSession.getAllApps().size() != 0;
1621             if (appLoaded) {
1622                 mLoadingViewController.showContent(false /* animate */);
1623             } else {
1624                 mLoadingViewController.showLoadingViewDelayed();
1625             }
1626         }
1627 
1628         @Override
onExtraInfoUpdated()1629         public void onExtraInfoUpdated() {
1630             mHasReceivedBridgeCallback = true;
1631             rebuild();
1632         }
1633 
1634         @Override
onRunningStateChanged(boolean running)1635         public void onRunningStateChanged(boolean running) {
1636             mManageApplications.getActivity().setProgressBarIndeterminateVisibility(running);
1637         }
1638 
1639         @Override
onPackageListChanged()1640         public void onPackageListChanged() {
1641             rebuild();
1642         }
1643 
1644         @Override
onPackageIconChanged()1645         public void onPackageIconChanged() {
1646             // We ensure icons are loaded when their item is displayed, so
1647             // don't care about icons loaded in the background.
1648         }
1649 
1650         @Override
onLoadEntriesCompleted()1651         public void onLoadEntriesCompleted() {
1652             mHasReceivedLoadEntries = true;
1653             // We may have been skipping rebuilds until this came in, trigger one now.
1654             rebuild();
1655         }
1656 
1657         @Override
onPackageSizeChanged(String packageName)1658         public void onPackageSizeChanged(String packageName) {
1659             if (mEntries == null) {
1660                 return;
1661             }
1662             final int size = mEntries.size();
1663             for (int i = 0; i < size; i++) {
1664                 final AppEntry entry = mEntries.get(i);
1665                 final ApplicationInfo info = entry.info;
1666                 if (info == null && !TextUtils.equals(packageName, info.packageName)) {
1667                     continue;
1668                 }
1669                 if (TextUtils.equals(mManageApplications.mCurrentPkgName, info.packageName)) {
1670                     // We got the size information for the last app the
1671                     // user viewed, and are sorting by size...  they may
1672                     // have cleared data, so we immediately want to resort
1673                     // the list with the new size to reflect it to the user.
1674                     rebuild();
1675                     return;
1676                 } else {
1677                     mOnScrollListener.postNotifyItemChange(i);
1678                 }
1679             }
1680         }
1681 
1682         @Override
onLauncherInfoChanged()1683         public void onLauncherInfoChanged() {
1684             if (!mManageApplications.mShowSystem) {
1685                 rebuild();
1686             }
1687         }
1688 
1689         @Override
onAllSizesComputed()1690         public void onAllSizesComputed() {
1691             if (mLastSortMode == R.id.sort_order_size) {
1692                 rebuild();
1693             }
1694         }
1695 
1696         /**
1697          * Item count include all items. If UI has a header on the app list, it shall shift 1 to
1698          * application count for the total item count.
1699          */
1700         @Override
getItemCount()1701         public int getItemCount() {
1702             int count = getApplicationCount();
1703             if (count != 0 && (mManageApplications.mListType == LIST_TYPE_APPS_LOCALE
1704                     || mManageApplications.mListType == LIST_TYPE_CLONED_APPS)) {
1705                 count++;
1706             }
1707             return count;
1708         }
1709 
getApplicationCount()1710         public int getApplicationCount() {
1711             return mEntries != null ? mEntries.size() : 0;
1712         }
1713 
getAppEntry(int applicationPosition)1714         public AppEntry getAppEntry(int applicationPosition) {
1715             return mEntries.get(applicationPosition);
1716         }
1717 
1718         /**
1719          * Item Id follows all item on the app list. If UI has a header on the list, it shall
1720          * shift 1 to the position for correct app entry.
1721          */
1722         @Override
getItemId(int position)1723         public long getItemId(int position) {
1724             int applicationPosition =
1725                     getApplicationPosition(mManageApplications.mListType, position);
1726             if (applicationPosition == mEntries.size()
1727                     || applicationPosition == RecyclerView.NO_POSITION) {
1728                 return -1;
1729             }
1730             return mEntries.get(applicationPosition).id;
1731         }
1732 
1733         /**
1734          * Check item in the list shall enable or disable.
1735          *
1736          * @param position The item position in the list
1737          */
isEnabled(int position)1738         public boolean isEnabled(int position) {
1739             int itemViewType = getItemViewType(position);
1740             if (itemViewType == VIEW_TYPE_EXTRA_VIEW || itemViewType == VIEW_TYPE_APP_HEADER
1741                     || mManageApplications.mListType != LIST_TYPE_HIGH_POWER) {
1742                 return true;
1743             }
1744 
1745             int applicationPosition =
1746                     getApplicationPosition(mManageApplications.mListType, position);
1747             if (applicationPosition == RecyclerView.NO_POSITION) {
1748                 return true;
1749             }
1750             ApplicationsState.AppEntry entry = mEntries.get(applicationPosition);
1751 
1752             return !mBackend.isSysAllowlisted(entry.info.packageName)
1753                     && !mBackend.isDefaultActiveApp(entry.info.packageName, entry.info.uid);
1754         }
1755 
1756         @Override
onBindViewHolder(ApplicationViewHolder holder, int position)1757         public void onBindViewHolder(ApplicationViewHolder holder, int position) {
1758             if (getItemViewType(position) == VIEW_TYPE_APP_HEADER) {
1759                 // It does not bind holder here, due to header view.
1760                 return;
1761             }
1762 
1763             int applicationPosition =
1764                     getApplicationPosition(mManageApplications.mListType, position);
1765             if (applicationPosition == RecyclerView.NO_POSITION) {
1766                 return;
1767             }
1768             // Bind the data efficiently with the holder
1769             // If there is a header on the list, the position shall be shifted. Thus, it shall use
1770             // #getApplicationPosition to get real application position for the app entry.
1771             final ApplicationsState.AppEntry entry = mEntries.get(applicationPosition);
1772 
1773             synchronized (entry) {
1774                 mState.ensureLabelDescription(entry);
1775                 holder.setTitle(entry.label, entry.labelDescription);
1776                 updateIcon(holder, entry);
1777                 updateSummary(holder, entry);
1778                 updateSwitch(holder, entry);
1779                 holder.updateDisableView(entry.info);
1780             }
1781             holder.setEnabled(isEnabled(position));
1782 
1783             holder.itemView.setOnClickListener(mManageApplications);
1784         }
1785 
updateIcon(ApplicationViewHolder holder, AppEntry entry)1786         private void updateIcon(ApplicationViewHolder holder, AppEntry entry) {
1787             final Drawable cachedIcon = AppUtils.getIconFromCache(entry);
1788             if (cachedIcon != null && entry.mounted) {
1789                 holder.setIcon(cachedIcon);
1790             } else {
1791                 ThreadUtils.postOnBackgroundThread(() -> {
1792                     final Drawable icon = AppUtils.getIcon(mContext, entry);
1793                     if (icon != null) {
1794                         ThreadUtils.postOnMainThread(() -> holder.setIcon(icon));
1795                     }
1796                 });
1797             }
1798         }
1799 
updateSummary(ApplicationViewHolder holder, AppEntry entry)1800         private void updateSummary(ApplicationViewHolder holder, AppEntry entry) {
1801             switch (mManageApplications.mListType) {
1802                 case LIST_TYPE_NOTIFICATION:
1803                     if (entry.extraInfo != null
1804                             && entry.extraInfo instanceof NotificationsSentState) {
1805                         holder.setSummary(AppStateNotificationBridge.getSummary(mContext,
1806                                 (NotificationsSentState) entry.extraInfo, mLastSortMode));
1807                     } else {
1808                         holder.setSummary(null);
1809                     }
1810                     break;
1811                 case LIST_TYPE_USAGE_ACCESS:
1812                     if (entry.extraInfo != null) {
1813                         holder.setSummary(
1814                                 (new UsageState((PermissionState) entry.extraInfo)).isPermissible()
1815                                         ? R.string.app_permission_summary_allowed
1816                                         : R.string.app_permission_summary_not_allowed);
1817                     } else {
1818                         holder.setSummary(null);
1819                     }
1820                     break;
1821                 case LIST_TYPE_HIGH_POWER:
1822                     holder.setSummary(HighPowerDetail.getSummary(mContext, entry));
1823                     break;
1824                 case LIST_TYPE_OVERLAY:
1825                     holder.setSummary(DrawOverlayDetails.getSummary(mContext, entry));
1826                     break;
1827                 case LIST_TYPE_WRITE_SETTINGS:
1828                     holder.setSummary(WriteSettingsDetails.getSummary(mContext, entry));
1829                     break;
1830                 case LIST_TYPE_MANAGE_SOURCES:
1831                     holder.setSummary(ExternalSourcesDetails.getPreferenceSummary(mContext, entry));
1832                     break;
1833                 case LIST_TYPE_WIFI_ACCESS:
1834                     holder.setSummary(ChangeWifiStateDetails.getSummary(mContext, entry));
1835                     break;
1836                 case LIST_MANAGE_EXTERNAL_STORAGE:
1837                     holder.setSummary(ManageExternalStorageDetails.getSummary(mContext, entry));
1838                     break;
1839                 case LIST_TYPE_ALARMS_AND_REMINDERS:
1840                     holder.setSummary(AlarmsAndRemindersDetails.getSummary(mContext, entry));
1841                     break;
1842                 case LIST_TYPE_MEDIA_MANAGEMENT_APPS:
1843                     holder.setSummary(MediaManagementAppsDetails.getSummary(mContext, entry));
1844                     break;
1845                 case LIST_TYPE_APPS_LOCALE:
1846                     holder.setSummary(AppLocaleDetails.getSummary(mContext, entry.info));
1847                     break;
1848                 case LIST_TYPE_BATTERY_OPTIMIZATION:
1849                     holder.setSummary(null);
1850                     break;
1851                 case LIST_TYPE_LONG_BACKGROUND_TASKS:
1852                     holder.setSummary(LongBackgroundTasksDetails.getSummary(mContext, entry));
1853                     break;
1854                 case LIST_TYPE_CLONED_APPS:
1855                     holder.setSummary(null);
1856                     break;
1857                 case LIST_TYPE_NFC_TAG_APPS:
1858                     holder.setSummary(
1859                             ChangeNfcTagAppsStateDetails.getSummary(mContext, entry));
1860                     break;
1861                 case LIST_TYPE_TURN_SCREEN_ON:
1862                     holder.setSummary(TurnScreenOnDetails.getSummary(mContext, entry));
1863                     break;
1864                 default:
1865                     holder.updateSizeText(entry, mManageApplications.mInvalidSizeStr, mWhichSize);
1866                     break;
1867             }
1868         }
1869 
updateSwitch(ApplicationViewHolder holder, AppEntry entry)1870         private void updateSwitch(ApplicationViewHolder holder, AppEntry entry) {
1871             switch (mManageApplications.mListType) {
1872                 case LIST_TYPE_NOTIFICATION:
1873                     holder.updateSwitch(((AppStateNotificationBridge) mExtraInfoBridge)
1874                                     .getSwitchOnCheckedListener(entry),
1875                             AppStateNotificationBridge.enableSwitch(entry),
1876                             AppStateNotificationBridge.checkSwitch(entry));
1877                     if (entry.extraInfo != null
1878                             && entry.extraInfo instanceof NotificationsSentState) {
1879                         holder.setSummary(AppStateNotificationBridge.getSummary(mContext,
1880                                 (NotificationsSentState) entry.extraInfo, mLastSortMode));
1881                     } else {
1882                         holder.setSummary(null);
1883                     }
1884                     break;
1885                 case LIST_TYPE_CLONED_APPS:
1886                     holder.updateAppCloneWidget(mContext,
1887                             holder.appCloneOnClickListener(entry, this,
1888                                     mManageApplications.getActivity()), entry);
1889                     break;
1890             }
1891         }
1892 
1893         /**
1894          * Adjusts position if this list adds a header.
1895          * TODO(b/232533002) Add a header view on adapter of RecyclerView may not a good idea since
1896          * ManageApplication is a generic purpose. In the future, here shall look for
1897          * a better way to add a header without using recyclerView or any other ways
1898          * to achieve the goal.
1899          */
getApplicationPosition(int listType, int position)1900         public static int getApplicationPosition(int listType, int position) {
1901             int applicationPosition = position;
1902             // Adjust position due to header added.
1903             if (listType == LIST_TYPE_APPS_LOCALE || listType == LIST_TYPE_CLONED_APPS) {
1904                 applicationPosition = position > 0 ? position - 1 : RecyclerView.NO_POSITION;
1905             }
1906             return applicationPosition;
1907         }
1908 
1909         public static class OnScrollListener extends RecyclerView.OnScrollListener {
1910             private int mScrollState = SCROLL_STATE_IDLE;
1911             private boolean mDelayNotifyDataChange;
1912             private ApplicationsAdapter mAdapter;
1913             private InputMethodManager mInputMethodManager;
1914             private InteractionJankMonitor mMonitor;
1915             private String mClassName;
1916 
OnScrollListener(ApplicationsAdapter adapter, String className)1917             public OnScrollListener(ApplicationsAdapter adapter, String className) {
1918                 mAdapter = adapter;
1919                 mInputMethodManager = mAdapter.mContext.getSystemService(
1920                         InputMethodManager.class);
1921                 mMonitor = InteractionJankMonitor.getInstance();
1922                 mClassName = className;
1923             }
1924 
1925             @Override
onScrollStateChanged(@onNull RecyclerView recyclerView, int newState)1926             public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
1927                 mScrollState = newState;
1928                 if (mScrollState == SCROLL_STATE_IDLE && mDelayNotifyDataChange) {
1929                     mDelayNotifyDataChange = false;
1930                     mAdapter.notifyDataSetChanged();
1931                 } else if (mScrollState == SCROLL_STATE_DRAGGING) {
1932                     // Hide keyboard when user start scrolling
1933                     if (mInputMethodManager != null && mInputMethodManager.isActive()) {
1934                         mInputMethodManager.hideSoftInputFromWindow(recyclerView.getWindowToken(),
1935                                 0);
1936                     }
1937                     // Start jank monitoring during page scrolling.
1938                     final InteractionJankMonitor.Configuration.Builder builder =
1939                             InteractionJankMonitor.Configuration.Builder.withView(
1940                                             CUJ_SETTINGS_PAGE_SCROLL, recyclerView)
1941                                     .setTag(mClassName);
1942                     mMonitor.begin(builder);
1943                 } else if (mScrollState == SCROLL_STATE_IDLE) {
1944                     // Stop jank monitoring on page scrolling.
1945                     mMonitor.end(CUJ_SETTINGS_PAGE_SCROLL);
1946                 }
1947             }
1948 
postNotifyItemChange(int index)1949             public void postNotifyItemChange(int index) {
1950                 if (mScrollState == SCROLL_STATE_IDLE) {
1951                     mAdapter.notifyItemChanged(index);
1952                 } else {
1953                     mDelayNotifyDataChange = true;
1954                 }
1955             }
1956         }
1957 
1958         /**
1959          * An array filter that constrains the content of the array adapter with a substring.
1960          * Item that does not contains the specified substring will be removed from the list.</p>
1961          */
1962         private class SearchFilter extends Filter {
1963             @WorkerThread
1964             @Override
performFiltering(CharSequence query)1965             protected FilterResults performFiltering(CharSequence query) {
1966                 final ArrayList<ApplicationsState.AppEntry> matchedEntries;
1967                 if (TextUtils.isEmpty(query)) {
1968                     matchedEntries = mOriginalEntries;
1969                 } else {
1970                     matchedEntries = new ArrayList<>();
1971                     for (ApplicationsState.AppEntry entry : mOriginalEntries) {
1972                         if (entry.label.toLowerCase().contains(query.toString().toLowerCase())) {
1973                             matchedEntries.add(entry);
1974                         }
1975                     }
1976                 }
1977                 final FilterResults results = new FilterResults();
1978                 results.values = matchedEntries;
1979                 results.count = matchedEntries.size();
1980                 return results;
1981             }
1982 
1983             @Override
publishResults(CharSequence constraint, FilterResults results)1984             protected void publishResults(CharSequence constraint, FilterResults results) {
1985                 mEntries = (ArrayList<ApplicationsState.AppEntry>) results.values;
1986                 notifyDataSetChanged();
1987             }
1988         }
1989     }
1990 }
1991