From 0034788844fd053f87beee60092c1dd44c7046e2 Mon Sep 17 00:00:00 2001 From: Jeff Sharkey Date: Mon, 17 Apr 2017 16:44:12 -0600 Subject: [PATCH] Fix code accounting bugs, track external app data. When counting code size, don't include APKs baked into the system. Settings already accounts external storage used by apps, so they need a way to exclude that from the total space used by external storage; give them new getAppSize() API. Refine docs to explain that emulated storage might be included in measured statistics. Resolve symlinks as part of matching getUuidForPath(). Test: cts-tradefed run commandAndExit cts-dev -m CtsAppSecurityHostTestCases -t android.appsecurity.cts.StorageHostTest Bug: 35812899, 35844919, 37193650 Change-Id: Iec3ce8b336b71dc98a7d25fdd30fa78e9ee826dc --- api/current.txt | 1 + api/system-current.txt | 1 + api/test-current.txt | 1 + .../app/usage/ExternalStorageStats.java | 14 ++++++ core/java/android/app/usage/StorageStats.java | 21 +++++--- .../app/usage/StorageStatsManager.java | 3 +- .../android/os/storage/StorageManager.java | 2 +- .../java/com/android/server/pm/Installer.java | 5 +- .../com/android/server/pm/InstallerTest.java | 28 +++++++---- .../server/usage/StorageStatsService.java | 50 +++++++++++++------ 10 files changed, 90 insertions(+), 36 deletions(-) diff --git a/api/current.txt b/api/current.txt index 5b55fdc4b9666..9f72f12f1e875 100644 --- a/api/current.txt +++ b/api/current.txt @@ -6924,6 +6924,7 @@ package android.app.usage { public final class ExternalStorageStats implements android.os.Parcelable { method public int describeContents(); + method public long getAppBytes(); method public long getAudioBytes(); method public long getImageBytes(); method public long getTotalBytes(); diff --git a/api/system-current.txt b/api/system-current.txt index 23a8cad152daf..3d95d0d550777 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -7389,6 +7389,7 @@ package android.app.usage { public final class ExternalStorageStats implements android.os.Parcelable { method public int describeContents(); + method public long getAppBytes(); method public long getAudioBytes(); method public long getImageBytes(); method public long getTotalBytes(); diff --git a/api/test-current.txt b/api/test-current.txt index 3373ab735a364..329b87199858b 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -6954,6 +6954,7 @@ package android.app.usage { public final class ExternalStorageStats implements android.os.Parcelable { method public int describeContents(); + method public long getAppBytes(); method public long getAudioBytes(); method public long getImageBytes(); method public long getTotalBytes(); diff --git a/core/java/android/app/usage/ExternalStorageStats.java b/core/java/android/app/usage/ExternalStorageStats.java index 1166df06c9175..10c9b5ff5a504 100644 --- a/core/java/android/app/usage/ExternalStorageStats.java +++ b/core/java/android/app/usage/ExternalStorageStats.java @@ -31,6 +31,7 @@ public final class ExternalStorageStats implements Parcelable { /** {@hide} */ public long audioBytes; /** {@hide} */ public long videoBytes; /** {@hide} */ public long imageBytes; + /** {@hide} */ public long appBytes; /** * Return the total bytes used by all files in the shared/external storage @@ -64,6 +65,17 @@ public final class ExternalStorageStats implements Parcelable { return imageBytes; } + /** + * Return the total bytes used by app files in the shared/external storage + * hosted on this volume. + *

+ * This data is already accounted against individual apps as returned + * through {@link StorageStats}. + */ + public long getAppBytes() { + return appBytes; + } + /** {@hide} */ public ExternalStorageStats() { } @@ -74,6 +86,7 @@ public final class ExternalStorageStats implements Parcelable { this.audioBytes = in.readLong(); this.videoBytes = in.readLong(); this.imageBytes = in.readLong(); + this.appBytes = in.readLong(); } @Override @@ -87,6 +100,7 @@ public final class ExternalStorageStats implements Parcelable { dest.writeLong(audioBytes); dest.writeLong(videoBytes); dest.writeLong(imageBytes); + dest.writeLong(appBytes); } public static final Creator CREATOR = new Creator() { diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java index b3104f6821e22..26c702c0eb4e5 100644 --- a/core/java/android/app/usage/StorageStats.java +++ b/core/java/android/app/usage/StorageStats.java @@ -22,12 +22,8 @@ import android.os.Parcelable; import android.os.UserHandle; /** - * 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. - *

+ * Storage statistics for a UID, package, or {@link UserHandle} on a single + * storage volume. * * @see StorageStatsManager */ @@ -40,6 +36,9 @@ public final class StorageStats implements Parcelable { * Return the size of all code. This includes {@code APK} files and * optimized compiler output. *

+ * If the primary external/shared storage is hosted on this storage device, + * then this includes files stored under {@link Context#getObbDir()}. + *

* Code is shared between all users on a multiuser device. */ public long getCodeBytes() { @@ -51,6 +50,12 @@ public final class StorageStats implements Parcelable { * {@link Context#getDataDir()}, {@link Context#getCacheDir()}, * {@link Context#getCodeCacheDir()}. *

+ * If the primary external/shared storage is hosted on this storage device, + * then this includes files stored under + * {@link Context#getExternalFilesDir(String)}, + * {@link Context#getExternalCacheDir()}, and + * {@link Context#getExternalMediaDirs()}. + *

* Data is isolated for each user on a multiuser device. */ public long getDataBytes() { @@ -61,6 +66,10 @@ public final class StorageStats implements Parcelable { * Return the size of all cached data. This includes files stored under * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}. *

+ * If the primary external/shared storage is hosted on this storage device, + * then this includes files stored under + * {@link Context#getExternalCacheDir()}. + *

* Cached data is isolated for each user on a multiuser device. */ public long getCacheBytes() { diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java index 4b6479ad4a7b8..5497d57ba2640 100644 --- a/core/java/android/app/usage/StorageStatsManager.java +++ b/core/java/android/app/usage/StorageStatsManager.java @@ -73,7 +73,8 @@ public class StorageStatsManager { } /** - * Return the total size of the media hosting this storage volume. + * Return the total size of the underlying media that is hosting this + * storage volume. *

* To reduce end user confusion, this value matches the total storage size * advertised in a retail environment, which is typically larger than the diff --git a/core/java/android/os/storage/StorageManager.java b/core/java/android/os/storage/StorageManager.java index 3942531735534..e8c256f2279f5 100644 --- a/core/java/android/os/storage/StorageManager.java +++ b/core/java/android/os/storage/StorageManager.java @@ -730,7 +730,7 @@ public class StorageManager { */ public @NonNull UUID getUuidForPath(@NonNull File path) throws IOException { Preconditions.checkNotNull(path); - final String pathString = path.getAbsolutePath(); + final String pathString = path.getCanonicalPath(); if (FileUtils.contains(Environment.getDataDirectory().getAbsolutePath(), pathString)) { return UUID_DEFAULT; } diff --git a/services/core/java/com/android/server/pm/Installer.java b/services/core/java/com/android/server/pm/Installer.java index 1e2b743d5e46c..c5cefc804f7e0 100644 --- a/services/core/java/com/android/server/pm/Installer.java +++ b/services/core/java/com/android/server/pm/Installer.java @@ -258,10 +258,11 @@ public class Installer extends SystemService { } } - public long[] getExternalSize(String uuid, int userId, int flags) throws InstallerException { + public long[] getExternalSize(String uuid, int userId, int flags, int[] appIds) + throws InstallerException { if (!checkBeforeRemote()) return new long[4]; try { - return mInstalld.getExternalSize(uuid, userId, flags); + return mInstalld.getExternalSize(uuid, userId, flags, appIds); } catch (Exception e) { throw InstallerException.from(e); } 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 b5a6178da700f..3fd1d556df147 100644 --- a/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/InstallerTest.java @@ -120,15 +120,7 @@ public class InstallerTest extends AndroidTestCase { } public void testGetUserSize() throws Exception { - int[] appIds = null; - - final PackageManager pm = getContext().getPackageManager(); - for (ApplicationInfo app : pm.getInstalledApplications(0)) { - final int appId = UserHandle.getAppId(app.uid); - if (!ArrayUtils.contains(appIds, appId)) { - appIds = ArrayUtils.appendInt(appIds, appId); - } - } + final int[] appIds = getAppIds(UserHandle.USER_SYSTEM); final PackageStats stats = new PackageStats("android"); final PackageStats quotaStats = new PackageStats("android"); @@ -147,13 +139,15 @@ public class InstallerTest extends AndroidTestCase { } public void testGetExternalSize() throws Exception { + final int[] appIds = getAppIds(UserHandle.USER_SYSTEM); + mManual.start(); - final long[] stats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, 0); + final long[] stats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, 0, appIds); mManual.stop(); mQuota.start(); final long[] quotaStats = mInstaller.getExternalSize(null, UserHandle.USER_SYSTEM, - Installer.FLAG_USE_QUOTA); + Installer.FLAG_USE_QUOTA, appIds); mQuota.stop(); for (int i = 0; i < stats.length; i++) { @@ -161,6 +155,18 @@ public class InstallerTest extends AndroidTestCase { } } + private int[] getAppIds(int userId) { + int[] appIds = null; + for (ApplicationInfo app : getContext().getPackageManager().getInstalledApplicationsAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { + final int appId = UserHandle.getAppId(app.uid); + if (!ArrayUtils.contains(appIds, appId)) { + appIds = ArrayUtils.appendInt(appIds, appId); + } + } + return appIds; + } + private static void checkEquals(String msg, PackageStats a, PackageStats b) { checkEquals(msg + " codeSize", a.codeSize, b.codeSize); checkEquals(msg + " dataSize", a.dataSize, b.dataSize); diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java index 094c7bd89ba0b..2ebf5fc1c5331 100644 --- a/services/usage/java/com/android/server/usage/StorageStatsService.java +++ b/services/usage/java/com/android/server/usage/StorageStatsService.java @@ -235,7 +235,14 @@ public class StorageStatsService extends IStorageStatsManager.Stub { 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() }; + String[] codePaths = new String[0]; + + if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { + // We don't count code baked into system image + } else { + codePaths = ArrayUtils.appendElement(String.class, codePaths, + appInfo.getCodePath()); + } final PackageStats stats = new PackageStats(TAG); try { @@ -261,12 +268,18 @@ public class StorageStatsService extends IStorageStatsManager.Stub { final String[] packageNames = mPackage.getPackagesForUid(uid); final long[] ceDataInodes = new long[packageNames.length]; - final String[] codePaths = new String[packageNames.length]; + String[] codePaths = new String[0]; for (int i = 0; i < packageNames.length; i++) { try { - codePaths[i] = mPackage.getApplicationInfoAsUser(packageNames[i], - PackageManager.MATCH_UNINSTALLED_PACKAGES, userId).getCodePath(); + final ApplicationInfo appInfo = mPackage.getApplicationInfoAsUser(packageNames[i], + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId); + if (appInfo.isSystemApp() && !appInfo.isUpdatedSystemApp()) { + // We don't count code baked into system image + } else { + codePaths = ArrayUtils.appendElement(String.class, codePaths, + appInfo.getCodePath()); + } } catch (NameNotFoundException e) { throw new ParcelableException(e); } @@ -297,15 +310,7 @@ public class StorageStatsService extends IStorageStatsManager.Stub { android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); } - int[] appIds = null; - for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( - PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { - final int appId = UserHandle.getAppId(app.uid); - if (!ArrayUtils.contains(appIds, appId)) { - appIds = ArrayUtils.appendInt(appIds, appId); - } - } - + final int[] appIds = getAppIds(userId); final PackageStats stats = new PackageStats(TAG); try { mInstaller.getUserSize(volumeUuid, userId, getDefaultFlags(), appIds, stats); @@ -330,12 +335,14 @@ public class StorageStatsService extends IStorageStatsManager.Stub { android.Manifest.permission.INTERACT_ACROSS_USERS, TAG); } + final int[] appIds = getAppIds(userId); final long[] stats; try { - stats = mInstaller.getExternalSize(volumeUuid, userId, getDefaultFlags()); + stats = mInstaller.getExternalSize(volumeUuid, userId, getDefaultFlags(), appIds); if (SystemProperties.getBoolean(PROP_VERIFY_STORAGE, false)) { - final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0); + final long[] manualStats = mInstaller.getExternalSize(volumeUuid, userId, 0, + appIds); checkEquals("External " + userId, manualStats, stats); } } catch (InstallerException e) { @@ -347,9 +354,22 @@ public class StorageStatsService extends IStorageStatsManager.Stub { res.audioBytes = stats[1]; res.videoBytes = stats[2]; res.imageBytes = stats[3]; + res.appBytes = stats[4]; return res; } + private int[] getAppIds(int userId) { + int[] appIds = null; + for (ApplicationInfo app : mPackage.getInstalledApplicationsAsUser( + PackageManager.MATCH_UNINSTALLED_PACKAGES, userId)) { + final int appId = UserHandle.getAppId(app.uid); + if (!ArrayUtils.contains(appIds, appId)) { + appIds = ArrayUtils.appendInt(appIds, appId); + } + } + return appIds; + } + private static int getDefaultFlags() { if (SystemProperties.getBoolean(PROP_DISABLE_QUOTA, false)) { return 0;