| /* |
| * Copyright 2019 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.BytesLong; |
| import android.annotation.CallbackExecutor; |
| 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; |
| import android.os.LimitExceededException; |
| 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; |
| import java.util.concurrent.TimeoutException; |
| import java.util.function.Consumer; |
| |
| /** |
| * This class provides access to the blob store managed by the system. |
| * |
| * <p> Apps can publish and access a data blob using a {@link BlobHandle} object which can |
| * be created with {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}. |
| * This {@link BlobHandle} object encapsulates the following pieces of information used for |
| * identifying the blobs: |
| * <ul> |
| * <li> {@link BlobHandle#getSha256Digest()} |
| * <li> {@link BlobHandle#getLabel()} |
| * <li> {@link BlobHandle#getExpiryTimeMillis()} |
| * <li> {@link BlobHandle#getTag()} |
| * </ul> |
| * For two {@link BlobHandle} objects to be considered identical, all these pieces of information |
| * must be equal. |
| * |
| * <p> For contributing a new data blob, an app needs to create a session using |
| * {@link BlobStoreManager#createSession(BlobHandle)} and then open this session for writing using |
| * {@link BlobStoreManager#openSession(long)}. |
| * |
| * <p> The following code snippet shows how to create and open a session for writing: |
| * <pre class="prettyprint"> |
| * final long sessionId = blobStoreManager.createSession(blobHandle); |
| * try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) { |
| * try (ParcelFileDescriptor pfd = new ParcelFileDescriptor.AutoCloseOutputStream( |
| * session.openWrite(offsetBytes, lengthBytes))) { |
| * writeData(pfd); |
| * } |
| * } |
| * </pre> |
| * |
| * <p> If all the data could not be written in a single attempt, apps can close this session |
| * and re-open it again using the session id obtained via |
| * {@link BlobStoreManager#createSession(BlobHandle)}. Note that the session data is persisted |
| * and can be re-opened for completing the data contribution, even across device reboots. |
| * |
| * <p> After the data is written to the session, it can be committed using |
| * {@link Session#commit(Executor, Consumer)}. Until the session is committed, data written |
| * to the session will not be shared with any app. |
| * |
| * <p class="note"> Once a session is committed using {@link Session#commit(Executor, Consumer)}, |
| * any data written as part of this session is sealed and cannot be modified anymore. |
| * |
| * <p> Before committing the session, apps can indicate which apps are allowed to access the |
| * contributed data using one or more of the following access modes: |
| * <ul> |
| * <li> {@link Session#allowPackageAccess(String, byte[])} which will allow whitelisting |
| * specific packages to access the blobs. |
| * <li> {@link Session#allowSameSignatureAccess()} which will allow only apps which are signed |
| * with the same certificate as the app which contributed the blob to access it. |
| * <li> {@link Session#allowPublicAccess()} which will allow any app on the device to access |
| * the blob. |
| * </ul> |
| * |
| * <p> The following code snippet shows how to specify the access mode and commit the session: |
| * <pre class="prettyprint"> |
| * try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) { |
| * try (ParcelFileDescriptor pfd = new ParcelFileDescriptor.AutoCloseOutputStream( |
| * session.openWrite(offsetBytes, lengthBytes))) { |
| * writeData(pfd); |
| * } |
| * session.allowSameSignatureAccess(); |
| * session.allowPackageAccess(packageName, certificate); |
| * session.commit(executor, callback); |
| * } |
| * </pre> |
| * |
| * <p> Apps that satisfy at least one of the access mode constraints specified by the publisher |
| * of the data blob will be able to access it. |
| * |
| * <p> A data blob published without specifying any of |
| * these access modes will be considered private and only the app that contributed the data |
| * blob will be allowed to access it. This is still useful for overall device system health as |
| * the System can try to keep one copy of data blob on disk when multiple apps contribute the |
| * same data. |
| * |
| * <p class="note"> It is strongly recommended that apps use one of |
| * {@link Session#allowPackageAccess(String, byte[])} or {@link Session#allowSameSignatureAccess()} |
| * when they know, ahead of time, the set of apps they would like to share the blobs with. |
| * {@link Session#allowPublicAccess()} is meant for publicly available data committed from |
| * libraries and SDKs. |
| * |
| * <p> Once a data blob is committed with {@link Session#commit(Executor, Consumer)}, it |
| * can be accessed using {@link BlobStoreManager#openBlob(BlobHandle)}, assuming the caller |
| * satisfies constraints of any of the access modes associated with that data blob. An app may |
| * acquire a lease on a blob with {@link BlobStoreManager#acquireLease(BlobHandle, int)} and |
| * release the lease with {@link BlobStoreManager#releaseLease(BlobHandle)}. A blob will not be |
| * deleted from the system while there is at least one app leasing it. |
| * |
| * <p> The following code snippet shows how to access the data blob: |
| * <pre class="prettyprint"> |
| * try (ParcelFileDescriptor pfd = new ParcelFileDescriptor.AutoCloseInputStream( |
| * blobStoreManager.openBlob(blobHandle)) { |
| * useData(pfd); |
| * } |
| * </pre> |
| */ |
| @SystemService(Context.BLOB_STORE_SERVICE) |
| public class BlobStoreManager { |
| /** @hide */ |
| public static final int COMMIT_RESULT_SUCCESS = 0; |
| /** @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; |
| |
| /** @hide */ |
| public BlobStoreManager(@NonNull Context context, @NonNull IBlobStoreManager service) { |
| mContext = context; |
| mService = service; |
| } |
| |
| /** |
| * Create a new session using the given {@link BlobHandle}, returning a unique id |
| * that represents the session. Once created, the session can be opened |
| * multiple times across multiple device boots. |
| * |
| * <p> The system may automatically destroy sessions that have not been |
| * finalized (either committed or abandoned) within a reasonable period of |
| * time, typically about a week. |
| * |
| * <p> If an app is planning to acquire a lease on this data (using |
| * {@link #acquireLease(BlobHandle, int)} or one of it's other variants) after committing |
| * this data (using {@link Session#commit(Executor, Consumer)}), it is recommended that |
| * the app checks the remaining quota for acquiring a lease first using |
| * {@link #getRemainingLeaseQuotaBytes()} and can skip contributing this data if needed. |
| * |
| * @param blobHandle the {@link BlobHandle} identifier for which a new session |
| * needs to be created. |
| * @return positive, non-zero unique id that represents the created session. |
| * This id remains consistent across device reboots until the |
| * session is finalized. IDs are not reused during a given boot. |
| * |
| * @throws IOException when there is an I/O error while creating the session. |
| * @throws SecurityException when the caller is not allowed to create a session, such |
| * as when called from an Instant app. |
| * @throws IllegalArgumentException when {@code blobHandle} is invalid. |
| * @throws IllegalStateException when a new session could not be created, such as when the |
| * caller is trying to create too many sessions or when the |
| * device is running low on space. |
| */ |
| public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle) |
| throws IOException { |
| try { |
| return mService.createSession(blobHandle, mContext.getOpPackageName()); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Open an existing session to actively perform work. |
| * |
| * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that |
| * represents a particular session. |
| * @return the {@link Session} object corresponding to the {@code sessionId}. |
| * |
| * @throws IOException when there is an I/O error while opening the session. |
| * @throws SecurityException when the caller does not own the session, or |
| * the session does not exist or is invalid. |
| */ |
| public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException { |
| try { |
| return new Session(mService.openSession(sessionId, mContext.getOpPackageName())); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Abandons an existing session and deletes any data that was written to that session so far. |
| * |
| * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that |
| * represents a particular session. |
| * |
| * @throws IOException when there is an I/O error while deleting the session. |
| * @throws SecurityException when the caller does not own the session, or |
| * the session does not exist or is invalid. |
| */ |
| public void abandonSession(@IntRange(from = 1) long sessionId) throws IOException { |
| try { |
| mService.abandonSession(sessionId, mContext.getOpPackageName()); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Opens an existing blob for reading from the blob store managed by the system. |
| * |
| * @param blobHandle the {@link BlobHandle} representing the blob that the caller |
| * wants to access. |
| * @return a {@link ParcelFileDescriptor} that can be used to read the blob content. |
| * |
| * @throws IOException when there is an I/O while opening the blob for read. |
| * @throws IllegalArgumentException when {@code blobHandle} is invalid. |
| * @throws SecurityException when the blob represented by the {@code blobHandle} does not |
| * exist or the caller does not have access to it. |
| */ |
| public @NonNull ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle) |
| throws IOException { |
| try { |
| return mService.openBlob(blobHandle, 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. |
| * |
| * <p> Any active leases will be automatically released when the blob's expiry time |
| * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. |
| * |
| * <p> This lease information is persisted and calling this more than once will result in |
| * latest lease overriding any previous lease. |
| * |
| * <p> When an app acquires a lease on a blob, the System will try to keep this |
| * blob around but note that it can still be deleted if it was requested by the user. |
| * |
| * <p> In case the resource name for the {@code descriptionResId} is modified as part of |
| * an app update, apps should re-acquire the lease with the new resource id. |
| * |
| * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to |
| * acquire a lease for. |
| * @param descriptionResId the resource id for 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 LimitExceededException when a lease could not be acquired, such as when the |
| * caller is trying to acquire leases on too much data. Apps |
| * can avoid this by checking the remaining quota using |
| * {@link #getRemainingLeaseQuotaBytes()} before trying to |
| * acquire a lease. |
| * |
| * @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, null, leaseExpiryTimeMillis, |
| mContext.getOpPackageName()); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| e.maybeRethrow(LimitExceededException.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. |
| * |
| * <p> This is a 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. |
| * |
| * <p> Any active leases will be automatically released when the blob's expiry time |
| * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. |
| * |
| * <p> This lease information is persisted and calling this more than once will result in |
| * latest lease overriding any previous lease. |
| * |
| * <p> When an app acquires a lease on a blob, the System will try to keep this |
| * blob around but note that it can still be deleted if it was requested by the user. |
| * |
| * @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 LimitExceededException when a lease could not be acquired, such as when the |
| * caller is trying to acquire leases on too much data. Apps |
| * can avoid this by checking the remaining quota using |
| * {@link #getRemainingLeaseQuotaBytes()} before trying to |
| * acquire a lease. |
| * |
| * @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); |
| e.maybeRethrow(LimitExceededException.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. |
| * |
| * <p> This is similar to {@link #acquireLease(BlobHandle, int, 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. |
| * |
| * <p> Any active leases will be automatically released when the blob's expiry time |
| * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. |
| * |
| * <p> This lease information is persisted and calling this more than once will result in |
| * latest lease overriding any previous lease. |
| * |
| * <p> When an app acquires a lease on a blob, the System will try to keep this |
| * blob around but note that it can still be deleted if it was requested by the user. |
| * |
| * <p> In case the resource name for the {@code descriptionResId} is modified as part of |
| * an app update, apps should re-acquire the lease with the new resource id. |
| * |
| * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to |
| * acquire a lease for. |
| * @param descriptionResId the resource id for 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 LimitExceededException when a lease could not be acquired, such as when the |
| * caller is trying to acquire leases on too much data. Apps |
| * can avoid this by checking the remaining quota using |
| * {@link #getRemainingLeaseQuotaBytes()} before trying to |
| * acquire a lease. |
| * |
| * @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. |
| * |
| * <p> This is a 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. |
| * |
| * <p> 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. |
| * |
| * <p> Any active leases will be automatically released when the blob's expiry time |
| * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed. |
| * |
| * <p> This lease information is persisted and calling this more than once will result in |
| * latest lease overriding any previous lease. |
| * |
| * <p> When an app acquires a lease on a blob, the System will try to keep this |
| * blob around but note that it can still be deleted if it was requested by the user. |
| * |
| * @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 LimitExceededException when a lease could not be acquired, such as when the |
| * caller is trying to acquire leases on too much data. Apps |
| * can avoid this by checking the remaining quota using |
| * {@link #getRemainingLeaseQuotaBytes()} before trying to |
| * acquire a lease. |
| * |
| * @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 any active lease to the blob represented by {@code blobHandle} which is |
| * currently held by the caller. |
| * |
| * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to |
| * release the lease for. |
| * |
| * @throws IOException when there is an I/O error while releasing the release 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. |
| */ |
| public void releaseLease(@NonNull BlobHandle blobHandle) throws IOException { |
| try { |
| mService.releaseLease(blobHandle, mContext.getOpPackageName()); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Return the remaining quota size for acquiring a lease (in bytes) which indicates the |
| * remaining amount of data that an app can acquire a lease on before the System starts |
| * rejecting lease requests. |
| * |
| * If an app wants to acquire a lease on a blob but the remaining quota size is not sufficient, |
| * then it can try releasing leases on any older blobs which are not needed anymore. |
| * |
| * @return the remaining quota size for acquiring a lease. |
| */ |
| public @IntRange(from = 0) long getRemainingLeaseQuotaBytes() { |
| try { |
| return mService.getRemainingLeaseQuotaBytes(mContext.getOpPackageName()); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Wait until any pending tasks (like persisting data to disk) have finished. |
| * |
| * @hide |
| */ |
| @TestApi |
| public void waitForIdle(long timeoutMillis) throws InterruptedException, TimeoutException { |
| try { |
| final CountDownLatch countDownLatch = new CountDownLatch(1); |
| mService.waitForIdle(new RemoteCallback((result) -> countDownLatch.countDown())); |
| if (!countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { |
| throw new TimeoutException("Timed out waiting for service to become idle"); |
| } |
| } catch (ParcelableException e) { |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** @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(); |
| } |
| } |
| |
| /** |
| * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that |
| * the calling app currently has a lease on. |
| * |
| * @return a list of {@link BlobHandle BlobHandles} that the caller has a lease on. |
| */ |
| @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. |
| * |
| * <p> Clients that want to contribute a blob need to first create a {@link Session} using |
| * {@link #createSession(BlobHandle)} and once the session is created, clients can open and |
| * close this session multiple times using {@link #openSession(long)} and |
| * {@link Session#close()} before committing it using |
| * {@link Session#commit(Executor, Consumer)}, at which point system will take |
| * ownership of the blob and the client can no longer make any modifications to the blob's |
| * content. |
| */ |
| public static class Session implements Closeable { |
| private final IBlobStoreSession mSession; |
| |
| private Session(@NonNull IBlobStoreSession session) { |
| mSession = session; |
| } |
| |
| /** |
| * Opens a file descriptor to write a blob into the session. |
| * |
| * <p> The returned file descriptor will start writing data at the requested offset |
| * in the underlying file, which can be used to resume a partially |
| * written file. If a valid file length is specified, the system will |
| * preallocate the underlying disk space to optimize placement on disk. |
| * It is strongly recommended to provide a valid file length when known. |
| * |
| * @param offsetBytes offset into the file to begin writing at, or 0 to |
| * start at the beginning of the file. |
| * @param lengthBytes total size of the file being written, used to |
| * preallocate the underlying disk space, or -1 if unknown. |
| * The system may clear various caches as needed to allocate |
| * this space. |
| * |
| * @return a {@link ParcelFileDescriptor} for writing to the blob file. |
| * |
| * @throws IOException when there is an I/O error while opening the file to write. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to write to the file after it is |
| * abandoned (using {@link #abandon()}) |
| * or committed (using {@link #commit}) |
| * or closed (using {@link #close()}). |
| */ |
| public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, |
| @BytesLong long lengthBytes) throws IOException { |
| try { |
| final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes); |
| pfd.seekTo(offsetBytes); |
| return pfd; |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Opens a file descriptor to read the blob content already written into this session. |
| * |
| * @return a {@link ParcelFileDescriptor} for reading from the blob file. |
| * |
| * @throws IOException when there is an I/O error while opening the file to read. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to read the file after it is |
| * abandoned (using {@link #abandon()}) |
| * or closed (using {@link #close()}). |
| */ |
| public @NonNull ParcelFileDescriptor openRead() throws IOException { |
| try { |
| return mSession.openRead(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Gets the size of the blob file that was written to the session so far. |
| * |
| * @return the size of the blob file so far. |
| * |
| * @throws IOException when there is an I/O error while opening the file to read. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to get the file size after it is |
| * abandoned (using {@link #abandon()}) |
| * or closed (using {@link #close()}). |
| */ |
| public @BytesLong long getSize() throws IOException { |
| try { |
| return mSession.getSize(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Close this session. It can be re-opened for writing/reading if it has not been |
| * abandoned (using {@link #abandon}) or committed (using {@link #commit}). |
| * |
| * @throws IOException when there is an I/O error while closing the session. |
| * @throws SecurityException when the caller is not the owner of the session. |
| */ |
| public void close() throws IOException { |
| try { |
| mSession.close(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Abandon this session and delete any data that was written to this session so far. |
| * |
| * @throws IOException when there is an I/O error while abandoning the session. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to abandon a session which was |
| * already finalized. |
| */ |
| public void abandon() throws IOException { |
| try { |
| mSession.abandon(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Allow {@code packageName} with a particular signing certificate to access this blob |
| * data once it is committed using a {@link BlobHandle} representing the blob. |
| * |
| * <p> This needs to be called before committing the blob using |
| * {@link #commit(Executor, Consumer)}. |
| * |
| * @param packageName the name of the package which should be allowed to access the blob. |
| * @param certificate the input bytes representing a certificate of type |
| * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. |
| * |
| * @throws IOException when there is an I/O error while changing the access. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to change access for a blob which is |
| * already committed. |
| */ |
| public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) |
| throws IOException { |
| try { |
| mSession.allowPackageAccess(packageName, certificate); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if access has been allowed for a {@code packageName} using either |
| * {@link #allowPackageAccess(String, byte[])}. |
| * Otherwise, {@code false}. |
| * |
| * @param packageName the name of the package to check the access for. |
| * @param certificate the input bytes representing a certificate of type |
| * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. |
| * |
| * @throws IOException when there is an I/O error while getting the access type. |
| * @throws IllegalStateException when the caller tries to get access type from a session |
| * which is closed or abandoned. |
| */ |
| public boolean isPackageAccessAllowed(@NonNull String packageName, |
| @NonNull byte[] certificate) throws IOException { |
| try { |
| return mSession.isPackageAccessAllowed(packageName, certificate); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Allow packages which are signed with the same certificate as the caller to access this |
| * blob data once it is committed using a {@link BlobHandle} representing the blob. |
| * |
| * <p> This needs to be called before committing the blob using |
| * {@link #commit(Executor, Consumer)}. |
| * |
| * @throws IOException when there is an I/O error while changing the access. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to change access for a blob which is |
| * already committed. |
| */ |
| public void allowSameSignatureAccess() throws IOException { |
| try { |
| mSession.allowSameSignatureAccess(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if access has been allowed for packages signed with the same |
| * certificate as the caller by using {@link #allowSameSignatureAccess()}. |
| * Otherwise, {@code false}. |
| * |
| * @throws IOException when there is an I/O error while getting the access type. |
| * @throws IllegalStateException when the caller tries to get access type from a session |
| * which is closed or abandoned. |
| */ |
| public boolean isSameSignatureAccessAllowed() throws IOException { |
| try { |
| return mSession.isSameSignatureAccessAllowed(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Allow any app on the device to access this blob data once it is committed using |
| * a {@link BlobHandle} representing the blob. |
| * |
| * <p><strong>Note:</strong> This is only meant to be used from libraries and SDKs where |
| * the apps which we want to allow access is not known ahead of time. |
| * If a blob is being committed to be shared with a particular set of apps, it is highly |
| * recommended to use {@link #allowPackageAccess(String, byte[])} instead. |
| * |
| * <p> This needs to be called before committing the blob using |
| * {@link #commit(Executor, Consumer)}. |
| * |
| * @throws IOException when there is an I/O error while changing the access. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalStateException when the caller tries to change access for a blob which is |
| * already committed. |
| */ |
| public void allowPublicAccess() throws IOException { |
| try { |
| mSession.allowPublicAccess(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Returns {@code true} if public access has been allowed by using |
| * {@link #allowPublicAccess()}. Otherwise, {@code false}. |
| * |
| * @throws IOException when there is an I/O error while getting the access type. |
| * @throws IllegalStateException when the caller tries to get access type from a session |
| * which is closed or abandoned. |
| */ |
| public boolean isPublicAccessAllowed() throws IOException { |
| try { |
| return mSession.isPublicAccessAllowed(); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| |
| /** |
| * Commit the file that was written so far to this session to the blob store maintained by |
| * the system. |
| * |
| * <p> Once this method is called, the session is finalized and no additional |
| * mutations can be performed on the session. If the device reboots |
| * before the session has been finalized, you may commit the session again. |
| * |
| * <p> Note that this commit operation will fail if the hash of the data written so far |
| * to this session does not match with the one used for |
| * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)} BlobHandle} |
| * associated with this session. |
| * |
| * <p> Committing the same data more than once will result in replacing the corresponding |
| * access mode (via calling one of {@link #allowPackageAccess(String, byte[])}, |
| * {@link #allowSameSignatureAccess()}, etc) with the latest one. |
| * |
| * @param executor the executor on which result callback will be invoked. |
| * @param resultCallback a callback to receive the commit result. when the result is |
| * {@code 0}, it indicates success. Otherwise, failure. |
| * |
| * @throws IOException when there is an I/O error while committing the session. |
| * @throws SecurityException when the caller is not the owner of the session. |
| * @throws IllegalArgumentException when the passed parameters are not valid. |
| * @throws IllegalStateException when the caller tries to commit a session which was |
| * already finalized. |
| */ |
| public void commit(@NonNull @CallbackExecutor Executor executor, |
| @NonNull Consumer<Integer> resultCallback) throws IOException { |
| try { |
| mSession.commit(new IBlobCommitCallback.Stub() { |
| public void onResult(int result) { |
| executor.execute(PooledLambda.obtainRunnable( |
| Consumer::accept, resultCallback, result)); |
| } |
| }); |
| } catch (ParcelableException e) { |
| e.maybeRethrow(IOException.class); |
| throw new RuntimeException(e); |
| } catch (RemoteException e) { |
| throw e.rethrowFromSystemServer(); |
| } |
| } |
| } |
| } |