1 /* 2 * Copyright 2020 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 com.android.server.blob; 17 18 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR; 19 import static android.app.blob.BlobStoreManager.MAX_CERTIFICATE_LENGTH; 20 import static android.app.blob.BlobStoreManager.MAX_PACKAGE_NAME_LENGTH; 21 import static android.app.blob.XmlTags.ATTR_CREATION_TIME_MS; 22 import static android.app.blob.XmlTags.ATTR_ID; 23 import static android.app.blob.XmlTags.ATTR_PACKAGE; 24 import static android.app.blob.XmlTags.ATTR_UID; 25 import static android.app.blob.XmlTags.TAG_ACCESS_MODE; 26 import static android.app.blob.XmlTags.TAG_BLOB_HANDLE; 27 import static android.os.Trace.TRACE_TAG_SYSTEM_SERVER; 28 import static android.system.OsConstants.O_CREAT; 29 import static android.system.OsConstants.O_RDONLY; 30 import static android.system.OsConstants.O_RDWR; 31 import static android.system.OsConstants.SEEK_SET; 32 import static android.text.format.Formatter.FLAG_IEC_UNITS; 33 import static android.text.format.Formatter.formatFileSize; 34 35 import static com.android.server.blob.BlobStoreConfig.TAG; 36 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_ADD_SESSION_CREATION_TIME; 37 import static com.android.server.blob.BlobStoreConfig.getMaxPermittedPackages; 38 import static com.android.server.blob.BlobStoreConfig.hasSessionExpired; 39 40 import android.annotation.BytesLong; 41 import android.annotation.NonNull; 42 import android.annotation.Nullable; 43 import android.app.blob.BlobHandle; 44 import android.app.blob.IBlobCommitCallback; 45 import android.app.blob.IBlobStoreSession; 46 import android.content.Context; 47 import android.os.Binder; 48 import android.os.FileUtils; 49 import android.os.LimitExceededException; 50 import android.os.ParcelFileDescriptor; 51 import android.os.ParcelableException; 52 import android.os.RemoteException; 53 import android.os.RevocableFileDescriptor; 54 import android.os.Trace; 55 import android.os.storage.StorageManager; 56 import android.system.ErrnoException; 57 import android.system.Os; 58 import android.util.ExceptionUtils; 59 import android.util.IndentingPrintWriter; 60 import android.util.Slog; 61 62 import com.android.internal.annotations.GuardedBy; 63 import com.android.internal.annotations.VisibleForTesting; 64 import com.android.internal.util.FrameworkStatsLog; 65 import com.android.internal.util.Preconditions; 66 import com.android.internal.util.XmlUtils; 67 import com.android.server.blob.BlobStoreManagerService.DumpArgs; 68 import com.android.server.blob.BlobStoreManagerService.SessionStateChangeListener; 69 70 import libcore.io.IoUtils; 71 72 import org.xmlpull.v1.XmlPullParser; 73 import org.xmlpull.v1.XmlPullParserException; 74 import org.xmlpull.v1.XmlSerializer; 75 76 import java.io.File; 77 import java.io.FileDescriptor; 78 import java.io.IOException; 79 import java.security.NoSuchAlgorithmException; 80 import java.util.ArrayList; 81 import java.util.Arrays; 82 import java.util.Objects; 83 84 /** 85 * Class to represent the state corresponding to an ongoing 86 * {@link android.app.blob.BlobStoreManager.Session} 87 */ 88 @VisibleForTesting 89 class BlobStoreSession extends IBlobStoreSession.Stub { 90 91 static final int STATE_OPENED = 1; 92 static final int STATE_CLOSED = 0; 93 static final int STATE_ABANDONED = 2; 94 static final int STATE_COMMITTED = 3; 95 static final int STATE_VERIFIED_VALID = 4; 96 static final int STATE_VERIFIED_INVALID = 5; 97 98 private final Object mSessionLock = new Object(); 99 100 private final Context mContext; 101 private final SessionStateChangeListener mListener; 102 103 private final BlobHandle mBlobHandle; 104 private final long mSessionId; 105 private final int mOwnerUid; 106 private final String mOwnerPackageName; 107 private final long mCreationTimeMs; 108 109 // Do not access this directly, instead use getSessionFile(). 110 private File mSessionFile; 111 112 @GuardedBy("mRevocableFds") 113 private final ArrayList<RevocableFileDescriptor> mRevocableFds = new ArrayList<>(); 114 115 // This will be accessed from only one thread at any point of time, so no need to grab 116 // a lock for this. 117 private byte[] mDataDigest; 118 119 @GuardedBy("mSessionLock") 120 private int mState = STATE_CLOSED; 121 122 @GuardedBy("mSessionLock") 123 private final BlobAccessMode mBlobAccessMode = new BlobAccessMode(); 124 125 @GuardedBy("mSessionLock") 126 private IBlobCommitCallback mBlobCommitCallback; 127 BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, int ownerUid, String ownerPackageName, long creationTimeMs, SessionStateChangeListener listener)128 private BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, 129 int ownerUid, String ownerPackageName, long creationTimeMs, 130 SessionStateChangeListener listener) { 131 this.mContext = context; 132 this.mBlobHandle = blobHandle; 133 this.mSessionId = sessionId; 134 this.mOwnerUid = ownerUid; 135 this.mOwnerPackageName = ownerPackageName; 136 this.mCreationTimeMs = creationTimeMs; 137 this.mListener = listener; 138 } 139 BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, int ownerUid, String ownerPackageName, SessionStateChangeListener listener)140 BlobStoreSession(Context context, long sessionId, BlobHandle blobHandle, 141 int ownerUid, String ownerPackageName, SessionStateChangeListener listener) { 142 this(context, sessionId, blobHandle, ownerUid, ownerPackageName, 143 System.currentTimeMillis(), listener); 144 } 145 getBlobHandle()146 public BlobHandle getBlobHandle() { 147 return mBlobHandle; 148 } 149 getSessionId()150 public long getSessionId() { 151 return mSessionId; 152 } 153 getOwnerUid()154 public int getOwnerUid() { 155 return mOwnerUid; 156 } 157 getOwnerPackageName()158 public String getOwnerPackageName() { 159 return mOwnerPackageName; 160 } 161 hasAccess(int callingUid, String callingPackageName)162 boolean hasAccess(int callingUid, String callingPackageName) { 163 return mOwnerUid == callingUid && mOwnerPackageName.equals(callingPackageName); 164 } 165 open()166 void open() { 167 synchronized (mSessionLock) { 168 if (isFinalized()) { 169 throw new IllegalStateException("Not allowed to open session with state: " 170 + stateToString(mState)); 171 } 172 mState = STATE_OPENED; 173 } 174 } 175 getState()176 int getState() { 177 synchronized (mSessionLock) { 178 return mState; 179 } 180 } 181 sendCommitCallbackResult(int result)182 void sendCommitCallbackResult(int result) { 183 synchronized (mSessionLock) { 184 try { 185 mBlobCommitCallback.onResult(result); 186 } catch (RemoteException e) { 187 Slog.d(TAG, "Error sending the callback result", e); 188 } 189 mBlobCommitCallback = null; 190 } 191 } 192 getBlobAccessMode()193 BlobAccessMode getBlobAccessMode() { 194 synchronized (mSessionLock) { 195 return mBlobAccessMode; 196 } 197 } 198 isFinalized()199 boolean isFinalized() { 200 synchronized (mSessionLock) { 201 return mState == STATE_COMMITTED || mState == STATE_ABANDONED; 202 } 203 } 204 isExpired()205 boolean isExpired() { 206 final long lastModifiedTimeMs = getSessionFile().lastModified(); 207 return hasSessionExpired(lastModifiedTimeMs == 0 208 ? mCreationTimeMs : lastModifiedTimeMs); 209 } 210 211 @Override 212 @NonNull openWrite(@ytesLong long offsetBytes, @BytesLong long lengthBytes)213 public ParcelFileDescriptor openWrite(@BytesLong long offsetBytes, 214 @BytesLong long lengthBytes) { 215 Preconditions.checkArgumentNonnegative(offsetBytes, "offsetBytes must not be negative"); 216 217 assertCallerIsOwner(); 218 synchronized (mSessionLock) { 219 if (mState != STATE_OPENED) { 220 throw new IllegalStateException("Not allowed to write in state: " 221 + stateToString(mState)); 222 } 223 } 224 225 FileDescriptor fd = null; 226 try { 227 fd = openWriteInternal(offsetBytes, lengthBytes); 228 final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd, 229 BlobStoreUtils.getRevocableFdHandler()); 230 synchronized (mSessionLock) { 231 if (mState != STATE_OPENED) { 232 IoUtils.closeQuietly(fd); 233 throw new IllegalStateException("Not allowed to write in state: " 234 + stateToString(mState)); 235 } 236 trackRevocableFdLocked(revocableFd); 237 return revocableFd.getRevocableFileDescriptor(); 238 } 239 } catch (IOException e) { 240 IoUtils.closeQuietly(fd); 241 throw ExceptionUtils.wrap(e); 242 } 243 } 244 245 @NonNull openWriteInternal(@ytesLong long offsetBytes, @BytesLong long lengthBytes)246 private FileDescriptor openWriteInternal(@BytesLong long offsetBytes, 247 @BytesLong long lengthBytes) throws IOException { 248 // TODO: Add limit on active open sessions/writes/reads 249 try { 250 final File sessionFile = getSessionFile(); 251 if (sessionFile == null) { 252 throw new IllegalStateException("Couldn't get the file for this session"); 253 } 254 final FileDescriptor fd = Os.open(sessionFile.getPath(), O_CREAT | O_RDWR, 0600); 255 if (offsetBytes > 0) { 256 final long curOffset = Os.lseek(fd, offsetBytes, SEEK_SET); 257 if (curOffset != offsetBytes) { 258 throw new IllegalStateException("Failed to seek " + offsetBytes 259 + "; curOffset=" + offsetBytes); 260 } 261 } 262 if (lengthBytes > 0) { 263 mContext.getSystemService(StorageManager.class).allocateBytes(fd, lengthBytes); 264 } 265 return fd; 266 } catch (ErrnoException e) { 267 throw e.rethrowAsIOException(); 268 } 269 } 270 271 @Override 272 @NonNull openRead()273 public ParcelFileDescriptor openRead() { 274 assertCallerIsOwner(); 275 synchronized (mSessionLock) { 276 if (mState != STATE_OPENED) { 277 throw new IllegalStateException("Not allowed to read in state: " 278 + stateToString(mState)); 279 } 280 if (!BlobStoreConfig.shouldUseRevocableFdForReads()) { 281 try { 282 return new ParcelFileDescriptor(openReadInternal()); 283 } catch (IOException e) { 284 throw ExceptionUtils.wrap(e); 285 } 286 } 287 } 288 289 FileDescriptor fd = null; 290 try { 291 fd = openReadInternal(); 292 final RevocableFileDescriptor revocableFd = new RevocableFileDescriptor(mContext, fd); 293 synchronized (mSessionLock) { 294 if (mState != STATE_OPENED) { 295 IoUtils.closeQuietly(fd); 296 throw new IllegalStateException("Not allowed to read in state: " 297 + stateToString(mState)); 298 } 299 trackRevocableFdLocked(revocableFd); 300 return revocableFd.getRevocableFileDescriptor(); 301 } 302 } catch (IOException e) { 303 IoUtils.closeQuietly(fd); 304 throw ExceptionUtils.wrap(e); 305 } 306 } 307 308 @NonNull openReadInternal()309 private FileDescriptor openReadInternal() throws IOException { 310 try { 311 final File sessionFile = getSessionFile(); 312 if (sessionFile == null) { 313 throw new IllegalStateException("Couldn't get the file for this session"); 314 } 315 final FileDescriptor fd = Os.open(sessionFile.getPath(), O_RDONLY, 0); 316 return fd; 317 } catch (ErrnoException e) { 318 throw e.rethrowAsIOException(); 319 } 320 } 321 322 @Override 323 @BytesLong getSize()324 public long getSize() { 325 return getSessionFile().length(); 326 } 327 328 @Override allowPackageAccess(@onNull String packageName, @NonNull byte[] certificate)329 public void allowPackageAccess(@NonNull String packageName, 330 @NonNull byte[] certificate) { 331 assertCallerIsOwner(); 332 Objects.requireNonNull(packageName, "packageName must not be null"); 333 Preconditions.checkArgument(packageName.length() <= MAX_PACKAGE_NAME_LENGTH, 334 "packageName is longer than " + MAX_PACKAGE_NAME_LENGTH + " chars"); 335 Objects.requireNonNull(certificate, "certificate must not be null"); 336 Preconditions.checkArgument(certificate.length <= MAX_CERTIFICATE_LENGTH, 337 "certificate is longer than " + MAX_CERTIFICATE_LENGTH + " chars"); 338 synchronized (mSessionLock) { 339 if (mState != STATE_OPENED) { 340 throw new IllegalStateException("Not allowed to change access type in state: " 341 + stateToString(mState)); 342 } 343 if (mBlobAccessMode.getAllowedPackagesCount() >= getMaxPermittedPackages()) { 344 throw new ParcelableException(new LimitExceededException( 345 "Too many packages permitted to access the blob: " 346 + mBlobAccessMode.getAllowedPackagesCount())); 347 } 348 mBlobAccessMode.allowPackageAccess(packageName, certificate); 349 } 350 } 351 352 @Override allowSameSignatureAccess()353 public void allowSameSignatureAccess() { 354 assertCallerIsOwner(); 355 synchronized (mSessionLock) { 356 if (mState != STATE_OPENED) { 357 throw new IllegalStateException("Not allowed to change access type in state: " 358 + stateToString(mState)); 359 } 360 mBlobAccessMode.allowSameSignatureAccess(); 361 } 362 } 363 364 @Override allowPublicAccess()365 public void allowPublicAccess() { 366 assertCallerIsOwner(); 367 synchronized (mSessionLock) { 368 if (mState != STATE_OPENED) { 369 throw new IllegalStateException("Not allowed to change access type in state: " 370 + stateToString(mState)); 371 } 372 mBlobAccessMode.allowPublicAccess(); 373 } 374 } 375 376 @Override isPackageAccessAllowed(@onNull String packageName, @NonNull byte[] certificate)377 public boolean isPackageAccessAllowed(@NonNull String packageName, 378 @NonNull byte[] certificate) { 379 assertCallerIsOwner(); 380 Objects.requireNonNull(packageName, "packageName must not be null"); 381 Preconditions.checkByteArrayNotEmpty(certificate, "certificate"); 382 383 synchronized (mSessionLock) { 384 if (mState != STATE_OPENED) { 385 throw new IllegalStateException("Not allowed to get access type in state: " 386 + stateToString(mState)); 387 } 388 return mBlobAccessMode.isPackageAccessAllowed(packageName, certificate); 389 } 390 } 391 392 @Override isSameSignatureAccessAllowed()393 public boolean isSameSignatureAccessAllowed() { 394 assertCallerIsOwner(); 395 synchronized (mSessionLock) { 396 if (mState != STATE_OPENED) { 397 throw new IllegalStateException("Not allowed to get access type in state: " 398 + stateToString(mState)); 399 } 400 return mBlobAccessMode.isSameSignatureAccessAllowed(); 401 } 402 } 403 404 @Override isPublicAccessAllowed()405 public boolean isPublicAccessAllowed() { 406 assertCallerIsOwner(); 407 synchronized (mSessionLock) { 408 if (mState != STATE_OPENED) { 409 throw new IllegalStateException("Not allowed to get access type in state: " 410 + stateToString(mState)); 411 } 412 return mBlobAccessMode.isPublicAccessAllowed(); 413 } 414 } 415 416 @Override close()417 public void close() { 418 closeSession(STATE_CLOSED, false /* sendCallback */); 419 } 420 421 @Override abandon()422 public void abandon() { 423 closeSession(STATE_ABANDONED, true /* sendCallback */); 424 } 425 426 @Override commit(IBlobCommitCallback callback)427 public void commit(IBlobCommitCallback callback) { 428 synchronized (mSessionLock) { 429 mBlobCommitCallback = callback; 430 431 closeSession(STATE_COMMITTED, true /* sendCallback */); 432 } 433 } 434 closeSession(int state, boolean sendCallback)435 private void closeSession(int state, boolean sendCallback) { 436 assertCallerIsOwner(); 437 synchronized (mSessionLock) { 438 if (mState != STATE_OPENED) { 439 if (state == STATE_CLOSED) { 440 // Just trying to close the session which is already deleted or abandoned, 441 // ignore. 442 return; 443 } else { 444 throw new IllegalStateException("Not allowed to delete or abandon a session" 445 + " with state: " + stateToString(mState)); 446 } 447 } 448 449 mState = state; 450 revokeAllFds(); 451 452 if (sendCallback) { 453 mListener.onStateChanged(this); 454 } 455 } 456 } 457 computeDigest()458 void computeDigest() { 459 try { 460 Trace.traceBegin(TRACE_TAG_SYSTEM_SERVER, 461 "computeBlobDigest-i" + mSessionId + "-l" + getSessionFile().length()); 462 mDataDigest = FileUtils.digest(getSessionFile(), mBlobHandle.algorithm); 463 } catch (IOException | NoSuchAlgorithmException e) { 464 Slog.e(TAG, "Error computing the digest", e); 465 } finally { 466 Trace.traceEnd(TRACE_TAG_SYSTEM_SERVER); 467 } 468 } 469 verifyBlobData()470 void verifyBlobData() { 471 synchronized (mSessionLock) { 472 if (mDataDigest != null && Arrays.equals(mDataDigest, mBlobHandle.digest)) { 473 mState = STATE_VERIFIED_VALID; 474 // Commit callback will be sent once the data is persisted. 475 } else { 476 Slog.d(TAG, "Digest of the data (" 477 + (mDataDigest == null ? "null" : BlobHandle.safeDigest(mDataDigest)) 478 + ") didn't match the given BlobHandle.digest (" 479 + BlobHandle.safeDigest(mBlobHandle.digest) + ")"); 480 mState = STATE_VERIFIED_INVALID; 481 482 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED, getOwnerUid(), mSessionId, 483 getSize(), FrameworkStatsLog.BLOB_COMMITTED__RESULT__DIGEST_MISMATCH); 484 sendCommitCallbackResult(COMMIT_RESULT_ERROR); 485 } 486 mListener.onStateChanged(this); 487 } 488 } 489 destroy()490 void destroy() { 491 revokeAllFds(); 492 getSessionFile().delete(); 493 } 494 revokeAllFds()495 private void revokeAllFds() { 496 synchronized (mRevocableFds) { 497 for (int i = mRevocableFds.size() - 1; i >= 0; --i) { 498 mRevocableFds.get(i).revoke(); 499 } 500 mRevocableFds.clear(); 501 } 502 } 503 504 @GuardedBy("mSessionLock") trackRevocableFdLocked(RevocableFileDescriptor revocableFd)505 private void trackRevocableFdLocked(RevocableFileDescriptor revocableFd) { 506 synchronized (mRevocableFds) { 507 mRevocableFds.add(revocableFd); 508 } 509 revocableFd.addOnCloseListener((e) -> { 510 synchronized (mRevocableFds) { 511 mRevocableFds.remove(revocableFd); 512 } 513 }); 514 } 515 516 @Nullable getSessionFile()517 File getSessionFile() { 518 if (mSessionFile == null) { 519 mSessionFile = BlobStoreConfig.prepareBlobFile(mSessionId); 520 } 521 return mSessionFile; 522 } 523 524 @NonNull stateToString(int state)525 static String stateToString(int state) { 526 switch (state) { 527 case STATE_OPENED: 528 return "<opened>"; 529 case STATE_CLOSED: 530 return "<closed>"; 531 case STATE_ABANDONED: 532 return "<abandoned>"; 533 case STATE_COMMITTED: 534 return "<committed>"; 535 case STATE_VERIFIED_VALID: 536 return "<verified_valid>"; 537 case STATE_VERIFIED_INVALID: 538 return "<verified_invalid>"; 539 default: 540 Slog.wtf(TAG, "Unknown state: " + state); 541 return "<unknown>"; 542 } 543 } 544 545 @Override toString()546 public String toString() { 547 return "BlobStoreSession {" 548 + "id:" + mSessionId 549 + ",handle:" + mBlobHandle 550 + ",uid:" + mOwnerUid 551 + ",pkg:" + mOwnerPackageName 552 + "}"; 553 } 554 assertCallerIsOwner()555 private void assertCallerIsOwner() { 556 final int callingUid = Binder.getCallingUid(); 557 if (callingUid != mOwnerUid) { 558 throw new SecurityException(mOwnerUid + " is not the session owner"); 559 } 560 } 561 dump(IndentingPrintWriter fout, DumpArgs dumpArgs)562 void dump(IndentingPrintWriter fout, DumpArgs dumpArgs) { 563 synchronized (mSessionLock) { 564 fout.println("state: " + stateToString(mState)); 565 fout.println("ownerUid: " + mOwnerUid); 566 fout.println("ownerPkg: " + mOwnerPackageName); 567 fout.println("creation time: " + BlobStoreUtils.formatTime(mCreationTimeMs)); 568 fout.println("size: " + formatFileSize(mContext, getSize(), FLAG_IEC_UNITS)); 569 570 fout.println("blobHandle:"); 571 fout.increaseIndent(); 572 mBlobHandle.dump(fout, dumpArgs.shouldDumpFull()); 573 fout.decreaseIndent(); 574 575 fout.println("accessMode:"); 576 fout.increaseIndent(); 577 mBlobAccessMode.dump(fout); 578 fout.decreaseIndent(); 579 580 fout.println("Open fds: #" + mRevocableFds.size()); 581 } 582 } 583 writeToXml(@onNull XmlSerializer out)584 void writeToXml(@NonNull XmlSerializer out) throws IOException { 585 synchronized (mSessionLock) { 586 XmlUtils.writeLongAttribute(out, ATTR_ID, mSessionId); 587 XmlUtils.writeStringAttribute(out, ATTR_PACKAGE, mOwnerPackageName); 588 XmlUtils.writeIntAttribute(out, ATTR_UID, mOwnerUid); 589 XmlUtils.writeLongAttribute(out, ATTR_CREATION_TIME_MS, mCreationTimeMs); 590 591 out.startTag(null, TAG_BLOB_HANDLE); 592 mBlobHandle.writeToXml(out); 593 out.endTag(null, TAG_BLOB_HANDLE); 594 595 out.startTag(null, TAG_ACCESS_MODE); 596 mBlobAccessMode.writeToXml(out); 597 out.endTag(null, TAG_ACCESS_MODE); 598 } 599 } 600 601 @Nullable createFromXml(@onNull XmlPullParser in, int version, @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener)602 static BlobStoreSession createFromXml(@NonNull XmlPullParser in, int version, 603 @NonNull Context context, @NonNull SessionStateChangeListener stateChangeListener) 604 throws IOException, XmlPullParserException { 605 final long sessionId = XmlUtils.readLongAttribute(in, ATTR_ID); 606 final String ownerPackageName = XmlUtils.readStringAttribute(in, ATTR_PACKAGE); 607 final int ownerUid = XmlUtils.readIntAttribute(in, ATTR_UID); 608 final long creationTimeMs = version >= XML_VERSION_ADD_SESSION_CREATION_TIME 609 ? XmlUtils.readLongAttribute(in, ATTR_CREATION_TIME_MS) 610 : System.currentTimeMillis(); 611 612 final int depth = in.getDepth(); 613 BlobHandle blobHandle = null; 614 BlobAccessMode blobAccessMode = null; 615 while (XmlUtils.nextElementWithin(in, depth)) { 616 if (TAG_BLOB_HANDLE.equals(in.getName())) { 617 blobHandle = BlobHandle.createFromXml(in); 618 } else if (TAG_ACCESS_MODE.equals(in.getName())) { 619 blobAccessMode = BlobAccessMode.createFromXml(in); 620 } 621 } 622 623 if (blobHandle == null) { 624 Slog.wtf(TAG, "blobHandle should be available"); 625 return null; 626 } 627 if (blobAccessMode == null) { 628 Slog.wtf(TAG, "blobAccessMode should be available"); 629 return null; 630 } 631 632 final BlobStoreSession blobStoreSession = new BlobStoreSession(context, sessionId, 633 blobHandle, ownerUid, ownerPackageName, creationTimeMs, stateChangeListener); 634 blobStoreSession.mBlobAccessMode.allow(blobAccessMode); 635 return blobStoreSession; 636 } 637 } 638