• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2018 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.tv.settings.device;
18 
19 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_CLASSIC;
20 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_TWO_PANEL;
21 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_VENDOR;
22 import static com.android.tv.settings.overlay.FlavorUtils.FLAVOR_X;
23 
24 import android.app.tvsettings.TvSettingsEnums;
25 import android.content.Context;
26 import android.os.Bundle;
27 import android.os.Handler;
28 import android.os.storage.DiskInfo;
29 import android.os.storage.StorageManager;
30 import android.os.storage.VolumeInfo;
31 import android.os.storage.VolumeRecord;
32 import android.util.ArraySet;
33 import android.util.Log;
34 
35 import androidx.annotation.Keep;
36 import androidx.preference.Preference;
37 import androidx.preference.PreferenceCategory;
38 
39 import com.android.tv.settings.R;
40 import com.android.tv.settings.SettingsPreferenceFragment;
41 import com.android.tv.settings.device.storage.MissingStorageFragment;
42 import com.android.tv.settings.device.storage.NewStorageActivity;
43 import com.android.tv.settings.device.storage.StorageFragment;
44 import com.android.tv.settings.device.storage.StoragePreference;
45 import com.android.tv.settings.overlay.FlavorUtils;
46 
47 import java.io.File;
48 import java.util.ArrayList;
49 import java.util.List;
50 import java.util.Set;
51 
52 /**
53  * The "Storage" screen in TV settings.
54  */
55 @Keep
56 public class StorageSummaryFragment extends SettingsPreferenceFragment {
57     private static final String TAG = "StorageSummaryFragment";
58 
59     private static final String KEY_DEVICE_CATEGORY = "device_storage";
60     private static final String KEY_REMOVABLE_CATEGORY = "removable_storage";
61 
62     private static final int REFRESH_DELAY_MILLIS = 500;
63 
64     private StorageManager mStorageManager;
65     private final StorageSummaryFragment.StorageEventListener
66             mStorageEventListener = new StorageSummaryFragment.StorageEventListener();
67 
68     private final Handler mHandler = new Handler();
69     private final Runnable mRefreshRunnable = new Runnable() {
70         @Override
71         public void run() {
72             refresh();
73         }
74     };
75 
newInstance()76     public static StorageSummaryFragment newInstance() {
77         return new StorageSummaryFragment();
78     }
79 
80     @Override
onCreate(Bundle savedInstanceState)81     public void onCreate(Bundle savedInstanceState) {
82         mStorageManager = getContext().getSystemService(StorageManager.class);
83         super.onCreate(savedInstanceState);
84     }
85 
getPreferenceScreenResId()86     private int getPreferenceScreenResId() {
87         switch (FlavorUtils.getFlavor(getContext())) {
88             case FLAVOR_CLASSIC:
89             case FLAVOR_TWO_PANEL:
90                 return R.xml.storage_summary;
91             case FLAVOR_X:
92             case FLAVOR_VENDOR:
93                 return R.xml.storage_summary_x;
94             default:
95                 return R.xml.storage_summary;
96         }
97     }
98 
99     @Override
onCreatePreferences(Bundle savedInstanceState, String rootKey)100     public void onCreatePreferences(Bundle savedInstanceState, String rootKey) {
101         setPreferencesFromResource(getPreferenceScreenResId(), null);
102         findPreference(KEY_REMOVABLE_CATEGORY).setVisible(false);
103     }
104 
105     @Override
onStart()106     public void onStart() {
107         super.onStart();
108         mStorageManager.registerListener(mStorageEventListener);
109     }
110 
111     @Override
onResume()112     public void onResume() {
113         super.onResume();
114         mHandler.removeCallbacks(mRefreshRunnable);
115         // Delay to allow entrance animations to complete
116         mHandler.postDelayed(mRefreshRunnable, REFRESH_DELAY_MILLIS);
117     }
118 
119     @Override
onPause()120     public void onPause() {
121         super.onPause();
122         mHandler.removeCallbacks(mRefreshRunnable);
123     }
124 
125     @Override
onStop()126     public void onStop() {
127         super.onStop();
128         mStorageManager.unregisterListener(mStorageEventListener);
129     }
130 
refresh()131     private void refresh() {
132         if (!isResumed()) {
133             return;
134         }
135         final Context themedContext = getPreferenceManager().getContext();
136 
137         final List<VolumeInfo> volumes = mStorageManager.getVolumes();
138         volumes.sort(VolumeInfo.getDescriptionComparator());
139 
140         final List<VolumeInfo> privateVolumes = new ArrayList<>(volumes.size());
141         final List<VolumeInfo> publicVolumes = new ArrayList<>(volumes.size());
142 
143         // Find mounted volumes
144         for (final VolumeInfo vol : volumes) {
145             if (vol.getType() == VolumeInfo.TYPE_PRIVATE) {
146                 privateVolumes.add(vol);
147             } else if (vol.getType() == VolumeInfo.TYPE_PUBLIC) {
148                 publicVolumes.add(vol);
149             } else {
150                 Log.d(TAG, "Skipping volume " + vol.toString());
151             }
152         }
153 
154         // Find missing private filesystems
155         final List<VolumeRecord> volumeRecords = mStorageManager.getVolumeRecords();
156         final List<VolumeRecord> privateMissingVolumes = new ArrayList<>(volumeRecords.size());
157 
158         for (final VolumeRecord record : volumeRecords) {
159             if (record.getType() == VolumeInfo.TYPE_PRIVATE
160                     && mStorageManager.findVolumeByUuid(record.getFsUuid()) == null) {
161                 privateMissingVolumes.add(record);
162             }
163         }
164 
165         // Find unreadable disks
166         final List<DiskInfo> disks = mStorageManager.getDisks();
167         final List<DiskInfo> unsupportedDisks = new ArrayList<>(disks.size());
168         for (final DiskInfo disk : disks) {
169             if (disk.volumeCount == 0 && disk.size > 0) {
170                 unsupportedDisks.add(disk);
171             }
172         }
173 
174         // Add the prefs
175         final PreferenceCategory deviceCategory =
176                 (PreferenceCategory) findPreference(KEY_DEVICE_CATEGORY);
177         final Set<String> touchedDeviceKeys =
178                 new ArraySet<>(privateVolumes.size() + privateMissingVolumes.size());
179 
180         for (final VolumeInfo volumeInfo : privateVolumes) {
181             final String key = StorageSummaryFragment.VolPreference.makeKey(volumeInfo);
182             touchedDeviceKeys.add(key);
183             StorageSummaryFragment.VolPreference volPreference =
184                         (StorageSummaryFragment.VolPreference) deviceCategory.findPreference(key);
185             if (volPreference == null) {
186                 volPreference = new StorageSummaryFragment.VolPreference(themedContext, volumeInfo);
187             }
188             volPreference.refresh(themedContext, mStorageManager, volumeInfo);
189             deviceCategory.addPreference(volPreference);
190         }
191 
192         for (final VolumeRecord volumeRecord : privateMissingVolumes) {
193             final String key = StorageSummaryFragment.MissingPreference.makeKey(volumeRecord);
194             touchedDeviceKeys.add(key);
195             StorageSummaryFragment.MissingPreference missingPreference =
196                     (StorageSummaryFragment.MissingPreference) deviceCategory.findPreference(key);
197             if (missingPreference == null) {
198                 missingPreference = new StorageSummaryFragment.MissingPreference(
199                             themedContext, volumeRecord);
200             }
201             deviceCategory.addPreference(missingPreference);
202         }
203 
204         for (int i = 0; i < deviceCategory.getPreferenceCount();) {
205             final Preference pref = deviceCategory.getPreference(i);
206             if (touchedDeviceKeys.contains(pref.getKey())) {
207                 i++;
208             } else {
209                 deviceCategory.removePreference(pref);
210             }
211         }
212 
213         final PreferenceCategory removableCategory =
214                 (PreferenceCategory) findPreference(KEY_REMOVABLE_CATEGORY);
215         final int publicCount = publicVolumes.size() + unsupportedDisks.size();
216         final Set<String> touchedRemovableKeys = new ArraySet<>(publicCount);
217         // Only show section if there are public/unknown volumes present
218         removableCategory.setVisible(publicCount > 0);
219 
220         for (final VolumeInfo volumeInfo : publicVolumes) {
221             final String key = StorageSummaryFragment.VolPreference.makeKey(volumeInfo);
222             touchedRemovableKeys.add(key);
223             StorageSummaryFragment.VolPreference volPreference =
224                     (StorageSummaryFragment.VolPreference) removableCategory.findPreference(key);
225             if (volPreference == null) {
226                 volPreference = new StorageSummaryFragment.VolPreference(themedContext, volumeInfo);
227             }
228             volPreference.refresh(themedContext, mStorageManager, volumeInfo);
229             removableCategory.addPreference(volPreference);
230         }
231         for (final DiskInfo diskInfo : unsupportedDisks) {
232             final String key = StorageSummaryFragment.UnsupportedDiskPreference.makeKey(diskInfo);
233             touchedRemovableKeys.add(key);
234             StorageSummaryFragment.UnsupportedDiskPreference unsupportedDiskPreference =
235                     (StorageSummaryFragment.UnsupportedDiskPreference) findPreference(key);
236             if (unsupportedDiskPreference == null) {
237                 unsupportedDiskPreference = new StorageSummaryFragment.UnsupportedDiskPreference(
238                             themedContext, diskInfo);
239             }
240             removableCategory.addPreference(unsupportedDiskPreference);
241         }
242 
243         for (int i = 0; i < removableCategory.getPreferenceCount();) {
244             final Preference pref = removableCategory.getPreference(i);
245             if (touchedRemovableKeys.contains(pref.getKey())) {
246                 i++;
247             } else {
248                 removableCategory.removePreference(pref);
249             }
250         }
251     }
252 
253     private static class VolPreference extends Preference {
VolPreference(Context context, VolumeInfo volumeInfo)254         VolPreference(Context context, VolumeInfo volumeInfo) {
255             super(context);
256             setKey(makeKey(volumeInfo));
257         }
258 
refresh(Context context, StorageManager storageManager, VolumeInfo volumeInfo)259         private void refresh(Context context, StorageManager storageManager,
260                 VolumeInfo volumeInfo) {
261             final String description = storageManager
262                     .getBestVolumeDescription(volumeInfo);
263             setTitle(description);
264             if (volumeInfo.isMountedReadable()) {
265                 setSummary(getSizeString(volumeInfo));
266                 setFragment(StorageFragment.class.getName());
267                 StorageFragment.prepareArgs(getExtras(), volumeInfo);
268             } else {
269                 setSummary(context.getString(R.string.storage_unmount_success, description));
270             }
271         }
272 
getSizeString(VolumeInfo vol)273         private String getSizeString(VolumeInfo vol) {
274             final File path = vol.getPath();
275             if (vol.isMountedReadable() && path != null) {
276                 return String.format(getContext().getString(R.string.storage_size),
277                         StoragePreference.formatSize(getContext(), path.getTotalSpace()));
278             } else {
279                 return null;
280             }
281         }
282 
makeKey(VolumeInfo volumeInfo)283         public static String makeKey(VolumeInfo volumeInfo) {
284             return "VolPref:" + volumeInfo.getId();
285         }
286     }
287 
288     private static class MissingPreference extends Preference {
MissingPreference(Context context, VolumeRecord volumeRecord)289         MissingPreference(Context context, VolumeRecord volumeRecord) {
290             super(context);
291             setKey(makeKey(volumeRecord));
292             setTitle(volumeRecord.getNickname());
293             setSummary(R.string.storage_not_connected);
294             setFragment(MissingStorageFragment.class.getName());
295             MissingStorageFragment.prepareArgs(getExtras(), volumeRecord.getFsUuid());
296         }
297 
makeKey(VolumeRecord volumeRecord)298         public static String makeKey(VolumeRecord volumeRecord) {
299             return "MissingPref:" + volumeRecord.getFsUuid();
300         }
301     }
302 
303     private static class UnsupportedDiskPreference extends Preference {
UnsupportedDiskPreference(Context context, DiskInfo info)304         UnsupportedDiskPreference(Context context, DiskInfo info) {
305             super(context);
306             setKey(makeKey(info));
307             setTitle(info.getDescription());
308             setIntent(NewStorageActivity.getNewStorageLaunchIntent(context, null, info.getId()));
309         }
310 
makeKey(DiskInfo info)311         public static String makeKey(DiskInfo info) {
312             return "UnsupportedPref:" + info.getId();
313         }
314     }
315 
316     private class StorageEventListener extends android.os.storage.StorageEventListener {
317         @Override
onStorageStateChanged(String path, String oldState, String newState)318         public void onStorageStateChanged(String path, String oldState, String newState) {
319             refresh();
320         }
321 
322         @Override
onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)323         public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
324             refresh();
325         }
326 
327         @Override
onVolumeRecordChanged(VolumeRecord rec)328         public void onVolumeRecordChanged(VolumeRecord rec) {
329             refresh();
330         }
331 
332         @Override
onVolumeForgotten(String fsUuid)333         public void onVolumeForgotten(String fsUuid) {
334             refresh();
335         }
336 
337         @Override
onDiskScanned(DiskInfo disk, int volumeCount)338         public void onDiskScanned(DiskInfo disk, int volumeCount) {
339             refresh();
340         }
341 
342         @Override
onDiskDestroyed(DiskInfo disk)343         public void onDiskDestroyed(DiskInfo disk) {
344             refresh();
345         }
346 
347     }
348 
349     @Override
getPageId()350     protected int getPageId() {
351         return TvSettingsEnums.SYSTEM_STORAGE;
352     }
353 }
354