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