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