Wire up storage stats API to installd.

Now that installd has the implementation details we need, we can wire
up the public APIs to use them.

Shuffle APIs around a bit so that StorageStats can be reused for both
UID and UserHandle results, and rename StorageSummary to
ExternalStorageStats.  Provide getTotalBytes() and getFreeBytes() as
first-class methods so we can answer those questions quickly without
paying the cost of measuring external storage details.

Current costs on a typical device with a test account after flushing
dentry caches:

queryStatsForUid() manual: 6922ms
queryStatsForUid() quota: 525ms

queryStatsForUser() manual: 1686ms
queryStatsForUser() quota: 113ms

queryExternalStatsForUser() manual: 42ms
queryExternalStatsForUser() quota: 2ms

For verification purposes, a new "fw.verify_storage" system property
can be set to compute both manual and quota statistics, and log any
discrepancies.

Test: builds, boots
Bug: 27948817, 32206268
Change-Id: I4ea3df3372a7379aa8cf4c20c44120c8f0702c15
This commit is contained in:
Jeff Sharkey
2017-01-09 18:21:21 -07:00
parent bd439dcef0
commit 8dedad31f7
12 changed files with 455 additions and 297 deletions

View File

@@ -690,8 +690,8 @@ aidl_files := \
frameworks/base/core/java/android/service/notification/StatusBarNotification.aidl \
frameworks/base/core/java/android/service/chooser/ChooserTarget.aidl \
frameworks/base/core/java/android/speech/tts/Voice.aidl \
frameworks/base/core/java/android/app/usage/ExternalStorageStats.aidl \
frameworks/base/core/java/android/app/usage/StorageStats.aidl \
frameworks/base/core/java/android/app/usage/StorageSummary.aidl \
frameworks/base/core/java/android/app/usage/UsageEvents.aidl \
frameworks/base/core/java/android/app/Notification.aidl \
frameworks/base/core/java/android/app/NotificationManager.aidl \

View File

@@ -6627,6 +6627,16 @@ package android.app.usage {
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
public final class ExternalStorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getAudioBytes();
method public long getImageBytes();
method public long getTotalBytes();
method public long getVideoBytes();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.ExternalStorageStats> CREATOR;
}
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6681,31 +6691,18 @@ package android.app.usage {
public final class StorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getCacheBytes();
method public long getCacheQuotaBytes();
method public long getCodeBytes();
method public long getDataBytes();
method public int getUid();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
public class StorageStatsManager {
method public long getFreeBytes(java.lang.String);
method public long getTotalBytes(java.lang.String);
method public android.app.usage.ExternalStorageStats queryExternalStatsForUser(java.lang.String, android.os.UserHandle);
method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int);
method public android.app.usage.StorageSummary querySummary(java.lang.String);
}
public final class StorageSummary implements android.os.Parcelable {
method public int describeContents();
method public long getFreeBytes();
method public long getSharedAudioBytes();
method public long getSharedImagesBytes();
method public long getSharedTotalBytes();
method public long getSharedVideoBytes();
method public long getTotalBytes();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageSummary> CREATOR;
method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle);
}
public final class UsageEvents implements android.os.Parcelable {

View File

@@ -6940,6 +6940,16 @@ package android.app.usage {
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
public final class ExternalStorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getAudioBytes();
method public long getImageBytes();
method public long getTotalBytes();
method public long getVideoBytes();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.ExternalStorageStats> CREATOR;
}
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6994,31 +7004,18 @@ package android.app.usage {
public final class StorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getCacheBytes();
method public long getCacheQuotaBytes();
method public long getCodeBytes();
method public long getDataBytes();
method public int getUid();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
public class StorageStatsManager {
method public long getFreeBytes(java.lang.String);
method public long getTotalBytes(java.lang.String);
method public android.app.usage.ExternalStorageStats queryExternalStatsForUser(java.lang.String, android.os.UserHandle);
method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int);
method public android.app.usage.StorageSummary querySummary(java.lang.String);
}
public final class StorageSummary implements android.os.Parcelable {
method public int describeContents();
method public long getFreeBytes();
method public long getSharedAudioBytes();
method public long getSharedImagesBytes();
method public long getSharedTotalBytes();
method public long getSharedVideoBytes();
method public long getTotalBytes();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageSummary> CREATOR;
method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle);
}
public final class UsageEvents implements android.os.Parcelable {

View File

@@ -6649,6 +6649,16 @@ package android.app.usage {
field public static final android.os.Parcelable.Creator<android.app.usage.ConfigurationStats> CREATOR;
}
public final class ExternalStorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getAudioBytes();
method public long getImageBytes();
method public long getTotalBytes();
method public long getVideoBytes();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.ExternalStorageStats> CREATOR;
}
public final class NetworkStats implements java.lang.AutoCloseable {
method public void close();
method public boolean getNextBucket(android.app.usage.NetworkStats.Bucket);
@@ -6703,31 +6713,18 @@ package android.app.usage {
public final class StorageStats implements android.os.Parcelable {
method public int describeContents();
method public long getCacheBytes();
method public long getCacheQuotaBytes();
method public long getCodeBytes();
method public long getDataBytes();
method public int getUid();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageStats> CREATOR;
}
public class StorageStatsManager {
method public long getFreeBytes(java.lang.String);
method public long getTotalBytes(java.lang.String);
method public android.app.usage.ExternalStorageStats queryExternalStatsForUser(java.lang.String, android.os.UserHandle);
method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int);
method public android.app.usage.StorageSummary querySummary(java.lang.String);
}
public final class StorageSummary implements android.os.Parcelable {
method public int describeContents();
method public long getFreeBytes();
method public long getSharedAudioBytes();
method public long getSharedImagesBytes();
method public long getSharedTotalBytes();
method public long getSharedVideoBytes();
method public long getTotalBytes();
method public java.lang.String getVolumeUuid();
method public void writeToParcel(android.os.Parcel, int);
field public static final android.os.Parcelable.Creator<android.app.usage.StorageSummary> CREATOR;
method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle);
}
public final class UsageEvents implements android.os.Parcelable {

View File

@@ -16,4 +16,4 @@
package android.app.usage;
parcelable StorageSummary;
parcelable ExternalStorageStats;

View File

@@ -0,0 +1,103 @@
/*
* Copyright (C) 2017 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 android.app.usage;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
/**
* Shared/external storage statistics for a {@link UserHandle} on a single
* storage volume.
*
* @see StorageStatsManager
*/
public final class ExternalStorageStats implements Parcelable {
/** {@hide} */ public long totalBytes;
/** {@hide} */ public long audioBytes;
/** {@hide} */ public long videoBytes;
/** {@hide} */ public long imageBytes;
/**
* Return the total bytes used by all files in the shared/external storage
* hosted on this volume.
*/
public long getTotalBytes() {
return totalBytes;
}
/**
* Return the total bytes used by audio files in the shared/external storage
* hosted on this volume.
*/
public long getAudioBytes() {
return audioBytes;
}
/**
* Return the total bytes used by video files in the shared/external storage
* hosted on this volume.
*/
public long getVideoBytes() {
return videoBytes;
}
/**
* Return the total bytes used by image files in the shared/external storage
* hosted on this volume.
*/
public long getImageBytes() {
return imageBytes;
}
/** {@hide} */
public ExternalStorageStats() {
}
/** {@hide} */
public ExternalStorageStats(Parcel in) {
this.totalBytes = in.readLong();
this.audioBytes = in.readLong();
this.videoBytes = in.readLong();
this.imageBytes = in.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeLong(totalBytes);
dest.writeLong(audioBytes);
dest.writeLong(videoBytes);
dest.writeLong(imageBytes);
}
public static final Creator<ExternalStorageStats> CREATOR = new Creator<ExternalStorageStats>() {
@Override
public ExternalStorageStats createFromParcel(Parcel in) {
return new ExternalStorageStats(in);
}
@Override
public ExternalStorageStats[] newArray(int size) {
return new ExternalStorageStats[size];
}
};
}

View File

@@ -17,10 +17,13 @@
package android.app.usage;
import android.app.usage.StorageStats;
import android.app.usage.StorageSummary;
import android.app.usage.ExternalStorageStats;
/** {@hide} */
interface IStorageStatsManager {
StorageStats queryStats(String volumeUuid, int uid, String callingPackage);
StorageSummary querySummary(String volumeUuid, String callingPackage);
long getTotalBytes(String volumeUuid, String callingPackage);
long getFreeBytes(String volumeUuid, String callingPackage);
StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage);
StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage);
ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId, String callingPackage);
}

View File

@@ -17,13 +17,13 @@
package android.app.usage;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.os.Parcel;
import android.os.Parcelable;
import android.os.UserHandle;
/**
* Storage statistics for a single UID on a single storage volume.
* Storage statistics for a UID or {@link UserHandle} on a single storage
* volume.
* <p class="note">
* Note: multiple packages using the same {@code sharedUserId} in their manifest
* will be merged into a single UID.
@@ -32,40 +32,13 @@ import android.os.Parcelable;
* @see StorageStatsManager
*/
public final class StorageStats implements Parcelable {
/** {@hide} */ public final String volumeUuid;
/** {@hide} */ public final int uid;
/** {@hide} */ public long codeBytes;
/** {@hide} */ public long dataBytes;
/** {@hide} */ public long cacheQuotaBytes;
/** {@hide} */ public long cacheBytes;
/**
* Return the UUID of the storage volume that these statistics refer to. The
* value {@code null} indicates the default internal storage.
*/
public String getVolumeUuid() {
return volumeUuid;
}
/**
* Return the UID that these statistics refer to.
*
* @see ApplicationInfo#uid
* @see PackageManager#getPackagesForUid(int)
*/
public int getUid() {
return uid;
}
/**
* Return the size of all code associated with {@link #getUid()} on
* {@link #getVolumeUuid()}. This includes {@code APK} files and optimized
* compiler output.
* <p>
* If the primary shared storage location is also hosted on
* {@link #getVolumeUuid()} then this includes files stored under
* {@link Context#getObbDir()}.
* Return the size of all code. This includes {@code APK} files and
* optimized compiler output.
* <p>
* Code is shared between all users on a multiuser device.
*/
@@ -74,16 +47,10 @@ public final class StorageStats implements Parcelable {
}
/**
* Return the size of all data associated with {@link #getUid()} on
* {@link #getVolumeUuid()}. This includes files stored under
* Return the size of all data. This includes files stored under
* {@link Context#getDataDir()}, {@link Context#getCacheDir()},
* {@link Context#getCodeCacheDir()}.
* <p>
* If the primary shared storage location is also hosted on
* {@link #getVolumeUuid()} then this includes files stored under
* {@link Context#getExternalFilesDir(String)} and
* {@link Context#getExternalCacheDir()}.
* <p>
* Data is isolated for each user on a multiuser device.
*/
public long getDataBytes() {
@@ -91,26 +58,9 @@ public final class StorageStats implements Parcelable {
}
/**
* Return the quota for cached data associated with {@link #getUid()} on
* {@link #getVolumeUuid()}. This quota value is calculated based on how
* frequently the user has interacted with the UID.
* <p>
* When clearing cached data, the system will first focus on packages whose
* cached data is larger than their allocated quota.
*/
public long getCacheQuotaBytes() {
return cacheQuotaBytes;
}
/**
* Return the size of all cached data associated with {@link #getUid()} on
* {@link #getVolumeUuid()}. This includes files stored under
* Return the size of all cached data. This includes files stored under
* {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}.
* <p>
* If the primary shared storage location is also hosted on
* {@link #getVolumeUuid()} then this includes files stored under
* {@link Context#getExternalCacheDir()}.
* <p>
* Cached data is isolated for each user on a multiuser device.
*/
public long getCacheBytes() {
@@ -118,18 +68,13 @@ public final class StorageStats implements Parcelable {
}
/** {@hide} */
public StorageStats(String uuid, int uid) {
this.volumeUuid = uuid;
this.uid = uid;
public StorageStats() {
}
/** {@hide} */
public StorageStats(Parcel in) {
this.volumeUuid = in.readString();
this.uid = in.readInt();
this.codeBytes = in.readLong();
this.dataBytes = in.readLong();
this.cacheQuotaBytes = in.readLong();
this.cacheBytes = in.readLong();
}
@@ -140,11 +85,8 @@ public final class StorageStats implements Parcelable {
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(volumeUuid);
dest.writeInt(uid);
dest.writeLong(codeBytes);
dest.writeLong(dataBytes);
dest.writeLong(cacheQuotaBytes);
dest.writeLong(cacheBytes);
}

View File

@@ -19,9 +19,12 @@ package android.app.usage;
import android.annotation.WorkerThread;
import android.content.Context;
import android.os.RemoteException;
import android.os.UserHandle;
import com.android.internal.util.Preconditions;
import java.io.File;
/**
* Provides access to detailed storage statistics.
* <p class="note">
@@ -43,8 +46,52 @@ public class StorageStatsManager {
}
/**
* Return detailed statistics for the a specific UID on the requested
* storage volume.
* Return the total space on the requested storage volume.
* <p>
* To reduce end user confusion, this value is the total storage size
* advertised in a retail environment, which is typically larger than the
* actual writable partition total size.
* <p>
* This method may take several seconds to calculate the requested values,
* so it should only be called from a worker thread.
*
* @param volumeUuid the UUID of the storage volume you're interested in, or
* {@code null} to specify the default internal storage.
*/
@WorkerThread
public long getTotalBytes(String volumeUuid) {
try {
return mService.getTotalBytes(volumeUuid, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return the free space on the requested storage volume.
* <p>
* The free space is equivalent to {@link File#getFreeSpace()} plus the size
* of any cached data that can be automatically deleted by the system as
* additional space is needed.
* <p>
* This method may take several seconds to calculate the requested values,
* so it should only be called from a worker thread.
*
* @param volumeUuid the UUID of the storage volume you're interested in, or
* {@code null} to specify the default internal storage.
*/
@WorkerThread
public long getFreeBytes(String volumeUuid) {
try {
return mService.getFreeBytes(volumeUuid, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return storage statistics for a specific UID on the requested storage
* volume.
* <p>
* This method may take several seconds to calculate the requested values,
* so it should only be called from a worker thread.
@@ -56,25 +103,50 @@ public class StorageStatsManager {
@WorkerThread
public StorageStats queryStatsForUid(String volumeUuid, int uid) {
try {
return mService.queryStats(volumeUuid, uid, mContext.getOpPackageName());
return mService.queryStatsForUid(volumeUuid, uid, mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return summary statistics for the requested storage volume.
* Return storage statistics for a specific {@link UserHandle} on the
* requested storage volume.
* <p>
* This method may take several seconds to calculate the requested values,
* so it should only be called from a worker thread.
*
* @param volumeUuid the UUID of the storage volume you're interested in, or
* {@code null} to specify the default internal storage.
* @param user the user you're interested in.
* @see android.os.Process#myUserHandle()
*/
@WorkerThread
public StorageSummary querySummary(String volumeUuid) {
public StorageStats queryStatsForUser(String volumeUuid, UserHandle user) {
try {
return mService.querySummary(volumeUuid, mContext.getOpPackageName());
return mService.queryStatsForUser(volumeUuid, user.getIdentifier(),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/**
* Return shared/external storage statistics for a specific
* {@link UserHandle} on the requested storage volume.
* <p>
* This method may take several seconds to calculate the requested values,
* so it should only be called from a worker thread.
*
* @param volumeUuid the UUID of the storage volume you're interested in, or
* {@code null} to specify the default internal storage.
* @see android.os.Process#myUserHandle()
*/
@WorkerThread
public ExternalStorageStats queryExternalStatsForUser(String volumeUuid, UserHandle user) {
try {
return mService.queryExternalStatsForUser(volumeUuid, user.getIdentifier(),
mContext.getOpPackageName());
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}

View File

@@ -1,161 +0,0 @@
/*
* Copyright (C) 2017 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 android.app.usage;
import android.os.Parcel;
import android.os.Parcelable;
import java.io.File;
/**
* Storage summary for a single storage volume.
* <p>
* When presenting this summary to users, the
* <p>
* Details for specific UIDs are available through {@link StorageStats}.
*
* @see StorageStatsManager
*/
public final class StorageSummary implements Parcelable {
/** {@hide} */ public final String volumeUuid;
/** {@hide} */ public long totalBytes;
/** {@hide} */ public long freeBytes;
/** {@hide} */ public long sharedTotalBytes;
/** {@hide} */ public long sharedAudioBytes;
/** {@hide} */ public long sharedVideoBytes;
/** {@hide} */ public long sharedImagesBytes;
/**
* Return the UUID of the storage volume that these statistics refer to. The
* value {@code null} indicates the default internal storage.
*/
public String getVolumeUuid() {
return volumeUuid;
}
/**
* Return the total size of the storage volume.
* <p>
* To reduce end user confusion, this value is the total storage size
* advertised in a retail environment, which is typically larger than the
* actual writable partition total size.
*/
public long getTotalBytes() {
return totalBytes;
}
/**
* Return the free space on this storage volume.
* <p>
* The free space is equivalent to {@link File#getFreeSpace()} plus the size
* of any cached data that can be automatically deleted by the system as
* additional space is needed.
*/
public long getFreeBytes() {
return freeBytes;
}
/**
* Return the total bytes used by all files in the shared storage hosted on
* this volume.
*
* @return total bytes, or {@code -1} if no shared storage is hosted on this
* volume.
*/
public long getSharedTotalBytes() {
return sharedTotalBytes;
}
/**
* Return the total bytes used by audio files in the shared storage hosted
* on this volume.
*
* @return total bytes, or {@code -1} if no shared storage is hosted on this
* volume.
*/
public long getSharedAudioBytes() {
return sharedAudioBytes;
}
/**
* Return the total bytes used by video files in the shared storage hosted
* on this volume.
*
* @return total bytes, or {@code -1} if no shared storage is hosted on this
* volume.
*/
public long getSharedVideoBytes() {
return sharedVideoBytes;
}
/**
* Return the total bytes used by image files in the shared storage hosted
* on this volume.
*
* @return total bytes, or {@code -1} if no shared storage is hosted on this
* volume.
*/
public long getSharedImagesBytes() {
return sharedImagesBytes;
}
/** {@hide} */
public StorageSummary(String uuid) {
this.volumeUuid = uuid;
}
/** {@hide} */
public StorageSummary(Parcel in) {
this.volumeUuid = in.readString();
this.totalBytes = in.readLong();
this.freeBytes = in.readLong();
this.sharedTotalBytes = in.readLong();
this.sharedAudioBytes = in.readLong();
this.sharedVideoBytes = in.readLong();
this.sharedImagesBytes = in.readLong();
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeString(volumeUuid);
dest.writeLong(totalBytes);
dest.writeLong(freeBytes);
dest.writeLong(sharedTotalBytes);
dest.writeLong(sharedAudioBytes);
dest.writeLong(sharedVideoBytes);
dest.writeLong(sharedImagesBytes);
}
public static final Creator<StorageSummary> CREATOR = new Creator<StorageSummary>() {
@Override
public StorageSummary createFromParcel(Parcel in) {
return new StorageSummary(in);
}
@Override
public StorageSummary[] newArray(int size) {
return new StorageSummary[size];
}
};
}

View File

@@ -19,6 +19,7 @@ package com.android.server.pm;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageStats;
import android.os.SystemClock;
import android.os.UserHandle;
import android.test.AndroidTestCase;
import android.util.Log;
@@ -32,14 +33,49 @@ public class InstallerTest extends AndroidTestCase {
private Installer mInstaller;
private final Timer mManual = new Timer("Manual");
private final Timer mQuota = new Timer("Quota");
private static class Timer {
private final String mTitle;
private long mStart;
private long mTotal;
public Timer(String title) {
mTitle = title;
}
public void start() {
mStart = SystemClock.currentTimeMicro();
}
public void stop() {
mTotal += SystemClock.currentTimeMicro() - mStart;
}
public void reset() {
mStart = 0;
mTotal = 0;
}
@Override
public String toString() {
return mTitle + ": " + (mTotal / 1000) + "ms";
}
}
@Override
public void setUp() throws Exception {
mInstaller = new Installer(getContext());
mInstaller.onStart();
mManual.reset();
mQuota.reset();
}
@Override
public void tearDown() throws Exception {
Log.i(TAG, mManual.toString());
Log.i(TAG, mQuota.toString());
mInstaller = null;
}
@@ -61,11 +97,15 @@ public class InstallerTest extends AndroidTestCase {
final PackageStats stats = new PackageStats(app.packageName);
final PackageStats quotaStats = new PackageStats(app.packageName);
mManual.start();
mInstaller.getAppSize(app.volumeUuid, packageNames, userId, 0,
appId, ceDataInodes, codePaths, stats);
mManual.stop();
mQuota.start();
mInstaller.getAppSize(app.volumeUuid, packageNames, userId, Installer.FLAG_USE_QUOTA,
appId, ceDataInodes, codePaths, quotaStats);
mQuota.stop();
checkEquals(Arrays.toString(packageNames) + " UID=" + app.uid, stats, quotaStats);
}
@@ -85,21 +125,28 @@ public class InstallerTest extends AndroidTestCase {
final PackageStats stats = new PackageStats("android");
final PackageStats quotaStats = new PackageStats("android");
mManual.start();
mInstaller.getUserSize(null, UserHandle.USER_SYSTEM, 0,
appIds, stats);
mManual.stop();
mQuota.start();
mInstaller.getUserSize(null, UserHandle.USER_SYSTEM, Installer.FLAG_USE_QUOTA,
appIds, quotaStats);
mQuota.stop();
checkEquals(Arrays.toString(appIds), stats, quotaStats);
}
public void testGetExternalSize() throws Exception {
mManual.start();
final long[] stats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, 0);
mManual.stop();
mQuota.start();
final long[] quotaStats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM,
Installer.FLAG_USE_QUOTA);
mQuota.stop();
for (int i = 0; i < stats.length; i++) {
checkEquals("#" + i, stats[i], quotaStats[i]);

View File

@@ -17,20 +17,36 @@
package com.android.server.usage;
import android.app.AppOpsManager;
import android.app.usage.ExternalStorageStats;
import android.app.usage.IStorageStatsManager;
import android.app.usage.StorageStats;
import android.app.usage.StorageSummary;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageStats;
import android.content.pm.UserInfo;
import android.os.Binder;
import android.os.Environment;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.UserManager;
import android.os.storage.StorageManager;
import android.os.storage.VolumeInfo;
import android.util.Log;
import com.android.internal.util.ArrayUtils;
import com.android.server.SystemService;
import com.android.server.pm.Installer;
import com.android.server.pm.Installer.InstallerException;
public class StorageStatsService extends IStorageStatsManager.Stub {
private static final String TAG = "StorageStatsService";
private static final String PROP_VERIFY_STORAGE = "fw.verify_storage";
// TODO: pivot all methods to manual mode when quota isn't supported
public static class Lifecycle extends SystemService {
private StorageStatsService mService;
@@ -47,12 +63,16 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
private final Context mContext;
private final AppOpsManager mAppOps;
private final UserManager mUser;
private final PackageManager mPackage;
private final StorageManager mStorage;
private final Installer mInstaller;
public StorageStatsService(Context context) {
mContext = context;
mAppOps = context.getSystemService(AppOpsManager.class);
mUser = context.getSystemService(UserManager.class);
mPackage = context.getSystemService(PackageManager.class);
mStorage = context.getSystemService(StorageManager.class);
mInstaller = new Installer(context);
}
@@ -72,22 +92,163 @@ public class StorageStatsService extends IStorageStatsManager.Stub {
}
@Override
public StorageStats queryStats(String volumeUuid, int uid, String callingPackage) {
public long getTotalBytes(String volumeUuid, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
// TODO: round total size to nearest power of two
return mStorage.getPrimaryStorageSize();
} else {
final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
return vol.disk.size;
}
}
@Override
public long getFreeBytes(String volumeUuid, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
long cacheBytes = 0;
for (UserInfo user : mUser.getUsers()) {
final StorageStats stats = queryStatsForUser(volumeUuid, user.id, null);
cacheBytes += stats.cacheBytes;
}
if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) {
return Environment.getDataDirectory().getFreeSpace() + cacheBytes;
} else {
final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid);
return vol.getPath().getFreeSpace() + cacheBytes;
}
}
@Override
public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
if (UserHandle.getUserId(uid) != UserHandle.getCallingUserId()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
}
// TODO: call installd to collect quota stats
return null;
final int userId = UserHandle.getUserId(uid);
final int appId = UserHandle.getUserId(uid);
final String[] packageNames = mPackage.getPackagesForUid(uid);
final long[] ceDataInodes = new long[packageNames.length];
final String[] codePaths = new String[packageNames.length];
for (int i = 0; i < packageNames.length; i++) {
try {
codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], 0,
userId).getCodePath();
} catch (NameNotFoundException e) {
throw new IllegalStateException(e);
}
}
final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getAppSize(volumeUuid, packageNames, userId, Installer.FLAG_USE_QUOTA,
appId, ceDataInodes, codePaths, stats);
if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
final PackageStats manualStats = new PackageStats(TAG);
mInstaller.getAppSize(volumeUuid, packageNames, userId, 0,
appId, ceDataInodes, codePaths, manualStats);
checkEquals("UID " + uid, manualStats, stats);
}
} catch (InstallerException e) {
throw new IllegalStateException(e);
}
return translate(stats);
}
@Override
public StorageSummary querySummary(String volumeUuid, String callingPackage) {
public StorageStats queryStatsForUser(String volumeUuid, int userId, String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
if (userId != UserHandle.getCallingUserId()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
}
// TODO: call installd to collect quota stats
return null;
int[] appIds = null;
for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser(0, userId)) {
final int appId = UserHandle.getAppId(app.uid);
if (!ArrayUtils.contains(appIds, appId)) {
appIds = ArrayUtils.appendInt(appIds, appId);
}
}
final PackageStats stats = new PackageStats(TAG);
try {
mInstaller.getUserSize(volumeUuid, userId, Installer.FLAG_USE_QUOTA, appIds, stats);
if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
final PackageStats manualStats = new PackageStats(TAG);
mInstaller.getUserSize(volumeUuid, userId, 0, appIds, manualStats);
checkEquals("User " + userId, manualStats, stats);
}
} catch (InstallerException e) {
throw new IllegalStateException(e);
}
return translate(stats);
}
@Override
public ExternalStorageStats queryExternalStatsForUser(String volumeUuid, int userId,
String callingPackage) {
enforcePermission(Binder.getCallingUid(), callingPackage);
if (userId != UserHandle.getCallingUserId()) {
mContext.enforceCallingOrSelfPermission(
android.Manifest.permission.INTERACT_ACROSS_USERS, TAG);
}
final long[] stats;
try {
stats = mInstaller.getExternalSize(volumeUuid, userId, Installer.FLAG_USE_QUOTA);
if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) {
final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0);
checkEquals("External " + userId, manualStats, stats);
}
} catch (InstallerException e) {
throw new IllegalStateException(e);
}
final ExternalStorageStats res = new ExternalStorageStats();
res.totalBytes = stats[0];
res.audioBytes = stats[1];
res.videoBytes = stats[2];
res.imageBytes = stats[3];
return res;
}
private static void checkEquals(String msg, long[] a, long[] b) {
for (int i = 0; i < a.length; i++) {
checkEquals(msg + "[" + i + "]", a[i], b[i]);
}
}
private static void checkEquals(String msg, PackageStats a, PackageStats b) {
checkEquals(msg + " codeSize", a.codeSize, b.codeSize);
checkEquals(msg + " dataSize", a.dataSize, b.dataSize);
checkEquals(msg + " cacheSize", a.cacheSize, b.cacheSize);
checkEquals(msg + " externalCodeSize", a.externalCodeSize, b.externalCodeSize);
checkEquals(msg + " externalDataSize", a.externalDataSize, b.externalDataSize);
checkEquals(msg + " externalCacheSize", a.externalCacheSize, b.externalCacheSize);
}
private static void checkEquals(String msg, long expected, long actual) {
if (expected != actual) {
Log.e(TAG, msg + " expected " + expected + " actual " + actual);
}
}
private static StorageStats translate(PackageStats stats) {
final StorageStats res = new StorageStats();
res.codeBytes = stats.codeSize + stats.externalCodeSize;
res.dataBytes = stats.dataSize + stats.externalDataSize;
res.cacheBytes = stats.cacheSize + stats.externalCacheSize;
return res;
}
}