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