OSDN Git Service

Storage stats on external SD card in Settings.
authorGilles Debunne <debunne@google.com>
Thu, 19 May 2011 17:34:14 +0000 (10:34 -0700)
committerGilles Debunne <debunne@google.com>
Wed, 25 May 2011 19:12:22 +0000 (12:12 -0700)
Storage categories are dynamically created from list of StorageVolumes instead
of a static XML.

Unknown files' sizes are part of Misc rather than Apps.

Categories with a size of 0 are removed.

TODO : remove the notion of a "nosdcard" product. Change strings accordingly.
See all TODO in code

Change-Id: I017ac20f5fa50ad9bdeba8e666754ec84acf3858

res/values/colors.xml
res/values/strings.xml
res/xml/device_info_memory.xml
src/com/android/settings/deviceinfo/Constants.java [deleted file]
src/com/android/settings/deviceinfo/Memory.java
src/com/android/settings/deviceinfo/MemoryMeasurement.java [deleted file]
src/com/android/settings/deviceinfo/MiscFilesHandler.java
src/com/android/settings/deviceinfo/PercentageBarChart.java
src/com/android/settings/deviceinfo/StorageMeasurement.java [new file with mode: 0644]
src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java [new file with mode: 0644]

index 6879f9d..631fb19 100644 (file)
@@ -22,8 +22,8 @@
     <color name="memory_avail">#333333</color>
     <color name="memory_apps_usage">#77831A</color>
     <color name="memory_downloads">#476093</color>
-    <color name="memory_video">#793A7F</color>
-    <color name="memory_audio">#8E562A</color>
+    <color name="memory_dcim">#793A7F</color>
+    <color name="memory_music">#8E562A</color>
     <color name="memory_misc">#7C3030</color>
     
     <color name="crypt_keeper_clock_background">#ff9a9a9a</color>
index 80f3d57..bb9360d 100644 (file)
     <string name="proxy_clear_text">Clear</string>
     <!-- HTTP proxy settings. The port number label. -->
     <string name="proxy_port_label">Proxy port</string>
-    <!-- HTTP proxy settings. The hint text field for port. [CHAR LIMIT=50]-->
-    <string name="proxy_port_hint">8080</string>
+    <!-- HTTP proxy settings. The hint text field for port. -->
+    <string name="proxy_port_hint" translatable="false">8080</string>
     <!-- HTTP proxy settings. The exclusion list label. -->
     <string name="proxy_exclusionlist_label">Bypass proxy for</string>
     <!-- HTTP proxy settings. The hint text for proxy exclusion list. [CHAR LIMIT=50]-->
     <string name="wifi_speed">Link speed</string>
     <!-- Label for the IP address of the connection -->
     <string name="wifi_ip_address">IP address</string>
-    <!-- Hint text for the IP address [CHAR LIMIT=25]-->
-    <string name="wifi_ip_address_hint">192.168.1.128</string>
+    <!-- Hint text for the IP address -->
+    <string name="wifi_ip_address_hint" translatable="false">192.168.1.128</string>
     <!-- Label for the EAP method of the network -->
     <string name="wifi_eap_method">EAP method</string>
     <!-- Label for the phase2 -->
     <string name="wifi_ip_settings_invalid_network_prefix_length">Please type a network prefix length between 0 and 32.</string>
     <!-- Label for the DNS (first one) -->
     <string name="wifi_dns1">DNS 1</string>
-    <!-- Hint text for DNS [CHAR LIMIT=25]-->
-    <string name="wifi_dns1_hint">8.8.8.8</string>
+    <!-- Hint text for DNS -->
+    <string name="wifi_dns1_hint" translatable="false">8.8.8.8</string>
     <!-- Label for the DNS (second one)-->
     <string name="wifi_dns2">DNS 2</string>
-    <!-- Hint text for DNS [CHAR LIMIT=25]-->
-    <string name="wifi_dns2_hint">4.4.4.4</string>
+    <!-- Hint text for DNS -->
+    <string name="wifi_dns2_hint" translatable="false">4.4.4.4</string>
     <!-- Label for the gateway of the network -->
     <string name="wifi_gateway">Gateway</string>
-    <!-- Hint text for the gateway [CHAR LIMIT=25]-->
-    <string name="wifi_gateway_hint">192.168.1.1</string>
+    <!-- Hint text for the gateway -->
+    <string name="wifi_gateway_hint" translatable="false">192.168.1.1</string>
     <!-- Label for the network prefix of the network [CHAR LIMIT=25]-->
     <string name="wifi_network_prefix_length">Network prefix length</string>
-    <!-- Hint text for network prefix length [CHAR LIMIT=25] -->
-    <string name="wifi_network_prefix_length_hint">24</string>
+    <!-- Hint text for network prefix length -->
+    <string name="wifi_network_prefix_length_hint" translatable="false">24</string>
 
     <!-- Wifi AP settings-->
     <!-- Label for wifi tether checkbox. Toggles Access Point on/off -->
     <!-- SD card & phone storage settings title. Displayed as a title when showing the total usage of media on the device. Below it will be a number like "123.4 MB" indicating used storage. [CHAR LIMIT=50] -->
     <string name="memory_media_usage">Media</string>
     <!-- SD card & phone storage settings title. Displayed as a title when showing the total usage of /sdcard/Download on the device. Below it will be a number like "123.4 MB" indicating used storage. [CHAR LIMIT=50] -->
-    <string name="memory_downloads_usage">Downloads</string>
+    <string name="memory_downloads_usage">Downloads</string>    
     <!-- SD card & phone storage settings title. Displayed as a title when showing the total usage of all pictures, videos in /sdcard/DCIM, /sdcard/Pictures folders on the device. Below it will be a number like "123.4 MB" indicating used storage. [CHAR LIMIT=50] -->
     <string name="memory_dcim_usage">Pictures, Videos</string>
     <!-- SD card & phone storage settings title. Displayed as a title when showing the total usage of audio files in /sdcard on the device. Below it will be a number like "123.4 MB" indicating used storage. [CHAR LIMIT=50] -->
     <!-- SD card & phone storage settings item title that will result in the phone mounting the SD card. -->
     <string name="sd_mount" product="default">Mount SD card</string>
 
-    <!-- Subtext for Mount USB storage in Storage settings. User should never see this. -->
-    <string name="sd_mount_summary" product="nosdcard">Mount the USB storage</string>
-    <!-- SD card & phone storage settings item title that will result in the phone mounting the SD card. -->
-    <string name="sd_mount_summary" product="default">Mount the SD card</string>
+    <!-- Subtext for Mount USB storage in Storage settings. User should never see this since we use automount. -->
+    <string name="sd_mount_summary" product="nosdcard"></string>
+    <!-- Subtext for Mount SD Card in Storage settings. User should never see this since we use automount. -->
+    <string name="sd_mount_summary" product="default"></string>
     <!-- SD card & phone storage settings item title that will result in the phone formatting the USB storage.   [CHAR LIMIT=25] -->
     <string name="sd_format" product="nosdcard">Erase USB storage</string>
     <!-- SD card & phone storage settings item title that will result in the phone formatting the SD card.   [CHAR LIMIT=25] -->
     <string name="sd_format_summary" product="nosdcard">Erases all data on the internal USB storage, such as music and photos</string>
     <!-- SD card & phone storage settings item title that will result in the phone unmounting the SD card.   [CHAR LIMIT=80] -->
     <string name="sd_format_summary" product="default">Erases all data on the SD card, such as music and photos</string>
-    <!-- SD card status when it is not available status -->
-    <string name="sd_unavailable">Unavailable</string>
-    <!-- SD card status when it is mounted as read only  -->
+    <!-- SD card status when it is mounted as read only. Will be appended to size, starts with an unbreakable space -->
     <string name="read_only">\u0020(Read-only)</string>
     <!-- SD card eject confirmation dialog title   [CHAR LIMIT=25] -->
     <string name="dlg_confirm_unmount_title" product="nosdcard">Unmount USB storage</string>
index aa36698..e905f39 100644 (file)
 <PreferenceScreen xmlns:android="http://schemas.android.com/apk/res/android"
         android:title="@string/storage_settings_title">
 
-    <PreferenceCategory android:key="memory_sd"
-            android:title="@string/sd_memory">
-        <Preference android:key="memory_sd_size" 
-            style="?android:attr/preferenceInformationStyle" 
-            android:title="@string/memory_size"
-            android:summary="00"/>
-        <Preference android:key="memory_sd_avail" 
-            style="?android:attr/preferenceInformationStyle" 
-            android:title="@string/memory_available"
-            android:summary="00"/>
-        <Preference android:key="memory_sd_mount_toggle"
-            android:title="@string/sd_eject"
-            android:summary="@string/sd_eject_summary"/>
-        <Preference android:key="memory_sd_format"
-            android:title="@string/sd_format"
-            android:summary="@string/sd_format_summary"/>
-    </PreferenceCategory>
+<!-- Preference categories are dynamically created based on the list of available storage volumes -->
 
-    <PreferenceCategory android:title="@string/internal_memory">
-        <com.android.settings.deviceinfo.UsageBarPreference
-            android:key="memory_internal_chart"/>
-
-        <Preference android:key="memory_internal_size"
-            android:title="@string/memory_size"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_apps"
-            android:title="@string/memory_apps_usage"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_downloads"
-            android:title="@string/memory_downloads_usage"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_dcim"
-            android:title="@string/memory_dcim_usage"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_music"
-            android:title="@string/memory_music_usage"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_media_misc"
-            android:title="@string/memory_media_misc_usage"
-            android:summary="@string/memory_calculating_size"/>
-
-        <Preference android:key="memory_internal_avail"
-            android:title="@string/memory_available"
-            android:summary="@string/memory_calculating_size"/>
-     </PreferenceCategory>
 </PreferenceScreen>
diff --git a/src/com/android/settings/deviceinfo/Constants.java b/src/com/android/settings/deviceinfo/Constants.java
deleted file mode 100644 (file)
index 9f49479..0000000
+++ /dev/null
@@ -1,102 +0,0 @@
-/*
- * Copyright (C) 2010 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.os.Environment;
-
-import java.util.ArrayList;
-import java.util.List;
-
-/**
- * Some of the constants used in this package
- */
-class Constants {
-    static final int MEDIA_INDEX = 0;
-    static final int DOWNLOADS_INDEX = 1;
-    static final int PIC_VIDEO_INDEX = 2;
-    static final int MUSIC_INDEX = 3;
-    static final int MEDIA_APPS_DATA_INDEX = 4;
-    static final int MEDIA_MISC_INDEX = 5;
-    static final int NUM_MEDIA_DIRS_TRACKED = MEDIA_MISC_INDEX + 1;
-
-    static class MediaDirectory {
-        final String[] mDirPaths;
-        final String mKey;
-        final String mPreferenceName;
-        MediaDirectory(String pref, String debugInfo, String... paths) {
-            mDirPaths = paths;
-            mKey = debugInfo;
-            mPreferenceName = pref;
-        }
-    }
-    static final ArrayList<MediaDirectory> mMediaDirs = new ArrayList<MediaDirectory>();
-    static final List<String> ExclusionTargetsForMiscFiles = new ArrayList<String>();
-    static {
-        mMediaDirs.add(MEDIA_INDEX,
-                new MediaDirectory(null,
-                        "/sdcard",
-                        Environment.getExternalStorageDirectory().getAbsolutePath()));
-        mMediaDirs.add(DOWNLOADS_INDEX,
-                new MediaDirectory("memory_internal_downloads",
-                        "/sdcard/download",
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_DOWNLOADS).getAbsolutePath()));
-        mMediaDirs.add(PIC_VIDEO_INDEX,
-                new MediaDirectory("memory_internal_dcim",
-                        "/sdcard/pic_video",
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_DCIM).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_MOVIES).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_PICTURES).getAbsolutePath()));
-        mMediaDirs.add(MUSIC_INDEX,
-                new MediaDirectory("memory_internal_music",
-                        "/sdcard/audio",
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_MUSIC).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_ALARMS).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_NOTIFICATIONS).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_RINGTONES).getAbsolutePath(),
-                        Environment.getExternalStoragePublicDirectory(
-                                Environment.DIRECTORY_PODCASTS).getAbsolutePath()));
-        mMediaDirs.add(MEDIA_APPS_DATA_INDEX,
-                new MediaDirectory(null,
-                        "/sdcard/Android",
-                        Environment.getExternalStorageAndroidDataDir().getAbsolutePath()));
-        mMediaDirs.add(MEDIA_MISC_INDEX,
-                new MediaDirectory("memory_internal_media_misc",
-                        "misc on /sdcard",
-                        "not relevant"));
-        // prepare a lit of strings representing dirpaths that should be skipped while looking
-        // for 'other' files
-        for (int j = 0; j < Constants.NUM_MEDIA_DIRS_TRACKED - 1; j++) {
-            String[] dirs = Constants.mMediaDirs.get(j).mDirPaths;
-            int len = dirs.length;
-            if (len > 0) {
-                for (int k = 0; k < len; k++) {
-                    ExclusionTargetsForMiscFiles.add(dirs[k]);
-                }
-            }
-            // also add /sdcard/Android
-            ExclusionTargetsForMiscFiles.add(
-                    Environment.getExternalStorageDirectory().getAbsolutePath() + "/Android");
-        }
-    }
-}
index 1e10c58..955e578 100644 (file)
 
 package com.android.settings.deviceinfo;
 
-import com.android.settings.R;
-import com.android.settings.SettingsPreferenceFragment;
-import com.android.settings.deviceinfo.MemoryMeasurement.MeasurementReceiver;
-
-import android.app.ActivityManager;
 import android.app.AlertDialog;
 import android.app.Dialog;
-import android.app.DownloadManager;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.DialogInterface;
+import android.content.DialogInterface.OnCancelListener;
 import android.content.Intent;
 import android.content.IntentFilter;
-import android.content.DialogInterface.OnCancelListener;
-import android.content.pm.ApplicationInfo;
 import android.content.res.Resources;
-import android.graphics.drawable.ShapeDrawable;
-import android.graphics.drawable.shapes.RectShape;
 import android.os.Bundle;
 import android.os.Environment;
-import android.os.Handler;
 import android.os.IBinder;
-import android.os.Message;
+import android.os.Parcelable;
 import android.os.RemoteException;
 import android.os.ServiceManager;
 import android.os.storage.IMountService;
 import android.os.storage.StorageEventListener;
 import android.os.storage.StorageManager;
+import android.os.storage.StorageVolume;
 import android.preference.Preference;
-import android.preference.PreferenceGroup;
 import android.preference.PreferenceScreen;
-import android.text.format.Formatter;
 import android.util.Log;
 import android.widget.Toast;
 
-import java.util.List;
+import com.android.settings.R;
+import com.android.settings.SettingsPreferenceFragment;
 
-public class Memory extends SettingsPreferenceFragment implements OnCancelListener,
-        MeasurementReceiver {
+public class Memory extends SettingsPreferenceFragment implements OnCancelListener {
     private static final String TAG = "MemorySettings";
 
-    private static final String MEMORY_SD_SIZE = "memory_sd_size";
-
-    private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
-
-    private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
-
-    private static final String MEMORY_SD_FORMAT = "memory_sd_format";
-
-    private static final String MEMORY_SD_GROUP = "memory_sd";
-
-    private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
-
-    private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
-
-    private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
-
-    private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
-
     private static final int DLG_CONFIRM_UNMOUNT = 1;
     private static final int DLG_ERROR_UNMOUNT = 2;
 
-    private Resources mRes;
-
-    // External storage preferences
-    private Preference mSdSize;
-    private Preference mSdAvail;
-    private Preference mSdMountToggle;
-    private Preference mSdFormat;
-    private PreferenceGroup mSdMountPreferenceGroup;
+    private Resources mResources;
 
-    // Internal storage preferences
-    private Preference mInternalSize;
-    private Preference mInternalAvail;
-    private Preference mInternalAppsUsage;
-    private final Preference[] mMediaPreferences = new Preference[Constants.NUM_MEDIA_DIRS_TRACKED];
-    private UsageBarPreference mInternalUsageChart;
-
-    // Internal storage chart colors
-    private int mInternalAppsColor;
-    private int mInternalAvailColor;
-    private int mInternalUsedColor;
-
-    boolean mSdMountToggleAdded = true;
+    // The mountToggle Preference that has been clicked.
+    // The click event will be discarded if this value is not null. Reset to null after (un)mount.
+    private Preference mClickedMountToggle;
+    private String mClickedMountPoint;
     
     // Access using getMountService()
     private IMountService mMountService = null;
 
     private StorageManager mStorageManager = null;
 
-    // Updates the memory usage bar graph.
-    private static final int MSG_UI_UPDATE_INTERNAL_APPROXIMATE = 1;
-
-    // Updates the memory usage bar graph.
-    private static final int MSG_UI_UPDATE_INTERNAL_EXACT = 2;
-
-    // Updates the memory usage stats for external.
-    private static final int MSG_UI_UPDATE_EXTERNAL_APPROXIMATE = 3;
-
-    private Handler mUpdateHandler = new Handler() {
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_UI_UPDATE_INTERNAL_APPROXIMATE: {
-                    Bundle bundle = msg.getData();
-                    final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
-                    final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
-                    updateUiApproximate(totalSize, availSize);
-                    break;
-                }
-                case MSG_UI_UPDATE_INTERNAL_EXACT: {
-                    Bundle bundle = msg.getData();
-                    final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
-                    final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
-                    final long appsUsed = bundle.getLong(MemoryMeasurement.APPS_USED);
-                    final long[] mediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED];
-                    for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
-                        mediaSizes[i] = bundle.getLong(Constants.mMediaDirs.get(i).mKey);
-                    }
-                    updateUiExact(totalSize, availSize, appsUsed, mediaSizes);
-                    break;
-                }
-                case MSG_UI_UPDATE_EXTERNAL_APPROXIMATE: {
-                    Bundle bundle = msg.getData();
-                    final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
-                    final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
-                    updateExternalStorage(totalSize, availSize);
-                    break;
-                }
-            }
-        }
-    };
-
-    private MemoryMeasurement mMeasurement;
+    private StorageVolumePreferenceCategory[] mStorageVolumePreferenceCategories;
 
     @Override
     public void onCreate(Bundle icicle) {
@@ -162,84 +74,39 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
 
         addPreferencesFromResource(R.xml.device_info_memory);
 
-        mRes = getResources();
-        mSdSize = findPreference(MEMORY_SD_SIZE);
-        mSdAvail = findPreference(MEMORY_SD_AVAIL);
-        mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE);
-        mSdFormat = findPreference(MEMORY_SD_FORMAT);
-        mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
+        mResources = getResources();
 
-        if (Environment.isExternalStorageEmulated()) {
-            getPreferenceScreen().removePreference(mSdMountPreferenceGroup);
-        }
-
-        mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
-        mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
-        mInternalUsedColor = android.graphics.Color.GRAY;
-        mInternalAvailColor = mRes.getColor(R.color.memory_avail);
-        final int buttonWidth = (int) mRes.getDimension(R.dimen.device_memory_usage_button_width);
-        final int buttonHeight = (int) mRes.getDimension(R.dimen.device_memory_usage_button_height);
-
-        // total available space
-        mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
-        mInternalAvail.setIcon(createRectShape(buttonHeight, buttonWidth, mInternalAvailColor));
-
-        // used by apps
-        mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
-        mInternalAppsUsage.setIcon(createRectShape(buttonHeight, buttonWidth, mInternalAppsColor));
-
-        // space used by individual major directories on /sdcard
-        for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
-            // nothing to be displayed for certain entries in Constants.mMediaDirs
-            if (Constants.mMediaDirs.get(i).mPreferenceName == null) {
-                continue;
-            }
-            mMediaPreferences[i] = findPreference(Constants.mMediaDirs.get(i).mPreferenceName);
-            int color = 0;
-            switch (i) {
-                case Constants.DOWNLOADS_INDEX:
-                    color = mRes.getColor(R.color.memory_downloads);
-                    break;
-                case Constants.PIC_VIDEO_INDEX:
-                    color = mRes.getColor(R.color.memory_video);
-                    break;
-                case Constants.MUSIC_INDEX:
-                    color = mRes.getColor(R.color.memory_audio);
-                    break;
-                case Constants.MEDIA_MISC_INDEX:
-                    color = mRes.getColor(R.color.memory_misc);
-                    break;
+        try {
+            IMountService mountService = IMountService.Stub.asInterface(ServiceManager
+                    .getService("mount"));
+            Parcelable[] volumes = mountService.getVolumeList();
+            int length = volumes.length;
+            mStorageVolumePreferenceCategories = new StorageVolumePreferenceCategory[length];
+            for (int i = 0; i < length; i++) {
+                StorageVolume storageVolume = (StorageVolume) volumes[i];
+                StorageVolumePreferenceCategory storagePreferenceCategory =
+                    new StorageVolumePreferenceCategory(getActivity(), mResources, storageVolume,
+                            i == 0); // The first volume is the primary volume
+                mStorageVolumePreferenceCategories[i] = storagePreferenceCategory;
+                getPreferenceScreen().addPreference(storagePreferenceCategory);
+                storagePreferenceCategory.init();
             }
-            mMediaPreferences[i].setIcon(createRectShape(buttonHeight, buttonWidth, color));
+        } catch (Exception e) {
+            Log.e(TAG, "couldn't talk to MountService", e);
         }
-        mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
-
-        mMeasurement = MemoryMeasurement.getInstance(getActivity());
-        mMeasurement.setReceiver(this);
-    }
-
-    private ShapeDrawable createRectShape(int height, int width, int color) {
-        ShapeDrawable shape = new ShapeDrawable(new RectShape());
-        shape.setIntrinsicHeight(height);
-        shape.setIntrinsicWidth(width);
-        shape.getPaint().setColor(color);
-        return shape;
     }
 
     @Override
     public void onResume() {
         super.onResume();
-        mMeasurement.setReceiver(this);
         IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
         intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
         intentFilter.addDataScheme("file");
-        getActivity().registerReceiver(mReceiver, intentFilter);
+        getActivity().registerReceiver(mMediaScannerReceiver, intentFilter);
 
-        mMeasurement.invalidate();
-        if (!Environment.isExternalStorageEmulated()) {
-            mMeasurement.measureExternal();
+        for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
+            mStorageVolumePreferenceCategories[i].onResume();
         }
-        mMeasurement.measureInternal();
     }
 
     StorageEventListener mStorageListener = new StorageEventListener() {
@@ -248,8 +115,12 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
             Log.i(TAG, "Received storage state changed notification that " +
                     path + " changed state from " + oldState +
                     " to " + newState);
-            if (!Environment.isExternalStorageEmulated()) {
-                mMeasurement.measureExternal();
+            for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
+                StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i];
+                if (path.equals(svpc.getMountPoint())) {
+                    svpc.onStorageStateChanged();
+                    break;
+                }
             }
         }
     };
@@ -257,8 +128,10 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
     @Override
     public void onPause() {
         super.onPause();
-        getActivity().unregisterReceiver(mReceiver);
-        mMeasurement.cleanUp();
+        getActivity().unregisterReceiver(mMediaScannerReceiver);
+        for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
+            mStorageVolumePreferenceCategories[i].onPause();
+        }
     }
 
     @Override
@@ -283,60 +156,38 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
     
     @Override
     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
-        if (preference == mSdMountToggle) {
-            String status = Environment.getExternalStorageState();
-            if (status.equals(Environment.MEDIA_MOUNTED)) {
-                unmount();
-            } else {
-                mount();
+        for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
+            StorageVolumePreferenceCategory svpc = mStorageVolumePreferenceCategories[i];
+            Intent intent = svpc.intentForClick(preference);
+            if (intent != null) {
+                startActivity(intent);
+                return true;
             }
-            return true;
-        } else if (preference == mSdFormat) {
-            Intent intent = new Intent(Intent.ACTION_VIEW);
-            intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
-            startActivity(intent);
-            return true;
-        } else if (preference == mInternalAppsUsage) {
-            Intent intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
-            intent.setClass(getActivity(),
-                    com.android.settings.Settings.ManageApplicationsActivity.class);
-            startActivity(intent);
-            return true;
-        } else if (preference == mMediaPreferences[Constants.DOWNLOADS_INDEX]) {
-            Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
-                    .putExtra(DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
-            startActivity(intent);
-            return true;
-        } else if (preference == mMediaPreferences[Constants.MUSIC_INDEX]) {
-            Intent intent = new Intent("android.intent.action.GET_CONTENT");
-            intent.setType("audio/mp3");
-            startActivity(intent);
-            return true;
-        } else if (preference == mMediaPreferences[Constants.PIC_VIDEO_INDEX]) {
-            Intent intent = new Intent("android.intent.action.GET_CONTENT");
-            intent.setType("image/jpeg");
-            startActivity(intent);
-            return true;
-        } else if (preference == mMediaPreferences[Constants.MEDIA_MISC_INDEX]) {
-            Context context = getActivity().getApplicationContext();
-            if (MemoryMeasurement.getInstance(context).isSizeOfMiscCategoryNonZero()) {
-                startActivity(new Intent(context, MiscFilesHandler.class));
+
+            boolean mountToggleClicked = svpc.mountToggleClicked(preference);
+            if (mountToggleClicked && mClickedMountToggle == null) {
+                mClickedMountToggle = preference;
+                mClickedMountPoint = svpc.getMountPoint();
+                String state = svpc.getStorageVolumeState();
+                if (state.equals(Environment.MEDIA_MOUNTED) ||
+                        state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+                    unmount();
+                } else {
+                    mount();
+                }
+                return true;
             }
-            return true;
         }
 
         return false;
     }
      
-    private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
+    private final BroadcastReceiver mMediaScannerReceiver = new BroadcastReceiver() {
         @Override
         public void onReceive(Context context, Intent intent) {
-            mMeasurement.invalidate();
-
-            if (!Environment.isExternalStorageEmulated()) {
-                mMeasurement.measureExternal();
+            for (int i = 0; i < mStorageVolumePreferenceCategories.length; i++) {
+                mStorageVolumePreferenceCategories[i].onMediaScannerFinished();
             }
-            mMeasurement.measureInternal();
         }
     };
 
@@ -348,7 +199,7 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
                     .setTitle(R.string.dlg_confirm_unmount_title)
                     .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
                         public void onClick(DialogInterface dialog, int which) {
-                            doUnmount(true);
+                            doUnmount();
                         }})
                     .setNegativeButton(R.string.cancel, null)
                     .setMessage(R.string.dlg_confirm_unmount_text)
@@ -368,28 +219,27 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
         super.showDialog(id);
 
         switch (id) {
-        case DLG_CONFIRM_UNMOUNT:
-        case DLG_ERROR_UNMOUNT:
-            setOnCancelListener(this);
-            break;
+            case DLG_CONFIRM_UNMOUNT:
+            case DLG_ERROR_UNMOUNT:
+                setOnCancelListener(this);
+                break;
         }
     }
 
-    private void doUnmount(boolean force) {
+    private void doUnmount() {
         // Present a toast here
         Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
         IMountService mountService = getMountService();
-        String extStoragePath = Environment.getExternalStorageDirectory().toString();
         try {
-            mSdMountToggle.setEnabled(false);
-            mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
-            mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
-            mountService.unmountVolume(extStoragePath, force);
+            mClickedMountToggle.setEnabled(false);
+            mClickedMountToggle.setTitle(mResources.getString(R.string.sd_ejecting_title));
+            mClickedMountToggle.setSummary(mResources.getString(R.string.sd_ejecting_summary));
+            mountService.unmountVolume(mClickedMountPoint, true);
         } catch (RemoteException e) {
-            // Informative dialog to user that
-            // unmount failed.
+            // Informative dialog to user that unmount failed.
             showDialogInner(DLG_ERROR_UNMOUNT);
         }
+        mClickedMountToggle = null;
     }
 
     private void showDialogInner(int id) {
@@ -398,17 +248,21 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
     }
 
     private boolean hasAppsAccessingStorage() throws RemoteException {
-        String extStoragePath = Environment.getExternalStorageDirectory().toString();
         IMountService mountService = getMountService();
-        int stUsers[] = mountService.getStorageUsers(extStoragePath);
+        int stUsers[] = mountService.getStorageUsers(mClickedMountPoint);
         if (stUsers != null && stUsers.length > 0) {
             return true;
         }
+        // TODO FIXME Parameterize with mountPoint and uncomment.
+        // On HC-MR2, no apps can be installed on sd and the emulated internal storage is not
+        // removable: application cannot interfere with unmount
+        /*
         ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
         List<ApplicationInfo> list = am.getRunningExternalApplications();
         if (list != null && list.size() > 0) {
             return true;
         }
+        */
         return false;
     }
 
@@ -419,12 +273,13 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
                // Present dialog to user
                showDialogInner(DLG_CONFIRM_UNMOUNT);
            } else {
-               doUnmount(true);
+               doUnmount();
            }
         } catch (RemoteException e) {
             // Very unlikely. But present an error dialog anyway
             Log.e(TAG, "Is MountService running?");
             showDialogInner(DLG_ERROR_UNMOUNT);
+            mClickedMountToggle = null;
         }
     }
 
@@ -432,151 +287,17 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
         IMountService mountService = getMountService();
         try {
             if (mountService != null) {
-                mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
+                mountService.mountVolume(mClickedMountPoint);
             } else {
                 Log.e(TAG, "Mount service is null, can't mount");
             }
         } catch (RemoteException ex) {
+            // Not much can be done
         }
-    }
-
-    private void updateUiExact(long totalSize, long availSize, long appsSize, long[] mediaSizes) {
-        // There are other things that can take up storage, but we didn't measure it.
-        // add that unaccounted-for-usage to Apps Usage
-        long appsPlusRemaining = totalSize - availSize - mediaSizes[Constants.DOWNLOADS_INDEX] -
-                mediaSizes[Constants.PIC_VIDEO_INDEX] - mediaSizes[Constants.MUSIC_INDEX] -
-                mediaSizes[Constants.MEDIA_MISC_INDEX];
-        mInternalSize.setSummary(formatSize(totalSize));
-        mInternalAvail.setSummary(formatSize(availSize));
-        mInternalAppsUsage.setSummary(formatSize(appsPlusRemaining));
-
-        mInternalUsageChart.clear();
-        mInternalUsageChart.addEntry(appsPlusRemaining / (float) totalSize, mInternalAppsColor);
-
-        for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
-            if (Constants.mMediaDirs.get(i).mPreferenceName == null) {
-                continue;
-            }
-            this.mMediaPreferences[i].setSummary(formatSize(mediaSizes[i]));
-            // don't add entry to color chart for media usage and for zero-sized dirs
-            if (i != Constants.MEDIA_INDEX && mediaSizes[i] > 0) {
-                int color = 0;
-                switch (i) {
-                    case Constants.DOWNLOADS_INDEX:
-                        color = mRes.getColor(R.color.memory_downloads);
-                        break;
-                    case Constants.PIC_VIDEO_INDEX:
-                        color = mRes.getColor(R.color.memory_video);
-                        break;
-                    case Constants.MUSIC_INDEX:
-                        color = mRes.getColor(R.color.memory_audio);
-                        break;
-                    case Constants.MEDIA_MISC_INDEX:
-                        color = mRes.getColor(R.color.memory_misc);
-                        break;
-                }
-                mInternalUsageChart.addEntry(mediaSizes[i] / (float) totalSize, color);
-            }
-        }
-        mInternalUsageChart.addEntry(availSize / (float) totalSize, mInternalAvailColor);
-        mInternalUsageChart.commit();
-    }
-
-    private void updateUiApproximate(long totalSize, long availSize) {
-        mInternalSize.setSummary(formatSize(totalSize));
-        mInternalAvail.setSummary(formatSize(availSize));
-
-        final long usedSize = totalSize - availSize;
-
-        mInternalUsageChart.clear();
-        mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
-        mInternalUsageChart.commit();
-    }
-
-    private void updateExternalStorage(long totalSize, long availSize) {
-        String status = Environment.getExternalStorageState();
-        String readOnly = "";
-        if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
-            status = Environment.MEDIA_MOUNTED;
-            readOnly = mRes.getString(R.string.read_only);
-        }
-
-        if (status.equals(Environment.MEDIA_MOUNTED)) {
-            if (!Environment.isExternalStorageRemovable()) {
-                // This device has built-in storage that is not removable.
-                // There is no reason for the user to unmount it.
-                if (mSdMountToggleAdded) {
-                    mSdMountPreferenceGroup.removePreference(mSdMountToggle);
-                    mSdMountToggleAdded = false;
-                }
-            }
-            try {
-                mSdSize.setSummary(formatSize(totalSize));
-                mSdAvail.setSummary(formatSize(availSize) + readOnly);
-
-                mSdMountToggle.setEnabled(true);
-                mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
-                mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
-
-            } catch (IllegalArgumentException e) {
-                // this can occur if the SD card is removed, but we haven't
-                // received the
-                // ACTION_MEDIA_REMOVED Intent yet.
-                status = Environment.MEDIA_REMOVED;
-            }
-        } else {
-            mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
-            mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
-
-            if (!Environment.isExternalStorageRemovable()) {
-                if (status.equals(Environment.MEDIA_UNMOUNTED)) {
-                    if (!mSdMountToggleAdded) {
-                        mSdMountPreferenceGroup.addPreference(mSdMountToggle);
-                        mSdMountToggleAdded = true;
-                    }
-                }
-            }
-
-            if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
-                    || status.equals(Environment.MEDIA_UNMOUNTABLE)) {
-                mSdMountToggle.setEnabled(true);
-                mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
-                mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
-            } else {
-                mSdMountToggle.setEnabled(false);
-                mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
-                mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
-            }
-        }
-    }
-
-    private String formatSize(long size) {
-        return Formatter.formatFileSize(getActivity(), size);
+        mClickedMountToggle = null;
     }
 
     public void onCancel(DialogInterface dialog) {
-        // TODO: Is this really required?
-        // finish();
-    }
-
-    @Override
-    public void updateApproximateExternal(Bundle bundle) {
-        final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
-        message.setData(bundle);
-        mUpdateHandler.sendMessage(message);
-    }
-
-    @Override
-    public void updateApproximateInternal(Bundle bundle) {
-        final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
-        message.setData(bundle);
-        mUpdateHandler.sendMessage(message);
-    }
-
-    @Override
-    public void updateExactInternal(Bundle bundle) {
-        final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
-        message.setData(bundle);
-        mUpdateHandler.sendMessage(message);
+        mClickedMountToggle = null;
     }
 }
diff --git a/src/com/android/settings/deviceinfo/MemoryMeasurement.java b/src/com/android/settings/deviceinfo/MemoryMeasurement.java
deleted file mode 100644 (file)
index 1b42bc1..0000000
+++ /dev/null
@@ -1,505 +0,0 @@
-package com.android.settings.deviceinfo;
-
-import com.android.internal.app.IMediaContainerService;
-
-import android.content.ComponentName;
-import android.content.Context;
-import android.content.Intent;
-import android.content.ServiceConnection;
-import android.content.pm.ApplicationInfo;
-import android.content.pm.IPackageStatsObserver;
-import android.content.pm.PackageManager;
-import android.content.pm.PackageStats;
-import android.os.Bundle;
-import android.os.Environment;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.IBinder;
-import android.os.Looper;
-import android.os.Message;
-import android.os.StatFs;
-import android.util.Log;
-
-import java.io.File;
-import java.lang.ref.WeakReference;
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-
-/**
- * Measure the memory for various systems.
- *
- * TODO: This class should ideally have less knowledge about what the context
- * it's measuring is. In the future, reduce the amount of stuff it needs to
- * know about by just keeping an array of measurement types of the following
- * properties:
- *
- *   Filesystem stats (using StatFs)
- *   Directory measurements (using DefaultContainerService.measureDir)
- *   Application measurements (using PackageManager)
- *
- * Then the calling application would just specify the type and an argument.
- * This class would keep track of it while the calling application would
- * decide on how to use it.
- */
-public class MemoryMeasurement {
-    private static final String TAG = "MemorySettings";
-    private static final boolean LOCAL_LOGV = true;
-    static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
-
-    public static final String TOTAL_SIZE = "total_size";
-
-    public static final String AVAIL_SIZE = "avail_size";
-
-    public static final String APPS_USED = "apps_used";
-
-    private long[] mMediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED];
-
-    private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
-
-    private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
-            DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
-
-    private final MeasurementHandler mHandler;
-
-    private static volatile MemoryMeasurement sInstance;
-
-    private volatile WeakReference<MeasurementReceiver> mReceiver;
-
-    // Internal memory fields
-    private long mInternalTotalSize;
-    private long mInternalAvailSize;
-    private long mInternalAppsSize;
-
-    // External memory fields
-    private long mExternalTotalSize;
-
-    private long mExternalAvailSize;
-    List<FileInfo> mFileInfoForMisc;
-
-    private MemoryMeasurement(Context context) {
-        // Start the thread that will measure the disk usage.
-        final HandlerThread t = new HandlerThread("MemoryMeasurement");
-        t.start();
-        mHandler = new MeasurementHandler(context, t.getLooper());
-    }
-
-    /**
-     * Get the singleton of the MemoryMeasurement class. The application
-     * context is used to avoid leaking activities.
-     */
-    public static MemoryMeasurement getInstance(Context context) {
-        if (sInstance == null) {
-            synchronized (MemoryMeasurement.class) {
-                if (sInstance == null) {
-                    sInstance = new MemoryMeasurement(context.getApplicationContext());
-                }
-            }
-        }
-
-        return sInstance;
-    }
-
-    public void setReceiver(MeasurementReceiver receiver) {
-        if (mReceiver == null || mReceiver.get() == null) {
-            mReceiver = new WeakReference<MeasurementReceiver>(receiver);
-        }
-    }
-
-    public void measureExternal() {
-        if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_EXTERNAL)) {
-            mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_EXTERNAL);
-        }
-    }
-
-    public void measureInternal() {
-        if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_INTERNAL)) {
-            mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_INTERNAL);
-        }
-    }
-
-    public void cleanUp() {
-        mReceiver = null;
-        mHandler.cleanUp();
-    }
-
-    private void sendInternalApproximateUpdate() {
-        MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
-        if (receiver == null) {
-            return;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
-        bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
-
-        receiver.updateApproximateInternal(bundle);
-    }
-
-    private void sendInternalExactUpdate() {
-        MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
-        if (receiver == null) {
-            if (LOGV) {
-                Log.i(TAG, "measurements dropped because receiver is null! wasted effort");
-            }
-            return;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
-        bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
-        bundle.putLong(APPS_USED, mInternalAppsSize);
-        for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
-            bundle.putLong(Constants.mMediaDirs.get(i).mKey, mMediaSizes[i]);
-        }
-
-        receiver.updateExactInternal(bundle);
-    }
-
-    private void sendExternalApproximateUpdate() {
-        MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
-        if (receiver == null) {
-            return;
-        }
-
-        Bundle bundle = new Bundle();
-        bundle.putLong(TOTAL_SIZE, mExternalTotalSize);
-        bundle.putLong(AVAIL_SIZE, mExternalAvailSize);
-
-        receiver.updateApproximateExternal(bundle);
-    }
-
-    public interface MeasurementReceiver {
-        public void updateApproximateInternal(Bundle bundle);
-
-        public void updateExactInternal(Bundle bundle);
-
-        public void updateApproximateExternal(Bundle bundle);
-    }
-
-    private class MeasurementHandler extends Handler {
-        public static final int MSG_MEASURE_INTERNAL = 1;
-
-        public static final int MSG_MEASURE_EXTERNAL = 2;
-
-        public static final int MSG_CONNECTED = 3;
-
-        public static final int MSG_DISCONNECT = 4;
-
-        public static final int MSG_COMPLETED = 5;
-
-        public static final int MSG_INVALIDATE = 6;
-
-        private Object mLock = new Object();
-
-        private IMediaContainerService mDefaultContainer;
-
-        private volatile boolean mBound = false;
-
-        private volatile boolean mMeasured = false;
-
-        private StatsObserver mStatsObserver;
-
-        private final WeakReference<Context> mContext;
-
-        final private ServiceConnection mDefContainerConn = new ServiceConnection() {
-            public void onServiceConnected(ComponentName name, IBinder service) {
-                final IMediaContainerService imcs = IMediaContainerService.Stub
-                        .asInterface(service);
-                mDefaultContainer = imcs;
-                mBound = true;
-                sendMessage(obtainMessage(MSG_CONNECTED, imcs));
-            }
-
-            public void onServiceDisconnected(ComponentName name) {
-                mBound = false;
-                removeMessages(MSG_CONNECTED);
-            }
-        };
-
-        public MeasurementHandler(Context context, Looper looper) {
-            super(looper);
-            mContext = new WeakReference<Context>(context);
-        }
-
-        @Override
-        public void handleMessage(Message msg) {
-            switch (msg.what) {
-                case MSG_MEASURE_EXTERNAL: {
-                    if (mMeasured) {
-                        sendExternalApproximateUpdate();
-                        break;
-                    }
-
-                    measureApproximateExternalStorage();
-                    break;
-                }
-                case MSG_MEASURE_INTERNAL: {
-                    if (mMeasured) {
-                        sendInternalExactUpdate();
-                        break;
-                    }
-
-                    final Context context = (mContext != null) ? mContext.get() : null;
-                    if (context == null) {
-                        return;
-                    }
-
-                    measureApproximateInternalStorage();
-
-                    synchronized (mLock) {
-                        if (mBound) {
-                            removeMessages(MSG_DISCONNECT);
-                            sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
-                        } else {
-                            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
-                            context.bindService(service, mDefContainerConn,
-                                    Context.BIND_AUTO_CREATE);
-                        }
-                    }
-                    break;
-                }
-                case MSG_CONNECTED: {
-                    IMediaContainerService imcs = (IMediaContainerService) msg.obj;
-                    measureExactInternalStorage(imcs);
-                    break;
-                }
-                case MSG_DISCONNECT: {
-                    synchronized (mLock) {
-                        if (mBound) {
-                            final Context context = (mContext != null) ? mContext.get() : null;
-                            if (context == null) {
-                                return;
-                            }
-
-                            mBound = false;
-                            context.unbindService(mDefContainerConn);
-                        }
-                    }
-                    break;
-                }
-                case MSG_COMPLETED: {
-                    mMeasured = true;
-                    sendInternalExactUpdate();
-                    break;
-                }
-                case MSG_INVALIDATE: {
-                    mMeasured = false;
-                    break;
-                }
-            }
-        }
-
-        public void cleanUp() {
-            removeMessages(MSG_MEASURE_INTERNAL);
-            removeMessages(MSG_MEASURE_EXTERNAL);
-
-            sendEmptyMessage(MSG_DISCONNECT);
-        }
-
-        /**
-         * Request measurement of each package.
-         *
-         * @param pm PackageManager instance to query
-         */
-        public void requestQueuedMeasurementsLocked(PackageManager pm) {
-            final List<String> appsList = mStatsObserver.getAppsList();
-            final int N = appsList.size();
-            for (int i = 0; i < N; i++) {
-                pm.getPackageSizeInfo(appsList.get(i), mStatsObserver);
-            }
-        }
-
-        private class StatsObserver extends IPackageStatsObserver.Stub {
-            private long mAppsSizeForThisStatsObserver = 0;
-            private final List<String> mAppsList = new ArrayList<String>();
-            public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
-                if (!mStatsObserver.equals(this)) {
-                    // this callback's class object is no longer in use. ignore this callback.
-                    return;
-                }
-                if (succeeded) {
-                    mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
-                            stats.externalCacheSize + stats.externalDataSize +
-                            stats.externalMediaSize + stats.externalObbSize;
-                }
-
-                synchronized (mAppsList) {
-                    mAppsList.remove(stats.packageName);
-
-                    if (mAppsList.size() == 0) {
-                        mInternalAppsSize = mAppsSizeForThisStatsObserver;
-
-                        onInternalMeasurementComplete();
-                    }
-                }
-            }
-
-            public void queuePackageMeasurementLocked(String packageName) {
-                mAppsList.add(packageName);
-            }
-            public List<String> getAppsList() {
-                return mAppsList;
-            }
-        }
-
-        private void onInternalMeasurementComplete() {
-            sendEmptyMessage(MSG_COMPLETED);
-        }
-
-        private void measureApproximateInternalStorage() {
-            final File dataPath = Environment.getDataDirectory();
-            final StatFs stat = new StatFs(dataPath.getPath());
-            final long blockSize = stat.getBlockSize();
-            final long totalBlocks = stat.getBlockCount();
-            final long availableBlocks = stat.getAvailableBlocks();
-
-            final long totalSize = totalBlocks * blockSize;
-            final long availSize = availableBlocks * blockSize;
-
-            mInternalTotalSize = totalSize;
-            mInternalAvailSize = availSize;
-
-            sendInternalApproximateUpdate();
-        }
-
-        private void measureExactInternalStorage(IMediaContainerService imcs) {
-            Context context = mContext != null ? mContext.get() : null;
-            if (context == null) {
-                return;
-            }
-            // We have to get installd to measure the package sizes.
-            PackageManager pm = context.getPackageManager();
-            if (pm == null) {
-                return;
-            }
-            // measure sizes for all except "media_misc" - which is computed
-            for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) {
-                mMediaSizes[i] = 0;
-                String[] dirs = Constants.mMediaDirs.get(i).mDirPaths;
-                int len = dirs.length;
-                if (len > 0) {
-                    for (int k = 0; k < len; k++) {
-                        long dirSize = getSize(imcs, dirs[k]);
-                        mMediaSizes[i] += dirSize;
-                        if (LOGV) {
-                            Log.i(TAG, "size of " + dirs[k] + ": " + dirSize);
-                        }
-                    }
-                }
-            }
-
-            // compute the size of "misc"
-            mMediaSizes[Constants.MEDIA_MISC_INDEX] = mMediaSizes[Constants.MEDIA_INDEX];
-            for (int i = 1; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) {
-                mMediaSizes[Constants.MEDIA_MISC_INDEX] -= mMediaSizes[i];
-            }
-            if (LOGV) {
-                Log.i(TAG, "media_misc size: " + mMediaSizes[Constants.MEDIA_MISC_INDEX]);
-            }
-
-            // compute the sizes of each of the files/directories under 'misc' category
-            measureSizesOfMisc(imcs);
-
-            // compute apps sizes
-            final List<ApplicationInfo> apps = pm
-                    .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
-                            | PackageManager.GET_DISABLED_COMPONENTS);
-            if (apps != null) {
-                // initiate measurement of all package sizes. need new StatsObserver object.
-                mStatsObserver = new StatsObserver();
-                synchronized (mStatsObserver.mAppsList) {
-                    for (int i = 0; i < apps.size(); i++) {
-                        final ApplicationInfo info = apps.get(i);
-                        mStatsObserver.queuePackageMeasurementLocked(info.packageName);
-                    }
-
-                    requestQueuedMeasurementsLocked(pm);
-                }
-            }
-
-            // Sending of the message back to the MeasurementReceiver is
-            // completed in the PackageObserver
-        }
-        private void measureSizesOfMisc(IMediaContainerService imcs) {
-            File top = Environment.getExternalStorageDirectory();
-            mFileInfoForMisc = new ArrayList<FileInfo>();
-            File[] files = top.listFiles();
-            int len = files.length;
-            if (len == 0) {
-                return;
-            }
-            // get sizes of all top level nodes in /sdcard dir except the ones already computed...
-            long counter = 0;
-            for (int i = 0; i < len; i++) {
-                String path = files[i].getAbsolutePath();
-                if (Constants.ExclusionTargetsForMiscFiles.contains(path)) {
-                    continue;
-                }
-                if (files[i].isFile()) {
-                    mFileInfoForMisc.add(new FileInfo(path, files[i].length(), counter++));
-                } else if (files[i].isDirectory()) {
-                    long dirSize = getSize(imcs, path);
-                    mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++));
-                } else {
-                }
-            }
-            // sort the list of FileInfo objects collected above in descending order of their sizes
-            Collections.sort(mFileInfoForMisc);
-        }
-
-        private long getSize(IMediaContainerService imcs, String dir) {
-            try {
-                long size = imcs.calculateDirectorySize(dir);
-                return size;
-            } catch (Exception e) {
-                Log.w(TAG, "Could not read memory from default container service for " +
-                        dir, e);
-                return -1;
-            }
-        }
-
-        public void measureApproximateExternalStorage() {
-            File path = Environment.getExternalStorageDirectory();
-
-            StatFs stat = new StatFs(path.getPath());
-            long blockSize = stat.getBlockSize();
-            long totalBlocks = stat.getBlockCount();
-            long availableBlocks = stat.getAvailableBlocks();
-
-            mExternalTotalSize = totalBlocks * blockSize;
-            mExternalAvailSize = availableBlocks * blockSize;
-
-            sendExternalApproximateUpdate();
-        }
-    }
-
-    public void invalidate() {
-        mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
-    }
-
-    boolean isSizeOfMiscCategoryNonZero() {
-        return mFileInfoForMisc != null && mFileInfoForMisc.size() > 0;
-    }
-
-    static class FileInfo implements Comparable<FileInfo> {
-        String mFileName;
-        long mSize;
-        long mId;
-        FileInfo(String fileName, long size, long id) {
-            mFileName = fileName;
-            mSize = size;
-            mId = id;
-        }
-        @Override
-        public int compareTo(FileInfo that) {
-            if (this == that || mSize == that.mSize) return 0;
-            else if (mSize < that.mSize) return 1; // for descending sort
-            else return -1;
-        }
-        @Override
-        public String toString() {
-            return mFileName  + " : " + mSize + ", id:" + mId;
-        }
-    }
-}
index 15951c9..eae6861 100644 (file)
 
 package com.android.settings.deviceinfo;
 
-import com.android.settings.R;
-import com.android.settings.deviceinfo.MemoryMeasurement.FileInfo;
-
+import android.app.Activity;
 import android.app.ListActivity;
 import android.content.Context;
 import android.os.Bundle;
+import android.os.storage.StorageVolume;
 import android.text.format.Formatter;
 import android.util.Log;
 import android.util.SparseBooleanArray;
@@ -39,6 +38,9 @@ import android.widget.CompoundButton;
 import android.widget.CompoundButton.OnCheckedChangeListener;
 import android.widget.ListView;
 
+import com.android.settings.R;
+import com.android.settings.deviceinfo.StorageMeasurement.FileInfo;
+
 import java.io.File;
 import java.util.ArrayList;
 import java.util.List;
@@ -107,7 +109,7 @@ public class MiscFilesHandler extends ListActivity {
                             //item not selected
                             continue;
                         }
-                        if (MemoryMeasurement.LOGV) {
+                        if (StorageMeasurement.LOGV) {
                             Log.i(TAG, "deleting: " + mAdapter.getItem(i));
                         }
                         // delete the file
@@ -156,6 +158,7 @@ public class MiscFilesHandler extends ListActivity {
         }
 
         public void onDestroyActionMode(ActionMode mode) {
+            // This block intentionally left blank
         }
 
         public void onItemCheckedStateChanged(ActionMode mode, int position, long id,
@@ -181,17 +184,21 @@ public class MiscFilesHandler extends ListActivity {
         }
     }
 
-    public class MemoryMearurementAdapter extends BaseAdapter {
-        private ArrayList<MemoryMeasurement.FileInfo> mData = null;
+    class MemoryMearurementAdapter extends BaseAdapter {
+        private ArrayList<StorageMeasurement.FileInfo> mData = null;
         private long mDataSize = 0;
         private Context mContext;
 
-        public MemoryMearurementAdapter(Context context) {
-            mContext = context;
-            MemoryMeasurement mMeasurement = MemoryMeasurement.getInstance(context);
-            mData = (ArrayList<MemoryMeasurement.FileInfo>)mMeasurement.mFileInfoForMisc;
+        public MemoryMearurementAdapter(Activity activity) {
+            mContext = activity;
+            final Bundle extras = activity.getIntent().getExtras();
+            final StorageVolume storageVolume = extras.getParcelable(
+                    StorageVolumePreferenceCategory.STORAGE_VOLUME);
+            StorageMeasurement mMeasurement = 
+                StorageMeasurement.getInstance(activity, storageVolume, false);
+            mData = (ArrayList<StorageMeasurement.FileInfo>) mMeasurement.mFileInfoForMisc;
             if (mData != null) {
-                for (MemoryMeasurement.FileInfo info : mData) {
+                for (StorageMeasurement.FileInfo info : mData) {
                     mDataSize += info.mSize;
                 }
             }
@@ -203,7 +210,7 @@ public class MiscFilesHandler extends ListActivity {
         }
 
         @Override
-        public MemoryMeasurement.FileInfo getItem(int position) {
+        public StorageMeasurement.FileInfo getItem(int position) {
             if (mData == null || mData.size() <= position) {
                 return null;
             }
@@ -217,13 +224,14 @@ public class MiscFilesHandler extends ListActivity {
             }
             return mData.get(position).mId;
         }
+
         public void removeAll(List<Object> objs) {
             if (mData == null) {
                 return;
             }
             for (Object o : objs) {
                 mData.remove(o);
-                mDataSize -= ((MemoryMeasurement.FileInfo) o).mSize;
+                mDataSize -= ((StorageMeasurement.FileInfo) o).mSize;
             }
         }
 
index 7d02301..0c71c12 100644 (file)
@@ -99,6 +99,7 @@ public class PercentageBarChart extends View {
      * Sets the background for this chart. Callers are responsible for later
      * calling {@link #invalidate()}.
      */
+    @Override
     public void setBackgroundColor(int color) {
         mEmptyPaint.setColor(color);
     }
diff --git a/src/com/android/settings/deviceinfo/StorageMeasurement.java b/src/com/android/settings/deviceinfo/StorageMeasurement.java
new file mode 100644 (file)
index 0000000..14b5108
--- /dev/null
@@ -0,0 +1,527 @@
+/*
+ * 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.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.StatFs;
+import android.os.storage.StorageVolume;
+import android.util.Log;
+
+import com.android.internal.app.IMediaContainerService;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.concurrent.ConcurrentHashMap;
+
+/**
+ * Measure the memory for various systems.
+ *
+ * TODO: This class should ideally have less knowledge about what the context
+ * it's measuring is. In the future, reduce the amount of stuff it needs to
+ * know about by just keeping an array of measurement types of the following
+ * properties:
+ *
+ *   Filesystem stats (using StatFs)
+ *   Directory measurements (using DefaultContainerService.measureDir)
+ *   Application measurements (using PackageManager)
+ *
+ * Then the calling application would just specify the type and an argument.
+ * This class would keep track of it while the calling application would
+ * decide on how to use it.
+ */
+public class StorageMeasurement {
+    private static final String TAG = "StorageMeasurement";
+
+    private static final boolean LOCAL_LOGV = true;
+    static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
+
+    public static final String TOTAL_SIZE = "total_size";
+
+    public static final String AVAIL_SIZE = "avail_size";
+
+    public static final String APPS_USED = "apps_used";
+
+    public static final String DOWNLOADS_SIZE = "downloads_size";
+
+    public static final String MISC_SIZE = "misc_size";
+
+    public static final String MEDIA_SIZES = "media_sizes";
+
+    private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
+
+    private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
+            DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
+
+    private final MeasurementHandler mHandler;
+
+    private static Map<StorageVolume, StorageMeasurement> sInstances =
+        new ConcurrentHashMap<StorageVolume, StorageMeasurement>();
+
+    private volatile WeakReference<MeasurementReceiver> mReceiver;
+
+    private long mTotalSize;
+    private long mAvailSize;
+    private long mAppsSize;
+    private long mDownloadsSize;
+    private long mMiscSize;
+    private long[] mMediaSizes = new long[StorageVolumePreferenceCategory.sMediaCategories.length];
+
+    final private StorageVolume mStorageVolume;
+    final private boolean mIsPrimary;
+
+    List<FileInfo> mFileInfoForMisc;
+
+    public interface MeasurementReceiver {
+        public void updateApproximate(Bundle bundle);
+        public void updateExact(Bundle bundle);
+    }
+
+    private StorageMeasurement(Context context, StorageVolume storageVolume, boolean isPrimary) {
+        mStorageVolume = storageVolume;
+        mIsPrimary = isPrimary;
+
+        // Start the thread that will measure the disk usage.
+        final HandlerThread handlerThread = new HandlerThread("MemoryMeasurement");
+        handlerThread.start();
+        mHandler = new MeasurementHandler(context, handlerThread.getLooper());
+    }
+
+    /**
+     * Get the singleton of the StorageMeasurement class. The application
+     * context is used to avoid leaking activities.
+     * @param storageVolume The {@link StorageVolume} that will be measured
+     * @param isPrimary true when this storage volume is the primary volume
+     */
+    public static StorageMeasurement getInstance(Context context, StorageVolume storageVolume,
+            boolean isPrimary) {
+        if (sInstances.containsKey(storageVolume)) {
+            return sInstances.get(storageVolume);
+        } else {
+            StorageMeasurement storageMeasurement =
+                new StorageMeasurement(context.getApplicationContext(), storageVolume, isPrimary);
+            sInstances.put(storageVolume, storageMeasurement);
+            return storageMeasurement;
+        }
+    }
+
+    public void setReceiver(MeasurementReceiver receiver) {
+        if (mReceiver == null || mReceiver.get() == null) {
+            mReceiver = new WeakReference<MeasurementReceiver>(receiver);
+        }
+    }
+
+    public void measure() {
+        if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) {
+            mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE);
+        }
+    }
+
+    public void cleanUp() {
+        mReceiver = null;
+        mHandler.removeMessages(MeasurementHandler.MSG_MEASURE);
+        mHandler.sendEmptyMessage(MeasurementHandler.MSG_DISCONNECT);
+    }
+
+    public void invalidate() {
+        mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
+    }
+
+    private void sendInternalApproximateUpdate() {
+        MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
+        if (receiver == null) {
+            return;
+        }
+
+        Bundle bundle = new Bundle();
+        bundle.putLong(TOTAL_SIZE, mTotalSize);
+        bundle.putLong(AVAIL_SIZE, mAvailSize);
+
+        receiver.updateApproximate(bundle);
+    }
+
+    private void sendExactUpdate() {
+        MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
+        if (receiver == null) {
+            if (LOGV) {
+                Log.i(TAG, "measurements dropped because receiver is null! wasted effort");
+            }
+            return;
+        }
+
+        Bundle bundle = new Bundle();
+        bundle.putLong(TOTAL_SIZE, mTotalSize);
+        bundle.putLong(AVAIL_SIZE, mAvailSize);
+        bundle.putLong(APPS_USED, mAppsSize);
+        bundle.putLong(DOWNLOADS_SIZE, mDownloadsSize);
+        bundle.putLong(MISC_SIZE, mMiscSize);
+        bundle.putLongArray(MEDIA_SIZES, mMediaSizes);
+
+        receiver.updateExact(bundle);
+    }
+
+    private class MeasurementHandler extends Handler {
+        public static final int MSG_MEASURE = 1;
+
+        public static final int MSG_CONNECTED = 2;
+
+        public static final int MSG_DISCONNECT = 3;
+
+        public static final int MSG_COMPLETED = 4;
+
+        public static final int MSG_INVALIDATE = 5;
+
+        private Object mLock = new Object();
+
+        private IMediaContainerService mDefaultContainer;
+
+        private volatile boolean mBound = false;
+
+        private volatile boolean mMeasured = false;
+
+        private StatsObserver mStatsObserver;
+
+        private final WeakReference<Context> mContext;
+
+        final private ServiceConnection mDefContainerConn = new ServiceConnection() {
+            public void onServiceConnected(ComponentName name, IBinder service) {
+                final IMediaContainerService imcs = IMediaContainerService.Stub
+                .asInterface(service);
+                mDefaultContainer = imcs;
+                mBound = true;
+                sendMessage(obtainMessage(MSG_CONNECTED, imcs));
+            }
+
+            public void onServiceDisconnected(ComponentName name) {
+                mBound = false;
+                removeMessages(MSG_CONNECTED);
+            }
+        };
+
+        public MeasurementHandler(Context context, Looper looper) {
+            super(looper);
+            mContext = new WeakReference<Context>(context);
+        }
+
+        @Override
+        public void handleMessage(Message msg) {
+            switch (msg.what) {
+                case MSG_MEASURE: {
+                    if (mMeasured) {
+                        sendExactUpdate();
+                        break;
+                    }
+
+                    final Context context = (mContext != null) ? mContext.get() : null;
+                    if (context == null) {
+                        return;
+                    }
+
+                    measureApproximateStorage();
+
+                    synchronized (mLock) {
+                        if (mBound) {
+                            removeMessages(MSG_DISCONNECT);
+                            sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
+                        } else {
+                            Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+                            context.bindService(service, mDefContainerConn,
+                                    Context.BIND_AUTO_CREATE);
+                        }
+                    }
+                    break;
+                }
+                case MSG_CONNECTED: {
+                    IMediaContainerService imcs = (IMediaContainerService) msg.obj;
+                    measureExactStorage(imcs);
+                    break;
+                }
+                case MSG_DISCONNECT: {
+                    synchronized (mLock) {
+                        if (mBound) {
+                            final Context context = (mContext != null) ? mContext.get() : null;
+                            if (context == null) {
+                                return;
+                            }
+
+                            mBound = false;
+                            context.unbindService(mDefContainerConn);
+                        }
+                    }
+                    break;
+                }
+                case MSG_COMPLETED: {
+                    mMeasured = true;
+                    sendExactUpdate();
+                    break;
+                }
+                case MSG_INVALIDATE: {
+                    mMeasured = false;
+                    break;
+                }
+            }
+        }
+
+        /**
+         * Request measurement of each package.
+         *
+         * @param pm PackageManager instance to query
+         */
+        public void requestQueuedMeasurementsLocked(PackageManager pm) {
+            final String[] appsList = mStatsObserver.getAppsList();
+            final int N = appsList.length;
+            for (int i = 0; i < N; i++) {
+                pm.getPackageSizeInfo(appsList[i], mStatsObserver);
+            }
+        }
+
+        private class StatsObserver extends IPackageStatsObserver.Stub {
+            private long mAppsSizeForThisStatsObserver = 0;
+            private final List<String> mAppsList = new ArrayList<String>();
+
+            public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
+                if (!mStatsObserver.equals(this)) {
+                    // this callback's class object is no longer in use. ignore this callback.
+                    return;
+                }
+
+                if (succeeded) {
+                    mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
+                    stats.externalCacheSize + stats.externalDataSize +
+                    stats.externalMediaSize + stats.externalObbSize;
+                }
+
+                synchronized (mAppsList) {
+                    mAppsList.remove(stats.packageName);
+                    if (mAppsList.size() > 0) return;
+                }
+
+                mAppsSize = mAppsSizeForThisStatsObserver;
+                onInternalMeasurementComplete();
+            }
+
+            public void queuePackageMeasurementLocked(String packageName) {
+                synchronized (mAppsList) {
+                    mAppsList.add(packageName);
+                }
+            }
+
+            public String[] getAppsList() {
+                synchronized (mAppsList) {
+                    return mAppsList.toArray(new String[mAppsList.size()]);
+                }
+            }
+        }
+
+        private void onInternalMeasurementComplete() {
+            sendEmptyMessage(MSG_COMPLETED);
+        }
+
+        private void measureApproximateStorage() {
+            final StatFs stat = new StatFs(mStorageVolume.getPath());
+            final long blockSize = stat.getBlockSize();
+            final long totalBlocks = stat.getBlockCount();
+            final long availableBlocks = stat.getAvailableBlocks();
+
+            mTotalSize = totalBlocks * blockSize;
+            mAvailSize = availableBlocks * blockSize;
+
+            sendInternalApproximateUpdate();
+        }
+
+        private void measureExactStorage(IMediaContainerService imcs) {
+            Context context = mContext != null ? mContext.get() : null;
+            if (context == null) {
+                return;
+            }
+
+            // Media
+            for (int i = 0; i < StorageVolumePreferenceCategory.sMediaCategories.length; i++) {
+                if (mIsPrimary) {
+                    String[] dirs = StorageVolumePreferenceCategory.sMediaCategories[i].mDirPaths;
+                    final int length = dirs.length;
+                    mMediaSizes[i] = 0;
+                    for (int d = 0; d < length; d++) {
+                        final String path = dirs[d];
+                        mMediaSizes[i] += getDirectorySize(imcs, path);
+                    }
+                } else {
+                    // TODO Compute sizes using the MediaStore
+                    mMediaSizes[i] = 0;
+                }
+            }
+
+            /* Compute sizes using the media provider
+            // Media sizes are measured by the MediaStore. Query database.
+            ContentResolver contentResolver = context.getContentResolver();
+            // TODO "external" as a static String from MediaStore?
+            Uri audioUri = MediaStore.Files.getContentUri("external");
+            final String[] projection =
+                new String[] { "sum(" + MediaStore.Files.FileColumns.SIZE + ")" };
+            final String selection =
+                MediaStore.Files.FileColumns.STORAGE_ID + "=" +
+                Integer.toString(mStorageVolume.getStorageId()) + " AND " +
+                MediaStore.Files.FileColumns.MEDIA_TYPE + "=?";
+
+            for (int i = 0; i < StorageVolumePreferenceCategory.sMediaCategories.length; i++) {
+                mMediaSizes[i] = 0;
+                int mediaType = StorageVolumePreferenceCategory.sMediaCategories[i].mediaType;
+                Cursor c = null;
+                try {
+                    c = contentResolver.query(audioUri, projection, selection,
+                            new String[] { Integer.toString(mediaType) } , null);
+
+                    if (c != null && c.moveToNext()) {
+                        long size = c.getLong(0);
+                        mMediaSizes[i] = size;
+                    }
+                } finally {
+                    if (c != null) c.close();
+                }
+            }
+             */
+
+            // Downloads (primary volume only)
+            if (mIsPrimary) {
+                final String downloadsPath = Environment.getExternalStoragePublicDirectory(
+                        Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
+                mDownloadsSize = getDirectorySize(imcs, downloadsPath);
+            } else {
+                mDownloadsSize = 0;
+            }
+
+            // Misc
+            mMiscSize = 0;
+            if (mIsPrimary) {
+                measureSizesOfMisc(imcs);
+            }
+
+            // Apps
+            // We have to get installd to measure the package sizes.
+            PackageManager pm = context.getPackageManager();
+            if (pm == null) {
+                return;
+            }
+            final List<ApplicationInfo> apps;
+            if (mIsPrimary) {
+                apps = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES |
+                        PackageManager.GET_DISABLED_COMPONENTS);
+            } else {
+                // TODO also measure apps installed on the SD card
+                apps = Collections.emptyList();
+            }
+
+            if (apps != null && apps.size() > 0) {
+                // initiate measurement of all package sizes. need new StatsObserver object.
+                mStatsObserver = new StatsObserver();
+                synchronized (mStatsObserver.mAppsList) {
+                    for (int i = 0; i < apps.size(); i++) {
+                        final ApplicationInfo info = apps.get(i);
+                        mStatsObserver.queuePackageMeasurementLocked(info.packageName);
+                    }
+                }
+
+                requestQueuedMeasurementsLocked(pm);
+                // Sending of the message back to the MeasurementReceiver is
+                // completed in the PackageObserver
+            } else {
+                onInternalMeasurementComplete();
+            }
+        }
+    }
+
+    private long getDirectorySize(IMediaContainerService imcs, String dir) {
+        try {
+            return imcs.calculateDirectorySize(dir);
+        } catch (Exception e) {
+            Log.w(TAG, "Could not read memory from default container service for " + dir, e);
+            return 0;
+        }
+    }
+
+    long getMiscSize() {
+        return mMiscSize;
+    }
+
+    private void measureSizesOfMisc(IMediaContainerService imcs) {
+        File top = new File(mStorageVolume.getPath());
+        mFileInfoForMisc = new ArrayList<FileInfo>();
+        File[] files = top.listFiles();
+        final int len = files.length;
+        // Get sizes of all top level nodes except the ones already computed...
+        long counter = 0;
+        for (int i = 0; i < len; i++) {
+            String path = files[i].getAbsolutePath();
+            if (StorageVolumePreferenceCategory.sPathsExcludedForMisc.contains(path)) {
+                continue;
+            }
+            if (files[i].isFile()) {
+                final long fileSize = files[i].length();
+                mFileInfoForMisc.add(new FileInfo(path, fileSize, counter++));
+                mMiscSize += fileSize;
+            } else if (files[i].isDirectory()) {
+                final long dirSize = getDirectorySize(imcs, path);
+                mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++));
+                mMiscSize += dirSize;
+            } else {
+                // Non directory, non file: not listed
+            }
+        }
+        // sort the list of FileInfo objects collected above in descending order of their sizes
+        Collections.sort(mFileInfoForMisc);
+    }
+
+    static class FileInfo implements Comparable<FileInfo> {
+        final String mFileName;
+        final long mSize;
+        final long mId;
+
+        FileInfo(String fileName, long size, long id) {
+            mFileName = fileName;
+            mSize = size;
+            mId = id;
+        }
+
+        @Override
+        public int compareTo(FileInfo that) {
+            if (this == that || mSize == that.mSize) return 0;
+            else return (mSize < that.mSize) ? 1 : -1; // for descending sort
+        }
+
+        @Override
+        public String toString() {
+            return mFileName  + " : " + mSize + ", id:" + mId;
+        }
+    }
+}
diff --git a/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java b/src/com/android/settings/deviceinfo/StorageVolumePreferenceCategory.java
new file mode 100644 (file)
index 0000000..7b52bcb
--- /dev/null
@@ -0,0 +1,417 @@
+/*
+ * 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.DownloadManager;
+import android.content.Context;
+import android.content.Intent;
+import android.content.res.Resources;
+import android.graphics.drawable.ShapeDrawable;
+import android.graphics.drawable.shapes.RectShape;
+import android.os.Bundle;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.Message;
+import android.os.ServiceManager;
+import android.os.storage.IMountService;
+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 int[] mColors;
+
+    private Resources mResources;
+
+    private StorageVolume mStorageVolume;
+
+    private StorageMeasurement mMeasurement;
+
+    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;
+
+    // Key for the extra StorageVolume bundle added to the Misc intent.
+    static final String STORAGE_VOLUME = "storage_volume";
+
+    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, boolean isPrimary) {
+        super(context);
+        mResources = resources;
+        mStorageVolume = storageVolume;
+        setTitle(storageVolume.getDescription());
+        mMeasurement = StorageMeasurement.getInstance(context, storageVolume, isPrimary);
+        mMeasurement.setReceiver(this);
+    }
+
+    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);
+
+        mFormatPreference = new Preference(getContext());
+        mFormatPreference.setTitle(R.string.sd_format);
+        mFormatPreference.setSummary(R.string.sd_format_summary);
+    }
+
+    public String getMountPoint() {
+        return mStorageVolume.getPath();
+    }
+
+    public String getStorageVolumeState() {
+        try {
+            IMountService mountService =
+                IMountService.Stub.asInterface(ServiceManager.getService("mount"));
+            return mountService.getVolumeState(getMountPoint());
+        } catch (Exception rex) {
+            return Environment.MEDIA_REMOVED;
+        }
+    }
+
+    /**
+     * 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);
+        removePreference(mFormatPreference);
+
+        addPreference(mUsageBarPreference);
+        for (int i = 0; i < numberOfCategories; i++) {
+            addPreference(mPreferences[i]);
+        }
+        addPreference(mMountTogglePreference);
+        addPreference(mFormatPreference);
+
+        mMountTogglePreference.setEnabled(true);
+    }
+
+    private void updatePreferencesFromState() {
+        resetPreferences();
+
+        String state = getStorageVolumeState();
+
+        String readOnly = "";
+        if (state.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
+            state = Environment.MEDIA_MOUNTED;
+            readOnly = mResources.getString(R.string.read_only);
+            removePreference(mFormatPreference);
+        }
+
+        if (mStorageVolume.isEmulated()) {
+            removePreference(mFormatPreference);
+        }
+
+        if (!mStorageVolume.isRemovable() && !state.equals(Environment.MEDIA_UNMOUNTED)) {
+            // This device has built-in storage that is not removable.
+            // There is no reason for the user to unmount it.
+            removePreference(mMountTogglePreference);
+        }
+
+        if (state.equals(Environment.MEDIA_MOUNTED)) {
+            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 (state.equals(Environment.MEDIA_UNMOUNTED) || state.equals(Environment.MEDIA_NOFS)
+                    || state.equals(Environment.MEDIA_UNMOUNTABLE)) {
+                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]);
+            removePreference(mFormatPreference);
+        }
+    }
+
+    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));
+
+        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 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);
+        } 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_GET_CONTENT);
+            intent.setType("image/jpeg"); // TODO Create a Videos category, type = video/*
+        } else if (preference == mPreferences[MISC]) {
+            Context context = getContext().getApplicationContext();
+            if (mMeasurement.getMiscSize() > 0) {
+                intent = new Intent(context, MiscFilesHandler.class);
+                intent.putExtra(STORAGE_VOLUME, mStorageVolume);
+            }
+        }
+
+        return intent;
+    }
+}