1 /*
2  * Copyright 2019 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package android.app.blob;
17 
18 import android.annotation.BytesLong;
19 import android.annotation.CallbackExecutor;
20 import android.annotation.CurrentTimeMillisLong;
21 import android.annotation.IdRes;
22 import android.annotation.IntRange;
23 import android.annotation.NonNull;
24 import android.annotation.Nullable;
25 import android.annotation.SystemService;
26 import android.annotation.TestApi;
27 import android.content.Context;
28 import android.os.LimitExceededException;
29 import android.os.ParcelFileDescriptor;
30 import android.os.ParcelableException;
31 import android.os.RemoteCallback;
32 import android.os.RemoteException;
33 import android.os.UserHandle;
34 
35 import com.android.internal.util.function.pooled.PooledLambda;
36 
37 import java.io.Closeable;
38 import java.io.IOException;
39 import java.util.List;
40 import java.util.concurrent.CountDownLatch;
41 import java.util.concurrent.Executor;
42 import java.util.concurrent.TimeUnit;
43 import java.util.concurrent.TimeoutException;
44 import java.util.function.Consumer;
45 
46 /**
47  * This class provides access to the blob store managed by the system.
48  *
49  * <p> Apps can publish and access a data blob using a {@link BlobHandle} object which can
50  * be created with {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}.
51  * This {@link BlobHandle} object encapsulates the following pieces of information used for
52  * identifying the blobs:
53  * <ul>
54  *     <li> {@link BlobHandle#getSha256Digest()}
55  *     <li> {@link BlobHandle#getLabel()}
56  *     <li> {@link BlobHandle#getExpiryTimeMillis()}
57  *     <li> {@link BlobHandle#getTag()}
58  * </ul>
59  * For two {@link BlobHandle} objects to be considered identical, all these pieces of information
60  * must be equal.
61  *
62  * <p> For contributing a new data blob, an app needs to create a session using
63  * {@link BlobStoreManager#createSession(BlobHandle)} and then open this session for writing using
64  * {@link BlobStoreManager#openSession(long)}.
65  *
66  * <p> The following code snippet shows how to create and open a session for writing:
67  * <pre class="prettyprint">
68  *     final long sessionId = blobStoreManager.createSession(blobHandle);
69  *     try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) {
70  *         try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
71  *                 session.openWrite(offsetBytes, lengthBytes))) {
72  *             writeData(out);
73  *         }
74  *     }
75  * </pre>
76  *
77  * <p> If all the data could not be written in a single attempt, apps can close this session
78  * and re-open it again using the session id obtained via
79  * {@link BlobStoreManager#createSession(BlobHandle)}. Note that the session data is persisted
80  * and can be re-opened for completing the data contribution, even across device reboots.
81  *
82  * <p> After the data is written to the session, it can be committed using
83  * {@link Session#commit(Executor, Consumer)}. Until the session is committed, data written
84  * to the session will not be shared with any app.
85  *
86  * <p class="note"> Once a session is committed using {@link Session#commit(Executor, Consumer)},
87  * any data written as part of this session is sealed and cannot be modified anymore.
88  *
89  * <p> Before committing the session, apps can indicate which apps are allowed to access the
90  * contributed data using one or more of the following access modes:
91  * <ul>
92  *     <li> {@link Session#allowPackageAccess(String, byte[])} which will allow specific packages
93  *          to access the blobs.
94  *     <li> {@link Session#allowSameSignatureAccess()} which will allow only apps which are signed
95  *          with the same certificate as the app which contributed the blob to access it.
96  *     <li> {@link Session#allowPublicAccess()} which will allow any app on the device to access
97  *          the blob.
98  * </ul>
99  *
100  * <p> The following code snippet shows how to specify the access mode and commit the session:
101  * <pre class="prettyprint">
102  *     try (BlobStoreManager.Session session = blobStoreManager.openSession(sessionId)) {
103  *         try (OutputStream out = new ParcelFileDescriptor.AutoCloseOutputStream(
104  *                 session.openWrite(offsetBytes, lengthBytes))) {
105  *             writeData(out);
106  *         }
107  *         session.allowSameSignatureAccess();
108  *         session.allowPackageAccess(packageName, certificate);
109  *         session.commit(executor, callback);
110  *     }
111  * </pre>
112  *
113  * <p> Apps that satisfy at least one of the access mode constraints specified by the publisher
114  * of the data blob will be able to access it.
115  *
116  * <p> A data blob published without specifying any of
117  * these access modes will be considered private and only the app that contributed the data
118  * blob will be allowed to access it. This is still useful for overall device system health as
119  * the System can try to keep one copy of data blob on disk when multiple apps contribute the
120  * same data.
121  *
122  * <p class="note"> It is strongly recommended that apps use one of
123  * {@link Session#allowPackageAccess(String, byte[])} or {@link Session#allowSameSignatureAccess()}
124  * when they know, ahead of time, the set of apps they would like to share the blobs with.
125  * {@link Session#allowPublicAccess()} is meant for publicly available data committed from
126  * libraries and SDKs.
127  *
128  * <p> Once a data blob is committed with {@link Session#commit(Executor, Consumer)}, it
129  * can be accessed using {@link BlobStoreManager#openBlob(BlobHandle)}, assuming the caller
130  * satisfies constraints of any of the access modes associated with that data blob. An app may
131  * acquire a lease on a blob with {@link BlobStoreManager#acquireLease(BlobHandle, int)} and
132  * release the lease with {@link BlobStoreManager#releaseLease(BlobHandle)}. A blob will not be
133  * deleted from the system while there is at least one app leasing it.
134  *
135  * <p> The following code snippet shows how to access the data blob:
136  * <pre class="prettyprint">
137  *     try (InputStream in = new ParcelFileDescriptor.AutoCloseInputStream(
138  *             blobStoreManager.openBlob(blobHandle)) {
139  *         useData(in);
140  *     }
141  * </pre>
142  */
143 @SystemService(Context.BLOB_STORE_SERVICE)
144 public class BlobStoreManager {
145     /** @hide */
146     public static final int COMMIT_RESULT_SUCCESS = 0;
147     /** @hide */
148     public static final int COMMIT_RESULT_ERROR = 1;
149 
150     /** @hide */
151     public static final int INVALID_RES_ID = -1;
152 
153     private final Context mContext;
154     private final IBlobStoreManager mService;
155 
156     /** @hide */
BlobStoreManager(@onNull Context context, @NonNull IBlobStoreManager service)157     public BlobStoreManager(@NonNull Context context, @NonNull IBlobStoreManager service) {
158         mContext = context;
159         mService = service;
160     }
161 
162     /**
163      * Create a new session using the given {@link BlobHandle}, returning a unique id
164      * that represents the session. Once created, the session can be opened
165      * multiple times across multiple device boots.
166      *
167      * <p> The system may automatically destroy sessions that have not been
168      * finalized (either committed or abandoned) within a reasonable period of
169      * time, typically about a week.
170      *
171      * <p> If an app is planning to acquire a lease on this data (using
172      * {@link #acquireLease(BlobHandle, int)} or one of it's other variants) after committing
173      * this data (using {@link Session#commit(Executor, Consumer)}), it is recommended that
174      * the app checks the remaining quota for acquiring a lease first using
175      * {@link #getRemainingLeaseQuotaBytes()} and can skip contributing this data if needed.
176      *
177      * @param blobHandle the {@link BlobHandle} identifier for which a new session
178      *                   needs to be created.
179      * @return positive, non-zero unique id that represents the created session.
180      *         This id remains consistent across device reboots until the
181      *         session is finalized. IDs are not reused during a given boot.
182      *
183      * @throws IOException when there is an I/O error while creating the session.
184      * @throws SecurityException when the caller is not allowed to create a session, such
185      *                           as when called from an Instant app.
186      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
187      * @throws LimitExceededException when a new session could not be created, such as when the
188      *                                caller is trying to create too many sessions.
189      */
createSession(@onNull BlobHandle blobHandle)190     public @IntRange(from = 1) long createSession(@NonNull BlobHandle blobHandle)
191             throws IOException {
192         try {
193             return mService.createSession(blobHandle, mContext.getOpPackageName());
194         } catch (ParcelableException e) {
195             e.maybeRethrow(IOException.class);
196             e.maybeRethrow(LimitExceededException.class);
197             throw new RuntimeException(e);
198         } catch (RemoteException e) {
199             throw e.rethrowFromSystemServer();
200         }
201     }
202 
203     /**
204      * Open an existing session to actively perform work.
205      *
206      * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
207      *                  represents a particular session.
208      * @return the {@link Session} object corresponding to the {@code sessionId}.
209      *
210      * @throws IOException when there is an I/O error while opening the session.
211      * @throws SecurityException when the caller does not own the session, or
212      *                           the session does not exist or is invalid.
213      */
openSession(@ntRangefrom = 1) long sessionId)214     public @NonNull Session openSession(@IntRange(from = 1) long sessionId) throws IOException {
215         try {
216             return new Session(mService.openSession(sessionId, mContext.getOpPackageName()));
217         } catch (ParcelableException e) {
218             e.maybeRethrow(IOException.class);
219             throw new RuntimeException(e);
220         } catch (RemoteException e) {
221             throw e.rethrowFromSystemServer();
222         }
223     }
224 
225     /**
226      * Abandons an existing session and deletes any data that was written to that session so far.
227      *
228      * @param sessionId a unique id obtained via {@link #createSession(BlobHandle)} that
229      *                  represents a particular session.
230      *
231      * @throws IOException when there is an I/O error while deleting the session.
232      * @throws SecurityException when the caller does not own the session, or
233      *                           the session does not exist or is invalid.
234      */
abandonSession(@ntRangefrom = 1) long sessionId)235     public void abandonSession(@IntRange(from = 1) long sessionId) throws IOException {
236         try {
237             mService.abandonSession(sessionId, mContext.getOpPackageName());
238         } catch (ParcelableException e) {
239             e.maybeRethrow(IOException.class);
240             throw new RuntimeException(e);
241         } catch (RemoteException e) {
242             throw e.rethrowFromSystemServer();
243         }
244     }
245 
246     /**
247      * Opens an existing blob for reading from the blob store managed by the system.
248      *
249      * @param blobHandle the {@link BlobHandle} representing the blob that the caller
250      *                   wants to access.
251      * @return a {@link ParcelFileDescriptor} that can be used to read the blob content.
252      *
253      * @throws IOException when there is an I/O while opening the blob for read.
254      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
255      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
256      *                           exist or the caller does not have access to it.
257      */
openBlob(@onNull BlobHandle blobHandle)258     public @NonNull ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle)
259             throws IOException {
260         try {
261             return mService.openBlob(blobHandle, mContext.getOpPackageName());
262         } catch (ParcelableException e) {
263             e.maybeRethrow(IOException.class);
264             throw new RuntimeException(e);
265         } catch (RemoteException e) {
266             throw e.rethrowFromSystemServer();
267         }
268     }
269 
270     /**
271      * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
272      * system that the caller wants the blob to be kept around.
273      *
274      * <p> Any active leases will be automatically released when the blob's expiry time
275      * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
276      *
277      * <p> This lease information is persisted and calling this more than once will result in
278      * latest lease overriding any previous lease.
279      *
280      * <p> When an app acquires a lease on a blob, the System will try to keep this
281      * blob around but note that it can still be deleted if it was requested by the user.
282      *
283      * <p> In case the resource name for the {@code descriptionResId} is modified as part of
284      * an app update, apps should re-acquire the lease with the new resource id.
285      *
286      * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
287      *                   acquire a lease for.
288      * @param descriptionResId the resource id for a short description string that can be surfaced
289      *                         to the user explaining what the blob is used for.
290      * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
291      *                              automatically released, in {@link System#currentTimeMillis()}
292      *                              timebase. If its value is {@code 0}, then the behavior of this
293      *                              API is identical to {@link #acquireLease(BlobHandle, int)}
294      *                              where clients have to explicitly call
295      *                              {@link #releaseLease(BlobHandle)} when they don't
296      *                              need the blob anymore.
297      *
298      * @throws IOException when there is an I/O error while acquiring a lease to the blob.
299      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
300      *                           exist or the caller does not have access to it.
301      * @throws IllegalArgumentException when {@code blobHandle} is invalid or
302      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
303      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
304      * @throws LimitExceededException when a lease could not be acquired, such as when the
305      *                                caller is trying to acquire too many leases or acquire
306      *                                leases on too much data. Apps can avoid this by checking
307      *                                the remaining quota using
308      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
309      *                                acquire a lease.
310      *
311      * @see #acquireLease(BlobHandle, int)
312      * @see #acquireLease(BlobHandle, CharSequence)
313      */
acquireLease(@onNull BlobHandle blobHandle, @IdRes int descriptionResId, @CurrentTimeMillisLong long leaseExpiryTimeMillis)314     public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
315             @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
316         try {
317             mService.acquireLease(blobHandle, descriptionResId, null, leaseExpiryTimeMillis,
318                     mContext.getOpPackageName());
319         } catch (ParcelableException e) {
320             e.maybeRethrow(IOException.class);
321             e.maybeRethrow(LimitExceededException.class);
322             throw new RuntimeException(e);
323         } catch (RemoteException e) {
324             throw e.rethrowFromSystemServer();
325         }
326     }
327 
328     /**
329      * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
330      * system that the caller wants the blob to be kept around.
331      *
332      * <p> This is a variant of {@link #acquireLease(BlobHandle, int, long)} taking a
333      * {@link CharSequence} for {@code description}. It is highly recommended that callers only
334      * use this when a valid resource ID for {@code description} could not be provided. Otherwise,
335      * apps should prefer using {@link #acquireLease(BlobHandle, int)} which will allow
336      * {@code description} to be localized.
337      *
338      * <p> Any active leases will be automatically released when the blob's expiry time
339      * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
340      *
341      * <p> This lease information is persisted and calling this more than once will result in
342      * latest lease overriding any previous lease.
343      *
344      * <p> When an app acquires a lease on a blob, the System will try to keep this
345      * blob around but note that it can still be deleted if it was requested by the user.
346      *
347      * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
348      *                   acquire a lease for.
349      * @param description a short description string that can be surfaced
350      *                    to the user explaining what the blob is used for. It is recommended to
351      *                    keep this description brief. This may be truncated and ellipsized
352      *                    if it is too long to be displayed to the user.
353      * @param leaseExpiryTimeMillis the time in milliseconds after which the lease can be
354      *                              automatically released, in {@link System#currentTimeMillis()}
355      *                              timebase. If its value is {@code 0}, then the behavior of this
356      *                              API is identical to {@link #acquireLease(BlobHandle, int)}
357      *                              where clients have to explicitly call
358      *                              {@link #releaseLease(BlobHandle)} when they don't
359      *                              need the blob anymore.
360      *
361      * @throws IOException when there is an I/O error while acquiring a lease to the blob.
362      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
363      *                           exist or the caller does not have access to it.
364      * @throws IllegalArgumentException when {@code blobHandle} is invalid or
365      *                                  if the {@code leaseExpiryTimeMillis} is greater than the
366      *                                  {@link BlobHandle#getExpiryTimeMillis()}.
367      * @throws LimitExceededException when a lease could not be acquired, such as when the
368      *                                caller is trying to acquire too many leases or acquire
369      *                                leases on too much data. Apps can avoid this by checking
370      *                                the remaining quota using
371      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
372      *                                acquire a lease.
373      *
374      * @see #acquireLease(BlobHandle, int, long)
375      * @see #acquireLease(BlobHandle, CharSequence)
376      */
acquireLease(@onNull BlobHandle blobHandle, @NonNull CharSequence description, @CurrentTimeMillisLong long leaseExpiryTimeMillis)377     public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description,
378             @CurrentTimeMillisLong long leaseExpiryTimeMillis) throws IOException {
379         try {
380             mService.acquireLease(blobHandle, INVALID_RES_ID, description, leaseExpiryTimeMillis,
381                     mContext.getOpPackageName());
382         } catch (ParcelableException e) {
383             e.maybeRethrow(IOException.class);
384             e.maybeRethrow(LimitExceededException.class);
385             throw new RuntimeException(e);
386         } catch (RemoteException e) {
387             throw e.rethrowFromSystemServer();
388         }
389     }
390 
391     /**
392      * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
393      * system that the caller wants the blob to be kept around.
394      *
395      * <p> This is similar to {@link #acquireLease(BlobHandle, int, long)} except clients don't
396      * have to specify the lease expiry time upfront using this API and need to explicitly
397      * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
398      * a blob around.
399      *
400      * <p> Any active leases will be automatically released when the blob's expiry time
401      * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
402      *
403      * <p> This lease information is persisted and calling this more than once will result in
404      * latest lease overriding any previous lease.
405      *
406      * <p> When an app acquires a lease on a blob, the System will try to keep this
407      * blob around but note that it can still be deleted if it was requested by the user.
408      *
409      * <p> In case the resource name for the {@code descriptionResId} is modified as part of
410      * an app update, apps should re-acquire the lease with the new resource id.
411      *
412      * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
413      *                   acquire a lease for.
414      * @param descriptionResId the resource id for a short description string that can be surfaced
415      *                         to the user explaining what the blob is used for.
416      *
417      * @throws IOException when there is an I/O error while acquiring a lease to the blob.
418      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
419      *                           exist or the caller does not have access to it.
420      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
421      * @throws LimitExceededException when a lease could not be acquired, such as when the
422      *                                caller is trying to acquire too many leases or acquire
423      *                                leases on too much data. Apps can avoid this by checking
424      *                                the remaining quota using
425      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
426      *                                acquire a lease.
427      *
428      * @see #acquireLease(BlobHandle, int, long)
429      * @see #acquireLease(BlobHandle, CharSequence, long)
430      */
acquireLease(@onNull BlobHandle blobHandle, @IdRes int descriptionResId)431     public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId)
432             throws IOException {
433         acquireLease(blobHandle, descriptionResId, 0);
434     }
435 
436     /**
437      * Acquire a lease to the blob represented by {@code blobHandle}. This lease indicates to the
438      * system that the caller wants the blob to be kept around.
439      *
440      * <p> This is a variant of {@link #acquireLease(BlobHandle, int)} taking a {@link CharSequence}
441      * for {@code description}. It is highly recommended that callers only use this when a valid
442      * resource ID for {@code description} could not be provided. Otherwise, apps should prefer
443      * using {@link #acquireLease(BlobHandle, int)} which will allow {@code description} to be
444      * localized.
445      *
446      * <p> This is similar to {@link #acquireLease(BlobHandle, CharSequence, long)} except clients
447      * don't have to specify the lease expiry time upfront using this API and need to explicitly
448      * release the lease using {@link #releaseLease(BlobHandle)} when they no longer like to keep
449      * a blob around.
450      *
451      * <p> Any active leases will be automatically released when the blob's expiry time
452      * ({@link BlobHandle#getExpiryTimeMillis()}) is elapsed.
453      *
454      * <p> This lease information is persisted and calling this more than once will result in
455      * latest lease overriding any previous lease.
456      *
457      * <p> When an app acquires a lease on a blob, the System will try to keep this
458      * blob around but note that it can still be deleted if it was requested by the user.
459      *
460      * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
461      *                   acquire a lease for.
462      * @param description a short description string that can be surfaced
463      *                    to the user explaining what the blob is used for. It is recommended to
464      *                    keep this description brief. This may be truncated and
465      *                    ellipsized if it is too long to be displayed to the user.
466      *
467      * @throws IOException when there is an I/O error while acquiring a lease to the blob.
468      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
469      *                           exist or the caller does not have access to it.
470      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
471      * @throws LimitExceededException when a lease could not be acquired, such as when the
472      *                                caller is trying to acquire too many leases or acquire
473      *                                leases on too much data. Apps can avoid this by checking
474      *                                the remaining quota using
475      *                                {@link #getRemainingLeaseQuotaBytes()} before trying to
476      *                                acquire a lease.
477      *
478      * @see #acquireLease(BlobHandle, int)
479      * @see #acquireLease(BlobHandle, CharSequence, long)
480      */
acquireLease(@onNull BlobHandle blobHandle, @NonNull CharSequence description)481     public void acquireLease(@NonNull BlobHandle blobHandle, @NonNull CharSequence description)
482             throws IOException {
483         acquireLease(blobHandle, description, 0);
484     }
485 
486     /**
487      * Release any active lease to the blob represented by {@code blobHandle} which is
488      * currently held by the caller.
489      *
490      * @param blobHandle the {@link BlobHandle} representing the blob that the caller wants to
491      *                   release the lease for.
492      *
493      * @throws IOException when there is an I/O error while releasing the release to the blob.
494      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
495      *                           exist or the caller does not have access to it.
496      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
497      */
releaseLease(@onNull BlobHandle blobHandle)498     public void releaseLease(@NonNull BlobHandle blobHandle) throws IOException {
499         try {
500             mService.releaseLease(blobHandle, mContext.getOpPackageName());
501         } catch (ParcelableException e) {
502             e.maybeRethrow(IOException.class);
503             throw new RuntimeException(e);
504         } catch (RemoteException e) {
505             throw e.rethrowFromSystemServer();
506         }
507     }
508 
509     /**
510      * Release all the leases which are currently held by the caller.
511      *
512      * @hide
513      */
releaseAllLeases()514     public void releaseAllLeases() throws Exception {
515         try {
516             mService.releaseAllLeases(mContext.getOpPackageName());
517         } catch (ParcelableException e) {
518             e.maybeRethrow(IOException.class);
519             throw new RuntimeException(e);
520         } catch (RemoteException e) {
521             throw e.rethrowFromSystemServer();
522         }
523     }
524 
525     /**
526      * Return the remaining quota size for acquiring a lease (in bytes) which indicates the
527      * remaining amount of data that an app can acquire a lease on before the System starts
528      * rejecting lease requests.
529      *
530      * If an app wants to acquire a lease on a blob but the remaining quota size is not sufficient,
531      * then it can try releasing leases on any older blobs which are not needed anymore.
532      *
533      * @return the remaining quota size for acquiring a lease.
534      */
getRemainingLeaseQuotaBytes()535     public @IntRange(from = 0) long getRemainingLeaseQuotaBytes() {
536         try {
537             return mService.getRemainingLeaseQuotaBytes(mContext.getOpPackageName());
538         } catch (RemoteException e) {
539             throw e.rethrowFromSystemServer();
540         }
541     }
542 
543     /**
544      * Wait until any pending tasks (like persisting data to disk) have finished.
545      *
546      * @hide
547      */
548     @TestApi
waitForIdle(long timeoutMillis)549     public void waitForIdle(long timeoutMillis) throws InterruptedException, TimeoutException {
550         try {
551             final CountDownLatch countDownLatch = new CountDownLatch(1);
552             mService.waitForIdle(new RemoteCallback((result) -> countDownLatch.countDown()));
553             if (!countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) {
554                 throw new TimeoutException("Timed out waiting for service to become idle");
555             }
556         } catch (ParcelableException e) {
557             throw new RuntimeException(e);
558         } catch (RemoteException e) {
559             throw e.rethrowFromSystemServer();
560         }
561     }
562 
563     /** @hide */
564     @NonNull
queryBlobsForUser(@onNull UserHandle user)565     public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException {
566         try {
567             return mService.queryBlobsForUser(user.getIdentifier());
568         } catch (ParcelableException e) {
569             e.maybeRethrow(IOException.class);
570             throw new RuntimeException(e);
571         } catch (RemoteException e) {
572             throw e.rethrowFromSystemServer();
573         }
574     }
575 
576     /** @hide */
deleteBlob(@onNull BlobInfo blobInfo)577     public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException {
578         try {
579             mService.deleteBlob(blobInfo.getId());
580         } catch (ParcelableException e) {
581             e.maybeRethrow(IOException.class);
582             throw new RuntimeException(e);
583         } catch (RemoteException e) {
584             throw e.rethrowFromSystemServer();
585         }
586     }
587 
588     /**
589      * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that
590      * the calling app currently has a lease on.
591      *
592      * @return a list of {@link BlobHandle BlobHandles} that the caller has a lease on.
593      */
594     @NonNull
getLeasedBlobs()595     public List<BlobHandle> getLeasedBlobs() throws IOException {
596         try {
597             return mService.getLeasedBlobs(mContext.getOpPackageName());
598         } catch (ParcelableException e) {
599             e.maybeRethrow(IOException.class);
600             throw new RuntimeException(e);
601         } catch (RemoteException e) {
602             throw e.rethrowFromSystemServer();
603         }
604     }
605 
606     /**
607      * Return {@link LeaseInfo} representing a lease acquired using
608      * {@link #acquireLease(BlobHandle, int)} or one of it's other variants,
609      * or {@code null} if there is no lease acquired.
610      *
611      * @throws SecurityException when the blob represented by the {@code blobHandle} does not
612      *                           exist or the caller does not have access to it.
613      * @throws IllegalArgumentException when {@code blobHandle} is invalid.
614      *
615      * @hide
616      */
617     @TestApi
618     @Nullable
getLeaseInfo(@onNull BlobHandle blobHandle)619     public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException {
620         try {
621             return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName());
622         } catch (ParcelableException e) {
623             e.maybeRethrow(IOException.class);
624             throw new RuntimeException(e);
625         } catch (RemoteException e) {
626             throw e.rethrowFromSystemServer();
627         }
628     }
629 
630     /**
631      * Represents an ongoing session of a blob's contribution to the blob store managed by the
632      * system.
633      *
634      * <p> Clients that want to contribute a blob need to first create a {@link Session} using
635      * {@link #createSession(BlobHandle)} and once the session is created, clients can open and
636      * close this session multiple times using {@link #openSession(long)} and
637      * {@link Session#close()} before committing it using
638      * {@link Session#commit(Executor, Consumer)}, at which point system will take
639      * ownership of the blob and the client can no longer make any modifications to the blob's
640      * content.
641      */
642     public static class Session implements Closeable {
643         private final IBlobStoreSession mSession;
644 
Session(@onNull IBlobStoreSession session)645         private Session(@NonNull IBlobStoreSession session) {
646             mSession = session;
647         }
648 
649         /**
650          * Opens a file descriptor to write a blob into the session.
651          *
652          * <p> The returned file descriptor will start writing data at the requested offset
653          * in the underlying file, which can be used to resume a partially
654          * written file. If a valid file length is specified, the system will
655          * preallocate the underlying disk space to optimize placement on disk.
656          * It is strongly recommended to provide a valid file length when known.
657          *
658          * @param offsetBytes offset into the file to begin writing at, or 0 to
659          *                    start at the beginning of the file.
660          * @param lengthBytes total size of the file being written, used to
661          *                    preallocate the underlying disk space, or -1 if unknown.
662          *                    The system may clear various caches as needed to allocate
663          *                    this space.
664          *
665          * @return a {@link ParcelFileDescriptor} for writing to the blob file.
666          *
667          * @throws IOException when there is an I/O error while opening the file to write.
668          * @throws SecurityException when the caller is not the owner of the session.
669          * @throws IllegalStateException when the caller tries to write to the file after it is
670          *                               abandoned (using {@link #abandon()})
671          *                               or committed (using {@link #commit})
672          *                               or closed (using {@link #close()}).
673          */
openWrite(@ytesLong long offsetBytes, @BytesLong long lengthBytes)674         public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes,
675                 @BytesLong long lengthBytes) throws IOException {
676             try {
677                 final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes);
678                 pfd.seekTo(offsetBytes);
679                 return pfd;
680             } catch (ParcelableException e) {
681                 e.maybeRethrow(IOException.class);
682                 throw new RuntimeException(e);
683             } catch (RemoteException e) {
684                 throw e.rethrowFromSystemServer();
685             }
686         }
687 
688         /**
689          * Opens a file descriptor to read the blob content already written into this session.
690          *
691          * @return a {@link ParcelFileDescriptor} for reading from the blob file.
692          *
693          * @throws IOException when there is an I/O error while opening the file to read.
694          * @throws SecurityException when the caller is not the owner of the session.
695          * @throws IllegalStateException when the caller tries to read the file after it is
696          *                               abandoned (using {@link #abandon()})
697          *                               or closed (using {@link #close()}).
698          */
openRead()699         public @NonNull ParcelFileDescriptor openRead() throws IOException {
700             try {
701                 return mSession.openRead();
702             } catch (ParcelableException e) {
703                 e.maybeRethrow(IOException.class);
704                 throw new RuntimeException(e);
705             } catch (RemoteException e) {
706                 throw e.rethrowFromSystemServer();
707             }
708         }
709 
710         /**
711          * Gets the size of the blob file that was written to the session so far.
712          *
713          * @return the size of the blob file so far.
714          *
715          * @throws IOException when there is an I/O error while opening the file to read.
716          * @throws SecurityException when the caller is not the owner of the session.
717          * @throws IllegalStateException when the caller tries to get the file size after it is
718          *                               abandoned (using {@link #abandon()})
719          *                               or closed (using {@link #close()}).
720          */
getSize()721         public @BytesLong long getSize() throws IOException {
722             try {
723                 return mSession.getSize();
724             } catch (ParcelableException e) {
725                 e.maybeRethrow(IOException.class);
726                 throw new RuntimeException(e);
727             } catch (RemoteException e) {
728                 throw e.rethrowFromSystemServer();
729             }
730         }
731 
732         /**
733          * Close this session. It can be re-opened for writing/reading if it has not been
734          * abandoned (using {@link #abandon}) or committed (using {@link #commit}).
735          *
736          * @throws IOException when there is an I/O error while closing the session.
737          * @throws SecurityException when the caller is not the owner of the session.
738          */
close()739         public void close() throws IOException {
740             try {
741                 mSession.close();
742             } catch (ParcelableException e) {
743                 e.maybeRethrow(IOException.class);
744                 throw new RuntimeException(e);
745             } catch (RemoteException e) {
746                 throw e.rethrowFromSystemServer();
747             }
748         }
749 
750         /**
751          * Abandon this session and delete any data that was written to this session so far.
752          *
753          * @throws IOException when there is an I/O error while abandoning the session.
754          * @throws SecurityException when the caller is not the owner of the session.
755          * @throws IllegalStateException when the caller tries to abandon a session which was
756          *                               already finalized.
757          */
abandon()758         public void abandon() throws IOException {
759             try {
760                 mSession.abandon();
761             } catch (ParcelableException e) {
762                 e.maybeRethrow(IOException.class);
763                 throw new RuntimeException(e);
764             } catch (RemoteException e) {
765                 throw e.rethrowFromSystemServer();
766             }
767         }
768 
769         /**
770          * Allow {@code packageName} with a particular signing certificate to access this blob
771          * data once it is committed using a {@link BlobHandle} representing the blob.
772          *
773          * <p> This needs to be called before committing the blob using
774          * {@link #commit(Executor, Consumer)}.
775          *
776          * @param packageName the name of the package which should be allowed to access the blob.
777          * @param certificate the input bytes representing a certificate of type
778          *                    {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
779          *
780          * @throws IOException when there is an I/O error while changing the access.
781          * @throws SecurityException when the caller is not the owner of the session.
782          * @throws IllegalStateException when the caller tries to change access for a blob which is
783          *                               already committed.
784          * @throws LimitExceededException when the caller tries to explicitly allow too
785          *                                many packages using this API.
786          */
allowPackageAccess(@onNull String packageName, @NonNull byte[] certificate)787         public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate)
788                 throws IOException {
789             try {
790                 mSession.allowPackageAccess(packageName, certificate);
791             } catch (ParcelableException e) {
792                 e.maybeRethrow(IOException.class);
793                 e.maybeRethrow(LimitExceededException.class);
794                 throw new RuntimeException(e);
795             } catch (RemoteException e) {
796                 throw e.rethrowFromSystemServer();
797             }
798         }
799 
800         /**
801          * Returns {@code true} if access has been allowed for a {@code packageName} using either
802          * {@link #allowPackageAccess(String, byte[])}.
803          * Otherwise, {@code false}.
804          *
805          * @param packageName the name of the package to check the access for.
806          * @param certificate the input bytes representing a certificate of type
807          *                    {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}.
808          *
809          * @throws IOException when there is an I/O error while getting the access type.
810          * @throws IllegalStateException when the caller tries to get access type from a session
811          *                               which is closed or abandoned.
812          */
isPackageAccessAllowed(@onNull String packageName, @NonNull byte[] certificate)813         public boolean isPackageAccessAllowed(@NonNull String packageName,
814                 @NonNull byte[] certificate) throws IOException {
815             try {
816                 return mSession.isPackageAccessAllowed(packageName, certificate);
817             } catch (ParcelableException e) {
818                 e.maybeRethrow(IOException.class);
819                 throw new RuntimeException(e);
820             } catch (RemoteException e) {
821                 throw e.rethrowFromSystemServer();
822             }
823         }
824 
825         /**
826          * Allow packages which are signed with the same certificate as the caller to access this
827          * blob data once it is committed using a {@link BlobHandle} representing the blob.
828          *
829          * <p> This needs to be called before committing the blob using
830          * {@link #commit(Executor, Consumer)}.
831          *
832          * @throws IOException when there is an I/O error while changing the access.
833          * @throws SecurityException when the caller is not the owner of the session.
834          * @throws IllegalStateException when the caller tries to change access for a blob which is
835          *                               already committed.
836          */
allowSameSignatureAccess()837         public void allowSameSignatureAccess() throws IOException {
838             try {
839                 mSession.allowSameSignatureAccess();
840             } catch (ParcelableException e) {
841                 e.maybeRethrow(IOException.class);
842                 throw new RuntimeException(e);
843             } catch (RemoteException e) {
844                 throw e.rethrowFromSystemServer();
845             }
846         }
847 
848         /**
849          * Returns {@code true} if access has been allowed for packages signed with the same
850          * certificate as the caller by using {@link #allowSameSignatureAccess()}.
851          * Otherwise, {@code false}.
852          *
853          * @throws IOException when there is an I/O error while getting the access type.
854          * @throws IllegalStateException when the caller tries to get access type from a session
855          *                               which is closed or abandoned.
856          */
isSameSignatureAccessAllowed()857         public boolean isSameSignatureAccessAllowed() throws IOException {
858             try {
859                 return mSession.isSameSignatureAccessAllowed();
860             } catch (ParcelableException e) {
861                 e.maybeRethrow(IOException.class);
862                 throw new RuntimeException(e);
863             } catch (RemoteException e) {
864                 throw e.rethrowFromSystemServer();
865             }
866         }
867 
868         /**
869          * Allow any app on the device to access this blob data once it is committed using
870          * a {@link BlobHandle} representing the blob.
871          *
872          * <p><strong>Note:</strong> This is only meant to be used from libraries and SDKs where
873          * the apps which we want to allow access is not known ahead of time.
874          * If a blob is being committed to be shared with a particular set of apps, it is highly
875          * recommended to use {@link #allowPackageAccess(String, byte[])} instead.
876          *
877          * <p> This needs to be called before committing the blob using
878          * {@link #commit(Executor, Consumer)}.
879          *
880          * @throws IOException when there is an I/O error while changing the access.
881          * @throws SecurityException when the caller is not the owner of the session.
882          * @throws IllegalStateException when the caller tries to change access for a blob which is
883          *                               already committed.
884          */
allowPublicAccess()885         public void allowPublicAccess() throws IOException {
886             try {
887                 mSession.allowPublicAccess();
888             } catch (ParcelableException e) {
889                 e.maybeRethrow(IOException.class);
890                 throw new RuntimeException(e);
891             } catch (RemoteException e) {
892                 throw e.rethrowFromSystemServer();
893             }
894         }
895 
896         /**
897          * Returns {@code true} if public access has been allowed by using
898          * {@link #allowPublicAccess()}. Otherwise, {@code false}.
899          *
900          * @throws IOException when there is an I/O error while getting the access type.
901          * @throws IllegalStateException when the caller tries to get access type from a session
902          *                               which is closed or abandoned.
903          */
isPublicAccessAllowed()904         public boolean isPublicAccessAllowed() throws IOException {
905             try {
906                 return mSession.isPublicAccessAllowed();
907             } catch (ParcelableException e) {
908                 e.maybeRethrow(IOException.class);
909                 throw new RuntimeException(e);
910             } catch (RemoteException e) {
911                 throw e.rethrowFromSystemServer();
912             }
913         }
914 
915         /**
916          * Commit the file that was written so far to this session to the blob store maintained by
917          * the system.
918          *
919          * <p> Once this method is called, the session is finalized and no additional
920          * mutations can be performed on the session. If the device reboots
921          * before the session has been finalized, you may commit the session again.
922          *
923          * <p> Note that this commit operation will fail if the hash of the data written so far
924          * to this session does not match with the one used for
925          * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)}  BlobHandle}
926          * associated with this session.
927          *
928          * <p> Committing the same data more than once will result in replacing the corresponding
929          * access mode (via calling one of {@link #allowPackageAccess(String, byte[])},
930          * {@link #allowSameSignatureAccess()}, etc) with the latest one.
931          *
932          * @param executor the executor on which result callback will be invoked.
933          * @param resultCallback a callback to receive the commit result. when the result is
934          *                       {@code 0}, it indicates success. Otherwise, failure.
935          *
936          * @throws IOException when there is an I/O error while committing the session.
937          * @throws SecurityException when the caller is not the owner of the session.
938          * @throws IllegalArgumentException when the passed parameters are not valid.
939          * @throws IllegalStateException when the caller tries to commit a session which was
940          *                               already finalized.
941          */
commit(@onNull @allbackExecutor Executor executor, @NonNull Consumer<Integer> resultCallback)942         public void commit(@NonNull @CallbackExecutor Executor executor,
943                 @NonNull Consumer<Integer> resultCallback) throws IOException {
944             try {
945                 mSession.commit(new IBlobCommitCallback.Stub() {
946                     public void onResult(int result) {
947                         executor.execute(PooledLambda.obtainRunnable(
948                                 Consumer::accept, resultCallback, result));
949                     }
950                 });
951             } catch (ParcelableException e) {
952                 e.maybeRethrow(IOException.class);
953                 throw new RuntimeException(e);
954             } catch (RemoteException e) {
955                 throw e.rethrowFromSystemServer();
956             }
957         }
958     }
959 }
960