• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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