OSDN Git Service

Add usage bar chart to storage preferences
[android-x86/packages-apps-Settings.git] / src / com / android / settings / deviceinfo / Memory.java
1 /*
2  * Copyright (C) 2008 The Android Open Source Project
3  *
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
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
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.
15  */
16
17 package com.android.settings.deviceinfo;
18
19 import com.android.internal.app.IMediaContainerService;
20 import com.android.settings.R;
21 import com.android.settings.SettingsPreferenceFragment;
22
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;
62
63 import java.io.File;
64 import java.util.ArrayList;
65 import java.util.List;
66
67 public class Memory extends SettingsPreferenceFragment implements OnCancelListener {
68     private static final String TAG = "Memory";
69     private static final boolean localLOGV = false;
70
71     private static final String MEMORY_SD_SIZE = "memory_sd_size";
72
73     private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
74
75     private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
76
77     private static final String MEMORY_SD_FORMAT = "memory_sd_format";
78
79     private static final String MEMORY_SD_GROUP = "memory_sd";
80
81     private static final String PTP_MODE_TOGGLE = "ptp_mode_toggle";
82
83     private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
84
85     private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
86
87     private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
88
89     private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media";
90
91     private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
92
93     private static final int DLG_CONFIRM_UNMOUNT = 1;
94     private static final int DLG_ERROR_UNMOUNT = 2;
95
96     private Resources mRes;
97
98     // External storage preferences
99     private Preference mSdSize;
100     private Preference mSdAvail;
101     private Preference mSdMountToggle;
102     private Preference mSdFormat;
103     private PreferenceGroup mSdMountPreferenceGroup;
104
105     // Internal storage preferences
106     private Preference mInternalSize;
107     private Preference mInternalAvail;
108     private Preference mInternalMediaUsage;
109     private Preference mInternalAppsUsage;
110     private UsageBarPreference mInternalUsageChart;
111
112     // Internal storage chart colors
113     private int mInternalMediaColor;
114     private int mInternalAppsColor;
115     private int mInternalUsedColor;
116
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;
123
124     boolean mSdMountToggleAdded = true;
125
126     private CheckBoxPreference mPtpModeToggle;
127     
128     // Access using getMountService()
129     private IMountService mMountService = null;
130
131     private StorageManager mStorageManager = null;
132
133     // Updates the memory usage bar graph.
134     private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
135
136     // Updates the memory usage bar graph.
137     private static final int MSG_UI_UPDATE_EXACT = 2;
138
139     private Handler mUpdateHandler = new Handler() {
140         @Override
141         public void handleMessage(Message msg) {
142             switch (msg.what) {
143                 case MSG_UI_UPDATE_APPROXIMATE:
144                     updateUiApproximate();
145                     break;
146                 case MSG_UI_UPDATE_EXACT:
147                     updateUiExact();
148                     mMeasured = true;
149                     break;
150             }
151         }
152     };
153
154     private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
155
156     private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
157             DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
158
159     class MemoryMeasurementHandler extends Handler {
160         public static final int MSG_MEASURE_ALL = 1;
161
162         public static final int MSG_CONNECTED = 2;
163
164         public static final int MSG_DISCONNECTED = 3;
165
166         private List<String> mPendingApps = new ArrayList<String>();
167
168         private volatile boolean mBound = false;
169
170         private long mAppsSize = 0;
171
172         final private ServiceConnection mDefContainerConn = new ServiceConnection() {
173             public void onServiceConnected(ComponentName name, IBinder service) {
174                 mBound = true;
175                 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
176                 mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
177                         MemoryMeasurementHandler.MSG_CONNECTED, imcs));
178             }
179
180             public void onServiceDisconnected(ComponentName name) {
181                 mBound = false;
182             }
183         };
184
185         MemoryMeasurementHandler(Looper looper) {
186             super(looper);
187         }
188
189         @Override
190         public void handleMessage(Message msg) {
191             switch (msg.what) {
192                 case MSG_MEASURE_ALL: {
193                     updateExternalStorage();
194                     updateApproximateInternalStorage();
195
196                     Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
197                     getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
198
199                     mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
200                     break;
201                 }
202                 case MSG_CONNECTED: {
203                     IMediaContainerService imcs = (IMediaContainerService) msg.obj;
204                     updateExactInternalStorage(imcs);
205                 }
206             }
207         }
208
209         public void cleanUp() {
210             if (mBound) {
211                 getActivity().unbindService(mDefContainerConn);
212             }
213         }
214
215         public void queuePackageMeasurementLocked(String packageName) {
216             mPendingApps.add(packageName);
217         }
218
219         public void requestQueuedMeasurementsLocked() {
220             final Activity activity = getActivity();
221             if (activity == null) {
222                 return;
223             }
224
225             final PackageManager pm = activity.getPackageManager();
226             if (pm == null) {
227                 return;
228             }
229
230             final int N = mPendingApps.size();
231             for (int i = 0; i < N; i++) {
232                 pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
233             }
234         }
235
236         final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
237             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
238                 if (succeeded) {
239                     mAppsSize += stats.codeSize + stats.dataSize;
240                 }
241
242                 synchronized (mPendingApps) {
243                     mPendingApps.remove(stats.packageName);
244
245                     if (mPendingApps.size() == 0) {
246                         mInternalAppsSize = mAppsSize;
247
248                         mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
249                     }
250                 }
251             }
252         };
253
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();
260
261             final long totalSize = totalBlocks * blockSize;
262             final long availSize = availableBlocks * blockSize;
263             mInternalSize.setSummary(formatSize(totalSize));
264             mInternalAvail.setSummary(formatSize(availSize));
265
266             mInternalTotalSize = totalSize;
267             mInternalUsedSize = totalSize - availSize;
268         }
269
270         private void updateExactInternalStorage(IMediaContainerService imcs) {
271             long mediaSize;
272             try {
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");
277                 return;
278             }
279
280             mInternalMediaSize = mediaSize;
281
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);
287             if (apps != null) {
288                 synchronized (mPendingApps) {
289                     for (int i = 0; i < apps.size(); i++) {
290                         final ApplicationInfo info = apps.get(i);
291                         queuePackageMeasurementLocked(info.packageName);
292                     }
293
294                     requestQueuedMeasurementsLocked();
295                 }
296             }
297         }
298
299         private void updateExternalStorage() {
300             if (Environment.isExternalStorageEmulated()) {
301                 return;
302             }
303
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);
309             }
310
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;
318                     }
319                 }
320                 try {
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();
326
327                     mSdSize.setSummary(formatSize(totalBlocks * blockSize));
328                     mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
329
330                     mSdMountToggle.setEnabled(true);
331                     mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
332                     mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
333
334                 } catch (IllegalArgumentException e) {
335                     // this can occur if the SD card is removed, but we haven't
336                     // received the
337                     // ACTION_MEDIA_REMOVED Intent yet.
338                     status = Environment.MEDIA_REMOVED;
339                 }
340             } else {
341                 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
342                 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
343
344                 if (!Environment.isExternalStorageRemovable()) {
345                     if (status.equals(Environment.MEDIA_UNMOUNTED)) {
346                         if (!mSdMountToggleAdded) {
347                             mSdMountPreferenceGroup.addPreference(mSdMountToggle);
348                             mSdMountToggleAdded = true;
349                         }
350                     }
351                 }
352
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));
359                 } else {
360                     mSdMountToggle.setEnabled(false);
361                     mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
362                     mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
363                 }
364             }
365         }
366     }
367
368     private MemoryMeasurementHandler mMeasurementHandler;
369
370     @Override
371     public void onCreate(Bundle icicle) {
372         super.onCreate(icicle);
373
374         if (mStorageManager == null) {
375             mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
376             mStorageManager.registerListener(mStorageListener);
377         }
378
379         addPreferencesFromResource(R.xml.device_info_memory);
380
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);
387
388         if (Environment.isExternalStorageEmulated()) {
389             mSdMountPreferenceGroup.removePreference(mSdSize);
390             mSdMountPreferenceGroup.removePreference(mSdAvail);
391             mSdMountPreferenceGroup.removePreference(mSdMountToggle);
392         }
393
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);
399         } else {
400             // hide the PTP mode toggle checkbox if MTP is not supported
401             getPreferenceScreen().removePreference(mPtpModeToggle);
402         }
403
404         mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
405         mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
406         mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
407         mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
408
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);
412
413         mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
414
415         // Start the thread that will measure the disk usage.
416         final HandlerThread t = new HandlerThread("MeasurementHandler");
417         t.start();
418         mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
419     }
420
421     @Override
422     public void onResume() {
423         super.onResume();
424
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);
429
430         if (!mMeasured) {
431             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
432         }
433     }
434
435     StorageEventListener mStorageListener = new StorageEventListener() {
436         @Override
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 +
440                     " to " + newState);
441             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
442         }
443     };
444
445     @Override
446     public void onPause() {
447         super.onPause();
448         getActivity().unregisterReceiver(mReceiver);
449         mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL);
450     }
451
452     @Override
453     public void onDestroy() {
454         if (mStorageManager != null && mStorageListener != null) {
455             mStorageManager.unregisterListener(mStorageListener);
456         }
457         mMeasurementHandler.cleanUp();
458         super.onDestroy();
459     }
460
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);
466            } else {
467                Log.e(TAG, "Can't get mount service");
468            }
469        }
470        return mMountService;
471     }
472     
473     @Override
474     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
475         if (preference == mSdMountToggle) {
476             String status = Environment.getExternalStorageState();
477             if (status.equals(Environment.MEDIA_MOUNTED)) {
478                 unmount();
479             } else {
480                 mount();
481             }
482             return true;
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);
487             return true;
488         } else if (preference == mPtpModeToggle) {
489             Settings.System.putInt(getContentResolver(),
490                     Settings.System.USE_PTP_INTERFACE,
491                     mPtpModeToggle.isChecked() ? 1 : 0);
492             return true;
493         }
494
495         return false;
496     }
497      
498     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
499         @Override
500         public void onReceive(Context context, Intent intent) {
501             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
502         }
503     };
504
505     @Override
506     public Dialog onCreateDialog(int id) {
507         switch (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) {
513                             doUnmount(true);
514                         }})
515                     .setNegativeButton(R.string.cancel, null)
516                     .setMessage(R.string.dlg_confirm_unmount_text)
517                     .setOnCancelListener(this)
518                     .create();
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)
525             .create();
526         }
527         return null;
528     }
529
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();
535         try {
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
542             // unmount failed.
543             showDialogInner(DLG_ERROR_UNMOUNT);
544         }
545     }
546
547     private void showDialogInner(int id) {
548         removeDialog(id);
549         showDialog(id);
550     }
551
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) {
557             return true;
558         }
559         ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
560         List<ApplicationInfo> list = am.getRunningExternalApplications();
561         if (list != null && list.size() > 0) {
562             return true;
563         }
564         return false;
565     }
566
567     private void unmount() {
568         // Check if external media is in use.
569         try {
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);
574            } else {
575                doUnmount(true);
576            }
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);
581         }
582     }
583
584     private void mount() {
585         IMountService mountService = getMountService();
586         try {
587             if (mountService != null) {
588                 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
589             } else {
590                 Log.e(TAG, "Mount service is null, can't mount");
591             }
592         } catch (RemoteException ex) {
593         }
594     }
595
596     private void updateUiExact() {
597         final float totalSize = mInternalTotalSize;
598
599         final long mediaSize = mInternalMediaSize;
600         final long appsSize = mInternalAppsSize;
601
602         mInternalUsageChart.clear();
603         mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor);
604         mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor);
605
606         // There are other things that can take up storage, but we didn't
607         // measure it.
608         final long remaining = mInternalUsedSize - (mediaSize + appsSize);
609         if (remaining > 0) {
610             mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor);
611         }
612         mInternalUsageChart.commit();
613
614         mInternalMediaUsage.setSummary(formatSize(mediaSize));
615         mInternalAppsUsage.setSummary(formatSize(appsSize));
616     }
617
618     private void updateUiApproximate() {
619         mInternalUsageChart.clear();
620         mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources()
621                 .getColor(R.color.memory_used));
622         mInternalUsageChart.commit();
623     }
624
625     private String formatSize(long size) {
626         return Formatter.formatFileSize(getActivity(), size);
627     }
628
629     public void onCancel(DialogInterface dialog) {
630         // TODO: Is this really required?
631         // finish();
632     }
633 }