Add BlobStore atoms

Bug: 144155167
Test: atest android.cts.statsd.atom.UidAtomTests
Change-Id: I650dfdf6e2f7b6fff29ba6fdf5010a151fd503b5
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
index ec7ba28..91b954b 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobAccessMode.java
@@ -127,6 +127,14 @@
         return false;
     }
 
+    int getAccessType() {
+        return mAccessType;
+    }
+
+    int getNumWhitelistedPackages() {
+        return mWhitelistedPackages.size();
+    }
+
     void dump(IndentingPrintWriter fout) {
         fout.println("accessType: " + DebugUtils.flagsToString(
                 BlobAccessMode.class, "ACCESS_TYPE_", mAccessType));
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
index cea7fcc..3d06083 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -54,6 +54,8 @@
 import android.util.ArraySet;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsEvent;
+import android.util.proto.ProtoOutputStream;
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
@@ -410,6 +412,49 @@
         return true;
     }
 
+    StatsEvent dumpAsStatsEvent(int atomTag) {
+        synchronized (mMetadataLock) {
+            ProtoOutputStream proto = new ProtoOutputStream();
+            // Write Committer data to proto format
+            for (int i = 0, size = mCommitters.size(); i < size; ++i) {
+                final Committer committer = mCommitters.valueAt(i);
+                final long token = proto.start(
+                        BlobStatsEventProto.BlobCommitterListProto.COMMITTER);
+                proto.write(BlobStatsEventProto.BlobCommitterProto.UID, committer.uid);
+                proto.write(BlobStatsEventProto.BlobCommitterProto.COMMIT_TIMESTAMP_MILLIS,
+                        committer.commitTimeMs);
+                proto.write(BlobStatsEventProto.BlobCommitterProto.ACCESS_MODE,
+                        committer.blobAccessMode.getAccessType());
+                proto.write(BlobStatsEventProto.BlobCommitterProto.NUM_WHITELISTED_PACKAGE,
+                        committer.blobAccessMode.getNumWhitelistedPackages());
+                proto.end(token);
+            }
+            final byte[] committersBytes = proto.getBytes();
+
+            proto = new ProtoOutputStream();
+            // Write Leasee data to proto format
+            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+                final Leasee leasee = mLeasees.valueAt(i);
+                final long token = proto.start(BlobStatsEventProto.BlobLeaseeListProto.LEASEE);
+                proto.write(BlobStatsEventProto.BlobLeaseeProto.UID, leasee.uid);
+                proto.write(BlobStatsEventProto.BlobLeaseeProto.LEASE_EXPIRY_TIMESTAMP_MILLIS,
+                        leasee.expiryTimeMillis);
+                proto.end(token);
+            }
+            final byte[] leaseesBytes = proto.getBytes();
+
+            // Construct the StatsEvent to represent this Blob
+            return StatsEvent.newBuilder()
+                    .setAtomId(atomTag)
+                    .writeLong(mBlobId)
+                    .writeLong(getSize())
+                    .writeLong(mBlobHandle.getExpiryTimeMillis())
+                    .writeByteArray(committersBytes)
+                    .writeByteArray(leaseesBytes)
+                    .build();
+        }
+    }
+
     void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) {
         fout.println("blobHandle:");
         fout.increaseIndent();
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
index 6567266..08ee244 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java
@@ -49,6 +49,9 @@
 
     public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_SESSION_CREATION_TIME;
 
+    public static final long INVALID_BLOB_ID = 0;
+    public static final long INVALID_BLOB_SIZE = 0;
+
     private static final String ROOT_DIR_NAME = "blobstore";
     private static final String BLOBS_DIR_NAME = "blobs";
     private static final String SESSIONS_INDEX_FILE_NAME = "sessions_index.xml";
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
index 68c4bb6..850a1d2 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -28,6 +28,8 @@
 import static android.os.UserHandle.USER_CURRENT;
 import static android.os.UserHandle.USER_NULL;
 
+import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID;
+import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
 import static com.android.server.blob.BlobStoreConfig.LOGV;
 import static com.android.server.blob.BlobStoreConfig.TAG;
 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
@@ -48,6 +50,7 @@
 import android.annotation.UserIdInt;
 import android.app.ActivityManager;
 import android.app.ActivityManagerInternal;
+import android.app.StatsManager;
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreManager;
@@ -80,6 +83,7 @@
 import android.util.LongSparseArray;
 import android.util.Slog;
 import android.util.SparseArray;
+import android.util.StatsEvent;
 import android.util.Xml;
 
 import com.android.internal.annotations.GuardedBy;
@@ -88,6 +92,7 @@
 import com.android.internal.util.CollectionUtils;
 import com.android.internal.util.DumpUtils;
 import com.android.internal.util.FastXmlSerializer;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -159,6 +164,8 @@
             new SessionStateChangeListener();
 
     private PackageManagerInternal mPackageManagerInternal;
+    private StatsManager mStatsManager;
+    private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl();
 
     private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo;
     private final Runnable mSaveSessionsRunnable = this::writeBlobSessions;
@@ -192,6 +199,7 @@
         LocalServices.addService(BlobStoreManagerInternal.class, new LocalService());
 
         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
+        mStatsManager = getContext().getSystemService(StatsManager.class);
         registerReceivers();
         LocalServices.getService(StorageStatsManagerInternal.class)
                 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG);
@@ -207,6 +215,7 @@
                 readBlobSessionsLocked(allPackages);
                 readBlobsInfoLocked(allPackages);
             }
+            registerBlobStorePuller();
         } else if (phase == PHASE_BOOT_COMPLETED) {
             BlobStoreIdleJobService.schedule(mContext);
         }
@@ -219,7 +228,7 @@
         long sessionId;
         do {
             sessionId = Math.abs(mRandom.nextLong());
-            if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != 0) {
+            if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) {
                 return sessionId;
             }
         } while (n++ < 32);
@@ -376,9 +385,23 @@
                     .get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid)) {
+                if (blobMetadata == null) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
+                            INVALID_BLOB_ID, INVALID_BLOB_SIZE,
+                            FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE);
+                } else {
+                    FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
+                            blobMetadata.getBlobId(), blobMetadata.getSize(),
+                            FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
+                }
                 throw new SecurityException("Caller not allowed to access " + blobHandle
                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
             }
+
+            FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
+                    blobMetadata.getBlobId(), blobMetadata.getSize(),
+                    FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
+
             return blobMetadata.openForRead(callingPackage);
         }
     }
@@ -391,19 +414,41 @@
                     .get(blobHandle);
             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
                     callingPackage, callingUid)) {
+                if (blobMetadata == null) {
+                    FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                            INVALID_BLOB_ID, INVALID_BLOB_SIZE,
+                            FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE);
+                } else {
+                    FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                            blobMetadata.getBlobId(), blobMetadata.getSize(),
+                            FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
+                }
                 throw new SecurityException("Caller not allowed to access " + blobHandle
                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
             }
             if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
                     && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
+
+                FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                        blobMetadata.getBlobId(), blobMetadata.getSize(),
+                        FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
                 throw new IllegalArgumentException(
                         "Lease expiry cannot be later than blobs expiry time");
             }
             if (blobMetadata.getSize()
                     > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
+
+                FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                        blobMetadata.getBlobId(), blobMetadata.getSize(),
+                        FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED);
                 throw new LimitExceededException("Total amount of data with an active lease"
                         + " is exceeding the max limit");
             }
+
+            FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
+                    blobMetadata.getBlobId(), blobMetadata.getSize(),
+                    FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS);
+
             blobMetadata.addOrReplaceLeasee(callingPackage, callingUid,
                     descriptionResId, description, leaseExpiryTimeMillis);
             if (LOGV) {
@@ -587,6 +632,9 @@
                     blob.addOrReplaceCommitter(newCommitter);
                     try {
                         writeBlobsInfoLocked();
+                        FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
+                                session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
+                                FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS);
                         session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS);
                     } catch (Exception e) {
                         if (existingCommitter == null) {
@@ -595,6 +643,9 @@
                             blob.addOrReplaceCommitter(existingCommitter);
                         }
                         Slog.d(TAG, "Error committing the blob", e);
+                        FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
+                                session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
+                                FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
                         session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
                     }
                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
@@ -1684,6 +1735,40 @@
         }
     }
 
+    private void registerBlobStorePuller() {
+        mStatsManager.setPullAtomCallback(
+                FrameworkStatsLog.BLOB_INFO,
+                null, // use default PullAtomMetadata values
+                BackgroundThread.getExecutor(),
+                mStatsCallbackImpl
+        );
+    }
+
+    private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
+        @Override
+        public int onPullAtom(int atomTag, List<StatsEvent> data) {
+            switch (atomTag) {
+                case FrameworkStatsLog.BLOB_INFO:
+                    return pullBlobData(atomTag, data);
+                default:
+                    throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
+            }
+        }
+    }
+
+    private int pullBlobData(int atomTag, List<StatsEvent> data) {
+        synchronized (mBlobsLock) {
+            for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
+                final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
+                for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
+                    final BlobMetadata blob = userBlobs.valueAt(j);
+                    data.add(blob.dumpAsStatsEvent(atomTag));
+                }
+            }
+        }
+        return StatsManager.PULL_SUCCESS;
+    }
+
     private class LocalService extends BlobStoreManagerInternal {
         @Override
         public void onIdleMaintenance() {
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
index 22d5d11..c2bf3e4 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java
@@ -53,6 +53,7 @@
 
 import com.android.internal.annotations.GuardedBy;
 import com.android.internal.annotations.VisibleForTesting;
+import com.android.internal.util.FrameworkStatsLog;
 import com.android.internal.util.IndentingPrintWriter;
 import com.android.internal.util.Preconditions;
 import com.android.internal.util.XmlUtils;
@@ -427,6 +428,9 @@
                         + ") didn't match the given BlobHandle.digest ("
                         + BlobHandle.safeDigest(mBlobHandle.digest) + ")");
                 mState = STATE_VERIFIED_INVALID;
+
+                FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId,
+                        getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH);
                 sendCommitCallbackResult(COMMIT_RESULT_ERROR);
             }
             mListener.onStateChanged(this);
diff --git a/cmds/statsd/src/atoms.proto b/cmds/statsd/src/atoms.proto
index e998711..278278f 100644
--- a/cmds/statsd/src/atoms.proto
+++ b/cmds/statsd/src/atoms.proto
@@ -478,13 +478,16 @@
             296;
         MediametricsAudioDeviceConnectionReported mediametrics_audiodeviceconnection_reported =
             297;
+        BlobCommitted blob_committed = 298 [(module) = "framework"];
+        BlobLeased blob_leased = 299 [(module) = "framework"];
+        BlobOpened blob_opened = 300 [(module) = "framework"];
 
         // StatsdStats tracks platform atoms with ids upto 500.
         // Update StatsdStats::kMaxPushedAtomId when atom ids here approach that value.
     }
 
     // Pulled events will start at field 10000.
-    // Next: 10081
+    // Next: 10084
     oneof pulled {
         WifiBytesTransfer wifi_bytes_transfer = 10000 [(module) = "framework"];
         WifiBytesTransferByFgBg wifi_bytes_transfer_by_fg_bg = 10001 [(module) = "framework"];
@@ -575,7 +578,7 @@
         SimSlotState sim_slot_state = 10078 [(module) = "telephony"];
         SupportedRadioAccessFamily supported_radio_access_family = 10079 [(module) = "telephony"];
         SettingSnapshot setting_snapshot = 10080 [(module) = "framework"];
-        //10081 free for use
+        BlobInfo blob_info = 10081 [(module) = "framework"];
         DataUsageBytesTransfer data_usage_bytes_transfer = 10082 [(module) = "framework"];
         BytesTransferByTagAndMetered bytes_transfer_by_tag_and_metered =
                 10083 [(module) = "framework"];
@@ -4905,6 +4908,94 @@
     optional int64 cow_file_size_bytes = 5;
 }
 
+/**
+ * Event representing when BlobStoreManager.Session#commit() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobCommitted {
+    // Uid of the Blob committer
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob committed
+    optional int64 blob_id = 2;
+
+    // Size of the Blob
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Commit Succeeded
+        SUCCESS = 1;
+        // Commit Failed: Error occurred during commit
+        ERROR_DURING_COMMIT = 2;
+        // Commit Failed: Digest of the data did not match Blob digest
+        DIGEST_MISMATCH = 3;
+    }
+    optional Result result = 4;
+}
+
+/**
+ * Event representing when BlobStoreManager#acquireLease() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobLeased{
+    // Uid of the Blob leasee
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob leased or 0 if the Blob does not exist
+    optional int64 blob_id = 2;
+
+    // Size of the Blob or 0 if the Blob does not exist
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Lease Succeeded
+        SUCCESS = 1;
+        // Lease Failed: Blob does not exist
+        BLOB_DNE = 2;
+        // Lease Failed: Leasee does not have access to the Blob
+        ACCESS_NOT_ALLOWED = 3;
+        // Lease Failed: Leasee requested an invalid expiry duration
+        LEASE_EXPIRY_INVALID = 4;
+        // Lease Failed: Leasee has exceeded the total data lease limit
+        DATA_SIZE_LIMIT_EXCEEDED = 5;
+    }
+    optional Result result = 4;
+}
+
+/**
+ * Event representing when BlobStoreManager#openBlob() is called
+ *
+ * Logged from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobOpened{
+    // Uid of the Blob opener
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Id of the Blob opened or 0 if the Blob does not exist
+    optional int64 blob_id = 2;
+
+    // Size of the Blob or 0 if the Blob does not exist
+    optional int64 size = 3;
+
+    enum Result {
+        UNKNOWN = 0;
+        // Open Succeeded
+        SUCCESS = 1;
+        // Open Failed: Blob does not exist
+        BLOB_DNE = 2;
+        // Open Failed: Opener does not have access to the Blob
+        ACCESS_NOT_ALLOWED = 3;
+    }
+    optional Result result = 4;
+}
+
 //////////////////////////////////////////////////////////////////////
 // Pulled atoms below this line //
 //////////////////////////////////////////////////////////////////////
@@ -10791,3 +10882,72 @@
     // Number of connections if aggregated statistics, otherwise 1.
     optional int32 connection_count = 6;
 }
+
+// Blob Committer stats
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobCommitterProto {
+    // Committer app's uid
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Unix epoch timestamp of the commit in milliseconds
+    optional int64 commit_timestamp_millis = 2;
+
+    // Flags of what access types the committer has set for the Blob
+    optional int32 access_mode = 3;
+
+    // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST
+    optional int32 num_whitelisted_package = 4;
+}
+
+// Blob Leasee stats
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobLeaseeProto {
+    // Leasee app's uid
+    optional int32 uid = 1 [(is_uid) = true];
+
+    // Unix epoch timestamp for lease expiration in milliseconds
+    optional int64 lease_expiry_timestamp_millis = 2;
+}
+
+// List of Blob Committers
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobCommitterListProto {
+    repeated BlobCommitterProto committer = 1;
+}
+
+// List of Blob Leasees
+// Keep in sync between:
+//     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+//     frameworks/base/cmds/statsd/src/atoms.proto
+message BlobLeaseeListProto {
+    repeated BlobLeaseeProto leasee = 1;
+}
+
+/**
+ * Logs the current state of a Blob committed with BlobStoreManager
+ *
+ * Pulled from:
+ *  frameworks/base/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+ */
+message BlobInfo {
+    // Id of the Blob
+    optional int64 blob_id = 1;
+
+    // Size of the Blob data
+    optional int64 size = 2;
+
+    // Unix epoch timestamp of the Blob's expiration in milliseconds
+    optional int64 expiry_timestamp_millis = 3;
+
+    // List of committers of this Blob
+    optional BlobCommitterListProto committers = 4;
+
+    // List of leasees of this Blob
+    optional BlobLeaseeListProto leasees = 5;
+}
diff --git a/core/proto/android/server/blobstoremanagerservice.proto b/core/proto/android/server/blobstoremanagerservice.proto
new file mode 100644
index 0000000..583b646
--- /dev/null
+++ b/core/proto/android/server/blobstoremanagerservice.proto
@@ -0,0 +1,70 @@
+/*
+ * Copyright (C) 2020 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.
+ */
+
+syntax = "proto2";
+package com.android.server.blob;
+
+option java_multiple_files = true;
+
+// The nested messages are used for statsd logging and should be kept in sync with the messages
+// of the same name in frameworks/base/cmds/statsd/src/atoms.proto
+message BlobStatsEventProto {
+  // Blob Committer stats
+  // Keep in sync between:
+  //     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+  //     frameworks/base/cmds/statsd/src/atoms.proto
+  message BlobCommitterProto {
+    // Committer app's uid
+    optional int32 uid = 1;
+
+    // Unix epoch timestamp of the commit in milliseconds
+    optional int64 commit_timestamp_millis = 2;
+
+    // Flags of what access types the committer has set for the Blob
+    optional int32 access_mode = 3;
+
+    // Number of packages that have been whitelisted for ACCESS_TYPE_WHITELIST
+    optional int32 num_whitelisted_package = 4;
+  }
+
+  // Blob Leasee stats
+  // Keep in sync between:
+  //     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+  //     frameworks/base/cmds/statsd/src/atoms.proto
+  message BlobLeaseeProto {
+    // Leasee app's uid
+    optional int32 uid = 1;
+
+    // Unix epoch timestamp for lease expiration in milliseconds
+    optional int64 lease_expiry_timestamp_millis = 2;
+  }
+
+  // List of Blob Committers
+  // Keep in sync between:
+  //     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+  //     frameworks/base/cmds/statsd/src/atoms.proto
+  message BlobCommitterListProto {
+    repeated BlobCommitterProto committer = 1;
+  }
+
+  // List of Blob Leasees
+  // Keep in sync between:
+  //     frameworks/base/core/proto/android/server/blobstoremanagerservice.proto
+  //     frameworks/base/cmds/statsd/src/atoms.proto
+  message BlobLeaseeListProto {
+    repeated BlobLeaseeProto leasee = 1;
+  }
+}
\ No newline at end of file