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.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      */
    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      */
    114     public static StorageVolumePreferenceCategory buildForPhysical(
    115             Context context, StorageVolume volume) {
    116         return new StorageVolumePreferenceCategory(context, volume);
    117     }
    118 
    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 
    133     private StorageItemPreference buildItem(int titleRes, int colorRes) {
    134         return new StorageItemPreference(getContext(), titleRes, colorRes);
    135     }
    136 
    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         if (isRemovable) {
    198             mMountTogglePreference = new Preference(context);
    199             mMountTogglePreference.setTitle(R.string.sd_eject);
    200             mMountTogglePreference.setSummary(R.string.sd_eject_summary);
    201             addPreference(mMountTogglePreference);
    202         }
    203 
    204         // Only allow formatting of primary physical storage
    205         // TODO: enable for non-primary volumes once MTP is fixed
    206         final boolean allowFormat = mVolume != null ? mVolume.isPrimary() : false;
    207         if (allowFormat) {
    208             mFormatPreference = new Preference(context);
    209             mFormatPreference.setTitle(R.string.sd_format);
    210             mFormatPreference.setSummary(R.string.sd_format_summary);
    211             addPreference(mFormatPreference);
    212         }
    213 
    214         final IPackageManager pm = ActivityThread.getPackageManager();
    215         try {
    216             if (pm.isStorageLow()) {
    217                 mStorageLow = new Preference(context);
    218                 mStorageLow.setOrder(ORDER_STORAGE_LOW);
    219                 mStorageLow.setTitle(R.string.storage_low_title);
    220                 mStorageLow.setSummary(R.string.storage_low_summary);
    221                 addPreference(mStorageLow);
    222             } else if (mStorageLow != null) {
    223                 removePreference(mStorageLow);
    224                 mStorageLow = null;
    225             }
    226         } catch (RemoteException e) {
    227         }
    228     }
    229 
    230     public StorageVolume getStorageVolume() {
    231         return mVolume;
    232     }
    233 
    234     private void updatePreferencesFromState() {
    235         // Only update for physical volumes
    236         if (mVolume == null) return;
    237 
    238         mMountTogglePreference.setEnabled(true);
    239 
    240         final String state = mStorageManager.getVolumeState(mVolume.getPath());
    241 
    242         if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) {
    243             mItemAvailable.setSummary(R.string.memory_available_read_only);
    244             if (mFormatPreference != null) {
    245                 removePreference(mFormatPreference);
    246             }
    247         } else {
    248             mItemAvailable.setSummary(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         } else {
    257             if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state)
    258                     || Environment.MEDIA_UNMOUNTABLE.equals(state)) {
    259                 mMountTogglePreference.setEnabled(true);
    260                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
    261                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary));
    262             } else {
    263                 mMountTogglePreference.setEnabled(false);
    264                 mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount));
    265                 mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary));
    266             }
    267 
    268             removePreference(mUsageBarPreference);
    269             removePreference(mItemTotal);
    270             removePreference(mItemAvailable);
    271             if (mFormatPreference != null) {
    272                 removePreference(mFormatPreference);
    273             }
    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(true);
    291             mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary));
    292         }
    293     }
    294 
    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 
    310     private static long totalValues(HashMap<String, Long> map, String... keys) {
    311         long total = 0;
    312         for (String key : keys) {
    313             total += map.get(key);
    314         }
    315         return total;
    316     }
    317 
    318     public void updateDetails(MeasurementDetails details) {
    319         final boolean showDetails = mVolume == null || mVolume.isPrimary();
    320         if (!showDetails) return;
    321 
    322         // Count caches as available space, since system manages them
    323         mItemTotal.setSummary(formatSize(details.totalSize));
    324         mItemAvailable.setSummary(formatSize(details.availSize));
    325 
    326         mUsageBarPreference.clear();
    327 
    328         updatePreference(mItemApps, details.appsSize);
    329 
    330         final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM,
    331                 Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES);
    332         updatePreference(mItemDcim, dcimSize);
    333 
    334         final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC,
    335                 Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
    336                 Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS);
    337         updatePreference(mItemMusic, musicSize);
    338 
    339         final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS);
    340         updatePreference(mItemDownloads, downloadsSize);
    341 
    342         updatePreference(mItemCache, details.cacheSize);
    343         updatePreference(mItemMisc, details.miscSize);
    344 
    345         for (StorageItemPreference userPref : mItemUsers) {
    346             final long userSize = details.usersSize.get(userPref.userHandle);
    347             updatePreference(userPref, userSize);
    348         }
    349 
    350         mUsageBarPreference.commit();
    351     }
    352 
    353     private void updatePreference(StorageItemPreference pref, long size) {
    354         if (size > 0) {
    355             pref.setSummary(formatSize(size));
    356             final int order = pref.getOrder();
    357             mUsageBarPreference.addEntry(order, size / (float) mTotalSize, pref.color);
    358         } else {
    359             removePreference(pref);
    360         }
    361     }
    362 
    363     private void measure() {
    364         mMeasure.invalidate();
    365         mMeasure.measure();
    366     }
    367 
    368     public void onResume() {
    369         mMeasure.setReceiver(mReceiver);
    370         measure();
    371     }
    372 
    373     public void onStorageStateChanged() {
    374         measure();
    375     }
    376 
    377     public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) {
    378         mUsbConnected = isUsbConnected;
    379         mUsbFunction = usbFunction;
    380         measure();
    381     }
    382 
    383     public void onMediaScannerFinished() {
    384         measure();
    385     }
    386 
    387     public void onCacheCleared() {
    388         measure();
    389     }
    390 
    391     public void onPause() {
    392         mMeasure.cleanUp();
    393     }
    394 
    395     private String formatSize(long size) {
    396         return Formatter.formatFileSize(getContext(), size);
    397     }
    398 
    399     private MeasurementReceiver mReceiver = new MeasurementReceiver() {
    400         @Override
    401         public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) {
    402             mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] {
    403                     totalSize, availSize }).sendToTarget();
    404         }
    405 
    406         @Override
    407         public void updateDetails(StorageMeasurement meas, MeasurementDetails details) {
    408             mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget();
    409         }
    410     };
    411 
    412     public boolean mountToggleClicked(Preference preference) {
    413         return preference == mMountTogglePreference;
    414     }
    415 
    416     public Intent intentForClick(Preference pref) {
    417         Intent intent = null;
    418 
    419         // TODO The current "delete" story is not fully handled by the respective applications.
    420         // When it is done, make sure the intent types below are correct.
    421         // If that cannot be done, remove these intents.
    422         final String key = pref.getKey();
    423         if (pref == mFormatPreference) {
    424             intent = new Intent(Intent.ACTION_VIEW);
    425             intent.setClass(getContext(), com.android.settings.MediaFormat.class);
    426             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume);
    427         } else if (pref == mItemApps) {
    428             intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
    429             intent.setClass(getContext(),
    430                     com.android.settings.Settings.ManageApplicationsActivity.class);
    431         } else if (pref == mItemDownloads) {
    432             intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra(
    433                     DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
    434         } else if (pref == mItemMusic) {
    435             intent = new Intent(Intent.ACTION_GET_CONTENT);
    436             intent.setType("audio/mp3");
    437         } else if (pref == mItemDcim) {
    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 (pref == mItemMisc) {
    443             Context context = getContext().getApplicationContext();
    444             intent = new Intent(context, MiscFilesHandler.class);
    445             intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume);
    446         }
    447 
    448         return intent;
    449     }
    450 
    451     public static class PreferenceHeader extends Preference {
    452         public PreferenceHeader(Context context, int titleRes) {
    453             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
    454             setTitle(titleRes);
    455         }
    456 
    457         public PreferenceHeader(Context context, CharSequence title) {
    458             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
    459             setTitle(title);
    460         }
    461 
    462         @Override
    463         public boolean isEnabled() {
    464             return false;
    465         }
    466     }
    467 
    468     /**
    469      * Return list of other users, excluding the current user.
    470      */
    471     private List<UserInfo> getUsersExcluding(UserInfo excluding) {
    472         final List<UserInfo> users = mUserManager.getUsers();
    473         final Iterator<UserInfo> i = users.iterator();
    474         while (i.hasNext()) {
    475             if (i.next().id == excluding.id) {
    476                 i.remove();
    477             }
    478         }
    479         return users;
    480     }
    481 }
    482