2 * Copyright (C) 2008 The Android Open Source Project
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
8 * http://www.apache.org/licenses/LICENSE-2.0
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
17 package com.android.settings.deviceinfo;
19 import com.android.settings.R;
20 import com.android.settings.SettingsPreferenceFragment;
21 import com.android.settings.deviceinfo.MemoryMeasurement.MeasurementReceiver;
23 import android.app.ActivityManager;
24 import android.app.AlertDialog;
25 import android.app.Dialog;
26 import android.content.BroadcastReceiver;
27 import android.content.Context;
28 import android.content.DialogInterface;
29 import android.content.Intent;
30 import android.content.IntentFilter;
31 import android.content.DialogInterface.OnCancelListener;
32 import android.content.pm.ApplicationInfo;
33 import android.content.res.Resources;
34 import android.graphics.drawable.ShapeDrawable;
35 import android.graphics.drawable.shapes.RectShape;
36 import android.graphics.drawable.shapes.RoundRectShape;
37 import android.hardware.UsbManager;
38 import android.os.Bundle;
39 import android.os.Environment;
40 import android.os.Handler;
41 import android.os.IBinder;
42 import android.os.Message;
43 import android.os.RemoteException;
44 import android.os.ServiceManager;
45 import android.os.storage.IMountService;
46 import android.os.storage.StorageEventListener;
47 import android.os.storage.StorageManager;
48 import android.preference.Preference;
49 import android.preference.PreferenceGroup;
50 import android.preference.PreferenceScreen;
51 import android.text.format.Formatter;
52 import android.util.Log;
53 import android.widget.Toast;
55 import java.util.List;
57 public class Memory extends SettingsPreferenceFragment implements OnCancelListener,
59 private static final String TAG = "Memory";
60 private static final boolean localLOGV = false;
62 private static final String MEMORY_SD_SIZE = "memory_sd_size";
64 private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
66 private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
68 private static final String MEMORY_SD_FORMAT = "memory_sd_format";
70 private static final String MEMORY_SD_GROUP = "memory_sd";
72 private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
74 private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
76 private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
78 private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media";
80 private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
82 private static final int DLG_CONFIRM_UNMOUNT = 1;
83 private static final int DLG_ERROR_UNMOUNT = 2;
85 private Resources mRes;
87 // External storage preferences
88 private Preference mSdSize;
89 private Preference mSdAvail;
90 private Preference mSdMountToggle;
91 private Preference mSdFormat;
92 private PreferenceGroup mSdMountPreferenceGroup;
94 // Internal storage preferences
95 private Preference mInternalSize;
96 private Preference mInternalAvail;
97 private Preference mInternalMediaUsage;
98 private Preference mInternalAppsUsage;
99 private UsageBarPreference mInternalUsageChart;
101 // Internal storage chart colors
102 private int mInternalMediaColor;
103 private int mInternalAppsColor;
104 private int mInternalUsedColor;
106 boolean mSdMountToggleAdded = true;
108 // Access using getMountService()
109 private IMountService mMountService = null;
111 private StorageManager mStorageManager = null;
113 // Updates the memory usage bar graph.
114 private static final int MSG_UI_UPDATE_INTERNAL_APPROXIMATE = 1;
116 // Updates the memory usage bar graph.
117 private static final int MSG_UI_UPDATE_INTERNAL_EXACT = 2;
119 // Updates the memory usage stats for external.
120 private static final int MSG_UI_UPDATE_EXTERNAL_APPROXIMATE = 3;
122 private Handler mUpdateHandler = new Handler() {
124 public void handleMessage(Message msg) {
126 case MSG_UI_UPDATE_INTERNAL_APPROXIMATE: {
127 Bundle bundle = msg.getData();
128 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
129 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
130 updateUiApproximate(totalSize, availSize);
133 case MSG_UI_UPDATE_INTERNAL_EXACT: {
134 Bundle bundle = msg.getData();
135 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
136 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
137 final long mediaUsed = bundle.getLong(MemoryMeasurement.MEDIA_USED);
138 final long appsUsed = bundle.getLong(MemoryMeasurement.APPS_USED);
139 updateUiExact(totalSize, availSize, mediaUsed, appsUsed);
142 case MSG_UI_UPDATE_EXTERNAL_APPROXIMATE: {
143 Bundle bundle = msg.getData();
144 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
145 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
146 updateExternalStorage(totalSize, availSize);
153 private MemoryMeasurement mMeasurement;
156 public void onCreate(Bundle icicle) {
157 super.onCreate(icicle);
159 if (mStorageManager == null) {
160 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
161 mStorageManager.registerListener(mStorageListener);
164 addPreferencesFromResource(R.xml.device_info_memory);
166 mRes = getResources();
167 mSdSize = findPreference(MEMORY_SD_SIZE);
168 mSdAvail = findPreference(MEMORY_SD_AVAIL);
169 mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE);
170 mSdFormat = findPreference(MEMORY_SD_FORMAT);
171 mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
173 if (Environment.isExternalStorageEmulated()) {
174 mSdMountPreferenceGroup.removePreference(mSdSize);
175 mSdMountPreferenceGroup.removePreference(mSdAvail);
176 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
179 mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
180 mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
181 mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
182 mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
184 mInternalMediaColor = mRes.getColor(R.color.memory_media_usage);
185 mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
186 mInternalUsedColor = mRes.getColor(R.color.memory_used);
188 float[] radius = new float[] {
189 5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f
191 RoundRectShape shape1 = new RoundRectShape(radius, null, null);
193 ShapeDrawable mediaShape = new ShapeDrawable(shape1);
194 mediaShape.setIntrinsicWidth(32);
195 mediaShape.setIntrinsicHeight(32);
196 mediaShape.getPaint().setColor(mInternalMediaColor);
197 mInternalMediaUsage.setIcon(mediaShape);
199 ShapeDrawable appsShape = new ShapeDrawable(shape1);
200 appsShape.setIntrinsicWidth(32);
201 appsShape.setIntrinsicHeight(32);
202 appsShape.getPaint().setColor(mInternalAppsColor);
203 mInternalAppsUsage.setIcon(appsShape);
205 mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
207 mMeasurement = MemoryMeasurement.getInstance(getActivity());
208 mMeasurement.setReceiver(this);
212 public void onResume() {
215 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
216 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
217 intentFilter.addDataScheme("file");
218 getActivity().registerReceiver(mReceiver, intentFilter);
220 if (!Environment.isExternalStorageEmulated()) {
221 mMeasurement.measureExternal();
223 mMeasurement.measureInternal();
226 StorageEventListener mStorageListener = new StorageEventListener() {
228 public void onStorageStateChanged(String path, String oldState, String newState) {
229 Log.i(TAG, "Received storage state changed notification that " +
230 path + " changed state from " + oldState +
232 if (!Environment.isExternalStorageEmulated()) {
233 mMeasurement.measureExternal();
239 public void onPause() {
241 getActivity().unregisterReceiver(mReceiver);
242 mMeasurement.cleanUp();
246 public void onDestroy() {
247 if (mStorageManager != null && mStorageListener != null) {
248 mStorageManager.unregisterListener(mStorageListener);
253 private synchronized IMountService getMountService() {
254 if (mMountService == null) {
255 IBinder service = ServiceManager.getService("mount");
256 if (service != null) {
257 mMountService = IMountService.Stub.asInterface(service);
259 Log.e(TAG, "Can't get mount service");
262 return mMountService;
266 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
267 if (preference == mSdMountToggle) {
268 String status = Environment.getExternalStorageState();
269 if (status.equals(Environment.MEDIA_MOUNTED)) {
275 } else if (preference == mSdFormat) {
276 Intent intent = new Intent(Intent.ACTION_VIEW);
277 intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
278 startActivity(intent);
285 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
287 public void onReceive(Context context, Intent intent) {
288 mMeasurement.invalidate();
290 if (!Environment.isExternalStorageEmulated()) {
291 mMeasurement.measureExternal();
293 mMeasurement.measureInternal();
298 public Dialog onCreateDialog(int id) {
300 case DLG_CONFIRM_UNMOUNT:
301 return new AlertDialog.Builder(getActivity())
302 .setTitle(R.string.dlg_confirm_unmount_title)
303 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
304 public void onClick(DialogInterface dialog, int which) {
307 .setNegativeButton(R.string.cancel, null)
308 .setMessage(R.string.dlg_confirm_unmount_text)
309 .setOnCancelListener(this)
311 case DLG_ERROR_UNMOUNT:
312 return new AlertDialog.Builder(getActivity())
313 .setTitle(R.string.dlg_error_unmount_title)
314 .setNeutralButton(R.string.dlg_ok, null)
315 .setMessage(R.string.dlg_error_unmount_text)
316 .setOnCancelListener(this)
322 private void doUnmount(boolean force) {
323 // Present a toast here
324 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
325 IMountService mountService = getMountService();
326 String extStoragePath = Environment.getExternalStorageDirectory().toString();
328 mSdMountToggle.setEnabled(false);
329 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
330 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
331 mountService.unmountVolume(extStoragePath, force);
332 } catch (RemoteException e) {
333 // Informative dialog to user that
335 showDialogInner(DLG_ERROR_UNMOUNT);
339 private void showDialogInner(int id) {
344 private boolean hasAppsAccessingStorage() throws RemoteException {
345 String extStoragePath = Environment.getExternalStorageDirectory().toString();
346 IMountService mountService = getMountService();
347 int stUsers[] = mountService.getStorageUsers(extStoragePath);
348 if (stUsers != null && stUsers.length > 0) {
351 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
352 List<ApplicationInfo> list = am.getRunningExternalApplications();
353 if (list != null && list.size() > 0) {
359 private void unmount() {
360 // Check if external media is in use.
362 if (hasAppsAccessingStorage()) {
363 if (localLOGV) Log.i(TAG, "Do have storage users accessing media");
364 // Present dialog to user
365 showDialogInner(DLG_CONFIRM_UNMOUNT);
369 } catch (RemoteException e) {
370 // Very unlikely. But present an error dialog anyway
371 Log.e(TAG, "Is MountService running?");
372 showDialogInner(DLG_ERROR_UNMOUNT);
376 private void mount() {
377 IMountService mountService = getMountService();
379 if (mountService != null) {
380 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
382 Log.e(TAG, "Mount service is null, can't mount");
384 } catch (RemoteException ex) {
388 private void updateUiExact(long totalSize, long availSize, long mediaSize, long appsSize) {
389 mInternalSize.setSummary(formatSize(totalSize));
390 mInternalAvail.setSummary(formatSize(availSize));
391 mInternalMediaUsage.setSummary(formatSize(mediaSize));
392 mInternalAppsUsage.setSummary(formatSize(appsSize));
394 mInternalUsageChart.clear();
395 mInternalUsageChart.addEntry(mediaSize / (float) totalSize, mInternalMediaColor);
396 mInternalUsageChart.addEntry(appsSize / (float) totalSize, mInternalAppsColor);
398 final long usedSize = totalSize - availSize;
400 // There are other things that can take up storage, but we didn't
402 final long remaining = usedSize - (mediaSize + appsSize);
404 mInternalUsageChart.addEntry(remaining / (float) totalSize, mInternalUsedColor);
406 mInternalUsageChart.commit();
409 private void updateUiApproximate(long totalSize, long availSize) {
410 mInternalSize.setSummary(formatSize(totalSize));
411 mInternalAvail.setSummary(formatSize(availSize));
413 final long usedSize = totalSize - availSize;
415 mInternalUsageChart.clear();
416 mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
417 mInternalUsageChart.commit();
420 private void updateExternalStorage(long totalSize, long availSize) {
421 String status = Environment.getExternalStorageState();
422 String readOnly = "";
423 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
424 status = Environment.MEDIA_MOUNTED;
425 readOnly = mRes.getString(R.string.read_only);
428 if (status.equals(Environment.MEDIA_MOUNTED)) {
429 if (!Environment.isExternalStorageRemovable()) {
430 // This device has built-in storage that is not removable.
431 // There is no reason for the user to unmount it.
432 if (mSdMountToggleAdded) {
433 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
434 mSdMountToggleAdded = false;
438 mSdSize.setSummary(formatSize(totalSize));
439 mSdAvail.setSummary(formatSize(availSize) + readOnly);
441 mSdMountToggle.setEnabled(true);
442 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
443 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
445 } catch (IllegalArgumentException e) {
446 // this can occur if the SD card is removed, but we haven't
448 // ACTION_MEDIA_REMOVED Intent yet.
449 status = Environment.MEDIA_REMOVED;
452 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
453 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
455 if (!Environment.isExternalStorageRemovable()) {
456 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
457 if (!mSdMountToggleAdded) {
458 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
459 mSdMountToggleAdded = true;
464 if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
465 || status.equals(Environment.MEDIA_UNMOUNTABLE)) {
466 mSdMountToggle.setEnabled(true);
467 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
468 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
470 mSdMountToggle.setEnabled(false);
471 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
472 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
477 private String formatSize(long size) {
478 return Formatter.formatFileSize(getActivity(), size);
481 public void onCancel(DialogInterface dialog) {
482 // TODO: Is this really required?
487 public void updateApproximateExternal(Bundle bundle) {
488 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
489 message.setData(bundle);
490 mUpdateHandler.sendMessage(message);
494 public void updateApproximateInternal(Bundle bundle) {
495 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
496 message.setData(bundle);
497 mUpdateHandler.sendMessage(message);
501 public void updateExactInternal(Bundle bundle) {
502 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
503 message.setData(bundle);
504 mUpdateHandler.sendMessage(message);