Merge "Fix code accounting bugs, track external app data." into oc-dev
diff --git a/api/current.txt b/api/current.txt
index a7e4d13..9070dfc 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6925,6 +6925,7 @@
 
   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 0cf4a89..02aed37 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -7390,6 +7390,7 @@
 
   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 cd3106f..ea57559 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6955,6 +6955,7 @@
 
   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 1166df0..10c9b5f 100644
--- a/core/java/android/app/usage/ExternalStorageStats.java
+++ b/core/java/android/app/usage/ExternalStorageStats.java
@@ -31,6 +31,7 @@
     /** {@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 @@
         return imageBytes;
     }
 
+    /**
+     * Return the total bytes used by app files in the shared/external storage
+     * hosted on this volume.
+     * <p>
+     * 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 @@
         this.audioBytes = in.readLong();
         this.videoBytes = in.readLong();
         this.imageBytes = in.readLong();
+        this.appBytes = in.readLong();
     }
 
     @Override
@@ -87,6 +100,7 @@
         dest.writeLong(audioBytes);
         dest.writeLong(videoBytes);
         dest.writeLong(imageBytes);
+        dest.writeLong(appBytes);
     }
 
     public static final Creator<ExternalStorageStats> CREATOR = new Creator<ExternalStorageStats>() {
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
index b3104f6..26c702c0 100644
--- a/core/java/android/app/usage/StorageStats.java
+++ b/core/java/android/app/usage/StorageStats.java
@@ -22,12 +22,8 @@
 import android.os.UserHandle;
 
 /**
- * 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.
- * </p>
+ * Storage statistics for a UID, package, or {@link UserHandle} on a single
+ * storage volume.
  *
  * @see StorageStatsManager
  */
@@ -40,6 +36,9 @@
      * Return the size of all code. This includes {@code APK} files and
      * optimized compiler output.
      * <p>
+     * If the primary external/shared storage is hosted on this storage device,
+     * then this includes files stored under {@link Context#getObbDir()}.
+     * <p>
      * Code is shared between all users on a multiuser device.
      */
     public long getCodeBytes() {
@@ -51,6 +50,12 @@
      * {@link Context#getDataDir()}, {@link Context#getCacheDir()},
      * {@link Context#getCodeCacheDir()}.
      * <p>
+     * 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()}.
+     * <p>
      * Data is isolated for each user on a multiuser device.
      */
     public long getDataBytes() {
@@ -61,6 +66,10 @@
      * Return the size of all cached data. This includes files stored under
      * {@link Context#getCacheDir()} and {@link Context#getCodeCacheDir()}.
      * <p>
+     * If the primary external/shared storage is hosted on this storage device,
+     * then this includes files stored under
+     * {@link Context#getExternalCacheDir()}.
+     * <p>
      * 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 4b6479a..5497d57 100644
--- a/core/java/android/app/usage/StorageStatsManager.java
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -73,7 +73,8 @@
     }
 
     /**
-     * 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.
      * <p>
      * 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 1c15004..baa29b0 100644
--- a/core/java/android/os/storage/StorageManager.java
+++ b/core/java/android/os/storage/StorageManager.java
@@ -730,7 +730,7 @@
      */
     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 1e2b743..c5cefc8 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 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 b5a6178..3fd1d55 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 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 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 @@
         }
     }
 
+    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 094c7bd..2ebf5fc 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 @@
             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 @@
 
         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 @@
                     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 @@
                     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 @@
         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;