| /* |
| * 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.ActivityThread; |
| import android.app.DownloadManager; |
| import android.content.Context; |
| import android.content.Intent; |
| import android.content.pm.IPackageManager; |
| import android.content.res.Resources; |
| import android.graphics.drawable.ShapeDrawable; |
| import android.graphics.drawable.shapes.RectShape; |
| import android.hardware.usb.UsbManager; |
| import android.os.Bundle; |
| import android.os.Environment; |
| import android.os.Handler; |
| import android.os.Message; |
| import android.os.RemoteException; |
| 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.MeasurementReceiver; |
| |
| import java.util.HashSet; |
| import java.util.Set; |
| |
| public class StorageVolumePreferenceCategory extends PreferenceCategory implements |
| MeasurementReceiver { |
| |
| static final int TOTAL_SIZE = 0; |
| static final int APPLICATIONS = 1; |
| static final int DCIM = 2; // Pictures and Videos |
| static final int MUSIC = 3; |
| static final int DOWNLOADS = 4; |
| static final int MISC = 5; |
| static final int AVAILABLE = 6; |
| |
| private UsageBarPreference mUsageBarPreference; |
| private Preference[] mPreferences; |
| private Preference mMountTogglePreference; |
| private Preference mFormatPreference; |
| private Preference mStorageLow; |
| private int[] mColors; |
| |
| private Resources mResources; |
| |
| private StorageVolume mStorageVolume; |
| |
| private StorageManager mStorageManager = null; |
| |
| private StorageMeasurement mMeasurement; |
| |
| private boolean mAllowFormat; |
| |
| private boolean mUsbConnected; |
| private String mUsbFunction; |
| |
| static class CategoryInfo { |
| final int mTitle; |
| final int mColor; |
| |
| public CategoryInfo(int title, int color) { |
| mTitle = title; |
| mColor = color; |
| } |
| } |
| |
| static final CategoryInfo[] sCategoryInfos = new CategoryInfo[] { |
| new CategoryInfo(R.string.memory_size, 0), |
| new CategoryInfo(R.string.memory_apps_usage, R.color.memory_apps_usage), |
| new CategoryInfo(R.string.memory_dcim_usage, R.color.memory_dcim), |
| new CategoryInfo(R.string.memory_music_usage, R.color.memory_music), |
| new CategoryInfo(R.string.memory_downloads_usage, R.color.memory_downloads), |
| new CategoryInfo(R.string.memory_media_misc_usage, R.color.memory_misc), |
| new CategoryInfo(R.string.memory_available, R.color.memory_avail), |
| }; |
| |
| public static final Set<String> sPathsExcludedForMisc = new HashSet<String>(); |
| |
| static class MediaCategory { |
| final String[] mDirPaths; |
| final int mCategory; |
| //final int mMediaType; |
| |
| public MediaCategory(int category, String... directories) { |
| mCategory = category; |
| final int length = directories.length; |
| mDirPaths = new String[length]; |
| for (int i = 0; i < length; i++) { |
| final String name = directories[i]; |
| final String path = Environment.getExternalStoragePublicDirectory(name). |
| getAbsolutePath(); |
| mDirPaths[i] = path; |
| sPathsExcludedForMisc.add(path); |
| } |
| } |
| } |
| |
| static final MediaCategory[] sMediaCategories = new MediaCategory[] { |
| new MediaCategory(DCIM, Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES, |
| Environment.DIRECTORY_PICTURES), |
| new MediaCategory(MUSIC, Environment.DIRECTORY_MUSIC, Environment.DIRECTORY_ALARMS, |
| Environment.DIRECTORY_NOTIFICATIONS, Environment.DIRECTORY_RINGTONES, |
| Environment.DIRECTORY_PODCASTS) |
| }; |
| |
| static { |
| // Downloads |
| sPathsExcludedForMisc.add(Environment.getExternalStoragePublicDirectory( |
| Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()); |
| // Apps |
| sPathsExcludedForMisc.add(Environment.getExternalStorageDirectory().getAbsolutePath() + |
| "/Android"); |
| } |
| |
| // Updates the memory usage bar graph. |
| private static final int MSG_UI_UPDATE_APPROXIMATE = 1; |
| |
| // Updates the memory usage bar graph. |
| private static final int MSG_UI_UPDATE_EXACT = 2; |
| |
| private Handler mUpdateHandler = new Handler() { |
| @Override |
| public void handleMessage(Message msg) { |
| switch (msg.what) { |
| case MSG_UI_UPDATE_APPROXIMATE: { |
| Bundle bundle = msg.getData(); |
| final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE); |
| final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE); |
| updateApproximate(totalSize, availSize); |
| break; |
| } |
| case MSG_UI_UPDATE_EXACT: { |
| Bundle bundle = msg.getData(); |
| final long totalSize = bundle.getLong(StorageMeasurement.TOTAL_SIZE); |
| final long availSize = bundle.getLong(StorageMeasurement.AVAIL_SIZE); |
| final long appsUsed = bundle.getLong(StorageMeasurement.APPS_USED); |
| final long downloadsSize = bundle.getLong(StorageMeasurement.DOWNLOADS_SIZE); |
| final long miscSize = bundle.getLong(StorageMeasurement.MISC_SIZE); |
| final long[] mediaSizes = bundle.getLongArray(StorageMeasurement.MEDIA_SIZES); |
| updateExact(totalSize, availSize, appsUsed, downloadsSize, miscSize, |
| mediaSizes); |
| break; |
| } |
| } |
| } |
| }; |
| |
| public StorageVolumePreferenceCategory(Context context, Resources resources, |
| StorageVolume storageVolume, StorageManager storageManager, boolean isPrimary) { |
| super(context); |
| mResources = resources; |
| mStorageVolume = storageVolume; |
| mStorageManager = storageManager; |
| setTitle(storageVolume != null ? storageVolume.getDescription(context) |
| : resources.getText(R.string.internal_storage)); |
| mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary); |
| mMeasurement.setReceiver(this); |
| |
| // Cannot format emulated storage |
| mAllowFormat = mStorageVolume != null && !mStorageVolume.isEmulated(); |
| // For now we are disabling reformatting secondary external storage |
| // until some interoperability problems with MTP are fixed |
| if (!isPrimary) mAllowFormat = false; |
| } |
| |
| public void init() { |
| mUsageBarPreference = new UsageBarPreference(getContext()); |
| |
| final int width = (int) mResources.getDimension(R.dimen.device_memory_usage_button_width); |
| final int height = (int) mResources.getDimension(R.dimen.device_memory_usage_button_height); |
| |
| final int numberOfCategories = sCategoryInfos.length; |
| mPreferences = new Preference[numberOfCategories]; |
| mColors = new int[numberOfCategories]; |
| for (int i = 0; i < numberOfCategories; i++) { |
| final Preference preference = new Preference(getContext()); |
| mPreferences[i] = preference; |
| preference.setTitle(sCategoryInfos[i].mTitle); |
| preference.setSummary(R.string.memory_calculating_size); |
| if (i != TOTAL_SIZE) { |
| // TOTAL_SIZE has no associated color |
| mColors[i] = mResources.getColor(sCategoryInfos[i].mColor); |
| preference.setIcon(createRectShape(width, height, mColors[i])); |
| } |
| } |
| |
| mMountTogglePreference = new Preference(getContext()); |
| mMountTogglePreference.setTitle(R.string.sd_eject); |
| mMountTogglePreference.setSummary(R.string.sd_eject_summary); |
| |
| if (mAllowFormat) { |
| mFormatPreference = new Preference(getContext()); |
| mFormatPreference.setTitle(R.string.sd_format); |
| mFormatPreference.setSummary(R.string.sd_format_summary); |
| } |
| |
| final IPackageManager pm = ActivityThread.getPackageManager(); |
| try { |
| if (pm.isStorageLow()) { |
| mStorageLow = new Preference(getContext()); |
| mStorageLow.setTitle(R.string.storage_low_title); |
| mStorageLow.setSummary(R.string.storage_low_summary); |
| } else { |
| mStorageLow = null; |
| } |
| } catch (RemoteException e) { |
| } |
| } |
| |
| public StorageVolume getStorageVolume() { |
| return mStorageVolume; |
| } |
| |
| /** |
| * Successive mounts can change the list of visible preferences. |
| * This makes sure all preferences are visible and displayed in the right order. |
| */ |
| private void resetPreferences() { |
| final int numberOfCategories = sCategoryInfos.length; |
| |
| removePreference(mUsageBarPreference); |
| for (int i = 0; i < numberOfCategories; i++) { |
| removePreference(mPreferences[i]); |
| } |
| removePreference(mMountTogglePreference); |
| if (mFormatPreference != null) { |
| removePreference(mFormatPreference); |
| } |
| |
| addPreference(mUsageBarPreference); |
| if (mStorageLow != null) { |
| addPreference(mStorageLow); |
| } |
| for (int i = 0; i < numberOfCategories; i++) { |
| addPreference(mPreferences[i]); |
| } |
| addPreference(mMountTogglePreference); |
| if (mFormatPreference != null) { |
| addPreference(mFormatPreference); |
| } |
| |
| mMountTogglePreference.setEnabled(true); |
| } |
| |
| private void updatePreferencesFromState() { |
| resetPreferences(); |
| |
| String state = mStorageVolume != null |
| ? mStorageManager.getVolumeState(mStorageVolume.getPath()) |
| : Environment.MEDIA_MOUNTED; |
| |
| String readOnly = ""; |
| if (Environment.MEDIA_MOUNTED_READ_ONLY.equals(state)) { |
| state = Environment.MEDIA_MOUNTED; |
| readOnly = mResources.getString(R.string.read_only); |
| if (mFormatPreference != null) { |
| removePreference(mFormatPreference); |
| } |
| } |
| |
| if ((mStorageVolume == null || !mStorageVolume.isRemovable()) |
| && !Environment.MEDIA_UNMOUNTED.equals(state)) { |
| // This device has built-in storage that is not removable. |
| // There is no reason for the user to unmount it. |
| removePreference(mMountTogglePreference); |
| } |
| |
| if (Environment.MEDIA_MOUNTED.equals(state)) { |
| mPreferences[AVAILABLE].setSummary(mPreferences[AVAILABLE].getSummary() + readOnly); |
| |
| 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(mPreferences[TOTAL_SIZE]); |
| removePreference(mPreferences[AVAILABLE]); |
| 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)) { |
| 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) { |
| mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize)); |
| mPreferences[AVAILABLE].setSummary(formatSize(availSize)); |
| |
| final long usedSize = totalSize - availSize; |
| |
| mUsageBarPreference.clear(); |
| mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY); |
| mUsageBarPreference.commit(); |
| |
| updatePreferencesFromState(); |
| } |
| |
| public void updateExact(long totalSize, long availSize, long appsSize, long downloadsSize, |
| long miscSize, long[] mediaSizes) { |
| mUsageBarPreference.clear(); |
| |
| mPreferences[TOTAL_SIZE].setSummary(formatSize(totalSize)); |
| |
| if (mMeasurement.isExternalSDCard()) { |
| // TODO FIXME: external SD card will not report any size. Show used space in bar graph |
| final long usedSize = totalSize - availSize; |
| mUsageBarPreference.addEntry(usedSize / (float) totalSize, android.graphics.Color.GRAY); |
| } |
| |
| updatePreference(appsSize, totalSize, APPLICATIONS); |
| |
| long totalMediaSize = 0; |
| for (int i = 0; i < sMediaCategories.length; i++) { |
| final int category = sMediaCategories[i].mCategory; |
| final long size = mediaSizes[i]; |
| updatePreference(size, totalSize, category); |
| totalMediaSize += size; |
| } |
| |
| updatePreference(downloadsSize, totalSize, DOWNLOADS); |
| |
| // Note miscSize != totalSize - availSize - appsSize - downloadsSize - totalMediaSize |
| // Block size is taken into account. That can be extra space from folders. TODO Investigate |
| updatePreference(miscSize, totalSize, MISC); |
| |
| updatePreference(availSize, totalSize, AVAILABLE); |
| |
| mUsageBarPreference.commit(); |
| } |
| |
| private void updatePreference(long size, long totalSize, int category) { |
| if (size > 0) { |
| mPreferences[category].setSummary(formatSize(size)); |
| mUsageBarPreference.addEntry(size / (float) totalSize, mColors[category]); |
| } else { |
| removePreference(mPreferences[category]); |
| } |
| } |
| |
| private void measure() { |
| mMeasurement.invalidate(); |
| mMeasurement.measure(); |
| } |
| |
| public void onResume() { |
| mMeasurement.setReceiver(this); |
| measure(); |
| } |
| |
| public void onStorageStateChanged() { |
| measure(); |
| } |
| |
| public void onUsbStateChanged(boolean isUsbConnected, String usbFunction) { |
| mUsbConnected = isUsbConnected; |
| mUsbFunction = usbFunction; |
| measure(); |
| } |
| public void onMediaScannerFinished() { |
| measure(); |
| } |
| |
| public void onPause() { |
| mMeasurement.cleanUp(); |
| } |
| |
| private static ShapeDrawable createRectShape(int width, int height, int color) { |
| ShapeDrawable shape = new ShapeDrawable(new RectShape()); |
| shape.setIntrinsicHeight(height); |
| shape.setIntrinsicWidth(width); |
| shape.getPaint().setColor(color); |
| return shape; |
| } |
| |
| private String formatSize(long size) { |
| return Formatter.formatFileSize(getContext(), size); |
| } |
| |
| @Override |
| public void updateApproximate(Bundle bundle) { |
| final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_APPROXIMATE); |
| message.setData(bundle); |
| mUpdateHandler.sendMessage(message); |
| } |
| |
| @Override |
| public void updateExact(Bundle bundle) { |
| final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXACT); |
| message.setData(bundle); |
| mUpdateHandler.sendMessage(message); |
| } |
| |
| public boolean mountToggleClicked(Preference preference) { |
| return preference == mMountTogglePreference; |
| } |
| |
| public Intent intentForClick(Preference preference) { |
| 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. |
| if (preference == mFormatPreference) { |
| intent = new Intent(Intent.ACTION_VIEW); |
| intent.setClass(getContext(), com.android.settings.MediaFormat.class); |
| intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume); |
| } else if (preference == mPreferences[APPLICATIONS]) { |
| intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE); |
| intent.setClass(getContext(), |
| com.android.settings.Settings.ManageApplicationsActivity.class); |
| } else if (preference == mPreferences[DOWNLOADS]) { |
| intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS).putExtra( |
| DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true); |
| } else if (preference == mPreferences[MUSIC]) { |
| intent = new Intent(Intent.ACTION_GET_CONTENT); |
| intent.setType("audio/mp3"); |
| } else if (preference == mPreferences[DCIM]) { |
| 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 (preference == mPreferences[MISC]) { |
| Context context = getContext().getApplicationContext(); |
| if (mMeasurement.getMiscSize() > 0) { |
| intent = new Intent(context, MiscFilesHandler.class); |
| intent.putExtra(StorageVolume.EXTRA_STORAGE_VOLUME, mStorageVolume); |
| } |
| } |
| |
| return intent; |
| } |
| } |