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