• 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.dashboard.profileselector;
18 
19 import android.app.Activity;
20 import android.app.settings.SettingsEnums;
21 import android.content.Context;
22 import android.os.Bundle;
23 import android.os.UserHandle;
24 import android.os.storage.DiskInfo;
25 import android.os.storage.StorageEventListener;
26 import android.os.storage.StorageManager;
27 import android.os.storage.VolumeInfo;
28 import android.os.storage.VolumeRecord;
29 import android.text.TextUtils;
30 
31 import androidx.annotation.VisibleForTesting;
32 import androidx.fragment.app.Fragment;
33 
34 import com.android.settings.R;
35 import com.android.settings.Utils;
36 import com.android.settings.deviceinfo.StorageCategoryFragment;
37 import com.android.settings.deviceinfo.VolumeOptionMenuController;
38 import com.android.settings.deviceinfo.storage.AutomaticStorageManagementSwitchPreferenceController;
39 import com.android.settings.deviceinfo.storage.DiskInitFragment;
40 import com.android.settings.deviceinfo.storage.StorageCacheHelper;
41 import com.android.settings.deviceinfo.storage.StorageEntry;
42 import com.android.settings.deviceinfo.storage.StorageSelectionPreferenceController;
43 import com.android.settings.deviceinfo.storage.StorageUsageProgressBarPreferenceController;
44 import com.android.settings.deviceinfo.storage.StorageUtils;
45 
46 import java.util.ArrayList;
47 import java.util.List;
48 
49 /**
50  * Storage Settings main UI is composed by 3 fragments:
51  *
52  * StorageDashboardFragment only shows when there is only personal profile for current user.
53  *
54  * ProfileSelectStorageFragment (controls preferences above profile tab) and
55  * StorageCategoryFragment (controls preferences below profile tab) only show when current
56  * user has installed work profile.
57  *
58  * ProfileSelectStorageFragment and StorageCategoryFragment have many similar or the same
59  * code as StorageDashboardFragment. Remember to sync code between these fragments when you have to
60  * change Storage Settings.
61  */
62 public class ProfileSelectStorageFragment extends ProfileSelectFragment {
63 
64     private static final String TAG = "ProfileSelStorageFrag";
65     private static final String SELECTED_STORAGE_ENTRY_KEY = "selected_storage_entry_key";
66 
67     private StorageManager mStorageManager;
68 
69     private final List<StorageEntry> mStorageEntries = new ArrayList<>();
70     private StorageEntry mSelectedStorageEntry;
71     private Fragment[] mFragments;
72 
73     private StorageSelectionPreferenceController mStorageSelectionController;
74     private StorageUsageProgressBarPreferenceController mStorageUsageProgressBarController;
75     private VolumeOptionMenuController mOptionMenuController;
76     private boolean mIsLoadedFromCache;
77     private StorageCacheHelper mStorageCacheHelper;
78 
79     private final StorageEventListener mStorageEventListener = new StorageEventListener() {
80         @Override
81         public void onVolumeStateChanged(VolumeInfo volumeInfo, int oldState, int newState) {
82             if (!StorageUtils.isStorageSettingsInterestedVolume(volumeInfo)) {
83                 return;
84             }
85 
86             final StorageEntry changedStorageEntry = new StorageEntry(getContext(), volumeInfo);
87             final int volumeState = volumeInfo.getState();
88             switch (volumeState) {
89                 case VolumeInfo.STATE_REMOVED:
90                 case VolumeInfo.STATE_BAD_REMOVAL:
91                     // Remove removed storage from list and don't show it on spinner.
92                     if (!mStorageEntries.remove(changedStorageEntry)) {
93                         break;
94                     }
95                 case VolumeInfo.STATE_MOUNTED:
96                 case VolumeInfo.STATE_MOUNTED_READ_ONLY:
97                 case VolumeInfo.STATE_UNMOUNTABLE:
98                 case VolumeInfo.STATE_UNMOUNTED:
99                 case VolumeInfo.STATE_EJECTING:
100                     // Add mounted or unmountable storage in the list and show it on spinner.
101                     // Unmountable storages are the storages which has a problem format and android
102                     // is not able to mount it automatically.
103                     // Users can format an unmountable storage by the UI and then use the storage.
104                     mStorageEntries.removeIf(storageEntry -> {
105                         return storageEntry.equals(changedStorageEntry);
106                     });
107                     if (volumeState != VolumeInfo.STATE_REMOVED
108                             && volumeState != VolumeInfo.STATE_BAD_REMOVAL) {
109                         mStorageEntries.add(changedStorageEntry);
110                     }
111                     if (changedStorageEntry.equals(mSelectedStorageEntry)) {
112                         mSelectedStorageEntry = changedStorageEntry;
113                     }
114                     refreshUi();
115                     break;
116                 default:
117                     // Do nothing.
118             }
119         }
120 
121         @Override
122         public void onVolumeRecordChanged(VolumeRecord volumeRecord) {
123             if (StorageUtils.isVolumeRecordMissed(mStorageManager, volumeRecord)) {
124                 // VolumeRecord is a metadata of VolumeInfo, if a VolumeInfo is missing
125                 // (e.g., internal SD card is removed.) show the missing storage to users,
126                 // users can insert the SD card or manually forget the storage from the device.
127                 final StorageEntry storageEntry = new StorageEntry(volumeRecord);
128                 if (!mStorageEntries.contains(storageEntry)) {
129                     mStorageEntries.add(storageEntry);
130                     refreshUi();
131                 }
132             } else {
133                 // Find mapped VolumeInfo and replace with existing one for something changed.
134                 // (e.g., Renamed.)
135                 final VolumeInfo mappedVolumeInfo =
136                         mStorageManager.findVolumeByUuid(volumeRecord.getFsUuid());
137                 if (mappedVolumeInfo == null) {
138                     return;
139                 }
140 
141                 final boolean removeMappedStorageEntry = mStorageEntries.removeIf(storageEntry ->
142                         storageEntry.isVolumeInfo()
143                             && TextUtils.equals(storageEntry.getFsUuid(), volumeRecord.getFsUuid())
144                 );
145                 if (removeMappedStorageEntry) {
146                     mStorageEntries.add(new StorageEntry(getContext(), mappedVolumeInfo));
147                     refreshUi();
148                 }
149             }
150         }
151 
152         @Override
153         public void onVolumeForgotten(String fsUuid) {
154             final StorageEntry storageEntry = new StorageEntry(
155                     new VolumeRecord(VolumeInfo.TYPE_PUBLIC, fsUuid));
156             if (mStorageEntries.remove(storageEntry)) {
157                 if (mSelectedStorageEntry.equals(storageEntry)) {
158                     mSelectedStorageEntry =
159                             StorageEntry.getDefaultInternalStorageEntry(getContext());
160                 }
161                 refreshUi();
162             }
163         }
164 
165         @Override
166         public void onDiskScanned(DiskInfo disk, int volumeCount) {
167             if (!StorageUtils.isDiskUnsupported(disk)) {
168                 return;
169             }
170             final StorageEntry storageEntry = new StorageEntry(disk);
171             if (!mStorageEntries.contains(storageEntry)) {
172                 mStorageEntries.add(storageEntry);
173                 refreshUi();
174             }
175         }
176 
177         @Override
178         public void onDiskDestroyed(DiskInfo disk) {
179             final StorageEntry storageEntry = new StorageEntry(disk);
180             if (mStorageEntries.remove(storageEntry)) {
181                 if (mSelectedStorageEntry.equals(storageEntry)) {
182                     mSelectedStorageEntry =
183                             StorageEntry.getDefaultInternalStorageEntry(getContext());
184                 }
185                 refreshUi();
186             }
187         }
188     };
189 
190     @Override
getFragments()191     public Fragment[] getFragments() {
192         if (mFragments != null) {
193             return mFragments;
194         }
195 
196         final Bundle workBundle = new Bundle();
197         workBundle.putInt(EXTRA_PROFILE, ProfileType.WORK);
198         final Fragment workFragment = new StorageCategoryFragment();
199         workFragment.setArguments(workBundle);
200 
201         final Bundle personalBundle = new Bundle();
202         personalBundle.putInt(EXTRA_PROFILE, ProfileType.PERSONAL);
203         final Fragment personalFragment = new StorageCategoryFragment();
204         personalFragment.setArguments(personalBundle);
205 
206         mFragments = new Fragment[] {
207             personalFragment,
208             workFragment
209         };
210         return mFragments;
211     }
212 
213     @Override
getPreferenceScreenResId()214     protected int getPreferenceScreenResId() {
215         return R.xml.storage_dashboard_header_fragment;
216     }
217 
refreshUi()218     private void refreshUi() {
219         mStorageSelectionController.setStorageEntries(mStorageEntries);
220         mStorageSelectionController.setSelectedStorageEntry(mSelectedStorageEntry);
221         mStorageUsageProgressBarController.setSelectedStorageEntry(mSelectedStorageEntry);
222 
223         for (Fragment fragment : getFragments()) {
224             if (!(fragment instanceof StorageCategoryFragment)) {
225                 throw new IllegalStateException("Wrong fragment type to refreshUi");
226             }
227             ((StorageCategoryFragment) fragment).refreshUi(mSelectedStorageEntry);
228         }
229 
230         mOptionMenuController.setSelectedStorageEntry(mSelectedStorageEntry);
231         getActivity().invalidateOptionsMenu();
232     }
233 
234     @Override
onCreate(Bundle icicle)235     public void onCreate(Bundle icicle) {
236         super.onCreate(icicle);
237 
238         final Activity activity = getActivity();
239         mStorageManager = activity.getSystemService(StorageManager.class);
240 
241         if (icicle == null) {
242             final VolumeInfo specifiedVolumeInfo =
243                     Utils.maybeInitializeVolume(mStorageManager, getArguments());
244             mSelectedStorageEntry = specifiedVolumeInfo == null
245                     ? StorageEntry.getDefaultInternalStorageEntry(getContext())
246                     : new StorageEntry(getContext(), specifiedVolumeInfo);
247         } else {
248             mSelectedStorageEntry = icicle.getParcelable(SELECTED_STORAGE_ENTRY_KEY);
249         }
250 
251         initializeOptionsMenu(activity);
252 
253         if (mStorageCacheHelper.hasCachedSizeInfo()) {
254             mIsLoadedFromCache = true;
255             mStorageEntries.clear();
256             mStorageEntries.addAll(
257                     StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
258             refreshUi();
259         }
260     }
261 
262     @Override
onAttach(Context context)263     public void onAttach(Context context) {
264         super.onAttach(context);
265         mStorageCacheHelper = new StorageCacheHelper(getContext(), UserHandle.myUserId());
266         use(AutomaticStorageManagementSwitchPreferenceController.class).setFragmentManager(
267                 getFragmentManager());
268         mStorageSelectionController = use(StorageSelectionPreferenceController.class);
269         mStorageSelectionController.setOnItemSelectedListener(storageEntry -> {
270             mSelectedStorageEntry = storageEntry;
271             refreshUi();
272 
273             if (storageEntry.isDiskInfoUnsupported() || storageEntry.isUnmountable()) {
274                 DiskInitFragment.show(this, R.string.storage_dialog_unmountable,
275                         storageEntry.getDiskId());
276             } else if (storageEntry.isVolumeRecordMissed()) {
277                 StorageUtils.launchForgetMissingVolumeRecordFragment(getContext(), storageEntry);
278             }
279         });
280         mStorageUsageProgressBarController = use(StorageUsageProgressBarPreferenceController.class);
281     }
282 
283     @VisibleForTesting
initializeOptionsMenu(Activity activity)284     void initializeOptionsMenu(Activity activity) {
285         mOptionMenuController = new VolumeOptionMenuController(activity, this,
286                 mSelectedStorageEntry);
287         getSettingsLifecycle().addObserver(mOptionMenuController);
288         setHasOptionsMenu(true);
289         activity.invalidateOptionsMenu();
290     }
291 
292     @Override
onResume()293     public void onResume() {
294         super.onResume();
295 
296         if (mIsLoadedFromCache) {
297             mIsLoadedFromCache = false;
298         } else {
299             mStorageEntries.clear();
300             mStorageEntries.addAll(
301                     StorageUtils.getAllStorageEntries(getContext(), mStorageManager));
302             refreshUi();
303         }
304         mStorageManager.registerListener(mStorageEventListener);
305     }
306 
307     @Override
onPause()308     public void onPause() {
309         super.onPause();
310         mStorageManager.unregisterListener(mStorageEventListener);
311     }
312 
313     @Override
onSaveInstanceState(Bundle outState)314     public void onSaveInstanceState(Bundle outState) {
315         outState.putParcelable(SELECTED_STORAGE_ENTRY_KEY, mSelectedStorageEntry);
316         super.onSaveInstanceState(outState);
317     }
318 
319     @Override
getHelpResource()320     public int getHelpResource() {
321         return R.string.help_url_storage_dashboard;
322     }
323 
324     @Override
getMetricsCategory()325     public int getMetricsCategory() {
326         return SettingsEnums.SETTINGS_STORAGE_PROFILE_SELECTOR;
327     }
328 
329     @Override
getLogTag()330     protected String getLogTag() {
331         return TAG;
332     }
333 }
334