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