/* * Copyright (C) 2011 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.settings.deviceinfo; import android.app.ActivityManagerNative; import android.app.ActivityThread; import android.app.DownloadManager; import android.content.Context; import android.content.Intent; import android.content.pm.IPackageManager; import android.content.pm.UserInfo; import android.content.res.Resources; import android.hardware.usb.UsbManager; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.RemoteException; import android.os.UserManager; import android.os.storage.StorageManager; import android.os.storage.StorageVolume; import android.preference.Preference; import android.preference.PreferenceCategory; import android.text.format.Formatter; import com.android.settings.R; import com.android.settings.deviceinfo.StorageMeasurement.MeasurementDetails; import com.android.settings.deviceinfo.StorageMeasurement.MeasurementReceiver; import com.google.android.collect.Lists; import java.util.HashMap; import java.util.Iterator; import java.util.List; public class StorageVolumePreferenceCategory extends PreferenceCategory { public static final String KEY_CACHE = "cache"; private static final int ORDER_USAGE_BAR = -2; private static final int ORDER_STORAGE_LOW = -1; /** Physical volume being measured, or {@code null} for internal. */ private final StorageVolume mVolume; private final StorageMeasurement mMeasure; private final Resources mResources; private final StorageManager mStorageManager; private final UserManager mUserManager; private UsageBarPreference mUsageBarPreference; private Preference mMountTogglePreference; private Preference mFormatPreference; private Preference mStorageLow; private StorageItemPreference mItemTotal; private StorageItemPreference mItemAvailable; private StorageItemPreference mItemApps; private StorageItemPreference mItemDcim; private StorageItemPreference mItemMusic; private StorageItemPreference mItemDownloads; private StorageItemPreference mItemCache; private StorageItemPreference mItemMisc; private List<StorageItemPreference> mItemUsers = Lists.newArrayList(); private boolean mUsbConnected; private String mUsbFunction; private long mTotalSize; private static final int MSG_UI_UPDATE_APPROXIMATE = 1; private static final int MSG_UI_UPDATE_DETAILS = 2; private Handler mUpdateHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_UI_UPDATE_APPROXIMATE: { final long[] size = (long[]) msg.obj; updateApproximate(size[0], size[1]); break; } case MSG_UI_UPDATE_DETAILS: { final MeasurementDetails details = (MeasurementDetails) msg.obj; updateDetails(details); break; } } } }; /** * Build category to summarize internal storage, including any emulated * {@link StorageVolume}. */ public static StorageVolumePreferenceCategory buildForInternal(Context context) { return new StorageVolumePreferenceCategory(context, null); } /** * Build category to summarize specific physical {@link StorageVolume}. */ public static StorageVolumePreferenceCategory buildForPhysical( Context context, StorageVolume volume) { return new StorageVolumePreferenceCategory(context, volume); } private StorageVolumePreferenceCategory(Context context, StorageVolume volume) { super(context); mVolume = volume; mMeasure = StorageMeasurement.getInstance(context, volume); mResources = context.getResources(); mStorageManager = StorageManager.from(context); mUserManager = (UserManager) context.getSystemService(Context.USER_SERVICE); setTitle(volume != null ? volume.getDescription(context) : context.getText(R.string.internal_storage)); } private StorageItemPreference buildItem(int titleRes, int colorRes) { return new StorageItemPreference(getContext(), titleRes, colorRes); } public void init() { final Context context = getContext(); final UserInfo currentUser; try { currentUser = ActivityManagerNative.getDefault().getCurrentUser(); } catch (RemoteException e) { throw new RuntimeException("Failed to get current user"); } final List<UserInfo> otherUsers = getUsersExcluding(currentUser); final boolean showUsers = mVolume == null && otherUsers.size() > 0; mUsageBarPreference = new UsageBarPreference(context); mUsageBarPreference.setOrder(ORDER_USAGE_BAR); addPreference(mUsageBarPreference); mItemTotal = buildItem(R.string.memory_size, 0); mItemAvailable = buildItem(R.string.memory_available, R.color.memory_avail); addPreference(mItemTotal); addPreference(mItemAvailable); mItemApps = buildItem(R.string.memory_apps_usage, R.color.memory_apps_usage); mItemDcim = buildItem(R.string.memory_dcim_usage, R.color.memory_dcim); mItemMusic = buildItem(R.string.memory_music_usage, R.color.memory_music); mItemDownloads = buildItem(R.string.memory_downloads_usage, R.color.memory_downloads); mItemCache = buildItem(R.string.memory_media_cache_usage, R.color.memory_cache); mItemMisc = buildItem(R.string.memory_media_misc_usage, R.color.memory_misc); mItemCache.setKey(KEY_CACHE); final boolean showDetails = mVolume == null || mVolume.isPrimary(); if (showDetails) { if (showUsers) { addPreference(new PreferenceHeader(context, currentUser.name)); } addPreference(mItemApps); addPreference(mItemDcim); addPreference(mItemMusic); addPreference(mItemDownloads); addPreference(mItemCache); addPreference(mItemMisc); if (showUsers) { addPreference(new PreferenceHeader(context, R.string.storage_other_users)); int count = 0; for (UserInfo info : otherUsers) { final int colorRes = count++ % 2 == 0 ? R.color.memory_user_light : R.color.memory_user_dark; final StorageItemPreference userPref = new StorageItemPreference( getContext(), info.name, colorRes, info.id); mItemUsers.add(userPref); addPreference(userPref); } } } final boolean isRemovable = mVolume != null ? mVolume.isRemovable() : false; // Always create the preference since many code rely on it existing mMountTogglePreference = new Preference(context); if (isRemovable) { mMountTogglePreference.setTitle(R.string.sd_eject); mMountTogglePreference.setSummary(R.string.sd_eject_summary); addPreference(mMountTogglePreference); } // Only allow formatting of primary physical storage // TODO: enable for non-primary volumes once MTP is fixed final boolean allowFormat = mVolume != null ? mVolume.isPrimary() : false; if (allowFormat) { mFormatPreference = new Preference(context); mFormatPreference.setTitle(R.string.sd_format); mFormatPreference.setSummary(R.string.sd_format_summary); addPreference(mFormatPreference); } final IPackageManager pm = ActivityThread.getPackageManager(); try { if (pm.isStorageLow()) { mStorageLow = new Preference(context); mStorageLow.setOrder(ORDER_STORAGE_LOW); mStorageLow.setTitle(R.string.storage_low_title); mStorageLow.setSummary(R.string.storage_low_summary); addPreference(mStorageLow); } else if (mStorageLow != null) { removePreference(mStorageLow); mStorageLow = null; } } catch (RemoteException e) { } } public StorageVolume getStorageVolume() { return mVolume; } private void updatePreferencesFromState() { // Only update for physical volumes if (mVolume == null) return; mMountTogglePreference.setEnabled(true); final String state = mStorageManager.getVolumeState(mVolume.getPath()); if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { mItemAvailable.setTitle(R.string.memory_available_read_only); if (mFormatPreference != null) { removePreference(mFormatPreference); } } else { mItemAvailable.setTitle(R.string.memory_available); } if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { mMountTogglePreference.setEnabled(true); mMountTogglePreference.setTitle(mResources.getString(R.string.sd_eject)); mMountTogglePreference.setSummary(mResources.getString(R.string.sd_eject_summary)); } else { if (Environment.MEDIA_UNMOUNTED.equals(state) || Environment.MEDIA_NOFS.equals(state) || Environment.MEDIA_UNMOUNTABLE.equals(state)) { mMountTogglePreference.setEnabled(true); mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); mMountTogglePreference.setSummary(mResources.getString(R.string.sd_mount_summary)); } else { mMountTogglePreference.setEnabled(false); mMountTogglePreference.setTitle(mResources.getString(R.string.sd_mount)); mMountTogglePreference.setSummary(mResources.getString(R.string.sd_insert_summary)); } removePreference(mUsageBarPreference); removePreference(mItemTotal); removePreference(mItemAvailable); if (mFormatPreference != null) { removePreference(mFormatPreference); } } if (mUsbConnected && (UsbManager.USB_FUNCTION_MTP.equals(mUsbFunction) || UsbManager.USB_FUNCTION_PTP.equals(mUsbFunction))) { mMountTogglePreference.setEnabled(false); if (Environment.MEDIA_MOUNTED.equals(state) || Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { mMountTogglePreference.setSummary( mResources.getString(R.string.mtp_ptp_mode_summary)); } if (mFormatPreference != null) { mFormatPreference.setEnabled(false); mFormatPreference.setSummary(mResources.getString(R.string.mtp_ptp_mode_summary)); } } else if (mFormatPreference != null) { mFormatPreference.setEnabled(true); mFormatPreference.setSummary(mResources.getString(R.string.sd_format_summary)); } } public void updateApproximate(long totalSize, long availSize) { mItemTotal.setSummary(formatSize(totalSize)); mItemAvailable.setSummary(formatSize(availSize)); mTotalSize = totalSize; final long usedSize = totalSize - availSize; mUsageBarPreference.clear(); mUsageBarPreference.addEntry(0, usedSize / (float) totalSize, android.graphics.Color.GRAY); mUsageBarPreference.commit(); updatePreferencesFromState(); } private static long totalValues(HashMap<String, Long> map, String... keys) { long total = 0; for (String key : keys) { if (map.containsKey(key)) { total += map.get(key); } } return total; } public void updateDetails(MeasurementDetails details) { final boolean showDetails = mVolume == null || mVolume.isPrimary(); if (!showDetails) return; // Count caches as available space, since system manages them mItemTotal.setSummary(formatSize(details.totalSize)); mItemAvailable.setSummary(formatSize(details.availSize)); mUsageBarPreference.clear(); updatePreference(mItemApps, details.appsSize); final long dcimSize = totalValues(details.mediaSize, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES, Environment.DIRECTORY_PICTURES); updatePreference(mItemDcim, dcimSize); final long musicSize = totalValues(details.mediaSize, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS); updatePreference(mItemMusic, musicSize); final long downloadsSize = totalValues(details.mediaSize, Environment.DIRECTORY_DOWNLOADS); updatePreference(mItemDownloads, downloadsSize); updatePreference(mItemCache, details.cacheSize); updatePreference(mItemMisc, details.miscSize); for (StorageItemPreference userPref : mItemUsers) { final long userSize = details.usersSize.get(userPref.userHandle); updatePreference(userPref, userSize); } mUsageBarPreference.commit(); } private void updatePreference(StorageItemPreference pref, long size) { if (size > 0) { pref.setSummary(formatSize(size)); final int order = pref.getOrder(); mUsageBarPreference.addEntry(order, size / (float) mTotalSize, pref.color); } else { removePreference(pref); } } private void measure() { mMeasure.invalidate(); mMeasure.measure(); } public void onResume() { mMeasure.setReceiver(mReceiver); measure(); } public void onStorageStateChanged() { measure(); } public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) { mUsbConnected = isUsbConnected; mUsbFunction = usbFunction; measure(); } public void onMediaScannerFinished() { measure(); } public void onCacheCleared() { measure(); } public void onPause() { mMeasure.cleanUp(); } private String formatSize(long size) { return Formatter.formatFileSize(getContext(), size); } private MeasurementReceiver mReceiver = new MeasurementReceiver() { @Override public void updateApproximate(StorageMeasurement meas, long totalSize, long availSize) { mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE, new long[] { totalSize, availSize }).sendToTarget(); } @Override public void updateDetails(StorageMeasurement meas, MeasurementDetails details) { mUpdateHandler.obtainMessage(MSG_UI_UPDATE_DETAILS, details).sendToTarget(); } }; public boolean mountToggleClicked(Preference preference) { return preference == mMountTogglePreference; } public Intent intentForClick(Preference pref) { Intent intent = null; // TODO The current "delete" story is not fully handled by the respective applications. // When it is done, make sure the intent types below are correct. // If that cannot be done, remove these intents. final String key = pref.getKey(); if (pref == mFormatPreference) { intent = new Intent(Intent.ACTION_VIEW); intent.setClass(getContext(), com.android.settings.MediaFormat.class); intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); } else if (pref == mItemApps) { intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); intent.setClass(getContext(), com.android.settings.Settings.ManageApplicationsActivity.class); } else if (pref == mItemDownloads) { intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); } else if (pref == mItemMusic) { intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("audio/mp3"); } else if (pref == mItemDcim) { intent = new Intent(Intent.ACTION_VIEW); intent.putExtra(Intent.EXTRA_LOCAL_ONLY, true); // TODO Create a Videos category, type = vnd.android.cursor.dir/video intent.setType("vnd.android.cursor.dir/image"); } else if (pref == mItemMisc) { Context context = getContext().getApplicationContext(); intent = new Intent(context, MiscFilesHandler.class); intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mVolume); } return intent; } public static class PreferenceHeader extends Preference { public PreferenceHeader(Context context, int titleRes) { super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); setTitle(titleRes); } public PreferenceHeader(Context context, CharSequence title) { super(context, null, com.android.internal.R.attr.preferenceCategoryStyle); setTitle(title); } @Override public boolean isEnabled() { return false; } } /** * Return list of other users, excluding the current user. */ private List<UserInfo> getUsersExcluding(UserInfo excluding) { final List<UserInfo> users = mUserManager.getUsers(); final Iterator<UserInfo> i = users.iterator(); while (i.hasNext()) { if (i.next().id == excluding.id) { i.remove(); } } return users; } }