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 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);
281 } else if (preference == mInternalAppsUsage) {
282 Intent intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
283 intent.setClass(getActivity(),
284 com.android.settings.Settings.ManageApplicationsActivity.class);
285 startActivity(intent);
292 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
294 public void onReceive(Context context, Intent intent) {
295 mMeasurement.invalidate();
297 if (!Environment.isExternalStorageEmulated()) {
298 mMeasurement.measureExternal();
300 mMeasurement.measureInternal();
305 public Dialog onCreateDialog(int id) {
307 case DLG_CONFIRM_UNMOUNT:
308 return new AlertDialog.Builder(getActivity())
309 .setTitle(R.string.dlg_confirm_unmount_title)
310 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
311 public void onClick(DialogInterface dialog, int which) {
314 .setNegativeButton(R.string.cancel, null)
315 .setMessage(R.string.dlg_confirm_unmount_text)
316 .setOnCancelListener(this)
318 case DLG_ERROR_UNMOUNT:
319 return new AlertDialog.Builder(getActivity())
320 .setTitle(R.string.dlg_error_unmount_title)
321 .setNeutralButton(R.string.dlg_ok, null)
322 .setMessage(R.string.dlg_error_unmount_text)
323 .setOnCancelListener(this)
329 private void doUnmount(boolean force) {
330 // Present a toast here
331 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
332 IMountService mountService = getMountService();
333 String extStoragePath = Environment.getExternalStorageDirectory().toString();
335 mSdMountToggle.setEnabled(false);
336 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
337 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
338 mountService.unmountVolume(extStoragePath, force);
339 } catch (RemoteException e) {
340 // Informative dialog to user that
342 showDialogInner(DLG_ERROR_UNMOUNT);
346 private void showDialogInner(int id) {
351 private boolean hasAppsAccessingStorage() throws RemoteException {
352 String extStoragePath = Environment.getExternalStorageDirectory().toString();
353 IMountService mountService = getMountService();
354 int stUsers[] = mountService.getStorageUsers(extStoragePath);
355 if (stUsers != null && stUsers.length > 0) {
358 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
359 List<ApplicationInfo> list = am.getRunningExternalApplications();
360 if (list != null && list.size() > 0) {
366 private void unmount() {
367 // Check if external media is in use.
369 if (hasAppsAccessingStorage()) {
370 if (localLOGV) Log.i(TAG, "Do have storage users accessing media");
371 // Present dialog to user
372 showDialogInner(DLG_CONFIRM_UNMOUNT);
376 } catch (RemoteException e) {
377 // Very unlikely. But present an error dialog anyway
378 Log.e(TAG, "Is MountService running?");
379 showDialogInner(DLG_ERROR_UNMOUNT);
383 private void mount() {
384 IMountService mountService = getMountService();
386 if (mountService != null) {
387 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
389 Log.e(TAG, "Mount service is null, can't mount");
391 } catch (RemoteException ex) {
395 private void updateUiExact(long totalSize, long availSize, long mediaSize, long appsSize) {
396 mInternalSize.setSummary(formatSize(totalSize));
397 mInternalAvail.setSummary(formatSize(availSize));
398 mInternalMediaUsage.setSummary(formatSize(mediaSize));
399 mInternalAppsUsage.setSummary(formatSize(appsSize));
401 mInternalUsageChart.clear();
402 mInternalUsageChart.addEntry(mediaSize / (float) totalSize, mInternalMediaColor);
403 mInternalUsageChart.addEntry(appsSize / (float) totalSize, mInternalAppsColor);
405 final long usedSize = totalSize - availSize;
407 // There are other things that can take up storage, but we didn't
409 final long remaining = usedSize - (mediaSize + appsSize);
411 mInternalUsageChart.addEntry(remaining / (float) totalSize, mInternalUsedColor);
413 mInternalUsageChart.commit();
416 private void updateUiApproximate(long totalSize, long availSize) {
417 mInternalSize.setSummary(formatSize(totalSize));
418 mInternalAvail.setSummary(formatSize(availSize));
420 final long usedSize = totalSize - availSize;
422 mInternalUsageChart.clear();
423 mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
424 mInternalUsageChart.commit();
427 private void updateExternalStorage(long totalSize, long availSize) {
428 String status = Environment.getExternalStorageState();
429 String readOnly = "";
430 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
431 status = Environment.MEDIA_MOUNTED;
432 readOnly = mRes.getString(R.string.read_only);
435 if (status.equals(Environment.MEDIA_MOUNTED)) {
436 if (!Environment.isExternalStorageRemovable()) {
437 // This device has built-in storage that is not removable.
438 // There is no reason for the user to unmount it.
439 if (mSdMountToggleAdded) {
440 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
441 mSdMountToggleAdded = false;
445 mSdSize.setSummary(formatSize(totalSize));
446 mSdAvail.setSummary(formatSize(availSize) + readOnly);
448 mSdMountToggle.setEnabled(true);
449 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
450 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
452 } catch (IllegalArgumentException e) {
453 // this can occur if the SD card is removed, but we haven't
455 // ACTION_MEDIA_REMOVED Intent yet.
456 status = Environment.MEDIA_REMOVED;
459 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
460 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
462 if (!Environment.isExternalStorageRemovable()) {
463 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
464 if (!mSdMountToggleAdded) {
465 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
466 mSdMountToggleAdded = true;
471 if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
472 || status.equals(Environment.MEDIA_UNMOUNTABLE)) {
473 mSdMountToggle.setEnabled(true);
474 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
475 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
477 mSdMountToggle.setEnabled(false);
478 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
479 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
484 private String formatSize(long size) {
485 return Formatter.formatFileSize(getActivity(), size);
488 public void onCancel(DialogInterface dialog) {
489 // TODO: Is this really required?
494 public void updateApproximateExternal(Bundle bundle) {
495 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
496 message.setData(bundle);
497 mUpdateHandler.sendMessage(message);
501 public void updateApproximateInternal(Bundle bundle) {
502 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
503 message.setData(bundle);
504 mUpdateHandler.sendMessage(message);
508 public void updateExactInternal(Bundle bundle) {
509 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
510 message.setData(bundle);
511 mUpdateHandler.sendMessage(message);