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