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