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