diff --git a/res/layout/preference_memoryusage.xml b/res/layout/preference_memoryusage.xml
new file mode 100644
index 00000000000..7972de9d41a
--- /dev/null
+++ b/res/layout/preference_memoryusage.xml
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/res/values/colors.xml b/res/values/colors.xml
index 193388c8aea..8a2b68c30bf 100644
--- a/res/values/colors.xml
+++ b/res/values/colors.xml
@@ -17,5 +17,10 @@
#000
#F00
+
+ #333
+ #F33
+ #3F3
+ #FFF
diff --git a/res/values/strings.xml b/res/values/strings.xml
index 7c09c711b3d..4c62fe49e17 100644
--- a/res/values/strings.xml
+++ b/res/values/strings.xml
@@ -1456,6 +1456,12 @@
Available space
Total space
+
+ Calculating…
+
+ Application usage
+
+ Media usage
Unmount shared storage
diff --git a/res/xml/device_info_memory.xml b/res/xml/device_info_memory.xml
index 56fe0876920..0c05213ad84 100644
--- a/res/xml/device_info_memory.xml
+++ b/res/xml/device_info_memory.xml
@@ -36,8 +36,21 @@
-
+
+
+
+
mPendingApps = new ArrayList();
+
+ private volatile boolean mBound = false;
+
+ private long mAppsSize = 0;
+
+ final private ServiceConnection mDefContainerConn = new ServiceConnection() {
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ mBound = true;
+ IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
+ mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
+ MemoryMeasurementHandler.MSG_CONNECTED, imcs));
+ }
+
+ public void onServiceDisconnected(ComponentName name) {
+ mBound = false;
+ }
+ };
+
+ MemoryMeasurementHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_MEASURE_ALL: {
+ updateExternalStorage();
+ updateApproximateInternalStorage();
+
+ Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+ getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
+
+ mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
+ break;
+ }
+ case MSG_CONNECTED: {
+ IMediaContainerService imcs = (IMediaContainerService) msg.obj;
+ updateExactInternalStorage(imcs);
+ }
+ }
+ }
+
+ public void cleanUp() {
+ if (mBound) {
+ getActivity().unbindService(mDefContainerConn);
+ }
+ }
+
+ public void queuePackageMeasurementLocked(String packageName) {
+ mPendingApps.add(packageName);
+ }
+
+ public void requestQueuedMeasurementsLocked() {
+ final Activity activity = getActivity();
+ if (activity == null) {
+ return;
+ }
+
+ final PackageManager pm = activity.getPackageManager();
+ if (pm == null) {
+ return;
+ }
+
+ final int N = mPendingApps.size();
+ for (int i = 0; i < N; i++) {
+ pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
+ }
+ }
+
+ final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
+ public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
+ if (succeeded) {
+ mAppsSize += stats.codeSize + stats.dataSize;
+ }
+
+ synchronized (mPendingApps) {
+ mPendingApps.remove(stats.packageName);
+
+ if (mPendingApps.size() == 0) {
+ mInternalAppsSize = mAppsSize;
+
+ mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
+ }
+ }
+ }
+ };
+
+ private void updateApproximateInternalStorage() {
+ 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;
+ mInternalSize.setSummary(formatSize(totalSize));
+ mInternalAvail.setSummary(formatSize(availSize));
+
+ mInternalTotalSize = totalSize;
+ mInternalUsedSize = totalSize - availSize;
+ }
+
+ private void updateExactInternalStorage(IMediaContainerService imcs) {
+ long mediaSize;
+ try {
+ // TODO get these directories from somewhere
+ mediaSize = imcs.calculateDirectorySize("/data/media");
+ } catch (Exception e) {
+ Log.i(TAG, "Could not read memory from default container service");
+ return;
+ }
+
+ mInternalMediaSize = mediaSize;
+
+ // We have to get installd to measure the package sizes.
+ PackageManager pm = getPackageManager();
+ List apps = pm
+ .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+ if (apps != null) {
+ synchronized (mPendingApps) {
+ for (int i = 0; i < apps.size(); i++) {
+ final ApplicationInfo info = apps.get(i);
+ queuePackageMeasurementLocked(info.packageName);
+ }
+
+ requestQueuedMeasurementsLocked();
+ }
+ }
+ }
+
+ private void updateExternalStorage() {
+ if (Environment.isExternalStorageEmulated()) {
+ return;
+ }
+
+ 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 {
+ File path = Environment.getExternalStorageDirectory();
+ StatFs stat = new StatFs(path.getPath());
+ long blockSize = stat.getBlockSize();
+ long totalBlocks = stat.getBlockCount();
+ long availableBlocks = stat.getAvailableBlocks();
+
+ mSdSize.setSummary(formatSize(totalBlocks * blockSize));
+ mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + 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 MemoryMeasurementHandler mMeasurementHandler;
+
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
@@ -99,7 +377,7 @@ 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);
@@ -107,6 +385,12 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
mSdFormat = findPreference(MEMORY_SD_FORMAT);
mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
+ if (Environment.isExternalStorageEmulated()) {
+ mSdMountPreferenceGroup.removePreference(mSdSize);
+ mSdMountPreferenceGroup.removePreference(mSdAvail);
+ mSdMountPreferenceGroup.removePreference(mSdMountToggle);
+ }
+
mPtpModeToggle = (CheckBoxPreference)findPreference(PTP_MODE_TOGGLE);
if (Usb.isFunctionSupported(Usb.USB_FUNCTION_MTP)) {
mPtpModeToggle.setChecked(Settings.System.getInt(
@@ -116,35 +400,53 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
// hide the PTP mode toggle checkbox if MTP is not supported
getPreferenceScreen().removePreference(mPtpModeToggle);
}
+
+ mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
+ mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
+ mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
+ mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
+
+ mInternalMediaColor = mRes.getColor(R.color.memory_media_usage);
+ mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
+ mInternalUsedColor = mRes.getColor(R.color.memory_used);
+
+ mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
+
+ // Start the thread that will measure the disk usage.
+ final HandlerThread t = new HandlerThread("MeasurementHandler");
+ t.start();
+ mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
}
-
+
@Override
public void onResume() {
super.onResume();
-
+
IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
intentFilter.addDataScheme("file");
getActivity().registerReceiver(mReceiver, intentFilter);
- updateMemoryStatus();
+ if (!mMeasured) {
+ mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
+ }
}
StorageEventListener mStorageListener = new StorageEventListener() {
-
@Override
public void onStorageStateChanged(String path, String oldState, String newState) {
Log.i(TAG, "Received storage state changed notification that " +
path + " changed state from " + oldState +
" to " + newState);
- updateMemoryStatus();
+ mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
}
};
-
+
@Override
public void onPause() {
super.onPause();
getActivity().unregisterReceiver(mReceiver);
+ mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL);
}
@Override
@@ -152,6 +454,7 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
if (mStorageManager != null && mStorageListener != null) {
mStorageManager.unregisterListener(mStorageListener);
}
+ mMeasurementHandler.cleanUp();
super.onDestroy();
}
@@ -195,7 +498,7 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
- updateMemoryStatus();
+ mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
}
};
@@ -290,77 +593,35 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
}
}
- private void updateMemoryStatus() {
- 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);
+ private void updateUiExact() {
+ final float totalSize = mInternalTotalSize;
+
+ final long mediaSize = mInternalMediaSize;
+ final long appsSize = mInternalAppsSize;
+
+ mInternalUsageChart.clear();
+ mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor);
+ mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor);
+
+ // There are other things that can take up storage, but we didn't
+ // measure it.
+ final long remaining = mInternalUsedSize - (mediaSize + appsSize);
+ if (remaining > 0) {
+ mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor);
}
-
- 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 {
- File path = Environment.getExternalStorageDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long totalBlocks = stat.getBlockCount();
- long availableBlocks = stat.getAvailableBlocks();
-
- mSdSize.setSummary(formatSize(totalBlocks * blockSize));
- mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
+ mInternalUsageChart.commit();
- 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));
- }
- }
-
- File path = Environment.getDataDirectory();
- StatFs stat = new StatFs(path.getPath());
- long blockSize = stat.getBlockSize();
- long availableBlocks = stat.getAvailableBlocks();
- findPreference("memory_internal_avail").setSummary(formatSize(availableBlocks * blockSize));
+ mInternalMediaUsage.setSummary(formatSize(mediaSize));
+ mInternalAppsUsage.setSummary(formatSize(appsSize));
}
-
+
+ private void updateUiApproximate() {
+ mInternalUsageChart.clear();
+ mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources()
+ .getColor(R.color.memory_used));
+ mInternalUsageChart.commit();
+ }
+
private String formatSize(long size) {
return Formatter.formatFileSize(getActivity(), size);
}
@@ -369,5 +630,4 @@ public class Memory extends SettingsPreferenceFragment implements OnCancelListen
// TODO: Is this really required?
// finish();
}
-
}
diff --git a/src/com/android/settings/deviceinfo/PercentageBarChart.java b/src/com/android/settings/deviceinfo/PercentageBarChart.java
new file mode 100644
index 00000000000..e8fb62adf68
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/PercentageBarChart.java
@@ -0,0 +1,109 @@
+/*
+ * 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.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.Collection;
+
+/**
+ *
+ */
+public class PercentageBarChart extends View {
+ private final Paint mBackgroundPaint = new Paint();
+
+ private Collection mEntries;
+
+ public static class Entry {
+ public final float percentage;
+ public final Paint paint;
+
+ protected Entry(float percentage, Paint paint) {
+ this.percentage = percentage;
+ this.paint = paint;
+ }
+ }
+
+ public PercentageBarChart(Context context, AttributeSet attrs) {
+ super(context, attrs);
+
+ mBackgroundPaint.setARGB(255, 64, 64, 64);
+ mBackgroundPaint.setStyle(Paint.Style.FILL);
+ }
+
+ @Override
+ protected void onDraw(Canvas canvas) {
+ super.onDraw(canvas);
+
+ final int width = getWidth();
+ final int height = getHeight();
+
+ canvas.drawPaint(mBackgroundPaint);
+
+ int lastX = 0;
+
+ if (mEntries != null) {
+ for (final Entry e : mEntries) {
+ final int entryWidth;
+ if (e.percentage == 0f) {
+ entryWidth = 0;
+ } else {
+ entryWidth = Math.max(1, (int) (width * e.percentage));
+ }
+
+ final int nextX = lastX + entryWidth;
+ if (nextX >= width) {
+ break;
+ }
+
+ canvas.drawRect(lastX, 0, nextX, height, e.paint);
+ lastX = nextX;
+ }
+ }
+ }
+
+ /**
+ * Sets the background for this chart. Callers are responsible for later
+ * calling {@link #invalidate()}.
+ */
+ public void setBackgroundColor(int color) {
+ mBackgroundPaint.setColor(color);
+ }
+
+ /**
+ * Adds a new slice to the percentage bar chart. Callers are responsible for
+ * later calling {@link #invalidate()}.
+ *
+ * @param percentage the total width that
+ * @param color the color to draw the entry
+ */
+ public static Entry createEntry(float percentage, int color) {
+ final Paint p = new Paint();
+ p.setColor(color);
+ p.setStyle(Paint.Style.FILL);
+
+ return new Entry(percentage, p);
+ }
+
+ public void setEntries(Collection entries) {
+ mEntries = entries;
+ }
+}
diff --git a/src/com/android/settings/deviceinfo/UsageBarPreference.java b/src/com/android/settings/deviceinfo/UsageBarPreference.java
new file mode 100644
index 00000000000..e9909f19a4e
--- /dev/null
+++ b/src/com/android/settings/deviceinfo/UsageBarPreference.java
@@ -0,0 +1,74 @@
+/*
+ * 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 com.android.settings.R;
+
+import android.content.Context;
+import android.preference.Preference;
+import android.util.AttributeSet;
+import android.view.View;
+
+import java.util.ArrayList;
+import java.util.Collection;
+
+/**
+ * Creates a percentage bar chart inside a preference.
+ */
+public class UsageBarPreference extends Preference {
+ private PercentageBarChart mChart = null;
+
+ private final Collection mEntries = new ArrayList();
+
+ public UsageBarPreference(Context context, AttributeSet attrs, int defStyle) {
+ super(context, attrs, defStyle);
+ setWidgetLayoutResource(R.layout.preference_memoryusage);
+ }
+
+ public UsageBarPreference(Context context) {
+ super(context);
+ setWidgetLayoutResource(R.layout.preference_memoryusage);
+ }
+
+ public UsageBarPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.preference_memoryusage);
+ }
+
+ public void addEntry(float percentage, int color) {
+ mEntries.add(PercentageBarChart.createEntry(percentage, color));
+ }
+
+ @Override
+ protected void onBindView(View view) {
+ super.onBindView(view);
+
+ mChart = (PercentageBarChart) view.findViewById(R.id.percentage_bar_chart);
+
+ mChart.setEntries(mEntries);
+ }
+
+ public void commit() {
+ if (mChart != null) {
+ mChart.invalidate();
+ }
+ }
+
+ public void clear() {
+ mEntries.clear();
+ }
+}