diff --git a/api/current.txt b/api/current.txt index 202ea21ca2961..e58aa0c44717c 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6882,6 +6882,7 @@ package android.app.usage { 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 queryStatsForPackage(java.lang.String, java.lang.String, android.os.UserHandle); method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int); method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } diff --git a/api/system-current.txt b/api/system-current.txt index 2f67204594706..367d471fdc161 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7304,6 +7304,7 @@ package android.app.usage { 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 queryStatsForPackage(java.lang.String, java.lang.String, android.os.UserHandle); method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int); method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } diff --git a/api/test-current.txt b/api/test-current.txt index 41f279bf20fdf..78c2d504da009 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6908,6 +6908,7 @@ package android.app.usage { 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 queryStatsForPackage(java.lang.String, java.lang.String, android.os.UserHandle); method public android.app.usage.StorageStats queryStatsForUid(java.lang.String, int); method public android.app.usage.StorageStats queryStatsForUser(java.lang.String, android.os.UserHandle); } diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl index f4c18dda51606..76c0293566bbd 100644 --- a/core/java/android/app/usage/IStorageStatsManager.aidl +++ b/core/java/android/app/usage/IStorageStatsManager.aidl @@ -24,6 +24,7 @@ interface IStorageStatsManager { boolean isQuotaSupported(String volumeUuid, String callingPackage); long getTotalBytes(String volumeUuid, String callingPackage); long getFreeBytes(String volumeUuid, String callingPackage); + StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, 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/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index 7d4efb9c108b2..081ccd956d6ea 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -19,6 +19,7 @@ package android.app.usage; import android.annotation.WorkerThread; import android.content.Context; import android.content.pm.ApplicationInfo; +import android.content.pm.PackageInfo; import android.os.RemoteException; import android.os.UserHandle; @@ -99,6 +100,37 @@ public class StorageStatsManager { } } + /** + * Return storage statistics for a specific package 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. + *
+ * Note: if the requested package uses the {@code android:sharedUserId} + * manifest feature, this call will be forced into a slower manual + * calculation path. If possible, consider always using + * {@link #queryStatsForUid(String, int)}, which is typically faster. + *
+ * + * @param volumeUuid the UUID of the storage volume you're interested in, or + * {@code null} to specify the default internal storage. + * @param packageName the package name you're interested in. + * @param user the user you're interested in. + * @see ApplicationInfo#volumeUuid + * @see PackageInfo#packageName + */ + @WorkerThread + public StorageStats queryStatsForPackage(String volumeUuid, String packageName, + UserHandle user) { + try { + return mService.queryStatsForPackage(volumeUuid, packageName, user.getIdentifier(), + mContext.getOpPackageName()); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + /** * Return storage statistics for a specific UID on the requested storage * volume. diff --git a/core/java/android/os/FileUtils.java b/core/java/android/os/FileUtils.java index 280fa2ca9496a..af05ee7978ceb 100644 --- a/core/java/android/os/FileUtils.java +++ b/core/java/android/os/FileUtils.java @@ -770,4 +770,17 @@ public class FileUtils { return dir.mkdir() ? dir : null; } + + /** + * Round the given size of a storage device to a nice round power-of-two + * value, such as 256MB or 32GB. This avoids showing weird values like + * "29.5GB" in UI. + */ + public static long roundStorageSize(long size) { + long res = 1; + while (res < size) { + res <<= 1; + } + return res; + } } diff --git a/core/tests/coretests/src/android/os/FileUtilsTest.java b/core/tests/coretests/src/android/os/FileUtilsTest.java index bd900793f87d9..5c7da7099f32b 100644 --- a/core/tests/coretests/src/android/os/FileUtilsTest.java +++ b/core/tests/coretests/src/android/os/FileUtilsTest.java @@ -311,6 +311,28 @@ public class FileUtilsTest extends AndroidTestCase { assertNameEquals("test.foo (1).bar", FileUtils.buildUniqueFile(mTarget, "test.foo.bar")); } + public void testRoundStorageSize() throws Exception { + final long M128 = 134217728L; + final long M256 = M128 * 2; + final long M512 = M256 * 2; + final long M1024 = M512 * 2; + final long G16 = M1024 * 16; + final long G32 = M1024 * 32; + final long G64 = M1024 * 64; + + assertEquals(M128, FileUtils.roundStorageSize(M128)); + assertEquals(M256, FileUtils.roundStorageSize(M128 + 1)); + assertEquals(M256, FileUtils.roundStorageSize(M256 - 1)); + assertEquals(M256, FileUtils.roundStorageSize(M256)); + assertEquals(M512, FileUtils.roundStorageSize(M256 + 1)); + + assertEquals(G16, FileUtils.roundStorageSize(G16)); + assertEquals(G32, FileUtils.roundStorageSize(G16 + 1)); + assertEquals(G32, FileUtils.roundStorageSize(G32 - 1)); + assertEquals(G32, FileUtils.roundStorageSize(G32)); + assertEquals(G64, FileUtils.roundStorageSize(G32 + 1)); + } + private static void assertNameEquals(String expected, File actual) { assertEquals(expected, actual.getName()); } diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 632c045e21115..ed1530a4eddb2 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -30,6 +30,7 @@ import android.content.pm.PackageStats; import android.content.pm.UserInfo; import android.os.Binder; import android.os.Environment; +import android.os.FileUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; @@ -148,11 +149,10 @@ public class StorageStatsService extends IStorageStatsManager.Stub { enforcePermission(Binder.getCallingUid(), callingPackage); if (volumeUuid == StorageManager.UUID_PRIVATE_INTERNAL) { - // TODO: round total size to nearest power of two - return mStorage.getPrimaryStorageSize(); + return FileUtils.roundStorageSize(mStorage.getPrimaryStorageSize()); } else { final VolumeInfo vol = mStorage.findVolumeByUuid(volumeUuid); - return vol.disk.size; + return FileUtils.roundStorageSize(vol.disk.size); } } @@ -174,6 +174,43 @@ public class StorageStatsService extends IStorageStatsManager.Stub { } } + @Override + public StorageStats queryStatsForPackage(String volumeUuid, String packageName, int userId, + String callingPackage) { + enforcePermission(Binder.getCallingUid(), callingPackage); + if (userId != UserHandle.getCallingUserId()) { + mContext.enforceCallingOrSelfPermission( + android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); + } + + final ApplicationInfo appInfo; + try { + appInfo = mPackage.getApplicationInfoAsUser(packageName, 0, userId); + } catch (NameNotFoundException e) { + throw new IllegalStateException(e); + } + + if (mPackage.getPackagesForUid(appInfo.uid).length == 1) { + // Only one package inside UID means we can fast-path + return queryStatsForUid(volumeUuid, appInfo.uid, callingPackage); + } else { + // Multiple packages means we need to go manual + final int appId = UserHandle.getUserId(appInfo.uid); + final String[] packageNames = new String[] { packageName }; + final long[] ceDataInodes = new long[1]; + final String[] codePaths = new String[] { appInfo.getCodePath() }; + + final PackageStats stats = new PackageStats(TAG); + try { + mInstaller.getAppSize(volumeUuid, packageNames, userId, 0, + appId, ceDataInodes, codePaths, stats); + } catch (InstallerException e) { + throw new IllegalStateException(e); + } + return translate(stats); + } + } + @Override public StorageStats queryStatsForUid(String volumeUuid, int uid, String callingPackage) { enforcePermission(Binder.getCallingUid(), callingPackage);