Initial API shape for storage statistics.

This API is designed to provide both UID-level stats and overall
summary data for a given storage device, as identified by UUID.

The use of UID-level granularity might appear a bit clunky, but it
matches other usage statistics (such as network and battery), and it
allows us to implement it using an extremely fast quota kernel
feature.

A future CL will wire up the implementation to installd.

Test: builds, boots
Bug: 32206268
Change-Id: I7b51877682d0370c2402c19346f57809f0e7ac53
diff --git a/Android.mk b/Android.mk
index 7c9397f..8c0c640 100644
--- a/Android.mk
+++ b/Android.mk
@@ -108,6 +108,7 @@
 	core/java/android/app/backup/IFullBackupRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreObserver.aidl \
 	core/java/android/app/backup/IRestoreSession.aidl \
+	core/java/android/app/usage/IStorageStatsManager.aidl \
 	core/java/android/app/usage/IUsageStatsManager.aidl \
 	core/java/android/bluetooth/IBluetooth.aidl \
 	core/java/android/bluetooth/IBluetoothA2dp.aidl \
@@ -689,6 +690,8 @@
 	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/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 b017f9d..3fa06fb 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -6679,6 +6679,36 @@
     method public abstract void onThresholdReached(int, java.lang.String);
   }
 
+  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 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;
+  }
+
   public final class UsageEvents implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getNextEvent(android.app.usage.UsageEvents.Event);
@@ -8415,6 +8445,7 @@
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
+    field public static final java.lang.String STORAGE_STATS_SERVICE = "storagestats";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
diff --git a/api/system-current.txt b/api/system-current.txt
index a5534de..7490325 100644
--- a/api/system-current.txt
+++ b/api/system-current.txt
@@ -6992,6 +6992,36 @@
     method public abstract void onThresholdReached(int, java.lang.String);
   }
 
+  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 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;
+  }
+
   public final class UsageEvents implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getNextEvent(android.app.usage.UsageEvents.Event);
@@ -8770,6 +8800,7 @@
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
+    field public static final java.lang.String STORAGE_STATS_SERVICE = "storagestats";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
diff --git a/api/test-current.txt b/api/test-current.txt
index 5db134a..8ef48ec 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -6701,6 +6701,36 @@
     method public abstract void onThresholdReached(int, java.lang.String);
   }
 
+  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 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;
+  }
+
   public final class UsageEvents implements android.os.Parcelable {
     method public int describeContents();
     method public boolean getNextEvent(android.app.usage.UsageEvents.Event);
@@ -8439,6 +8469,7 @@
     field public static final java.lang.String SENSOR_SERVICE = "sensor";
     field public static final java.lang.String SHORTCUT_SERVICE = "shortcut";
     field public static final java.lang.String STORAGE_SERVICE = "storage";
+    field public static final java.lang.String STORAGE_STATS_SERVICE = "storagestats";
     field public static final java.lang.String SYSTEM_HEALTH_SERVICE = "systemhealth";
     field public static final java.lang.String TELECOM_SERVICE = "telecom";
     field public static final java.lang.String TELEPHONY_SERVICE = "phone";
diff --git a/core/java/android/app/SystemServiceRegistry.java b/core/java/android/app/SystemServiceRegistry.java
index 6bddfba..9387019 100644
--- a/core/java/android/app/SystemServiceRegistry.java
+++ b/core/java/android/app/SystemServiceRegistry.java
@@ -23,8 +23,10 @@
 import android.app.job.IJobScheduler;
 import android.app.job.JobScheduler;
 import android.app.trust.TrustManager;
+import android.app.usage.IStorageStatsManager;
 import android.app.usage.IUsageStatsManager;
 import android.app.usage.NetworkStatsManager;
+import android.app.usage.StorageStatsManager;
 import android.app.usage.UsageStatsManager;
 import android.appwidget.AppWidgetManager;
 import android.bluetooth.BluetoothManager;
@@ -434,6 +436,15 @@
                 return new StorageManager(ctx, ctx.mMainThread.getHandler().getLooper());
             }});
 
+        registerService(Context.STORAGE_STATS_SERVICE, StorageStatsManager.class,
+                new CachedServiceFetcher<StorageStatsManager>() {
+            @Override
+            public StorageStatsManager createService(ContextImpl ctx) throws ServiceNotFoundException {
+                IStorageStatsManager service = IStorageStatsManager.Stub.asInterface(
+                        ServiceManager.getServiceOrThrow(Context.STORAGE_STATS_SERVICE));
+                return new StorageStatsManager(ctx, service);
+            }});
+
         registerService(Context.TELEPHONY_SERVICE, TelephonyManager.class,
                 new CachedServiceFetcher<TelephonyManager>() {
             @Override
diff --git a/core/java/android/app/usage/IStorageStatsManager.aidl b/core/java/android/app/usage/IStorageStatsManager.aidl
new file mode 100644
index 0000000..96125a1
--- /dev/null
+++ b/core/java/android/app/usage/IStorageStatsManager.aidl
@@ -0,0 +1,26 @@
+/*
+ * 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.app.usage.StorageStats;
+import android.app.usage.StorageSummary;
+
+/** {@hide} */
+interface IStorageStatsManager {
+    StorageStats queryStats(String volumeUuid, int uid, String callingPackage);
+    StorageSummary querySummary(String volumeUuid, String callingPackage);
+}
diff --git a/core/java/android/app/usage/StorageStats.aidl b/core/java/android/app/usage/StorageStats.aidl
new file mode 100644
index 0000000..fde6267
--- /dev/null
+++ b/core/java/android/app/usage/StorageStats.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable StorageStats;
diff --git a/core/java/android/app/usage/StorageStats.java b/core/java/android/app/usage/StorageStats.java
new file mode 100644
index 0000000..b2486d8
--- /dev/null
+++ b/core/java/android/app/usage/StorageStats.java
@@ -0,0 +1,162 @@
+/*
+ * 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.content.Context;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.os.Parcel;
+import android.os.Parcelable;
+
+/**
+ * Storage statistics for a single UID 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>
+ *
+ * @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()}.
+     * <p>
+     * Code is shared between all users on a multiuser device.
+     */
+    public long getCodeBytes() {
+        return codeBytes;
+    }
+
+    /**
+     * Return the size of all data associated with {@link #getUid()} on
+     * {@link #getVolumeUuid()}. 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() {
+        return dataBytes;
+    }
+
+    /**
+     * 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
+     * {@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() {
+        return cacheBytes;
+    }
+
+    /** {@hide} */
+    public StorageStats(String uuid, int uid) {
+        this.volumeUuid = uuid;
+        this.uid = uid;
+    }
+
+    /** {@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();
+    }
+
+    @Override
+    public int describeContents() {
+        return 0;
+    }
+
+    @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);
+    }
+
+    public static final Creator<StorageStats> CREATOR = new Creator<StorageStats>() {
+        @Override
+        public StorageStats createFromParcel(Parcel in) {
+            return new StorageStats(in);
+        }
+
+        @Override
+        public StorageStats[] newArray(int size) {
+            return new StorageStats[size];
+        }
+    };
+}
diff --git a/core/java/android/app/usage/StorageStatsManager.java b/core/java/android/app/usage/StorageStatsManager.java
new file mode 100644
index 0000000..cdfbe0c
--- /dev/null
+++ b/core/java/android/app/usage/StorageStatsManager.java
@@ -0,0 +1,82 @@
+/*
+ * 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.annotation.WorkerThread;
+import android.content.Context;
+import android.os.RemoteException;
+
+import com.android.internal.util.Preconditions;
+
+/**
+ * Provides access to detailed storage statistics.
+ * <p class="note">
+ * Note: this API requires the permission
+ * {@code android.permission.PACKAGE_USAGE_STATS}, which is a system-level
+ * permission that will not be granted to normal apps. However, declaring the
+ * permission expresses your intention to use this API and an end user can then
+ * choose to grant this permission through the Settings application.
+ * </p>
+ */
+public class StorageStatsManager {
+    private final Context mContext;
+    private final IStorageStatsManager mService;
+
+    /** {@hide} */
+    public StorageStatsManager(Context context, IStorageStatsManager service) {
+        mContext = Preconditions.checkNotNull(context);
+        mService = Preconditions.checkNotNull(service);
+    }
+
+    /**
+     * Return detailed statistics for the 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.
+     *
+     * @param volumeUuid the UUID of the storage volume you're interested in, or
+     *            {@code null} to specify the default internal storage.
+     * @param uid the UID you're interested in.
+     */
+    @WorkerThread
+    public StorageStats queryStatsForUid(String volumeUuid, int uid) {
+        try {
+            return mService.queryStats(volumeUuid, uid, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return summary statistics for 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.
+     */
+    @WorkerThread
+    public StorageSummary querySummary(String volumeUuid) {
+        try {
+            return mService.querySummary(volumeUuid, mContext.getOpPackageName());
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+}
diff --git a/core/java/android/app/usage/StorageSummary.aidl b/core/java/android/app/usage/StorageSummary.aidl
new file mode 100644
index 0000000..b20e025
--- /dev/null
+++ b/core/java/android/app/usage/StorageSummary.aidl
@@ -0,0 +1,19 @@
+/*
+ * 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;
+
+parcelable StorageSummary;
diff --git a/core/java/android/app/usage/StorageSummary.java b/core/java/android/app/usage/StorageSummary.java
new file mode 100644
index 0000000..e72c9ec
--- /dev/null
+++ b/core/java/android/app/usage/StorageSummary.java
@@ -0,0 +1,161 @@
+/*
+ * 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];
+        }
+    };
+}
diff --git a/core/java/android/content/Context.java b/core/java/android/content/Context.java
index 9dc60ab..ffe1248 100644
--- a/core/java/android/content/Context.java
+++ b/core/java/android/content/Context.java
@@ -2680,6 +2680,7 @@
             SEARCH_SERVICE,
             SENSOR_SERVICE,
             STORAGE_SERVICE,
+            STORAGE_STATS_SERVICE,
             WALLPAPER_SERVICE,
             VIBRATOR_SERVICE,
             //@hide: STATUS_BAR_SERVICE,
@@ -3077,6 +3078,16 @@
     public static final String STORAGE_SERVICE = "storage";
 
     /**
+     * Use with {@link #getSystemService} to retrieve a {@link
+     * android.app.usage.StorageStatsManager} for accessing system storage
+     * statistics.
+     *
+     * @see #getSystemService
+     * @see android.app.usage.StorageStatsManager
+     */
+    public static final String STORAGE_STATS_SERVICE = "storagestats";
+
+    /**
      * Use with {@link #getSystemService} to retrieve a
      * com.android.server.WallpaperService for accessing wallpapers.
      *
diff --git a/services/java/com/android/server/SystemServer.java b/services/java/com/android/server/SystemServer.java
index 1d550d2..a22581c 100644
--- a/services/java/com/android/server/SystemServer.java
+++ b/services/java/com/android/server/SystemServer.java
@@ -165,6 +165,8 @@
             "com.android.server.LockSettingsService$Lifecycle";
     private static final String STORAGE_MANAGER_SERVICE_CLASS =
             "com.android.server.StorageManagerService$Lifecycle";
+    private static final String STORAGE_STATS_SERVICE_CLASS =
+            "com.android.server.usage.StorageStatsService$Lifecycle";
     private static final String SEARCH_MANAGER_SERVICE_CLASS =
             "com.android.server.search.SearchManagerService$Lifecycle";
     private static final String THERMAL_OBSERVER_CLASS =
@@ -792,7 +794,7 @@
 
         if (mFactoryTestMode != FactoryTest.FACTORY_TEST_LOW_LEVEL) {
             if (!disableStorage &&
-                !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
+                    !"0".equals(SystemProperties.get("system_init.startmountservice"))) {
                 traceBeginAndSlog("StartStorageManagerService");
                 try {
                     /*
@@ -803,7 +805,15 @@
                     storageManager = IStorageManager.Stub.asInterface(
                             ServiceManager.getService("mount"));
                 } catch (Throwable e) {
-                    reportWtf("starting StorageManager Service", e);
+                    reportWtf("starting StorageManagerService", e);
+                }
+                traceEnd();
+
+                traceBeginAndSlog("StartStorageStatsService");
+                try {
+                    mSystemServiceManager.startService(STORAGE_STATS_SERVICE_CLASS);
+                } catch (Throwable e) {
+                    reportWtf("starting StorageStatsService", e);
                 }
                 traceEnd();
             }
diff --git a/services/usage/java/com/android/server/usage/StorageStatsService.java b/services/usage/java/com/android/server/usage/StorageStatsService.java
new file mode 100644
index 0000000..cb9cb121
--- /dev/null
+++ b/services/usage/java/com/android/server/usage/StorageStatsService.java
@@ -0,0 +1,93 @@
+/*
+ * 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 com.android.server.usage;
+
+import android.app.AppOpsManager;
+import android.app.usage.IStorageStatsManager;
+import android.app.usage.StorageStats;
+import android.app.usage.StorageSummary;
+import android.content.Context;
+import android.os.Binder;
+import android.os.UserHandle;
+import android.os.storage.StorageManager;
+
+import com.android.server.SystemService;
+import com.android.server.pm.Installer;
+
+public class StorageStatsService extends IStorageStatsManager.Stub {
+    private static final String TAG = "StorageStatsService";
+
+    public static class Lifecycle extends SystemService {
+        private StorageStatsService mService;
+
+        public Lifecycle(Context context) {
+            super(context);
+        }
+
+        @Override
+        public void onStart() {
+            mService = new StorageStatsService(getContext());
+            publishBinderService(Context.STORAGE_STATS_SERVICE, mService);
+        }
+    }
+
+    private final Context mContext;
+    private final AppOpsManager mAppOps;
+    private final StorageManager mStorage;
+    private final Installer mInstaller;
+
+    public StorageStatsService(Context context) {
+        mContext = context;
+        mAppOps = context.getSystemService(AppOpsManager.class);
+        mStorage = context.getSystemService(StorageManager.class);
+        mInstaller = new Installer(context);
+    }
+
+    private void enforcePermission(int callingUid, String callingPackage) {
+        final int mode = mAppOps.checkOp(AppOpsManager.OP_GET_USAGE_STATS,
+                callingUid, callingPackage);
+        switch (mode) {
+            case AppOpsManager.MODE_ALLOWED:
+                return;
+            case AppOpsManager.MODE_DEFAULT:
+                mContext.enforceCallingPermission(
+                        android.Manifest.permission.PACKAGE_USAGE_STATS, TAG);
+            default:
+                throw new SecurityException("Blocked by mode " + mode);
+        }
+    }
+
+    @Override
+    public StorageStats queryStats(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;
+    }
+
+    @Override
+    public StorageSummary querySummary(String volumeUuid, String callingPackage) {
+        enforcePermission(Binder.getCallingUid(), callingPackage);
+
+        // TODO: call installd to collect quota stats
+        return null;
+    }
+}