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