diff --git a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java index 8cea645f4e71c..821305e182405 100644 --- a/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java +++ b/apex/blobstore/framework/java/android/app/blob/BlobStoreManager.java @@ -142,6 +142,9 @@ public class BlobStoreManager { /** @hide */ public static final int COMMIT_RESULT_ERROR = 1; + /** @hide */ + public static final int INVALID_RES_ID = -1; + private final Context mContext; private final IBlobStoreManager mService; @@ -285,11 +288,65 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { try { - mService.acquireLease(blobHandle, descriptionResId, leaseExpiryTimeMillis, + mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis, + mContext.getOpPackageName()); + } catch (ParcelableException e) { + e.maybeRethrow(IOException.class); + throw new RuntimeException(e); + } catch (RemoteException e) { + throw e.rethrowFromSystemServer(); + } + } + + /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + *

This is variant of {@link #acquireLease(BlobHandle, int, long)} taking a + * {@link CharSequence} for {@code description}. It is highly recommended that callers only + * use this when a valid resource ID for {@code description} could not be provided. Otherwise, + * apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow + * {@code description} to be localized. + * + *

Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + *

This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to + * acquire a lease for. + * @param description a short description string that can be surfaced + * to the user explaining what the blob is used for. + * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be + * automatically released, in {@link System#currentTimeMillis()} + * timebase. If its value is {@code 0}, then the behavior of this + * API is identical to {@link #acquireLease(BlobHandle, int)} + * where clients have to explicitly call + * {@link #releaseLease(BlobHandle)} when they don't + * need the blob anymore. + * + * @throws IOException when there is an I/O error while acquiring a lease to the blob. + * @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 or + * if the {@code leaseExpiryTimeMillis} is greater than the + * {@link BlobHandle#getExpiryTimeMillis()}. + * @throws IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description, + @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException { + try { + mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis, mContext.getOpPackageName()); } catch (ParcelableException e) { e.maybeRethrow(IOException.class); @@ -327,12 +384,54 @@ public class BlobStoreManager { * caller is trying to acquire too many leases. * * @see {@link #acquireLease(BlobHandle, int, long)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} */ public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId) throws IOException { acquireLease(blobHandle, descriptionResId, 0); } + /** + * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the + * system that the caller wants the blob to be kept around. + * + *

This is variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence} + * for {@code description}. It is highly recommended that callers only use this when a valid + * resource ID for {@code description} could not be provided. Otherwise, apps should prefer + * using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be + * localized. + * + *

This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients + * don't have to specify the lease expiry time upfront using this API and need to explicitly + * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep + * a blob around. + * + *

Any active leases will be automatically released when the blob's expiry time + * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. + * + *

This lease information is persisted and calling this more than once will result in + * latest lease overriding any previous lease. + * + * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to + * acquire a lease for. + * @param description a short description string that can be surfaced + * to the user explaining what the blob is used for. + * + * @throws IOException when there is an I/O error while acquiring a lease to the blob. + * @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. + * @throws IllegalStateException when a lease could not be acquired, such as when the + * caller is trying to acquire too many leases. + * + * @see {@link #acquireLease(BlobHandle, int)} + * @see {@link #acquireLease(BlobHandle, CharSequence, long)} + */ + public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description) + throws IOException { + acquireLease(blobHandle, description, 0); + } + /** * Release all active leases to the blob represented by {@code blobHandle} which are * currently held by the caller. diff --git a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl index e2128b4217465..a85a25c9c4ad6 100644 --- a/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl +++ b/apex/blobstore/framework/java/android/app/blob/IBlobStoreManager.aidl @@ -26,8 +26,8 @@ interface IBlobStoreManager { ParcelFileDescriptor openBlob(in BlobHandle handle, in String packageName); void deleteSession(long sessionId, in String packageName); - void acquireLease(in BlobHandle handle, int descriptionResId, long leaseTimeout, - in String packageName); + void acquireLease(in BlobHandle handle, int descriptionResId, in CharSequence description, + long leaseTimeoutMillis, in String packageName); void releaseLease(in BlobHandle handle, in String packageName); void waitForIdle(in RemoteCallback callback); diff --git a/apex/blobstore/framework/java/android/app/blob/XmlTags.java b/apex/blobstore/framework/java/android/app/blob/XmlTags.java index 803c9a40e5eac..9834d7477838b 100644 --- a/apex/blobstore/framework/java/android/app/blob/XmlTags.java +++ b/apex/blobstore/framework/java/android/app/blob/XmlTags.java @@ -52,4 +52,5 @@ public final class XmlTags { // For leasee public static final String TAG_LEASEE = "l"; public static final String ATTR_DESCRIPTION_RES_ID = "rid"; + public static final String ATTR_DESCRIPTION = "d"; } 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 c12e0ec8aec96..c7d803c884108 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobMetadata.java @@ -15,6 +15,7 @@ */ package com.android.server.blob; +import static android.app.blob.XmlTags.ATTR_DESCRIPTION; import static android.app.blob.XmlTags.ATTR_DESCRIPTION_RES_ID; import static android.app.blob.XmlTags.ATTR_EXPIRY_TIME; import static android.app.blob.XmlTags.ATTR_ID; @@ -28,12 +29,14 @@ import static android.app.blob.XmlTags.TAG_LEASEE; import static android.system.OsConstants.O_RDONLY; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_STRING_DESC; 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; import android.os.RevocableFileDescriptor; @@ -141,11 +144,11 @@ class BlobMetadata { } } - void addLeasee(String callingPackage, int callingUid, - int descriptionResId, long leaseExpiryTimeMillis) { + void addLeasee(String callingPackage, int callingUid, int descriptionResId, + CharSequence description, long leaseExpiryTimeMillis) { synchronized (mMetadataLock) { mLeasees.add(new Leasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis)); + descriptionResId, description, leaseExpiryTimeMillis)); } } @@ -308,7 +311,7 @@ class BlobMetadata { } @Nullable - static BlobMetadata createFromXml(Context context, XmlPullParser in) + static BlobMetadata createFromXml(XmlPullParser in, int version, Context context) throws XmlPullParserException, IOException { final long blobId = XmlUtils.readLongAttribute(in, ATTR_ID); final int userId = XmlUtils.readIntAttribute(in, ATTR_USER_ID); @@ -321,12 +324,12 @@ class BlobMetadata { if (TAG_BLOB_HANDLE.equals(in.getName())) { blobHandle = BlobHandle.createFromXml(in); } else if (TAG_COMMITTER.equals(in.getName())) { - final Committer committer = Committer.createFromXml(in); + final Committer committer = Committer.createFromXml(in, version); if (committer != null) { committers.add(committer); } } else if (TAG_LEASEE.equals(in.getName())) { - leasees.add(Leasee.createFromXml(in)); + leasees.add(Leasee.createFromXml(in, version)); } } @@ -366,7 +369,7 @@ class BlobMetadata { } @Nullable - static Committer createFromXml(@NonNull XmlPullParser in) + static Committer createFromXml(@NonNull XmlPullParser in, int version) throws XmlPullParserException, IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); @@ -388,12 +391,15 @@ class BlobMetadata { static final class Leasee extends Accessor { public final int descriptionResId; + public final CharSequence description; public final long expiryTimeMillis; - Leasee(String packageName, int uid, int descriptionResId, long expiryTimeMillis) { + Leasee(String packageName, int uid, int descriptionResId, CharSequence description, + long expiryTimeMillis) { super(packageName, uid); this.descriptionResId = descriptionResId; this.expiryTimeMillis = expiryTimeMillis; + this.description = description; } boolean isStillValid() { @@ -401,35 +407,51 @@ class BlobMetadata { } void dump(Context context, IndentingPrintWriter fout) { - String desc = null; - try { - final Resources leaseeRes = context.getPackageManager() - .getResourcesForApplicationAsUser(packageName, UserHandle.getUserId(uid)); - desc = leaseeRes.getString(descriptionResId); - } catch (PackageManager.NameNotFoundException e) { - Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " - + packageName, e); - desc = ""; - } - fout.println("desc: " + desc); + fout.println("desc: " + getDescriptionToDump(context)); fout.println("expiryMs: " + expiryTimeMillis); } + private String getDescriptionToDump(Context context) { + String desc = null; + if (ResourceId.isValid(descriptionResId)) { + try { + final Resources leaseeRes = context.getPackageManager() + .getResourcesForApplicationAsUser( + packageName, UserHandle.getUserId(uid)); + desc = leaseeRes.getString(descriptionResId); + } catch (PackageManager.NameNotFoundException e) { + Slog.d(TAG, "Unknown package in user " + UserHandle.getUserId(uid) + ": " + + packageName, e); + desc = ""; + } + } else { + desc = description.toString(); + } + return desc; + } + void writeToXml(@NonNull XmlSerializer out) throws IOException { XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, packageName); XmlUtils.writeIntAttribute(out, ATTR_UID, uid); XmlUtils.writeIntAttribute(out, ATTR_DESCRIPTION_RES_ID, descriptionResId); XmlUtils.writeLongAttribute(out, ATTR_EXPIRY_TIME, expiryTimeMillis); + XmlUtils.writeStringAttribute(out, ATTR_DESCRIPTION, description); } @NonNull - static Leasee createFromXml(@NonNull XmlPullParser in) throws IOException { + static Leasee createFromXml(@NonNull XmlPullParser in, int version) throws IOException { final String packageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); final int uid = XmlUtils.readIntAttribute(in, ATTR_UID); final int descriptionResId = XmlUtils.readIntAttribute(in, ATTR_DESCRIPTION_RES_ID); final long expiryTimeMillis = XmlUtils.readLongAttribute(in, ATTR_EXPIRY_TIME); + final CharSequence description; + if (version >= XML_VERSION_ADD_STRING_DESC) { + description = XmlUtils.readStringAttribute(in, ATTR_DESCRIPTION); + } else { + description = null; + } - return new Leasee(packageName, uid, descriptionResId, expiryTimeMillis); + return new Leasee(packageName, uid, descriptionResId, description, expiryTimeMillis); } } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java index ba2e559afdabd..bcc1610435d95 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreConfig.java @@ -28,7 +28,12 @@ class BlobStoreConfig { public static final String TAG = "BlobStore"; public static final boolean LOGV = Log.isLoggable(TAG, Log.VERBOSE); - public static final int CURRENT_XML_VERSION = 1; + // Initial version. + public static final int XML_VERSION_INIT = 1; + // Added a string variant of lease description. + public static final int XML_VERSION_ADD_STRING_DESC = 2; + + public static final int XML_VERSION_CURRENT = XML_VERSION_ADD_STRING_DESC; private static final String ROOT_DIR_NAME = "blobstore"; private static final String BLOBS_DIR_NAME = "blobs"; 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 0ba34cab6560f..1efdbda97fe58 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreManagerService.java @@ -27,10 +27,10 @@ import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE; import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES; import static android.os.UserHandle.USER_NULL; -import static com.android.server.blob.BlobStoreConfig.CURRENT_XML_VERSION; import static com.android.server.blob.BlobStoreConfig.LOGV; import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS; import static com.android.server.blob.BlobStoreConfig.TAG; +import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT; import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED; import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED; import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID; @@ -324,7 +324,8 @@ public class BlobStoreManagerService extends SystemService { } private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, - long leaseExpiryTimeMillis, int callingUid, String callingPackage) { + CharSequence description, long leaseExpiryTimeMillis, + int callingUid, String callingPackage) { synchronized (mBlobsLock) { final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid)) .get(blobHandle); @@ -338,7 +339,7 @@ public class BlobStoreManagerService extends SystemService { "Lease expiry cannot be later than blobs expiry time"); } blobMetadata.addLeasee(callingPackage, callingUid, - descriptionResId, leaseExpiryTimeMillis); + descriptionResId, description, leaseExpiryTimeMillis); if (LOGV) { Slog.v(TAG, "Acquired lease on " + blobHandle + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage); @@ -450,7 +451,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_SESSIONS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) { final LongSparseArray userSessions = @@ -491,6 +492,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_SESSIONS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -499,7 +501,7 @@ public class BlobStoreManagerService extends SystemService { if (TAG_SESSION.equals(in.getName())) { final BlobStoreSession session = BlobStoreSession.createFromXml( - in, mContext, mSessionStateChangeListener); + in, version, mContext, mSessionStateChangeListener); if (session == null) { continue; } @@ -539,7 +541,7 @@ public class BlobStoreManagerService extends SystemService { out.setOutput(fos, StandardCharsets.UTF_8.name()); out.startDocument(null, true); out.startTag(null, TAG_BLOBS); - XmlUtils.writeIntAttribute(out, ATTR_VERSION, CURRENT_XML_VERSION); + XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT); for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) { final ArrayMap userBlobs = mBlobsMap.valueAt(i); @@ -579,6 +581,7 @@ public class BlobStoreManagerService extends SystemService { final XmlPullParser in = Xml.newPullParser(); in.setInput(fis, StandardCharsets.UTF_8.name()); XmlUtils.beginDocument(in, TAG_BLOBS); + final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION); while (true) { XmlUtils.nextElement(in); if (in.getEventType() == XmlPullParser.END_DOCUMENT) { @@ -586,7 +589,8 @@ public class BlobStoreManagerService extends SystemService { } if (TAG_BLOB.equals(in.getName())) { - final BlobMetadata blobMetadata = BlobMetadata.createFromXml(mContext, in); + final BlobMetadata blobMetadata = BlobMetadata.createFromXml( + in, version, mContext); final SparseArray userPackages = allPackages.get( blobMetadata.getUserId()); if (userPackages == null) { @@ -1032,11 +1036,14 @@ public class BlobStoreManagerService extends SystemService { @Override public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId, + @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) { Objects.requireNonNull(blobHandle, "blobHandle must not be null"); blobHandle.assertIsValid(); - Preconditions.checkArgument(ResourceId.isValid(descriptionResId), - "descriptionResId is not valid"); + Preconditions.checkArgument( + ResourceId.isValid(descriptionResId) || description != null, + "Description must be valid; descriptionId=" + descriptionResId + + ", description=" + description); Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis, "leaseExpiryTimeMillis must not be negative"); Objects.requireNonNull(packageName, "packageName must not be null"); @@ -1044,7 +1051,7 @@ public class BlobStoreManagerService extends SystemService { final int callingUid = Binder.getCallingUid(); verifyCallingPackage(callingUid, packageName); - acquireLeaseInternal(blobHandle, descriptionResId, leaseExpiryTimeMillis, + acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis, callingUid, packageName); } diff --git a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java index bd35b86babd89..80b42355ef7c1 100644 --- a/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java +++ b/apex/blobstore/service/java/com/android/server/blob/BlobStoreSession.java @@ -511,7 +511,7 @@ class BlobStoreSession extends IBlobStoreSession.Stub { } @Nullable - static BlobStoreSession createFromXml(@NonNull XmlPullParser in, + static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version, @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) throws IOException, XmlPullParserException { final int sessionId = XmlUtils.readIntAttribute(in, ATTR_ID); diff --git a/api/current.txt b/api/current.txt index ff3701023df15..475506a317bc5 100644 --- a/api/current.txt +++ b/api/current.txt @@ -7574,7 +7574,9 @@ package android.app.blob { public class BlobStoreManager { method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int, long) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence, long) throws java.io.IOException; method public void acquireLease(@NonNull android.app.blob.BlobHandle, @IdRes int) throws java.io.IOException; + method public void acquireLease(@NonNull android.app.blob.BlobHandle, @NonNull CharSequence) throws java.io.IOException; method @IntRange(from=1) public long createSession(@NonNull android.app.blob.BlobHandle) throws java.io.IOException; method public void deleteSession(@IntRange(from=1) long) throws java.io.IOException; method @NonNull public android.os.ParcelFileDescriptor openBlob(@NonNull android.app.blob.BlobHandle) throws java.io.IOException;