1 package com.android.settings.deviceinfo;
3 import com.android.internal.app.IMediaContainerService;
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;
24 import java.lang.ref.WeakReference;
25 import java.util.ArrayList;
26 import java.util.Collections;
27 import java.util.List;
30 * Measure the memory for various systems.
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
37 * Filesystem stats (using StatFs)
38 * Directory measurements (using DefaultContainerService.measureDir)
39 * Application measurements (using PackageManager)
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.
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);
50 public static final String TOTAL_SIZE = "total_size";
52 public static final String AVAIL_SIZE = "avail_size";
54 public static final String APPS_USED = "apps_used";
56 private long[] mMediaSizes = new long[Constants.NUM_MEDIA_DIRS_TRACKED];
58 private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
60 private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
61 DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
63 private final MeasurementHandler mHandler;
65 private static volatile MemoryMeasurement sInstance;
67 private volatile WeakReference<MeasurementReceiver> mReceiver;
69 // Internal memory fields
70 private long mInternalTotalSize;
71 private long mInternalAvailSize;
72 private long mInternalAppsSize;
74 // External memory fields
75 private long mExternalTotalSize;
77 private long mExternalAvailSize;
78 List<FileInfo> mFileInfoForMisc;
80 private MemoryMeasurement(Context context) {
81 // Start the thread that will measure the disk usage.
82 final HandlerThread t = new HandlerThread("MemoryMeasurement");
84 mHandler = new MeasurementHandler(context, t.getLooper());
88 * Get the singleton of the MemoryMeasurement class. The application
89 * context is used to avoid leaking activities.
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());
103 public void setReceiver(MeasurementReceiver receiver) {
104 if (mReceiver == null || mReceiver.get() == null) {
105 mReceiver = new WeakReference<MeasurementReceiver>(receiver);
109 public void measureExternal() {
110 if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_EXTERNAL)) {
111 mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_EXTERNAL);
115 public void measureInternal() {
116 if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE_INTERNAL)) {
117 mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE_INTERNAL);
121 public void cleanUp() {
126 private void sendInternalApproximateUpdate() {
127 MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
128 if (receiver == null) {
132 Bundle bundle = new Bundle();
133 bundle.putLong(TOTAL_SIZE, mInternalTotalSize);
134 bundle.putLong(AVAIL_SIZE, mInternalAvailSize);
136 receiver.updateApproximateInternal(bundle);
139 private void sendInternalExactUpdate() {
140 MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
141 if (receiver == null) {
143 Log.i(TAG, "measurements dropped because receiver is null! wasted effort");
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]);
156 receiver.updateExactInternal(bundle);
159 private void sendExternalApproximateUpdate() {
160 MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
161 if (receiver == null) {
165 Bundle bundle = new Bundle();
166 bundle.putLong(TOTAL_SIZE, mExternalTotalSize);
167 bundle.putLong(AVAIL_SIZE, mExternalAvailSize);
169 receiver.updateApproximateExternal(bundle);
172 public interface MeasurementReceiver {
173 public void updateApproximateInternal(Bundle bundle);
175 public void updateExactInternal(Bundle bundle);
177 public void updateApproximateExternal(Bundle bundle);
180 private class MeasurementHandler extends Handler {
181 public static final int MSG_MEASURE_INTERNAL = 1;
183 public static final int MSG_MEASURE_EXTERNAL = 2;
185 public static final int MSG_CONNECTED = 3;
187 public static final int MSG_DISCONNECT = 4;
189 public static final int MSG_COMPLETED = 5;
191 public static final int MSG_INVALIDATE = 6;
193 private Object mLock = new Object();
195 private IMediaContainerService mDefaultContainer;
197 private volatile boolean mBound = false;
199 private volatile boolean mMeasured = false;
201 private StatsObserver mStatsObserver;
203 private final WeakReference<Context> mContext;
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;
211 sendMessage(obtainMessage(MSG_CONNECTED, imcs));
214 public void onServiceDisconnected(ComponentName name) {
216 removeMessages(MSG_CONNECTED);
220 public MeasurementHandler(Context context, Looper looper) {
222 mContext = new WeakReference<Context>(context);
226 public void handleMessage(Message msg) {
228 case MSG_MEASURE_EXTERNAL: {
230 sendExternalApproximateUpdate();
234 measureApproximateExternalStorage();
237 case MSG_MEASURE_INTERNAL: {
239 sendInternalExactUpdate();
243 final Context context = (mContext != null) ? mContext.get() : null;
244 if (context == null) {
248 measureApproximateInternalStorage();
250 synchronized (mLock) {
252 removeMessages(MSG_DISCONNECT);
253 sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
255 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
256 context.bindService(service, mDefContainerConn,
257 Context.BIND_AUTO_CREATE);
262 case MSG_CONNECTED: {
263 IMediaContainerService imcs = (IMediaContainerService) msg.obj;
264 measureExactInternalStorage(imcs);
267 case MSG_DISCONNECT: {
268 synchronized (mLock) {
270 final Context context = (mContext != null) ? mContext.get() : null;
271 if (context == null) {
276 context.unbindService(mDefContainerConn);
281 case MSG_COMPLETED: {
283 sendInternalExactUpdate();
286 case MSG_INVALIDATE: {
293 public void cleanUp() {
294 removeMessages(MSG_MEASURE_INTERNAL);
295 removeMessages(MSG_MEASURE_EXTERNAL);
297 sendEmptyMessage(MSG_DISCONNECT);
301 * Request measurement of each package.
303 * @param pm PackageManager instance to query
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);
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.
322 mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
323 stats.externalCacheSize + stats.externalDataSize +
324 stats.externalMediaSize + stats.externalObbSize;
327 synchronized (mAppsList) {
328 mAppsList.remove(stats.packageName);
330 if (mAppsList.size() == 0) {
331 mInternalAppsSize = mAppsSizeForThisStatsObserver;
333 onInternalMeasurementComplete();
338 public void queuePackageMeasurementLocked(String packageName) {
339 mAppsList.add(packageName);
341 public List<String> getAppsList() {
346 private void onInternalMeasurementComplete() {
347 sendEmptyMessage(MSG_COMPLETED);
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();
357 final long totalSize = totalBlocks * blockSize;
358 final long availSize = availableBlocks * blockSize;
360 mInternalTotalSize = totalSize;
361 mInternalAvailSize = availSize;
363 sendInternalApproximateUpdate();
366 private void measureExactInternalStorage(IMediaContainerService imcs) {
367 Context context = mContext != null ? mContext.get() : null;
368 if (context == null) {
371 // We have to get installd to measure the package sizes.
372 PackageManager pm = context.getPackageManager();
376 // measure sizes for all except "media_misc" - which is computed
377 for (int i = 0; i < Constants.NUM_MEDIA_DIRS_TRACKED - 1; i++) {
379 String[] dirs = Constants.mMediaDirs.get(i).mDirPaths;
380 int len = dirs.length;
382 for (int k = 0; k < len; k++) {
383 long dirSize = getSize(imcs, dirs[k]);
384 mMediaSizes[i] += dirSize;
386 Log.i(TAG, "size of " + dirs[k] + ": " + dirSize);
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];
398 Log.i(TAG, "media_misc size: " + mMediaSizes[Constants.MEDIA_MISC_INDEX]);
401 // compute the sizes of each of the files/directories under 'misc' category
402 measureSizesOfMisc(imcs);
404 // compute apps sizes
405 final List<ApplicationInfo> apps = pm
406 .getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES
407 | PackageManager.GET_DISABLED_COMPONENTS);
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);
417 requestQueuedMeasurementsLocked(pm);
421 // Sending of the message back to the MeasurementReceiver is
422 // completed in the PackageObserver
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;
432 // get sizes of all top level nodes in /sdcard dir except the ones already computed...
434 for (int i = 0; i < len; i++) {
435 String path = files[i].getAbsolutePath();
436 if (Constants.ExclusionTargetsForMiscFiles.contains(path)) {
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++));
447 // sort the list of FileInfo objects collected above in descending order of their sizes
448 Collections.sort(mFileInfoForMisc);
451 private long getSize(IMediaContainerService imcs, String dir) {
453 long size = imcs.calculateDirectorySize(dir);
455 } catch (Exception e) {
456 Log.w(TAG, "Could not read memory from default container service for " +
462 public void measureApproximateExternalStorage() {
463 File path = Environment.getExternalStorageDirectory();
465 StatFs stat = new StatFs(path.getPath());
466 long blockSize = stat.getBlockSize();
467 long totalBlocks = stat.getBlockCount();
468 long availableBlocks = stat.getAvailableBlocks();
470 mExternalTotalSize = totalBlocks * blockSize;
471 mExternalAvailSize = availableBlocks * blockSize;
473 sendExternalApproximateUpdate();
477 public void invalidate() {
478 mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
481 boolean isSizeOfMiscCategoryNonZero() {
482 return mFileInfoForMisc != null && mFileInfoForMisc.size() > 0;
485 static class FileInfo implements Comparable<FileInfo> {
489 FileInfo(String fileName, long size, long id) {
490 mFileName = fileName;
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
501 public String toString() {
502 return mFileName + " : " + mSize + ", id:" + mId;