blob: 156184ab5c5879c2f80dcb32b6568f404b581154 [file] [log] [blame]
/*
* 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;
}
}