diff options
author | Sudheer Shanka <sudheersai@google.com> | 2020-03-04 13:54:36 -0800 |
---|---|---|
committer | Sudheer Shanka <sudheersai@google.com> | 2020-03-09 11:05:53 -0700 |
commit | 130d79cc8b05e82fbaf0172ececfcea6d159460c (patch) | |
tree | 0057f1ba843be32e762143b6589228628ce29d7a /apex/blobstore | |
parent | 606626e020abd13b87af7e2db59f6dafaef24452 (diff) |
Add hidden APIs to query and delete shared blobs.
Bug: 150804006
Test: atest --test-mapping apex/blobstore
Change-Id: Ia445715879c1b6ec74c2a2ee9d2ff0eb1f4ee72b
Diffstat (limited to 'apex/blobstore')
8 files changed, 408 insertions, 29 deletions
diff --git a/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java new file mode 100644 index 000000000000..3725ad4a6c09 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/AccessorInfo.java @@ -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]; + } + }; +} diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl b/apex/blobstore/framework/java/android/app/blob/BlobInfo.aidl new file mode 100644 index 000000000000..25497738f685 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.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 BlobInfo;
\ No newline at end of file diff --git a/apex/blobstore/framework/java/android/app/blob/BlobInfo.java b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java new file mode 100644 index 000000000000..9746dd023002 --- /dev/null +++ b/apex/blobstore/framework/java/android/app/blob/BlobInfo.java @@ -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]; + } + }; +} diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index d1e28e99b6d6..7af3f662c195 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -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. diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index a85a25c9c4ad..beeb03f06ed7 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -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); }
\ No newline at end of file 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 dab4797b313f..970766d2c8a6 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -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); 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 96f7b7aee165..ed3dda44e131 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -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) { @@ -1250,6 +1298,28 @@ public class BlobStoreManagerService extends SystemService { } @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) { // 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 new file mode 100644 index 000000000000..6af540acd6a4 --- /dev/null +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreUtils.java @@ -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); + } +} |