1 /* 2 * Copyright (C) 2016 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.deviceinfo; 18 19 import android.app.Activity; 20 import android.app.settings.SettingsEnums; 21 import android.app.usage.StorageStatsManager; 22 import android.content.Context; 23 import android.graphics.drawable.Drawable; 24 import android.os.Bundle; 25 import android.os.UserHandle; 26 import android.os.UserManager; 27 import android.os.storage.DiskInfo; 28 import android.os.storage.StorageEventListener; 29 import android.os.storage.StorageManager; 30 import android.os.storage.VolumeInfo; 31 import android.os.storage.VolumeRecord; 32 import android.provider.SearchIndexableResource; 33 import android.text.TextUtils; 34 import android.util.SparseArray; 35 36 import androidx.annotation.VisibleForTesting; 37 import androidx.loader.app.LoaderManager; 38 import androidx.loader.content.Loader; 39 import androidx.preference.PreferenceGroup; 40 import androidx.preference.PreferenceScreen; 41 42 import com.android.settings.R; 43 import com.android.settings.Utils; 44 import com.android.settings.dashboard.DashboardFragment; 45 import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController; 46 import com.android.settings.deviceinfo.storage.DiskInitFragment; 47 import com.android.settings.deviceinfo.storage.ManageStoragePreferenceController; 48 import com.android.settings.deviceinfo.storage.NonCurrentUserController; 49 import com.android.settings.deviceinfo.storage.StorageAsyncLoader; 50 import com.android.settings.deviceinfo.storage.StorageCacheHelper; 51 import com.android.settings.deviceinfo.storage.StorageEntry; 52 import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; 53 import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController; 54 import com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController; 55 import com.android.settings.deviceinfo.storage.StorageUtils; 56 import com.android.settings.deviceinfo.storage.UserIconLoader; 57 import com.android.settings.deviceinfo.storage.VolumeSizesLoader; 58 import com.android.settings.search.BaseSearchIndexProvider; 59 import com.android.settingslib.applications.StorageStatsSource; 60 import com.android.settingslib.core.AbstractPreferenceController; 61 import com.android.settingslib.deviceinfo.PrivateStorageInfo; 62 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; 63 import com.android.settingslib.search.SearchIndexable; 64 65 import java.util.ArrayList; 66 import java.util.Arrays; 67 import java.util.List; 68 69 /** 70 * Storage Settings main UI is composed by 3 fragments: 71 * 72 * StorageDashboardFragment only shows when there is only personal profile for current user. 73 * 74 * ProfileSelectStorageFragment (controls preferences above profile tab) and 75 * StorageCategoryFragment (controls preferences below profile tab) only show when current 76 * user has installed work profile. 77 * 78 * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same 79 * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to 80 * change Storage Settings. 81 */ 82 @SearchIndexable 83 public class StorageDashboardFragment extends DashboardFragment 84 implements 85 LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.StorageResult>> { 86 private static final String TAG = "StorageDashboardFrag"; 87 private static final String SUMMARY_PREF_KEY = "storage_summary"; 88 private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key"; 89 private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_non_current_users"; 90 private static final int STORAGE_JOB_ID = 0; 91 private static final int ICON_JOB_ID = 1; 92 private static final int VOLUME_SIZE_JOB_ID = 2; 93 94 private StorageManager mStorageManager; 95 private UserManager mUserManager; 96 private final List<StorageEntry> mStorageEntries = new ArrayList<>(); 97 private StorageEntry mSelectedStorageEntry; 98 private PrivateStorageInfo mStorageInfo; 99 private SparseArray<StorageAsyncLoader.StorageResult> mAppsResult; 100 101 private StorageItemPreferenceController mPreferenceController; 102 private VolumeOptionMenuController mOptionMenuController; 103 private StorageSelectionPreferenceController mStorageSelectionController; 104 private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController; 105 private List<NonCurrentUserController> mNonCurrentUsers; 106 private boolean mIsWorkProfile; 107 private int mUserId; 108 private boolean mIsLoadedFromCache; 109 private StorageCacheHelper mStorageCacheHelper; 110 111 private final StorageEventListener mStorageEventListener = new StorageEventListener() { 112 @Override 113 public void onVolumeStateChanged(VolumeInfo volumeInfo, int oldState, int newState) { 114 if (!StorageUtils.isStorageSettingsInterestedVolume(volumeInfo)) { 115 return; 116 } 117 118 final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo); 119 final int volumeState = volumeInfo.getState(); 120 switch (volumeState) { 121 case VolumeInfo.STATE_REMOVED: 122 case VolumeInfo.STATE_BAD_REMOVAL: 123 // Remove removed storage from list and don't show it on spinner. 124 if (!mStorageEntries.remove(changedStorageEntry)) { 125 break; 126 } 127 case VolumeInfo.STATE_MOUNTED: 128 case VolumeInfo.STATE_MOUNTED_READ_ONLY: 129 case VolumeInfo.STATE_UNMOUNTABLE: 130 case VolumeInfo.STATE_UNMOUNTED: 131 case VolumeInfo.STATE_EJECTING: 132 // Add mounted or unmountable storage in the list and show it on spinner. 133 // Unmountable storages are the storages which has a problem format and android 134 // is not able to mount it automatically. 135 // Users can format an unmountable storage by the UI and then use the storage. 136 mStorageEntries.removeIf(storageEntry -> { 137 return storageEntry.equals(changedStorageEntry); 138 }); 139 if (volumeState == VolumeInfo.STATE_MOUNTED 140 || volumeState == VolumeInfo.STATE_MOUNTED_READ_ONLY 141 || volumeState == VolumeInfo.STATE_UNMOUNTABLE) { 142 mStorageEntries.add(changedStorageEntry); 143 if (changedStorageEntry.equals(mSelectedStorageEntry)) { 144 mSelectedStorageEntry = changedStorageEntry; 145 } 146 } else { 147 if (changedStorageEntry.equals(mSelectedStorageEntry)) { 148 mSelectedStorageEntry = 149 StorageEntry.getDefaultInternalStorageEntry(getContext()); 150 } 151 } 152 refreshUi(); 153 break; 154 default: 155 // Do nothing. 156 } 157 } 158 159 @Override 160 public void onVolumeRecordChanged(VolumeRecord volumeRecord) { 161 if (StorageUtils.isVolumeRecordMissed(mStorageManager, volumeRecord)) { 162 // VolumeRecord is a metadata of VolumeInfo, if a VolumeInfo is missing 163 // (e.g., internal SD card is removed.) show the missing storage to users, 164 // users can insert the SD card or manually forget the storage from the device. 165 final StorageEntry storageEntry = new StorageEntry(volumeRecord); 166 if (!mStorageEntries.contains(storageEntry)) { 167 mStorageEntries.add(storageEntry); 168 refreshUi(); 169 } 170 } else { 171 // Find mapped VolumeInfo and replace with existing one for something changed. 172 // (e.g., Renamed.) 173 final VolumeInfo mappedVolumeInfo = 174 mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid()); 175 if (mappedVolumeInfo == null) { 176 return; 177 } 178 179 final boolean removeMappedStorageEntry = mStorageEntries.removeIf(storageEntry -> 180 storageEntry.isVolumeInfo() 181 && TextUtils.equals(storageEntry.getFsUuid(), volumeRecord.getFsUuid()) 182 ); 183 if (removeMappedStorageEntry) { 184 mStorageEntries.add(new StorageEntry(getContext(), mappedVolumeInfo)); 185 refreshUi(); 186 } 187 } 188 } 189 190 @Override 191 public void onVolumeForgotten(String fsUuid) { 192 final StorageEntry storageEntry = new StorageEntry( 193 new VolumeRecord(VolumeInfo.TYPE_PUBLIC, fsUuid)); 194 if (mStorageEntries.remove(storageEntry)) { 195 if (mSelectedStorageEntry.equals(storageEntry)) { 196 mSelectedStorageEntry = 197 StorageEntry.getDefaultInternalStorageEntry(getContext()); 198 } 199 refreshUi(); 200 } 201 } 202 203 @Override 204 public void onDiskScanned(DiskInfo disk, int volumeCount) { 205 if (!StorageUtils.isDiskUnsupported(disk)) { 206 return; 207 } 208 final StorageEntry storageEntry = new StorageEntry(disk); 209 if (!mStorageEntries.contains(storageEntry)) { 210 mStorageEntries.add(storageEntry); 211 refreshUi(); 212 } 213 } 214 215 @Override 216 public void onDiskDestroyed(DiskInfo disk) { 217 final StorageEntry storageEntry = new StorageEntry(disk); 218 if (mStorageEntries.remove(storageEntry)) { 219 if (mSelectedStorageEntry.equals(storageEntry)) { 220 mSelectedStorageEntry = 221 StorageEntry.getDefaultInternalStorageEntry(getContext()); 222 } 223 refreshUi(); 224 } 225 } 226 }; 227 refreshUi()228 private void refreshUi() { 229 mStorageSelectionController.setStorageEntries(mStorageEntries); 230 mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry); 231 mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry); 232 233 mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry); 234 getActivity().invalidateOptionsMenu(); 235 236 // To prevent flicker, hides non-current users preference. 237 // onReceivedSizes will set it visible for private storage. 238 setNonCurrentUsersVisible(false); 239 240 if (!mSelectedStorageEntry.isMounted()) { 241 // Set null volume to hide category stats. 242 mPreferenceController.setVolume(null); 243 return; 244 } 245 246 if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) { 247 StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize(); 248 mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); 249 mPreferenceController.setUsedSize(cachedData.totalUsedSize); 250 mPreferenceController.setTotalSize(cachedData.totalSize); 251 } 252 253 if (mSelectedStorageEntry.isPrivate()) { 254 mStorageInfo = null; 255 mAppsResult = null; 256 // Hide the loading spinner if there is cached data. 257 if (mStorageCacheHelper.hasCachedSizeInfo()) { 258 //TODO(b/220259287): apply cache mechanism to non-current user 259 mPreferenceController.onLoadFinished(mAppsResult, mUserId); 260 } else { 261 maybeSetLoading(isQuotaSupported()); 262 // To prevent flicker, sets null volume to hide category preferences. 263 // onReceivedSizes will setVolume with the volume of selected storage. 264 mPreferenceController.setVolume(null); 265 } 266 // Stats data is only available on private volumes. 267 getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this); 268 getLoaderManager() 269 .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks()); 270 getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks()); 271 } else { 272 mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); 273 } 274 } 275 276 @Override onCreate(Bundle icicle)277 public void onCreate(Bundle icicle) { 278 super.onCreate(icicle); 279 280 final Activity activity = getActivity(); 281 mStorageManager = activity.getSystemService(StorageManager.class); 282 283 if (icicle == null) { 284 final VolumeInfo specifiedVolumeInfo = 285 Utils.maybeInitializeVolume(mStorageManager, getArguments()); 286 mSelectedStorageEntry = specifiedVolumeInfo == null 287 ? StorageEntry.getDefaultInternalStorageEntry(getContext()) 288 : new StorageEntry(getContext(), specifiedVolumeInfo); 289 } else { 290 mSelectedStorageEntry = icicle.getParcelable(SELECTED_STORAGE_ENTRY_KEY); 291 } 292 293 initializeOptionsMenu(activity); 294 295 if (mStorageCacheHelper.hasCachedSizeInfo()) { 296 mIsLoadedFromCache = true; 297 mStorageEntries.clear(); 298 mStorageEntries.addAll( 299 StorageUtils.getAllStorageEntries(getContext(), mStorageManager)); 300 refreshUi(); 301 updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult); 302 setNonCurrentUsersVisible(true); 303 } 304 } 305 306 @Override onAttach(Context context)307 public void onAttach(Context context) { 308 // These member variables are initialized befoer super.onAttach for 309 // createPreferenceControllers to work correctly. 310 mUserManager = context.getSystemService(UserManager.class); 311 mUserId = UserHandle.myUserId(); 312 mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId); 313 314 super.onAttach(context); 315 use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager( 316 getFragmentManager()); 317 mStorageSelectionController = use(StorageSelectionPreferenceController.class); 318 mStorageSelectionController.setOnItemSelectedListener(storageEntry -> { 319 mSelectedStorageEntry = storageEntry; 320 refreshUi(); 321 322 if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) { 323 DiskInitFragment.show(this, R.string.storage_dialog_unmountable, 324 storageEntry.getDiskId()); 325 } else if (storageEntry.isVolumeRecordMissed()) { 326 StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry); 327 } 328 }); 329 mStorageUsageProgressBarController = use(StorageUsageProgressBarPreferenceController.class); 330 331 ManageStoragePreferenceController manageStoragePreferenceController = 332 use(ManageStoragePreferenceController.class); 333 manageStoragePreferenceController.setUserId(mUserId); 334 } 335 336 @VisibleForTesting initializeOptionsMenu(Activity activity)337 void initializeOptionsMenu(Activity activity) { 338 mOptionMenuController = new VolumeOptionMenuController(activity, this, 339 mSelectedStorageEntry); 340 getSettingsLifecycle().addObserver(mOptionMenuController); 341 setHasOptionsMenu(true); 342 activity.invalidateOptionsMenu(); 343 } 344 345 @Override onResume()346 public void onResume() { 347 super.onResume(); 348 349 if (mIsLoadedFromCache) { 350 mIsLoadedFromCache = false; 351 } else { 352 mStorageEntries.clear(); 353 mStorageEntries.addAll( 354 StorageUtils.getAllStorageEntries(getContext(), mStorageManager)); 355 refreshUi(); 356 } 357 mStorageManager.registerListener(mStorageEventListener); 358 } 359 360 @Override onPause()361 public void onPause() { 362 super.onPause(); 363 mStorageManager.unregisterListener(mStorageEventListener); 364 // Destroy the data loaders to prevent unnecessary data loading when switching back to the 365 // page. 366 getLoaderManager().destroyLoader(STORAGE_JOB_ID); 367 getLoaderManager().destroyLoader(ICON_JOB_ID); 368 getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID); 369 } 370 371 @Override onSaveInstanceState(Bundle outState)372 public void onSaveInstanceState(Bundle outState) { 373 outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry); 374 super.onSaveInstanceState(outState); 375 } 376 377 @Override getHelpResource()378 public int getHelpResource() { 379 return R.string.help_url_storage_dashboard; 380 } 381 onReceivedSizes()382 private void onReceivedSizes() { 383 if (mStorageInfo == null || mAppsResult == null) { 384 return; 385 } 386 387 setLoading(false /* loading */, false /* animate */); 388 389 final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes; 390 mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo()); 391 mPreferenceController.setUsedSize(privateUsedBytes); 392 mPreferenceController.setTotalSize(mStorageInfo.totalBytes); 393 // Cache total size and used size 394 mStorageCacheHelper 395 .cacheTotalSizeAndTotalUsedSize(mStorageInfo.totalBytes, privateUsedBytes); 396 for (NonCurrentUserController userController : mNonCurrentUsers) { 397 userController.setTotalSize(mStorageInfo.totalBytes); 398 } 399 400 mPreferenceController.onLoadFinished(mAppsResult, mUserId); 401 updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult); 402 setNonCurrentUsersVisible(true); 403 } 404 405 @Override getMetricsCategory()406 public int getMetricsCategory() { 407 return SettingsEnums.SETTINGS_STORAGE_CATEGORY; 408 } 409 410 @Override getLogTag()411 protected String getLogTag() { 412 return TAG; 413 } 414 415 @Override getPreferenceScreenResId()416 protected int getPreferenceScreenResId() { 417 return R.xml.storage_dashboard_fragment; 418 } 419 420 @Override createPreferenceControllers(Context context)421 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 422 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 423 final StorageManager sm = context.getSystemService(StorageManager.class); 424 mPreferenceController = new StorageItemPreferenceController(context, this, 425 null /* volume */, new StorageManagerVolumeProvider(sm)); 426 controllers.add(mPreferenceController); 427 428 mNonCurrentUsers = NonCurrentUserController.getNonCurrentUserControllers(context, 429 mUserManager); 430 controllers.addAll(mNonCurrentUsers); 431 432 return controllers; 433 } 434 435 /** 436 * Updates the non-current user controller sizes. 437 */ updateNonCurrentUserControllers(List<NonCurrentUserController> controllers, SparseArray<StorageAsyncLoader.StorageResult> stats)438 private void updateNonCurrentUserControllers(List<NonCurrentUserController> controllers, 439 SparseArray<StorageAsyncLoader.StorageResult> stats) { 440 for (AbstractPreferenceController controller : controllers) { 441 if (controller instanceof StorageAsyncLoader.ResultHandler) { 442 StorageAsyncLoader.ResultHandler userController = 443 (StorageAsyncLoader.ResultHandler) controller; 444 userController.handleResult(stats); 445 } 446 } 447 } 448 449 /** 450 * For Search. 451 */ 452 public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 453 new BaseSearchIndexProvider() { 454 @Override 455 public List<SearchIndexableResource> getXmlResourcesToIndex( 456 Context context, boolean enabled) { 457 final SearchIndexableResource sir = new SearchIndexableResource(context); 458 sir.xmlResId = R.xml.storage_dashboard_fragment; 459 return Arrays.asList(sir); 460 } 461 462 @Override 463 public List<AbstractPreferenceController> createPreferenceControllers( 464 Context context) { 465 final StorageManager sm = context.getSystemService(StorageManager.class); 466 final UserManager userManager = context.getSystemService(UserManager.class); 467 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 468 controllers.add(new StorageItemPreferenceController(context, null /* host */, 469 null /* volume */, new StorageManagerVolumeProvider(sm))); 470 controllers.addAll(NonCurrentUserController.getNonCurrentUserControllers( 471 context, userManager)); 472 return controllers; 473 } 474 475 }; 476 477 @Override onCreateLoader(int id, Bundle args)478 public Loader<SparseArray<StorageAsyncLoader.StorageResult>> onCreateLoader(int id, 479 Bundle args) { 480 final Context context = getContext(); 481 return new StorageAsyncLoader(context, mUserManager, 482 mSelectedStorageEntry.getFsUuid(), 483 new StorageStatsSource(context), 484 context.getPackageManager()); 485 } 486 487 @Override onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader, SparseArray<StorageAsyncLoader.StorageResult> data)488 public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader, 489 SparseArray<StorageAsyncLoader.StorageResult> data) { 490 mAppsResult = data; 491 onReceivedSizes(); 492 } 493 494 @Override onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader)495 public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader) { 496 } 497 498 499 @Override displayResourceTilesToScreen(PreferenceScreen screen)500 public void displayResourceTilesToScreen(PreferenceScreen screen) { 501 final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY); 502 if (mNonCurrentUsers.isEmpty()) { 503 screen.removePreference(group); 504 } 505 super.displayResourceTilesToScreen(screen); 506 } 507 508 @VisibleForTesting getPrivateStorageInfo()509 public PrivateStorageInfo getPrivateStorageInfo() { 510 return mStorageInfo; 511 } 512 513 @VisibleForTesting setPrivateStorageInfo(PrivateStorageInfo info)514 public void setPrivateStorageInfo(PrivateStorageInfo info) { 515 mStorageInfo = info; 516 } 517 518 @VisibleForTesting getStorageResult()519 public SparseArray<StorageAsyncLoader.StorageResult> getStorageResult() { 520 return mAppsResult; 521 } 522 523 @VisibleForTesting setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info)524 public void setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info) { 525 mAppsResult = info; 526 } 527 528 /** 529 * Activate loading UI and animation if it's necessary. 530 */ 531 @VisibleForTesting maybeSetLoading(boolean isQuotaSupported)532 public void maybeSetLoading(boolean isQuotaSupported) { 533 // If we have fast stats, we load until both have loaded. 534 // If we have slow stats, we load when we get the total volume sizes. 535 if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null)) 536 || (!isQuotaSupported && mStorageInfo == null)) { 537 setLoading(true /* loading */, false /* animate */); 538 } 539 } 540 isQuotaSupported()541 private boolean isQuotaSupported() { 542 return mSelectedStorageEntry.isMounted() 543 && getActivity().getSystemService(StorageStatsManager.class) 544 .isQuotaSupported(mSelectedStorageEntry.getFsUuid()); 545 } 546 setNonCurrentUsersVisible(boolean visible)547 private void setNonCurrentUsersVisible(boolean visible) { 548 if (!mNonCurrentUsers.isEmpty()) { 549 mNonCurrentUsers.get(0).setPreferenceGroupVisible(visible); 550 } 551 } 552 553 /** 554 * IconLoaderCallbacks exists because StorageDashboardFragment already implements 555 * LoaderCallbacks for a different type. 556 */ 557 public final class IconLoaderCallbacks 558 implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> { 559 @Override onCreateLoader(int id, Bundle args)560 public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) { 561 return new UserIconLoader( 562 getContext(), 563 () -> UserIconLoader.loadUserIconsWithContext(getContext())); 564 } 565 566 @Override onLoadFinished( Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data)567 public void onLoadFinished( 568 Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) { 569 mNonCurrentUsers 570 .stream() 571 .filter(controller -> controller instanceof UserIconLoader.UserIconHandler) 572 .forEach( 573 controller -> 574 ((UserIconLoader.UserIconHandler) controller) 575 .handleUserIcons(data)); 576 } 577 578 @Override onLoaderReset(Loader<SparseArray<Drawable>> loader)579 public void onLoaderReset(Loader<SparseArray<Drawable>> loader) { 580 } 581 } 582 583 /** 584 * VolumeSizeCallbacks exists because StorageCategoryFragment already implements 585 * LoaderCallbacks for a different type. 586 */ 587 public final class VolumeSizeCallbacks 588 implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> { 589 @Override onCreateLoader(int id, Bundle args)590 public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) { 591 final Context context = getContext(); 592 final StorageManagerVolumeProvider smvp = 593 new StorageManagerVolumeProvider(mStorageManager); 594 final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class); 595 return new VolumeSizesLoader(context, smvp, stats, 596 mSelectedStorageEntry.getVolumeInfo()); 597 } 598 599 @Override onLoaderReset(Loader<PrivateStorageInfo> loader)600 public void onLoaderReset(Loader<PrivateStorageInfo> loader) { 601 } 602 603 @Override onLoadFinished( Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo)604 public void onLoadFinished( 605 Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) { 606 if (privateStorageInfo == null) { 607 getActivity().finish(); 608 return; 609 } 610 611 mStorageInfo = privateStorageInfo; 612 onReceivedSizes(); 613 } 614 } 615 } 616