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