Home | History | Annotate | Download | only in deviceinfo
      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