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.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      */
    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      */
    116     public static StorageVolumePreferenceCategory buildForPhysical(
    117             Context context, StorageVolume volume) {
    118         return new StorageVolumePreferenceCategory(context, volume);
    119     }
    120 
    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 
    135     private StorageItemPreference buildItem(int titleRes, int colorRes) {
    136         return new StorageItemPreference(getContext(), titleRes, colorRes);
    137     }
    138 
    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 
    233     public StorageVolume getStorageVolume() {
    234         return mVolume;
    235     }
    236 
    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 
    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             if (map.containsKey(key)) {
    314                 total += map.get(key);
    315             }
    316         }
    317         return total;
    318     }
    319 
    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 
    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 
    365     private void measure() {
    366         mMeasure.invalidate();
    367         mMeasure.measure();
    368     }
    369 
    370     public void onResume() {
    371         mMeasure.setReceiver(mReceiver);
    372         measure();
    373     }
    374 
    375     public void onStorageStateChanged() {
    376         init();
    377         measure();
    378     }
    379 
    380     public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) {
    381         mUsbConnected = isUsbConnected;
    382         mUsbFunction = usbFunction;
    383         measure();
    384     }
    385 
    386     public void onMediaScannerFinished() {
    387         measure();
    388     }
    389 
    390     public void onCacheCleared() {
    391         measure();
    392     }
    393 
    394     public void onPause() {
    395         mMeasure.cleanUp();
    396     }
    397 
    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 
    415     public boolean mountToggleClicked(Preference preference) {
    416         return preference == mMountTogglePreference;
    417     }
    418 
    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 {
    454         public PreferenceHeader(Context context, int titleRes) {
    455             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
    456             setTitle(titleRes);
    457         }
    458 
    459         public PreferenceHeader(Context context, CharSequence title) {
    460             super(context, null, com.android.internal.R.attr.preferenceCategoryStyle);
    461             setTitle(title);
    462         }
    463 
    464         @Override
    465         public boolean isEnabled() {
    466             return false;
    467         }
    468     }
    469 
    470     /**
    471      * Return list of other users, excluding the current user.
    472      */
    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