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.ActivityThread; 20 import android.app.DownloadManager; 21 import android.content.Context; 22 import android.content.Intent; 23 import android.content.pm.IPackageManager; 24 import android.content.res.Resources; 25 import android.graphics.drawable.ShapeDrawable; 26 import android.graphics.drawable.shapes.RectShape; 27 import android.os.Bundle; 28 import android.os.Environment; 29 import android.os.Handler; 30 import android.os.Message; 31 import android.os.RemoteException; 32 import android.os.storage.StorageManager; 33 import android.os.storage.StorageVolume; 34 import android.preference.Preference; 35 import android.preference.PreferenceCategory; 36 import android.text.format.Formatter; 37 38 import com.android.settings.R; 39 import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver; 40 41 import java.util.HashSet; 42 import java.util.Set; 43 44 public class StorageVolumePreferenceCategory extends PreferenceCategory implements 45 MeasurementReceiver { 46 47 static final int TOTAL_SIZE = 0; 48 static final int APPLICATIONS = 1; 49 static final int DCIM = 2; // Pictures and Videos 50 static final int MUSIC = 3; 51 static final int DOWNLOADS = 4; 52 static final int MISC = 5; 53 static final int AVAILABLE = 6; 54 55 private UsageBarPreference mUsageBarPreference; 56 private Preference[] mPreferences; 57 private Preference mMountTogglePreference; 58 private Preference mFormatPreference; 59 private Preference mStorageLow; 60 private int[] mColors; 61 62 private Resources mResources; 63 64 private StorageVolume mStorageVolume; 65 66 private StorageManager mStorageManager = null; 67 68 private StorageMeasurement mMeasurement; 69 70 private boolean mAllowFormat; 71 72 static class CategoryInfo { 73 final int mTitle; 74 final int mColor; 75 76 public CategoryInfo(int title, int color) { 77 mTitle = title; 78 mColor = color; 79 } 80 } 81 82 static final CategoryInfo[] sCategoryInfos = new CategoryInfo[] { 83 new CategoryInfo(R.string.memory_size, 0), 84 new CategoryInfo(R.string.memory_apps_usage, R.color.memory_apps_usage), 85 new CategoryInfo(R.string.memory_dcim_usage, R.color.memory_dcim), 86 new CategoryInfo(R.string.memory_music_usage, R.color.memory_music), 87 new CategoryInfo(R.string.memory_downloads_usage, R.color.memory_downloads), 88 new CategoryInfo(R.string.memory_media_misc_usage, R.color.memory_misc), 89 new CategoryInfo(R.string.memory_available, R.color.memory_avail), 90 }; 91 92 public static final Set<String> sPathsExcludedForMisc = new HashSet<String>(); 93 94 static class MediaCategory { 95 final String[] mDirPaths; 96 final int mCategory; 97 //final int mMediaType; 98 99 public MediaCategory(int category, String... directories) { 100 mCategory = category; 101 final int length = directories.length; 102 mDirPaths = new String[length]; 103 for (int i = 0; i < length; i++) { 104 final String name = directories[i]; 105 final String path = Environment.getExternalStoragePublicDirectory(name). 106 getAbsolutePath(); 107 mDirPaths[i] = path; 108 sPathsExcludedForMisc.add(path); 109 } 110 } 111 } 112 113 static final MediaCategory[] sMediaCategories = new MediaCategory[] { 114 new MediaCategory(DCIM, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES, 115 Environment.DIRECTORY_PICTURES), 116 new MediaCategory(MUSIC, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS, 117 Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_RINGTONES, 118 Environment.DIRECTORY_PODCASTS) 119 }; 120 121 static { 122 // Downloads 123 sPathsExcludedForMisc.add(Environment.getExternalStoragePublicDirectory( 124 Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); 125 // Apps 126 sPathsExcludedForMisc.add(Environment.getExternalStorageDirectory().getAbsolutePath() + 127 "/Android"); 128 } 129 130 // Updates the memory usage bar graph. 131 private static final int MSG_UI_UPDATE_APPROXIMATE = 1; 132 133 // Updates the memory usage bar graph. 134 private static final int MSG_UI_UPDATE_EXACT = 2; 135 136 private Handler mUpdateHandler = new Handler() { 137 @Override 138 public void handleMessage(Message msg) { 139 switch (msg.what) { 140 case MSG_UI_UPDATE_APPROXIMATE: { 141 Bundle bundle = msg.getData(); 142 final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE); 143 final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE); 144 updateApproximate(totalSize, availSize); 145 break; 146 } 147 case MSG_UI_UPDATE_EXACT: { 148 Bundle bundle = msg.getData(); 149 final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE); 150 final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE); 151 final long appsUsed = bundle.getLong(StorageMeasurement.APPS_USED); 152 final long downloadsSize = bundle.getLong(StorageMeasurement.DOWNLOADS_SIZE); 153 final long miscSize = bundle.getLong(StorageMeasurement.MISC_SIZE); 154 final long[] mediaSizes = bundle.getLongArray(StorageMeasurement.MEDIA_SIZES); 155 updateExact(totalSize, availSize, appsUsed, downloadsSize, miscSize, 156 mediaSizes); 157 break; 158 } 159 } 160 } 161 }; 162 163 public StorageVolumePreferenceCategory(Context context, Resources resources, 164 StorageVolume storageVolume, StorageManager storageManager, boolean isPrimary) { 165 super(context); 166 mResources = resources; 167 mStorageVolume = storageVolume; 168 mStorageManager = storageManager; 169 setTitle(storageVolume != null ? storageVolume.getDescription(context) 170 : resources.getText(R.string.internal_storage)); 171 mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary); 172 mMeasurement.setReceiver(this); 173 174 // Cannot format emulated storage 175 mAllowFormat = mStorageVolume != null && !mStorageVolume.isEmulated(); 176 // For now we are disabling reformatting secondary external storage 177 // until some interoperability problems with MTP are fixed 178 if (!isPrimary) mAllowFormat = false; 179 } 180 181 public void init() { 182 mUsageBarPreference = new UsageBarPreference(getContext()); 183 184 final int width = (int) mResources.getDimension(R.dimen.device_memory_usage_button_width); 185 final int height = (int) mResources.getDimension(R.dimen.device_memory_usage_button_height); 186 187 final int numberOfCategories = sCategoryInfos.length; 188 mPreferences = new Preference[numberOfCategories]; 189 mColors = new int[numberOfCategories]; 190 for (int i = 0; i < numberOfCategories; i++) { 191 final Preference preference = new Preference(getContext()); 192 mPreferences[i] = preference; 193 preference.setTitle(sCategoryInfos[i].mTitle); 194 preference.setSummary(R.string.memory_calculating_size); 195 if (i != TOTAL_SIZE) { 196 // TOTAL_SIZE has no associated color 197 mColors[i] = mResources.getColor(sCategoryInfos[i].mColor); 198 preference.setIcon(createRectShape(width, height, mColors[i])); 199 } 200 } 201 202 mMountTogglePreference = new Preference(getContext()); 203 mMountTogglePreference.setTitle(R.string.sd_eject); 204 mMountTogglePreference.setSummary(R.string.sd_eject_summary); 205 206 if (mAllowFormat) { 207 mFormatPreference = new Preference(getContext()); 208 mFormatPreference.setTitle(R.string.sd_format); 209 mFormatPreference.setSummary(R.string.sd_format_summary); 210 } 211 212 final IPackageManager pm = ActivityThread.getPackageManager(); 213 try { 214 if (pm.isStorageLow()) { 215 mStorageLow = new Preference(getContext()); 216 mStorageLow.setTitle(R.string.storage_low_title); 217 mStorageLow.setSummary(R.string.storage_low_summary); 218 } else { 219 mStorageLow = null; 220 } 221 } catch (RemoteException e) { 222 } 223 } 224 225 public StorageVolume getStorageVolume() { 226 return mStorageVolume; 227 } 228 229 /** 230 * Successive mounts can change the list of visible preferences. 231 * This makes sure all preferences are visible and displayed in the right order. 232 */ 233 private void resetPreferences() { 234 final int numberOfCategories = sCategoryInfos.length; 235 236 removePreference(mUsageBarPreference); 237 for (int i = 0; i < numberOfCategories; i++) { 238 removePreference(mPreferences[i]); 239 } 240 removePreference(mMountTogglePreference); 241 if (mFormatPreference != null) { 242 removePreference(mFormatPreference); 243 } 244 245 addPreference(mUsageBarPreference); 246 if (mStorageLow != null) { 247 addPreference(mStorageLow); 248 } 249 for (int i = 0; i < numberOfCategories; i++) { 250 addPreference(mPreferences[i]); 251 } 252 addPreference(mMountTogglePreference); 253 if (mFormatPreference != null) { 254 addPreference(mFormatPreference); 255 } 256 257 mMountTogglePreference.setEnabled(true); 258 } 259 260 private void updatePreferencesFromState() { 261 resetPreferences(); 262 263 String state = mStorageVolume != null 264 ? mStorageManager.getVolumeState(mStorageVolume.getPath()) 265 : Environment.MEDIA_MOUNTED; 266 267 String readOnly = ""; 268 if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { 269 state = Environment.MEDIA_MOUNTED; 270 readOnly = mResources.getString(R.string.read_only); 271 if (mFormatPreference != null) { 272 removePreference(mFormatPreference); 273 } 274 } 275 276 if ((mStorageVolume == null || !mStorageVolume.isRemovable()) 277 && !Environment.MEDIA_UNMOUNTED.equals(state)) { 278 // This device has built-in storage that is not removable. 279 // There is no reason for the user to unmount it. 280 removePreference(mMountTogglePreference); 281 } 282 283 if (Environment.MEDIA_MOUNTED.equals(state)) { 284 mPreferences[AVAILABLE].setSummary(mPreferences[AVAILABLE].getSummary() + readOnly); 285 286 mMountTogglePreference.setEnabled(true); 287 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject)); 288 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary)); 289 } else { 290 if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state) 291 || Environment.MEDIA_UNMOUNTABLE.equals(state)) { 292 mMountTogglePreference.setEnabled(true); 293 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); 294 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary)); 295 } else { 296 mMountTogglePreference.setEnabled(false); 297 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); 298 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary)); 299 } 300 301 removePreference(mUsageBarPreference); 302 removePreference(mPreferences[TOTAL_SIZE]); 303 removePreference(mPreferences[AVAILABLE]); 304 if (mFormatPreference != null) { 305 removePreference(mFormatPreference); 306 } 307 } 308 } 309 310 public void updateApproximate(long totalSize, long availSize) { 311 mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize)); 312 mPreferences[AVAILABLE].setSummary(formatSize(availSize)); 313 314 final long usedSize = totalSize - availSize; 315 316 mUsageBarPreference.clear(); 317 mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY); 318 mUsageBarPreference.commit(); 319 320 updatePreferencesFromState(); 321 } 322 323 public void updateExact(long totalSize, long availSize, long appsSize, long downloadsSize, 324 long miscSize, long[] mediaSizes) { 325 mUsageBarPreference.clear(); 326 327 mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize)); 328 329 if (mMeasurement.isExternalSDCard()) { 330 // TODO FIXME: external SD card will not report any size. Show used space in bar graph 331 final long usedSize = totalSize - availSize; 332 mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY); 333 } 334 335 updatePreference(appsSize, totalSize, APPLICATIONS); 336 337 long totalMediaSize = 0; 338 for (int i = 0; i < sMediaCategories.length; i++) { 339 final int category = sMediaCategories[i].mCategory; 340 final long size = mediaSizes[i]; 341 updatePreference(size, totalSize, category); 342 totalMediaSize += size; 343 } 344 345 updatePreference(downloadsSize, totalSize, DOWNLOADS); 346 347 // Note miscSize != totalSize - availSize - appsSize - downloadsSize - totalMediaSize 348 // Block size is taken into account. That can be extra space from folders. TODO Investigate 349 updatePreference(miscSize, totalSize, MISC); 350 351 updatePreference(availSize, totalSize, AVAILABLE); 352 353 mUsageBarPreference.commit(); 354 } 355 356 private void updatePreference(long size, long totalSize, int category) { 357 if (size > 0) { 358 mPreferences[category].setSummary(formatSize(size)); 359 mUsageBarPreference.addEntry(size / (float) totalSize, mColors[category]); 360 } else { 361 removePreference(mPreferences[category]); 362 } 363 } 364 365 private void measure() { 366 mMeasurement.invalidate(); 367 mMeasurement.measure(); 368 } 369 370 public void onResume() { 371 mMeasurement.setReceiver(this); 372 measure(); 373 } 374 375 public void onStorageStateChanged() { 376 measure(); 377 } 378 379 public void onMediaScannerFinished() { 380 measure(); 381 } 382 383 public void onPause() { 384 mMeasurement.cleanUp(); 385 } 386 387 private static ShapeDrawable createRectShape(int width, int height, int color) { 388 ShapeDrawable shape = new ShapeDrawable(new RectShape()); 389 shape.setIntrinsicHeight(height); 390 shape.setIntrinsicWidth(width); 391 shape.getPaint().setColor(color); 392 return shape; 393 } 394 395 private String formatSize(long size) { 396 return Formatter.formatFileSize(getContext(), size); 397 } 398 399 @Override 400 public void updateApproximate(Bundle bundle) { 401 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE); 402 message.setData(bundle); 403 mUpdateHandler.sendMessage(message); 404 } 405 406 @Override 407 public void updateExact(Bundle bundle) { 408 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXACT); 409 message.setData(bundle); 410 mUpdateHandler.sendMessage(message); 411 } 412 413 public boolean mountToggleClicked(Preference preference) { 414 return preference == mMountTogglePreference; 415 } 416 417 public Intent intentForClick(Preference preference) { 418 Intent intent = null; 419 420 // TODO The current "delete" story is not fully handled by the respective applications. 421 // When it is done, make sure the intent types below are correct. 422 // If that cannot be done, remove these intents. 423 if (preference == mFormatPreference) { 424 intent = new Intent(Intent.ACTION_VIEW); 425 intent.setClass(getContext(), com.android.settings.MediaFormat.class); 426 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume); 427 } else if (preference == mPreferences[APPLICATIONS]) { 428 intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); 429 intent.setClass(getContext(), 430 com.android.settings.Settings.ManageApplicationsActivity.class); 431 } else if (preference == mPreferences[DOWNLOADS]) { 432 intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( 433 DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); 434 } else if (preference == mPreferences[MUSIC]) { 435 intent = new Intent(Intent.ACTION_GET_CONTENT); 436 intent.setType("audio/mp3"); 437 } else if (preference == mPreferences[DCIM]) { 438 intent = new Intent(Intent.ACTION_VIEW); 439 intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); 440 // TODO Create a Videos category, type = vnd.android.cursor.dir/video 441 intent.setType("vnd.android.cursor.dir/image"); 442 } else if (preference == mPreferences[MISC]) { 443 Context context = getContext().getApplicationContext(); 444 if (mMeasurement.getMiscSize() > 0) { 445 intent = new Intent(context, MiscFilesHandler.class); 446 intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume); 447 } 448 } 449 450 return intent; 451 } 452 } 453