diff options
author | Tony Mantler <nicoya@google.com> | 2015-04-23 14:16:44 -0700 |
---|---|---|
committer | Tony Mantler <nicoya@google.com> | 2015-04-23 14:16:44 -0700 |
commit | 547e4caebe0aac21d0856de96e121b8bf63fd9bd (patch) | |
tree | 6432c4faf977672ceee234fe8f5a7d91698c8d17 /packages/SettingsLib | |
parent | ce510d5f62a0e4dc46d3d3c05fafe9b08eb5b497 (diff) | |
download | frameworks_base-547e4caebe0aac21d0856de96e121b8bf63fd9bd.zip frameworks_base-547e4caebe0aac21d0856de96e121b8bf63fd9bd.tar.gz frameworks_base-547e4caebe0aac21d0856de96e121b8bf63fd9bd.tar.bz2 |
Move StorageMeasurement to SettingsLib
Change-Id: I90ea3b32971f02def418385ebac1f03f06390ba7
Diffstat (limited to 'packages/SettingsLib')
-rw-r--r-- | packages/SettingsLib/src/com/android/settingslib/deviceinfo/StorageMeasurement.java | 441 |
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); + } +} |