diff --git a/Android.mk b/Android.mk index 8c0c640f27645..2ea22f31fdb57 100644 --- a/Android.mk +++ b/Android.mk @@ -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 \ diff --git a/api/current.txt b/api/current.txt index 8a19a7e72d0d9..8323f5ae2e1fe 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6627,6 +6627,16 @@ package android.app.usage { field public static final android.os.Parcelable.Creator 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 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 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 CREATOR; + method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } public final class UsageEvents implements android.os.Parcelable { diff --git a/api/system-current.txt b/api/system-current.txt index 58fc103215f11..a141a4a9cfd41 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -6940,6 +6940,16 @@ package android.app.usage { field public static final android.os.Parcelable.Creator 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 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 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 CREATOR; + method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } public final class UsageEvents implements android.os.Parcelable { diff --git a/api/test-current.txt b/api/test-current.txt index 0129832a07b34..9d17ea6ee336e 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6649,6 +6649,16 @@ package android.app.usage { field public static final android.os.Parcelable.Creator 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 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 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 CREATOR; + method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } public final class UsageEvents implements android.os.Parcelable { diff --git a/core/java/android/app/usage/StorageSummary.aidl b/core/java/android/app/usage/ExternalStorageStats.aidl similarity index 95% rename from core/java/android/app/usage/StorageSummary.aidl rename to core/java/android/app/usage/ExternalStorageStats.aidl index b20e025130e62..2d9375c1f9dc6 100644 --- a/core/java/android/app/usage/StorageSummary.aidl +++ b/core/java/android/app/usage/ExternalStorageStats.aidl @@ -16,4 +16,4 @@ package android.app.usage; -parcelable StorageSummary; +parcelable ExternalStorageStats; diff --git a/core/java/android/app/usage/ExternalStorageStats.java b/core/java/android/app/usage/ExternalStorageStats.java new file mode 100644 index 0000000000000..1166df06c9175 --- /dev/null +++ b/core/java/android/app/usage/ExternalStorageStats.java @@ -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 CREATOR = new Creator() { + @Override + public ExternalStorageStats createFromParcel(Parcel in) { + return new ExternalStorageStats(in); + } + + @Override + public ExternalStorageStats[] newArray(int size) { + return new ExternalStorageStats[size]; + } + }; +} diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl index 96125a17b3a69..62ebf6085aba8 100644 --- a/core/java/android/app/usage/IStorageStatsManager.aidl +++ b/core/java/android/app/usage/IStorageStatsManager.aidl @@ -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); } diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java index b2486d8c6b235..b3104f6821e22 100644 --- a/core/java/android/app/usage/StorageStats.java +++ b/core/java/android/app/usage/StorageStats.java @@ -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. *

* 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. - *

- * 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. *

* 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()}. *

- * 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()}. - *

* 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. - *

- * 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()}. *

- * If the primary shared storage location is also hosted on - * {@link #getVolumeUuid()} then this includes files stored under - * {@link Context#getExternalCacheDir()}. - *

* 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); } diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index cdfbe0c0b82fa..695994b4eae35 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -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. *

@@ -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. + *

+ * 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. + *

+ * 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. + *

+ * 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. + *

+ * 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. *

* 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. *

* 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. + *

+ * 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(); } diff --git a/core/java/android/app/usage/StorageSummary.java b/core/java/android/app/usage/StorageSummary.java deleted file mode 100644 index e72c9ecd2e251..0000000000000 --- a/core/java/android/app/usage/StorageSummary.java +++ /dev/null @@ -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. - *

- * When presenting this summary to users, the - *

- * 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. - *

- * 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. - *

- * 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 CREATOR = new Creator() { - @Override - public StorageSummary createFromParcel(Parcel in) { - return new StorageSummary(in); - } - - @Override - public StorageSummary[] newArray(int size) { - return new StorageSummary[size]; - } - }; -} diff --git a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java index 2a7cbc21137ab..5ab902083b22b 100644 --- a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java @@ -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]); diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index cb9cb1219a0b8..512a119927bb6 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -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; } }