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