• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2021 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 static java.util.Collections.EMPTY_LIST;
20 
21 import android.app.settings.SettingsEnums;
22 import android.app.usage.StorageStatsManager;
23 import android.content.Context;
24 import android.graphics.drawable.Drawable;
25 import android.os.Bundle;
26 import android.os.UserManager;
27 import android.os.storage.StorageManager;
28 import android.util.SparseArray;
29 
30 import androidx.annotation.VisibleForTesting;
31 import androidx.loader.app.LoaderManager;
32 import androidx.loader.content.Loader;
33 import androidx.preference.PreferenceGroup;
34 import androidx.preference.PreferenceScreen;
35 
36 import com.android.settings.R;
37 import com.android.settings.Utils;
38 import com.android.settings.dashboard.DashboardFragment;
39 import com.android.settings.dashboard.profileselector.ProfileSelectFragment;
40 import com.android.settings.deviceinfo.storage.ManageStoragePreferenceController;
41 import com.android.settings.deviceinfo.storage.NonCurrentUserController;
42 import com.android.settings.deviceinfo.storage.StorageAsyncLoader;
43 import com.android.settings.deviceinfo.storage.StorageCacheHelper;
44 import com.android.settings.deviceinfo.storage.StorageEntry;
45 import com.android.settings.deviceinfo.storage.StorageItemPreferenceController;
46 import com.android.settings.deviceinfo.storage.UserIconLoader;
47 import com.android.settings.deviceinfo.storage.VolumeSizesLoader;
48 import com.android.settingslib.applications.StorageStatsSource;
49 import com.android.settingslib.core.AbstractPreferenceController;
50 import com.android.settingslib.deviceinfo.PrivateStorageInfo;
51 import com.android.settingslib.deviceinfo.StorageManagerVolumeProvider;
52 
53 import java.util.ArrayList;
54 import java.util.List;
55 
56 /**
57  * Storage Settings main UI is composed by 3 fragments:
58  *
59  * StorageDashboardFragment only shows when there is only personal profile for current user.
60  *
61  * ProfileSelectStorageFragment (controls preferences above profile tab) and
62  * StorageCategoryFragment (controls preferences below profile tab) only show when current
63  * user has installed work profile.
64  *
65  * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same
66  * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to
67  * change Storage Settings.
68  */
69 public class StorageCategoryFragment extends DashboardFragment
70         implements
71         LoaderManager.LoaderCallbacks<SparseArray<StorageAsyncLoader.StorageResult>> {
72     private static final String TAG = "StorageCategoryFrag";
73     private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key";
74     private static final String SUMMARY_PREF_KEY = "storage_summary";
75     private static final String TARGET_PREFERENCE_GROUP_KEY = "pref_non_current_users";
76     private static final int STORAGE_JOB_ID = 0;
77     private static final int ICON_JOB_ID = 1;
78     private static final int VOLUME_SIZE_JOB_ID = 2;
79 
80     private StorageManager mStorageManager;
81     private UserManager mUserManager;
82     private StorageEntry mSelectedStorageEntry;
83     private PrivateStorageInfo mStorageInfo;
84     private SparseArray<StorageAsyncLoader.StorageResult> mAppsResult;
85 
86     private StorageItemPreferenceController mPreferenceController;
87     private List<NonCurrentUserController> mNonCurrentUsers;
88     private boolean mIsWorkProfile;
89     private int mUserId;
90     private boolean mIsLoadedFromCache;
91     private StorageCacheHelper mStorageCacheHelper;
92 
93     /**
94      * Refresh UI for specified storageEntry.
95      */
refreshUi(StorageEntry storageEntry)96     public void refreshUi(StorageEntry storageEntry) {
97         mSelectedStorageEntry = storageEntry;
98         if (mPreferenceController == null) {
99             // Check null here because when onResume, StorageCategoryFragment may not
100             // onAttach to createPreferenceControllers and mPreferenceController will be null.
101             return;
102         }
103 
104         // To prevent flicker, hides non-current users preference.
105         // onReceivedSizes will set it visible for private storage.
106         setNonCurrentUsersVisible(false);
107 
108         if (!mSelectedStorageEntry.isMounted()) {
109             // Set null volume to hide category stats.
110             mPreferenceController.setVolume(null);
111             return;
112         }
113         if (mStorageCacheHelper.hasCachedSizeInfo() && mSelectedStorageEntry.isPrivate()) {
114             StorageCacheHelper.StorageCache cachedData = mStorageCacheHelper.retrieveCachedSize();
115             mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
116             mPreferenceController.setUsedSize(cachedData.totalUsedSize);
117             mPreferenceController.setTotalSize(cachedData.totalSize);
118         }
119         if (mSelectedStorageEntry.isPrivate()) {
120             mStorageInfo = null;
121             mAppsResult = null;
122             if (mStorageCacheHelper.hasCachedSizeInfo()) {
123                 mPreferenceController.onLoadFinished(mAppsResult, mUserId);
124             } else {
125                 maybeSetLoading(isQuotaSupported());
126                 // To prevent flicker, sets null volume to hide category preferences.
127                 // onReceivedSizes will setVolume with the volume of selected storage.
128                 mPreferenceController.setVolume(null);
129             }
130 
131             // Stats data is only available on private volumes.
132             getLoaderManager().restartLoader(STORAGE_JOB_ID, Bundle.EMPTY, this);
133             getLoaderManager()
134                  .restartLoader(VOLUME_SIZE_JOB_ID, Bundle.EMPTY, new VolumeSizeCallbacks());
135             getLoaderManager().restartLoader(ICON_JOB_ID, Bundle.EMPTY, new IconLoaderCallbacks());
136         } else {
137             mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
138         }
139     }
140 
141     @Override
onCreate(Bundle icicle)142     public void onCreate(Bundle icicle) {
143         super.onCreate(icicle);
144 
145         mStorageManager = getActivity().getSystemService(StorageManager.class);
146 
147         if (icicle != null) {
148             mSelectedStorageEntry = icicle.getParcelable(SELECTED_STORAGE_ENTRY_KEY);
149         }
150 
151         if (mStorageCacheHelper.hasCachedSizeInfo()) {
152             mIsLoadedFromCache = true;
153             if (mSelectedStorageEntry != null) {
154                 refreshUi(mSelectedStorageEntry);
155             }
156             updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
157             setNonCurrentUsersVisible(true);
158         }
159     }
160 
161     @Override
onAttach(Context context)162     public void onAttach(Context context) {
163         // These member variables are initialized befoer super.onAttach for
164         // createPreferenceControllers to work correctly.
165         mUserManager = context.getSystemService(UserManager.class);
166         mIsWorkProfile = getArguments().getInt(ProfileSelectFragment.EXTRA_PROFILE)
167                 == ProfileSelectFragment.ProfileType.WORK;
168         mUserId = Utils.getCurrentUserId(mUserManager, mIsWorkProfile);
169         mStorageCacheHelper = new StorageCacheHelper(getContext(), mUserId);
170 
171         super.onAttach(context);
172 
173         ManageStoragePreferenceController manageStoragePreferenceController =
174                 use(ManageStoragePreferenceController.class);
175         manageStoragePreferenceController.setUserId(mUserId);
176     }
177 
178     @Override
onResume()179     public void onResume() {
180         super.onResume();
181 
182         if (mIsLoadedFromCache) {
183             mIsLoadedFromCache = false;
184         } else {
185             if (mSelectedStorageEntry != null) {
186                 refreshUi(mSelectedStorageEntry);
187             }
188         }
189     }
190 
191     @Override
onPause()192     public void onPause() {
193         super.onPause();
194         // Destroy the data loaders to prevent unnecessary data loading when switching back to the
195         // page.
196         getLoaderManager().destroyLoader(STORAGE_JOB_ID);
197         getLoaderManager().destroyLoader(ICON_JOB_ID);
198         getLoaderManager().destroyLoader(VOLUME_SIZE_JOB_ID);
199     }
200 
201     @Override
onSaveInstanceState(Bundle outState)202     public void onSaveInstanceState(Bundle outState) {
203         outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry);
204         super.onSaveInstanceState(outState);
205     }
206 
onReceivedSizes()207     private void onReceivedSizes() {
208         if (mStorageInfo == null || mAppsResult == null) {
209             return;
210         }
211 
212         setLoading(false /* loading */, false /* animate */);
213 
214         final long privateUsedBytes = mStorageInfo.totalBytes - mStorageInfo.freeBytes;
215         mPreferenceController.setVolume(mSelectedStorageEntry.getVolumeInfo());
216         mPreferenceController.setUsedSize(privateUsedBytes);
217         mPreferenceController.setTotalSize(mStorageInfo.totalBytes);
218         // Cache total size infor and used size info
219         mStorageCacheHelper
220                 .cacheTotalSizeAndTotalUsedSize(mStorageInfo.totalBytes, privateUsedBytes);
221         for (NonCurrentUserController userController : mNonCurrentUsers) {
222             userController.setTotalSize(mStorageInfo.totalBytes);
223         }
224 
225         mPreferenceController.onLoadFinished(mAppsResult, mUserId);
226         updateNonCurrentUserControllers(mNonCurrentUsers, mAppsResult);
227         setNonCurrentUsersVisible(true);
228     }
229 
230     @Override
getMetricsCategory()231     public int getMetricsCategory() {
232         return mIsWorkProfile ? SettingsEnums.SETTINGS_STORAGE_CATEGORY_WORK :
233                 SettingsEnums.SETTINGS_STORAGE_CATEGORY;
234     }
235 
236     @Override
getLogTag()237     protected String getLogTag() {
238         return TAG;
239     }
240 
241     @Override
getPreferenceScreenResId()242     protected int getPreferenceScreenResId() {
243         return R.xml.storage_category_fragment;
244     }
245 
246     @Override
createPreferenceControllers(Context context)247     protected List<AbstractPreferenceController> createPreferenceControllers(Context context) {
248         final List<AbstractPreferenceController> controllers = new ArrayList<>();
249         final StorageManager sm = context.getSystemService(StorageManager.class);
250         mPreferenceController = new StorageItemPreferenceController(context, this,
251                 null /* volume */, new StorageManagerVolumeProvider(sm), mIsWorkProfile);
252         controllers.add(mPreferenceController);
253 
254         mNonCurrentUsers = mIsWorkProfile ? EMPTY_LIST :
255                 NonCurrentUserController.getNonCurrentUserControllers(context, mUserManager);
256         controllers.addAll(mNonCurrentUsers);
257         return controllers;
258     }
259 
260     /**
261      * Updates the non-current user controller sizes.
262      */
updateNonCurrentUserControllers(List<NonCurrentUserController> controllers, SparseArray<StorageAsyncLoader.StorageResult> stats)263     private void updateNonCurrentUserControllers(List<NonCurrentUserController> controllers,
264             SparseArray<StorageAsyncLoader.StorageResult> stats) {
265         for (AbstractPreferenceController controller : controllers) {
266             if (controller instanceof StorageAsyncLoader.ResultHandler) {
267                 StorageAsyncLoader.ResultHandler userController =
268                         (StorageAsyncLoader.ResultHandler) controller;
269                 userController.handleResult(stats);
270             }
271         }
272     }
273 
274     @Override
onCreateLoader(int id, Bundle args)275     public Loader<SparseArray<StorageAsyncLoader.StorageResult>> onCreateLoader(int id,
276             Bundle args) {
277         final Context context = getContext();
278         return new StorageAsyncLoader(context, mUserManager,
279                 mSelectedStorageEntry.getFsUuid(),
280                 new StorageStatsSource(context),
281                 context.getPackageManager());
282     }
283 
284     @Override
onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader, SparseArray<StorageAsyncLoader.StorageResult> data)285     public void onLoadFinished(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader,
286             SparseArray<StorageAsyncLoader.StorageResult> data) {
287         mAppsResult = data;
288         onReceivedSizes();
289     }
290 
291     @Override
onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader)292     public void onLoaderReset(Loader<SparseArray<StorageAsyncLoader.StorageResult>> loader) {
293     }
294 
295     @Override
displayResourceTilesToScreen(PreferenceScreen screen)296     public void displayResourceTilesToScreen(PreferenceScreen screen) {
297         final PreferenceGroup group = screen.findPreference(TARGET_PREFERENCE_GROUP_KEY);
298         if (mNonCurrentUsers.isEmpty()) {
299             screen.removePreference(group);
300         }
301         super.displayResourceTilesToScreen(screen);
302     }
303 
304     @VisibleForTesting
getPrivateStorageInfo()305     public PrivateStorageInfo getPrivateStorageInfo() {
306         return mStorageInfo;
307     }
308 
309     @VisibleForTesting
setPrivateStorageInfo(PrivateStorageInfo info)310     public void setPrivateStorageInfo(PrivateStorageInfo info) {
311         mStorageInfo = info;
312     }
313 
314     @VisibleForTesting
getStorageResult()315     public SparseArray<StorageAsyncLoader.StorageResult> getStorageResult() {
316         return mAppsResult;
317     }
318 
319     @VisibleForTesting
setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info)320     public void setStorageResult(SparseArray<StorageAsyncLoader.StorageResult> info) {
321         mAppsResult = info;
322     }
323 
324     /**
325      * Activate loading UI and animation if it's necessary.
326      */
327     @VisibleForTesting
maybeSetLoading(boolean isQuotaSupported)328     public void maybeSetLoading(boolean isQuotaSupported) {
329         // If we have fast stats, we load until both have loaded.
330         // If we have slow stats, we load when we get the total volume sizes.
331         if ((isQuotaSupported && (mStorageInfo == null || mAppsResult == null))
332                 || (!isQuotaSupported && mStorageInfo == null)) {
333             setLoading(true /* loading */, false /* animate */);
334         }
335     }
336 
isQuotaSupported()337     private boolean isQuotaSupported() {
338         return mSelectedStorageEntry.isMounted()
339                 && getActivity().getSystemService(StorageStatsManager.class)
340                         .isQuotaSupported(mSelectedStorageEntry.getFsUuid());
341     }
342 
setNonCurrentUsersVisible(boolean visible)343     private void setNonCurrentUsersVisible(boolean visible) {
344         if (!mNonCurrentUsers.isEmpty()) {
345             mNonCurrentUsers.get(0).setPreferenceGroupVisible(visible);
346         }
347     }
348 
349     /**
350      * IconLoaderCallbacks exists because StorageCategoryFragment already implements
351      * LoaderCallbacks for a different type.
352      */
353     public final class IconLoaderCallbacks
354             implements LoaderManager.LoaderCallbacks<SparseArray<Drawable>> {
355         @Override
onCreateLoader(int id, Bundle args)356         public Loader<SparseArray<Drawable>> onCreateLoader(int id, Bundle args) {
357             return new UserIconLoader(
358                     getContext(),
359                     () -> UserIconLoader.loadUserIconsWithContext(getContext()));
360         }
361 
362         @Override
onLoadFinished( Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data)363         public void onLoadFinished(
364                 Loader<SparseArray<Drawable>> loader, SparseArray<Drawable> data) {
365             mNonCurrentUsers
366                     .stream()
367                     .filter(controller -> controller instanceof UserIconLoader.UserIconHandler)
368                     .forEach(
369                             controller ->
370                                     ((UserIconLoader.UserIconHandler) controller)
371                                             .handleUserIcons(data));
372         }
373 
374         @Override
onLoaderReset(Loader<SparseArray<Drawable>> loader)375         public void onLoaderReset(Loader<SparseArray<Drawable>> loader) {
376         }
377     }
378 
379     /**
380      * VolumeSizeCallbacks exists because StorageCategoryFragment already implements
381      * LoaderCallbacks for a different type.
382      */
383     public final class VolumeSizeCallbacks
384             implements LoaderManager.LoaderCallbacks<PrivateStorageInfo> {
385         @Override
onCreateLoader(int id, Bundle args)386         public Loader<PrivateStorageInfo> onCreateLoader(int id, Bundle args) {
387             final Context context = getContext();
388             final StorageManagerVolumeProvider smvp =
389                     new StorageManagerVolumeProvider(mStorageManager);
390             final StorageStatsManager stats = context.getSystemService(StorageStatsManager.class);
391             return new VolumeSizesLoader(context, smvp, stats,
392                     mSelectedStorageEntry.getVolumeInfo());
393         }
394 
395         @Override
onLoaderReset(Loader<PrivateStorageInfo> loader)396         public void onLoaderReset(Loader<PrivateStorageInfo> loader) {
397         }
398 
399         @Override
onLoadFinished( Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo)400         public void onLoadFinished(
401                 Loader<PrivateStorageInfo> loader, PrivateStorageInfo privateStorageInfo) {
402             if (privateStorageInfo == null) {
403                 getActivity().finish();
404                 return;
405             }
406 
407             mStorageInfo = privateStorageInfo;
408             onReceivedSizes();
409         }
410     }
411 }
412