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