• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 com.android.server.blob;
17 
18 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR;
19 import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS;
20 import static android.app.blob.XmlTags.ATTR_VERSION;
21 import static android.app.blob.XmlTags.TAG_BLOB;
22 import static android.app.blob.XmlTags.TAG_BLOBS;
23 import static android.app.blob.XmlTags.TAG_SESSION;
24 import static android.app.blob.XmlTags.TAG_SESSIONS;
25 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
26 import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
27 import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
28 import static android.os.UserHandle.USER_CURRENT;
29 import static android.os.UserHandle.USER_NULL;
30 
31 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID;
32 import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
33 import static com.android.server.blob.BlobStoreConfig.LOGV;
34 import static com.android.server.blob.BlobStoreConfig.TAG;
35 import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
36 import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
37 import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
38 import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions;
39 import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs;
40 import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs;
41 import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
42 import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
43 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
44 import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
45 import static com.android.server.blob.BlobStoreSession.stateToString;
46 import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
47 import static com.android.server.blob.BlobStoreUtils.getPackageResources;
48 
49 import android.annotation.CurrentTimeSecondsLong;
50 import android.annotation.IdRes;
51 import android.annotation.IntRange;
52 import android.annotation.NonNull;
53 import android.annotation.Nullable;
54 import android.annotation.UserIdInt;
55 import android.app.ActivityManager;
56 import android.app.ActivityManagerInternal;
57 import android.app.StatsManager;
58 import android.app.blob.BlobHandle;
59 import android.app.blob.BlobInfo;
60 import android.app.blob.IBlobStoreManager;
61 import android.app.blob.IBlobStoreSession;
62 import android.app.blob.LeaseInfo;
63 import android.content.BroadcastReceiver;
64 import android.content.Context;
65 import android.content.Intent;
66 import android.content.IntentFilter;
67 import android.content.pm.ApplicationInfo;
68 import android.content.pm.PackageManagerInternal;
69 import android.content.pm.PackageStats;
70 import android.content.res.ResourceId;
71 import android.content.res.Resources;
72 import android.os.Binder;
73 import android.os.Handler;
74 import android.os.HandlerThread;
75 import android.os.LimitExceededException;
76 import android.os.ParcelFileDescriptor;
77 import android.os.ParcelableException;
78 import android.os.Process;
79 import android.os.RemoteCallback;
80 import android.os.SystemClock;
81 import android.os.UserHandle;
82 import android.os.UserManagerInternal;
83 import android.util.ArrayMap;
84 import android.util.ArraySet;
85 import android.util.AtomicFile;
86 import android.util.ExceptionUtils;
87 import android.util.LongSparseArray;
88 import android.util.Slog;
89 import android.util.SparseArray;
90 import android.util.StatsEvent;
91 import android.util.Xml;
92 
93 import com.android.internal.annotations.GuardedBy;
94 import com.android.internal.annotations.VisibleForTesting;
95 import com.android.internal.os.BackgroundThread;
96 import com.android.internal.util.CollectionUtils;
97 import com.android.internal.util.DumpUtils;
98 import com.android.internal.util.FastXmlSerializer;
99 import com.android.internal.util.FrameworkStatsLog;
100 import com.android.internal.util.IndentingPrintWriter;
101 import com.android.internal.util.Preconditions;
102 import com.android.internal.util.XmlUtils;
103 import com.android.internal.util.function.pooled.PooledLambda;
104 import com.android.server.LocalServices;
105 import com.android.server.ServiceThread;
106 import com.android.server.SystemService;
107 import com.android.server.Watchdog;
108 import com.android.server.blob.BlobMetadata.Committer;
109 import com.android.server.usage.StorageStatsManagerInternal;
110 import com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
111 
112 import org.xmlpull.v1.XmlPullParser;
113 import org.xmlpull.v1.XmlSerializer;
114 
115 import java.io.File;
116 import java.io.FileDescriptor;
117 import java.io.FileInputStream;
118 import java.io.FileOutputStream;
119 import java.io.IOException;
120 import java.io.PrintWriter;
121 import java.lang.ref.WeakReference;
122 import java.nio.charset.StandardCharsets;
123 import java.security.SecureRandom;
124 import java.util.ArrayList;
125 import java.util.Arrays;
126 import java.util.List;
127 import java.util.Objects;
128 import java.util.Random;
129 import java.util.Set;
130 import java.util.concurrent.atomic.AtomicInteger;
131 import java.util.concurrent.atomic.AtomicLong;
132 import java.util.function.Consumer;
133 import java.util.function.Function;
134 
135 /**
136  * Service responsible for maintaining and facilitating access to data blobs published by apps.
137  */
138 public class BlobStoreManagerService extends SystemService {
139 
140     private final Object mBlobsLock = new Object();
141 
142     // Contains data of userId -> {sessionId -> {BlobStoreSession}}.
143     @GuardedBy("mBlobsLock")
144     private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>();
145 
146     @GuardedBy("mBlobsLock")
147     private long mCurrentMaxSessionId;
148 
149     // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
150     @GuardedBy("mBlobsLock")
151     private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
152 
153     // Contains all ids that are currently in use.
154     @GuardedBy("mBlobsLock")
155     private final ArraySet<Long> mActiveBlobIds = new ArraySet<>();
156     // Contains all ids that are currently in use and those that were in use but got deleted in the
157     // current boot session.
158     @GuardedBy("mBlobsLock")
159     private final ArraySet<Long> mKnownBlobIds = new ArraySet<>();
160 
161     // Random number generator for new session ids.
162     private final Random mRandom = new SecureRandom();
163 
164     private final Context mContext;
165     private final Handler mHandler;
166     private final Handler mBackgroundHandler;
167     private final Injector mInjector;
168     private final SessionStateChangeListener mSessionStateChangeListener =
169             new SessionStateChangeListener();
170 
171     private PackageManagerInternal mPackageManagerInternal;
172     private StatsManager mStatsManager;
173     private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl();
174 
175     private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo;
176     private final Runnable mSaveSessionsRunnable = this::writeBlobSessions;
177 
BlobStoreManagerService(Context context)178     public BlobStoreManagerService(Context context) {
179         this(context, new Injector());
180     }
181 
182     @VisibleForTesting
BlobStoreManagerService(Context context, Injector injector)183     BlobStoreManagerService(Context context, Injector injector) {
184         super(context);
185 
186         mContext = context;
187         mInjector = injector;
188         mHandler = mInjector.initializeMessageHandler();
189         mBackgroundHandler = mInjector.getBackgroundHandler();
190     }
191 
initializeMessageHandler()192     private static Handler initializeMessageHandler() {
193         final HandlerThread handlerThread = new ServiceThread(TAG,
194                 Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
195         handlerThread.start();
196         final Handler handler = new Handler(handlerThread.getLooper());
197         Watchdog.getInstance().addThread(handler);
198         return handler;
199     }
200 
201     @Override
onStart()202     public void onStart() {
203         publishBinderService(Context.BLOB_STORE_SERVICE, new Stub());
204         LocalServices.addService(BlobStoreManagerInternal.class, new LocalService());
205 
206         mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
207         mStatsManager = getContext().getSystemService(StatsManager.class);
208         registerReceivers();
209         LocalServices.getService(StorageStatsManagerInternal.class)
210                 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG);
211     }
212 
213     @Override
onBootPhase(int phase)214     public void onBootPhase(int phase) {
215         if (phase == PHASE_ACTIVITY_MANAGER_READY) {
216             BlobStoreConfig.initialize(mContext);
217         } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
218             synchronized (mBlobsLock) {
219                 final SparseArray<SparseArray<String>> allPackages = getAllPackages();
220                 readBlobSessionsLocked(allPackages);
221                 readBlobsInfoLocked(allPackages);
222             }
223             registerBlobStorePuller();
224         } else if (phase == PHASE_BOOT_COMPLETED) {
225             BlobStoreIdleJobService.schedule(mContext);
226         }
227     }
228 
229     @GuardedBy("mBlobsLock")
generateNextSessionIdLocked()230     private long generateNextSessionIdLocked() {
231         // Logic borrowed from PackageInstallerService.
232         int n = 0;
233         long sessionId;
234         do {
235             final long randomLong = mRandom.nextLong();
236             sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong);
237             if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) {
238                 return sessionId;
239             }
240         } while (n++ < 32);
241         throw new IllegalStateException("Failed to allocate session ID");
242     }
243 
registerReceivers()244     private void registerReceivers() {
245         final IntentFilter packageChangedFilter = new IntentFilter();
246         packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
247         packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
248         packageChangedFilter.addDataScheme("package");
249         mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
250                 packageChangedFilter, null, mHandler);
251 
252         final IntentFilter userActionFilter = new IntentFilter();
253         userActionFilter.addAction(Intent.ACTION_USER_REMOVED);
254         mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
255                 userActionFilter, null, mHandler);
256     }
257 
258     @GuardedBy("mBlobsLock")
getUserSessionsLocked(int userId)259     private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) {
260         LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId);
261         if (userSessions == null) {
262             userSessions = new LongSparseArray<>();
263             mSessions.put(userId, userSessions);
264         }
265         return userSessions;
266     }
267 
268     @GuardedBy("mBlobsLock")
getUserBlobsLocked(int userId)269     private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
270         ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
271         if (userBlobs == null) {
272             userBlobs = new ArrayMap<>();
273             mBlobsMap.put(userId, userBlobs);
274         }
275         return userBlobs;
276     }
277 
278     @VisibleForTesting
addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId)279     void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
280         synchronized (mBlobsLock) {
281             mSessions.put(userId, userSessions);
282         }
283     }
284 
285     @VisibleForTesting
addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId)286     void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
287         synchronized (mBlobsLock) {
288             mBlobsMap.put(userId, userBlobs);
289         }
290     }
291 
292     @VisibleForTesting
addActiveIdsForTest(long... activeIds)293     void addActiveIdsForTest(long... activeIds) {
294         synchronized (mBlobsLock) {
295             for (long id : activeIds) {
296                 addActiveBlobIdLocked(id);
297             }
298         }
299     }
300 
301     @VisibleForTesting
getActiveIdsForTest()302     Set<Long> getActiveIdsForTest() {
303         synchronized (mBlobsLock) {
304             return mActiveBlobIds;
305         }
306     }
307 
308     @VisibleForTesting
getKnownIdsForTest()309     Set<Long> getKnownIdsForTest() {
310         synchronized (mBlobsLock) {
311             return mKnownBlobIds;
312         }
313     }
314 
315     @GuardedBy("mBlobsLock")
addSessionForUserLocked(BlobStoreSession session, int userId)316     private void addSessionForUserLocked(BlobStoreSession session, int userId) {
317         getUserSessionsLocked(userId).put(session.getSessionId(), session);
318         addActiveBlobIdLocked(session.getSessionId());
319     }
320 
321     @GuardedBy("mBlobsLock")
addBlobForUserLocked(BlobMetadata blobMetadata, int userId)322     private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
323         addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
324     }
325 
326     @GuardedBy("mBlobsLock")
addBlobForUserLocked(BlobMetadata blobMetadata, ArrayMap<BlobHandle, BlobMetadata> userBlobs)327     private void addBlobForUserLocked(BlobMetadata blobMetadata,
328             ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
329         userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
330         addActiveBlobIdLocked(blobMetadata.getBlobId());
331     }
332 
333     @GuardedBy("mBlobsLock")
addActiveBlobIdLocked(long id)334     private void addActiveBlobIdLocked(long id) {
335         mActiveBlobIds.add(id);
336         mKnownBlobIds.add(id);
337     }
338 
339     @GuardedBy("mBlobsLock")
getSessionsCountLocked(int uid, String packageName)340     private int getSessionsCountLocked(int uid, String packageName) {
341         // TODO: Maintain a counter instead of traversing all the sessions
342         final AtomicInteger sessionsCount = new AtomicInteger(0);
343         forEachSessionInUser(session -> {
344             if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) {
345                 sessionsCount.getAndIncrement();
346             }
347         }, UserHandle.getUserId(uid));
348         return sessionsCount.get();
349     }
350 
createSessionInternal(BlobHandle blobHandle, int callingUid, String callingPackage)351     private long createSessionInternal(BlobHandle blobHandle,
352             int callingUid, String callingPackage) {
353         synchronized (mBlobsLock) {
354             final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage);
355             if (sessionsCount >= getMaxActiveSessions()) {
356                 throw new LimitExceededException("Too many active sessions for the caller: "
357                         + sessionsCount);
358             }
359             // TODO: throw if there is already an active session associated with blobHandle.
360             final long sessionId = generateNextSessionIdLocked();
361             final BlobStoreSession session = new BlobStoreSession(mContext,
362                     sessionId, blobHandle, callingUid, callingPackage,
363                     mSessionStateChangeListener);
364             addSessionForUserLocked(session, UserHandle.getUserId(callingUid));
365             if (LOGV) {
366                 Slog.v(TAG, "Created session for " + blobHandle
367                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
368             }
369             writeBlobSessionsAsync();
370             return sessionId;
371         }
372     }
373 
openSessionInternal(long sessionId, int callingUid, String callingPackage)374     private BlobStoreSession openSessionInternal(long sessionId,
375             int callingUid, String callingPackage) {
376         final BlobStoreSession session;
377         synchronized (mBlobsLock) {
378             session = getUserSessionsLocked(
379                     UserHandle.getUserId(callingUid)).get(sessionId);
380             if (session == null || !session.hasAccess(callingUid, callingPackage)
381                     || session.isFinalized()) {
382                 throw new SecurityException("Session not found: " + sessionId);
383             }
384         }
385         session.open();
386         return session;
387     }
388 
abandonSessionInternal(long sessionId, int callingUid, String callingPackage)389     private void abandonSessionInternal(long sessionId,
390             int callingUid, String callingPackage) {
391         synchronized (mBlobsLock) {
392             final BlobStoreSession session = openSessionInternal(sessionId,
393                     callingUid, callingPackage);
394             session.open();
395             session.abandon();
396             if (LOGV) {
397                 Slog.v(TAG, "Abandoned session with id " + sessionId
398                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
399             }
400             writeBlobSessionsAsync();
401         }
402     }
403 
openBlobInternal(BlobHandle blobHandle, int callingUid, String callingPackage)404     private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
405             String callingPackage) throws IOException {
406         synchronized (mBlobsLock) {
407             final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
408                     .get(blobHandle);
409             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
410                     callingPackage, callingUid)) {
411                 if (blobMetadata == null) {
412                     FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
413                             INVALID_BLOB_ID, INVALID_BLOB_SIZE,
414                             FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE);
415                 } else {
416                     FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
417                             blobMetadata.getBlobId(), blobMetadata.getSize(),
418                             FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
419                 }
420                 throw new SecurityException("Caller not allowed to access " + blobHandle
421                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
422             }
423 
424             FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
425                     blobMetadata.getBlobId(), blobMetadata.getSize(),
426                     FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
427 
428             return blobMetadata.openForRead(callingPackage);
429         }
430     }
431 
432     @GuardedBy("mBlobsLock")
getCommittedBlobsCountLocked(int uid, String packageName)433     private int getCommittedBlobsCountLocked(int uid, String packageName) {
434         // TODO: Maintain a counter instead of traversing all the blobs
435         final AtomicInteger blobsCount = new AtomicInteger(0);
436         forEachBlobInUser((blobMetadata) -> {
437             if (blobMetadata.isACommitter(packageName, uid)) {
438                 blobsCount.getAndIncrement();
439             }
440         }, UserHandle.getUserId(uid));
441         return blobsCount.get();
442     }
443 
444     @GuardedBy("mBlobsLock")
getLeasedBlobsCountLocked(int uid, String packageName)445     private int getLeasedBlobsCountLocked(int uid, String packageName) {
446         // TODO: Maintain a counter instead of traversing all the blobs
447         final AtomicInteger blobsCount = new AtomicInteger(0);
448         forEachBlobInUser((blobMetadata) -> {
449             if (blobMetadata.isALeasee(packageName, uid)) {
450                 blobsCount.getAndIncrement();
451             }
452         }, UserHandle.getUserId(uid));
453         return blobsCount.get();
454     }
455 
acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId, CharSequence description, long leaseExpiryTimeMillis, int callingUid, String callingPackage)456     private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
457             CharSequence description, long leaseExpiryTimeMillis,
458             int callingUid, String callingPackage) {
459         synchronized (mBlobsLock) {
460             final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
461             if (leasesCount >= getMaxLeasedBlobs()) {
462                 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
463                         INVALID_BLOB_ID, INVALID_BLOB_SIZE,
464                         FrameworkStatsLog.BLOB_LEASED__RESULT__COUNT_LIMIT_EXCEEDED);
465                 throw new LimitExceededException("Too many leased blobs for the caller: "
466                         + leasesCount);
467             }
468             final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
469                     .get(blobHandle);
470             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
471                     callingPackage, callingUid)) {
472                 if (blobMetadata == null) {
473                     FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
474                             INVALID_BLOB_ID, INVALID_BLOB_SIZE,
475                             FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE);
476                 } else {
477                     FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
478                             blobMetadata.getBlobId(), blobMetadata.getSize(),
479                             FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
480                 }
481                 throw new SecurityException("Caller not allowed to access " + blobHandle
482                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
483             }
484             if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
485                     && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
486 
487                 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
488                         blobMetadata.getBlobId(), blobMetadata.getSize(),
489                         FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
490                 throw new IllegalArgumentException(
491                         "Lease expiry cannot be later than blobs expiry time");
492             }
493             if (blobMetadata.getSize()
494                     > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
495 
496                 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
497                         blobMetadata.getBlobId(), blobMetadata.getSize(),
498                         FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED);
499                 throw new LimitExceededException("Total amount of data with an active lease"
500                         + " is exceeding the max limit");
501             }
502 
503             FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
504                     blobMetadata.getBlobId(), blobMetadata.getSize(),
505                     FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS);
506 
507             blobMetadata.addOrReplaceLeasee(callingPackage, callingUid,
508                     descriptionResId, description, leaseExpiryTimeMillis);
509             if (LOGV) {
510                 Slog.v(TAG, "Acquired lease on " + blobHandle
511                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
512             }
513             writeBlobsInfoAsync();
514         }
515     }
516 
517     @VisibleForTesting
518     @GuardedBy("mBlobsLock")
getTotalUsageBytesLocked(int callingUid, String callingPackage)519     long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
520         final AtomicLong totalBytes = new AtomicLong(0);
521         forEachBlobInUser((blobMetadata) -> {
522             if (blobMetadata.isALeasee(callingPackage, callingUid)) {
523                 totalBytes.getAndAdd(blobMetadata.getSize());
524             }
525         }, UserHandle.getUserId(callingUid));
526         return totalBytes.get();
527     }
528 
releaseLeaseInternal(BlobHandle blobHandle, int callingUid, String callingPackage)529     private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
530             String callingPackage) {
531         synchronized (mBlobsLock) {
532             final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
533                     getUserBlobsLocked(UserHandle.getUserId(callingUid));
534             final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
535             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
536                     callingPackage, callingUid)) {
537                 throw new SecurityException("Caller not allowed to access " + blobHandle
538                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
539             }
540             blobMetadata.removeLeasee(callingPackage, callingUid);
541             if (LOGV) {
542                 Slog.v(TAG, "Released lease on " + blobHandle
543                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
544             }
545             if (!blobMetadata.hasValidLeases()) {
546                 mHandler.postDelayed(() -> {
547                     synchronized (mBlobsLock) {
548                         // Check if blobMetadata object is still valid. If it is not, then
549                         // it means that it was already deleted and nothing else to do here.
550                         if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) {
551                             return;
552                         }
553                         if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
554                             deleteBlobLocked(blobMetadata);
555                             userBlobs.remove(blobHandle);
556                         }
557                         writeBlobsInfoAsync();
558                     }
559                 }, getDeletionOnLastLeaseDelayMs());
560             }
561             writeBlobsInfoAsync();
562         }
563     }
564 
getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage)565     private long getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage) {
566         synchronized (mBlobsLock) {
567             final long remainingQuota = BlobStoreConfig.getAppDataBytesLimit()
568                     - getTotalUsageBytesLocked(callingUid, callingPackage);
569             return remainingQuota > 0 ? remainingQuota : 0;
570         }
571     }
572 
queryBlobsForUserInternal(int userId)573     private List<BlobInfo> queryBlobsForUserInternal(int userId) {
574         final ArrayList<BlobInfo> blobInfos = new ArrayList<>();
575         synchronized (mBlobsLock) {
576             final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>();
577             final Function<String, Resources> resourcesGetter = (packageName) -> {
578                 final WeakReference<Resources> resourcesRef = resources.get(packageName);
579                 Resources packageResources = resourcesRef == null ? null : resourcesRef.get();
580                 if (packageResources == null) {
581                     packageResources = getPackageResources(mContext, packageName, userId);
582                     resources.put(packageName, new WeakReference<>(packageResources));
583                 }
584                 return packageResources;
585             };
586             getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
587                 final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
588                 blobMetadata.forEachLeasee(leasee -> {
589                     if (!leasee.isStillValid()) {
590                         return;
591                     }
592                     final int descriptionResId = leasee.descriptionResEntryName == null
593                             ? Resources.ID_NULL
594                             : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
595                                     leasee.descriptionResEntryName, leasee.packageName);
596                     final long expiryTimeMs = leasee.expiryTimeMillis == 0
597                             ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis;
598                     leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs,
599                             descriptionResId, leasee.description));
600                 });
601                 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
602                         blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(),
603                         blobMetadata.getSize(), leaseInfos));
604             });
605         }
606         return blobInfos;
607     }
608 
deleteBlobInternal(long blobId, int callingUid)609     private void deleteBlobInternal(long blobId, int callingUid) {
610         synchronized (mBlobsLock) {
611             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
612                     UserHandle.getUserId(callingUid));
613             userBlobs.entrySet().removeIf(entry -> {
614                 final BlobMetadata blobMetadata = entry.getValue();
615                 if (blobMetadata.getBlobId() == blobId) {
616                     deleteBlobLocked(blobMetadata);
617                     return true;
618                 }
619                 return false;
620             });
621             writeBlobsInfoAsync();
622         }
623     }
624 
getLeasedBlobsInternal(int callingUid, @NonNull String callingPackage)625     private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
626             @NonNull String callingPackage) {
627         final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
628         forEachBlobInUser(blobMetadata -> {
629             if (blobMetadata.isALeasee(callingPackage, callingUid)) {
630                 leasedBlobs.add(blobMetadata.getBlobHandle());
631             }
632         }, UserHandle.getUserId(callingUid));
633         return leasedBlobs;
634     }
635 
getLeaseInfoInternal(BlobHandle blobHandle, int callingUid, @NonNull String callingPackage)636     private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
637             int callingUid, @NonNull String callingPackage) {
638         synchronized (mBlobsLock) {
639             final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
640                     .get(blobHandle);
641             if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
642                     callingPackage, callingUid)) {
643                 throw new SecurityException("Caller not allowed to access " + blobHandle
644                         + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
645             }
646             return blobMetadata.getLeaseInfo(callingPackage, callingUid);
647         }
648     }
649 
verifyCallingPackage(int callingUid, String callingPackage)650     private void verifyCallingPackage(int callingUid, String callingPackage) {
651         if (mPackageManagerInternal.getPackageUid(
652                 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
653             throw new SecurityException("Specified calling package [" + callingPackage
654                     + "] does not match the calling uid " + callingUid);
655         }
656     }
657 
658     class SessionStateChangeListener {
onStateChanged(@onNull BlobStoreSession session)659         public void onStateChanged(@NonNull BlobStoreSession session) {
660             mHandler.post(PooledLambda.obtainRunnable(
661                     BlobStoreManagerService::onStateChangedInternal,
662                     BlobStoreManagerService.this, session).recycleOnUse());
663         }
664     }
665 
onStateChangedInternal(@onNull BlobStoreSession session)666     private void onStateChangedInternal(@NonNull BlobStoreSession session) {
667         switch (session.getState()) {
668             case STATE_ABANDONED:
669             case STATE_VERIFIED_INVALID:
670                 synchronized (mBlobsLock) {
671                     deleteSessionLocked(session);
672                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
673                             .remove(session.getSessionId());
674                     if (LOGV) {
675                         Slog.v(TAG, "Session is invalid; deleted " + session);
676                     }
677                 }
678                 break;
679             case STATE_COMMITTED:
680                 mBackgroundHandler.post(() -> {
681                     session.computeDigest();
682                     mHandler.post(PooledLambda.obtainRunnable(
683                             BlobStoreSession::verifyBlobData, session).recycleOnUse());
684                 });
685                 break;
686             case STATE_VERIFIED_VALID:
687                 synchronized (mBlobsLock) {
688                     final int committedBlobsCount = getCommittedBlobsCountLocked(
689                             session.getOwnerUid(), session.getOwnerPackageName());
690                     if (committedBlobsCount >= getMaxCommittedBlobs()) {
691                         Slog.d(TAG, "Failed to commit: too many committed blobs. count: "
692                                 + committedBlobsCount + "; blob: " + session);
693                         session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
694                         deleteSessionLocked(session);
695                         getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
696                                 .remove(session.getSessionId());
697                         FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
698                                 session.getOwnerUid(), session.getSessionId(), session.getSize(),
699                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__COUNT_LIMIT_EXCEEDED);
700                         break;
701                     }
702                     final int userId = UserHandle.getUserId(session.getOwnerUid());
703                     final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
704                             userId);
705                     BlobMetadata blob = userBlobs.get(session.getBlobHandle());
706                     if (blob == null) {
707                         blob = new BlobMetadata(mContext, session.getSessionId(),
708                                 session.getBlobHandle(), userId);
709                         addBlobForUserLocked(blob, userBlobs);
710                     }
711                     final Committer existingCommitter = blob.getExistingCommitter(
712                             session.getOwnerPackageName(), session.getOwnerUid());
713                     final long existingCommitTimeMs =
714                             (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs();
715                     final Committer newCommitter = new Committer(session.getOwnerPackageName(),
716                             session.getOwnerUid(), session.getBlobAccessMode(),
717                             getAdjustedCommitTimeMs(existingCommitTimeMs,
718                                     System.currentTimeMillis()));
719                     blob.addOrReplaceCommitter(newCommitter);
720                     try {
721                         writeBlobsInfoLocked();
722                         FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
723                                 session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
724                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS);
725                         session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS);
726                     } catch (Exception e) {
727                         if (existingCommitter == null) {
728                             blob.removeCommitter(newCommitter);
729                         } else {
730                             blob.addOrReplaceCommitter(existingCommitter);
731                         }
732                         Slog.d(TAG, "Error committing the blob: " + session, e);
733                         FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
734                                 session.getOwnerUid(), session.getSessionId(), blob.getSize(),
735                                 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
736                         session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
737                         // If the commit fails and this blob data didn't exist before, delete it.
738                         // But if it is a recommit, just leave it as is.
739                         if (session.getSessionId() == blob.getBlobId()) {
740                             deleteBlobLocked(blob);
741                             userBlobs.remove(blob.getBlobHandle());
742                         }
743                     }
744                     // Delete redundant data from recommits.
745                     if (session.getSessionId() != blob.getBlobId()) {
746                         deleteSessionLocked(session);
747                     }
748                     getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
749                             .remove(session.getSessionId());
750                     if (LOGV) {
751                         Slog.v(TAG, "Successfully committed session " + session);
752                     }
753                 }
754                 break;
755             default:
756                 Slog.wtf(TAG, "Invalid session state: "
757                         + stateToString(session.getState()));
758         }
759         synchronized (mBlobsLock) {
760             try {
761                 writeBlobSessionsLocked();
762             } catch (Exception e) {
763                 // already logged, ignore.
764             }
765         }
766     }
767 
768     @GuardedBy("mBlobsLock")
writeBlobSessionsLocked()769     private void writeBlobSessionsLocked() throws Exception {
770         final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
771         if (sessionsIndexFile == null) {
772             Slog.wtf(TAG, "Error creating sessions index file");
773             return;
774         }
775         FileOutputStream fos = null;
776         try {
777             fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis());
778             final XmlSerializer out = new FastXmlSerializer();
779             out.setOutput(fos, StandardCharsets.UTF_8.name());
780             out.startDocument(null, true);
781             out.startTag(null, TAG_SESSIONS);
782             XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
783 
784             for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
785                 final LongSparseArray<BlobStoreSession> userSessions =
786                         mSessions.valueAt(i);
787                 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
788                     out.startTag(null, TAG_SESSION);
789                     userSessions.valueAt(j).writeToXml(out);
790                     out.endTag(null, TAG_SESSION);
791                 }
792             }
793 
794             out.endTag(null, TAG_SESSIONS);
795             out.endDocument();
796             sessionsIndexFile.finishWrite(fos);
797             if (LOGV) {
798                 Slog.v(TAG, "Finished persisting sessions data");
799             }
800         } catch (Exception e) {
801             sessionsIndexFile.failWrite(fos);
802             Slog.wtf(TAG, "Error writing sessions data", e);
803             throw e;
804         }
805     }
806 
807     @GuardedBy("mBlobsLock")
readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages)808     private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) {
809         if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
810             return;
811         }
812         final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
813         if (sessionsIndexFile == null) {
814             Slog.wtf(TAG, "Error creating sessions index file");
815             return;
816         } else if (!sessionsIndexFile.exists()) {
817             Slog.w(TAG, "Sessions index file not available: " + sessionsIndexFile.getBaseFile());
818             return;
819         }
820 
821         mSessions.clear();
822         try (FileInputStream fis = sessionsIndexFile.openRead()) {
823             final XmlPullParser in = Xml.newPullParser();
824             in.setInput(fis, StandardCharsets.UTF_8.name());
825             XmlUtils.beginDocument(in, TAG_SESSIONS);
826             final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
827             while (true) {
828                 XmlUtils.nextElement(in);
829                 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
830                     break;
831                 }
832 
833                 if (TAG_SESSION.equals(in.getName())) {
834                     final BlobStoreSession session = BlobStoreSession.createFromXml(
835                             in, version, mContext, mSessionStateChangeListener);
836                     if (session == null) {
837                         continue;
838                     }
839                     final SparseArray<String> userPackages = allPackages.get(
840                             UserHandle.getUserId(session.getOwnerUid()));
841                     if (userPackages != null
842                             && session.getOwnerPackageName().equals(
843                                     userPackages.get(session.getOwnerUid()))) {
844                         addSessionForUserLocked(session,
845                                 UserHandle.getUserId(session.getOwnerUid()));
846                     } else {
847                         // Unknown package or the session data does not belong to this package.
848                         session.getSessionFile().delete();
849                     }
850                     mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId());
851                 }
852             }
853             if (LOGV) {
854                 Slog.v(TAG, "Finished reading sessions data");
855             }
856         } catch (Exception e) {
857             Slog.wtf(TAG, "Error reading sessions data", e);
858         }
859     }
860 
861     @GuardedBy("mBlobsLock")
writeBlobsInfoLocked()862     private void writeBlobsInfoLocked() throws Exception {
863         final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
864         if (blobsIndexFile == null) {
865             Slog.wtf(TAG, "Error creating blobs index file");
866             return;
867         }
868         FileOutputStream fos = null;
869         try {
870             fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis());
871             final XmlSerializer out = new FastXmlSerializer();
872             out.setOutput(fos, StandardCharsets.UTF_8.name());
873             out.startDocument(null, true);
874             out.startTag(null, TAG_BLOBS);
875             XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
876 
877             for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
878                 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
879                 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
880                     out.startTag(null, TAG_BLOB);
881                     userBlobs.valueAt(j).writeToXml(out);
882                     out.endTag(null, TAG_BLOB);
883                 }
884             }
885 
886             out.endTag(null, TAG_BLOBS);
887             out.endDocument();
888             blobsIndexFile.finishWrite(fos);
889             if (LOGV) {
890                 Slog.v(TAG, "Finished persisting blobs data");
891             }
892         } catch (Exception e) {
893             blobsIndexFile.failWrite(fos);
894             Slog.wtf(TAG, "Error writing blobs data", e);
895             throw e;
896         }
897     }
898 
899     @GuardedBy("mBlobsLock")
readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages)900     private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) {
901         if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
902             return;
903         }
904         final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
905         if (blobsIndexFile == null) {
906             Slog.wtf(TAG, "Error creating blobs index file");
907             return;
908         } else if (!blobsIndexFile.exists()) {
909             Slog.w(TAG, "Blobs index file not available: " + blobsIndexFile.getBaseFile());
910             return;
911         }
912 
913         mBlobsMap.clear();
914         try (FileInputStream fis = blobsIndexFile.openRead()) {
915             final XmlPullParser in = Xml.newPullParser();
916             in.setInput(fis, StandardCharsets.UTF_8.name());
917             XmlUtils.beginDocument(in, TAG_BLOBS);
918             final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
919             while (true) {
920                 XmlUtils.nextElement(in);
921                 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
922                     break;
923                 }
924 
925                 if (TAG_BLOB.equals(in.getName())) {
926                     final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
927                             in, version, mContext);
928                     final SparseArray<String> userPackages = allPackages.get(
929                             blobMetadata.getUserId());
930                     if (userPackages == null) {
931                         blobMetadata.getBlobFile().delete();
932                     } else {
933                         addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
934                         blobMetadata.removeCommittersFromUnknownPkgs(userPackages);
935                         blobMetadata.removeLeaseesFromUnknownPkgs(userPackages);
936                     }
937                     mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
938                 }
939             }
940             if (LOGV) {
941                 Slog.v(TAG, "Finished reading blobs data");
942             }
943         } catch (Exception e) {
944             Slog.wtf(TAG, "Error reading blobs data", e);
945         }
946     }
947 
writeBlobsInfo()948     private void writeBlobsInfo() {
949         synchronized (mBlobsLock) {
950             try {
951                 writeBlobsInfoLocked();
952             } catch (Exception e) {
953                 // Already logged, ignore
954             }
955         }
956     }
957 
writeBlobsInfoAsync()958     private void writeBlobsInfoAsync() {
959         if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) {
960             mHandler.post(mSaveBlobsInfoRunnable);
961         }
962     }
963 
writeBlobSessions()964     private void writeBlobSessions() {
965         synchronized (mBlobsLock) {
966             try {
967                 writeBlobSessionsLocked();
968             } catch (Exception e) {
969                 // Already logged, ignore
970             }
971         }
972     }
973 
writeBlobSessionsAsync()974     private void writeBlobSessionsAsync() {
975         if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) {
976             mHandler.post(mSaveSessionsRunnable);
977         }
978     }
979 
getPackageUid(String packageName, int userId)980     private int getPackageUid(String packageName, int userId) {
981         final int uid = mPackageManagerInternal.getPackageUid(
982                 packageName,
983                 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
984                 userId);
985         return uid;
986     }
987 
getAllPackages()988     private SparseArray<SparseArray<String>> getAllPackages() {
989         final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
990         final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
991         for (int userId : allUsers) {
992             final SparseArray<String> userPackages = new SparseArray<>();
993             allPackages.put(userId, userPackages);
994             final List<ApplicationInfo> applicationInfos = mPackageManagerInternal
995                     .getInstalledApplications(
996                             MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
997                                     | MATCH_UNINSTALLED_PACKAGES,
998                             userId, Process.myUid());
999             for (int i = 0, count = applicationInfos.size(); i < count; ++i) {
1000                 final ApplicationInfo applicationInfo = applicationInfos.get(i);
1001                 userPackages.put(applicationInfo.uid, applicationInfo.packageName);
1002             }
1003         }
1004         return allPackages;
1005     }
1006 
prepareSessionsIndexFile()1007     AtomicFile prepareSessionsIndexFile() {
1008         final File file = BlobStoreConfig.prepareSessionIndexFile();
1009         if (file == null) {
1010             return null;
1011         }
1012         return new AtomicFile(file, "session_index" /* commitLogTag */);
1013     }
1014 
prepareBlobsIndexFile()1015     AtomicFile prepareBlobsIndexFile() {
1016         final File file = BlobStoreConfig.prepareBlobsIndexFile();
1017         if (file == null) {
1018             return null;
1019         }
1020         return new AtomicFile(file, "blobs_index" /* commitLogTag */);
1021     }
1022 
1023     @VisibleForTesting
handlePackageRemoved(String packageName, int uid)1024     void handlePackageRemoved(String packageName, int uid) {
1025         synchronized (mBlobsLock) {
1026             // Clean up any pending sessions
1027             final LongSparseArray<BlobStoreSession> userSessions =
1028                     getUserSessionsLocked(UserHandle.getUserId(uid));
1029             userSessions.removeIf((sessionId, blobStoreSession) -> {
1030                 if (blobStoreSession.getOwnerUid() == uid
1031                         && blobStoreSession.getOwnerPackageName().equals(packageName)) {
1032                     deleteSessionLocked(blobStoreSession);
1033                     return true;
1034                 }
1035                 return false;
1036             });
1037             writeBlobSessionsAsync();
1038 
1039             // Remove the package from the committer and leasee list
1040             final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
1041                     getUserBlobsLocked(UserHandle.getUserId(uid));
1042             userBlobs.entrySet().removeIf(entry -> {
1043                 final BlobMetadata blobMetadata = entry.getValue();
1044                 final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
1045                 if (isACommitter) {
1046                     blobMetadata.removeCommitter(packageName, uid);
1047                 }
1048                 blobMetadata.removeLeasee(packageName, uid);
1049                 // Regardless of when the blob is committed, we need to delete
1050                 // it if it was from the deleted package to ensure we delete all traces of it.
1051                 if (blobMetadata.shouldBeDeleted(isACommitter /* respectLeaseWaitTime */)) {
1052                     deleteBlobLocked(blobMetadata);
1053                     return true;
1054                 }
1055                 return false;
1056             });
1057             writeBlobsInfoAsync();
1058 
1059             if (LOGV) {
1060                 Slog.v(TAG, "Removed blobs data associated with pkg="
1061                         + packageName + ", uid=" + uid);
1062             }
1063         }
1064     }
1065 
handleUserRemoved(int userId)1066     private void handleUserRemoved(int userId) {
1067         synchronized (mBlobsLock) {
1068             final LongSparseArray<BlobStoreSession> userSessions =
1069                     mSessions.removeReturnOld(userId);
1070             if (userSessions != null) {
1071                 for (int i = 0, count = userSessions.size(); i < count; ++i) {
1072                     final BlobStoreSession session = userSessions.valueAt(i);
1073                     deleteSessionLocked(session);
1074                 }
1075             }
1076 
1077             final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
1078                     mBlobsMap.removeReturnOld(userId);
1079             if (userBlobs != null) {
1080                 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
1081                     final BlobMetadata blobMetadata = userBlobs.valueAt(i);
1082                     deleteBlobLocked(blobMetadata);
1083                 }
1084             }
1085             if (LOGV) {
1086                 Slog.v(TAG, "Removed blobs data in user " + userId);
1087             }
1088         }
1089     }
1090 
1091     @GuardedBy("mBlobsLock")
1092     @VisibleForTesting
handleIdleMaintenanceLocked()1093     void handleIdleMaintenanceLocked() {
1094         // Cleanup any left over data on disk that is not part of index.
1095         final ArrayList<Long> deletedBlobIds = new ArrayList<>();
1096         final ArrayList<File> filesToDelete = new ArrayList<>();
1097         final File blobsDir = BlobStoreConfig.getBlobsDir();
1098         if (blobsDir.exists()) {
1099             for (File file : blobsDir.listFiles()) {
1100                 try {
1101                     final long id = Long.parseLong(file.getName());
1102                     if (mActiveBlobIds.indexOf(id) < 0) {
1103                         filesToDelete.add(file);
1104                         deletedBlobIds.add(id);
1105                     }
1106                 } catch (NumberFormatException e) {
1107                     Slog.wtf(TAG, "Error parsing the file name: " + file, e);
1108                     filesToDelete.add(file);
1109                 }
1110             }
1111             for (int i = 0, count = filesToDelete.size(); i < count; ++i) {
1112                 filesToDelete.get(i).delete();
1113             }
1114         }
1115 
1116         // Cleanup any stale blobs.
1117         for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1118             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1119             userBlobs.entrySet().removeIf(entry -> {
1120                 final BlobMetadata blobMetadata = entry.getValue();
1121 
1122                 // Remove expired leases
1123                 blobMetadata.removeExpiredLeases();
1124 
1125                 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
1126                     deleteBlobLocked(blobMetadata);
1127                     deletedBlobIds.add(blobMetadata.getBlobId());
1128                     return true;
1129                 }
1130                 return false;
1131             });
1132         }
1133         writeBlobsInfoAsync();
1134 
1135         // Cleanup any stale sessions.
1136         for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1137             final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
1138             userSessions.removeIf((sessionId, blobStoreSession) -> {
1139                 boolean shouldRemove = false;
1140 
1141                 // Cleanup sessions which haven't been modified in a while.
1142                 if (blobStoreSession.isExpired()) {
1143                     shouldRemove = true;
1144                 }
1145 
1146                 // Cleanup sessions with already expired data.
1147                 if (blobStoreSession.getBlobHandle().isExpired()) {
1148                     shouldRemove = true;
1149                 }
1150 
1151                 if (shouldRemove) {
1152                     deleteSessionLocked(blobStoreSession);
1153                     deletedBlobIds.add(blobStoreSession.getSessionId());
1154                 }
1155                 return shouldRemove;
1156             });
1157         }
1158         Slog.d(TAG, "Completed idle maintenance; deleted "
1159                 + Arrays.toString(deletedBlobIds.toArray()));
1160         writeBlobSessionsAsync();
1161     }
1162 
1163     @GuardedBy("mBlobsLock")
deleteSessionLocked(BlobStoreSession blobStoreSession)1164     private void deleteSessionLocked(BlobStoreSession blobStoreSession) {
1165         blobStoreSession.destroy();
1166         mActiveBlobIds.remove(blobStoreSession.getSessionId());
1167     }
1168 
1169     @GuardedBy("mBlobsLock")
deleteBlobLocked(BlobMetadata blobMetadata)1170     private void deleteBlobLocked(BlobMetadata blobMetadata) {
1171         blobMetadata.destroy();
1172         mActiveBlobIds.remove(blobMetadata.getBlobId());
1173     }
1174 
runClearAllSessions(@serIdInt int userId)1175     void runClearAllSessions(@UserIdInt int userId) {
1176         synchronized (mBlobsLock) {
1177             for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1178                 final int sessionUserId = mSessions.keyAt(i);
1179                 if (userId != UserHandle.USER_ALL && userId != sessionUserId) {
1180                     continue;
1181                 }
1182                 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
1183                 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
1184                     mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId());
1185                 }
1186             }
1187             if (userId == UserHandle.USER_ALL) {
1188                 mSessions.clear();
1189             } else {
1190                 mSessions.remove(userId);
1191             }
1192             writeBlobSessionsAsync();
1193         }
1194     }
1195 
runClearAllBlobs(@serIdInt int userId)1196     void runClearAllBlobs(@UserIdInt int userId) {
1197         synchronized (mBlobsLock) {
1198             for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1199                 final int blobUserId = mBlobsMap.keyAt(i);
1200                 if (userId != UserHandle.USER_ALL && userId != blobUserId) {
1201                     continue;
1202                 }
1203                 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1204                 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1205                     mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
1206                 }
1207             }
1208             if (userId == UserHandle.USER_ALL) {
1209                 mBlobsMap.clear();
1210             } else {
1211                 mBlobsMap.remove(userId);
1212             }
1213             writeBlobsInfoAsync();
1214         }
1215     }
1216 
deleteBlob(@onNull BlobHandle blobHandle, @UserIdInt int userId)1217     void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
1218         synchronized (mBlobsLock) {
1219             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1220             final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
1221             if (blobMetadata == null) {
1222                 return;
1223             }
1224             deleteBlobLocked(blobMetadata);
1225             userBlobs.remove(blobHandle);
1226             writeBlobsInfoAsync();
1227         }
1228     }
1229 
runIdleMaintenance()1230     void runIdleMaintenance() {
1231         synchronized (mBlobsLock) {
1232             handleIdleMaintenanceLocked();
1233         }
1234     }
1235 
isBlobAvailable(long blobId, int userId)1236     boolean isBlobAvailable(long blobId, int userId) {
1237         synchronized (mBlobsLock) {
1238             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1239             for (BlobMetadata blobMetadata : userBlobs.values()) {
1240                 if (blobMetadata.getBlobId() == blobId) {
1241                     return true;
1242                 }
1243             }
1244             return false;
1245         }
1246     }
1247 
1248     @GuardedBy("mBlobsLock")
dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1249     private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
1250         for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1251             final int userId = mSessions.keyAt(i);
1252             if (!dumpArgs.shouldDumpUser(userId)) {
1253                 continue;
1254             }
1255             final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
1256             fout.println("List of sessions in user #"
1257                     + userId + " (" + userSessions.size() + "):");
1258             fout.increaseIndent();
1259             for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
1260                 final long sessionId = userSessions.keyAt(j);
1261                 final BlobStoreSession session = userSessions.valueAt(j);
1262                 if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(),
1263                         session.getOwnerUid(), session.getSessionId())) {
1264                     continue;
1265                 }
1266                 fout.println("Session #" + sessionId);
1267                 fout.increaseIndent();
1268                 session.dump(fout, dumpArgs);
1269                 fout.decreaseIndent();
1270             }
1271             fout.decreaseIndent();
1272         }
1273     }
1274 
1275     @GuardedBy("mBlobsLock")
dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs)1276     private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
1277         for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1278             final int userId = mBlobsMap.keyAt(i);
1279             if (!dumpArgs.shouldDumpUser(userId)) {
1280                 continue;
1281             }
1282             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1283             fout.println("List of blobs in user #"
1284                     + userId + " (" + userBlobs.size() + "):");
1285             fout.increaseIndent();
1286             for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1287                 final BlobMetadata blobMetadata = userBlobs.valueAt(j);
1288                 if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
1289                     continue;
1290                 }
1291                 fout.println("Blob #" + blobMetadata.getBlobId());
1292                 fout.increaseIndent();
1293                 blobMetadata.dump(fout, dumpArgs);
1294                 fout.decreaseIndent();
1295             }
1296             fout.decreaseIndent();
1297         }
1298     }
1299 
1300     private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
1301         @Override
augmentStatsForPackage(@onNull PackageStats stats, @NonNull String packageName, @UserIdInt int userId, boolean callerHasStatsPermission)1302         public void augmentStatsForPackage(@NonNull PackageStats stats, @NonNull String packageName,
1303                 @UserIdInt int userId, boolean callerHasStatsPermission) {
1304             final AtomicLong blobsDataSize = new AtomicLong(0);
1305             forEachSessionInUser(session -> {
1306                 if (session.getOwnerPackageName().equals(packageName)) {
1307                     blobsDataSize.getAndAdd(session.getSize());
1308                 }
1309             }, userId);
1310 
1311             forEachBlobInUser(blobMetadata -> {
1312                 if (blobMetadata.isALeasee(packageName)) {
1313                     if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
1314                         blobsDataSize.getAndAdd(blobMetadata.getSize());
1315                     }
1316                 }
1317             }, userId);
1318 
1319             stats.dataSize += blobsDataSize.get();
1320         }
1321 
1322         @Override
augmentStatsForUid(@onNull PackageStats stats, int uid, boolean callerHasStatsPermission)1323         public void augmentStatsForUid(@NonNull PackageStats stats, int uid,
1324                 boolean callerHasStatsPermission) {
1325             final int userId = UserHandle.getUserId(uid);
1326             final AtomicLong blobsDataSize = new AtomicLong(0);
1327             forEachSessionInUser(session -> {
1328                 if (session.getOwnerUid() == uid) {
1329                     blobsDataSize.getAndAdd(session.getSize());
1330                 }
1331             }, userId);
1332 
1333             forEachBlobInUser(blobMetadata -> {
1334                 if (blobMetadata.isALeasee(uid)) {
1335                     if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
1336                         blobsDataSize.getAndAdd(blobMetadata.getSize());
1337                     }
1338                 }
1339             }, userId);
1340 
1341             stats.dataSize += blobsDataSize.get();
1342         }
1343     }
1344 
forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId)1345     private void forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId) {
1346         synchronized (mBlobsLock) {
1347             final LongSparseArray<BlobStoreSession> userSessions = getUserSessionsLocked(userId);
1348             for (int i = 0, count = userSessions.size(); i < count; ++i) {
1349                 final BlobStoreSession session = userSessions.valueAt(i);
1350                 consumer.accept(session);
1351             }
1352         }
1353     }
1354 
forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId)1355     private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
1356         synchronized (mBlobsLock) {
1357             final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1358             for (int i = 0, count = userBlobs.size(); i < count; ++i) {
1359                 final BlobMetadata blobMetadata = userBlobs.valueAt(i);
1360                 consumer.accept(blobMetadata);
1361             }
1362         }
1363     }
1364 
1365     private class PackageChangedReceiver extends BroadcastReceiver {
1366         @Override
onReceive(Context context, Intent intent)1367         public void onReceive(Context context, Intent intent) {
1368             if (LOGV) {
1369                 Slog.v(TAG, "Received " + intent);
1370             }
1371             switch (intent.getAction()) {
1372                 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
1373                 case Intent.ACTION_PACKAGE_DATA_CLEARED:
1374                     final String packageName = intent.getData().getSchemeSpecificPart();
1375                     if (packageName == null) {
1376                         Slog.wtf(TAG, "Package name is missing in the intent: " + intent);
1377                         return;
1378                     }
1379                     final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
1380                     if (uid == -1) {
1381                         Slog.wtf(TAG, "uid is missing in the intent: " + intent);
1382                         return;
1383                     }
1384                     handlePackageRemoved(packageName, uid);
1385                     break;
1386                 default:
1387                     Slog.wtf(TAG, "Received unknown intent: " + intent);
1388             }
1389         }
1390     }
1391 
1392     private class UserActionReceiver extends BroadcastReceiver {
1393         @Override
onReceive(Context context, Intent intent)1394         public void onReceive(Context context, Intent intent) {
1395             if (LOGV) {
1396                 Slog.v(TAG, "Received: " + intent);
1397             }
1398             switch (intent.getAction()) {
1399                 case Intent.ACTION_USER_REMOVED:
1400                     final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
1401                             USER_NULL);
1402                     if (userId == USER_NULL) {
1403                         Slog.wtf(TAG, "userId is missing in the intent: " + intent);
1404                         return;
1405                     }
1406                     handleUserRemoved(userId);
1407                     break;
1408                 default:
1409                     Slog.wtf(TAG, "Received unknown intent: " + intent);
1410             }
1411         }
1412     }
1413 
1414     private class Stub extends IBlobStoreManager.Stub {
1415         @Override
1416         @IntRange(from = 1)
createSession(@onNull BlobHandle blobHandle, @NonNull String packageName)1417         public long createSession(@NonNull BlobHandle blobHandle,
1418                 @NonNull String packageName) {
1419             Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1420             blobHandle.assertIsValid();
1421             Objects.requireNonNull(packageName, "packageName must not be null");
1422 
1423             final int callingUid = Binder.getCallingUid();
1424             verifyCallingPackage(callingUid, packageName);
1425 
1426             if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1427                     packageName, UserHandle.getUserId(callingUid))) {
1428                 throw new SecurityException("Caller not allowed to create session; "
1429                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1430             }
1431 
1432             try {
1433                 return createSessionInternal(blobHandle, callingUid, packageName);
1434             } catch (LimitExceededException e) {
1435                 throw new ParcelableException(e);
1436             }
1437         }
1438 
1439         @Override
1440         @NonNull
openSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1441         public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId,
1442                 @NonNull String packageName) {
1443             Preconditions.checkArgumentPositive(sessionId,
1444                     "sessionId must be positive: " + sessionId);
1445             Objects.requireNonNull(packageName, "packageName must not be null");
1446 
1447             final int callingUid = Binder.getCallingUid();
1448             verifyCallingPackage(callingUid, packageName);
1449 
1450             return openSessionInternal(sessionId, callingUid, packageName);
1451         }
1452 
1453         @Override
abandonSession(@ntRangefrom = 1) long sessionId, @NonNull String packageName)1454         public void abandonSession(@IntRange(from = 1) long sessionId,
1455                 @NonNull String packageName) {
1456             Preconditions.checkArgumentPositive(sessionId,
1457                     "sessionId must be positive: " + sessionId);
1458             Objects.requireNonNull(packageName, "packageName must not be null");
1459 
1460             final int callingUid = Binder.getCallingUid();
1461             verifyCallingPackage(callingUid, packageName);
1462 
1463             abandonSessionInternal(sessionId, callingUid, packageName);
1464         }
1465 
1466         @Override
openBlob(@onNull BlobHandle blobHandle, @NonNull String packageName)1467         public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle,
1468                 @NonNull String packageName) {
1469             Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1470             blobHandle.assertIsValid();
1471             Objects.requireNonNull(packageName, "packageName must not be null");
1472 
1473             final int callingUid = Binder.getCallingUid();
1474             verifyCallingPackage(callingUid, packageName);
1475 
1476             if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1477                     packageName, UserHandle.getUserId(callingUid))) {
1478                 throw new SecurityException("Caller not allowed to open blob; "
1479                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1480             }
1481 
1482             try {
1483                 return openBlobInternal(blobHandle, callingUid, packageName);
1484             } catch (IOException e) {
1485                 throw ExceptionUtils.wrap(e);
1486             }
1487         }
1488 
1489         @Override
acquireLease(@onNull BlobHandle blobHandle, @IdRes int descriptionResId, @Nullable CharSequence description, @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName)1490         public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
1491                 @Nullable CharSequence description,
1492                 @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) {
1493             Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1494             blobHandle.assertIsValid();
1495             Preconditions.checkArgument(
1496                     ResourceId.isValid(descriptionResId) || description != null,
1497                     "Description must be valid; descriptionId=" + descriptionResId
1498                             + ", description=" + description);
1499             Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis,
1500                     "leaseExpiryTimeMillis must not be negative");
1501             Objects.requireNonNull(packageName, "packageName must not be null");
1502 
1503             description = BlobStoreConfig.getTruncatedLeaseDescription(description);
1504 
1505             final int callingUid = Binder.getCallingUid();
1506             verifyCallingPackage(callingUid, packageName);
1507 
1508             if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1509                     packageName, UserHandle.getUserId(callingUid))) {
1510                 throw new SecurityException("Caller not allowed to open blob; "
1511                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1512             }
1513 
1514             try {
1515                 acquireLeaseInternal(blobHandle, descriptionResId, description,
1516                         leaseExpiryTimeMillis, callingUid, packageName);
1517             } catch (Resources.NotFoundException e) {
1518                 throw new IllegalArgumentException(e);
1519             } catch (LimitExceededException e) {
1520                 throw new ParcelableException(e);
1521             }
1522         }
1523 
1524         @Override
releaseLease(@onNull BlobHandle blobHandle, @NonNull String packageName)1525         public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
1526             Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1527             blobHandle.assertIsValid();
1528             Objects.requireNonNull(packageName, "packageName must not be null");
1529 
1530             final int callingUid = Binder.getCallingUid();
1531             verifyCallingPackage(callingUid, packageName);
1532 
1533             if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1534                     packageName, UserHandle.getUserId(callingUid))) {
1535                 throw new SecurityException("Caller not allowed to open blob; "
1536                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1537             }
1538 
1539             releaseLeaseInternal(blobHandle, callingUid, packageName);
1540         }
1541 
1542         @Override
getRemainingLeaseQuotaBytes(@onNull String packageName)1543         public long getRemainingLeaseQuotaBytes(@NonNull String packageName) {
1544             final int callingUid = Binder.getCallingUid();
1545             verifyCallingPackage(callingUid, packageName);
1546 
1547             return getRemainingLeaseQuotaBytesInternal(callingUid, packageName);
1548         }
1549 
1550         @Override
waitForIdle(@onNull RemoteCallback remoteCallback)1551         public void waitForIdle(@NonNull RemoteCallback remoteCallback) {
1552             Objects.requireNonNull(remoteCallback, "remoteCallback must not be null");
1553 
1554             mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
1555                     "Caller is not allowed to call this; caller=" + Binder.getCallingUid());
1556             // We post messages back and forth between mHandler thread and mBackgroundHandler
1557             // thread while committing a blob. We need to replicate the same pattern here to
1558             // ensure pending messages have been handled.
1559             mHandler.post(() -> {
1560                 mBackgroundHandler.post(() -> {
1561                     mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null)
1562                             .recycleOnUse());
1563                 });
1564             });
1565         }
1566 
1567         @Override
1568         @NonNull
queryBlobsForUser(@serIdInt int userId)1569         public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
1570             if (Binder.getCallingUid() != Process.SYSTEM_UID) {
1571                 throw new SecurityException("Only system uid is allowed to call "
1572                         + "queryBlobsForUser()");
1573             }
1574 
1575             final int resolvedUserId = userId == USER_CURRENT
1576                     ? ActivityManager.getCurrentUser() : userId;
1577             // Don't allow any other special user ids apart from USER_CURRENT
1578             final ActivityManagerInternal amInternal = LocalServices.getService(
1579                     ActivityManagerInternal.class);
1580             amInternal.ensureNotSpecialUser(resolvedUserId);
1581 
1582             return queryBlobsForUserInternal(resolvedUserId);
1583         }
1584 
1585         @Override
deleteBlob(long blobId)1586         public void deleteBlob(long blobId) {
1587             final int callingUid = Binder.getCallingUid();
1588             if (callingUid != Process.SYSTEM_UID) {
1589                 throw new SecurityException("Only system uid is allowed to call "
1590                         + "deleteBlob()");
1591             }
1592 
1593             deleteBlobInternal(blobId, callingUid);
1594         }
1595 
1596         @Override
1597         @NonNull
getLeasedBlobs(@onNull String packageName)1598         public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) {
1599             Objects.requireNonNull(packageName, "packageName must not be null");
1600 
1601             final int callingUid = Binder.getCallingUid();
1602             verifyCallingPackage(callingUid, packageName);
1603 
1604             return getLeasedBlobsInternal(callingUid, packageName);
1605         }
1606 
1607         @Override
1608         @Nullable
getLeaseInfo(@onNull BlobHandle blobHandle, @NonNull String packageName)1609         public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
1610             Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1611             blobHandle.assertIsValid();
1612             Objects.requireNonNull(packageName, "packageName must not be null");
1613 
1614             final int callingUid = Binder.getCallingUid();
1615             verifyCallingPackage(callingUid, packageName);
1616 
1617             if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1618                     packageName, UserHandle.getUserId(callingUid))) {
1619                 throw new SecurityException("Caller not allowed to open blob; "
1620                         + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1621             }
1622 
1623             return getLeaseInfoInternal(blobHandle, callingUid, packageName);
1624         }
1625 
1626         @Override
dump(@onNull FileDescriptor fd, @NonNull PrintWriter writer, @Nullable String[] args)1627         public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
1628                 @Nullable String[] args) {
1629             // TODO: add proto-based version of this.
1630             if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return;
1631 
1632             final DumpArgs dumpArgs = DumpArgs.parse(args);
1633 
1634             final IndentingPrintWriter fout = new IndentingPrintWriter(writer, "    ");
1635             if (dumpArgs.shouldDumpHelp()) {
1636                 writer.println("dumpsys blob_store [options]:");
1637                 fout.increaseIndent();
1638                 dumpArgs.dumpArgsUsage(fout);
1639                 fout.decreaseIndent();
1640                 return;
1641             }
1642 
1643             synchronized (mBlobsLock) {
1644                 if (dumpArgs.shouldDumpAllSections()) {
1645                     fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
1646                     fout.println();
1647                 }
1648 
1649                 if (dumpArgs.shouldDumpSessions()) {
1650                     dumpSessionsLocked(fout, dumpArgs);
1651                     fout.println();
1652                 }
1653                 if (dumpArgs.shouldDumpBlobs()) {
1654                     dumpBlobsLocked(fout, dumpArgs);
1655                     fout.println();
1656                 }
1657             }
1658 
1659             if (dumpArgs.shouldDumpConfig()) {
1660                 fout.println("BlobStore config:");
1661                 fout.increaseIndent();
1662                 BlobStoreConfig.dump(fout, mContext);
1663                 fout.decreaseIndent();
1664                 fout.println();
1665             }
1666         }
1667 
1668         @Override
handleShellCommand(@onNull ParcelFileDescriptor in, @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err, @NonNull String[] args)1669         public int handleShellCommand(@NonNull ParcelFileDescriptor in,
1670                 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
1671                 @NonNull String[] args) {
1672             return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this,
1673                     in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
1674         }
1675     }
1676 
1677     static final class DumpArgs {
1678         private static final int FLAG_DUMP_SESSIONS = 1 << 0;
1679         private static final int FLAG_DUMP_BLOBS = 1 << 1;
1680         private static final int FLAG_DUMP_CONFIG = 1 << 2;
1681 
1682         private int mSelectedSectionFlags;
1683         private boolean mDumpUnredacted;
1684         private final ArrayList<String> mDumpPackages = new ArrayList<>();
1685         private final ArrayList<Integer> mDumpUids = new ArrayList<>();
1686         private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
1687         private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
1688         private boolean mDumpHelp;
1689         private boolean mDumpAll;
1690 
shouldDumpSession(String packageName, int uid, long blobId)1691         public boolean shouldDumpSession(String packageName, int uid, long blobId) {
1692             if (!CollectionUtils.isEmpty(mDumpPackages)
1693                     && mDumpPackages.indexOf(packageName) < 0) {
1694                 return false;
1695             }
1696             if (!CollectionUtils.isEmpty(mDumpUids)
1697                     && mDumpUids.indexOf(uid) < 0) {
1698                 return false;
1699             }
1700             if (!CollectionUtils.isEmpty(mDumpBlobIds)
1701                     && mDumpBlobIds.indexOf(blobId) < 0) {
1702                 return false;
1703             }
1704             return true;
1705         }
1706 
shouldDumpAllSections()1707         public boolean shouldDumpAllSections() {
1708             return mDumpAll || (mSelectedSectionFlags == 0);
1709         }
1710 
allowDumpSessions()1711         public void allowDumpSessions() {
1712             mSelectedSectionFlags |= FLAG_DUMP_SESSIONS;
1713         }
1714 
shouldDumpSessions()1715         public boolean shouldDumpSessions() {
1716             if (shouldDumpAllSections()) {
1717                 return true;
1718             }
1719             return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0;
1720         }
1721 
allowDumpBlobs()1722         public void allowDumpBlobs() {
1723             mSelectedSectionFlags |= FLAG_DUMP_BLOBS;
1724         }
1725 
shouldDumpBlobs()1726         public boolean shouldDumpBlobs() {
1727             if (shouldDumpAllSections()) {
1728                 return true;
1729             }
1730             return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0;
1731         }
1732 
allowDumpConfig()1733         public void allowDumpConfig() {
1734             mSelectedSectionFlags |= FLAG_DUMP_CONFIG;
1735         }
1736 
shouldDumpConfig()1737         public boolean shouldDumpConfig() {
1738             if (shouldDumpAllSections()) {
1739                 return true;
1740             }
1741             return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0;
1742         }
1743 
shouldDumpBlob(long blobId)1744         public boolean shouldDumpBlob(long blobId) {
1745             return CollectionUtils.isEmpty(mDumpBlobIds)
1746                     || mDumpBlobIds.indexOf(blobId) >= 0;
1747         }
1748 
shouldDumpFull()1749         public boolean shouldDumpFull() {
1750             return mDumpUnredacted;
1751         }
1752 
shouldDumpUser(int userId)1753         public boolean shouldDumpUser(int userId) {
1754             return CollectionUtils.isEmpty(mDumpUserIds)
1755                     || mDumpUserIds.indexOf(userId) >= 0;
1756         }
1757 
shouldDumpHelp()1758         public boolean shouldDumpHelp() {
1759             return mDumpHelp;
1760         }
1761 
DumpArgs()1762         private DumpArgs() {}
1763 
parse(String[] args)1764         public static DumpArgs parse(String[] args) {
1765             final DumpArgs dumpArgs = new DumpArgs();
1766             if (args == null) {
1767                 return dumpArgs;
1768             }
1769 
1770             for (int i = 0; i < args.length; ++i) {
1771                 final String opt = args[i];
1772                 if ("--all".equals(opt) || "-a".equals(opt)) {
1773                     dumpArgs.mDumpAll = true;
1774                 } else if ("--unredacted".equals(opt) || "-u".equals(opt)) {
1775                     final int callingUid = Binder.getCallingUid();
1776                     if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
1777                         dumpArgs.mDumpUnredacted = true;
1778                     }
1779                 } else if ("--sessions".equals(opt)) {
1780                     dumpArgs.allowDumpSessions();
1781                 } else if ("--blobs".equals(opt)) {
1782                     dumpArgs.allowDumpBlobs();
1783                 } else if ("--config".equals(opt)) {
1784                     dumpArgs.allowDumpConfig();
1785                 } else if ("--package".equals(opt) || "-p".equals(opt)) {
1786                     dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
1787                 } else if ("--uid".equals(opt)) {
1788                     dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid"));
1789                 } else if ("--user".equals(opt)) {
1790                     dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
1791                 } else if ("--blob".equals(opt) || "-b".equals(opt)) {
1792                     dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId"));
1793                 } else if ("--help".equals(opt) || "-h".equals(opt)) {
1794                     dumpArgs.mDumpHelp = true;
1795                 } else {
1796                     // Everything else is assumed to be blob ids.
1797                     dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId"));
1798                 }
1799             }
1800             return dumpArgs;
1801         }
1802 
getStringArgRequired(String[] args, int index, String argName)1803         private static String getStringArgRequired(String[] args, int index, String argName) {
1804             if (index >= args.length) {
1805                 throw new IllegalArgumentException("Missing " + argName);
1806             }
1807             return args[index];
1808         }
1809 
getIntArgRequired(String[] args, int index, String argName)1810         private static int getIntArgRequired(String[] args, int index, String argName) {
1811             if (index >= args.length) {
1812                 throw new IllegalArgumentException("Missing " + argName);
1813             }
1814             final int value;
1815             try {
1816                 value = Integer.parseInt(args[index]);
1817             } catch (NumberFormatException e) {
1818                 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1819             }
1820             return value;
1821         }
1822 
getLongArgRequired(String[] args, int index, String argName)1823         private static long getLongArgRequired(String[] args, int index, String argName) {
1824             if (index >= args.length) {
1825                 throw new IllegalArgumentException("Missing " + argName);
1826             }
1827             final long value;
1828             try {
1829                 value = Long.parseLong(args[index]);
1830             } catch (NumberFormatException e) {
1831                 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1832             }
1833             return value;
1834         }
1835 
dumpArgsUsage(IndentingPrintWriter pw)1836         private void dumpArgsUsage(IndentingPrintWriter pw) {
1837             pw.println("--help | -h");
1838             printWithIndent(pw, "Dump this help text");
1839             pw.println("--sessions");
1840             printWithIndent(pw, "Dump only the sessions info");
1841             pw.println("--blobs");
1842             printWithIndent(pw, "Dump only the committed blobs info");
1843             pw.println("--config");
1844             printWithIndent(pw, "Dump only the config values");
1845             pw.println("--package | -p [package-name]");
1846             printWithIndent(pw, "Dump blobs info associated with the given package");
1847             pw.println("--uid | -u [uid]");
1848             printWithIndent(pw, "Dump blobs info associated with the given uid");
1849             pw.println("--user [user-id]");
1850             printWithIndent(pw, "Dump blobs info in the given user");
1851             pw.println("--blob | -b [session-id | blob-id]");
1852             printWithIndent(pw, "Dump blob info corresponding to the given ID");
1853             pw.println("--full | -f");
1854             printWithIndent(pw, "Dump full unredacted blobs data");
1855         }
1856 
printWithIndent(IndentingPrintWriter pw, String str)1857         private void printWithIndent(IndentingPrintWriter pw, String str) {
1858             pw.increaseIndent();
1859             pw.println(str);
1860             pw.decreaseIndent();
1861         }
1862     }
1863 
registerBlobStorePuller()1864     private void registerBlobStorePuller() {
1865         mStatsManager.setPullAtomCallback(
1866                 FrameworkStatsLog.BLOB_INFO,
1867                 null, // use default PullAtomMetadata values
1868                 BackgroundThread.getExecutor(),
1869                 mStatsCallbackImpl
1870         );
1871     }
1872 
1873     private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
1874         @Override
onPullAtom(int atomTag, List<StatsEvent> data)1875         public int onPullAtom(int atomTag, List<StatsEvent> data) {
1876             switch (atomTag) {
1877                 case FrameworkStatsLog.BLOB_INFO:
1878                     return pullBlobData(atomTag, data);
1879                 default:
1880                     throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
1881             }
1882         }
1883     }
1884 
pullBlobData(int atomTag, List<StatsEvent> data)1885     private int pullBlobData(int atomTag, List<StatsEvent> data) {
1886         synchronized (mBlobsLock) {
1887             for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1888                 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1889                 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1890                     final BlobMetadata blob = userBlobs.valueAt(j);
1891                     data.add(blob.dumpAsStatsEvent(atomTag));
1892                 }
1893             }
1894         }
1895         return StatsManager.PULL_SUCCESS;
1896     }
1897 
1898     private class LocalService extends BlobStoreManagerInternal {
1899         @Override
onIdleMaintenance()1900         public void onIdleMaintenance() {
1901             runIdleMaintenance();
1902         }
1903     }
1904 
1905     @VisibleForTesting
1906     static class Injector {
initializeMessageHandler()1907         public Handler initializeMessageHandler() {
1908             return BlobStoreManagerService.initializeMessageHandler();
1909         }
1910 
getBackgroundHandler()1911         public Handler getBackgroundHandler() {
1912             return BackgroundThread.getHandler();
1913         }
1914     }
1915 }