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