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.app.DownloadManager;
27 import android.content.BroadcastReceiver;
28 import android.content.Context;
29 import android.content.DialogInterface;
30 import android.content.Intent;
31 import android.content.IntentFilter;
32 import android.content.DialogInterface.OnCancelListener;
33 import android.content.pm.ApplicationInfo;
34 import android.content.res.Resources;
35 import android.graphics.drawable.ShapeDrawable;
36 import android.graphics.drawable.shapes.RoundRectShape;
37 import android.os.Bundle;
38 import android.os.Environment;
39 import android.os.Handler;
40 import android.os.IBinder;
41 import android.os.Message;
42 import android.os.RemoteException;
43 import android.os.ServiceManager;
44 import android.os.storage.IMountService;
45 import android.os.storage.StorageEventListener;
46 import android.os.storage.StorageManager;
47 import android.preference.Preference;
48 import android.preference.PreferenceGroup;
49 import android.preference.PreferenceScreen;
50 import android.text.format.Formatter;
51 import android.util.Log;
52 import android.widget.Toast;
54 import java.util.List;
56 public class Memory extends SettingsPreferenceFragment implements OnCancelListener,
58 private static final String TAG = "MemorySettings";
60 private static final String MEMORY_SD_SIZE = "memory_sd_size";
62 private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
64 private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
66 private static final String MEMORY_SD_FORMAT = "memory_sd_format";
68 private static final String MEMORY_SD_GROUP = "memory_sd";
70 private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
72 private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
74 private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
76 private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
78 private static final int DLG_CONFIRM_UNMOUNT = 1;
79 private static final int DLG_ERROR_UNMOUNT = 2;
81 private Resources mRes;
83 // External storage preferences
84 private Preference mSdSize;
85 private Preference mSdAvail;
86 private Preference mSdMountToggle;
87 private Preference mSdFormat;
88 private PreferenceGroup mSdMountPreferenceGroup;
90 // Internal storage preferences
91 private Preference mInternalSize;
92 private Preference mInternalAvail;
93 private Preference mInternalAppsUsage;
94 private final Preference[] mMediaPreferences = new Preference[Constants.NUM_MEDIA_DIRS_TRACKED];
95 private UsageBarPreference mInternalUsageChart;
97 // Internal storage chart colors
98 private int mInternalAppsColor;
99 private int mInternalAvailColor;
100 private int mInternalUsedColor;
102 boolean mSdMountToggleAdded = true;
104 // Access using getMountService()
105 private IMountService mMountService = null;
107 private StorageManager mStorageManager = null;
109 // Updates the memory usage bar graph.
110 private static final int MSG_UI_UPDATE_INTERNAL_APPROXIMATE = 1;
112 // Updates the memory usage bar graph.
113 private static final int MSG_UI_UPDATE_INTERNAL_EXACT = 2;
115 // Updates the memory usage stats for external.
116 private static final int MSG_UI_UPDATE_EXTERNAL_APPROXIMATE = 3;
118 private Handler mUpdateHandler = new Handler() {
120 public void handleMessage(Message msg) {
122 case MSG_UI_UPDATE_INTERNAL_APPROXIMATE: {
123 Bundle bundle = msg.getData();
124 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
125 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
126 updateUiApproximate(totalSize, availSize);
129 case MSG_UI_UPDATE_INTERNAL_EXACT: {
130 Bundle bundle = msg.getData();
131 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
132 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
133 final long appsUsed = bundle.getLong(MemoryMeasurement.APPS_USED);
134 final long[] mediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED];
135 for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
136 mediaSizes[i] = bundle.getLong(Constants.mMediaDirs.get(i).mKey);
138 updateUiExact(totalSize, availSize, appsUsed, mediaSizes);
141 case MSG_UI_UPDATE_EXTERNAL_APPROXIMATE: {
142 Bundle bundle = msg.getData();
143 final long totalSize = bundle.getLong(MemoryMeasurement.TOTAL_SIZE);
144 final long availSize = bundle.getLong(MemoryMeasurement.AVAIL_SIZE);
145 updateExternalStorage(totalSize, availSize);
152 private MemoryMeasurement mMeasurement;
155 public void onCreate(Bundle icicle) {
156 super.onCreate(icicle);
158 if (mStorageManager == null) {
159 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
160 mStorageManager.registerListener(mStorageListener);
163 addPreferencesFromResource(R.xml.device_info_memory);
165 mRes = getResources();
166 mSdSize = findPreference(MEMORY_SD_SIZE);
167 mSdAvail = findPreference(MEMORY_SD_AVAIL);
168 mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE);
169 mSdFormat = findPreference(MEMORY_SD_FORMAT);
170 mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
172 if (Environment.isExternalStorageEmulated()) {
173 getPreferenceScreen().removePreference(mSdMountPreferenceGroup);
176 mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
177 mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
178 mInternalUsedColor = android.graphics.Color.GRAY;
179 mInternalAvailColor = mRes.getColor(R.color.memory_avail);
180 final int buttonSize = (int) mRes.getDimension(R.dimen.device_memory_usage_button_size);
181 float[] radius = new float[] {
182 5f, 5f, 5f, 5f, 5f, 5f, 5f, 5f
184 RoundRectShape shape1 = new RoundRectShape(radius, null, null);
186 // total available space
187 mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
188 ShapeDrawable availShape = new ShapeDrawable(shape1);
189 availShape.setIntrinsicWidth(buttonSize);
190 availShape.setIntrinsicHeight(buttonSize);
191 availShape.getPaint().setColor(mInternalAvailColor);
192 mInternalAvail.setIcon(availShape);
195 mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
196 ShapeDrawable appsShape = new ShapeDrawable(shape1);
197 appsShape.setIntrinsicWidth(buttonSize);
198 appsShape.setIntrinsicHeight(buttonSize);
199 appsShape.getPaint().setColor(mInternalAppsColor);
200 mInternalAppsUsage.setIcon(appsShape);
202 // space used by individual major directories on /sdcard
203 for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
204 // nothing to be displayed for certain entries in Constants.mMediaDirs
205 if (Constants.mMediaDirs.get(i).mPreferenceName == null) {
208 mMediaPreferences[i] = findPreference(Constants.mMediaDirs.get(i).mPreferenceName);
209 ShapeDrawable shape = new ShapeDrawable(shape1);
210 shape.setIntrinsicWidth(buttonSize);
211 shape.setIntrinsicHeight(buttonSize);
214 case Constants.DOWNLOADS_INDEX:
215 color = mRes.getColor(R.color.memory_downloads);
217 case Constants.PIC_VIDEO_INDEX:
218 color = mRes.getColor(R.color.memory_video);
220 case Constants.MUSIC_INDEX:
221 color = mRes.getColor(R.color.memory_audio);
223 case Constants.MEDIA_MISC_INDEX:
224 color = mRes.getColor(R.color.memory_misc);
227 shape.getPaint().setColor(color);
228 mMediaPreferences[i].setIcon(shape);
230 mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
232 mMeasurement = MemoryMeasurement.getInstance(getActivity());
233 mMeasurement.setReceiver(this);
237 public void onResume() {
239 mMeasurement.setReceiver(this);
240 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
241 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
242 intentFilter.addDataScheme("file");
243 getActivity().registerReceiver(mReceiver, intentFilter);
245 mMeasurement.invalidate();
246 if (!Environment.isExternalStorageEmulated()) {
247 mMeasurement.measureExternal();
249 mMeasurement.measureInternal();
252 StorageEventListener mStorageListener = new StorageEventListener() {
254 public void onStorageStateChanged(String path, String oldState, String newState) {
255 Log.i(TAG, "Received storage state changed notification that " +
256 path + " changed state from " + oldState +
258 if (!Environment.isExternalStorageEmulated()) {
259 mMeasurement.measureExternal();
265 public void onPause() {
267 getActivity().unregisterReceiver(mReceiver);
268 mMeasurement.cleanUp();
272 public void onDestroy() {
273 if (mStorageManager != null && mStorageListener != null) {
274 mStorageManager.unregisterListener(mStorageListener);
279 private synchronized IMountService getMountService() {
280 if (mMountService == null) {
281 IBinder service = ServiceManager.getService("mount");
282 if (service != null) {
283 mMountService = IMountService.Stub.asInterface(service);
285 Log.e(TAG, "Can't get mount service");
288 return mMountService;
292 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
293 if (preference == mSdMountToggle) {
294 String status = Environment.getExternalStorageState();
295 if (status.equals(Environment.MEDIA_MOUNTED)) {
301 } else if (preference == mSdFormat) {
302 Intent intent = new Intent(Intent.ACTION_VIEW);
303 intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
304 startActivity(intent);
306 } else if (preference == mInternalAppsUsage) {
307 Intent intent = new Intent(Intent.ACTION_MANAGE_PACKAGE_STORAGE);
308 intent.setClass(getActivity(),
309 com.android.settings.Settings.ManageApplicationsActivity.class);
310 startActivity(intent);
312 } else if (preference == mMediaPreferences[Constants.DOWNLOADS_INDEX]) {
313 Intent intent = new Intent(DownloadManager.ACTION_VIEW_DOWNLOADS)
314 .putExtra(DownloadManager.INTENT_EXTRAS_SORT_BY_SIZE, true);
315 startActivity(intent);
317 } else if (preference == mMediaPreferences[Constants.MUSIC_INDEX]) {
318 Intent intent = new Intent("android.intent.action.GET_CONTENT");
319 intent.setType("audio/mp3");
320 startActivity(intent);
322 } else if (preference == mMediaPreferences[Constants.PIC_VIDEO_INDEX]) {
323 Intent intent = new Intent("android.intent.action.GET_CONTENT");
324 intent.setType("image/jpeg");
325 startActivity(intent);
327 } else if (preference == mMediaPreferences[Constants.MEDIA_MISC_INDEX]) {
328 Context context = getActivity().getApplicationContext();
329 if (MemoryMeasurement.getInstance(context).isSizeOfMiscCategorynonZero()) {
330 startActivity(new Intent(context, MiscFilesHandler.class));
338 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
340 public void onReceive(Context context, Intent intent) {
341 mMeasurement.invalidate();
343 if (!Environment.isExternalStorageEmulated()) {
344 mMeasurement.measureExternal();
346 mMeasurement.measureInternal();
351 public Dialog onCreateDialog(int id) {
353 case DLG_CONFIRM_UNMOUNT:
354 return new AlertDialog.Builder(getActivity())
355 .setTitle(R.string.dlg_confirm_unmount_title)
356 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
357 public void onClick(DialogInterface dialog, int which) {
360 .setNegativeButton(R.string.cancel, null)
361 .setMessage(R.string.dlg_confirm_unmount_text)
363 case DLG_ERROR_UNMOUNT:
364 return new AlertDialog.Builder(getActivity())
365 .setTitle(R.string.dlg_error_unmount_title)
366 .setNeutralButton(R.string.dlg_ok, null)
367 .setMessage(R.string.dlg_error_unmount_text)
374 protected void showDialog(int id) {
375 super.showDialog(id);
378 case DLG_CONFIRM_UNMOUNT:
379 case DLG_ERROR_UNMOUNT:
380 setOnCancelListener(this);
385 private void doUnmount(boolean force) {
386 // Present a toast here
387 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
388 IMountService mountService = getMountService();
389 String extStoragePath = Environment.getExternalStorageDirectory().toString();
391 mSdMountToggle.setEnabled(false);
392 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
393 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
394 mountService.unmountVolume(extStoragePath, force);
395 } catch (RemoteException e) {
396 // Informative dialog to user that
398 showDialogInner(DLG_ERROR_UNMOUNT);
402 private void showDialogInner(int id) {
407 private boolean hasAppsAccessingStorage() throws RemoteException {
408 String extStoragePath = Environment.getExternalStorageDirectory().toString();
409 IMountService mountService = getMountService();
410 int stUsers[] = mountService.getStorageUsers(extStoragePath);
411 if (stUsers != null && stUsers.length > 0) {
414 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
415 List<ApplicationInfo> list = am.getRunningExternalApplications();
416 if (list != null && list.size() > 0) {
422 private void unmount() {
423 // Check if external media is in use.
425 if (hasAppsAccessingStorage()) {
426 // Present dialog to user
427 showDialogInner(DLG_CONFIRM_UNMOUNT);
431 } catch (RemoteException e) {
432 // Very unlikely. But present an error dialog anyway
433 Log.e(TAG, "Is MountService running?");
434 showDialogInner(DLG_ERROR_UNMOUNT);
438 private void mount() {
439 IMountService mountService = getMountService();
441 if (mountService != null) {
442 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
444 Log.e(TAG, "Mount service is null, can't mount");
446 } catch (RemoteException ex) {
450 private void updateUiExact(long totalSize, long availSize, long appsSize, long[] mediaSizes) {
451 // There are other things that can take up storage, but we didn't measure it.
452 // add that unaccounted-for-usage to Apps Usage
453 long appsPlusRemaining = totalSize - availSize - mediaSizes[Constants.DOWNLOADS_INDEX] -
454 mediaSizes[Constants.PIC_VIDEO_INDEX] - mediaSizes[Constants.MUSIC_INDEX] -
455 mediaSizes[Constants.MEDIA_MISC_INDEX];
456 mInternalSize.setSummary(formatSize(totalSize));
457 mInternalAvail.setSummary(formatSize(availSize));
458 mInternalAppsUsage.setSummary(formatSize(appsPlusRemaining));
460 mInternalUsageChart.clear();
461 mInternalUsageChart.addEntry(appsPlusRemaining / (float) totalSize, mInternalAppsColor);
463 for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
464 if (Constants.mMediaDirs.get(i).mPreferenceName == null) {
467 this.mMediaPreferences[i].setSummary(formatSize(mediaSizes[i]));
468 // don't add entry to color chart for media usage and for zero-sized dirs
469 if (i != Constants.MEDIA_INDEX && mediaSizes[i] > 0) {
472 case Constants.DOWNLOADS_INDEX:
473 color = mRes.getColor(R.color.memory_downloads);
475 case Constants.PIC_VIDEO_INDEX:
476 color = mRes.getColor(R.color.memory_video);
478 case Constants.MUSIC_INDEX:
479 color = mRes.getColor(R.color.memory_audio);
481 case Constants.MEDIA_MISC_INDEX:
482 color = mRes.getColor(R.color.memory_misc);
485 mInternalUsageChart.addEntry(mediaSizes[i] / (float) totalSize, color);
488 mInternalUsageChart.addEntry(availSize / (float) totalSize, mInternalAvailColor);
489 mInternalUsageChart.commit();
492 private void updateUiApproximate(long totalSize, long availSize) {
493 mInternalSize.setSummary(formatSize(totalSize));
494 mInternalAvail.setSummary(formatSize(availSize));
496 final long usedSize = totalSize - availSize;
498 mInternalUsageChart.clear();
499 mInternalUsageChart.addEntry(usedSize / (float) totalSize, mInternalUsedColor);
500 mInternalUsageChart.commit();
503 private void updateExternalStorage(long totalSize, long availSize) {
504 String status = Environment.getExternalStorageState();
505 String readOnly = "";
506 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
507 status = Environment.MEDIA_MOUNTED;
508 readOnly = mRes.getString(R.string.read_only);
511 if (status.equals(Environment.MEDIA_MOUNTED)) {
512 if (!Environment.isExternalStorageRemovable()) {
513 // This device has built-in storage that is not removable.
514 // There is no reason for the user to unmount it.
515 if (mSdMountToggleAdded) {
516 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
517 mSdMountToggleAdded = false;
521 mSdSize.setSummary(formatSize(totalSize));
522 mSdAvail.setSummary(formatSize(availSize) + readOnly);
524 mSdMountToggle.setEnabled(true);
525 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
526 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
528 } catch (IllegalArgumentException e) {
529 // this can occur if the SD card is removed, but we haven't
531 // ACTION_MEDIA_REMOVED Intent yet.
532 status = Environment.MEDIA_REMOVED;
535 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
536 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
538 if (!Environment.isExternalStorageRemovable()) {
539 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
540 if (!mSdMountToggleAdded) {
541 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
542 mSdMountToggleAdded = true;
547 if (status.equals(Environment.MEDIA_UNMOUNTED) || status.equals(Environment.MEDIA_NOFS)
548 || status.equals(Environment.MEDIA_UNMOUNTABLE)) {
549 mSdMountToggle.setEnabled(true);
550 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
551 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
553 mSdMountToggle.setEnabled(false);
554 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
555 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
560 private String formatSize(long size) {
561 return Formatter.formatFileSize(getActivity(), size);
564 public void onCancel(DialogInterface dialog) {
565 // TODO: Is this really required?
570 public void updateApproximateExternal(Bundle bundle) {
571 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_EXTERNAL_APPROXIMATE);
572 message.setData(bundle);
573 mUpdateHandler.sendMessage(message);
577 public void updateApproximateInternal(Bundle bundle) {
578 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_APPROXIMATE);
579 message.setData(bundle);
580 mUpdateHandler.sendMessage(message);
584 public void updateExactInternal(Bundle bundle) {
585 final Message message = mUpdateHandler.obtainMessage(MSG_UI_UPDATE_INTERNAL_EXACT);
586 message.setData(bundle);
587 mUpdateHandler.sendMessage(message);