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