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