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