• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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         mIsWorkProfile = false;
312         mUserId = UserHandle.myUserId();
313         mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId);
314 
315         super.onAttach(context);
316         use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
317                 getFragmentManager());
318         mStorageSelectionController = use(StorageSelectionPreferenceController.class);
319         mStorageSelectionController.setOnItemSelectedListener(storageEntry -> {
320             mSelectedStorageEntry = storageEntry;
321             refreshUi();
322 
323             if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) {
324                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable,
325                         storageEntry.getDiskId());
326             } else if (storageEntry.isVolumeRecordMissed()) {
327                 StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry);
328             }
329         });
330         mStorageUsageProgressBarController = use(StorageUsageProgressBarPreferenceController.class);
331 
332         ManageStoragePreferenceController manageStoragePreferenceController =
333                 use(ManageStoragePreferenceController.class);
334         manageStoragePreferenceController.setUserId(mUserId);
335     }
336 
337     @VisibleForTesting
initializeOptionsMenu(Activity activity)338     void initializeOptionsMenu(Activity activity) {
339         mOptionMenuController = new VolumeOptionMenuController(activity, this,
340                 mSelectedStorageEntry);
341         getSettingsLifecycle().addObserver(mOptionMenuController);
342         setHasOptionsMenu(true);
343         activity.invalidateOptionsMenu();
344     }
345 
346     @Override
onResume()347     public void onResume() {
348         super.onResume();
349 
350         if (mIsLoadedFromCache) {
351             mIsLoadedFromCache = false;
352         } else {
353             mStorageEntries.clear();
354             mStorageEntries.addAll(
355                     StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
356             refreshUi();
357         }
358         mStorageManager.registerListener(mStorageEventListener);
359     }
360 
361     @Override
onPause()362     public void onPause() {
363         super.onPause();
364         mStorageManager.unregisterListener(mStorageEventListener);
365         // Destroy the data loaders to prevent unnecessary data loading when switching back to the
366         // page.
367         getLoaderManager().destroyLoader(STORAGE_JOB_ID);
368         getLoaderManager().destroyLoader(ICON_JOB_ID);
369         getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID);
370     }
371 
372     @Override
onSaveInstanceState(Bundle outState)373     public void onSaveInstanceState(Bundle outState) {
374         outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry);
375         super.onSaveInstanceState(outState);
376     }
377 
378     @Override
getHelpResource()379     public int getHelpResource() {
380         return R.string.help_url_storage_dashboard;
381     }
382 
onReceivedSizes()383     private void onReceivedSizes() {
384         if (mStorageInfo == null || mAppsResult == null) {
385             return;
386         }
387 
388         setLoading(false /* loading */, false /* animate */);
389 
390         final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
391         mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
392         mPreferenceController.setUsedSize(privateUsedBytes);
393         mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
394         // Cache total size and used size
395         mStorageCacheHelper
396                 .cacheTotalSizeAndTotalUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
397         for (NonCurrentUserController userController : mNonCurrentUsers) {
398             userController.setTotalSize(mStorageInfo.totalBytes);
399         }
400 
401         mPreferenceController.onLoadFinished(mAppsResult, mUserId);
402         updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
403         setNonCurrentUsersVisible(true);
404     }
405 
406     @Override
getMetricsCategory()407     public int getMetricsCategory() {
408         return SettingsEnums.SETTINGS_STORAGE_CATEGORY;
409     }
410 
411     @Override
getLogTag()412     protected String getLogTag() {
413         return TAG;
414     }
415 
416     @Override
getPreferenceScreenResId()417     protected int getPreferenceScreenResId() {
418         return R.xml.storage_dashboard_fragment;
419     }
420 
421     @Override
createPreferenceControllers(Context context)422     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
423         final List<AbstractPreferenceController> controllers = new ArrayList<>();
424         final StorageManager sm = context.getSystemService(StorageManager.class);
425         mPreferenceController = new StorageItemPreferenceController(context, this,
426                 null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
427         controllers.add(mPreferenceController);
428 
429         mNonCurrentUsers = NonCurrentUserController.getNonCurrentUserControllers(context,
430                 mUserManager);
431         controllers.addAll(mNonCurrentUsers);
432 
433         return controllers;
434     }
435 
436     /**
437      * Updates the non-current user controller sizes.
438      */
updateNonCurrentUserControllers(List<NonCurrentUserController> controllers, SparseArray<StorageAsyncLoader.StorageResult> stats)439     private void updateNonCurrentUserControllers(List<NonCurrentUserController> controllers,
440             SparseArray<StorageAsyncLoader.StorageResult> stats) {
441         for (AbstractPreferenceController controller : controllers) {
442             if (controller instanceof StorageAsyncLoader.ResultHandler) {
443                 StorageAsyncLoader.ResultHandler userController =
444                         (StorageAsyncLoader.ResultHandler) controller;
445                 userController.handleResult(stats);
446             }
447         }
448     }
449 
450     /**
451      * For Search.
452      */
453     public static final BaseSearchIndexProvider SEARCH_INDEX_DATA_PROVIDER =
454             new BaseSearchIndexProvider() {
455                 @Override
456                 public List<SearchIndexableResource> getXmlResourcesToIndex(
457                         Context context, boolean enabled) {
458                     final SearchIndexableResource sir = new SearchIndexableResource(context);
459                     sir.xmlResId = R.xml.storage_dashboard_fragment;
460                     return Arrays.asList(sir);
461                 }
462 
463                 @Override
464                 public List<AbstractPreferenceController> createPreferenceControllers(
465                         Context context) {
466                     final StorageManager sm = context.getSystemService(StorageManager.class);
467                     final UserManager userManager = context.getSystemService(UserManager.class);
468                     final List<AbstractPreferenceController> controllers = new ArrayList<>();
469                     controllers.add(new StorageItemPreferenceController(context, null /* host */,
470                             null /* volume */, new StorageManagerVolumeProvider(sm),
471                             false /* isWorkProfile */));
472                     controllers.addAll(NonCurrentUserController.getNonCurrentUserControllers(
473                             context, userManager));
474                     return controllers;
475                 }
476 
477             };
478 
479     @Override
onCreateLoader(int id, Bundle args)480     public Loader<SparseArray<StorageAsyncLoader.StorageResult>> onCreateLoader(int id,
481             Bundle args) {
482         final Context context = getContext();
483         return new StorageAsyncLoader(context, mUserManager,
484                 mSelectedStorageEntry.getFsUuid(),
485                 new StorageStatsSource(context),
486                 context.getPackageManager());
487     }
488 
489     @Override
onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader, SparseArray<StorageAsyncLoader.StorageResult> data)490     public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader,
491             SparseArray<StorageAsyncLoader.StorageResult> data) {
492         mAppsResult = data;
493         onReceivedSizes();
494     }
495 
496     @Override
onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader)497     public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader) {
498     }
499 
500 
501     @Override
displayResourceTilesToScreen(PreferenceScreen screen)502     public void displayResourceTilesToScreen(PreferenceScreen screen) {
503         final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY);
504         if (mNonCurrentUsers.isEmpty()) {
505             screen.removePreference(group);
506         }
507         super.displayResourceTilesToScreen(screen);
508     }
509 
510     @VisibleForTesting
getPrivateStorageInfo()511     public PrivateStorageInfo getPrivateStorageInfo() {
512         return mStorageInfo;
513     }
514 
515     @VisibleForTesting
setPrivateStorageInfo(PrivateStorageInfo info)516     public void setPrivateStorageInfo(PrivateStorageInfo info) {
517         mStorageInfo = info;
518     }
519 
520     @VisibleForTesting
getStorageResult()521     public SparseArray<StorageAsyncLoader.StorageResult> getStorageResult() {
522         return mAppsResult;
523     }
524 
525     @VisibleForTesting
setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info)526     public void setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info) {
527         mAppsResult = info;
528     }
529 
530     /**
531      * Activate loading UI and animation if it's necessary.
532      */
533     @VisibleForTesting
maybeSetLoading(boolean isQuotaSupported)534     public void maybeSetLoading(boolean isQuotaSupported) {
535         // If we have fast stats, we load until both have loaded.
536         // If we have slow stats, we load when we get the total volume sizes.
537         if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null))
538                 || (!isQuotaSupported && mStorageInfo == null)) {
539             setLoading(true /* loading */, false /* animate */);
540         }
541     }
542 
isQuotaSupported()543     private boolean isQuotaSupported() {
544         return mSelectedStorageEntry.isMounted()
545                 && getActivity().getSystemService(StorageStatsManager.class)
546                         .isQuotaSupported(mSelectedStorageEntry.getFsUuid());
547     }
548 
setNonCurrentUsersVisible(boolean visible)549     private void setNonCurrentUsersVisible(boolean visible) {
550         if (!mNonCurrentUsers.isEmpty()) {
551             mNonCurrentUsers.get(0).setPreferenceGroupVisible(visible);
552         }
553     }
554 
555     /**
556      * IconLoaderCallbacks exists because StorageDashboardFragment already implements
557      * LoaderCallbacks for a different type.
558      */
559     public final class IconLoaderCallbacks
560             implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> {
561         @Override
onCreateLoader(int id, Bundle args)562         public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) {
563             return new UserIconLoader(
564                     getContext(),
565                     () -> UserIconLoader.loadUserIconsWithContext(getContext()));
566         }
567 
568         @Override
onLoadFinished( Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data)569         public void onLoadFinished(
570                 Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
571             mNonCurrentUsers
572                     .stream()
573                     .filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
574                     .forEach(
575                             controller ->
576                                     ((UserIconLoader.UserIconHandler) controller)
577                                             .handleUserIcons(data));
578         }
579 
580         @Override
onLoaderReset(Loader<SparseArray<Drawable>> loader)581         public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {
582         }
583     }
584 
585     /**
586      * VolumeSizeCallbacks exists because StorageCategoryFragment already implements
587      * LoaderCallbacks for a different type.
588      */
589     public final class VolumeSizeCallbacks
590             implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
591         @Override
onCreateLoader(int id, Bundle args)592         public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
593             final Context context = getContext();
594             final StorageManagerVolumeProvider smvp =
595                     new StorageManagerVolumeProvider(mStorageManager);
596             final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
597             return new VolumeSizesLoader(context, smvp, stats,
598                     mSelectedStorageEntry.getVolumeInfo());
599         }
600 
601         @Override
onLoaderReset(Loader<PrivateStorageInfo> loader)602         public void onLoaderReset(Loader<PrivateStorageInfo> loader) {
603         }
604 
605         @Override
onLoadFinished( Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo)606         public void onLoadFinished(
607                 Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
608             if (privateStorageInfo == null) {
609                 getActivity().finish();
610                 return;
611             }
612 
613             mStorageInfo = privateStorageInfo;
614             onReceivedSizes();
615         }
616     }
617 }
618