blob: 5963157fcf7afe882a4b2655b5d92e09264c701f [file] [log] [blame]
Sudheer Shankaf5b36962019-10-04 16:16:13 -07001/*
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 */
16package com.android.server.blob;
17
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080018import static android.app.blob.BlobStoreManager.COMMIT_RESULT_ERROR;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080019import static android.app.blob.BlobStoreManager.COMMIT_RESULT_SUCCESS;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080020import static android.app.blob.XmlTags.ATTR_VERSION;
21import static android.app.blob.XmlTags.TAG_BLOB;
22import static android.app.blob.XmlTags.TAG_BLOBS;
23import static android.app.blob.XmlTags.TAG_SESSION;
24import static android.app.blob.XmlTags.TAG_SESSIONS;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080025import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_AWARE;
26import static android.content.pm.PackageManager.MATCH_DIRECT_BOOT_UNAWARE;
27import static android.content.pm.PackageManager.MATCH_UNINSTALLED_PACKAGES;
28import static android.os.UserHandle.USER_NULL;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080029
Sudheer Shankae53e1ed2020-02-03 17:15:24 -080030import static com.android.server.blob.BlobStoreConfig.LOGV;
Sudheer Shankaae53d112020-01-31 14:20:53 -080031import static com.android.server.blob.BlobStoreConfig.SESSION_EXPIRY_TIMEOUT_MILLIS;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080032import static com.android.server.blob.BlobStoreConfig.TAG;
Sudheer Shanka1406bc82020-02-03 12:27:24 -080033import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080034import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
35import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
36import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
37import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
38import static com.android.server.blob.BlobStoreSession.stateToString;
39
Sudheer Shankad2da0672019-12-20 15:51:20 -080040import android.annotation.CurrentTimeSecondsLong;
41import android.annotation.IdRes;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080042import android.annotation.IntRange;
Sudheer Shankad2da0672019-12-20 15:51:20 -080043import android.annotation.NonNull;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080044import android.annotation.Nullable;
Sudheer Shanka5caeed52020-02-01 12:54:33 -080045import android.annotation.UserIdInt;
Sudheer Shankad2da0672019-12-20 15:51:20 -080046import android.app.blob.BlobHandle;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070047import android.app.blob.IBlobStoreManager;
Sudheer Shankad2da0672019-12-20 15:51:20 -080048import android.app.blob.IBlobStoreSession;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080049import android.content.BroadcastReceiver;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070050import android.content.Context;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080051import android.content.Intent;
52import android.content.IntentFilter;
53import android.content.pm.ApplicationInfo;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080054import android.content.pm.PackageManagerInternal;
Sudheer Shankae9232d62020-01-23 16:55:34 -080055import android.content.pm.PackageStats;
Sudheer Shanka4824e3d2020-01-29 23:50:24 -080056import android.content.res.ResourceId;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080057import android.os.Binder;
58import android.os.Handler;
59import android.os.HandlerThread;
Sudheer Shankad2da0672019-12-20 15:51:20 -080060import android.os.ParcelFileDescriptor;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080061import android.os.Process;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080062import android.os.RemoteCallback;
63import android.os.SystemClock;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080064import android.os.UserHandle;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080065import android.os.UserManagerInternal;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080066import android.util.ArrayMap;
Sudheer Shankaae53d112020-01-31 14:20:53 -080067import android.util.ArraySet;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080068import android.util.AtomicFile;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080069import android.util.ExceptionUtils;
70import android.util.LongSparseArray;
71import android.util.Slog;
72import android.util.SparseArray;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080073import android.util.Xml;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070074
Sudheer Shankaab1d4162020-01-07 10:37:50 -080075import com.android.internal.annotations.GuardedBy;
Sudheer Shankabda89c12020-01-30 15:19:24 -080076import com.android.internal.annotations.VisibleForTesting;
Sudheer Shankab76f7662020-02-27 15:17:43 -080077import com.android.internal.os.BackgroundThread;
Sudheer Shanka5caeed52020-02-01 12:54:33 -080078import com.android.internal.util.CollectionUtils;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080079import com.android.internal.util.DumpUtils;
80import com.android.internal.util.FastXmlSerializer;
81import com.android.internal.util.IndentingPrintWriter;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080082import com.android.internal.util.Preconditions;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080083import com.android.internal.util.XmlUtils;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080084import com.android.internal.util.function.pooled.PooledLambda;
85import com.android.server.LocalServices;
86import com.android.server.ServiceThread;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070087import com.android.server.SystemService;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080088import com.android.server.Watchdog;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080089import com.android.server.blob.BlobMetadata.Committer;
Sudheer Shankae9232d62020-01-23 16:55:34 -080090import com.android.server.usage.StorageStatsManagerInternal;
91import com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080092
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080093import org.xmlpull.v1.XmlPullParser;
94import org.xmlpull.v1.XmlSerializer;
95
96import java.io.File;
97import java.io.FileDescriptor;
98import java.io.FileInputStream;
99import java.io.FileOutputStream;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800100import java.io.IOException;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800101import java.io.PrintWriter;
102import java.nio.charset.StandardCharsets;
Sudheer Shanka60803032020-02-13 12:47:59 -0800103import java.security.SecureRandom;
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800104import java.util.ArrayList;
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800105import java.util.Arrays;
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800106import java.util.List;
Sudheer Shanka4824e3d2020-01-29 23:50:24 -0800107import java.util.Objects;
Sudheer Shanka60803032020-02-13 12:47:59 -0800108import java.util.Random;
Sudheer Shankaae53d112020-01-31 14:20:53 -0800109import java.util.Set;
Sudheer Shankae9232d62020-01-23 16:55:34 -0800110import java.util.concurrent.atomic.AtomicLong;
111import java.util.function.Consumer;
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700112
113/**
114 * Service responsible for maintaining and facilitating access to data blobs published by apps.
115 */
116public class BlobStoreManagerService extends SystemService {
117
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800118 private final Object mBlobsLock = new Object();
119
120 // Contains data of userId -> {sessionId -> {BlobStoreSession}}.
121 @GuardedBy("mBlobsLock")
122 private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>();
123
124 @GuardedBy("mBlobsLock")
125 private long mCurrentMaxSessionId;
126
127 // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
128 @GuardedBy("mBlobsLock")
129 private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
130
Sudheer Shankaae53d112020-01-31 14:20:53 -0800131 // Contains all ids that are currently in use.
132 @GuardedBy("mBlobsLock")
Sudheer Shanka60803032020-02-13 12:47:59 -0800133 private final ArraySet<Long> mActiveBlobIds = new ArraySet<>();
134 // Contains all ids that are currently in use and those that were in use but got deleted in the
135 // current boot session.
136 @GuardedBy("mBlobsLock")
Sudheer Shankaae53d112020-01-31 14:20:53 -0800137 private final ArraySet<Long> mKnownBlobIds = new ArraySet<>();
138
Sudheer Shanka60803032020-02-13 12:47:59 -0800139 // Random number generator for new session ids.
140 private final Random mRandom = new SecureRandom();
141
Sudheer Shankad2da0672019-12-20 15:51:20 -0800142 private final Context mContext;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800143 private final Handler mHandler;
Sudheer Shankab76f7662020-02-27 15:17:43 -0800144 private final Handler mBackgroundHandler;
Sudheer Shankabda89c12020-01-30 15:19:24 -0800145 private final Injector mInjector;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800146 private final SessionStateChangeListener mSessionStateChangeListener =
147 new SessionStateChangeListener();
148
149 private PackageManagerInternal mPackageManagerInternal;
Sudheer Shankad2da0672019-12-20 15:51:20 -0800150
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800151 private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo;
152 private final Runnable mSaveSessionsRunnable = this::writeBlobSessions;
153
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700154 public BlobStoreManagerService(Context context) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800155 this(context, new Injector());
156 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800157
Sudheer Shankabda89c12020-01-30 15:19:24 -0800158 @VisibleForTesting
159 BlobStoreManagerService(Context context, Injector injector) {
160 super(context);
161
162 mContext = context;
163 mInjector = injector;
164 mHandler = mInjector.initializeMessageHandler();
Sudheer Shankab76f7662020-02-27 15:17:43 -0800165 mBackgroundHandler = mInjector.getBackgroundHandler();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800166 }
167
168 private static Handler initializeMessageHandler() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800169 final HandlerThread handlerThread = new ServiceThread(TAG,
Sudheer Shankab76f7662020-02-27 15:17:43 -0800170 Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800171 handlerThread.start();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800172 final Handler handler = new Handler(handlerThread.getLooper());
173 Watchdog.getInstance().addThread(handler);
174 return handler;
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700175 }
176
177 @Override
178 public void onStart() {
179 publishBinderService(Context.BLOB_STORE_SERVICE, new Stub());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800180 LocalServices.addService(BlobStoreManagerInternal.class, new LocalService());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800181
182 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800183 registerReceivers();
Sudheer Shankae9232d62020-01-23 16:55:34 -0800184 LocalServices.getService(StorageStatsManagerInternal.class)
185 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800186 }
187
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800188 @Override
189 public void onBootPhase(int phase) {
190 if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
191 synchronized (mBlobsLock) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800192 final SparseArray<SparseArray<String>> allPackages = getAllPackages();
193 readBlobSessionsLocked(allPackages);
194 readBlobsInfoLocked(allPackages);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800195 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800196 } else if (phase == PHASE_BOOT_COMPLETED) {
197 BlobStoreIdleJobService.schedule(mContext);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800198 }
199 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800200
201 @GuardedBy("mBlobsLock")
202 private long generateNextSessionIdLocked() {
Sudheer Shanka60803032020-02-13 12:47:59 -0800203 // Logic borrowed from PackageInstallerService.
204 int n = 0;
205 long sessionId;
206 do {
207 sessionId = Math.abs(mRandom.nextLong());
208 if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != 0) {
209 return sessionId;
210 }
211 } while (n++ < 32);
212 throw new IllegalStateException("Failed to allocate session ID");
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800213 }
214
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800215 private void registerReceivers() {
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800216 final IntentFilter packageChangedFilter = new IntentFilter();
217 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
218 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
219 packageChangedFilter.addDataScheme("package");
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800220 mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800221 packageChangedFilter, null, mHandler);
222
223 final IntentFilter userActionFilter = new IntentFilter();
224 userActionFilter.addAction(Intent.ACTION_USER_REMOVED);
225 mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
226 userActionFilter, null, mHandler);
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800227 }
228
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800229 @GuardedBy("mBlobsLock")
230 private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) {
231 LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId);
232 if (userSessions == null) {
233 userSessions = new LongSparseArray<>();
234 mSessions.put(userId, userSessions);
235 }
236 return userSessions;
237 }
238
239 @GuardedBy("mBlobsLock")
240 private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
241 ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
242 if (userBlobs == null) {
243 userBlobs = new ArrayMap<>();
244 mBlobsMap.put(userId, userBlobs);
245 }
246 return userBlobs;
247 }
248
Sudheer Shankabda89c12020-01-30 15:19:24 -0800249 @VisibleForTesting
250 void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
251 synchronized (mBlobsLock) {
252 mSessions.put(userId, userSessions);
253 }
254 }
255
256 @VisibleForTesting
257 void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
258 synchronized (mBlobsLock) {
259 mBlobsMap.put(userId, userBlobs);
260 }
261 }
262
Sudheer Shankaae53d112020-01-31 14:20:53 -0800263 @VisibleForTesting
Sudheer Shanka60803032020-02-13 12:47:59 -0800264 void addActiveIdsForTest(long... activeIds) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800265 synchronized (mBlobsLock) {
Sudheer Shanka60803032020-02-13 12:47:59 -0800266 for (long id : activeIds) {
267 addActiveBlobIdLocked(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800268 }
269 }
270 }
271
272 @VisibleForTesting
Sudheer Shanka60803032020-02-13 12:47:59 -0800273 Set<Long> getActiveIdsForTest() {
274 synchronized (mBlobsLock) {
275 return mActiveBlobIds;
276 }
277 }
278
279 @VisibleForTesting
Sudheer Shankaae53d112020-01-31 14:20:53 -0800280 Set<Long> getKnownIdsForTest() {
281 synchronized (mBlobsLock) {
282 return mKnownBlobIds;
283 }
284 }
285
286 @GuardedBy("mBlobsLock")
287 private void addSessionForUserLocked(BlobStoreSession session, int userId) {
288 getUserSessionsLocked(userId).put(session.getSessionId(), session);
Sudheer Shanka60803032020-02-13 12:47:59 -0800289 addActiveBlobIdLocked(session.getSessionId());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800290 }
291
292 @GuardedBy("mBlobsLock")
293 private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
294 addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
295 }
296
297 @GuardedBy("mBlobsLock")
298 private void addBlobForUserLocked(BlobMetadata blobMetadata,
299 ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
300 userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
Sudheer Shanka60803032020-02-13 12:47:59 -0800301 addActiveBlobIdLocked(blobMetadata.getBlobId());
302 }
303
304 @GuardedBy("mBlobsLock")
305 private void addActiveBlobIdLocked(long id) {
306 mActiveBlobIds.add(id);
307 mKnownBlobIds.add(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800308 }
309
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800310 private long createSessionInternal(BlobHandle blobHandle,
311 int callingUid, String callingPackage) {
312 synchronized (mBlobsLock) {
313 // TODO: throw if there is already an active session associated with blobHandle.
314 final long sessionId = generateNextSessionIdLocked();
315 final BlobStoreSession session = new BlobStoreSession(mContext,
316 sessionId, blobHandle, callingUid, callingPackage,
317 mSessionStateChangeListener);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800318 addSessionForUserLocked(session, UserHandle.getUserId(callingUid));
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800319 if (LOGV) {
320 Slog.v(TAG, "Created session for " + blobHandle
321 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
322 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800323 writeBlobSessionsAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800324 return sessionId;
325 }
326 }
327
328 private BlobStoreSession openSessionInternal(long sessionId,
329 int callingUid, String callingPackage) {
330 final BlobStoreSession session;
331 synchronized (mBlobsLock) {
332 session = getUserSessionsLocked(
333 UserHandle.getUserId(callingUid)).get(sessionId);
334 if (session == null || !session.hasAccess(callingUid, callingPackage)
335 || session.isFinalized()) {
336 throw new SecurityException("Session not found: " + sessionId);
337 }
338 }
339 session.open();
340 return session;
341 }
342
343 private void deleteSessionInternal(long sessionId,
344 int callingUid, String callingPackage) {
345 synchronized (mBlobsLock) {
346 final BlobStoreSession session = openSessionInternal(sessionId,
347 callingUid, callingPackage);
348 session.open();
349 session.abandon();
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800350 if (LOGV) {
351 Slog.v(TAG, "Deleted session with id " + sessionId
352 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
353 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800354 writeBlobSessionsAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800355 }
356 }
357
358 private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
359 String callingPackage) throws IOException {
360 synchronized (mBlobsLock) {
361 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
362 .get(blobHandle);
363 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
364 callingPackage, callingUid)) {
365 throw new SecurityException("Caller not allowed to access " + blobHandle
366 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
367 }
368 return blobMetadata.openForRead(callingPackage);
369 }
370 }
371
372 private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800373 CharSequence description, long leaseExpiryTimeMillis,
374 int callingUid, String callingPackage) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800375 synchronized (mBlobsLock) {
376 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
377 .get(blobHandle);
378 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
379 callingPackage, callingUid)) {
380 throw new SecurityException("Caller not allowed to access " + blobHandle
381 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
382 }
Sudheer Shanka51a6c6e2020-02-18 17:28:12 -0800383 if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
384 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800385 throw new IllegalArgumentException(
386 "Lease expiry cannot be later than blobs expiry time");
387 }
388 blobMetadata.addLeasee(callingPackage, callingUid,
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800389 descriptionResId, description, leaseExpiryTimeMillis);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800390 if (LOGV) {
391 Slog.v(TAG, "Acquired lease on " + blobHandle
392 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
393 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800394 writeBlobsInfoAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800395 }
396 }
397
398 private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
399 String callingPackage) {
400 synchronized (mBlobsLock) {
401 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
402 .get(blobHandle);
403 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
404 callingPackage, callingUid)) {
405 throw new SecurityException("Caller not allowed to access " + blobHandle
406 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
407 }
408 blobMetadata.removeLeasee(callingPackage, callingUid);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800409 if (LOGV) {
410 Slog.v(TAG, "Released lease on " + blobHandle
411 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
412 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800413 writeBlobsInfoAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800414 }
415 }
416
417 private void verifyCallingPackage(int callingUid, String callingPackage) {
418 if (mPackageManagerInternal.getPackageUid(
419 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
420 throw new SecurityException("Specified calling package [" + callingPackage
421 + "] does not match the calling uid " + callingUid);
422 }
423 }
424
425 class SessionStateChangeListener {
426 public void onStateChanged(@NonNull BlobStoreSession session) {
427 mHandler.post(PooledLambda.obtainRunnable(
428 BlobStoreManagerService::onStateChangedInternal,
Sudheer Shankab76f7662020-02-27 15:17:43 -0800429 BlobStoreManagerService.this, session).recycleOnUse());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800430 }
431 }
432
433 private void onStateChangedInternal(@NonNull BlobStoreSession session) {
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800434 switch (session.getState()) {
435 case STATE_ABANDONED:
436 case STATE_VERIFIED_INVALID:
437 session.getSessionFile().delete();
438 synchronized (mBlobsLock) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800439 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
440 .remove(session.getSessionId());
Sudheer Shanka60803032020-02-13 12:47:59 -0800441 mActiveBlobIds.remove(session.getSessionId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800442 if (LOGV) {
443 Slog.v(TAG, "Session is invalid; deleted " + session);
444 }
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800445 }
446 break;
447 case STATE_COMMITTED:
Sudheer Shankab76f7662020-02-27 15:17:43 -0800448 mBackgroundHandler.post(() -> {
449 session.computeDigest();
450 mHandler.post(PooledLambda.obtainRunnable(
451 BlobStoreSession::verifyBlobData, session).recycleOnUse());
452 });
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800453 break;
454 case STATE_VERIFIED_VALID:
455 synchronized (mBlobsLock) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800456 final int userId = UserHandle.getUserId(session.getOwnerUid());
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800457 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
458 userId);
Sudheer Shankabda89c12020-01-30 15:19:24 -0800459 BlobMetadata blob = userBlobs.get(session.getBlobHandle());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800460 if (blob == null) {
461 blob = new BlobMetadata(mContext,
Sudheer Shankabda89c12020-01-30 15:19:24 -0800462 session.getSessionId(), session.getBlobHandle(), userId);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800463 addBlobForUserLocked(blob, userBlobs);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800464 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800465 final Committer newCommitter = new Committer(session.getOwnerPackageName(),
466 session.getOwnerUid(), session.getBlobAccessMode());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800467 final Committer existingCommitter = blob.getExistingCommitter(newCommitter);
468 blob.addCommitter(newCommitter);
469 try {
470 writeBlobsInfoLocked();
471 session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS);
472 } catch (Exception e) {
473 blob.addCommitter(existingCommitter);
474 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
475 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800476 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
477 .remove(session.getSessionId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800478 if (LOGV) {
479 Slog.v(TAG, "Successfully committed session " + session);
480 }
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800481 }
482 break;
483 default:
484 Slog.wtf(TAG, "Invalid session state: "
485 + stateToString(session.getState()));
486 }
487 synchronized (mBlobsLock) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800488 try {
489 writeBlobSessionsLocked();
490 } catch (Exception e) {
491 // already logged, ignore.
492 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800493 }
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700494 }
495
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800496 @GuardedBy("mBlobsLock")
497 private void writeBlobSessionsLocked() throws Exception {
498 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
499 if (sessionsIndexFile == null) {
500 Slog.wtf(TAG, "Error creating sessions index file");
501 return;
502 }
503 FileOutputStream fos = null;
504 try {
505 fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis());
506 final XmlSerializer out = new FastXmlSerializer();
507 out.setOutput(fos, StandardCharsets.UTF_8.name());
508 out.startDocument(null, true);
509 out.startTag(null, TAG_SESSIONS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800510 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800511
512 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
513 final LongSparseArray<BlobStoreSession> userSessions =
514 mSessions.valueAt(i);
515 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
516 out.startTag(null, TAG_SESSION);
517 userSessions.valueAt(j).writeToXml(out);
518 out.endTag(null, TAG_SESSION);
519 }
520 }
521
522 out.endTag(null, TAG_SESSIONS);
523 out.endDocument();
524 sessionsIndexFile.finishWrite(fos);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800525 if (LOGV) {
526 Slog.v(TAG, "Finished persisting sessions data");
527 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800528 } catch (Exception e) {
529 sessionsIndexFile.failWrite(fos);
530 Slog.wtf(TAG, "Error writing sessions data", e);
531 throw e;
532 }
533 }
534
535 @GuardedBy("mBlobsLock")
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800536 private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800537 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
538 return;
539 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800540 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
541 if (sessionsIndexFile == null) {
542 Slog.wtf(TAG, "Error creating sessions index file");
543 return;
Sudheer Shanka9c7eed42020-02-19 11:45:59 -0800544 } else if (!sessionsIndexFile.exists()) {
545 Slog.w(TAG, "Sessions index file not available: " + sessionsIndexFile.getBaseFile());
546 return;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800547 }
548
549 mSessions.clear();
550 try (FileInputStream fis = sessionsIndexFile.openRead()) {
551 final XmlPullParser in = Xml.newPullParser();
552 in.setInput(fis, StandardCharsets.UTF_8.name());
553 XmlUtils.beginDocument(in, TAG_SESSIONS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800554 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800555 while (true) {
556 XmlUtils.nextElement(in);
557 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
558 break;
559 }
560
561 if (TAG_SESSION.equals(in.getName())) {
562 final BlobStoreSession session = BlobStoreSession.createFromXml(
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800563 in, version, mContext, mSessionStateChangeListener);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800564 if (session == null) {
565 continue;
566 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800567 final SparseArray<String> userPackages = allPackages.get(
Sudheer Shankabda89c12020-01-30 15:19:24 -0800568 UserHandle.getUserId(session.getOwnerUid()));
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800569 if (userPackages != null
Sudheer Shankabda89c12020-01-30 15:19:24 -0800570 && session.getOwnerPackageName().equals(
571 userPackages.get(session.getOwnerUid()))) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800572 addSessionForUserLocked(session,
573 UserHandle.getUserId(session.getOwnerUid()));
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800574 } else {
575 // Unknown package or the session data does not belong to this package.
576 session.getSessionFile().delete();
577 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800578 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800579 }
580 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800581 if (LOGV) {
582 Slog.v(TAG, "Finished reading sessions data");
583 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800584 } catch (Exception e) {
585 Slog.wtf(TAG, "Error reading sessions data", e);
586 }
587 }
588
589 @GuardedBy("mBlobsLock")
590 private void writeBlobsInfoLocked() throws Exception {
591 final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
592 if (blobsIndexFile == null) {
593 Slog.wtf(TAG, "Error creating blobs index file");
594 return;
595 }
596 FileOutputStream fos = null;
597 try {
598 fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis());
599 final XmlSerializer out = new FastXmlSerializer();
600 out.setOutput(fos, StandardCharsets.UTF_8.name());
601 out.startDocument(null, true);
602 out.startTag(null, TAG_BLOBS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800603 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800604
605 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
606 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
607 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
608 out.startTag(null, TAG_BLOB);
609 userBlobs.valueAt(j).writeToXml(out);
610 out.endTag(null, TAG_BLOB);
611 }
612 }
613
614 out.endTag(null, TAG_BLOBS);
615 out.endDocument();
616 blobsIndexFile.finishWrite(fos);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800617 if (LOGV) {
618 Slog.v(TAG, "Finished persisting blobs data");
619 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800620 } catch (Exception e) {
621 blobsIndexFile.failWrite(fos);
622 Slog.wtf(TAG, "Error writing blobs data", e);
623 throw e;
624 }
625 }
626
627 @GuardedBy("mBlobsLock")
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800628 private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800629 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
630 return;
631 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800632 final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
633 if (blobsIndexFile == null) {
634 Slog.wtf(TAG, "Error creating blobs index file");
635 return;
Sudheer Shanka9c7eed42020-02-19 11:45:59 -0800636 } else if (!blobsIndexFile.exists()) {
637 Slog.w(TAG, "Blobs index file not available: " + blobsIndexFile.getBaseFile());
638 return;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800639 }
640
641 mBlobsMap.clear();
642 try (FileInputStream fis = blobsIndexFile.openRead()) {
643 final XmlPullParser in = Xml.newPullParser();
644 in.setInput(fis, StandardCharsets.UTF_8.name());
645 XmlUtils.beginDocument(in, TAG_BLOBS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800646 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800647 while (true) {
648 XmlUtils.nextElement(in);
649 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
650 break;
651 }
652
653 if (TAG_BLOB.equals(in.getName())) {
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800654 final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
655 in, version, mContext);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800656 final SparseArray<String> userPackages = allPackages.get(
657 blobMetadata.getUserId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800658 if (userPackages == null) {
659 blobMetadata.getBlobFile().delete();
660 } else {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800661 addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800662 blobMetadata.removeInvalidCommitters(userPackages);
663 blobMetadata.removeInvalidLeasees(userPackages);
664 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800665 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800666 }
667 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800668 if (LOGV) {
669 Slog.v(TAG, "Finished reading blobs data");
670 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800671 } catch (Exception e) {
672 Slog.wtf(TAG, "Error reading blobs data", e);
673 }
674 }
675
676 private void writeBlobsInfo() {
677 synchronized (mBlobsLock) {
678 try {
679 writeBlobsInfoLocked();
680 } catch (Exception e) {
681 // Already logged, ignore
682 }
683 }
684 }
685
686 private void writeBlobsInfoAsync() {
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800687 if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) {
688 mHandler.post(mSaveBlobsInfoRunnable);
689 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800690 }
691
692 private void writeBlobSessions() {
693 synchronized (mBlobsLock) {
694 try {
695 writeBlobSessionsLocked();
696 } catch (Exception e) {
697 // Already logged, ignore
698 }
699 }
700 }
701
702 private void writeBlobSessionsAsync() {
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800703 if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) {
704 mHandler.post(mSaveSessionsRunnable);
705 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800706 }
707
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800708 private int getPackageUid(String packageName, int userId) {
709 final int uid = mPackageManagerInternal.getPackageUid(
710 packageName,
711 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
712 userId);
713 return uid;
714 }
715
716 private SparseArray<SparseArray<String>> getAllPackages() {
717 final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
718 final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
719 for (int userId : allUsers) {
720 final SparseArray<String> userPackages = new SparseArray<>();
721 allPackages.put(userId, userPackages);
722 final List<ApplicationInfo> applicationInfos = mPackageManagerInternal
723 .getInstalledApplications(
724 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
725 | MATCH_UNINSTALLED_PACKAGES,
726 userId, Process.myUid());
727 for (int i = 0, count = applicationInfos.size(); i < count; ++i) {
728 final ApplicationInfo applicationInfo = applicationInfos.get(i);
729 userPackages.put(applicationInfo.uid, applicationInfo.packageName);
730 }
731 }
732 return allPackages;
733 }
734
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800735 AtomicFile prepareSessionsIndexFile() {
736 final File file = BlobStoreConfig.prepareSessionIndexFile();
737 if (file == null) {
738 return null;
739 }
740 return new AtomicFile(file, "session_index" /* commitLogTag */);
741 }
742
743 AtomicFile prepareBlobsIndexFile() {
744 final File file = BlobStoreConfig.prepareBlobsIndexFile();
745 if (file == null) {
746 return null;
747 }
748 return new AtomicFile(file, "blobs_index" /* commitLogTag */);
749 }
750
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800751 @VisibleForTesting
Sudheer Shankabda89c12020-01-30 15:19:24 -0800752 void handlePackageRemoved(String packageName, int uid) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800753 synchronized (mBlobsLock) {
754 // Clean up any pending sessions
755 final LongSparseArray<BlobStoreSession> userSessions =
756 getUserSessionsLocked(UserHandle.getUserId(uid));
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800757 userSessions.removeIf((sessionId, blobStoreSession) -> {
758 if (blobStoreSession.getOwnerUid() == uid
759 && blobStoreSession.getOwnerPackageName().equals(packageName)) {
760 blobStoreSession.getSessionFile().delete();
761 mActiveBlobIds.remove(blobStoreSession.getSessionId());
762 return true;
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800763 }
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800764 return false;
765 });
Sudheer Shankabda89c12020-01-30 15:19:24 -0800766 writeBlobSessionsAsync();
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800767
768 // Remove the package from the committer and leasee list
769 final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
770 getUserBlobsLocked(UserHandle.getUserId(uid));
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800771 userBlobs.entrySet().removeIf(entry -> {
772 final BlobMetadata blobMetadata = entry.getValue();
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800773 blobMetadata.removeCommitter(packageName, uid);
774 blobMetadata.removeLeasee(packageName, uid);
Sudheer Shankabda89c12020-01-30 15:19:24 -0800775 // Delete the blob if it doesn't have any active leases.
776 if (!blobMetadata.hasLeases()) {
777 blobMetadata.getBlobFile().delete();
Sudheer Shanka60803032020-02-13 12:47:59 -0800778 mActiveBlobIds.remove(blobMetadata.getBlobId());
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800779 return true;
Sudheer Shankabda89c12020-01-30 15:19:24 -0800780 }
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800781 return false;
782 });
Sudheer Shankabda89c12020-01-30 15:19:24 -0800783 writeBlobsInfoAsync();
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800784
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800785 if (LOGV) {
786 Slog.v(TAG, "Removed blobs data associated with pkg="
787 + packageName + ", uid=" + uid);
788 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800789 }
790 }
791
792 private void handleUserRemoved(int userId) {
793 synchronized (mBlobsLock) {
794 final LongSparseArray<BlobStoreSession> userSessions =
795 mSessions.removeReturnOld(userId);
796 if (userSessions != null) {
797 for (int i = 0, count = userSessions.size(); i < count; ++i) {
798 final BlobStoreSession session = userSessions.valueAt(i);
799 session.getSessionFile().delete();
Sudheer Shanka60803032020-02-13 12:47:59 -0800800 mActiveBlobIds.remove(session.getSessionId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800801 }
802 }
803
804 final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
805 mBlobsMap.removeReturnOld(userId);
806 if (userBlobs != null) {
807 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
808 final BlobMetadata blobMetadata = userBlobs.valueAt(i);
809 blobMetadata.getBlobFile().delete();
Sudheer Shanka60803032020-02-13 12:47:59 -0800810 mActiveBlobIds.remove(blobMetadata.getBlobId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800811 }
812 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800813 if (LOGV) {
814 Slog.v(TAG, "Removed blobs data in user " + userId);
815 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800816 }
817 }
818
Sudheer Shankaae53d112020-01-31 14:20:53 -0800819 @GuardedBy("mBlobsLock")
820 @VisibleForTesting
821 void handleIdleMaintenanceLocked() {
822 // Cleanup any left over data on disk that is not part of index.
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800823 final ArrayList<Long> deletedBlobIds = new ArrayList<>();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800824 final ArrayList<File> filesToDelete = new ArrayList<>();
825 final File blobsDir = BlobStoreConfig.getBlobsDir();
826 if (blobsDir.exists()) {
827 for (File file : blobsDir.listFiles()) {
828 try {
829 final long id = Long.parseLong(file.getName());
Sudheer Shanka60803032020-02-13 12:47:59 -0800830 if (mActiveBlobIds.indexOf(id) < 0) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800831 filesToDelete.add(file);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800832 deletedBlobIds.add(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800833 }
834 } catch (NumberFormatException e) {
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800835 Slog.wtf(TAG, "Error parsing the file name: " + file, e);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800836 filesToDelete.add(file);
837 }
838 }
839 for (int i = 0, count = filesToDelete.size(); i < count; ++i) {
840 filesToDelete.get(i).delete();
841 }
842 }
843
844 // Cleanup any stale blobs.
845 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
846 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
847 userBlobs.entrySet().removeIf(entry -> {
848 final BlobHandle blobHandle = entry.getKey();
849 final BlobMetadata blobMetadata = entry.getValue();
850 boolean shouldRemove = false;
851
852 // Cleanup expired data blobs.
853 if (blobHandle.isExpired()) {
854 shouldRemove = true;
855 }
856
857 // Cleanup blobs with no active leases.
858 // TODO: Exclude blobs which were just committed.
859 if (!blobMetadata.hasLeases()) {
860 shouldRemove = true;
861 }
862
863 if (shouldRemove) {
864 blobMetadata.getBlobFile().delete();
Sudheer Shanka60803032020-02-13 12:47:59 -0800865 mActiveBlobIds.remove(blobMetadata.getBlobId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800866 deletedBlobIds.add(blobMetadata.getBlobId());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800867 }
868 return shouldRemove;
869 });
870 }
871 writeBlobsInfoAsync();
872
873 // Cleanup any stale sessions.
Sudheer Shankaae53d112020-01-31 14:20:53 -0800874 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
875 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
Sudheer Shanka4acba542020-02-16 13:04:18 -0800876 userSessions.removeIf((sessionId, blobStoreSession) -> {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800877 boolean shouldRemove = false;
878
879 // Cleanup sessions which haven't been modified in a while.
880 if (blobStoreSession.getSessionFile().lastModified()
881 < System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MILLIS) {
882 shouldRemove = true;
883 }
884
885 // Cleanup sessions with already expired data.
886 if (blobStoreSession.getBlobHandle().isExpired()) {
887 shouldRemove = true;
888 }
889
890 if (shouldRemove) {
891 blobStoreSession.getSessionFile().delete();
Sudheer Shanka60803032020-02-13 12:47:59 -0800892 mActiveBlobIds.remove(blobStoreSession.getSessionId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800893 deletedBlobIds.add(blobStoreSession.getSessionId());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800894 }
Sudheer Shanka4acba542020-02-16 13:04:18 -0800895 return shouldRemove;
896 });
Sudheer Shankaae53d112020-01-31 14:20:53 -0800897 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800898 if (LOGV) {
899 Slog.v(TAG, "Completed idle maintenance; deleted "
900 + Arrays.toString(deletedBlobIds.toArray()));
901 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800902 writeBlobSessionsAsync();
903 }
904
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800905 void runClearAllSessions(@UserIdInt int userId) {
906 synchronized (mBlobsLock) {
907 if (userId == UserHandle.USER_ALL) {
908 mSessions.clear();
909 } else {
910 mSessions.remove(userId);
911 }
912 writeBlobSessionsAsync();
913 }
914 }
915
916 void runClearAllBlobs(@UserIdInt int userId) {
917 synchronized (mBlobsLock) {
918 if (userId == UserHandle.USER_ALL) {
919 mBlobsMap.clear();
920 } else {
921 mBlobsMap.remove(userId);
922 }
923 writeBlobsInfoAsync();
924 }
925 }
926
Sudheer Shanka285848d2020-02-16 19:46:17 -0800927 void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
928 synchronized (mBlobsLock) {
929 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
930 final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
931 if (blobMetadata == null) {
932 return;
933 }
934 blobMetadata.getBlobFile().delete();
935 userBlobs.remove(blobHandle);
Sudheer Shanka60803032020-02-13 12:47:59 -0800936 mActiveBlobIds.remove(blobMetadata.getBlobId());
Sudheer Shanka285848d2020-02-16 19:46:17 -0800937 writeBlobsInfoAsync();
938 }
939 }
940
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800941 @GuardedBy("mBlobsLock")
942 private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
943 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
944 final int userId = mSessions.keyAt(i);
945 if (!dumpArgs.shouldDumpUser(userId)) {
946 continue;
947 }
948 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
949 fout.println("List of sessions in user #"
950 + userId + " (" + userSessions.size() + "):");
951 fout.increaseIndent();
952 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
953 final long sessionId = userSessions.keyAt(j);
954 final BlobStoreSession session = userSessions.valueAt(j);
955 if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(),
956 session.getOwnerUid(), session.getSessionId())) {
957 continue;
958 }
959 fout.println("Session #" + sessionId);
960 fout.increaseIndent();
961 session.dump(fout, dumpArgs);
962 fout.decreaseIndent();
963 }
964 fout.decreaseIndent();
965 }
966 }
967
968 @GuardedBy("mBlobsLock")
969 private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
970 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
971 final int userId = mBlobsMap.keyAt(i);
972 if (!dumpArgs.shouldDumpUser(userId)) {
973 continue;
974 }
975 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
976 fout.println("List of blobs in user #"
977 + userId + " (" + userBlobs.size() + "):");
978 fout.increaseIndent();
979 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
980 final BlobMetadata blobMetadata = userBlobs.valueAt(j);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800981 if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800982 continue;
983 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800984 fout.println("Blob #" + blobMetadata.getBlobId());
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800985 fout.increaseIndent();
986 blobMetadata.dump(fout, dumpArgs);
987 fout.decreaseIndent();
988 }
989 fout.decreaseIndent();
990 }
991 }
992
Sudheer Shankae9232d62020-01-23 16:55:34 -0800993 private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
994 @Override
995 public void augmentStatsForPackage(@NonNull PackageStats stats, @NonNull String packageName,
996 @UserIdInt int userId, boolean callerHasStatsPermission) {
997 final AtomicLong blobsDataSize = new AtomicLong(0);
998 forEachSessionInUser(session -> {
999 if (session.getOwnerPackageName().equals(packageName)) {
1000 blobsDataSize.getAndAdd(session.getSize());
1001 }
1002 }, userId);
1003
1004 forEachBlobInUser(blobMetadata -> {
1005 if (blobMetadata.isALeasee(packageName)) {
1006 if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
1007 blobsDataSize.getAndAdd(blobMetadata.getSize());
1008 }
1009 }
1010 }, userId);
1011
1012 stats.dataSize += blobsDataSize.get();
1013 }
1014
1015 @Override
1016 public void augmentStatsForUid(@NonNull PackageStats stats, int uid,
1017 boolean callerHasStatsPermission) {
1018 final int userId = UserHandle.getUserId(uid);
1019 final AtomicLong blobsDataSize = new AtomicLong(0);
1020 forEachSessionInUser(session -> {
1021 if (session.getOwnerUid() == uid) {
1022 blobsDataSize.getAndAdd(session.getSize());
1023 }
1024 }, userId);
1025
1026 forEachBlobInUser(blobMetadata -> {
1027 if (blobMetadata.isALeasee(uid)) {
1028 if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
1029 blobsDataSize.getAndAdd(blobMetadata.getSize());
1030 }
1031 }
1032 }, userId);
1033
1034 stats.dataSize += blobsDataSize.get();
1035 }
1036 }
1037
1038 private void forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId) {
1039 synchronized (mBlobsLock) {
1040 final LongSparseArray<BlobStoreSession> userSessions = getUserSessionsLocked(userId);
1041 for (int i = 0, count = userSessions.size(); i < count; ++i) {
1042 final BlobStoreSession session = userSessions.valueAt(i);
1043 consumer.accept(session);
1044 }
1045 }
1046 }
1047
1048 private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
1049 synchronized (mBlobsLock) {
1050 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1051 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
1052 final BlobMetadata blobMetadata = userBlobs.valueAt(i);
1053 consumer.accept(blobMetadata);
1054 }
1055 }
1056 }
1057
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001058 private class PackageChangedReceiver extends BroadcastReceiver {
1059 @Override
1060 public void onReceive(Context context, Intent intent) {
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001061 if (LOGV) {
1062 Slog.v(TAG, "Received " + intent);
1063 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001064 switch (intent.getAction()) {
1065 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
1066 case Intent.ACTION_PACKAGE_DATA_CLEARED:
1067 final String packageName = intent.getData().getSchemeSpecificPart();
1068 if (packageName == null) {
1069 Slog.wtf(TAG, "Package name is missing in the intent: " + intent);
1070 return;
1071 }
1072 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
1073 if (uid == -1) {
1074 Slog.wtf(TAG, "uid is missing in the intent: " + intent);
1075 return;
1076 }
1077 handlePackageRemoved(packageName, uid);
1078 break;
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001079 default:
1080 Slog.wtf(TAG, "Received unknown intent: " + intent);
1081 }
1082 }
1083 }
1084
1085 private class UserActionReceiver extends BroadcastReceiver {
1086 @Override
1087 public void onReceive(Context context, Intent intent) {
1088 if (LOGV) {
1089 Slog.v(TAG, "Received: " + intent);
1090 }
1091 switch (intent.getAction()) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001092 case Intent.ACTION_USER_REMOVED:
1093 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
1094 USER_NULL);
1095 if (userId == USER_NULL) {
1096 Slog.wtf(TAG, "userId is missing in the intent: " + intent);
1097 return;
1098 }
1099 handleUserRemoved(userId);
1100 break;
1101 default:
1102 Slog.wtf(TAG, "Received unknown intent: " + intent);
1103 }
1104 }
1105 }
1106
Sudheer Shankaf5b36962019-10-04 16:16:13 -07001107 private class Stub extends IBlobStoreManager.Stub {
Sudheer Shankad2da0672019-12-20 15:51:20 -08001108 @Override
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001109 @IntRange(from = 1)
1110 public long createSession(@NonNull BlobHandle blobHandle,
1111 @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001112 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1113 blobHandle.assertIsValid();
1114 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001115
1116 final int callingUid = Binder.getCallingUid();
1117 verifyCallingPackage(callingUid, packageName);
1118
1119 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1120 packageName, UserHandle.getUserId(callingUid))) {
1121 throw new SecurityException("Caller not allowed to create session; "
1122 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1123 }
1124
1125 // TODO: Verify caller request is within limits (no. of calls/blob sessions/blobs)
1126 return createSessionInternal(blobHandle, callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001127 }
1128
1129 @Override
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001130 @NonNull
1131 public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId,
1132 @NonNull String packageName) {
1133 Preconditions.checkArgumentPositive(sessionId,
1134 "sessionId must be positive: " + sessionId);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001135 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001136
1137 final int callingUid = Binder.getCallingUid();
1138 verifyCallingPackage(callingUid, packageName);
1139
1140 return openSessionInternal(sessionId, callingUid, packageName);
1141 }
1142
1143 @Override
1144 public void deleteSession(@IntRange(from = 1) long sessionId,
1145 @NonNull String packageName) {
1146 Preconditions.checkArgumentPositive(sessionId,
1147 "sessionId must be positive: " + sessionId);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001148 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001149
1150 final int callingUid = Binder.getCallingUid();
1151 verifyCallingPackage(callingUid, packageName);
1152
1153 deleteSessionInternal(sessionId, callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001154 }
1155
1156 @Override
1157 public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle,
1158 @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001159 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1160 blobHandle.assertIsValid();
1161 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001162
1163 final int callingUid = Binder.getCallingUid();
1164 verifyCallingPackage(callingUid, packageName);
1165
1166 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1167 packageName, UserHandle.getUserId(callingUid))) {
1168 throw new SecurityException("Caller not allowed to open blob; "
1169 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1170 }
1171
1172 try {
1173 return openBlobInternal(blobHandle, callingUid, packageName);
1174 } catch (IOException e) {
1175 throw ExceptionUtils.wrap(e);
1176 }
Sudheer Shankad2da0672019-12-20 15:51:20 -08001177 }
1178
1179 @Override
1180 public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
Sudheer Shanka1406bc82020-02-03 12:27:24 -08001181 @Nullable CharSequence description,
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001182 @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) {
1183 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1184 blobHandle.assertIsValid();
Sudheer Shanka1406bc82020-02-03 12:27:24 -08001185 Preconditions.checkArgument(
1186 ResourceId.isValid(descriptionResId) || description != null,
1187 "Description must be valid; descriptionId=" + descriptionResId
1188 + ", description=" + description);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001189 Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis,
1190 "leaseExpiryTimeMillis must not be negative");
1191 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001192
1193 final int callingUid = Binder.getCallingUid();
1194 verifyCallingPackage(callingUid, packageName);
1195
Sudheer Shanka1406bc82020-02-03 12:27:24 -08001196 acquireLeaseInternal(blobHandle, descriptionResId, description, leaseExpiryTimeMillis,
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001197 callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001198 }
1199
1200 @Override
1201 public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001202 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1203 blobHandle.assertIsValid();
1204 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001205
1206 final int callingUid = Binder.getCallingUid();
1207 verifyCallingPackage(callingUid, packageName);
1208
1209 releaseLeaseInternal(blobHandle, callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001210 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001211
1212 @Override
1213 public void waitForIdle(@NonNull RemoteCallback remoteCallback) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001214 Objects.requireNonNull(remoteCallback, "remoteCallback must not be null");
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001215
1216 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
1217 "Caller is not allowed to call this; caller=" + Binder.getCallingUid());
1218 mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null)
1219 .recycleOnUse());
1220 }
1221
1222 @Override
1223 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
1224 @Nullable String[] args) {
1225 // TODO: add proto-based version of this.
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001226 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return;
1227
1228 final DumpArgs dumpArgs = DumpArgs.parse(args);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001229
1230 final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " ");
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001231 if (dumpArgs.shouldDumpHelp()) {
1232 writer.println("dumpsys blob_store [options]:");
1233 fout.increaseIndent();
1234 dumpArgs.dumpArgsUsage(fout);
1235 fout.decreaseIndent();
1236 return;
1237 }
1238
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001239 synchronized (mBlobsLock) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001240 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
1241 fout.println();
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001242
1243 if (dumpArgs.shouldDumpSessions()) {
1244 dumpSessionsLocked(fout, dumpArgs);
1245 fout.println();
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001246 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001247 if (dumpArgs.shouldDumpBlobs()) {
1248 dumpBlobsLocked(fout, dumpArgs);
1249 fout.println();
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001250 }
1251 }
1252 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001253
1254 @Override
1255 public int handleShellCommand(@NonNull ParcelFileDescriptor in,
1256 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
1257 @NonNull String[] args) {
1258 return (new BlobStoreManagerShellCommand(BlobStoreManagerService.this)).exec(this,
1259 in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
1260 }
1261 }
1262
1263 static final class DumpArgs {
1264 private boolean mDumpFull;
1265 private final ArrayList<String> mDumpPackages = new ArrayList<>();
1266 private final ArrayList<Integer> mDumpUids = new ArrayList<>();
1267 private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
1268 private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
1269 private boolean mDumpOnlySelectedSections;
1270 private boolean mDumpSessions;
1271 private boolean mDumpBlobs;
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001272 private boolean mDumpHelp;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001273
1274 public boolean shouldDumpSession(String packageName, int uid, long blobId) {
1275 if (!CollectionUtils.isEmpty(mDumpPackages)
1276 && mDumpPackages.indexOf(packageName) < 0) {
1277 return false;
1278 }
1279 if (!CollectionUtils.isEmpty(mDumpUids)
1280 && mDumpUids.indexOf(uid) < 0) {
1281 return false;
1282 }
1283 if (!CollectionUtils.isEmpty(mDumpBlobIds)
1284 && mDumpBlobIds.indexOf(blobId) < 0) {
1285 return false;
1286 }
1287 return true;
1288 }
1289
1290 public boolean shouldDumpSessions() {
1291 if (!mDumpOnlySelectedSections) {
1292 return true;
1293 }
1294 return mDumpSessions;
1295 }
1296
1297 public boolean shouldDumpBlobs() {
1298 if (!mDumpOnlySelectedSections) {
1299 return true;
1300 }
1301 return mDumpBlobs;
1302 }
1303
1304 public boolean shouldDumpBlob(long blobId) {
1305 return CollectionUtils.isEmpty(mDumpBlobIds)
1306 || mDumpBlobIds.indexOf(blobId) >= 0;
1307 }
1308
1309 public boolean shouldDumpFull() {
1310 return mDumpFull;
1311 }
1312
1313 public boolean shouldDumpUser(int userId) {
1314 return CollectionUtils.isEmpty(mDumpUserIds)
1315 || mDumpUserIds.indexOf(userId) >= 0;
1316 }
1317
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001318 public boolean shouldDumpHelp() {
1319 return mDumpHelp;
1320 }
1321
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001322 private DumpArgs() {}
1323
1324 public static DumpArgs parse(String[] args) {
1325 final DumpArgs dumpArgs = new DumpArgs();
1326 if (args == null) {
1327 return dumpArgs;
1328 }
1329
1330 for (int i = 0; i < args.length; ++i) {
1331 final String opt = args[i];
1332 if ("--full".equals(opt) || "-f".equals(opt)) {
1333 final int callingUid = Binder.getCallingUid();
1334 if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
1335 dumpArgs.mDumpFull = true;
1336 }
1337 } else if ("--sessions".equals(opt)) {
1338 dumpArgs.mDumpOnlySelectedSections = true;
1339 dumpArgs.mDumpSessions = true;
1340 } else if ("--blobs".equals(opt)) {
1341 dumpArgs.mDumpOnlySelectedSections = true;
1342 dumpArgs.mDumpBlobs = true;
1343 } else if ("--package".equals(opt) || "-p".equals(opt)) {
1344 dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
1345 } else if ("--uid".equals(opt) || "-u".equals(opt)) {
1346 dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid"));
1347 } else if ("--user".equals(opt)) {
1348 dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
1349 } else if ("--blob".equals(opt) || "-b".equals(opt)) {
1350 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId"));
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001351 } else if ("--help".equals(opt) || "-h".equals(opt)) {
1352 dumpArgs.mDumpHelp = true;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001353 } else {
1354 // Everything else is assumed to be blob ids.
1355 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId"));
1356 }
1357 }
1358 return dumpArgs;
1359 }
1360
1361 private static String getStringArgRequired(String[] args, int index, String argName) {
1362 if (index >= args.length) {
1363 throw new IllegalArgumentException("Missing " + argName);
1364 }
1365 return args[index];
1366 }
1367
1368 private static int getIntArgRequired(String[] args, int index, String argName) {
1369 if (index >= args.length) {
1370 throw new IllegalArgumentException("Missing " + argName);
1371 }
1372 final int value;
1373 try {
1374 value = Integer.parseInt(args[index]);
1375 } catch (NumberFormatException e) {
1376 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1377 }
1378 return value;
1379 }
1380
1381 private static long getLongArgRequired(String[] args, int index, String argName) {
1382 if (index >= args.length) {
1383 throw new IllegalArgumentException("Missing " + argName);
1384 }
1385 final long value;
1386 try {
1387 value = Long.parseLong(args[index]);
1388 } catch (NumberFormatException e) {
1389 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1390 }
1391 return value;
1392 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001393
1394 private void dumpArgsUsage(IndentingPrintWriter pw) {
1395 pw.println("--help | -h");
1396 printWithIndent(pw, "Dump this help text");
1397 pw.println("--sessions");
1398 printWithIndent(pw, "Dump only the sessions info");
1399 pw.println("--blobs");
1400 printWithIndent(pw, "Dump only the committed blobs info");
1401 pw.println("--package | -p [package-name]");
1402 printWithIndent(pw, "Dump blobs info associated with the given package");
1403 pw.println("--uid | -u [uid]");
1404 printWithIndent(pw, "Dump blobs info associated with the given uid");
1405 pw.println("--user [user-id]");
1406 printWithIndent(pw, "Dump blobs info in the given user");
1407 pw.println("--blob | -b [session-id | blob-id]");
1408 printWithIndent(pw, "Dump blob info corresponding to the given ID");
1409 pw.println("--full | -f");
1410 printWithIndent(pw, "Dump full unredacted blobs data");
1411 }
1412
1413 private void printWithIndent(IndentingPrintWriter pw, String str) {
1414 pw.increaseIndent();
1415 pw.println(str);
1416 pw.decreaseIndent();
1417 }
Sudheer Shankaf5b36962019-10-04 16:16:13 -07001418 }
Sudheer Shankabda89c12020-01-30 15:19:24 -08001419
Sudheer Shankaae53d112020-01-31 14:20:53 -08001420 private class LocalService extends BlobStoreManagerInternal {
1421 @Override
1422 public void onIdleMaintenance() {
1423 synchronized (mBlobsLock) {
1424 handleIdleMaintenanceLocked();
1425 }
1426 }
1427 }
1428
Sudheer Shankabda89c12020-01-30 15:19:24 -08001429 @VisibleForTesting
1430 static class Injector {
1431 public Handler initializeMessageHandler() {
1432 return BlobStoreManagerService.initializeMessageHandler();
1433 }
Sudheer Shankab76f7662020-02-27 15:17:43 -08001434
1435 public Handler getBackgroundHandler() {
1436 return BackgroundThread.getHandler();
1437 }
Sudheer Shankabda89c12020-01-30 15:19:24 -08001438 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001439}