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.Usb;
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.CheckBoxPreference;
55 import android.preference.Preference;
56 import android.preference.PreferenceGroup;
57 import android.preference.PreferenceScreen;
58 import android.provider.Settings;
59 import android.text.format.Formatter;
60 import android.util.Log;
61 import android.widget.Toast;
64 import java.util.ArrayList;
65 import java.util.List;
67 public class Memory extends SettingsPreferenceFragment implements OnCancelListener {
68 private static final String TAG = "Memory";
69 private static final boolean localLOGV = false;
71 private static final String MEMORY_SD_SIZE = "memory_sd_size";
73 private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
75 private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
77 private static final String MEMORY_SD_FORMAT = "memory_sd_format";
79 private static final String MEMORY_SD_GROUP = "memory_sd";
81 private static final String PTP_MODE_TOGGLE = "ptp_mode_toggle";
83 private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
85 private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
87 private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
89 private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media";
91 private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
93 private static final int DLG_CONFIRM_UNMOUNT = 1;
94 private static final int DLG_ERROR_UNMOUNT = 2;
96 private Resources mRes;
98 // External storage preferences
99 private Preference mSdSize;
100 private Preference mSdAvail;
101 private Preference mSdMountToggle;
102 private Preference mSdFormat;
103 private PreferenceGroup mSdMountPreferenceGroup;
105 // Internal storage preferences
106 private Preference mInternalSize;
107 private Preference mInternalAvail;
108 private Preference mInternalMediaUsage;
109 private Preference mInternalAppsUsage;
110 private UsageBarPreference mInternalUsageChart;
112 // Internal storage chart colors
113 private int mInternalMediaColor;
114 private int mInternalAppsColor;
115 private int mInternalUsedColor;
117 // Internal memory fields
118 private long mInternalTotalSize;
119 private long mInternalUsedSize;
120 private long mInternalMediaSize;
121 private long mInternalAppsSize;
122 private boolean mMeasured = false;
124 boolean mSdMountToggleAdded = true;
126 private CheckBoxPreference mPtpModeToggle;
128 // Access using getMountService()
129 private IMountService mMountService = null;
131 private StorageManager mStorageManager = null;
133 // Updates the memory usage bar graph.
134 private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
136 // Updates the memory usage bar graph.
137 private static final int MSG_UI_UPDATE_EXACT = 2;
139 private Handler mUpdateHandler = new Handler() {
141 public void handleMessage(Message msg) {
143 case MSG_UI_UPDATE_APPROXIMATE:
144 updateUiApproximate();
146 case MSG_UI_UPDATE_EXACT:
154 private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
156 private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
157 DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
159 class MemoryMeasurementHandler extends Handler {
160 public static final int MSG_MEASURE_ALL = 1;
162 public static final int MSG_CONNECTED = 2;
164 public static final int MSG_DISCONNECTED = 3;
166 private List<String> mPendingApps = new ArrayList<String>();
168 private volatile boolean mBound = false;
170 private long mAppsSize = 0;
172 final private ServiceConnection mDefContainerConn = new ServiceConnection() {
173 public void onServiceConnected(ComponentName name, IBinder service) {
175 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
176 mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
177 MemoryMeasurementHandler.MSG_CONNECTED, imcs));
180 public void onServiceDisconnected(ComponentName name) {
185 MemoryMeasurementHandler(Looper looper) {
190 public void handleMessage(Message msg) {
192 case MSG_MEASURE_ALL: {
193 updateExternalStorage();
194 updateApproximateInternalStorage();
196 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
197 getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
199 mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
202 case MSG_CONNECTED: {
203 IMediaContainerService imcs = (IMediaContainerService) msg.obj;
204 updateExactInternalStorage(imcs);
209 public void cleanUp() {
211 getActivity().unbindService(mDefContainerConn);
215 public void queuePackageMeasurementLocked(String packageName) {
216 mPendingApps.add(packageName);
219 public void requestQueuedMeasurementsLocked() {
220 final Activity activity = getActivity();
221 if (activity == null) {
225 final PackageManager pm = activity.getPackageManager();
230 final int N = mPendingApps.size();
231 for (int i = 0; i < N; i++) {
232 pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
236 final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
237 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
239 mAppsSize += stats.codeSize + stats.dataSize;
242 synchronized (mPendingApps) {
243 mPendingApps.remove(stats.packageName);
245 if (mPendingApps.size() == 0) {
246 mInternalAppsSize = mAppsSize;
248 mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
254 private void updateApproximateInternalStorage() {
255 final File dataPath = Environment.getDataDirectory();
256 final StatFs stat = new StatFs(dataPath.getPath());
257 final long blockSize = stat.getBlockSize();
258 final long totalBlocks = stat.getBlockCount();
259 final long availableBlocks = stat.getAvailableBlocks();
261 final long totalSize = totalBlocks * blockSize;
262 final long availSize = availableBlocks * blockSize;
263 mInternalSize.setSummary(formatSize(totalSize));
264 mInternalAvail.setSummary(formatSize(availSize));
266 mInternalTotalSize = totalSize;
267 mInternalUsedSize = totalSize - availSize;
270 private void updateExactInternalStorage(IMediaContainerService imcs) {
273 // TODO get these directories from somewhere
274 mediaSize = imcs.calculateDirectorySize("/data/media");
275 } catch (Exception e) {
276 Log.i(TAG, "Could not read memory from default container service");
280 mInternalMediaSize = mediaSize;
282 // We have to get installd to measure the package sizes.
283 PackageManager pm = getPackageManager();
284 List<ApplicationInfo> apps = pm
285 .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
286 | PackageManager.GET_DISABLED_COMPONENTS);
288 synchronized (mPendingApps) {
289 for (int i = 0; i < apps.size(); i++) {
290 final ApplicationInfo info = apps.get(i);
291 queuePackageMeasurementLocked(info.packageName);
294 requestQueuedMeasurementsLocked();
299 private void updateExternalStorage() {
300 if (Environment.isExternalStorageEmulated()) {
304 String status = Environment.getExternalStorageState();
305 String readOnly = "";
306 if (status.equals(Environment.MEDIA_MOUNTED_READ_ONLY)) {
307 status = Environment.MEDIA_MOUNTED;
308 readOnly = mRes.getString(R.string.read_only);
311 if (status.equals(Environment.MEDIA_MOUNTED)) {
312 if (!Environment.isExternalStorageRemovable()) {
313 // This device has built-in storage that is not removable.
314 // There is no reason for the user to unmount it.
315 if (mSdMountToggleAdded) {
316 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
317 mSdMountToggleAdded = false;
321 File path = Environment.getExternalStorageDirectory();
322 StatFs stat = new StatFs(path.getPath());
323 long blockSize = stat.getBlockSize();
324 long totalBlocks = stat.getBlockCount();
325 long availableBlocks = stat.getAvailableBlocks();
327 mSdSize.setSummary(formatSize(totalBlocks * blockSize));
328 mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
330 mSdMountToggle.setEnabled(true);
331 mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
332 mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
334 } catch (IllegalArgumentException e) {
335 // this can occur if the SD card is removed, but we haven't
337 // ACTION_MEDIA_REMOVED Intent yet.
338 status = Environment.MEDIA_REMOVED;
341 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
342 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
344 if (!Environment.isExternalStorageRemovable()) {
345 if (status.equals(Environment.MEDIA_UNMOUNTED)) {
346 if (!mSdMountToggleAdded) {
347 mSdMountPreferenceGroup.addPreference(mSdMountToggle);
348 mSdMountToggleAdded = true;
353 if (status.equals(Environment.MEDIA_UNMOUNTED) ||
354 status.equals(Environment.MEDIA_NOFS) ||
355 status.equals(Environment.MEDIA_UNMOUNTABLE) ) {
356 mSdMountToggle.setEnabled(true);
357 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
358 mSdMountToggle.setSummary(mRes.getString(R.string.sd_mount_summary));
360 mSdMountToggle.setEnabled(false);
361 mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
362 mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
368 private MemoryMeasurementHandler mMeasurementHandler;
371 public void onCreate(Bundle icicle) {
372 super.onCreate(icicle);
374 if (mStorageManager == null) {
375 mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
376 mStorageManager.registerListener(mStorageListener);
379 addPreferencesFromResource(R.xml.device_info_memory);
381 mRes = getResources();
382 mSdSize = findPreference(MEMORY_SD_SIZE);
383 mSdAvail = findPreference(MEMORY_SD_AVAIL);
384 mSdMountToggle = findPreference(MEMORY_SD_MOUNT_TOGGLE);
385 mSdFormat = findPreference(MEMORY_SD_FORMAT);
386 mSdMountPreferenceGroup = (PreferenceGroup)findPreference(MEMORY_SD_GROUP);
388 if (Environment.isExternalStorageEmulated()) {
389 mSdMountPreferenceGroup.removePreference(mSdSize);
390 mSdMountPreferenceGroup.removePreference(mSdAvail);
391 mSdMountPreferenceGroup.removePreference(mSdMountToggle);
394 mPtpModeToggle = (CheckBoxPreference)findPreference(PTP_MODE_TOGGLE);
395 if (Usb.isFunctionSupported(Usb.USB_FUNCTION_MTP)) {
396 mPtpModeToggle.setChecked(Settings.System.getInt(
397 getContentResolver(),
398 Settings.System.USE_PTP_INTERFACE, 0) != 0);
400 // hide the PTP mode toggle checkbox if MTP is not supported
401 getPreferenceScreen().removePreference(mPtpModeToggle);
404 mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
405 mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
406 mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
407 mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
409 mInternalMediaColor = mRes.getColor(R.color.memory_media_usage);
410 mInternalAppsColor = mRes.getColor(R.color.memory_apps_usage);
411 mInternalUsedColor = mRes.getColor(R.color.memory_used);
413 mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
415 // Start the thread that will measure the disk usage.
416 final HandlerThread t = new HandlerThread("MeasurementHandler");
418 mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
422 public void onResume() {
425 IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MEDIA_SCANNER_STARTED);
426 intentFilter.addAction(Intent.ACTION_MEDIA_SCANNER_FINISHED);
427 intentFilter.addDataScheme("file");
428 getActivity().registerReceiver(mReceiver, intentFilter);
431 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
435 StorageEventListener mStorageListener = new StorageEventListener() {
437 public void onStorageStateChanged(String path, String oldState, String newState) {
438 Log.i(TAG, "Received storage state changed notification that " +
439 path + " changed state from " + oldState +
441 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
446 public void onPause() {
448 getActivity().unregisterReceiver(mReceiver);
449 mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL);
453 public void onDestroy() {
454 if (mStorageManager != null && mStorageListener != null) {
455 mStorageManager.unregisterListener(mStorageListener);
457 mMeasurementHandler.cleanUp();
461 private synchronized IMountService getMountService() {
462 if (mMountService == null) {
463 IBinder service = ServiceManager.getService("mount");
464 if (service != null) {
465 mMountService = IMountService.Stub.asInterface(service);
467 Log.e(TAG, "Can't get mount service");
470 return mMountService;
474 public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
475 if (preference == mSdMountToggle) {
476 String status = Environment.getExternalStorageState();
477 if (status.equals(Environment.MEDIA_MOUNTED)) {
483 } else if (preference == mSdFormat) {
484 Intent intent = new Intent(Intent.ACTION_VIEW);
485 intent.setClass(getActivity(), com.android.settings.MediaFormat.class);
486 startActivity(intent);
488 } else if (preference == mPtpModeToggle) {
489 Settings.System.putInt(getContentResolver(),
490 Settings.System.USE_PTP_INTERFACE,
491 mPtpModeToggle.isChecked() ? 1 : 0);
498 private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
500 public void onReceive(Context context, Intent intent) {
501 mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
506 public Dialog onCreateDialog(int id) {
508 case DLG_CONFIRM_UNMOUNT:
509 return new AlertDialog.Builder(getActivity())
510 .setTitle(R.string.dlg_confirm_unmount_title)
511 .setPositiveButton(R.string.dlg_ok, new DialogInterface.OnClickListener() {
512 public void onClick(DialogInterface dialog, int which) {
515 .setNegativeButton(R.string.cancel, null)
516 .setMessage(R.string.dlg_confirm_unmount_text)
517 .setOnCancelListener(this)
519 case DLG_ERROR_UNMOUNT:
520 return new AlertDialog.Builder(getActivity())
521 .setTitle(R.string.dlg_error_unmount_title)
522 .setNeutralButton(R.string.dlg_ok, null)
523 .setMessage(R.string.dlg_error_unmount_text)
524 .setOnCancelListener(this)
530 private void doUnmount(boolean force) {
531 // Present a toast here
532 Toast.makeText(getActivity(), R.string.unmount_inform_text, Toast.LENGTH_SHORT).show();
533 IMountService mountService = getMountService();
534 String extStoragePath = Environment.getExternalStorageDirectory().toString();
536 mSdMountToggle.setEnabled(false);
537 mSdMountToggle.setTitle(mRes.getString(R.string.sd_ejecting_title));
538 mSdMountToggle.setSummary(mRes.getString(R.string.sd_ejecting_summary));
539 mountService.unmountVolume(extStoragePath, force);
540 } catch (RemoteException e) {
541 // Informative dialog to user that
543 showDialogInner(DLG_ERROR_UNMOUNT);
547 private void showDialogInner(int id) {
552 private boolean hasAppsAccessingStorage() throws RemoteException {
553 String extStoragePath = Environment.getExternalStorageDirectory().toString();
554 IMountService mountService = getMountService();
555 int stUsers[] = mountService.getStorageUsers(extStoragePath);
556 if (stUsers != null && stUsers.length > 0) {
559 ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
560 List<ApplicationInfo> list = am.getRunningExternalApplications();
561 if (list != null && list.size() > 0) {
567 private void unmount() {
568 // Check if external media is in use.
570 if (hasAppsAccessingStorage()) {
571 if (localLOGV) Log.i(TAG, "Do have storage users accessing media");
572 // Present dialog to user
573 showDialogInner(DLG_CONFIRM_UNMOUNT);
577 } catch (RemoteException e) {
578 // Very unlikely. But present an error dialog anyway
579 Log.e(TAG, "Is MountService running?");
580 showDialogInner(DLG_ERROR_UNMOUNT);
584 private void mount() {
585 IMountService mountService = getMountService();
587 if (mountService != null) {
588 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
590 Log.e(TAG, "Mount service is null, can't mount");
592 } catch (RemoteException ex) {
596 private void updateUiExact() {
597 final float totalSize = mInternalTotalSize;
599 final long mediaSize = mInternalMediaSize;
600 final long appsSize = mInternalAppsSize;
602 mInternalUsageChart.clear();
603 mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor);
604 mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor);
606 // There are other things that can take up storage, but we didn't
608 final long remaining = mInternalUsedSize - (mediaSize + appsSize);
610 mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor);
612 mInternalUsageChart.commit();
614 mInternalMediaUsage.setSummary(formatSize(mediaSize));
615 mInternalAppsUsage.setSummary(formatSize(appsSize));
618 private void updateUiApproximate() {
619 mInternalUsageChart.clear();
620 mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources()
621 .getColor(R.color.memory_used));
622 mInternalUsageChart.commit();
625 private String formatSize(long size) {
626 return Formatter.formatFileSize(getActivity(), size);
629 public void onCancel(DialogInterface dialog) {
630 // TODO: Is this really required?