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