1/* 2 * Copyright (C) 2011 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.settings.deviceinfo; 18 19import android.app.ActivityManagerNative; 20import android.app.ActivityThread; 21import android.app.DownloadManager; 22import android.content.Context; 23import android.content.Intent; 24import android.content.pm.IPackageManager; 25import android.content.pm.UserInfo; 26import android.content.res.Resources; 27import android.hardware.usb.UsbManager; 28import android.os.Environment; 29import android.os.Handler; 30import android.os.Message; 31import android.os.RemoteException; 32import android.os.UserManager; 33import android.os.storage.StorageManager; 34import android.os.storage.StorageVolume; 35import android.preference.Preference; 36import android.preference.PreferenceCategory; 37import android.provider.MediaStore; 38import android.text.format.Formatter; 39 40import com.android.settings.R; 41import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails; 42import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver; 43import com.google.android.collect.Lists; 44 45import java.util.HashMap; 46import java.util.Iterator; 47import java.util.List; 48 49public class StorageVolumePreferenceCategory extends PreferenceCategory { 50 public static final String KEY_CACHE = "cache"; 51 52 private static final int ORDER_USAGE_BAR = -2; 53 private static final int ORDER_STORAGE_LOW = -1; 54 55 /** Physical volume being measured, or {@code null} for internal. */ 56 private final StorageVolume mVolume; 57 private final StorageMeasurement mMeasure; 58 59 private final Resources mResources; 60 private final StorageManager mStorageManager; 61 private final UserManager mUserManager; 62 63 private UsageBarPreference mUsageBarPreference; 64 private Preference mMountTogglePreference; 65 private Preference mFormatPreference; 66 private Preference mStorageLow; 67 68 private StorageItemPreference mItemTotal; 69 private StorageItemPreference mItemAvailable; 70 private StorageItemPreference mItemApps; 71 private StorageItemPreference mItemDcim; 72 private StorageItemPreference mItemMusic; 73 private StorageItemPreference mItemDownloads; 74 private StorageItemPreference mItemCache; 75 private StorageItemPreference mItemMisc; 76 private List<StorageItemPreference> mItemUsers = Lists.newArrayList(); 77 78 private boolean mUsbConnected; 79 private String mUsbFunction; 80 81 private long mTotalSize; 82 83 private static final int MSG_UI_UPDATE_APPROXIMATE = 1; 84 private static final int MSG_UI_UPDATE_DETAILS = 2; 85 86 private Handler mUpdateHandler = new Handler() { 87 @Override 88 public void handleMessage(Message msg) { 89 switch (msg.what) { 90 case MSG_UI_UPDATE_APPROXIMATE: { 91 final long[] size = (long[]) msg.obj; 92 updateApproximate(size[0], size[1]); 93 break; 94 } 95 case MSG_UI_UPDATE_DETAILS: { 96 final MeasurementDetails details = (MeasurementDetails) msg.obj; 97 updateDetails(details); 98 break; 99 } 100 } 101 } 102 }; 103 104 /** 105 * Build category to summarize internal storage, including any emulated 106 * {@link StorageVolume}. 107 */ 108 public static StorageVolumePreferenceCategory buildForInternal(Context context) { 109 return new StorageVolumePreferenceCategory(context, null); 110 } 111 112 /** 113 * Build category to summarize specific physical {@link StorageVolume}. 114 */ 115 public static StorageVolumePreferenceCategory buildForPhysical( 116 Context context, StorageVolume volume) { 117 return new StorageVolumePreferenceCategory(context, volume); 118 } 119 120 private StorageVolumePreferenceCategory(Context context, StorageVolume volume) { 121 super(context); 122 123 mVolume = volume; 124 mMeasure = StorageMeasurement.getInstance(context, volume); 125 126 mResources = context.getResources(); 127 mStorageManager = StorageManager.from(context); 128 mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); 129 130 setTitle(volume != null ? volume.getDescription(context) 131 : context.getText(R.string.internal_storage)); 132 } 133 134 private StorageItemPreference buildItem(int titleRes, int colorRes) { 135 return new StorageItemPreference(getContext(), titleRes, colorRes); 136 } 137 138 public void init() { 139 final Context context = getContext(); 140 141 removeAll(); 142 143 final UserInfo currentUser; 144 try { 145 currentUser = ActivityManagerNative.getDefault().getCurrentUser(); 146 } catch (RemoteException e) { 147 throw new RuntimeException("Failed to get current user"); 148 } 149 150 final List<UserInfo> otherUsers = getUsersExcluding(currentUser); 151 final boolean showUsers = mVolume == null && otherUsers.size() > 0; 152 153 mUsageBarPreference = new UsageBarPreference(context); 154 mUsageBarPreference.setOrder(ORDER_USAGE_BAR); 155 addPreference(mUsageBarPreference); 156 157 mItemTotal = buildItem(R.string.memory_size, 0); 158 mItemAvailable = buildItem(R.string.memory_available, R.color.memory_avail); 159 addPreference(mItemTotal); 160 addPreference(mItemAvailable); 161 162 mItemApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage); 163 mItemDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim); 164 mItemMusic = buildItem(R.string.memory_music_usage, R.color.memory_music); 165 mItemDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads); 166 mItemCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache); 167 mItemMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc); 168 169 mItemCache.setKey(KEY_CACHE); 170 171 final boolean showDetails = mVolume == null || mVolume.isPrimary(); 172 if (showDetails) { 173 if (showUsers) { 174 addPreference(new PreferenceHeader(context, currentUser.name)); 175 } 176 177 addPreference(mItemApps); 178 addPreference(mItemDcim); 179 addPreference(mItemMusic); 180 addPreference(mItemDownloads); 181 addPreference(mItemCache); 182 addPreference(mItemMisc); 183 184 if (showUsers) { 185 addPreference(new PreferenceHeader(context, R.string.storage_other_users)); 186 187 int count = 0; 188 for (UserInfo info : otherUsers) { 189 final int colorRes = count++ % 2 == 0 ? R.color.memory_user_light 190 : R.color.memory_user_dark; 191 final StorageItemPreference userPref = new StorageItemPreference( 192 getContext(), info.name, colorRes, info.id); 193 mItemUsers.add(userPref); 194 addPreference(userPref); 195 } 196 } 197 } 198 199 final boolean isRemovable = mVolume != null ? mVolume.isRemovable() : false; 200 // Always create the preference since many code rely on it existing 201 mMountTogglePreference = new Preference(context); 202 if (isRemovable) { 203 mMountTogglePreference.setTitle(R.string.sd_eject); 204 mMountTogglePreference.setSummary(R.string.sd_eject_summary); 205 addPreference(mMountTogglePreference); 206 } 207 208 final boolean allowFormat = mVolume != null; 209 if (allowFormat) { 210 mFormatPreference = new Preference(context); 211 mFormatPreference.setTitle(R.string.sd_format); 212 mFormatPreference.setSummary(R.string.sd_format_summary); 213 addPreference(mFormatPreference); 214 } 215 216 final IPackageManager pm = ActivityThread.getPackageManager(); 217 try { 218 if (pm.isStorageLow()) { 219 mStorageLow = new Preference(context); 220 mStorageLow.setOrder(ORDER_STORAGE_LOW); 221 mStorageLow.setTitle(R.string.storage_low_title); 222 mStorageLow.setSummary(R.string.storage_low_summary); 223 addPreference(mStorageLow); 224 } else if (mStorageLow != null) { 225 removePreference(mStorageLow); 226 mStorageLow = null; 227 } 228 } catch (RemoteException e) { 229 } 230 } 231 232 public StorageVolume getStorageVolume() { 233 return mVolume; 234 } 235 236 private void updatePreferencesFromState() { 237 // Only update for physical volumes 238 if (mVolume == null) return; 239 240 mMountTogglePreference.setEnabled(true); 241 242 final String state = mStorageManager.getVolumeState(mVolume.getPath()); 243 244 if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 245 mItemAvailable.setTitle(R.string.memory_available_read_only); 246 } else { 247 mItemAvailable.setTitle(R.string.memory_available); 248 } 249 250 if (Environment.MEDIA_MOUNTED.equals(state) 251 || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 252 mMountTogglePreference.setEnabled(true); 253 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject)); 254 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary)); 255 } else { 256 if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state) 257 || Environment.MEDIA_UNMOUNTABLE.equals(state)) { 258 mMountTogglePreference.setEnabled(true); 259 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); 260 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary)); 261 } else { 262 mMountTogglePreference.setEnabled(false); 263 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); 264 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary)); 265 } 266 267 removePreference(mUsageBarPreference); 268 removePreference(mItemTotal); 269 removePreference(mItemAvailable); 270 } 271 272 if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) || 273 UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) { 274 mMountTogglePreference.setEnabled(false); 275 if (Environment.MEDIA_MOUNTED.equals(state) 276 || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 277 mMountTogglePreference.setSummary( 278 mResources.getString(R.string.mtp_ptp_mode_summary)); 279 } 280 281 if (mFormatPreference != null) { 282 mFormatPreference.setEnabled(false); 283 mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary)); 284 } 285 } else if (mFormatPreference != null) { 286 mFormatPreference.setEnabled(true); 287 mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary)); 288 } 289 } 290 291 public void updateApproximate(long totalSize, long availSize) { 292 mItemTotal.setSummary(formatSize(totalSize)); 293 mItemAvailable.setSummary(formatSize(availSize)); 294 295 mTotalSize = totalSize; 296 297 final long usedSize = totalSize - availSize; 298 299 mUsageBarPreference.clear(); 300 mUsageBarPreference.addEntry(0, usedSize / (float) totalSize, android.graphics.Color.GRAY); 301 mUsageBarPreference.commit(); 302 303 updatePreferencesFromState(); 304 } 305 306 private static long totalValues(HashMap<String, Long> map, String... keys) { 307 long total = 0; 308 for (String key : keys) { 309 if (map.containsKey(key)) { 310 total += map.get(key); 311 } 312 } 313 return total; 314 } 315 316 public void updateDetails(MeasurementDetails details) { 317 final boolean showDetails = mVolume == null || mVolume.isPrimary(); 318 if (!showDetails) return; 319 320 // Count caches as available space, since system manages them 321 mItemTotal.setSummary(formatSize(details.totalSize)); 322 mItemAvailable.setSummary(formatSize(details.availSize)); 323 324 mUsageBarPreference.clear(); 325 326 updatePreference(mItemApps, details.appsSize); 327 328 final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM, 329 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES); 330 updatePreference(mItemDcim, dcimSize); 331 332 final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC, 333 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, 334 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); 335 updatePreference(mItemMusic, musicSize); 336 337 final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS); 338 updatePreference(mItemDownloads, downloadsSize); 339 340 updatePreference(mItemCache, details.cacheSize); 341 updatePreference(mItemMisc, details.miscSize); 342 343 for (StorageItemPreference userPref : mItemUsers) { 344 final long userSize = details.usersSize.get(userPref.userHandle); 345 updatePreference(userPref, userSize); 346 } 347 348 mUsageBarPreference.commit(); 349 } 350 351 private void updatePreference(StorageItemPreference pref, long size) { 352 if (size > 0) { 353 pref.setSummary(formatSize(size)); 354 final int order = pref.getOrder(); 355 mUsageBarPreference.addEntry(order, size / (float) mTotalSize, pref.color); 356 } else { 357 removePreference(pref); 358 } 359 } 360 361 private void measure() { 362 mMeasure.invalidate(); 363 mMeasure.measure(); 364 } 365 366 public void onResume() { 367 mMeasure.setReceiver(mReceiver); 368 measure(); 369 } 370 371 public void onStorageStateChanged() { 372 init(); 373 measure(); 374 } 375 376 public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) { 377 mUsbConnected = isUsbConnected; 378 mUsbFunction = usbFunction; 379 measure(); 380 } 381 382 public void onMediaScannerFinished() { 383 measure(); 384 } 385 386 public void onCacheCleared() { 387 measure(); 388 } 389 390 public void onPause() { 391 mMeasure.cleanUp(); 392 } 393 394 private String formatSize(long size) { 395 return Formatter.formatFileSize(getContext(), size); 396 } 397 398 private MeasurementReceiver mReceiver = new MeasurementReceiver() { 399 @Override 400 public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) { 401 mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] { 402 totalSize, availSize }).sendToTarget(); 403 } 404 405 @Override 406 public void updateDetails(StorageMeasurement meas, MeasurementDetails details) { 407 mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget(); 408 } 409 }; 410 411 public boolean mountToggleClicked(Preference preference) { 412 return preference == mMountTogglePreference; 413 } 414 415 public Intent intentForClick(Preference pref) { 416 Intent intent = null; 417 418 // TODO The current "delete" story is not fully handled by the respective applications. 419 // When it is done, make sure the intent types below are correct. 420 // If that cannot be done, remove these intents. 421 final String key = pref.getKey(); 422 if (pref == mFormatPreference) { 423 intent = new Intent(Intent.ACTION_VIEW); 424 intent.setClass(getContext(), com.android.settings.MediaFormat.class); 425 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); 426 } else if (pref == mItemApps) { 427 intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); 428 intent.setClass(getContext(), 429 com.android.settings.Settings.ManageApplicationsActivity.class); 430 } else if (pref == mItemDownloads) { 431 intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( 432 DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); 433 } else if (pref == mItemMusic) { 434 intent = new Intent(Intent.ACTION_GET_CONTENT); 435 intent.setType("audio/mp3"); 436 } else if (pref == mItemDcim) { 437 intent = new Intent(Intent.ACTION_VIEW); 438 intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); 439 // TODO Create a Videos category, MediaStore.Video.Media.EXTERNAL_CONTENT_URI 440 intent.setData(MediaStore.Images.Media.EXTERNAL_CONTENT_URI); 441 } else if (pref == mItemMisc) { 442 Context context = getContext().getApplicationContext(); 443 intent = new Intent(context, MiscFilesHandler.class); 444 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); 445 } 446 447 return intent; 448 } 449 450 public static class PreferenceHeader extends Preference { 451 public PreferenceHeader(Context context, int titleRes) { 452 super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); 453 setTitle(titleRes); 454 } 455 456 public PreferenceHeader(Context context, CharSequence title) { 457 super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); 458 setTitle(title); 459 } 460 461 @Override 462 public boolean isEnabled() { 463 return false; 464 } 465 } 466 467 /** 468 * Return list of other users, excluding the current user. 469 */ 470 private List<UserInfo> getUsersExcluding(UserInfo excluding) { 471 final List<UserInfo> users = mUserManager.getUsers(); 472 final Iterator<UserInfo> i = users.iterator(); 473 while (i.hasNext()) { 474 if (i.next().id == excluding.id) { 475 i.remove(); 476 } 477 } 478 return users; 479 } 480} 481