Add @TestApis getLeasedBlobs() and getLeaseInfo().

+ Rename AccessorInfo to LeaseInfo.

Bug: 150619869
Test: atest --test-mapping apex/blobstore
Change-Id: I257b784350aa8bcf39847a3fd1a91977e4ff174a
diff --git a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
index 02df5e2..23f025b 100644
--- a/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
+++ b/apct-tests/perftests/blobstore/src/com/android/perftests/blob/BlobStorePerfTests.java
@@ -121,8 +121,9 @@
     }
 
     private DummyBlobData prepareDataBlob(int fileSizeInMb) throws Exception {
-        final DummyBlobData blobData = new DummyBlobData(mContext,
-                fileSizeInMb * 1024 * 1024 /* bytes */);
+        final DummyBlobData blobData = new DummyBlobData.Builder(mContext)
+                .setFileSize(fileSizeInMb * 1024 * 1024 /* bytes */)
+                .build();
         blobData.prepare();
         return blobData;
     }
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
index 9746dd0..80062d5 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java
@@ -32,21 +32,21 @@
     private final long mId;
     private final long mExpiryTimeMs;
     private final CharSequence mLabel;
-    private final List<AccessorInfo> mAccessors;
+    private final List<LeaseInfo> mLeaseInfos;
 
     public BlobInfo(long id, long expiryTimeMs, CharSequence label,
-            List<AccessorInfo> accessors) {
+            List<LeaseInfo> leaseInfos) {
         mId = id;
         mExpiryTimeMs = expiryTimeMs;
         mLabel = label;
-        mAccessors = accessors;
+        mLeaseInfos = leaseInfos;
     }
 
     private BlobInfo(Parcel in) {
         mId = in.readLong();
         mExpiryTimeMs = in.readLong();
         mLabel = in.readCharSequence();
-        mAccessors = in.readArrayList(null /* classloader */);
+        mLeaseInfos = in.readArrayList(null /* classloader */);
     }
 
     public long getId() {
@@ -61,8 +61,8 @@
         return mLabel;
     }
 
-    public List<AccessorInfo> getAccessors() {
-        return Collections.unmodifiableList(mAccessors);
+    public List<LeaseInfo> getLeases() {
+        return Collections.unmodifiableList(mLeaseInfos);
     }
 
     @Override
@@ -70,7 +70,7 @@
         dest.writeLong(mId);
         dest.writeLong(mExpiryTimeMs);
         dest.writeCharSequence(mLabel);
-        dest.writeList(mAccessors);
+        dest.writeList(mLeaseInfos);
     }
 
     @Override
@@ -83,7 +83,7 @@
                 + "id: " + mId + ","
                 + "expiryMs: " + mExpiryTimeMs + ","
                 + "label: " + mLabel + ","
-                + "accessors: " + AccessorInfo.toShortString(mAccessors) + ","
+                + "leases: " + LeaseInfo.toShortString(mLeaseInfos) + ","
                 + "}";
     }
 
diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
index 814ab6d..c339351 100644
--- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
+++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java
@@ -21,6 +21,7 @@
 import android.annotation.IdRes;
 import android.annotation.IntRange;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
 import android.annotation.SystemService;
 import android.annotation.TestApi;
 import android.content.Context;
@@ -522,6 +523,50 @@
     }
 
     /**
+     * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that
+     * the calling app has acquired a lease on using {@link #acquireLease(BlobHandle, int)} or
+     * one of it's other variants.
+     *
+     * @hide
+     */
+    @TestApi
+    @NonNull
+    public List<BlobHandle> getLeasedBlobs() throws IOException {
+        try {
+            return mService.getLeasedBlobs(mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
+     * Return {@link LeaseInfo} representing a lease acquired using
+     * {@link #acquireLease(BlobHandle, int)} or one of it's other variants,
+     * or {@code null} if there is no lease acquired.
+     *
+     * @throws SecurityException when the blob represented by the {@code blobHandle} does not
+     *                           exist or the caller does not have access to it.
+     * @throws IllegalArgumentException when {@code blobHandle} is invalid.
+     *
+     * @hide
+     */
+    @TestApi
+    @Nullable
+    public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException {
+        try {
+            return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName());
+        } catch (ParcelableException e) {
+            e.maybeRethrow(IOException.class);
+            throw new RuntimeException(e);
+        } catch (RemoteException e) {
+            throw e.rethrowFromSystemServer();
+        }
+    }
+
+    /**
      * Represents an ongoing session of a blob's contribution to the blob store managed by the
      * system.
      *
diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
index e783813..20c15ab 100644
--- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
+++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl
@@ -18,6 +18,7 @@
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreSession;
+import android.app.blob.LeaseInfo;
 import android.os.RemoteCallback;
 
 /** {@hide} */
@@ -35,4 +36,7 @@
 
     List<BlobInfo> queryBlobsForUser(int userId);
     void deleteBlob(long blobId);
+
+    List<BlobHandle> getLeasedBlobs(in String packageName);
+    LeaseInfo getLeaseInfo(in BlobHandle blobHandle, in String packageName);
 }
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl
new file mode 100644
index 0000000..9088857
--- /dev/null
+++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright 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.
+ */
+package android.app.blob;
+
+/** {@hide} */
+parcelable LeaseInfo;
\ No newline at end of file
diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
similarity index 61%
rename from apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
rename to apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
index 3725ad4..fef50c9 100644
--- a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java
+++ b/apex/blobstore/framework/java/android/app/blob/LeaseInfo.java
@@ -16,50 +16,61 @@
 
 package android.app.blob;
 
+import android.annotation.CurrentTimeMillisLong;
+import android.annotation.IdRes;
 import android.annotation.NonNull;
+import android.annotation.Nullable;
+import android.annotation.TestApi;
 import android.os.Parcel;
 import android.os.Parcelable;
 
 import java.util.List;
 
 /**
- * Class to provide information about an accessor of a shared blob.
+ * Class to provide information about a lease (acquired using
+ * {@link BlobStoreManager#acquireLease(BlobHandle, int)} or one of it's variants)
+ * for a shared blob.
  *
  * @hide
  */
-public final class AccessorInfo implements Parcelable {
+@TestApi
+public final class LeaseInfo implements Parcelable {
     private final String mPackageName;
-    private final long mExpiryTimeMs;
+    private final long mExpiryTimeMillis;
     private final int mDescriptionResId;
     private final CharSequence mDescription;
 
-    public AccessorInfo(String packageName, long expiryTimeMs,
-            int descriptionResId, CharSequence description) {
+    public LeaseInfo(@NonNull String packageName, @CurrentTimeMillisLong long expiryTimeMs,
+            @IdRes int descriptionResId, @Nullable CharSequence description) {
         mPackageName = packageName;
-        mExpiryTimeMs = expiryTimeMs;
+        mExpiryTimeMillis = expiryTimeMs;
         mDescriptionResId = descriptionResId;
         mDescription = description;
     }
 
-    private AccessorInfo(Parcel in) {
+    private LeaseInfo(Parcel in) {
         mPackageName = in.readString();
-        mExpiryTimeMs = in.readLong();
+        mExpiryTimeMillis = in.readLong();
         mDescriptionResId = in.readInt();
         mDescription = in.readCharSequence();
     }
 
+    @NonNull
     public String getPackageName() {
         return mPackageName;
     }
 
-    public long getExpiryTimeMs() {
-        return mExpiryTimeMs;
+    @CurrentTimeMillisLong
+    public long getExpiryTimeMillis() {
+        return mExpiryTimeMillis;
     }
 
+    @IdRes
     public int getDescriptionResId() {
         return mDescriptionResId;
     }
 
+    @Nullable
     public CharSequence getDescription() {
         return mDescription;
     }
@@ -67,16 +78,16 @@
     @Override
     public void writeToParcel(@NonNull Parcel dest, int flags) {
         dest.writeString(mPackageName);
-        dest.writeLong(mExpiryTimeMs);
+        dest.writeLong(mExpiryTimeMillis);
         dest.writeInt(mDescriptionResId);
         dest.writeCharSequence(mDescription);
     }
 
     @Override
     public String toString() {
-        return "AccessorInfo {"
+        return "LeaseInfo {"
                 + "package: " + mPackageName + ","
-                + "expiryMs: " + mExpiryTimeMs + ","
+                + "expiryMs: " + mExpiryTimeMillis + ","
                 + "descriptionResId: " + mDescriptionResId + ","
                 + "description: " + mDescription + ","
                 + "}";
@@ -86,11 +97,11 @@
         return mPackageName;
     }
 
-    public static String toShortString(List<AccessorInfo> accessors) {
+    static String toShortString(List<LeaseInfo> leaseInfos) {
         final StringBuilder sb = new StringBuilder();
         sb.append("[");
-        for (int i = 0, size = accessors.size(); i < size; ++i) {
-            sb.append(accessors.get(i).toShortString());
+        for (int i = 0, size = leaseInfos.size(); i < size; ++i) {
+            sb.append(leaseInfos.get(i).toShortString());
             sb.append(",");
         }
         sb.append("]");
@@ -103,17 +114,17 @@
     }
 
     @NonNull
-    public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() {
+    public static final Creator<LeaseInfo> CREATOR = new Creator<LeaseInfo>() {
         @Override
         @NonNull
-        public AccessorInfo createFromParcel(Parcel source) {
-            return new AccessorInfo(source);
+        public LeaseInfo createFromParcel(Parcel source) {
+            return new LeaseInfo(source);
         }
 
         @Override
         @NonNull
-        public AccessorInfo[] newArray(int size) {
-            return new AccessorInfo[size];
+        public LeaseInfo[] newArray(int size) {
+            return new LeaseInfo[size];
         }
     };
 }
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 970766d..8b640ca 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java
@@ -38,6 +38,7 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.app.blob.BlobHandle;
+import android.app.blob.LeaseInfo;
 import android.content.Context;
 import android.content.res.ResourceId;
 import android.content.res.Resources;
@@ -281,6 +282,25 @@
         return false;
     }
 
+    @Nullable
+    LeaseInfo getLeaseInfo(@NonNull String packageName, int uid) {
+        synchronized (mMetadataLock) {
+            for (int i = 0, size = mLeasees.size(); i < size; ++i) {
+                final Leasee leasee = mLeasees.valueAt(i);
+                if (leasee.uid == uid && leasee.packageName.equals(packageName)) {
+                    final int descriptionResId = leasee.descriptionResEntryName == null
+                            ? Resources.ID_NULL
+                            : BlobStoreUtils.getDescriptionResourceId(
+                                    mContext, leasee.descriptionResEntryName, leasee.packageName,
+                                    UserHandle.getUserId(leasee.uid));
+                    return new LeaseInfo(packageName, leasee.expiryTimeMillis,
+                            descriptionResId, leasee.description);
+                }
+            }
+        }
+        return null;
+    }
+
     void forEachLeasee(Consumer<Leasee> consumer) {
         mLeasees.forEach(consumer);
     }
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 53a97ce..f4b8f0f 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java
@@ -45,11 +45,11 @@
 import android.annotation.NonNull;
 import android.annotation.Nullable;
 import android.annotation.UserIdInt;
-import android.app.blob.AccessorInfo;
 import android.app.blob.BlobHandle;
 import android.app.blob.BlobInfo;
 import android.app.blob.IBlobStoreManager;
 import android.app.blob.IBlobStoreSession;
+import android.app.blob.LeaseInfo;
 import android.content.BroadcastReceiver;
 import android.content.Context;
 import android.content.Intent;
@@ -454,17 +454,17 @@
                 return packageResources;
             };
             getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
-                final ArrayList<AccessorInfo> accessorInfos = new ArrayList<>();
+                final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
                 blobMetadata.forEachLeasee(leasee -> {
                     final int descriptionResId = leasee.descriptionResEntryName == null
                             ? Resources.ID_NULL
                             : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
                                     leasee.descriptionResEntryName, leasee.packageName);
-                    accessorInfos.add(new AccessorInfo(leasee.packageName, leasee.expiryTimeMillis,
+                    leaseInfos.add(new LeaseInfo(leasee.packageName, leasee.expiryTimeMillis,
                             descriptionResId, leasee.description));
                 });
                 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
-                        blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos));
+                        blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), leaseInfos));
             });
         }
         return blobInfos;
@@ -482,6 +482,31 @@
         }
     }
 
+    private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
+            @NonNull String callingPackage) {
+        final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
+        forEachBlobInUser(blobMetadata -> {
+            if (blobMetadata.isALeasee(callingPackage, callingUid)) {
+                leasedBlobs.add(blobMetadata.getBlobHandle());
+            }
+        }, UserHandle.getUserId(callingUid));
+        return leasedBlobs;
+    }
+
+    private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
+            int callingUid, @NonNull String callingPackage) {
+        synchronized (mBlobsLock) {
+            final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
+                    .get(blobHandle);
+            if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
+                    callingPackage, callingUid)) {
+                throw new SecurityException("Caller not allowed to access " + blobHandle
+                        + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
+            }
+            return blobMetadata.getLeaseInfo(callingPackage, callingUid);
+        }
+    }
+
     private void verifyCallingPackage(int callingUid, String callingPackage) {
         if (mPackageManagerInternal.getPackageUid(
                 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
@@ -1267,6 +1292,12 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
             try {
                 acquireLeaseInternal(blobHandle, descriptionResId, description,
                         leaseExpiryTimeMillis, callingUid, packageName);
@@ -1284,6 +1315,12 @@
             final int callingUid = Binder.getCallingUid();
             verifyCallingPackage(callingUid, packageName);
 
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
             releaseLeaseInternal(blobHandle, callingUid, packageName);
         }
 
@@ -1320,6 +1357,36 @@
         }
 
         @Override
+        @NonNull
+        public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) {
+            Objects.requireNonNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            return getLeasedBlobsInternal(callingUid, packageName);
+        }
+
+        @Override
+        @Nullable
+        public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
+            Objects.requireNonNull(blobHandle, "blobHandle must not be null");
+            blobHandle.assertIsValid();
+            Objects.requireNonNull(packageName, "packageName must not be null");
+
+            final int callingUid = Binder.getCallingUid();
+            verifyCallingPackage(callingUid, packageName);
+
+            if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
+                    packageName, UserHandle.getUserId(callingUid))) {
+                throw new SecurityException("Caller not allowed to open blob; "
+                        + "callingUid=" + callingUid + ", callingPackage=" + packageName);
+            }
+
+            return getLeaseInfoInternal(blobHandle, callingUid, packageName);
+        }
+
+        @Override
         public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
                 @Nullable String[] args) {
             // TODO: add proto-based version of this.
diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
index 6af540a..fabce76 100644
--- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
+++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java
@@ -47,4 +47,13 @@
             @NonNull String resourceEntryName, @NonNull String packageName) {
         return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName);
     }
+
+    @IdRes
+    static int getDescriptionResourceId(@NonNull Context context,
+            @NonNull String resourceEntryName, @NonNull String packageName, int userId) {
+        final Resources resources = getPackageResources(context, packageName, userId);
+        return resources == null
+                ? Resources.ID_NULL
+                : getDescriptionResourceId(resources, resourceEntryName, packageName);
+    }
 }
diff --git a/api/test-current.txt b/api/test-current.txt
index 3ae7e52..a5d1896 100644
--- a/api/test-current.txt
+++ b/api/test-current.txt
@@ -593,9 +593,22 @@
 package android.app.blob {
 
   public class BlobStoreManager {
+    method @Nullable public android.app.blob.LeaseInfo getLeaseInfo(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;
+    method @NonNull public java.util.List<android.app.blob.BlobHandle> getLeasedBlobs() throws java.io.IOException;
     method public void waitForIdle(long) throws java.lang.InterruptedException, java.util.concurrent.TimeoutException;
   }
 
+  public final class LeaseInfo implements android.os.Parcelable {
+    ctor public LeaseInfo(@NonNull String, long, @IdRes int, @Nullable CharSequence);
+    method public int describeContents();
+    method @Nullable public CharSequence getDescription();
+    method @IdRes public int getDescriptionResId();
+    method public long getExpiryTimeMillis();
+    method @NonNull public String getPackageName();
+    method public void writeToParcel(@NonNull android.os.Parcel, int);
+    field @NonNull public static final android.os.Parcelable.Creator<android.app.blob.LeaseInfo> CREATOR;
+  }
+
 }
 
 package android.app.prediction {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
index 504bd17..b805744 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/DummyBlobData.java
@@ -38,38 +38,75 @@
 public class DummyBlobData {
     private static final long DEFAULT_SIZE_BYTES = 10 * 1024L * 1024L;
 
-    private final Context mContext;
     private final Random mRandom;
     private final File mFile;
     private final long mFileSize;
-    private final String mLabel;
+    private final CharSequence mLabel;
 
     byte[] mFileDigest;
     long mExpiryTimeMs;
 
-    public DummyBlobData(Context context) {
-        this(context, new Random(0), "blob_" + System.nanoTime());
+    public DummyBlobData(Builder builder) {
+        mRandom = new Random(builder.getRandomSeed());
+        mFile = new File(builder.getContext().getFilesDir(), builder.getFileName());
+        mFileSize = builder.getFileSize();
+        mLabel = builder.getLabel();
     }
 
-    public DummyBlobData(Context context, long fileSize) {
-        this(context, fileSize, new Random(0), "blob_" + System.nanoTime(), "Test label");
-    }
+    public static class Builder {
+        private final Context mContext;
+        private int mRandomSeed = 0;
+        private long mFileSize = DEFAULT_SIZE_BYTES;
+        private CharSequence mLabel = "Test label";
+        private String mFileName = "blob_" + System.nanoTime();
 
-    public DummyBlobData(Context context, Random random, String fileName) {
-        this(context, DEFAULT_SIZE_BYTES, random, fileName, "Test label");
-    }
+        public Builder(Context context) {
+            mContext = context;
+        }
 
-    public DummyBlobData(Context context, Random random, String fileName, String label) {
-        this(context, DEFAULT_SIZE_BYTES, random, fileName, label);
-    }
+        public Context getContext() {
+            return mContext;
+        }
 
-    public DummyBlobData(Context context, long fileSize, Random random, String fileName,
-            String label) {
-        mContext = context;
-        mRandom = random;
-        mFile = new File(mContext.getFilesDir(), fileName);
-        mFileSize = fileSize;
-        mLabel = label;
+        public Builder setRandomSeed(int randomSeed) {
+            mRandomSeed = randomSeed;
+            return this;
+        }
+
+        public int getRandomSeed() {
+            return mRandomSeed;
+        }
+
+        public Builder setFileSize(int fileSize) {
+            mFileSize = fileSize;
+            return this;
+        }
+
+        public long getFileSize() {
+            return mFileSize;
+        }
+
+        public Builder setLabel(CharSequence label) {
+            mLabel = label;
+            return this;
+        }
+
+        public CharSequence getLabel() {
+            return mLabel;
+        }
+
+        public Builder setFileName(String fileName) {
+            mFileName = fileName;
+            return this;
+        }
+
+        public String getFileName() {
+            return mFileName;
+        }
+
+        public DummyBlobData build() {
+            return new DummyBlobData(this);
+        }
     }
 
     public void prepare() throws Exception {
diff --git a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
index c35385c..654c1e2 100644
--- a/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
+++ b/tests/BlobStoreTestUtils/src/com/android/utils/blob/Utils.java
@@ -16,7 +16,13 @@
 
 package com.android.utils.blob;
 
+import static com.google.common.truth.Truth.assertThat;
+
+import android.app.blob.BlobHandle;
 import android.app.blob.BlobStoreManager;
+import android.app.blob.LeaseInfo;
+import android.content.Context;
+import android.content.res.Resources;
 import android.os.ParcelFileDescriptor;
 
 import java.io.FileInputStream;
@@ -56,4 +62,76 @@
             copy(in, out, lengthBytes);
         }
     }
+
+    public static void assertLeasedBlobs(BlobStoreManager blobStoreManager,
+            BlobHandle... expectedBlobHandles) throws IOException {
+        assertThat(blobStoreManager.getLeasedBlobs()).containsExactly(expectedBlobHandles);
+    }
+
+    public static void assertNoLeasedBlobs(BlobStoreManager blobStoreManager)
+            throws IOException {
+        assertThat(blobStoreManager.getLeasedBlobs()).isEmpty();
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, CharSequence description) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, description);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), 0,
+                Resources.ID_NULL, description);
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, int descriptionResId) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, descriptionResId);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), 0,
+                descriptionResId, context.getString(descriptionResId));
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, CharSequence description,
+            long expiryTimeMs) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, description, expiryTimeMs);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs,
+                Resources.ID_NULL, description);
+    }
+
+    public static void acquireLease(Context context,
+            BlobHandle blobHandle, int descriptionResId,
+            long expiryTimeMs) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.acquireLease(blobHandle, descriptionResId, expiryTimeMs);
+
+        final LeaseInfo leaseInfo = blobStoreManager.getLeaseInfo(blobHandle);
+        assertLeaseInfo(leaseInfo, context.getPackageName(), expiryTimeMs,
+                descriptionResId, context.getString(descriptionResId));
+    }
+
+    public static void releaseLease(Context context,
+            BlobHandle blobHandle) throws IOException {
+        final BlobStoreManager blobStoreManager = (BlobStoreManager) context.getSystemService(
+                Context.BLOB_STORE_SERVICE);
+        blobStoreManager.releaseLease(blobHandle);
+        assertThat(blobStoreManager.getLeaseInfo(blobHandle)).isNull();
+    }
+
+    private static void assertLeaseInfo(LeaseInfo leaseInfo, String packageName,
+            long expiryTimeMs, int descriptionResId, CharSequence description) {
+        assertThat(leaseInfo.getPackageName()).isEqualTo(packageName);
+        assertThat(leaseInfo.getExpiryTimeMillis()).isEqualTo(expiryTimeMs);
+        assertThat(leaseInfo.getDescriptionResId()).isEqualTo(descriptionResId);
+        assertThat(leaseInfo.getDescription()).isEqualTo(description);
+    }
 }