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