+++ /dev/null
-/*
- * 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.settings.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);
- }
-}