summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorTony Mantler <nicoya@google.com>2015-04-23 22:40:09 +0000
committerAndroid (Google) Code Review <android-gerrit@google.com>2015-04-23 22:40:12 +0000
commit3e452a9b9603a7a4298a2e1e1737cac01be567d0 (patch)
tree583e178c48753fdf9db5c0bda44226ce5426ef2c
parent48f8fe53ee6624ed64ccd5f47bbc28beb223ce6b (diff)
parent547e4caebe0aac21d0856de96e121b8bf63fd9bd (diff)
downloadframeworks_base-3e452a9b9603a7a4298a2e1e1737cac01be567d0.zip
frameworks_base-3e452a9b9603a7a4298a2e1e1737cac01be567d0.tar.gz
frameworks_base-3e452a9b9603a7a4298a2e1e1737cac01be567d0.tar.bz2
Merge "Move StorageMeasurement to SettingsLib"
-rw-r--r--packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java441
1 files changed, 441 insertions, 0 deletions
diff --git a/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
new file mode 100644
index 0000000..e0af29d
--- /dev/null
+++ b/packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java
@@ -0,0 +1,441 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package com.android.settingslib.deviceinfo;
+
+import android.app.ActivityManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.ServiceConnection;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.IPackageStatsObserver;
+import android.content.pm.PackageManager;
+import android.content.pm.PackageStats;
+import android.content.pm.UserInfo;
+import android.os.Environment;
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.IBinder;
+import android.os.Looper;
+import android.os.Message;
+import android.os.UserHandle;
+import android.os.UserManager;
+import android.os.storage.StorageVolume;
+import android.os.storage.VolumeInfo;
+import android.util.Log;
+import android.util.SparseLongArray;
+
+import com.android.internal.app.IMediaContainerService;
+import com.android.internal.util.ArrayUtils;
+import com.google.android.collect.Sets;
+
+import java.io.File;
+import java.lang.ref.WeakReference;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Objects;
+import java.util.Set;
+
+/**
+ * Utility for measuring the disk usage of internal storage or a physical
+ * {@link StorageVolume}. Connects with a remote {@link IMediaContainerService}
+ * and delivers results to {@link MeasurementReceiver}.
+ */
+public class StorageMeasurement {
+ private static final String TAG = "StorageMeasurement";
+
+ private static final boolean LOCAL_LOGV = true;
+ static final boolean LOGV = LOCAL_LOGV && Log.isLoggable(TAG, Log.VERBOSE);
+
+ private static final String DEFAULT_CONTAINER_PACKAGE = "com.android.defcontainer";
+
+ public static final ComponentName DEFAULT_CONTAINER_COMPONENT = new ComponentName(
+ DEFAULT_CONTAINER_PACKAGE, "com.android.defcontainer.DefaultContainerService");
+
+ /** Media types to measure on external storage. */
+ private static final Set<String> sMeasureMediaTypes = Sets.newHashSet(
+ Environment.DIRECTORY_DCIM, Environment.DIRECTORY_MOVIES,
+ Environment.DIRECTORY_PICTURES, Environment.DIRECTORY_MUSIC,
+ Environment.DIRECTORY_ALARMS, Environment.DIRECTORY_NOTIFICATIONS,
+ Environment.DIRECTORY_RINGTONES, Environment.DIRECTORY_PODCASTS,
+ Environment.DIRECTORY_DOWNLOADS, Environment.DIRECTORY_ANDROID);
+
+ public static class MeasurementDetails {
+ /**
+ * Total apps disk usage.
+ * <p>
+ * When measuring internal storage, this value includes the code size of
+ * all apps (regardless of install status for current user), and
+ * internal disk used by the current user's apps. When the device
+ * emulates external storage, this value also includes emulated storage
+ * used by the current user's apps.
+ * <p>
+ * When measuring a physical {@link StorageVolume}, this value includes
+ * usage by all apps on that volume.
+ */
+ public long appsSize;
+
+ /**
+ * Total cache disk usage by apps.
+ */
+ public long cacheSize;
+
+ /**
+ * Total media disk usage, categorized by types such as
+ * {@link Environment#DIRECTORY_MUSIC}.
+ * <p>
+ * When measuring internal storage, this reflects media on emulated
+ * storage for the current user.
+ * <p>
+ * When measuring a physical {@link StorageVolume}, this reflects media
+ * on that volume.
+ */
+ public HashMap<String, Long> mediaSize = new HashMap<>();
+
+ /**
+ * Misc external disk usage for the current user, unaccounted in
+ * {@link #mediaSize}.
+ */
+ public long miscSize;
+
+ /**
+ * Total disk usage for users, which is only meaningful for emulated
+ * internal storage. Key is {@link UserHandle}.
+ */
+ public SparseLongArray usersSize = new SparseLongArray();
+ }
+
+ public interface MeasurementReceiver {
+ public void onDetailsChanged(MeasurementDetails details);
+ }
+
+ private WeakReference<MeasurementReceiver> mReceiver;
+
+ private final Context mContext;
+
+ private final VolumeInfo mVolume;
+ private final VolumeInfo mSharedVolume;
+
+ private final MainHandler mMainHandler;
+ private final MeasurementHandler mMeasurementHandler;
+
+ public StorageMeasurement(Context context, VolumeInfo volume, VolumeInfo sharedVolume) {
+ mContext = context.getApplicationContext();
+
+ mVolume = volume;
+ mSharedVolume = sharedVolume;
+
+ // Start the thread that will measure the disk usage.
+ final HandlerThread handlerThread = new HandlerThread("MemoryMeasurement");
+ handlerThread.start();
+
+ mMainHandler = new MainHandler();
+ mMeasurementHandler = new MeasurementHandler(handlerThread.getLooper());
+ }
+
+ public void setReceiver(MeasurementReceiver receiver) {
+ if (mReceiver == null || mReceiver.get() == null) {
+ mReceiver = new WeakReference<MeasurementReceiver>(receiver);
+ }
+ }
+
+ public void forceMeasure() {
+ invalidate();
+ measure();
+ }
+
+ public void measure() {
+ if (!mMeasurementHandler.hasMessages(MeasurementHandler.MSG_MEASURE)) {
+ mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_MEASURE);
+ }
+ }
+
+ public void onDestroy() {
+ mReceiver = null;
+ mMeasurementHandler.removeMessages(MeasurementHandler.MSG_MEASURE);
+ mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_DISCONNECT);
+ }
+
+ private void invalidate() {
+ mMeasurementHandler.sendEmptyMessage(MeasurementHandler.MSG_INVALIDATE);
+ }
+
+ private static class StatsObserver extends IPackageStatsObserver.Stub {
+ private final boolean mIsPrivate;
+ private final MeasurementDetails mDetails;
+ private final int mCurrentUser;
+ private final Message mFinished;
+
+ private int mRemaining;
+
+ public StatsObserver(boolean isPrivate, MeasurementDetails details, int currentUser,
+ Message finished, int remaining) {
+ mIsPrivate = isPrivate;
+ mDetails = details;
+ mCurrentUser = currentUser;
+ mFinished = finished;
+ mRemaining = remaining;
+ }
+
+ @Override
+ public void onGetStatsCompleted(PackageStats stats, boolean succeeded) {
+ synchronized (mDetails) {
+ if (succeeded) {
+ addStatsLocked(stats);
+ }
+ if (--mRemaining == 0) {
+ mFinished.sendToTarget();
+ }
+ }
+ }
+
+ private void addStatsLocked(PackageStats stats) {
+ if (mIsPrivate) {
+ long codeSize = stats.codeSize;
+ long dataSize = stats.dataSize;
+ long cacheSize = stats.cacheSize;
+ if (Environment.isExternalStorageEmulated()) {
+ // Include emulated storage when measuring internal. OBB is
+ // shared on emulated storage, so treat as code.
+ codeSize += stats.externalCodeSize + stats.externalObbSize;
+ dataSize += stats.externalDataSize + stats.externalMediaSize;
+ cacheSize += stats.externalCacheSize;
+ }
+
+ // Count code and data for current user
+ if (stats.userHandle == mCurrentUser) {
+ mDetails.appsSize += codeSize;
+ mDetails.appsSize += dataSize;
+ }
+
+ // User summary only includes data (code is only counted once
+ // for the current user)
+ addValue(mDetails.usersSize, stats.userHandle, dataSize);
+
+ // Include cache for all users
+ mDetails.cacheSize += cacheSize;
+
+ } else {
+ // Physical storage; only count external sizes
+ mDetails.appsSize += stats.externalCodeSize + stats.externalDataSize
+ + stats.externalMediaSize + stats.externalObbSize;
+ mDetails.cacheSize += stats.externalCacheSize;
+ }
+ }
+ }
+
+ private class MainHandler extends Handler {
+ @Override
+ public void handleMessage(Message msg) {
+ final MeasurementDetails details = (MeasurementDetails) msg.obj;
+ final MeasurementReceiver receiver = (mReceiver != null) ? mReceiver.get() : null;
+ if (receiver != null) {
+ receiver.onDetailsChanged(details);
+ }
+ }
+ }
+
+ private class MeasurementHandler extends Handler {
+ public static final int MSG_MEASURE = 1;
+ public static final int MSG_CONNECTED = 2;
+ public static final int MSG_DISCONNECT = 3;
+ public static final int MSG_COMPLETED = 4;
+ public static final int MSG_INVALIDATE = 5;
+
+ private Object mLock = new Object();
+
+ private IMediaContainerService mDefaultContainer;
+
+ private volatile boolean mBound = false;
+
+ private MeasurementDetails mCached;
+
+ private final ServiceConnection mDefContainerConn = new ServiceConnection() {
+ @Override
+ public void onServiceConnected(ComponentName name, IBinder service) {
+ final IMediaContainerService imcs = IMediaContainerService.Stub.asInterface(
+ service);
+ mDefaultContainer = imcs;
+ mBound = true;
+ sendMessage(obtainMessage(MSG_CONNECTED, imcs));
+ }
+
+ @Override
+ public void onServiceDisconnected(ComponentName name) {
+ mBound = false;
+ removeMessages(MSG_CONNECTED);
+ }
+ };
+
+ public MeasurementHandler(Looper looper) {
+ super(looper);
+ }
+
+ @Override
+ public void handleMessage(Message msg) {
+ switch (msg.what) {
+ case MSG_MEASURE: {
+ if (mCached != null) {
+ mMainHandler.obtainMessage(0, mCached).sendToTarget();
+ break;
+ }
+
+ synchronized (mLock) {
+ if (mBound) {
+ removeMessages(MSG_DISCONNECT);
+ sendMessage(obtainMessage(MSG_CONNECTED, mDefaultContainer));
+ } else {
+ Intent service = new Intent().setComponent(DEFAULT_CONTAINER_COMPONENT);
+ mContext.bindServiceAsUser(service, mDefContainerConn,
+ Context.BIND_AUTO_CREATE, UserHandle.OWNER);
+ }
+ }
+ break;
+ }
+ case MSG_CONNECTED: {
+ final IMediaContainerService imcs = (IMediaContainerService) msg.obj;
+ measureExactStorage(imcs);
+ break;
+ }
+ case MSG_DISCONNECT: {
+ synchronized (mLock) {
+ if (mBound) {
+ mBound = false;
+ mContext.unbindService(mDefContainerConn);
+ }
+ }
+ break;
+ }
+ case MSG_COMPLETED: {
+ mCached = (MeasurementDetails) msg.obj;
+ mMainHandler.obtainMessage(0, mCached).sendToTarget();
+ break;
+ }
+ case MSG_INVALIDATE: {
+ mCached = null;
+ break;
+ }
+ }
+ }
+ }
+
+ private void measureExactStorage(IMediaContainerService imcs) {
+ final UserManager userManager = mContext.getSystemService(UserManager.class);
+ final PackageManager packageManager = mContext.getPackageManager();
+
+ final List<UserInfo> users = userManager.getUsers();
+ final int currentUser = ActivityManager.getCurrentUser();
+
+ final MeasurementDetails details = new MeasurementDetails();
+ final Message finished = mMeasurementHandler.obtainMessage(MeasurementHandler.MSG_COMPLETED,
+ details);
+
+ if (mSharedVolume != null && mSharedVolume.isMountedReadable()) {
+ final File basePath = mSharedVolume.getPathForUser(currentUser);
+
+ // Measure media types for emulated storage, or for primary physical
+ // external volume
+ for (String type : sMeasureMediaTypes) {
+ final File path = new File(basePath, type);
+ final long size = getDirectorySize(imcs, path);
+ details.mediaSize.put(type, size);
+ }
+
+ // Measure misc files not counted under media
+ details.miscSize = measureMisc(imcs, basePath);
+
+ if (mSharedVolume.getType() == VolumeInfo.TYPE_EMULATED) {
+ // Measure total emulated storage of all users; internal apps data
+ // will be spliced in later
+ for (UserInfo user : users) {
+ final File userPath = mSharedVolume.getPathForUser(user.id);
+ final long size = getDirectorySize(imcs, userPath);
+ addValue(details.usersSize, user.id, size);
+ }
+ }
+ }
+
+ // Measure all apps hosted on this volume for all users
+ if (mVolume.getType() == VolumeInfo.TYPE_PRIVATE) {
+ final List<ApplicationInfo> apps = packageManager.getInstalledApplications(
+ PackageManager.GET_UNINSTALLED_PACKAGES
+ | PackageManager.GET_DISABLED_COMPONENTS);
+
+ final List<ApplicationInfo> volumeApps = new ArrayList<>();
+ for (ApplicationInfo app : apps) {
+ if (Objects.equals(app.volumeUuid, mVolume.getFsUuid())) {
+ volumeApps.add(app);
+ }
+ }
+
+ final int count = users.size() * volumeApps.size();
+ if (count == 0) {
+ finished.sendToTarget();
+ return;
+ }
+
+ final StatsObserver observer = new StatsObserver(
+ true, details, currentUser, finished, count);
+ for (UserInfo user : users) {
+ for (ApplicationInfo app : volumeApps) {
+ packageManager.getPackageSizeInfo(app.packageName, user.id, observer);
+ }
+ }
+
+ } else {
+ finished.sendToTarget();
+ return;
+ }
+ }
+
+ private static long getDirectorySize(IMediaContainerService imcs, File path) {
+ try {
+ final long size = imcs.calculateDirectorySize(path.toString());
+ Log.d(TAG, "getDirectorySize(" + path + ") returned " + size);
+ return size;
+ } catch (Exception e) {
+ Log.w(TAG, "Could not read memory from default container service for " + path, e);
+ return 0;
+ }
+ }
+
+ private long measureMisc(IMediaContainerService imcs, File dir) {
+ final File[] files = dir.listFiles();
+ if (ArrayUtils.isEmpty(files)) return 0;
+
+ // Get sizes of all top level nodes except the ones already computed
+ long miscSize = 0;
+ for (File file : files) {
+ final String name = file.getName();
+ if (sMeasureMediaTypes.contains(name)) {
+ continue;
+ }
+
+ if (file.isFile()) {
+ miscSize += file.length();
+ } else if (file.isDirectory()) {
+ miscSize += getDirectorySize(imcs, file);
+ }
+ }
+ return miscSize;
+ }
+
+ private static void addValue(SparseLongArray array, int key, long value) {
+ array.put(key, array.get(key) + value);
+ }
+}