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