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 * Return the remaining quota size for acquiring a lease (in bytes) which indicates the 511 * remaining amount of data that an app can acquire a lease on before the System starts 512 * rejecting lease requests. 513 * 514 * If an app wants to acquire a lease on a blob but the remaining quota size is not sufficient, 515 * then it can try releasing leases on any older blobs which are not needed anymore. 516 * 517 * @return the remaining quota size for acquiring a lease. 518 */ getRemainingLeaseQuotaBytes()519 public @IntRange(from = 0) long getRemainingLeaseQuotaBytes() { 520 try { 521 return mService.getRemainingLeaseQuotaBytes(mContext.getOpPackageName()); 522 } catch (RemoteException e) { 523 throw e.rethrowFromSystemServer(); 524 } 525 } 526 527 /** 528 * Wait until any pending tasks (like persisting data to disk) have finished. 529 * 530 * @hide 531 */ 532 @TestApi waitForIdle(long timeoutMillis)533 public void waitForIdle(long timeoutMillis) throws InterruptedException, TimeoutException { 534 try { 535 final CountDownLatch countDownLatch = new CountDownLatch(1); 536 mService.waitForIdle(new RemoteCallback((result) -> countDownLatch.countDown())); 537 if (!countDownLatch.await(timeoutMillis, TimeUnit.MILLISECONDS)) { 538 throw new TimeoutException("Timed out waiting for service to become idle"); 539 } 540 } catch (ParcelableException e) { 541 throw new RuntimeException(e); 542 } catch (RemoteException e) { 543 throw e.rethrowFromSystemServer(); 544 } 545 } 546 547 /** @hide */ 548 @NonNull queryBlobsForUser(@onNull UserHandle user)549 public List<BlobInfo> queryBlobsForUser(@NonNull UserHandle user) throws IOException { 550 try { 551 return mService.queryBlobsForUser(user.getIdentifier()); 552 } catch (ParcelableException e) { 553 e.maybeRethrow(IOException.class); 554 throw new RuntimeException(e); 555 } catch (RemoteException e) { 556 throw e.rethrowFromSystemServer(); 557 } 558 } 559 560 /** @hide */ deleteBlob(@onNull BlobInfo blobInfo)561 public void deleteBlob(@NonNull BlobInfo blobInfo) throws IOException { 562 try { 563 mService.deleteBlob(blobInfo.getId()); 564 } catch (ParcelableException e) { 565 e.maybeRethrow(IOException.class); 566 throw new RuntimeException(e); 567 } catch (RemoteException e) { 568 throw e.rethrowFromSystemServer(); 569 } 570 } 571 572 /** 573 * Return the {@link BlobHandle BlobHandles} corresponding to the data blobs that 574 * the calling app currently has a lease on. 575 * 576 * @return a list of {@link BlobHandle BlobHandles} that the caller has a lease on. 577 */ 578 @NonNull getLeasedBlobs()579 public List<BlobHandle> getLeasedBlobs() throws IOException { 580 try { 581 return mService.getLeasedBlobs(mContext.getOpPackageName()); 582 } catch (ParcelableException e) { 583 e.maybeRethrow(IOException.class); 584 throw new RuntimeException(e); 585 } catch (RemoteException e) { 586 throw e.rethrowFromSystemServer(); 587 } 588 } 589 590 /** 591 * Return {@link LeaseInfo} representing a lease acquired using 592 * {@link #acquireLease(BlobHandle, int)} or one of it's other variants, 593 * or {@code null} if there is no lease acquired. 594 * 595 * @throws SecurityException when the blob represented by the {@code blobHandle} does not 596 * exist or the caller does not have access to it. 597 * @throws IllegalArgumentException when {@code blobHandle} is invalid. 598 * 599 * @hide 600 */ 601 @TestApi 602 @Nullable getLeaseInfo(@onNull BlobHandle blobHandle)603 public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle) throws IOException { 604 try { 605 return mService.getLeaseInfo(blobHandle, mContext.getOpPackageName()); 606 } catch (ParcelableException e) { 607 e.maybeRethrow(IOException.class); 608 throw new RuntimeException(e); 609 } catch (RemoteException e) { 610 throw e.rethrowFromSystemServer(); 611 } 612 } 613 614 /** 615 * Represents an ongoing session of a blob's contribution to the blob store managed by the 616 * system. 617 * 618 * <p> Clients that want to contribute a blob need to first create a {@link Session} using 619 * {@link #createSession(BlobHandle)} and once the session is created, clients can open and 620 * close this session multiple times using {@link #openSession(long)} and 621 * {@link Session#close()} before committing it using 622 * {@link Session#commit(Executor, Consumer)}, at which point system will take 623 * ownership of the blob and the client can no longer make any modifications to the blob's 624 * content. 625 */ 626 public static class Session implements Closeable { 627 private final IBlobStoreSession mSession; 628 Session(@onNull IBlobStoreSession session)629 private Session(@NonNull IBlobStoreSession session) { 630 mSession = session; 631 } 632 633 /** 634 * Opens a file descriptor to write a blob into the session. 635 * 636 * <p> The returned file descriptor will start writing data at the requested offset 637 * in the underlying file, which can be used to resume a partially 638 * written file. If a valid file length is specified, the system will 639 * preallocate the underlying disk space to optimize placement on disk. 640 * It is strongly recommended to provide a valid file length when known. 641 * 642 * @param offsetBytes offset into the file to begin writing at, or 0 to 643 * start at the beginning of the file. 644 * @param lengthBytes total size of the file being written, used to 645 * preallocate the underlying disk space, or -1 if unknown. 646 * The system may clear various caches as needed to allocate 647 * this space. 648 * 649 * @return a {@link ParcelFileDescriptor} for writing to the blob file. 650 * 651 * @throws IOException when there is an I/O error while opening the file to write. 652 * @throws SecurityException when the caller is not the owner of the session. 653 * @throws IllegalStateException when the caller tries to write to the file after it is 654 * abandoned (using {@link #abandon()}) 655 * or committed (using {@link #commit}) 656 * or closed (using {@link #close()}). 657 */ openWrite(@ytesLong long offsetBytes, @BytesLong long lengthBytes)658 public @NonNull ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, 659 @BytesLong long lengthBytes) throws IOException { 660 try { 661 final ParcelFileDescriptor pfd = mSession.openWrite(offsetBytes, lengthBytes); 662 pfd.seekTo(offsetBytes); 663 return pfd; 664 } catch (ParcelableException e) { 665 e.maybeRethrow(IOException.class); 666 throw new RuntimeException(e); 667 } catch (RemoteException e) { 668 throw e.rethrowFromSystemServer(); 669 } 670 } 671 672 /** 673 * Opens a file descriptor to read the blob content already written into this session. 674 * 675 * @return a {@link ParcelFileDescriptor} for reading from the blob file. 676 * 677 * @throws IOException when there is an I/O error while opening the file to read. 678 * @throws SecurityException when the caller is not the owner of the session. 679 * @throws IllegalStateException when the caller tries to read the file after it is 680 * abandoned (using {@link #abandon()}) 681 * or closed (using {@link #close()}). 682 */ openRead()683 public @NonNull ParcelFileDescriptor openRead() throws IOException { 684 try { 685 return mSession.openRead(); 686 } catch (ParcelableException e) { 687 e.maybeRethrow(IOException.class); 688 throw new RuntimeException(e); 689 } catch (RemoteException e) { 690 throw e.rethrowFromSystemServer(); 691 } 692 } 693 694 /** 695 * Gets the size of the blob file that was written to the session so far. 696 * 697 * @return the size of the blob file so far. 698 * 699 * @throws IOException when there is an I/O error while opening the file to read. 700 * @throws SecurityException when the caller is not the owner of the session. 701 * @throws IllegalStateException when the caller tries to get the file size after it is 702 * abandoned (using {@link #abandon()}) 703 * or closed (using {@link #close()}). 704 */ getSize()705 public @BytesLong long getSize() throws IOException { 706 try { 707 return mSession.getSize(); 708 } catch (ParcelableException e) { 709 e.maybeRethrow(IOException.class); 710 throw new RuntimeException(e); 711 } catch (RemoteException e) { 712 throw e.rethrowFromSystemServer(); 713 } 714 } 715 716 /** 717 * Close this session. It can be re-opened for writing/reading if it has not been 718 * abandoned (using {@link #abandon}) or committed (using {@link #commit}). 719 * 720 * @throws IOException when there is an I/O error while closing the session. 721 * @throws SecurityException when the caller is not the owner of the session. 722 */ close()723 public void close() throws IOException { 724 try { 725 mSession.close(); 726 } catch (ParcelableException e) { 727 e.maybeRethrow(IOException.class); 728 throw new RuntimeException(e); 729 } catch (RemoteException e) { 730 throw e.rethrowFromSystemServer(); 731 } 732 } 733 734 /** 735 * Abandon this session and delete any data that was written to this session so far. 736 * 737 * @throws IOException when there is an I/O error while abandoning the session. 738 * @throws SecurityException when the caller is not the owner of the session. 739 * @throws IllegalStateException when the caller tries to abandon a session which was 740 * already finalized. 741 */ abandon()742 public void abandon() throws IOException { 743 try { 744 mSession.abandon(); 745 } catch (ParcelableException e) { 746 e.maybeRethrow(IOException.class); 747 throw new RuntimeException(e); 748 } catch (RemoteException e) { 749 throw e.rethrowFromSystemServer(); 750 } 751 } 752 753 /** 754 * Allow {@code packageName} with a particular signing certificate to access this blob 755 * data once it is committed using a {@link BlobHandle} representing the blob. 756 * 757 * <p> This needs to be called before committing the blob using 758 * {@link #commit(Executor, Consumer)}. 759 * 760 * @param packageName the name of the package which should be allowed to access the blob. 761 * @param certificate the input bytes representing a certificate of type 762 * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. 763 * 764 * @throws IOException when there is an I/O error while changing the access. 765 * @throws SecurityException when the caller is not the owner of the session. 766 * @throws IllegalStateException when the caller tries to change access for a blob which is 767 * already committed. 768 * @throws LimitExceededException when the caller tries to explicitly allow too 769 * many packages using this API. 770 */ allowPackageAccess(@onNull String packageName, @NonNull byte[] certificate)771 public void allowPackageAccess(@NonNull String packageName, @NonNull byte[] certificate) 772 throws IOException { 773 try { 774 mSession.allowPackageAccess(packageName, certificate); 775 } catch (ParcelableException e) { 776 e.maybeRethrow(IOException.class); 777 e.maybeRethrow(LimitExceededException.class); 778 throw new RuntimeException(e); 779 } catch (RemoteException e) { 780 throw e.rethrowFromSystemServer(); 781 } 782 } 783 784 /** 785 * Returns {@code true} if access has been allowed for a {@code packageName} using either 786 * {@link #allowPackageAccess(String, byte[])}. 787 * Otherwise, {@code false}. 788 * 789 * @param packageName the name of the package to check the access for. 790 * @param certificate the input bytes representing a certificate of type 791 * {@link android.content.pm.PackageManager#CERT_INPUT_SHA256}. 792 * 793 * @throws IOException when there is an I/O error while getting the access type. 794 * @throws IllegalStateException when the caller tries to get access type from a session 795 * which is closed or abandoned. 796 */ isPackageAccessAllowed(@onNull String packageName, @NonNull byte[] certificate)797 public boolean isPackageAccessAllowed(@NonNull String packageName, 798 @NonNull byte[] certificate) throws IOException { 799 try { 800 return mSession.isPackageAccessAllowed(packageName, certificate); 801 } catch (ParcelableException e) { 802 e.maybeRethrow(IOException.class); 803 throw new RuntimeException(e); 804 } catch (RemoteException e) { 805 throw e.rethrowFromSystemServer(); 806 } 807 } 808 809 /** 810 * Allow packages which are signed with the same certificate as the caller to access this 811 * blob data once it is committed using a {@link BlobHandle} representing the blob. 812 * 813 * <p> This needs to be called before committing the blob using 814 * {@link #commit(Executor, Consumer)}. 815 * 816 * @throws IOException when there is an I/O error while changing the access. 817 * @throws SecurityException when the caller is not the owner of the session. 818 * @throws IllegalStateException when the caller tries to change access for a blob which is 819 * already committed. 820 */ allowSameSignatureAccess()821 public void allowSameSignatureAccess() throws IOException { 822 try { 823 mSession.allowSameSignatureAccess(); 824 } catch (ParcelableException e) { 825 e.maybeRethrow(IOException.class); 826 throw new RuntimeException(e); 827 } catch (RemoteException e) { 828 throw e.rethrowFromSystemServer(); 829 } 830 } 831 832 /** 833 * Returns {@code true} if access has been allowed for packages signed with the same 834 * certificate as the caller by using {@link #allowSameSignatureAccess()}. 835 * Otherwise, {@code false}. 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 */ isSameSignatureAccessAllowed()841 public boolean isSameSignatureAccessAllowed() throws IOException { 842 try { 843 return mSession.isSameSignatureAccessAllowed(); 844 } catch (ParcelableException e) { 845 e.maybeRethrow(IOException.class); 846 throw new RuntimeException(e); 847 } catch (RemoteException e) { 848 throw e.rethrowFromSystemServer(); 849 } 850 } 851 852 /** 853 * Allow any app on the device to access this blob data once it is committed using 854 * a {@link BlobHandle} representing the blob. 855 * 856 * <p><strong>Note:</strong> This is only meant to be used from libraries and SDKs where 857 * the apps which we want to allow access is not known ahead of time. 858 * If a blob is being committed to be shared with a particular set of apps, it is highly 859 * recommended to use {@link #allowPackageAccess(String, byte[])} instead. 860 * 861 * <p> This needs to be called before committing the blob using 862 * {@link #commit(Executor, Consumer)}. 863 * 864 * @throws IOException when there is an I/O error while changing the access. 865 * @throws SecurityException when the caller is not the owner of the session. 866 * @throws IllegalStateException when the caller tries to change access for a blob which is 867 * already committed. 868 */ allowPublicAccess()869 public void allowPublicAccess() throws IOException { 870 try { 871 mSession.allowPublicAccess(); 872 } catch (ParcelableException e) { 873 e.maybeRethrow(IOException.class); 874 throw new RuntimeException(e); 875 } catch (RemoteException e) { 876 throw e.rethrowFromSystemServer(); 877 } 878 } 879 880 /** 881 * Returns {@code true} if public access has been allowed by using 882 * {@link #allowPublicAccess()}. Otherwise, {@code false}. 883 * 884 * @throws IOException when there is an I/O error while getting the access type. 885 * @throws IllegalStateException when the caller tries to get access type from a session 886 * which is closed or abandoned. 887 */ isPublicAccessAllowed()888 public boolean isPublicAccessAllowed() throws IOException { 889 try { 890 return mSession.isPublicAccessAllowed(); 891 } catch (ParcelableException e) { 892 e.maybeRethrow(IOException.class); 893 throw new RuntimeException(e); 894 } catch (RemoteException e) { 895 throw e.rethrowFromSystemServer(); 896 } 897 } 898 899 /** 900 * Commit the file that was written so far to this session to the blob store maintained by 901 * the system. 902 * 903 * <p> Once this method is called, the session is finalized and no additional 904 * mutations can be performed on the session. If the device reboots 905 * before the session has been finalized, you may commit the session again. 906 * 907 * <p> Note that this commit operation will fail if the hash of the data written so far 908 * to this session does not match with the one used for 909 * {@link BlobHandle#createWithSha256(byte[], CharSequence, long, String)} BlobHandle} 910 * associated with this session. 911 * 912 * <p> Committing the same data more than once will result in replacing the corresponding 913 * access mode (via calling one of {@link #allowPackageAccess(String, byte[])}, 914 * {@link #allowSameSignatureAccess()}, etc) with the latest one. 915 * 916 * @param executor the executor on which result callback will be invoked. 917 * @param resultCallback a callback to receive the commit result. when the result is 918 * {@code 0}, it indicates success. Otherwise, failure. 919 * 920 * @throws IOException when there is an I/O error while committing the session. 921 * @throws SecurityException when the caller is not the owner of the session. 922 * @throws IllegalArgumentException when the passed parameters are not valid. 923 * @throws IllegalStateException when the caller tries to commit a session which was 924 * already finalized. 925 */ commit(@onNull @allbackExecutor Executor executor, @NonNull Consumer<Integer> resultCallback)926 public void commit(@NonNull @CallbackExecutor Executor executor, 927 @NonNull Consumer<Integer> resultCallback) throws IOException { 928 try { 929 mSession.commit(new IBlobCommitCallback.Stub() { 930 public void onResult(int result) { 931 executor.execute(PooledLambda.obtainRunnable( 932 Consumer::accept, resultCallback, result)); 933 } 934 }); 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