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