2 * Copyright (C) 2011 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 android.content.ComponentName;
20 import android.content.Context;
21 import android.content.Intent;
22 import android.content.ServiceConnection;
23 import android.content.pm.ApplicationInfo;
24 import android.content.pm.IPackageStatsObserver;
25 import android.content.pm.PackageManager;
26 import android.content.pm.PackageStats;
27 import android.os.Bundle;
28 import android.os.Environment;
29 import android.os.Handler;
30 import android.os.HandlerThread;
31 import android.os.IBinder;
32 import android.os.Looper;
33 import android.os.Message;
34 import android.os.StatFs;
35 import android.os.storage.StorageVolume;
36 import android.util.Log;
38 import com.android.internal.app.IMediaContainerService;
41 import java.lang.ref.WeakReference;
42 import java.util.ArrayList;
43 import java.util.Collections;
44 import java.util.List;
46 import java.util.concurrent.ConcurrentHashMap;
49 * Measure the memory for various systems.
51 * TODO: This class should ideally have less knowledge about what the context
52 * it's measuring is. In the future, reduce the amount of stuff it needs to
53 * know about by just keeping an array of measurement types of the following
56 * Filesystem stats (using StatFs)
57 * Directory measurements (using DefaultContainerService.measureDir)
58 * Application measurements (using PackageManager)
60 * Then the calling application would just specify the type and an argument.
61 * This class would keep track of it while the calling application would
62 * decide on how to use it.
64 public class StorageMeasurement {
65 private static final String TAG = "StorageMeasurement";
67 private static final boolean LOCAL_LOGV = true;
68 static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
70 public static final String TOTAL_SIZE = "total_size";
72 public static final String AVAIL_SIZE = "avail_size";
74 public static final String APPS_USED = "apps_used";
76 public static final String DOWNLOADS_SIZE = "downloads_size";
78 public static final String MISC_SIZE = "misc_size";
80 public static final String MEDIA_SIZES = "media_sizes";
82 private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
84 private static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
85 DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
87 private final MeasurementHandler mHandler;
89 private static Map<StorageVolume, StorageMeasurement> sInstances =
90 new ConcurrentHashMap<StorageVolume, StorageMeasurement>();
92 private volatile WeakReference<MeasurementReceiver> mReceiver;
94 private long mTotalSize;
95 private long mAvailSize;
96 private long mAppsSize;
97 private long mDownloadsSize;
98 private long mMiscSize;
99 private long[] mMediaSizes = new long[StorageVolumePreferenceCategory.sMediaCategories.length];
101 final private StorageVolume mStorageVolume;
102 final private boolean mIsPrimary;
104 List<FileInfo> mFileInfoForMisc;
106 public interface MeasurementReceiver {
107 public void updateApproximate(Bundle bundle);
108 public void updateExact(Bundle bundle);
111 private StorageMeasurement(Context context, StorageVolume storageVolume, boolean isPrimary) {
112 mStorageVolume = storageVolume;
113 mIsPrimary = isPrimary;
115 // Start the thread that will measure the disk usage.
116 final HandlerThread handlerThread = new HandlerThread("MemoryMeasurement");
117 handlerThread.start();
118 mHandler = new MeasurementHandler(context, handlerThread.getLooper());
122 * Get the singleton of the StorageMeasurement class. The application
123 * context is used to avoid leaking activities.
124 * @param storageVolume The {@link StorageVolume} that will be measured
125 * @param isPrimary true when this storage volume is the primary volume
127 public static StorageMeasurement getInstance(Context context, StorageVolume storageVolume,
129 if (sInstances.containsKey(storageVolume)) {
130 return sInstances.get(storageVolume);
132 StorageMeasurement storageMeasurement =
133 new StorageMeasurement(context.getApplicationContext(), storageVolume, isPrimary);
134 sInstances.put(storageVolume, storageMeasurement);
135 return storageMeasurement;
139 public void setReceiver(MeasurementReceiver receiver) {
140 if (mReceiver == null || mReceiver.get() == null) {
141 mReceiver = new WeakReference<MeasurementReceiver>(receiver);
145 public void measure() {
146 if (!mHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) {
147 mHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE);
151 public void cleanUp() {
153 mHandler.removeMessages(MeasurementHandler.MSG_MEASURE);
154 mHandler.sendEmptyMessage(MeasurementHandler.MSG_DISCONNECT);
157 public void invalidate() {
158 mHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
161 private void sendInternalApproximateUpdate() {
162 MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
163 if (receiver == null) {
167 Bundle bundle = new Bundle();
168 bundle.putLong(TOTAL_SIZE, mTotalSize);
169 bundle.putLong(AVAIL_SIZE, mAvailSize);
171 receiver.updateApproximate(bundle);
174 private void sendExactUpdate() {
175 MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
176 if (receiver == null) {
178 Log.i(TAG, "measurements dropped because receiver is null! wasted effort");
183 Bundle bundle = new Bundle();
184 bundle.putLong(TOTAL_SIZE, mTotalSize);
185 bundle.putLong(AVAIL_SIZE, mAvailSize);
186 bundle.putLong(APPS_USED, mAppsSize);
187 bundle.putLong(DOWNLOADS_SIZE, mDownloadsSize);
188 bundle.putLong(MISC_SIZE, mMiscSize);
189 bundle.putLongArray(MEDIA_SIZES, mMediaSizes);
191 receiver.updateExact(bundle);
194 private class MeasurementHandler extends Handler {
195 public static final int MSG_MEASURE = 1;
197 public static final int MSG_CONNECTED = 2;
199 public static final int MSG_DISCONNECT = 3;
201 public static final int MSG_COMPLETED = 4;
203 public static final int MSG_INVALIDATE = 5;
205 private Object mLock = new Object();
207 private IMediaContainerService mDefaultContainer;
209 private volatile boolean mBound = false;
211 private volatile boolean mMeasured = false;
213 private StatsObserver mStatsObserver;
215 private final WeakReference<Context> mContext;
217 final private ServiceConnection mDefContainerConn = new ServiceConnection() {
218 public void onServiceConnected(ComponentName name, IBinder service) {
219 final IMediaContainerService imcs = IMediaContainerService.Stub
220 .asInterface(service);
221 mDefaultContainer = imcs;
223 sendMessage(obtainMessage(MSG_CONNECTED, imcs));
226 public void onServiceDisconnected(ComponentName name) {
228 removeMessages(MSG_CONNECTED);
232 public MeasurementHandler(Context context, Looper looper) {
234 mContext = new WeakReference<Context>(context);
238 public void handleMessage(Message msg) {
246 final Context context = (mContext != null) ? mContext.get() : null;
247 if (context == null) {
251 measureApproximateStorage();
253 synchronized (mLock) {
255 removeMessages(MSG_DISCONNECT);
256 sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
258 Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
259 context.bindService(service, mDefContainerConn,
260 Context.BIND_AUTO_CREATE);
265 case MSG_CONNECTED: {
266 IMediaContainerService imcs = (IMediaContainerService) msg.obj;
267 measureExactStorage(imcs);
270 case MSG_DISCONNECT: {
271 synchronized (mLock) {
273 final Context context = (mContext != null) ? mContext.get() : null;
274 if (context == null) {
279 context.unbindService(mDefContainerConn);
284 case MSG_COMPLETED: {
289 case MSG_INVALIDATE: {
297 * Request measurement of each package.
299 * @param pm PackageManager instance to query
301 public void requestQueuedMeasurementsLocked(PackageManager pm) {
302 final String[] appsList = mStatsObserver.getAppsList();
303 final int N = appsList.length;
304 for (int i = 0; i < N; i++) {
305 pm.getPackageSizeInfo(appsList[i], mStatsObserver);
309 private class StatsObserver extends IPackageStatsObserver.Stub {
310 private long mAppsSizeForThisStatsObserver = 0;
311 private final List<String> mAppsList = new ArrayList<String>();
313 public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
314 if (!mStatsObserver.equals(this)) {
315 // this callback's class object is no longer in use. ignore this callback.
320 mAppsSizeForThisStatsObserver += stats.codeSize + stats.dataSize +
321 stats.externalCacheSize + stats.externalDataSize +
322 stats.externalMediaSize + stats.externalObbSize;
325 synchronized (mAppsList) {
326 mAppsList.remove(stats.packageName);
327 if (mAppsList.size() > 0) return;
330 mAppsSize = mAppsSizeForThisStatsObserver;
331 onInternalMeasurementComplete();
334 public void queuePackageMeasurementLocked(String packageName) {
335 synchronized (mAppsList) {
336 mAppsList.add(packageName);
340 public String[] getAppsList() {
341 synchronized (mAppsList) {
342 return mAppsList.toArray(new String[mAppsList.size()]);
347 private void onInternalMeasurementComplete() {
348 sendEmptyMessage(MSG_COMPLETED);
351 private void measureApproximateStorage() {
352 final StatFs stat = new StatFs(mStorageVolume.getPath());
353 final long blockSize = stat.getBlockSize();
354 final long totalBlocks = stat.getBlockCount();
355 final long availableBlocks = stat.getAvailableBlocks();
357 mTotalSize = totalBlocks * blockSize;
358 mAvailSize = availableBlocks * blockSize;
360 sendInternalApproximateUpdate();
363 private void measureExactStorage(IMediaContainerService imcs) {
364 Context context = mContext != null ? mContext.get() : null;
365 if (context == null) {
370 for (int i = 0; i < StorageVolumePreferenceCategory.sMediaCategories.length; i++) {
372 String[] dirs = StorageVolumePreferenceCategory.sMediaCategories[i].mDirPaths;
373 final int length = dirs.length;
375 for (int d = 0; d < length; d++) {
376 final String path = dirs[d];
377 mMediaSizes[i] += getDirectorySize(imcs, path);
380 // TODO Compute sizes using the MediaStore
385 /* Compute sizes using the media provider
386 // Media sizes are measured by the MediaStore. Query database.
387 ContentResolver contentResolver = context.getContentResolver();
388 // TODO "external" as a static String from MediaStore?
389 Uri audioUri = MediaStore.Files.getContentUri("external");
390 final String[] projection =
391 new String[] { "sum(" + MediaStore.Files.FileColumns.SIZE + ")" };
392 final String selection =
393 MediaStore.Files.FileColumns.STORAGE_ID + "=" +
394 Integer.toString(mStorageVolume.getStorageId()) + " AND " +
395 MediaStore.Files.FileColumns.MEDIA_TYPE + "=?";
397 for (int i = 0; i < StorageVolumePreferenceCategory.sMediaCategories.length; i++) {
399 int mediaType = StorageVolumePreferenceCategory.sMediaCategories[i].mediaType;
402 c = contentResolver.query(audioUri, projection, selection,
403 new String[] { Integer.toString(mediaType) } , null);
405 if (c != null && c.moveToNext()) {
406 long size = c.getLong(0);
407 mMediaSizes[i] = size;
410 if (c != null) c.close();
415 // Downloads (primary volume only)
417 final String downloadsPath = Environment.getExternalStoragePublicDirectory(
418 Environment.DIRECTORY_DOWNLOADS).getAbsolutePath();
419 mDownloadsSize = getDirectorySize(imcs, downloadsPath);
427 measureSizesOfMisc(imcs);
431 // We have to get installd to measure the package sizes.
432 PackageManager pm = context.getPackageManager();
436 final List<ApplicationInfo> apps;
438 apps = pm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES |
439 PackageManager.GET_DISABLED_COMPONENTS);
441 // TODO also measure apps installed on the SD card
442 apps = Collections.emptyList();
445 if (apps != null && apps.size() > 0) {
446 // initiate measurement of all package sizes. need new StatsObserver object.
447 mStatsObserver = new StatsObserver();
448 synchronized (mStatsObserver.mAppsList) {
449 for (int i = 0; i < apps.size(); i++) {
450 final ApplicationInfo info = apps.get(i);
451 mStatsObserver.queuePackageMeasurementLocked(info.packageName);
455 requestQueuedMeasurementsLocked(pm);
456 // Sending of the message back to the MeasurementReceiver is
457 // completed in the PackageObserver
459 onInternalMeasurementComplete();
464 private long getDirectorySize(IMediaContainerService imcs, String dir) {
466 return imcs.calculateDirectorySize(dir);
467 } catch (Exception e) {
468 Log.w(TAG, "Could not read memory from default container service for " + dir, e);
477 private void measureSizesOfMisc(IMediaContainerService imcs) {
478 File top = new File(mStorageVolume.getPath());
479 mFileInfoForMisc = new ArrayList<FileInfo>();
480 File[] files = top.listFiles();
481 final int len = files.length;
482 // Get sizes of all top level nodes except the ones already computed...
484 for (int i = 0; i < len; i++) {
485 String path = files[i].getAbsolutePath();
486 if (StorageVolumePreferenceCategory.sPathsExcludedForMisc.contains(path)) {
489 if (files[i].isFile()) {
490 final long fileSize = files[i].length();
491 mFileInfoForMisc.add(new FileInfo(path, fileSize, counter++));
492 mMiscSize += fileSize;
493 } else if (files[i].isDirectory()) {
494 final long dirSize = getDirectorySize(imcs, path);
495 mFileInfoForMisc.add(new FileInfo(path, dirSize, counter++));
496 mMiscSize += dirSize;
498 // Non directory, non file: not listed
501 // sort the list of FileInfo objects collected above in descending order of their sizes
502 Collections.sort(mFileInfoForMisc);
505 static class FileInfo implements Comparable<FileInfo> {
506 final String mFileName;
510 FileInfo(String fileName, long size, long id) {
511 mFileName = fileName;
517 public int compareTo(FileInfo that) {
518 if (this == that || mSize == that.mSize) return 0;
519 else return (mSize < that.mSize) ? 1 : -1; // for descending sort
523 public String toString() {
524 return mFileName + " : " + mSize + ", id:" + mId;