OSDN Git Service

Import revised translations. DO NOT MERGE
[android-x86/packages-apps-Settings.git] / src / com / android / settings / deviceinfo / MemoryMeasurement.java
1 package com.android.settings.deviceinfo;
2
3 import com.android.internal.app.IMediaContainerService;
4
5 import android.content.ComponentName;
6 import android.content.Context;
7 import android.content.Intent;
8 import android.content.ServiceConnection;
9 import android.content.pm.ApplicationInfo;
10 import android.content.pm.IPackageStatsObserver;
11 import android.content.pm.PackageManager;
12 import android.content.pm.PackageStats;
13 import android.os.Bundle;
14 import android.os.Environment;
15 import android.os.Handler;
16 import android.os.HandlerThread;
17 import android.os.IBinder;
18 import android.os.Looper;
19 import android.os.Message;
20 import android.os.StatFs;
21 import android.util.Log;
22
23 import java.io.File;
24 import java.lang.ref.WeakReference;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
28
29 /**
30  * Measure the memory for various systems.
31  *
32  * TODO: This class should ideally have less knowledge about what the context
33  * it's measuring is. In the future, reduce the amount of stuff it needs to
34  * know about by just keeping an array of measurement types of the following
35  * properties:
36  *
37  *   Filesystem stats (using StatFs)
38  *   Directory measurements (using DefaultContainerService.measureDir)
39  *   Application measurements (using PackageManager)
40  *
41  * Then the calling application would just specify the type and an argument.
42  * This class would keep track of it while the calling application would
43  * decide on how to use it.
44  */
45 public class MemoryMeasurement {
46     private static final String TAG = "MemorySettings";
47     private static final boolean LOCAL_LOGV = true;
48     static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
49
50     public static final String TOTAL_SIZE = "total_size";
51
52     public static final String AVAIL_SIZE = "avail_size";
53
54     public static final String APPS_USED = "apps_used";
55
56     private long[] mMediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED];
57
58     private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
59
60     private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
61             DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
62
63     private final MeasurementHandler mHandler;
64
65     private static volatile MemoryMeasurement sInstance;
66
67     private volatile WeakReference<MeasurementReceiver> mReceiver;
68
69     // Internal memory fields
70     private long mInternalTotalSize;
71     private long mInternalAvailSize;
72     private long mInternalAppsSize;
73
74     // External memory fields
75     private long mExternalTotalSize;
76
77     private long mExternalAvailSize;
78     List<FileInfo> mFileInfoForMisc;
79
80     private MemoryMeasurement(Context context) {
81         // Start the thread that will measure the disk usage.
82         final HandlerThread t = new HandlerThread("MemoryMeasurement");
83         t.start();
84         mHandler = new MeasurementHandler(context, t.getLooper());
85     }
86
87     /**
88      * Get the singleton of the MemoryMeasurement class. The application
89      * context is used to avoid leaking activities.
90      */
91     public static MemoryMeasurement getInstance(Context context) {
92         if (sInstance == null) {
93             synchronized (MemoryMeasurement.class) {
94                 if (sInstance == null) {
95                     sInstance = new MemoryMeasurement(context.getApplicationContext());
96                 }
97             }
98         }
99
100         return sInstance;
101     }
102
103     public void setReceiver(MeasurementReceiver receiver) {
104         if (mReceiver == null || mReceiver.get() == null) {
105             mReceiver = new WeakReference<MeasurementReceiver>(receiver);
106         }
107     }
108
109     public void measureExternal() {
110         if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_EXTERNAL)) {
111             mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_EXTERNAL);
112         }
113     }
114
115     public void measureInternal() {
116         if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_INTERNAL)) {
117             mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_INTERNAL);
118         }
119     }
120
121     public void cleanUp() {
122         mReceiver = null;
123         mHandler.cleanUp();
124     }
125
126     private void sendInternalApproximateUpdate() {
127         MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
128         if (receiver == null) {
129             return;
130         }
131
132         Bundle bundle = new Bundle();
133         bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
134         bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
135
136         receiver.updateApproximateInternal(bundle);
137     }
138
139     private void sendInternalExactUpdate() {
140         MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
141         if (receiver == null) {
142             if (LOGV) {
143                 Log.i(TAG, "measurements dropped because receiver is null! wasted effort");
144             }
145             return;
146         }
147
148         Bundle bundle = new Bundle();
149         bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
150         bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
151         bundle.putLong(APPS_USED, mInternalAppsSize);
152         for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED; i++) {
153             bundle.putLong(Constants.mMediaDirs.get(i).mKey, mMediaSizes[i]);
154         }
155
156         receiver.updateExactInternal(bundle);
157     }
158
159     private void sendExternalApproximateUpdate() {
160         MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
161         if (receiver == null) {
162             return;
163         }
164
165         Bundle bundle = new Bundle();
166         bundle.putLong(TOTAL_SIZE, mExternalTotalSize);
167         bundle.putLong(AVAIL_SIZE, mExternalAvailSize);
168
169         receiver.updateApproximateExternal(bundle);
170     }
171
172     public interface MeasurementReceiver {
173         public void updateApproximateInternal(Bundle bundle);
174
175         public void updateExactInternal(Bundle bundle);
176
177         public void updateApproximateExternal(Bundle bundle);
178     }
179
180     private class MeasurementHandler extends Handler {
181         public static final int MSG_MEASURE_INTERNAL = 1;
182
183         public static final int MSG_MEASURE_EXTERNAL = 2;
184
185         public static final int MSG_CONNECTED = 3;
186
187         public static final int MSG_DISCONNECT = 4;
188
189         public static final int MSG_COMPLETED = 5;
190
191         public static final int MSG_INVALIDATE = 6;
192
193         private Object mLock = new Object();
194
195         private IMediaContainerService mDefaultContainer;
196
197         private volatile boolean mBound = false;
198
199         private volatile boolean mMeasured = false;
200
201         private StatsObserver mStatsObserver;
202
203         private final WeakReference<Context> mContext;
204
205         final private ServiceConnection mDefContainerConn = new ServiceConnection() {
206             public void onServiceConnected(ComponentName name, IBinder service) {
207                 final IMediaContainerService imcs = IMediaContainerService.Stub
208                         .asInterface(service);
209                 mDefaultContainer = imcs;
210                 mBound = true;
211                 sendMessage(obtainMessage(MSG_CONNECTED, imcs));
212             }
213
214             public void onServiceDisconnected(ComponentName name) {
215                 mBound = false;
216                 removeMessages(MSG_CONNECTED);
217             }
218         };
219
220         public MeasurementHandler(Context context, Looper looper) {
221             super(looper);
222             mContext = new WeakReference<Context>(context);
223         }
224
225         @Override
226         public void handleMessage(Message msg) {
227             switch (msg.what) {
228                 case MSG_MEASURE_EXTERNAL: {
229                     if (mMeasured) {
230                         sendExternalApproximateUpdate();
231                         break;
232                     }
233
234                     measureApproximateExternalStorage();
235                     break;
236                 }
237                 case MSG_MEASURE_INTERNAL: {
238                     if (mMeasured) {
239                         sendInternalExactUpdate();
240                         break;
241                     }
242
243                     final Context context = (mContext != null) ? mContext.get() : null;
244                     if (context == null) {
245                         return;
246                     }
247
248                     measureApproximateInternalStorage();
249
250                     synchronized (mLock) {
251                         if (mBound) {
252                             removeMessages(MSG_DISCONNECT);
253                             sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
254                         } else {
255                             Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
256                             context.bindService(service, mDefContainerConn,
257                                     Context.BIND_AUTO_CREATE);
258                         }
259                     }
260                     break;
261                 }
262                 case MSG_CONNECTED: {
263                     IMediaContainerService imcs = (IMediaContainerService) msg.obj;
264                     measureExactInternalStorage(imcs);
265                     break;
266                 }
267                 case MSG_DISCONNECT: {
268                     synchronized (mLock) {
269                         if (mBound) {
270                             final Context context = (mContext != null) ? mContext.get() : null;
271                             if (context == null) {
272                                 return;
273                             }
274
275                             mBound = false;
276                             context.unbindService(mDefContainerConn);
277                         }
278                     }
279                     break;
280                 }
281                 case MSG_COMPLETED: {
282                     mMeasured = true;
283                     sendInternalExactUpdate();
284                     break;
285                 }
286                 case MSG_INVALIDATE: {
287                     mMeasured = false;
288                     break;
289                 }
290             }
291         }
292
293         public void cleanUp() {
294             removeMessages(MSG_MEASURE_INTERNAL);
295             removeMessages(MSG_MEASURE_EXTERNAL);
296
297             sendEmptyMessage(MSG_DISCONNECT);
298         }
299
300         /**
301          * Request measurement of each package.
302          *
303          * @param pm PackageManager instance to query
304          */
305         public void requestQueuedMeasurementsLocked(PackageManager pm) {
306             final List<String> appsList = mStatsObserver.getAppsList();
307             final int N = appsList.size();
308             for (int i = 0; i < N; i++) {
309                 pm.getPackageSizeInfo(appsList.get(i), mStatsObserver);
310             }
311         }
312
313         private class StatsObserver extends IPackageStatsObserver.Stub {
314             private long mAppsSizeForThisStatsObserver = 0;
315             private final List<String> mAppsList = new ArrayList<String>();
316             public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
317                 if (!mStatsObserver.equals(this)) {
318                     // this callback's class object is no longer in use. ignore this callback.
319                     return;
320                 }
321                 if (succeeded) {
322                     mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
323                             stats.externalCacheSize + stats.externalDataSize +
324                             stats.externalMediaSize + stats.externalObbSize;
325                 }
326
327                 synchronized (mAppsList) {
328                     mAppsList.remove(stats.packageName);
329
330                     if (mAppsList.size() == 0) {
331                         mInternalAppsSize = mAppsSizeForThisStatsObserver;
332
333                         onInternalMeasurementComplete();
334                     }
335                 }
336             }
337
338             public void queuePackageMeasurementLocked(String packageName) {
339                 mAppsList.add(packageName);
340             }
341             public List<String> getAppsList() {
342                 return mAppsList;
343             }
344         }
345
346         private void onInternalMeasurementComplete() {
347             sendEmptyMessage(MSG_COMPLETED);
348         }
349
350         private void measureApproximateInternalStorage() {
351             final File dataPath = Environment.getDataDirectory();
352             final StatFs stat = new StatFs(dataPath.getPath());
353             final long blockSize = stat.getBlockSize();
354             final long totalBlocks = stat.getBlockCount();
355             final long availableBlocks = stat.getAvailableBlocks();
356
357             final long totalSize = totalBlocks * blockSize;
358             final long availSize = availableBlocks * blockSize;
359
360             mInternalTotalSize = totalSize;
361             mInternalAvailSize = availSize;
362
363             sendInternalApproximateUpdate();
364         }
365
366         private void measureExactInternalStorage(IMediaContainerService imcs) {
367             Context context = mContext != null ? mContext.get() : null;
368             if (context == null) {
369                 return;
370             }
371             // We have to get installd to measure the package sizes.
372             PackageManager pm = context.getPackageManager();
373             if (pm == null) {
374                 return;
375             }
376             // measure sizes for all except "media_misc" - which is computed
377             for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) {
378                 mMediaSizes[i] = 0;
379                 String[] dirs = Constants.mMediaDirs.get(i).mDirPaths;
380                 int len = dirs.length;
381                 if (len > 0) {
382                     for (int k = 0; k < len; k++) {
383                         long dirSize = getSize(imcs, dirs[k]);
384                         mMediaSizes[i] += dirSize;
385                         if (LOGV) {
386                             Log.i(TAG, "size of " + dirs[k] + ": " + dirSize);
387                         }
388                     }
389                 }
390             }
391
392             // compute the size of "misc"
393             mMediaSizes[Constants.MEDIA_MISC_INDEX] = mMediaSizes[Constants.MEDIA_INDEX];
394             for (int i = 1; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) {
395                 mMediaSizes[Constants.MEDIA_MISC_INDEX] -= mMediaSizes[i];
396             }
397             if (LOGV) {
398                 Log.i(TAG, "media_misc size: " + mMediaSizes[Constants.MEDIA_MISC_INDEX]);
399             }
400
401             // compute the sizes of each of the files/directories under 'misc' category
402             measureSizesOfMisc(imcs);
403
404             // compute apps sizes
405             final List<ApplicationInfo> apps = pm
406                     .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
407                             | PackageManager.GET_DISABLED_COMPONENTS);
408             if (apps != null) {
409                 // initiate measurement of all package sizes. need new StatsObserver object.
410                 mStatsObserver = new StatsObserver();
411                 synchronized (mStatsObserver.mAppsList) {
412                     for (int i = 0; i < apps.size(); i++) {
413                         final ApplicationInfo info = apps.get(i);
414                         mStatsObserver.queuePackageMeasurementLocked(info.packageName);
415                     }
416
417                     requestQueuedMeasurementsLocked(pm);
418                 }
419             }
420
421             // Sending of the message back to the MeasurementReceiver is
422             // completed in the PackageObserver
423         }
424         private void measureSizesOfMisc(IMediaContainerService imcs) {
425             File top = Environment.getExternalStorageDirectory();
426             mFileInfoForMisc = new ArrayList<FileInfo>();
427             File[] files = top.listFiles();
428             int len = files.length;
429             if (len == 0) {
430                 return;
431             }
432             // get sizes of all top level nodes in /sdcard dir except the ones already computed...
433             long counter = 0;
434             for (int i = 0; i < len; i++) {
435                 String path = files[i].getAbsolutePath();
436                 if (Constants.ExclusionTargetsForMiscFiles.contains(path)) {
437                     continue;
438                 }
439                 if (files[i].isFile()) {
440                     mFileInfoForMisc.add(new FileInfo(path, files[i].length(), counter++));
441                 } else if (files[i].isDirectory()) {
442                     long dirSize = getSize(imcs, path);
443                     mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++));
444                 } else {
445                 }
446             }
447             // sort the list of FileInfo objects collected above in descending order of their sizes
448             Collections.sort(mFileInfoForMisc);
449         }
450
451         private long getSize(IMediaContainerService imcs, String dir) {
452             try {
453                 long size = imcs.calculateDirectorySize(dir);
454                 return size;
455             } catch (Exception e) {
456                 Log.w(TAG, "Could not read memory from default container service for " +
457                         dir, e);
458                 return -1;
459             }
460         }
461
462         public void measureApproximateExternalStorage() {
463             File path = Environment.getExternalStorageDirectory();
464
465             StatFs stat = new StatFs(path.getPath());
466             long blockSize = stat.getBlockSize();
467             long totalBlocks = stat.getBlockCount();
468             long availableBlocks = stat.getAvailableBlocks();
469
470             mExternalTotalSize = totalBlocks * blockSize;
471             mExternalAvailSize = availableBlocks * blockSize;
472
473             sendExternalApproximateUpdate();
474         }
475     }
476
477     public void invalidate() {
478         mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
479     }
480
481     boolean isSizeOfMiscCategoryNonZero() {
482         return mFileInfoForMisc != null && mFileInfoForMisc.size() > 0;
483     }
484
485     static class FileInfo implements Comparable<FileInfo> {
486         String mFileName;
487         long mSize;
488         long mId;
489         FileInfo(String fileName, long size, long id) {
490             mFileName = fileName;
491             mSize = size;
492             mId = id;
493         }
494         @Override
495         public int compareTo(FileInfo that) {
496             if (this == that || mSize == that.mSize) return 0;
497             else if (mSize < that.mSize) return 1; // for descending sort
498             else return -1;
499         }
500         @Override
501         public String toString() {
502             return mFileName  + " : " + mSize + ", id:" + mId;
503         }
504     }
505 }