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