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