Merge "Add hidden APIs to query and delete shared blobs." into rvc-dev

This commit is contained in:
Sudheer Shanka
2020-03-09 21:38:44 +00:00
committed by Android (Google) Code Review
8 changed files with 408 additions and 29 deletions

View File

@@ -0,0 +1,119 @@
/*
* 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.
*/
package android.app.blob;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.List;
/**
* Class to provide information about an accessor of a shared blob.
*
* @hide
*/
public final class AccessorInfo implements Parcelable {
private final String mPackageName;
private final long mExpiryTimeMs;
private final int mDescriptionResId;
private final CharSequence mDescription;
public AccessorInfo(String packageName, long expiryTimeMs,
int descriptionResId, CharSequence description) {
mPackageName = packageName;
mExpiryTimeMs = expiryTimeMs;
mDescriptionResId = descriptionResId;
mDescription = description;
}
private AccessorInfo(Parcel in) {
mPackageName = in.readString();
mExpiryTimeMs = in.readLong();
mDescriptionResId = in.readInt();
mDescription = in.readCharSequence();
}
public String getPackageName() {
return mPackageName;
}
public long getExpiryTimeMs() {
return mExpiryTimeMs;
}
public int getDescriptionResId() {
return mDescriptionResId;
}
public CharSequence getDescription() {
return mDescription;
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeString(mPackageName);
dest.writeLong(mExpiryTimeMs);
dest.writeInt(mDescriptionResId);
dest.writeCharSequence(mDescription);
}
@Override
public String toString() {
return "AccessorInfo {"
+ "package: " + mPackageName + ","
+ "expiryMs: " + mExpiryTimeMs + ","
+ "descriptionResId: " + mDescriptionResId + ","
+ "description: " + mDescription + ","
+ "}";
}
private String toShortString() {
return mPackageName;
}
public static String toShortString(List<AccessorInfo> accessors) {
final StringBuilder sb = new StringBuilder();
sb.append("[");
for (int i = 0, size = accessors.size(); i < size; ++i) {
sb.append(accessors.get(i).toShortString());
sb.append(",");
}
sb.append("]");
return sb.toString();
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<AccessorInfo> CREATOR = new Creator<AccessorInfo>() {
@Override
@NonNull
public AccessorInfo createFromParcel(Parcel source) {
return new AccessorInfo(source);
}
@Override
@NonNull
public AccessorInfo[] newArray(int size) {
return new AccessorInfo[size];
}
};
}

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

View File

@@ -0,0 +1,109 @@
/*
* 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.
*/
package android.app.blob;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Collections;
import java.util.List;
/**
* Class to provide information about a shared blob.
*
* @hide
*/
public final class BlobInfo implements Parcelable {
private final long mId;
private final long mExpiryTimeMs;
private final CharSequence mLabel;
private final List<AccessorInfo> mAccessors;
public BlobInfo(long id, long expiryTimeMs, CharSequence label,
List<AccessorInfo> accessors) {
mId = id;
mExpiryTimeMs = expiryTimeMs;
mLabel = label;
mAccessors = accessors;
}
private BlobInfo(Parcel in) {
mId = in.readLong();
mExpiryTimeMs = in.readLong();
mLabel = in.readCharSequence();
mAccessors = in.readArrayList(null /* classloader */);
}
public long getId() {
return mId;
}
public long getExpiryTimeMs() {
return mExpiryTimeMs;
}
public CharSequence getLabel() {
return mLabel;
}
public List<AccessorInfo> getAccessors() {
return Collections.unmodifiableList(mAccessors);
}
@Override
public void writeToParcel(@NonNull Parcel dest, int flags) {
dest.writeLong(mId);
dest.writeLong(mExpiryTimeMs);
dest.writeCharSequence(mLabel);
dest.writeList(mAccessors);
}
@Override
public String toString() {
return toShortString();
}
private String toShortString() {
return "BlobInfo {"
+ "id: " + mId + ","
+ "expiryMs: " + mExpiryTimeMs + ","
+ "label: " + mLabel + ","
+ "accessors: " + AccessorInfo.toShortString(mAccessors) + ","
+ "}";
}
@Override
public int describeContents() {
return 0;
}
@NonNull
public static final Creator<BlobInfo> CREATOR = new Creator<BlobInfo>() {
@Override
@NonNull
public BlobInfo createFromParcel(Parcel source) {
return new BlobInfo(source);
}
@Override
@NonNull
public BlobInfo[] newArray(int size) {
return new BlobInfo[size];
}
};
}

View File

@@ -27,11 +27,13 @@ import android.os.ParcelFileDescriptor;
import android.os.ParcelableException;
import android.os.RemoteCallback;
import android.os.RemoteException;
import android.os.UserHandle;
import com.android.internal.util.function.pooled.PooledLambda;
import java.io.Closeable;
import java.io.IOException;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.TimeUnit;
@@ -145,9 +147,6 @@ public class BlobStoreManager {
/** @hide */
public static final int INVALID_RES_ID = -1;
/** @hide */
public static final String DESC_RES_TYPE_STRING = "string";
private final Context mContext;
private final IBlobStoreManager mService;
@@ -495,6 +494,31 @@ public class BlobStoreManager {
}
}
/** @hide */
@NonNull
public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException {
try {
return mService.queryBlobsForUser(user.getIdentifier());
} catch (ParcelableException e) {
e.maybeRethrow(IOException.class);
throw new RuntimeException(e);
} catch (RemoteException e) {
throw e.rethrowFromSystemServer();
}
}
/** @hide */
public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException {
try {
mService.deleteBlob(blobInfo.getId());
} 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

@@ -16,6 +16,7 @@
package android.app.blob;
import android.app.blob.BlobHandle;
import android.app.blob.BlobInfo;
import android.app.blob.IBlobStoreSession;
import android.os.RemoteCallback;
@@ -31,4 +32,7 @@ interface IBlobStoreManager {
void releaseLease(in BlobHandle handle, in String packageName);
void waitForIdle(in RemoteCallback callback);
List<BlobInfo> queryBlobsForUser(int userId);
void deleteBlob(long blobId);
}

View File

@@ -15,7 +15,6 @@
*/
package com.android.server.blob;
import static android.app.blob.BlobStoreManager.DESC_RES_TYPE_STRING;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION;
import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_NAME;
import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME;
@@ -30,16 +29,16 @@ import static android.app.blob.XmlTags.TAG_LEASEE;
import static android.os.Process.INVALID_UID;
import static android.system.OsConstants.O_RDONLY;
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_ADD_DESC_RES_NAME;
import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.app.blob.BlobHandle;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.ResourceId;
import android.content.res.Resources;
import android.os.ParcelFileDescriptor;
@@ -65,6 +64,7 @@ import java.io.File;
import java.io.FileDescriptor;
import java.io.IOException;
import java.util.Objects;
import java.util.function.Consumer;
class BlobMetadata {
private final Object mMetadataLock = new Object();
@@ -281,6 +281,10 @@ class BlobMetadata {
return false;
}
void forEachLeasee(Consumer<Leasee> consumer) {
mLeasees.forEach(consumer);
}
File getBlobFile() {
if (mBlobFile == null) {
mBlobFile = BlobStoreConfig.getBlobFile(mBlobId);
@@ -506,16 +510,9 @@ class BlobMetadata {
if (resources == null) {
return null;
}
try {
final int resId = resources.getIdentifier(descriptionResEntryName,
DESC_RES_TYPE_STRING, packageName);
return resId <= 0 ? null : resources.getString(resId);
} catch (Resources.NotFoundException e) {
if (LOGV) {
Slog.w(TAG, "Description resource not found", e);
}
return null;
}
final int resId = getDescriptionResourceId(resources, descriptionResEntryName,
packageName);
return resId == Resources.ID_NULL ? null : resources.getString(resId);
}
@Nullable
@@ -546,19 +543,6 @@ class BlobMetadata {
return desc == null ? "<none>" : desc;
}
@Nullable
private static Resources getPackageResources(@NonNull Context context,
@NonNull String packageName, int userId) {
try {
return context.getPackageManager()
.getResourcesForApplicationAsUser(packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.d(TAG, "Unknown package in user " + userId + ": "
+ packageName, e);
return null;
}
}
void writeToXml(@NonNull XmlSerializer out) throws IOException {
XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName);
XmlUtils.writeIntAttribute(out, ATTR_UID, uid);

View File

@@ -36,6 +36,8 @@ import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
import static com.android.server.blob.BlobStoreSession.stateToString;
import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
import static com.android.server.blob.BlobStoreUtils.getPackageResources;
import android.annotation.CurrentTimeSecondsLong;
import android.annotation.IdRes;
@@ -43,7 +45,9 @@ 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.content.BroadcastReceiver;
@@ -100,6 +104,7 @@ import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintWriter;
import java.lang.ref.WeakReference;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.util.ArrayList;
@@ -110,6 +115,7 @@ import java.util.Random;
import java.util.Set;
import java.util.concurrent.atomic.AtomicLong;
import java.util.function.Consumer;
import java.util.function.Function;
/**
* Service responsible for maintaining and facilitating access to data blobs published by apps.
@@ -434,6 +440,48 @@ public class BlobStoreManagerService extends SystemService {
}
}
private List<BlobInfo> queryBlobsForUserInternal(int userId) {
final ArrayList<BlobInfo> blobInfos = new ArrayList<>();
synchronized (mBlobsLock) {
final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>();
final Function<String, Resources> resourcesGetter = (packageName) -> {
final WeakReference<Resources> resourcesRef = resources.get(packageName);
Resources packageResources = resourcesRef == null ? null : resourcesRef.get();
if (packageResources == null) {
packageResources = getPackageResources(mContext, packageName, userId);
resources.put(packageName, new WeakReference<>(packageResources));
}
return packageResources;
};
getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
final ArrayList<AccessorInfo> accessorInfos = 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,
descriptionResId, leasee.description));
});
blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(), accessorInfos));
});
}
return blobInfos;
}
private void deleteBlobInternal(long blobId, int callingUid) {
synchronized (mBlobsLock) {
final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
UserHandle.getUserId(callingUid));
userBlobs.entrySet().removeIf(entry -> {
final BlobMetadata blobMetadata = entry.getValue();
return blobMetadata.getBlobId() == blobId;
});
writeBlobsInfoAsync();
}
}
private void verifyCallingPackage(int callingUid, String callingPackage) {
if (mPackageManagerInternal.getPackageUid(
callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
@@ -1249,6 +1297,28 @@ public class BlobStoreManagerService extends SystemService {
.recycleOnUse());
}
@Override
@NonNull
public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
if (Binder.getCallingUid() != Process.SYSTEM_UID) {
throw new SecurityException("Only system uid is allowed to call "
+ "queryBlobsForUser()");
}
return queryBlobsForUserInternal(userId);
}
@Override
public void deleteBlob(long blobId) {
final int callingUid = Binder.getCallingUid();
if (callingUid != Process.SYSTEM_UID) {
throw new SecurityException("Only system uid is allowed to call "
+ "deleteBlob()");
}
deleteBlobInternal(blobId, callingUid);
}
@Override
public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
@Nullable String[] args) {

View File

@@ -0,0 +1,50 @@
/*
* 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.
*/
package com.android.server.blob;
import static com.android.server.blob.BlobStoreConfig.TAG;
import android.annotation.IdRes;
import android.annotation.NonNull;
import android.annotation.Nullable;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.util.Slog;
class BlobStoreUtils {
private static final String DESC_RES_TYPE_STRING = "string";
@Nullable
static Resources getPackageResources(@NonNull Context context,
@NonNull String packageName, int userId) {
try {
return context.getPackageManager()
.getResourcesForApplicationAsUser(packageName, userId);
} catch (PackageManager.NameNotFoundException e) {
Slog.d(TAG, "Unknown package in user " + userId + ": "
+ packageName, e);
return null;
}
}
@IdRes
static int getDescriptionResourceId(@NonNull Resources resources,
@NonNull String resourceEntryName, @NonNull String packageName) {
return resources.getIdentifier(resourceEntryName, DESC_RES_TYPE_STRING, packageName);
}
}