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.storage; 18 19 import static com.android.tv.settings.util.InstrumentationUtils.logEntrySelected; 20 21 import android.app.ActivityManager; 22 import android.app.tvsettings.TvSettingsEnums; 23 import android.content.pm.PackageManager; 24 import android.os.Bundle; 25 import android.os.Environment; 26 import android.os.storage.StorageManager; 27 import android.os.storage.VolumeInfo; 28 import android.text.TextUtils; 29 import android.util.Log; 30 31 import androidx.preference.Preference; 32 import androidx.preference.PreferenceScreen; 33 34 import com.android.internal.logging.nano.MetricsProto; 35 import com.android.settingslib.deviceinfo.StorageMeasurement; 36 import com.android.tv.settings.R; 37 import com.android.tv.settings.SettingsPreferenceFragment; 38 import com.android.tv.settings.device.apps.AppsFragment; 39 40 import java.util.HashMap; 41 import java.util.List; 42 43 /** 44 * The screen in TV settings that lets users manage external storage devices and that shows the 45 * usage summary by data type such as apps, music, download and so on. 46 */ 47 public class StorageFragment extends SettingsPreferenceFragment { 48 private static final String TAG = "StorageFragment"; 49 50 private static final String KEY_MIGRATE = "migrate"; 51 private static final String KEY_EJECT = "eject"; 52 private static final String KEY_ERASE = "erase"; 53 private static final String KEY_APPS_USAGE = "apps_usage"; 54 private static final String KEY_DCIM_USAGE = "dcim_usage"; 55 private static final String KEY_MUSIC_USAGE = "music_usage"; 56 private static final String KEY_DOWNLOADS_USAGE = "downloads_usage"; 57 private static final String KEY_CACHE_USAGE = "cache_usage"; 58 private static final String KEY_MISC_USAGE = "misc_usage"; 59 private static final String KEY_AVAILABLE = "available"; 60 61 private StorageManager mStorageManager; 62 private PackageManager mPackageManager; 63 64 private VolumeInfo mVolumeInfo; 65 66 private StorageMeasurement mMeasure; 67 private final StorageMeasurement.MeasurementReceiver mMeasurementReceiver = 68 new MeasurementReceiver(); 69 private final StorageEventListener mStorageEventListener = new StorageEventListener(); 70 71 private Preference mMigratePref; 72 private Preference mEjectPref; 73 private Preference mErasePref; 74 private StoragePreference mAppsUsagePref; 75 private StoragePreference mDcimUsagePref; 76 private StoragePreference mMusicUsagePref; 77 private StoragePreference mDownloadsUsagePref; 78 private StoragePreference mCacheUsagePref; 79 private StoragePreference mMiscUsagePref; 80 private StoragePreference mAvailablePref; 81 prepareArgs(Bundle bundle, VolumeInfo volumeInfo)82 public static void prepareArgs(Bundle bundle, VolumeInfo volumeInfo) { 83 bundle.putString(VolumeInfo.EXTRA_VOLUME_ID, volumeInfo.getId()); 84 } 85 86 @Override onCreate(Bundle savedInstanceState)87 public void onCreate(Bundle savedInstanceState) { 88 mStorageManager = getContext().getSystemService(StorageManager.class); 89 mPackageManager = getContext().getPackageManager(); 90 91 mVolumeInfo = mStorageManager.findVolumeById( 92 getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID)); 93 94 super.onCreate(savedInstanceState); 95 } 96 97 @Override onStart()98 public void onStart() { 99 super.onStart(); 100 mStorageManager.registerListener(mStorageEventListener); 101 startMeasurement(); 102 } 103 104 @Override onResume()105 public void onResume() { 106 super.onResume(); 107 mVolumeInfo = mStorageManager.findVolumeById( 108 getArguments().getString(VolumeInfo.EXTRA_VOLUME_ID)); 109 if (mVolumeInfo == null || !mVolumeInfo.isMountedReadable()) { 110 getFragmentManager().popBackStack(); 111 } else { 112 refresh(); 113 } 114 } 115 116 @Override onStop()117 public void onStop() { 118 super.onStop(); 119 mStorageManager.unregisterListener(mStorageEventListener); 120 stopMeasurement(); 121 } 122 123 @Override onCreatePreferences(Bundle savedInstanceState, String rootKey)124 public void onCreatePreferences(Bundle savedInstanceState, String rootKey) { 125 setPreferencesFromResource(R.xml.storage, null); 126 127 final PreferenceScreen screen = getPreferenceScreen(); 128 screen.setTitle(mStorageManager.getBestVolumeDescription(mVolumeInfo)); 129 130 mMigratePref = findPreference(KEY_MIGRATE); 131 mEjectPref = findPreference(KEY_EJECT); 132 mErasePref = findPreference(KEY_ERASE); 133 134 mAppsUsagePref = (StoragePreference) findPreference(KEY_APPS_USAGE); 135 mDcimUsagePref = (StoragePreference) findPreference(KEY_DCIM_USAGE); 136 mMusicUsagePref = (StoragePreference) findPreference(KEY_MUSIC_USAGE); 137 mDownloadsUsagePref = (StoragePreference) findPreference(KEY_DOWNLOADS_USAGE); 138 mCacheUsagePref = (StoragePreference) findPreference(KEY_CACHE_USAGE); 139 mMiscUsagePref = (StoragePreference) findPreference(KEY_MISC_USAGE); 140 mAvailablePref = (StoragePreference) findPreference(KEY_AVAILABLE); 141 } 142 143 @Override onPreferenceTreeClick(Preference preference)144 public boolean onPreferenceTreeClick(Preference preference) { 145 switch (preference.getKey()) { 146 case KEY_APPS_USAGE: 147 logEntrySelected(TvSettingsEnums.SYSTEM_STORAGE_INTERNAL_STORAGE_APPS); 148 break; 149 case KEY_CACHE_USAGE: 150 logEntrySelected(TvSettingsEnums.SYSTEM_STORAGE_INTERNAL_STORAGE_CACHED); 151 break; 152 } 153 return super.onPreferenceTreeClick(preference); 154 } 155 refresh()156 private void refresh() { 157 boolean showMigrate = false; 158 final VolumeInfo currentExternal = mPackageManager.getPrimaryStorageCurrentVolume(); 159 // currentExternal will be null if the drive is not mounted. Don't offer the option to 160 // migrate if so. 161 if (currentExternal != null 162 && !TextUtils.equals(currentExternal.getId(), mVolumeInfo.getId())) { 163 final List<VolumeInfo> candidates = 164 mPackageManager.getPrimaryStorageCandidateVolumes(); 165 for (final VolumeInfo candidate : candidates) { 166 if (TextUtils.equals(candidate.getId(), mVolumeInfo.getId())) { 167 showMigrate = true; 168 break; 169 } 170 } 171 } 172 173 mMigratePref.setVisible(showMigrate); 174 mMigratePref.setIntent( 175 MigrateStorageActivity.getLaunchIntent(getContext(), mVolumeInfo.getId(), true)); 176 177 final String description = mStorageManager.getBestVolumeDescription(mVolumeInfo); 178 179 final boolean privateInternal = VolumeInfo.ID_PRIVATE_INTERNAL.equals(mVolumeInfo.getId()); 180 final boolean isPrivate = mVolumeInfo.getType() == VolumeInfo.TYPE_PRIVATE; 181 182 mEjectPref.setVisible(!privateInternal); 183 mEjectPref.setIntent(UnmountActivity.getIntent(getContext(), mVolumeInfo.getId(), 184 description)); 185 mErasePref.setVisible(!privateInternal); 186 if (isPrivate) { 187 mErasePref.setIntent( 188 FormatActivity.getFormatAsPublicIntent(getContext(), mVolumeInfo.getDiskId())); 189 mErasePref.setTitle(R.string.storage_format_as_public); 190 } else { 191 mErasePref.setIntent( 192 FormatActivity.getFormatAsPrivateIntent(getContext(), mVolumeInfo.getDiskId())); 193 mErasePref.setTitle(R.string.storage_format_as_private); 194 } 195 196 mAppsUsagePref.setVisible(isPrivate); 197 mAppsUsagePref.setFragment(AppsFragment.class.getName()); 198 AppsFragment.prepareArgs(mAppsUsagePref.getExtras(), mVolumeInfo.fsUuid, description); 199 mDcimUsagePref.setVisible(isPrivate); 200 mMusicUsagePref.setVisible(isPrivate); 201 mDownloadsUsagePref.setVisible(isPrivate); 202 mCacheUsagePref.setVisible(isPrivate); 203 mCacheUsagePref.setFragment(ConfirmClearCacheFragment.class.getName()); 204 } 205 startMeasurement()206 private void startMeasurement() { 207 if (mVolumeInfo != null && mVolumeInfo.isMountedReadable()) { 208 final VolumeInfo sharedVolume = mStorageManager.findEmulatedForPrivate(mVolumeInfo); 209 mMeasure = new StorageMeasurement(getContext(), mVolumeInfo, sharedVolume); 210 mMeasure.setReceiver(mMeasurementReceiver); 211 mMeasure.forceMeasure(); 212 } 213 } 214 stopMeasurement()215 private void stopMeasurement() { 216 if (mMeasure != null) { 217 mMeasure.onDestroy(); 218 } 219 } 220 updateDetails(StorageMeasurement.MeasurementDetails details)221 private void updateDetails(StorageMeasurement.MeasurementDetails details) { 222 final int currentUser = ActivityManager.getCurrentUser(); 223 final long dcimSize = totalValues(details.mediaSize.get(currentUser), 224 Environment.DIRECTORY_DCIM, 225 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES); 226 227 final long musicSize = totalValues(details.mediaSize.get(currentUser), 228 Environment.DIRECTORY_MUSIC, 229 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, 230 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); 231 232 final long downloadsSize = totalValues(details.mediaSize.get(currentUser), 233 Environment.DIRECTORY_DOWNLOADS); 234 235 mAvailablePref.setSize(details.availSize); 236 mAppsUsagePref.setSize(details.appsSize.get(currentUser)); 237 mDcimUsagePref.setSize(dcimSize); 238 mMusicUsagePref.setSize(musicSize); 239 mDownloadsUsagePref.setSize(downloadsSize); 240 mCacheUsagePref.setSize(details.cacheSize); 241 mMiscUsagePref.setSize(details.miscSize.get(currentUser)); 242 } 243 totalValues(HashMap<String, Long> map, String... keys)244 private static long totalValues(HashMap<String, Long> map, String... keys) { 245 long total = 0; 246 if (map != null) { 247 for (String key : keys) { 248 if (map.containsKey(key)) { 249 total += map.get(key); 250 } 251 } 252 } else { 253 Log.w(TAG, 254 "MeasurementDetails mediaSize array does not have key for current user " + 255 ActivityManager.getCurrentUser()); 256 } 257 return total; 258 } 259 260 private class MeasurementReceiver implements StorageMeasurement.MeasurementReceiver { 261 262 @Override onDetailsChanged(StorageMeasurement.MeasurementDetails details)263 public void onDetailsChanged(StorageMeasurement.MeasurementDetails details) { 264 updateDetails(details); 265 } 266 } 267 268 private class StorageEventListener extends android.os.storage.StorageEventListener { 269 @Override onVolumeStateChanged(VolumeInfo vol, int oldState, int newState)270 public void onVolumeStateChanged(VolumeInfo vol, int oldState, int newState) { 271 mVolumeInfo = vol; 272 if (isResumed()) { 273 if (mVolumeInfo.isMountedReadable()) { 274 refresh(); 275 } else { 276 getFragmentManager().popBackStack(); 277 } 278 } 279 } 280 } 281 282 @Override getMetricsCategory()283 public int getMetricsCategory() { 284 return MetricsProto.MetricsEvent.SETTINGS_STORAGE_CATEGORY; 285 } 286 287 @Override getPageId()288 protected int getPageId() { 289 return TvSettingsEnums.SYSTEM_STORAGE_INTERNAL_STORAGE; 290 } 291 } 292