OSDN Git Service

Merge "Fix 3148496: Update LockScreen layouts on large device"
[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.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;
60
61 import java.io.File;
62 import java.util.ArrayList;
63 import java.util.List;
64
65 public class Memory extends SettingsPreferenceFragment implements OnCancelListener {
66     private static final String TAG = "Memory";
67     private static final boolean localLOGV = false;
68
69     private static final String MEMORY_SD_SIZE = "memory_sd_size";
70
71     private static final String MEMORY_SD_AVAIL = "memory_sd_avail";
72
73     private static final String MEMORY_SD_MOUNT_TOGGLE = "memory_sd_mount_toggle";
74
75     private static final String MEMORY_SD_FORMAT = "memory_sd_format";
76
77     private static final String MEMORY_SD_GROUP = "memory_sd";
78
79     private static final String MEMORY_INTERNAL_SIZE = "memory_internal_size";
80
81     private static final String MEMORY_INTERNAL_AVAIL = "memory_internal_avail";
82
83     private static final String MEMORY_INTERNAL_APPS = "memory_internal_apps";
84
85     private static final String MEMORY_INTERNAL_MEDIA = "memory_internal_media";
86
87     private static final String MEMORY_INTERNAL_CHART = "memory_internal_chart";
88
89     private static final int DLG_CONFIRM_UNMOUNT = 1;
90     private static final int DLG_ERROR_UNMOUNT = 2;
91
92     private Resources mRes;
93
94     // External storage preferences
95     private Preference mSdSize;
96     private Preference mSdAvail;
97     private Preference mSdMountToggle;
98     private Preference mSdFormat;
99     private PreferenceGroup mSdMountPreferenceGroup;
100
101     // Internal storage preferences
102     private Preference mInternalSize;
103     private Preference mInternalAvail;
104     private Preference mInternalMediaUsage;
105     private Preference mInternalAppsUsage;
106     private UsageBarPreference mInternalUsageChart;
107
108     // Internal storage chart colors
109     private int mInternalMediaColor;
110     private int mInternalAppsColor;
111     private int mInternalUsedColor;
112
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;
119
120     boolean mSdMountToggleAdded = true;
121     
122     // Access using getMountService()
123     private IMountService mMountService = null;
124
125     private StorageManager mStorageManager = null;
126
127     // Updates the memory usage bar graph.
128     private static final int MSG_UI_UPDATE_APPROXIMATE = 1;
129
130     // Updates the memory usage bar graph.
131     private static final int MSG_UI_UPDATE_EXACT = 2;
132
133     private Handler mUpdateHandler = new Handler() {
134         @Override
135         public void handleMessage(Message msg) {
136             switch (msg.what) {
137                 case MSG_UI_UPDATE_APPROXIMATE:
138                     updateUiApproximate();
139                     break;
140                 case MSG_UI_UPDATE_EXACT:
141                     updateUiExact();
142                     mMeasured = true;
143                     break;
144             }
145         }
146     };
147
148     private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
149
150     private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
151             DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
152
153     class MemoryMeasurementHandler extends Handler {
154         public static final int MSG_MEASURE_ALL = 1;
155
156         public static final int MSG_CONNECTED = 2;
157
158         public static final int MSG_DISCONNECTED = 3;
159
160         private List<String> mPendingApps = new ArrayList<String>();
161
162         private volatile boolean mBound = false;
163
164         private long mAppsSize = 0;
165
166         final private ServiceConnection mDefContainerConn = new ServiceConnection() {
167             public void onServiceConnected(ComponentName name, IBinder service) {
168                 mBound = true;
169                 IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(service);
170                 mMeasurementHandler.sendMessage(mMeasurementHandler.obtainMessage(
171                         MemoryMeasurementHandler.MSG_CONNECTED, imcs));
172             }
173
174             public void onServiceDisconnected(ComponentName name) {
175                 mBound = false;
176             }
177         };
178
179         MemoryMeasurementHandler(Looper looper) {
180             super(looper);
181         }
182
183         @Override
184         public void handleMessage(Message msg) {
185             switch (msg.what) {
186                 case MSG_MEASURE_ALL: {
187                     updateExternalStorage();
188                     updateApproximateInternalStorage();
189
190                     Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
191                     getActivity().bindService(service, mDefContainerConn, Context.BIND_AUTO_CREATE);
192
193                     mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_APPROXIMATE);
194                     break;
195                 }
196                 case MSG_CONNECTED: {
197                     IMediaContainerService imcs = (IMediaContainerService) msg.obj;
198                     updateExactInternalStorage(imcs);
199                 }
200             }
201         }
202
203         public void cleanUp() {
204             if (mBound) {
205                 getActivity().unbindService(mDefContainerConn);
206             }
207         }
208
209         public void queuePackageMeasurementLocked(String packageName) {
210             mPendingApps.add(packageName);
211         }
212
213         public void requestQueuedMeasurementsLocked() {
214             final Activity activity = getActivity();
215             if (activity == null) {
216                 return;
217             }
218
219             final PackageManager pm = activity.getPackageManager();
220             if (pm == null) {
221                 return;
222             }
223
224             final int N = mPendingApps.size();
225             for (int i = 0; i < N; i++) {
226                 pm.getPackageSizeInfo(mPendingApps.get(i), mStatsObserver);
227             }
228         }
229
230         final IPackageStatsObserver.Stub mStatsObserver = new IPackageStatsObserver.Stub() {
231             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
232                 if (succeeded) {
233                     mAppsSize += stats.codeSize + stats.dataSize;
234                 }
235
236                 synchronized (mPendingApps) {
237                     mPendingApps.remove(stats.packageName);
238
239                     if (mPendingApps.size() == 0) {
240                         mInternalAppsSize = mAppsSize;
241
242                         mUpdateHandler.sendEmptyMessage(MSG_UI_UPDATE_EXACT);
243                     }
244                 }
245             }
246         };
247
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();
254
255             final long totalSize = totalBlocks * blockSize;
256             final long availSize = availableBlocks * blockSize;
257             mInternalSize.setSummary(formatSize(totalSize));
258             mInternalAvail.setSummary(formatSize(availSize));
259
260             mInternalTotalSize = totalSize;
261             mInternalUsedSize = totalSize - availSize;
262         }
263
264         private void updateExactInternalStorage(IMediaContainerService imcs) {
265             long mediaSize;
266             try {
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");
271                 return;
272             }
273
274             mInternalMediaSize = mediaSize;
275
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);
281             if (apps != null) {
282                 synchronized (mPendingApps) {
283                     for (int i = 0; i < apps.size(); i++) {
284                         final ApplicationInfo info = apps.get(i);
285                         queuePackageMeasurementLocked(info.packageName);
286                     }
287
288                     requestQueuedMeasurementsLocked();
289                 }
290             }
291         }
292
293         private void updateExternalStorage() {
294             if (Environment.isExternalStorageEmulated()) {
295                 return;
296             }
297
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);
303             }
304
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;
312                     }
313                 }
314                 try {
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();
320
321                     mSdSize.setSummary(formatSize(totalBlocks * blockSize));
322                     mSdAvail.setSummary(formatSize(availableBlocks * blockSize) + readOnly);
323
324                     mSdMountToggle.setEnabled(true);
325                     mSdMountToggle.setTitle(mRes.getString(R.string.sd_eject));
326                     mSdMountToggle.setSummary(mRes.getString(R.string.sd_eject_summary));
327
328                 } catch (IllegalArgumentException e) {
329                     // this can occur if the SD card is removed, but we haven't
330                     // received the
331                     // ACTION_MEDIA_REMOVED Intent yet.
332                     status = Environment.MEDIA_REMOVED;
333                 }
334             } else {
335                 mSdSize.setSummary(mRes.getString(R.string.sd_unavailable));
336                 mSdAvail.setSummary(mRes.getString(R.string.sd_unavailable));
337
338                 if (!Environment.isExternalStorageRemovable()) {
339                     if (status.equals(Environment.MEDIA_UNMOUNTED)) {
340                         if (!mSdMountToggleAdded) {
341                             mSdMountPreferenceGroup.addPreference(mSdMountToggle);
342                             mSdMountToggleAdded = true;
343                         }
344                     }
345                 }
346
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));
353                 } else {
354                     mSdMountToggle.setEnabled(false);
355                     mSdMountToggle.setTitle(mRes.getString(R.string.sd_mount));
356                     mSdMountToggle.setSummary(mRes.getString(R.string.sd_insert_summary));
357                 }
358             }
359         }
360     }
361
362     private MemoryMeasurementHandler mMeasurementHandler;
363
364     @Override
365     public void onCreate(Bundle icicle) {
366         super.onCreate(icicle);
367
368         if (mStorageManager == null) {
369             mStorageManager = (StorageManager) getSystemService(Context.STORAGE_SERVICE);
370             mStorageManager.registerListener(mStorageListener);
371         }
372
373         addPreferencesFromResource(R.xml.device_info_memory);
374
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);
381
382         if (Environment.isExternalStorageEmulated()) {
383             mSdMountPreferenceGroup.removePreference(mSdSize);
384             mSdMountPreferenceGroup.removePreference(mSdAvail);
385             mSdMountPreferenceGroup.removePreference(mSdMountToggle);
386         }
387
388         mInternalSize = findPreference(MEMORY_INTERNAL_SIZE);
389         mInternalAvail = findPreference(MEMORY_INTERNAL_AVAIL);
390         mInternalMediaUsage = findPreference(MEMORY_INTERNAL_MEDIA);
391         mInternalAppsUsage = findPreference(MEMORY_INTERNAL_APPS);
392
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);
396
397         mInternalUsageChart = (UsageBarPreference) findPreference(MEMORY_INTERNAL_CHART);
398
399         // Start the thread that will measure the disk usage.
400         final HandlerThread t = new HandlerThread("MeasurementHandler");
401         t.start();
402         mMeasurementHandler = new MemoryMeasurementHandler(t.getLooper());
403     }
404
405     @Override
406     public void onResume() {
407         super.onResume();
408
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);
413
414         if (!mMeasured) {
415             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
416         }
417     }
418
419     StorageEventListener mStorageListener = new StorageEventListener() {
420         @Override
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 +
424                     " to " + newState);
425             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
426         }
427     };
428
429     @Override
430     public void onPause() {
431         super.onPause();
432         getActivity().unregisterReceiver(mReceiver);
433         mMeasurementHandler.removeMessages(MemoryMeasurementHandler.MSG_MEASURE_ALL);
434     }
435
436     @Override
437     public void onDestroy() {
438         if (mStorageManager != null && mStorageListener != null) {
439             mStorageManager.unregisterListener(mStorageListener);
440         }
441         mMeasurementHandler.cleanUp();
442         super.onDestroy();
443     }
444
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);
450            } else {
451                Log.e(TAG, "Can't get mount service");
452            }
453        }
454        return mMountService;
455     }
456     
457     @Override
458     public boolean onPreferenceTreeClick(PreferenceScreen preferenceScreen, Preference preference) {
459         if (preference == mSdMountToggle) {
460             String status = Environment.getExternalStorageState();
461             if (status.equals(Environment.MEDIA_MOUNTED)) {
462                 unmount();
463             } else {
464                 mount();
465             }
466             return true;
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);
471             return true;
472         }
473
474         return false;
475     }
476      
477     private final BroadcastReceiver mReceiver = new BroadcastReceiver() {
478         @Override
479         public void onReceive(Context context, Intent intent) {
480             mMeasurementHandler.sendEmptyMessage(MemoryMeasurementHandler.MSG_MEASURE_ALL);
481         }
482     };
483
484     @Override
485     public Dialog onCreateDialog(int id) {
486         switch (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) {
492                             doUnmount(true);
493                         }})
494                     .setNegativeButton(R.string.cancel, null)
495                     .setMessage(R.string.dlg_confirm_unmount_text)
496                     .setOnCancelListener(this)
497                     .create();
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)
504             .create();
505         }
506         return null;
507     }
508
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();
514         try {
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
521             // unmount failed.
522             showDialogInner(DLG_ERROR_UNMOUNT);
523         }
524     }
525
526     private void showDialogInner(int id) {
527         removeDialog(id);
528         showDialog(id);
529     }
530
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) {
536             return true;
537         }
538         ActivityManager am = (ActivityManager)getSystemService(Context.ACTIVITY_SERVICE);
539         List<ApplicationInfo> list = am.getRunningExternalApplications();
540         if (list != null && list.size() > 0) {
541             return true;
542         }
543         return false;
544     }
545
546     private void unmount() {
547         // Check if external media is in use.
548         try {
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);
553            } else {
554                doUnmount(true);
555            }
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);
560         }
561     }
562
563     private void mount() {
564         IMountService mountService = getMountService();
565         try {
566             if (mountService != null) {
567                 mountService.mountVolume(Environment.getExternalStorageDirectory().toString());
568             } else {
569                 Log.e(TAG, "Mount service is null, can't mount");
570             }
571         } catch (RemoteException ex) {
572         }
573     }
574
575     private void updateUiExact() {
576         final float totalSize = mInternalTotalSize;
577
578         final long mediaSize = mInternalMediaSize;
579         final long appsSize = mInternalAppsSize;
580
581         mInternalUsageChart.clear();
582         mInternalUsageChart.addEntry(mediaSize / totalSize, mInternalMediaColor);
583         mInternalUsageChart.addEntry(appsSize / totalSize, mInternalAppsColor);
584
585         // There are other things that can take up storage, but we didn't
586         // measure it.
587         final long remaining = mInternalUsedSize - (mediaSize + appsSize);
588         if (remaining > 0) {
589             mInternalUsageChart.addEntry(remaining / totalSize, mInternalUsedColor);
590         }
591         mInternalUsageChart.commit();
592
593         mInternalMediaUsage.setSummary(formatSize(mediaSize));
594         mInternalAppsUsage.setSummary(formatSize(appsSize));
595     }
596
597     private void updateUiApproximate() {
598         mInternalUsageChart.clear();
599         mInternalUsageChart.addEntry(mInternalUsedSize / (float) mInternalTotalSize, getResources()
600                 .getColor(R.color.memory_used));
601         mInternalUsageChart.commit();
602     }
603
604     private String formatSize(long size) {
605         return Formatter.formatFileSize(getActivity(), size);
606     }
607
608     public void onCancel(DialogInterface dialog) {
609         // TODO: Is this really required?
610         // finish();
611     }
612 }