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 mMeasurement.invalidate();
221 if (!Environment.isExternalStorageEmulated()) {
222 mMeasurement.measureExternal();
224 mMeasurement.measureInternal();
227 StorageEventListener mStorageListener = new StorageEventListener() {
229 public void onStorageStateChanged(String path, String oldState, String newState) {
230 Log.i(TAG, "Received storage state changed notification that " +
231 path + " changed state from " + oldState +
233 if (!Environment.isExternalStorageEmulated()) {
234 mMeasurement.measureExternal();
240 public void onPause() {
242 getActivity().unregisterReceiver(mReceiver);
243 mMeasurement.cleanUp();
247 public void onDestroy() {
248 if (mStorageManager != null && mStorageListener != null) {
249 mStorageManager.unregisterListener(mStorageListener);
254 private synchronized IMountService getMountService() {
255 if (mMountService == null) {
256 IBinder service = ServiceManager.getService("mount");
257 if (service != null) {
258 mMountService = IMountService.Stub.asInterface(service);
260 Log.e(TAG, "Can't get mount service");
263 return mMountService;
267 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
268 if (preference == mSdMountToggle) {
269 String status = Environment.getExternalStorageState();
270 if (status.equals(Environment.MEDIA_MOUNTED)) {
276 } else if (preference == mSdFormat) {
277 Intent intent = new Intent(Intent.ACTION_VIEW);
278 intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
279 startActivity(intent);
286 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
288 public void onReceive(Context context, Intent intent) {
289 mMeasurement.invalidate();
291 if (!Environment.isExternalStorageEmulated()) {
292 mMeasurement.measureExternal();
294 mMeasurement.measureInternal();
299 public Dialog onCreateDialog(int id) {
301 case DLG_CONFIRM_UNMOUNT:
302 return new AlertDialog.Builder(getActivity())
303 .setTitle(R.string.dlg_confirm_unmount_title)
304 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
305 public void onClick(DialogInterface dialog, int which) {
308 .setNegativeButton(R.string.cancel, null)
309 .setMessage(R.string.dlg_confirm_unmount_text)
310 .setOnCancelListener(this)
312 case DLG_ERROR_UNMOUNT:
313 return new AlertDialog.Builder(getActivity())
314 .setTitle(R.string.dlg_error_unmount_title)
315 .setNeutralButton(R.string.dlg_ok, null)
316 .setMessage(R.string.dlg_error_unmount_text)
317 .setOnCancelListener(this)
323 private void doUnmount(boolean force) {
324 // Present a toast here
325 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
326 IMountService mountService = getMountService();
327 String extStoragePath = Environment.getExternalStorageDirectory().toString();
329 mSdMountToggle.setEnabled(false);
330 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
331 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
332 mountService.unmountVolume(extStoragePath, force);
333 } catch (RemoteException e) {
334 // Informative dialog to user that
336 showDialogInner(DLG_ERROR_UNMOUNT);
340 private void showDialogInner(int id) {
345 private boolean hasAppsAccessingStorage() throws RemoteException {
346 String extStoragePath = Environment.getExternalStorageDirectory().toString();
347 IMountService mountService = getMountService();
348 int stUsers[] = mountService.getStorageUsers(extStoragePath);
349 if (stUsers != null && stUsers.length > 0) {
352 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
353 List<ApplicationInfo> list = am.getRunningExternalApplications();
354 if (list != null && list.size() > 0) {
360 private void unmount() {
361 // Check if external media is in use.
363 if (hasAppsAccessingStorage()) {
364 if (localLOGV) Log.i(TAG, "Do have storage users accessing media");
365 // Present dialog to user
366 showDialogInner(DLG_CONFIRM_UNMOUNT);
370 } catch (RemoteException e) {
371 // Very unlikely. But present an error dialog anyway
372 Log.e(TAG, "Is MountService running?");
373 showDialogInner(DLG_ERROR_UNMOUNT);
377 private void mount() {
378 IMountService mountService = getMountService();
380 if (mountService != null) {
381 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
383 Log.e(TAG, "Mount service is null, can't mount");
385 } catch (RemoteException ex) {
389 private void updateUiExact(long totalSize, long availSize, long mediaSize, long appsSize) {
390 mInternalSize.setSummary(formatSize(totalSize));
391 mInternalAvail.setSummary(formatSize(availSize));
392 mInternalMediaUsage.setSummary(formatSize(mediaSize));
393 mInternalAppsUsage.setSummary(formatSize(appsSize));
395 mInternalUsageChart.clear();
396 mInternalUsageChart.addEntry(mediaSize / (float) totalSize, mInternalMediaColor);
397 mInternalUsageChart.addEntry(appsSize / (float) totalSize, mInternalAppsColor);
399 final long usedSize = totalSize - availSize;
401 // There are other things that can take up storage, but we didn't
403 final long remaining = usedSize - (mediaSize + appsSize);
405 mInternalUsageChart.addEntry(remaining / (float) totalSize, mInternalUsedColor);
407 mInternalUsageChart.commit();
410 private void updateUiApproximate(long totalSize, long availSize) {
411 mInternalSize.setSummary(formatSize(totalSize));
412 mInternalAvail.setSummary(formatSize(availSize));
414 final long usedSize = totalSize - availSize;
416 mInternalUsageChart.clear();
417 mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
418 mInternalUsageChart.commit();
421 private void updateExternalStorage(long totalSize, long availSize) {
422 String status = Environment.getExternalStorageState();
423 String readOnly = "";
424 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
425 status = Environment.MEDIA_MOUNTED;
426 readOnly = mRes.getString(R.string.read_only);
429 if (status.equals(Environment.MEDIA_MOUNTED)) {
430 if (!Environment.isExternalStorageRemovable()) {
431 // This device has built-in storage that is not removable.
432 // There is no reason for the user to unmount it.
433 if (mSdMountToggleAdded) {
434 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
435 mSdMountToggleAdded = false;
439 mSdSize.setSummary(formatSize(totalSize));
440 mSdAvail.setSummary(formatSize(availSize) + readOnly);
442 mSdMountToggle.setEnabled(true);
443 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
444 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
446 } catch (IllegalArgumentException e) {
447 // this can occur if the SD card is removed, but we haven't
449 // ACTION_MEDIA_REMOVED Intent yet.
450 status = Environment.MEDIA_REMOVED;
453 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
454 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
456 if (!Environment.isExternalStorageRemovable()) {
457 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
458 if (!mSdMountToggleAdded) {
459 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
460 mSdMountToggleAdded = true;
465 if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
466 || status.equals(Environment.MEDIA_UNMOUNTABLE)) {
467 mSdMountToggle.setEnabled(true);
468 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
469 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
471 mSdMountToggle.setEnabled(false);
472 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
473 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
478 private String formatSize(long size) {
479 return Formatter.formatFileSize(getActivity(), size);
482 public void onCancel(DialogInterface dialog) {
483 // TODO: Is this really required?
488 public void updateApproximateExternal(Bundle bundle) {
489 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
490 message.setData(bundle);
491 mUpdateHandler.sendMessage(message);
495 public void updateApproximateInternal(Bundle bundle) {
496 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
497 message.setData(bundle);
498 mUpdateHandler.sendMessage(message);
502 public void updateExactInternal(Bundle bundle) {
503 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
504 message.setData(bundle);
505 mUpdateHandler.sendMessage(message);