diff options
12 files changed, 359 insertions, 55 deletions
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 02df5e2b6c31..23f025b0a759 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 @@ public class BlobStorePerfTests { } 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 9746dd023002..80062d5d245f 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 @@ public final class BlobInfo implements Parcelable { 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 @@ public final class BlobInfo implements Parcelable { return mLabel; } - public List<AccessorInfo> getAccessors() { - return Collections.unmodifiableList(mAccessors); + public List<LeaseInfo> getLeases() { + return Collections.unmodifiableList(mLeaseInfos); } @Override @@ -70,7 +70,7 @@ public final class BlobInfo implements Parcelable { dest.writeLong(mId); dest.writeLong(mExpiryTimeMs); dest.writeCharSequence(mLabel); - dest.writeList(mAccessors); + dest.writeList(mLeaseInfos); } @Override @@ -83,7 +83,7 @@ public final class BlobInfo implements Parcelable { + "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 814ab6dbd7fd..c339351759cd 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.CurrentTimeMillisLong; 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 @@ public class BlobStoreManager { } /** + * 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 e78381359b41..20c15ab57496 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 @@ package android.app.blob; 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 @@ interface IBlobStoreManager { 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 000000000000..908885731bb1 --- /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 index 3725ad4a6c09..fef50c9e8dba 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 @@ public final class AccessorInfo implements Parcelable { @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 @@ public final class AccessorInfo implements Parcelable { 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 @@ public final class AccessorInfo implements Parcelable { } @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 970766d2c8a6..8b640ca75698 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 static com.android.server.blob.BlobStoreUtils.getPackageResources; 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 @@ class BlobMetadata { 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 53a97cefa59b..f4b8f0f39e85 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.IntRange; 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 @@ public class BlobStoreManagerService extends SystemService { 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 @@ public class BlobStoreManagerService extends SystemService { } } + 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 @@ public class BlobStoreManagerService extends SystemService { 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 @@ public class BlobStoreManagerService extends SystemService { 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 @@ public class BlobStoreManagerService extends SystemService { } @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 6af540acd6a4..fabce766c237 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 @@ class BlobStoreUtils { @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 7c71c777d50b..937046c4b504 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -597,9 +597,22 @@ package android.app.backup { 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 504bd1727682..b805744a8387 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 @@ import java.util.concurrent.TimeUnit; 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 Builder setRandomSeed(int randomSeed) { + mRandomSeed = randomSeed; + return this; + } + + public int getRandomSeed() { + return mRandomSeed; + } + + public Builder setFileSize(int fileSize) { + mFileSize = fileSize; + return this; + } - 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 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 c35385cd0429..654c1e21999d 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 @@ public class Utils { 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); + } } |