Merge "Add @TestApis getLeasedBlobs() and getLeaseInfo()." into rvc-dev am: c95e8472c4 am: 188abab52a am: 59b86e4341

Change-Id: Ia4b58e165b183089fd09205ce95a86b40732eb83
This commit is contained in:
Automerger Merge Worker
2020-03-14 05:30:35 +00:00
12 changed files with 359 additions and 55 deletions

View File

@ -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;
}

View File

@ -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) + ","
+ "}";
}

View File

@ -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;
@ -521,6 +522,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.

View File

@ -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);
}

View File

@ -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;

View File

@ -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];
}
};
}

View File

@ -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);
}

View File

@ -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);
}
@ -1319,6 +1356,36 @@ public class BlobStoreManagerService extends SystemService {
deleteBlobInternal(blobId, callingUid);
}
@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) {

View File

@ -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);
}
}

View File

@ -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 {

View File

@ -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 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 {

View File

@ -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);
}
}