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
17package com.android.tv.settings.device;
18
19import android.content.Context;
20import android.os.Bundle;
21import android.os.Handler;
22import android.os.storage.DiskInfo;
23import android.os.storage.StorageManager;
24import android.os.storage.VolumeInfo;
25import android.os.storage.VolumeRecord;
26import android.support.v17.preference.LeanbackPreferenceFragment;
27import android.support.v7.preference.Preference;
28import android.support.v7.preference.PreferenceCategory;
29import android.util.ArraySet;
30import android.util.Log;
31
32import com.android.tv.settings.R;
33import com.android.tv.settings.device.storage.MissingStorageFragment;
34import com.android.tv.settings.device.storage.NewStorageActivity;
35import com.android.tv.settings.device.storage.StorageFragment;
36import com.android.tv.settings.device.storage.StoragePreference;
37
38import java.io.File;
39import java.util.ArrayList;
40import java.util.Collections;
41import java.util.List;
42import java.util.Set;
43
44public 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
64    public static StorageResetFragment newInstance() {
65        return new StorageResetFragment();
66    }
67    @Override
68    public void onCreate(Bundle savedInstanceState) {
69        mStorageManager = getContext().getSystemService(StorageManager.class);
70        super.onCreate(savedInstanceState);
71    }
72
73    @Override
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
80    public void onStart() {
81        super.onStart();
82        mStorageManager.registerListener(mStorageEventListener);
83    }
84
85    @Override
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
94    public void onPause() {
95        super.onPause();
96        mHandler.removeCallbacks(mRefreshRunnable);
97    }
98
99    @Override
100    public void onStop() {
101        super.onStop();
102        mStorageManager.unregisterListener(mStorageEventListener);
103    }
104
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 {
225        public VolPreference(Context context, VolumeInfo volumeInfo) {
226            super(context);
227            setKey(makeKey(volumeInfo));
228        }
229
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
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
254        public static String makeKey(VolumeInfo volumeInfo) {
255            return "VolPref:" + volumeInfo.getId();
256        }
257    }
258
259    private static class MissingPreference extends Preference {
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
269        public static String makeKey(VolumeRecord volumeRecord) {
270            return "MissingPref:" + volumeRecord.getFsUuid();
271        }
272    }
273
274    private static class UnsupportedDiskPreference extends Preference {
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
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
289        public void onStorageStateChanged(String path, String oldState, String newState) {
290            refresh();
291        }
292
293        @Override
294        public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) {
295            refresh();
296        }
297
298        @Override
299        public void onVolumeRecordChanged(VolumeRecord rec) {
300            refresh();
301        }
302
303        @Override
304        public void onVolumeForgotten(String fsUuid) {
305            refresh();
306        }
307
308        @Override
309        public void onDiskScanned(DiskInfo disk, int volumeCount) {
310            refresh();
311        }
312
313        @Override
314        public void onDiskDestroyed(DiskInfo disk) {
315            refresh();
316        }
317
318    }
319
320}
321