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.StorageManager; 28 import android.os.storage.VolumeInfo; 29 import android.provider.SearchIndexableResource; 30 import android.util.SparseArray; 31 import android.view.View; 32 33 import androidx.annotation.VisibleForTesting; 34 import androidx.loader.app.LoaderManager; 35 import androidx.loader.content.Loader; 36 37 import com.android.settings.R; 38 import com.android.settings.Utils; 39 import com.android.settings.dashboard.DashboardFragment; 40 import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController; 41 import com.android.settings.deviceinfo.storage.CachedStorageValuesHelper; 42 import com.android.settings.deviceinfo.storage.SecondaryUserController; 43 import com.android.settings.deviceinfo.storage.StorageAsyncLoader; 44 import com.android.settings.deviceinfo.storage.StorageItemPreferenceController; 45 import com.android.settings.deviceinfo.storage.StorageSummaryDonutPreferenceController; 46 import com.android.settings.deviceinfo.storage.UserIconLoader; 47 import com.android.settings.deviceinfo.storage.VolumeSizesLoader; 48 import com.android.settings.search.BaseSearchIndexProvider; 49 import com.android.settings.search.Indexable; 50 import com.android.settings.widget.EntityHeaderController; 51 import com.android.settingslib.applications.StorageStatsSource; 52 import com.android.settingslib.core.AbstractPreferenceController; 53 import com.android.settingslib.deviceinfo.PrivateStorageInfo; 54 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider; 55 import com.android.settingslib.search.SearchIndexable; 56 57 import java.util.ArrayList; 58 import java.util.Arrays; 59 import java.util.List; 60 61 @SearchIndexable 62 public class StorageDashboardFragment extends DashboardFragment 63 implements 64 LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.AppsStorageResult>> { 65 private static final String TAG = "StorageDashboardFrag"; 66 private static final int STORAGE_JOB_ID = 0; 67 private static final int ICON_JOB_ID = 1; 68 private static final int VOLUME_SIZE_JOB_ID = 2; 69 private static final int OPTIONS_MENU_MIGRATE_DATA = 100; 70 71 private VolumeInfo mVolume; 72 private PrivateStorageInfo mStorageInfo; 73 private SparseArray<StorageAsyncLoader.AppsStorageResult> mAppsResult; 74 private CachedStorageValuesHelper mCachedStorageValuesHelper; 75 76 private StorageSummaryDonutPreferenceController mSummaryController; 77 private StorageItemPreferenceController mPreferenceController; 78 private PrivateVolumeOptionMenuController mOptionMenuController; 79 private List<AbstractPreferenceController> mSecondaryUsers; 80 81 @Override onCreate(Bundle icicle)82 public void onCreate(Bundle icicle) { 83 super.onCreate(icicle); 84 85 // Initialize the storage sizes that we can quickly calc. 86 final Activity activity = getActivity(); 87 StorageManager sm = activity.getSystemService(StorageManager.class); 88 mVolume = Utils.maybeInitializeVolume(sm, getArguments()); 89 if (mVolume == null) { 90 activity.finish(); 91 return; 92 } 93 94 initializeOptionsMenu(activity); 95 } 96 97 @Override onAttach(Context context)98 public void onAttach(Context context) { 99 super.onAttach(context); 100 use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager( 101 getFragmentManager()); 102 } 103 104 @VisibleForTesting initializeOptionsMenu(Activity activity)105 void initializeOptionsMenu(Activity activity) { 106 mOptionMenuController = new PrivateVolumeOptionMenuController( 107 activity, mVolume, activity.getPackageManager()); 108 getSettingsLifecycle().addObserver(mOptionMenuController); 109 setHasOptionsMenu(true); 110 activity.invalidateOptionsMenu(); 111 } 112 113 @Override onViewCreated(View v, Bundle savedInstanceState)114 public void onViewCreated(View v, Bundle savedInstanceState) { 115 super.onViewCreated(v, savedInstanceState); 116 initializeCacheProvider(); 117 maybeSetLoading(isQuotaSupported()); 118 119 final Activity activity = getActivity(); 120 EntityHeaderController.newInstance(activity, this /*fragment*/, 121 null /* header view */) 122 .setRecyclerView(getListView(), getSettingsLifecycle()) 123 .styleActionBar(activity); 124 125 } 126 127 @Override onResume()128 public void onResume() { 129 super.onResume(); 130 getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this); 131 getLoaderManager() 132 .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks()); 133 getLoaderManager().initLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks()); 134 } 135 136 @Override getHelpResource()137 public int getHelpResource() { 138 return R.string.help_url_storage_dashboard; 139 } 140 onReceivedSizes()141 private void onReceivedSizes() { 142 if (mStorageInfo != null) { 143 long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes; 144 mSummaryController.updateBytes(privateUsedBytes, mStorageInfo.totalBytes); 145 mPreferenceController.setVolume(mVolume); 146 mPreferenceController.setUsedSize(privateUsedBytes); 147 mPreferenceController.setTotalSize(mStorageInfo.totalBytes); 148 for (int i = 0, size = mSecondaryUsers.size(); i < size; i++) { 149 AbstractPreferenceController controller = mSecondaryUsers.get(i); 150 if (controller instanceof SecondaryUserController) { 151 SecondaryUserController userController = (SecondaryUserController) controller; 152 userController.setTotalSize(mStorageInfo.totalBytes); 153 } 154 } 155 } 156 157 if (mAppsResult == null) { 158 return; 159 } 160 161 mPreferenceController.onLoadFinished(mAppsResult, UserHandle.myUserId()); 162 updateSecondaryUserControllers(mSecondaryUsers, mAppsResult); 163 164 // setLoading always causes a flicker, so let's avoid doing it. 165 if (getView().findViewById(R.id.loading_container).getVisibility() == View.VISIBLE) { 166 setLoading(false, true); 167 } 168 } 169 170 @Override getMetricsCategory()171 public int getMetricsCategory() { 172 return SettingsEnums.SETTINGS_STORAGE_CATEGORY; 173 } 174 175 @Override getLogTag()176 protected String getLogTag() { 177 return TAG; 178 } 179 180 @Override getPreferenceScreenResId()181 protected int getPreferenceScreenResId() { 182 return R.xml.storage_dashboard_fragment; 183 } 184 185 @Override createPreferenceControllers(Context context)186 protected List<AbstractPreferenceController> createPreferenceControllers(Context context) { 187 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 188 mSummaryController = new StorageSummaryDonutPreferenceController(context); 189 controllers.add(mSummaryController); 190 191 StorageManager sm = context.getSystemService(StorageManager.class); 192 mPreferenceController = new StorageItemPreferenceController(context, this, 193 mVolume, new StorageManagerVolumeProvider(sm)); 194 controllers.add(mPreferenceController); 195 196 final UserManager userManager = context.getSystemService(UserManager.class); 197 mSecondaryUsers = SecondaryUserController.getSecondaryUserControllers(context, userManager); 198 controllers.addAll(mSecondaryUsers); 199 200 return controllers; 201 } 202 203 @VisibleForTesting setVolume(VolumeInfo info)204 protected void setVolume(VolumeInfo info) { 205 mVolume = info; 206 } 207 208 /** 209 * Updates the secondary user controller sizes. 210 */ updateSecondaryUserControllers(List<AbstractPreferenceController> controllers, SparseArray<StorageAsyncLoader.AppsStorageResult> stats)211 private void updateSecondaryUserControllers(List<AbstractPreferenceController> controllers, 212 SparseArray<StorageAsyncLoader.AppsStorageResult> stats) { 213 for (int i = 0, size = controllers.size(); i < size; i++) { 214 AbstractPreferenceController controller = controllers.get(i); 215 if (controller instanceof StorageAsyncLoader.ResultHandler) { 216 StorageAsyncLoader.ResultHandler userController = 217 (StorageAsyncLoader.ResultHandler) controller; 218 userController.handleResult(stats); 219 } 220 } 221 } 222 223 /** 224 * For Search. 225 */ 226 public static final Indexable.SearchIndexProvider SEARCH_INDEX_DATA_PROVIDER = 227 new BaseSearchIndexProvider() { 228 @Override 229 public List<SearchIndexableResource> getXmlResourcesToIndex( 230 Context context, boolean enabled) { 231 final SearchIndexableResource sir = new SearchIndexableResource(context); 232 sir.xmlResId = R.xml.storage_dashboard_fragment; 233 return Arrays.asList(sir); 234 } 235 236 @Override 237 public List<AbstractPreferenceController> createPreferenceControllers( 238 Context context) { 239 final StorageManager sm = context.getSystemService(StorageManager.class); 240 final UserManager userManager = context.getSystemService(UserManager.class); 241 final List<AbstractPreferenceController> controllers = new ArrayList<>(); 242 controllers.add(new StorageSummaryDonutPreferenceController(context)); 243 controllers.add(new StorageItemPreferenceController(context, null /* host */, 244 null /* volume */, new StorageManagerVolumeProvider(sm))); 245 controllers.addAll(SecondaryUserController.getSecondaryUserControllers( 246 context, userManager)); 247 return controllers; 248 } 249 250 }; 251 252 @Override onCreateLoader(int id, Bundle args)253 public Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> onCreateLoader(int id, 254 Bundle args) { 255 final Context context = getContext(); 256 return new StorageAsyncLoader(context, context.getSystemService(UserManager.class), 257 mVolume.fsUuid, 258 new StorageStatsSource(context), 259 context.getPackageManager()); 260 } 261 262 @Override onLoadFinished(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader, SparseArray<StorageAsyncLoader.AppsStorageResult> data)263 public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader, 264 SparseArray<StorageAsyncLoader.AppsStorageResult> data) { 265 mAppsResult = data; 266 maybeCacheFreshValues(); 267 onReceivedSizes(); 268 } 269 270 @Override onLoaderReset(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader)271 public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.AppsStorageResult>> loader) { 272 } 273 274 @VisibleForTesting setCachedStorageValuesHelper(CachedStorageValuesHelper helper)275 public void setCachedStorageValuesHelper(CachedStorageValuesHelper helper) { 276 mCachedStorageValuesHelper = helper; 277 } 278 279 @VisibleForTesting getPrivateStorageInfo()280 public PrivateStorageInfo getPrivateStorageInfo() { 281 return mStorageInfo; 282 } 283 284 @VisibleForTesting setPrivateStorageInfo(PrivateStorageInfo info)285 public void setPrivateStorageInfo(PrivateStorageInfo info) { 286 mStorageInfo = info; 287 } 288 289 @VisibleForTesting getAppsStorageResult()290 public SparseArray<StorageAsyncLoader.AppsStorageResult> getAppsStorageResult() { 291 return mAppsResult; 292 } 293 294 @VisibleForTesting setAppsStorageResult(SparseArray<StorageAsyncLoader.AppsStorageResult> info)295 public void setAppsStorageResult(SparseArray<StorageAsyncLoader.AppsStorageResult> info) { 296 mAppsResult = info; 297 } 298 299 @VisibleForTesting initializeCachedValues()300 public void initializeCachedValues() { 301 PrivateStorageInfo info = mCachedStorageValuesHelper.getCachedPrivateStorageInfo(); 302 SparseArray<StorageAsyncLoader.AppsStorageResult> loaderResult = 303 mCachedStorageValuesHelper.getCachedAppsStorageResult(); 304 if (info == null || loaderResult == null) { 305 return; 306 } 307 308 mStorageInfo = info; 309 mAppsResult = loaderResult; 310 } 311 312 @VisibleForTesting maybeSetLoading(boolean isQuotaSupported)313 public void maybeSetLoading(boolean isQuotaSupported) { 314 // If we have fast stats, we load until both have loaded. 315 // If we have slow stats, we load when we get the total volume sizes. 316 if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null)) || 317 (!isQuotaSupported && mStorageInfo == null)) { 318 setLoading(true /* loading */, false /* animate */); 319 } 320 } 321 initializeCacheProvider()322 private void initializeCacheProvider() { 323 mCachedStorageValuesHelper = 324 new CachedStorageValuesHelper(getContext(), UserHandle.myUserId()); 325 initializeCachedValues(); 326 onReceivedSizes(); 327 } 328 maybeCacheFreshValues()329 private void maybeCacheFreshValues() { 330 if (mStorageInfo != null && mAppsResult != null) { 331 mCachedStorageValuesHelper.cacheResult( 332 mStorageInfo, mAppsResult.get(UserHandle.myUserId())); 333 } 334 } 335 isQuotaSupported()336 private boolean isQuotaSupported() { 337 final StorageStatsManager stats = getActivity().getSystemService(StorageStatsManager.class); 338 return stats.isQuotaSupported(mVolume.fsUuid); 339 } 340 341 /** 342 * IconLoaderCallbacks exists because StorageDashboardFragment already implements 343 * LoaderCallbacks for a different type. 344 */ 345 public final class IconLoaderCallbacks 346 implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> { 347 @Override onCreateLoader(int id, Bundle args)348 public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) { 349 return new UserIconLoader( 350 getContext(), 351 () -> UserIconLoader.loadUserIconsWithContext(getContext())); 352 } 353 354 @Override onLoadFinished( Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data)355 public void onLoadFinished( 356 Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) { 357 mSecondaryUsers 358 .stream() 359 .filter(controller -> controller instanceof UserIconLoader.UserIconHandler) 360 .forEach( 361 controller -> 362 ((UserIconLoader.UserIconHandler) controller) 363 .handleUserIcons(data)); 364 } 365 366 @Override onLoaderReset(Loader<SparseArray<Drawable>> loader)367 public void onLoaderReset(Loader<SparseArray<Drawable>> loader) { 368 } 369 } 370 371 public final class VolumeSizeCallbacks 372 implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> { 373 @Override onCreateLoader(int id, Bundle args)374 public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) { 375 Context context = getContext(); 376 StorageManager sm = context.getSystemService(StorageManager.class); 377 StorageManagerVolumeProvider smvp = new StorageManagerVolumeProvider(sm); 378 final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class); 379 return new VolumeSizesLoader(context, smvp, stats, mVolume); 380 } 381 382 @Override onLoaderReset(Loader<PrivateStorageInfo> loader)383 public void onLoaderReset(Loader<PrivateStorageInfo> loader) { 384 } 385 386 @Override onLoadFinished( Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo)387 public void onLoadFinished( 388 Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) { 389 if (privateStorageInfo == null) { 390 getActivity().finish(); 391 return; 392 } 393 394 mStorageInfo = privateStorageInfo; 395 maybeCacheFreshValues(); 396 onReceivedSizes(); 397 } 398 } 399 } 400