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.internal.app.IMediaContainerService;
20 import com.android.settings.R;
21 import com.android.settings.SettingsPreferenceFragment;
23 import android.app.Activity;
24 import android.app.ActivityManager;
25 import android.app.AlertDialog;
26 import android.app.Dialog;
27 import android.content.BroadcastReceiver;
28 import android.content.ComponentName;
29 import android.content.Context;
30 import android.content.DialogInterface;
31 import android.content.Intent;
32 import android.content.IntentFilter;
33 import android.content.ServiceConnection;
34 import android.content.DialogInterface.OnCancelListener;
35 import android.content.pm.ApplicationInfo;
36 import android.content.pm.IPackageStatsObserver;
37 import android.content.pm.PackageManager;
38 import android.content.pm.PackageStats;
39 import android.content.res.Resources;
40 import android.hardware.UsbManager;
41 import android.os.Bundle;
42 import android.os.Environment;
43 import android.os.Handler;
44 import android.os.HandlerThread;
45 import android.os.IBinder;
46 import android.os.Looper;
47 import android.os.Message;
48 import android.os.RemoteException;
49 import android.os.ServiceManager;
50 import android.os.StatFs;
51 import android.os.storage.IMountService;
52 import android.os.storage.StorageEventListener;
53 import android.os.storage.StorageManager;
54 import android.preference.Preference;
55 import android.preference.PreferenceGroup;
56 import android.preference.PreferenceScreen;
57 import android.text.format.Formatter;
58 import android.util.Log;
59 import android.widget.Toast;
62 import java.util.ArrayList;
63 import java.util.List;
65 public class Memory extends SettingsPreferenceFragment implements OnCancelListener {
66 private static final String TAG = "Memory";
67 private static final boolean localLOGV = false;
69 private static final String MEMORY_SD_SIZE = "memory_sd_size";
71 private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
73 private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
75 private static final String MEMORY_SD_FORMAT = "memory_sd_format";
77 private static final String MEMORY_SD_GROUP = "memory_sd";
79 private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
81 private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
83 private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
85 private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media";
87 private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
89 private static final int DLG_CONFIRM_UNMOUNT = 1;
90 private static final int DLG_ERROR_UNMOUNT = 2;
92 private Resources mRes;
94 // External storage preferences
95 private Preference mSdSize;
96 private Preference mSdAvail;
97 private Preference mSdMountToggle;
98 private Preference mSdFormat;
99 private PreferenceGroup mSdMountPreferenceGroup;
101 // Internal storage preferences
102 private Preference mInternalSize;
103 private Preference mInternalAvail;
104 private Preference mInternalMediaUsage;
105 private Preference mInternalAppsUsage;
106 private UsageBarPreference mInternalUsageChart;
108 // Internal storage chart colors
109 private int mInternalMediaColor;
110 private int mInternalAppsColor;
111 private int mInternalUsedColor;
113 // Internal memory fields
114 private long mInternalTotalSize;
115 private long mInternalUsedSize;
116 private long mInternalMediaSize;
117 private long mInternalAppsSize;
118 private boolean mMeasured = false;
120 boolean mSdMountToggleAdded = true;
122 // Access using getMountService()
123 private IMountService mMountService = null;
125 private StorageManager mStorageManager = null;
127 // Updates the memory usage bar graph.
128 private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
130 // Updates the memory usage bar graph.
131 private static final int MSG_UI_UPDATE_EXACT = 2;
133 private Handler mUpdateHandler = new Handler() {
135 public void handleMessage(Message msg) {
137 case MSG_UI_UPDATE_APPROXIMATE:
138 updateUiApproximate();
140 case MSG_UI_UPDATE_EXACT:
148 private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
150 private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
151 DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
153 class MemoryMeasurementHandler extends Handler {
154 public static final int MSG_MEASURE_ALL = 1;
156 public static final int MSG_CONNECTED = 2;
158 public static final int MSG_DISCONNECTED = 3;
160 private List<String> mPendingApps = new ArrayList<String>();
162 private volatile boolean mBound = false;
164 private long mAppsSize = 0;
166 final private ServiceConnection mDefContainerConn = new ServiceConnection() {
167 public void onServiceConnected(ComponentName name, IBinder service) {
169 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
170 mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
171 MemoryMeasurementHandler.MSG_CONNECTED, imcs));
174 public void onServiceDisconnected(ComponentName name) {
179 MemoryMeasurementHandler(Looper looper) {
184 public void handleMessage(Message msg) {
186 case MSG_MEASURE_ALL: {
187 updateExternalStorage();
188 updateApproximateInternalStorage();
190 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
191 getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
193 mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
196 case MSG_CONNECTED: {
197 IMediaContainerService imcs = (IMediaContainerService) msg.obj;
198 updateExactInternalStorage(imcs);
203 public void cleanUp() {
205 getActivity().unbindService(mDefContainerConn);
209 public void queuePackageMeasurementLocked(String packageName) {
210 mPendingApps.add(packageName);
213 public void requestQueuedMeasurementsLocked() {
214 final Activity activity = getActivity();
215 if (activity == null) {
219 final PackageManager pm = activity.getPackageManager();
224 final int N = mPendingApps.size();
225 for (int i = 0; i < N; i++) {
226 pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
230 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
231 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
233 mAppsSize += stats.codeSize + stats.dataSize;
236 synchronized (mPendingApps) {
237 mPendingApps.remove(stats.packageName);
239 if (mPendingApps.size() == 0) {
240 mInternalAppsSize = mAppsSize;
242 mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
248 private void updateApproximateInternalStorage() {
249 final File dataPath = Environment.getDataDirectory();
250 final StatFs stat = new StatFs(dataPath.getPath());
251 final long blockSize = stat.getBlockSize();
252 final long totalBlocks = stat.getBlockCount();
253 final long availableBlocks = stat.getAvailableBlocks();
255 final long totalSize = totalBlocks * blockSize;
256 final long availSize = availableBlocks * blockSize;
257 mInternalSize.setSummary(formatSize(totalSize));
258 mInternalAvail.setSummary(formatSize(availSize));
260 mInternalTotalSize = totalSize;
261 mInternalUsedSize = totalSize - availSize;
264 private void updateExactInternalStorage(IMediaContainerService imcs) {
267 // TODO get these directories from somewhere
268 mediaSize = imcs.calculateDirectorySize("/data/media");
269 } catch (Exception e) {
270 Log.i(TAG, "Could not read memory from default container service");
274 mInternalMediaSize = mediaSize;
276 // We have to get installd to measure the package sizes.
277 PackageManager pm = getPackageManager();
278 List<ApplicationInfo> apps = pm
279 .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
280 | PackageManager.GET_DISABLED_COMPONENTS);
282 synchronized (mPendingApps) {
283 for (int i = 0; i < apps.size(); i++) {
284 final ApplicationInfo info = apps.get(i);
285 queuePackageMeasurementLocked(info.packageName);
288 requestQueuedMeasurementsLocked();
293 private void updateExternalStorage() {
294 if (Environment.isExternalStorageEmulated()) {
298 String status = Environment.getExternalStorageState();
299 String readOnly = "";
300 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
301 status = Environment.MEDIA_MOUNTED;
302 readOnly = mRes.getString(R.string.read_only);
305 if (status.equals(Environment.MEDIA_MOUNTED)) {
306 if (!Environment.isExternalStorageRemovable()) {
307 // This device has built-in storage that is not removable.
308 // There is no reason for the user to unmount it.
309 if (mSdMountToggleAdded) {
310 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
311 mSdMountToggleAdded = false;
315 File path = Environment.getExternalStorageDirectory();
316 StatFs stat = new StatFs(path.getPath());
317 long blockSize = stat.getBlockSize();
318 long totalBlocks = stat.getBlockCount();
319 long availableBlocks = stat.getAvailableBlocks();
321 mSdSize.setSummary(formatSize(totalBlocks * blockSize));
322 mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
324 mSdMountToggle.setEnabled(true);
325 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
326 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
328 } catch (IllegalArgumentException e) {
329 // this can occur if the SD card is removed, but we haven't
331 // ACTION_MEDIA_REMOVED Intent yet.
332 status = Environment.MEDIA_REMOVED;
335 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
336 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
338 if (!Environment.isExternalStorageRemovable()) {
339 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
340 if (!mSdMountToggleAdded) {
341 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
342 mSdMountToggleAdded = true;
347 if (status.equals(Environment.MEDIA_UNMOUNTED) ||
348 status.equals(Environment.MEDIA_NOFS) ||
349 status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
350 mSdMountToggle.setEnabled(true);
351 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
352 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
354 mSdMountToggle.setEnabled(false);
355 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
356 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
362 private MemoryMeasurementHandler mMeasurementHandler;
365 public void onCreate(Bundle icicle) {
366 super.onCreate(icicle);
368 if (mStorageManager == null) {
369 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
370 mStorageManager.registerListener(mStorageListener);
373 addPreferencesFromResource(R.xml.device_info_memory);
375 mRes = getResources();
376 mSdSize = findPreference(MEMORY_SD_SIZE);
377 mSdAvail = findPreference(MEMORY_SD_AVAIL);
378 mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE);
379 mSdFormat = findPreference(MEMORY_SD_FORMAT);
380 mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
382 if (Environment.isExternalStorageEmulated()) {
383 mSdMountPreferenceGroup.removePreference(mSdSize);
384 mSdMountPreferenceGroup.removePreference(mSdAvail);
385 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
388 mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
389 mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
390 mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
391 mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
393 mInternalMediaColor = mRes.getColor(R.color.memory_media_usage);
394 mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
395 mInternalUsedColor = mRes.getColor(R.color.memory_used);
397 mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
399 // Start the thread that will measure the disk usage.
400 final HandlerThread t = new HandlerThread("MeasurementHandler");
402 mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
406 public void onResume() {
409 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
410 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
411 intentFilter.addDataScheme("file");
412 getActivity().registerReceiver(mReceiver, intentFilter);
415 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
419 StorageEventListener mStorageListener = new StorageEventListener() {
421 public void onStorageStateChanged(String path, String oldState, String newState) {
422 Log.i(TAG, "Received storage state changed notification that " +
423 path + " changed state from " + oldState +
425 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
430 public void onPause() {
432 getActivity().unregisterReceiver(mReceiver);
433 mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL);
437 public void onDestroy() {
438 if (mStorageManager != null && mStorageListener != null) {
439 mStorageManager.unregisterListener(mStorageListener);
441 mMeasurementHandler.cleanUp();
445 private synchronized IMountService getMountService() {
446 if (mMountService == null) {
447 IBinder service = ServiceManager.getService("mount");
448 if (service != null) {
449 mMountService = IMountService.Stub.asInterface(service);
451 Log.e(TAG, "Can't get mount service");
454 return mMountService;
458 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
459 if (preference == mSdMountToggle) {
460 String status = Environment.getExternalStorageState();
461 if (status.equals(Environment.MEDIA_MOUNTED)) {
467 } else if (preference == mSdFormat) {
468 Intent intent = new Intent(Intent.ACTION_VIEW);
469 intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
470 startActivity(intent);
477 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
479 public void onReceive(Context context, Intent intent) {
480 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
485 public Dialog onCreateDialog(int id) {
487 case DLG_CONFIRM_UNMOUNT:
488 return new AlertDialog.Builder(getActivity())
489 .setTitle(R.string.dlg_confirm_unmount_title)
490 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
491 public void onClick(DialogInterface dialog, int which) {
494 .setNegativeButton(R.string.cancel, null)
495 .setMessage(R.string.dlg_confirm_unmount_text)
496 .setOnCancelListener(this)
498 case DLG_ERROR_UNMOUNT:
499 return new AlertDialog.Builder(getActivity())
500 .setTitle(R.string.dlg_error_unmount_title)
501 .setNeutralButton(R.string.dlg_ok, null)
502 .setMessage(R.string.dlg_error_unmount_text)
503 .setOnCancelListener(this)
509 private void doUnmount(boolean force) {
510 // Present a toast here
511 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
512 IMountService mountService = getMountService();
513 String extStoragePath = Environment.getExternalStorageDirectory().toString();
515 mSdMountToggle.setEnabled(false);
516 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
517 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
518 mountService.unmountVolume(extStoragePath, force);
519 } catch (RemoteException e) {
520 // Informative dialog to user that
522 showDialogInner(DLG_ERROR_UNMOUNT);
526 private void showDialogInner(int id) {
531 private boolean hasAppsAccessingStorage() throws RemoteException {
532 String extStoragePath = Environment.getExternalStorageDirectory().toString();
533 IMountService mountService = getMountService();
534 int stUsers[] = mountService.getStorageUsers(extStoragePath);
535 if (stUsers != null && stUsers.length > 0) {
538 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
539 List<ApplicationInfo> list = am.getRunningExternalApplications();
540 if (list != null && list.size() > 0) {
546 private void unmount() {
547 // Check if external media is in use.
549 if (hasAppsAccessingStorage()) {
550 if (localLOGV) Log.i(TAG, "Do have storage users accessing media");
551 // Present dialog to user
552 showDialogInner(DLG_CONFIRM_UNMOUNT);
556 } catch (RemoteException e) {
557 // Very unlikely. But present an error dialog anyway
558 Log.e(TAG, "Is MountService running?");
559 showDialogInner(DLG_ERROR_UNMOUNT);
563 private void mount() {
564 IMountService mountService = getMountService();
566 if (mountService != null) {
567 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
569 Log.e(TAG, "Mount service is null, can't mount");
571 } catch (RemoteException ex) {
575 private void updateUiExact() {
576 final float totalSize = mInternalTotalSize;
578 final long mediaSize = mInternalMediaSize;
579 final long appsSize = mInternalAppsSize;
581 mInternalUsageChart.clear();
582 mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor);
583 mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor);
585 // There are other things that can take up storage, but we didn't
587 final long remaining = mInternalUsedSize - (mediaSize + appsSize);
589 mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor);
591 mInternalUsageChart.commit();
593 mInternalMediaUsage.setSummary(formatSize(mediaSize));
594 mInternalAppsUsage.setSummary(formatSize(appsSize));
597 private void updateUiApproximate() {
598 mInternalUsageChart.clear();
599 mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources()
600 .getColor(R.color.memory_used));
601 mInternalUsageChart.commit();
604 private String formatSize(long size) {
605 return Formatter.formatFileSize(getActivity(), size);
608 public void onCancel(DialogInterface dialog) {
609 // TODO: Is this really required?