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