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