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