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