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:
@@ -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 \
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -16,4 +16,4 @@
|
||||
|
||||
package android.app.usage;
|
||||
|
||||
parcelable StorageSummary;
|
||||
parcelable ExternalStorageStats;
|
||||
103
core/java/android/app/usage/ExternalStorageStats.java
Normal file
103
core/java/android/app/usage/ExternalStorageStats.java
Normal 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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
|
||||
@@ -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];
|
||||
}
|
||||
};
|
||||
}
|
||||
@@ -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]);
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user