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