blob: f7468d8faa62ce33e7a1673b3ac005d0d111f289 [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;
Sudheer Shanka7d4bd022020-05-20 13:49:17 -070028import static android.os.UserHandle.USER_CURRENT;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080029import static android.os.UserHandle.USER_NULL;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080030
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070031import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_ID;
32import static com.android.server.blob.BlobStoreConfig.INVALID_BLOB_SIZE;
Sudheer Shankae53e1ed2020-02-03 17:15:24 -080033import static com.android.server.blob.BlobStoreConfig.LOGV;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080034import static com.android.server.blob.BlobStoreConfig.TAG;
Sudheer Shanka1406bc82020-02-03 12:27:24 -080035import static com.android.server.blob.BlobStoreConfig.XML_VERSION_CURRENT;
Sudheer Shankaaf3f8872020-03-24 11:26:23 -070036import static com.android.server.blob.BlobStoreConfig.getAdjustedCommitTimeMs;
Sudheer Shankafbda8d72020-06-17 05:01:38 -070037import static com.android.server.blob.BlobStoreConfig.getDeletionOnLastLeaseDelayMs;
Sudheer Shanka07717c92020-06-23 14:06:47 -070038import static com.android.server.blob.BlobStoreConfig.getMaxActiveSessions;
39import static com.android.server.blob.BlobStoreConfig.getMaxCommittedBlobs;
40import static com.android.server.blob.BlobStoreConfig.getMaxLeasedBlobs;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080041import static com.android.server.blob.BlobStoreSession.STATE_ABANDONED;
42import static com.android.server.blob.BlobStoreSession.STATE_COMMITTED;
43import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_INVALID;
44import static com.android.server.blob.BlobStoreSession.STATE_VERIFIED_VALID;
45import static com.android.server.blob.BlobStoreSession.stateToString;
Sudheer Shanka130d79c2020-03-04 13:54:36 -080046import static com.android.server.blob.BlobStoreUtils.getDescriptionResourceId;
47import static com.android.server.blob.BlobStoreUtils.getPackageResources;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080048
Sudheer Shankad2da0672019-12-20 15:51:20 -080049import android.annotation.CurrentTimeSecondsLong;
50import android.annotation.IdRes;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080051import android.annotation.IntRange;
Sudheer Shankad2da0672019-12-20 15:51:20 -080052import android.annotation.NonNull;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080053import android.annotation.Nullable;
Sudheer Shanka5caeed52020-02-01 12:54:33 -080054import android.annotation.UserIdInt;
Sudheer Shanka7d4bd022020-05-20 13:49:17 -070055import android.app.ActivityManager;
56import android.app.ActivityManagerInternal;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070057import android.app.StatsManager;
Sudheer Shankad2da0672019-12-20 15:51:20 -080058import android.app.blob.BlobHandle;
Sudheer Shanka130d79c2020-03-04 13:54:36 -080059import android.app.blob.BlobInfo;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070060import android.app.blob.IBlobStoreManager;
Sudheer Shankad2da0672019-12-20 15:51:20 -080061import android.app.blob.IBlobStoreSession;
Sudheer Shankac6c79942020-03-12 13:20:46 -070062import android.app.blob.LeaseInfo;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080063import android.content.BroadcastReceiver;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070064import android.content.Context;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080065import android.content.Intent;
66import android.content.IntentFilter;
67import android.content.pm.ApplicationInfo;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080068import android.content.pm.PackageManagerInternal;
Sudheer Shankae9232d62020-01-23 16:55:34 -080069import android.content.pm.PackageStats;
Sudheer Shanka4824e3d2020-01-29 23:50:24 -080070import android.content.res.ResourceId;
Sudheer Shanka1b6b8252020-03-04 22:19:21 -080071import android.content.res.Resources;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080072import android.os.Binder;
73import android.os.Handler;
74import android.os.HandlerThread;
Sudheer Shankaa80a9972020-03-20 00:12:14 -070075import android.os.LimitExceededException;
Sudheer Shankad2da0672019-12-20 15:51:20 -080076import android.os.ParcelFileDescriptor;
Sudheer Shankaa80a9972020-03-20 00:12:14 -070077import android.os.ParcelableException;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080078import android.os.Process;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080079import android.os.RemoteCallback;
80import android.os.SystemClock;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080081import android.os.UserHandle;
Sudheer Shanka22f0b162020-01-21 13:32:04 -080082import android.os.UserManagerInternal;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080083import android.util.ArrayMap;
Sudheer Shankaae53d112020-01-31 14:20:53 -080084import android.util.ArraySet;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080085import android.util.AtomicFile;
Sudheer Shankaab1d4162020-01-07 10:37:50 -080086import android.util.ExceptionUtils;
87import android.util.LongSparseArray;
88import android.util.Slog;
89import android.util.SparseArray;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070090import android.util.StatsEvent;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080091import android.util.Xml;
Sudheer Shankaf5b36962019-10-04 16:16:13 -070092
Sudheer Shankaab1d4162020-01-07 10:37:50 -080093import com.android.internal.annotations.GuardedBy;
Sudheer Shankabda89c12020-01-30 15:19:24 -080094import com.android.internal.annotations.VisibleForTesting;
Sudheer Shankab76f7662020-02-27 15:17:43 -080095import com.android.internal.os.BackgroundThread;
Sudheer Shanka5caeed52020-02-01 12:54:33 -080096import com.android.internal.util.CollectionUtils;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -080097import com.android.internal.util.DumpUtils;
98import com.android.internal.util.FastXmlSerializer;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -070099import com.android.internal.util.FrameworkStatsLog;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800100import com.android.internal.util.IndentingPrintWriter;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800101import com.android.internal.util.Preconditions;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800102import com.android.internal.util.XmlUtils;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800103import com.android.internal.util.function.pooled.PooledLambda;
104import com.android.server.LocalServices;
105import com.android.server.ServiceThread;
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700106import com.android.server.SystemService;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800107import com.android.server.Watchdog;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800108import com.android.server.blob.BlobMetadata.Committer;
Sudheer Shankae9232d62020-01-23 16:55:34 -0800109import com.android.server.usage.StorageStatsManagerInternal;
110import com.android.server.usage.StorageStatsManagerInternal.StorageStatsAugmenter;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800111
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800112import org.xmlpull.v1.XmlPullParser;
113import org.xmlpull.v1.XmlSerializer;
114
115import java.io.File;
116import java.io.FileDescriptor;
117import java.io.FileInputStream;
118import java.io.FileOutputStream;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800119import java.io.IOException;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800120import java.io.PrintWriter;
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800121import java.lang.ref.WeakReference;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800122import java.nio.charset.StandardCharsets;
Sudheer Shanka60803032020-02-13 12:47:59 -0800123import java.security.SecureRandom;
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800124import java.util.ArrayList;
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800125import java.util.Arrays;
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800126import java.util.List;
Sudheer Shanka4824e3d2020-01-29 23:50:24 -0800127import java.util.Objects;
Sudheer Shanka60803032020-02-13 12:47:59 -0800128import java.util.Random;
Sudheer Shankaae53d112020-01-31 14:20:53 -0800129import java.util.Set;
Sudheer Shanka07717c92020-06-23 14:06:47 -0700130import java.util.concurrent.atomic.AtomicInteger;
Sudheer Shankae9232d62020-01-23 16:55:34 -0800131import java.util.concurrent.atomic.AtomicLong;
132import java.util.function.Consumer;
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800133import java.util.function.Function;
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700134
135/**
136 * Service responsible for maintaining and facilitating access to data blobs published by apps.
137 */
138public class BlobStoreManagerService extends SystemService {
139
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800140 private final Object mBlobsLock = new Object();
141
142 // Contains data of userId -> {sessionId -> {BlobStoreSession}}.
143 @GuardedBy("mBlobsLock")
144 private final SparseArray<LongSparseArray<BlobStoreSession>> mSessions = new SparseArray<>();
145
146 @GuardedBy("mBlobsLock")
147 private long mCurrentMaxSessionId;
148
149 // Contains data of userId -> {BlobHandle -> {BlobMetadata}}
150 @GuardedBy("mBlobsLock")
151 private final SparseArray<ArrayMap<BlobHandle, BlobMetadata>> mBlobsMap = new SparseArray<>();
152
Sudheer Shankaae53d112020-01-31 14:20:53 -0800153 // Contains all ids that are currently in use.
154 @GuardedBy("mBlobsLock")
Sudheer Shanka60803032020-02-13 12:47:59 -0800155 private final ArraySet<Long> mActiveBlobIds = new ArraySet<>();
156 // Contains all ids that are currently in use and those that were in use but got deleted in the
157 // current boot session.
158 @GuardedBy("mBlobsLock")
Sudheer Shankaae53d112020-01-31 14:20:53 -0800159 private final ArraySet<Long> mKnownBlobIds = new ArraySet<>();
160
Sudheer Shanka60803032020-02-13 12:47:59 -0800161 // Random number generator for new session ids.
162 private final Random mRandom = new SecureRandom();
163
Sudheer Shankad2da0672019-12-20 15:51:20 -0800164 private final Context mContext;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800165 private final Handler mHandler;
Sudheer Shankab76f7662020-02-27 15:17:43 -0800166 private final Handler mBackgroundHandler;
Sudheer Shankabda89c12020-01-30 15:19:24 -0800167 private final Injector mInjector;
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800168 private final SessionStateChangeListener mSessionStateChangeListener =
169 new SessionStateChangeListener();
170
171 private PackageManagerInternal mPackageManagerInternal;
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700172 private StatsManager mStatsManager;
173 private StatsPullAtomCallbackImpl mStatsCallbackImpl = new StatsPullAtomCallbackImpl();
Sudheer Shankad2da0672019-12-20 15:51:20 -0800174
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800175 private final Runnable mSaveBlobsInfoRunnable = this::writeBlobsInfo;
176 private final Runnable mSaveSessionsRunnable = this::writeBlobSessions;
177
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700178 public BlobStoreManagerService(Context context) {
Sudheer Shankabda89c12020-01-30 15:19:24 -0800179 this(context, new Injector());
180 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800181
Sudheer Shankabda89c12020-01-30 15:19:24 -0800182 @VisibleForTesting
183 BlobStoreManagerService(Context context, Injector injector) {
184 super(context);
185
186 mContext = context;
187 mInjector = injector;
188 mHandler = mInjector.initializeMessageHandler();
Sudheer Shankab76f7662020-02-27 15:17:43 -0800189 mBackgroundHandler = mInjector.getBackgroundHandler();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800190 }
191
192 private static Handler initializeMessageHandler() {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800193 final HandlerThread handlerThread = new ServiceThread(TAG,
Sudheer Shankab76f7662020-02-27 15:17:43 -0800194 Process.THREAD_PRIORITY_DEFAULT, true /* allowIo */);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800195 handlerThread.start();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800196 final Handler handler = new Handler(handlerThread.getLooper());
197 Watchdog.getInstance().addThread(handler);
198 return handler;
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700199 }
200
201 @Override
202 public void onStart() {
203 publishBinderService(Context.BLOB_STORE_SERVICE, new Stub());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800204 LocalServices.addService(BlobStoreManagerInternal.class, new LocalService());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800205
206 mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700207 mStatsManager = getContext().getSystemService(StatsManager.class);
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800208 registerReceivers();
Sudheer Shankae9232d62020-01-23 16:55:34 -0800209 LocalServices.getService(StorageStatsManagerInternal.class)
210 .registerStorageStatsAugmenter(new BlobStorageStatsAugmenter(), TAG);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800211 }
212
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800213 @Override
214 public void onBootPhase(int phase) {
Sudheer Shanka364364b2020-02-19 17:56:09 -0800215 if (phase == PHASE_ACTIVITY_MANAGER_READY) {
216 BlobStoreConfig.initialize(mContext);
217 } else if (phase == PHASE_THIRD_PARTY_APPS_CAN_START) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800218 synchronized (mBlobsLock) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800219 final SparseArray<SparseArray<String>> allPackages = getAllPackages();
220 readBlobSessionsLocked(allPackages);
221 readBlobsInfoLocked(allPackages);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800222 }
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700223 registerBlobStorePuller();
Sudheer Shankaae53d112020-01-31 14:20:53 -0800224 } else if (phase == PHASE_BOOT_COMPLETED) {
225 BlobStoreIdleJobService.schedule(mContext);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800226 }
227 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800228
229 @GuardedBy("mBlobsLock")
230 private long generateNextSessionIdLocked() {
Sudheer Shanka60803032020-02-13 12:47:59 -0800231 // Logic borrowed from PackageInstallerService.
232 int n = 0;
233 long sessionId;
234 do {
Sudheer Shanka36b25a12020-06-13 22:27:28 -0700235 final long randomLong = mRandom.nextLong();
236 sessionId = (randomLong == Long.MIN_VALUE) ? INVALID_BLOB_ID : Math.abs(randomLong);
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700237 if (mKnownBlobIds.indexOf(sessionId) < 0 && sessionId != INVALID_BLOB_ID) {
Sudheer Shanka60803032020-02-13 12:47:59 -0800238 return sessionId;
239 }
240 } while (n++ < 32);
241 throw new IllegalStateException("Failed to allocate session ID");
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800242 }
243
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800244 private void registerReceivers() {
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800245 final IntentFilter packageChangedFilter = new IntentFilter();
246 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_FULLY_REMOVED);
247 packageChangedFilter.addAction(Intent.ACTION_PACKAGE_DATA_CLEARED);
248 packageChangedFilter.addDataScheme("package");
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800249 mContext.registerReceiverAsUser(new PackageChangedReceiver(), UserHandle.ALL,
Sudheer Shankafd2611e2020-03-05 02:18:15 -0800250 packageChangedFilter, null, mHandler);
251
252 final IntentFilter userActionFilter = new IntentFilter();
253 userActionFilter.addAction(Intent.ACTION_USER_REMOVED);
254 mContext.registerReceiverAsUser(new UserActionReceiver(), UserHandle.ALL,
255 userActionFilter, null, mHandler);
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800256 }
257
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800258 @GuardedBy("mBlobsLock")
259 private LongSparseArray<BlobStoreSession> getUserSessionsLocked(int userId) {
260 LongSparseArray<BlobStoreSession> userSessions = mSessions.get(userId);
261 if (userSessions == null) {
262 userSessions = new LongSparseArray<>();
263 mSessions.put(userId, userSessions);
264 }
265 return userSessions;
266 }
267
268 @GuardedBy("mBlobsLock")
269 private ArrayMap<BlobHandle, BlobMetadata> getUserBlobsLocked(int userId) {
270 ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.get(userId);
271 if (userBlobs == null) {
272 userBlobs = new ArrayMap<>();
273 mBlobsMap.put(userId, userBlobs);
274 }
275 return userBlobs;
276 }
277
Sudheer Shankabda89c12020-01-30 15:19:24 -0800278 @VisibleForTesting
279 void addUserSessionsForTest(LongSparseArray<BlobStoreSession> userSessions, int userId) {
280 synchronized (mBlobsLock) {
281 mSessions.put(userId, userSessions);
282 }
283 }
284
285 @VisibleForTesting
286 void addUserBlobsForTest(ArrayMap<BlobHandle, BlobMetadata> userBlobs, int userId) {
287 synchronized (mBlobsLock) {
288 mBlobsMap.put(userId, userBlobs);
289 }
290 }
291
Sudheer Shankaae53d112020-01-31 14:20:53 -0800292 @VisibleForTesting
Sudheer Shanka60803032020-02-13 12:47:59 -0800293 void addActiveIdsForTest(long... activeIds) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800294 synchronized (mBlobsLock) {
Sudheer Shanka60803032020-02-13 12:47:59 -0800295 for (long id : activeIds) {
296 addActiveBlobIdLocked(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800297 }
298 }
299 }
300
301 @VisibleForTesting
Sudheer Shanka60803032020-02-13 12:47:59 -0800302 Set<Long> getActiveIdsForTest() {
303 synchronized (mBlobsLock) {
304 return mActiveBlobIds;
305 }
306 }
307
308 @VisibleForTesting
Sudheer Shankaae53d112020-01-31 14:20:53 -0800309 Set<Long> getKnownIdsForTest() {
310 synchronized (mBlobsLock) {
311 return mKnownBlobIds;
312 }
313 }
314
315 @GuardedBy("mBlobsLock")
316 private void addSessionForUserLocked(BlobStoreSession session, int userId) {
317 getUserSessionsLocked(userId).put(session.getSessionId(), session);
Sudheer Shanka60803032020-02-13 12:47:59 -0800318 addActiveBlobIdLocked(session.getSessionId());
Sudheer Shankaae53d112020-01-31 14:20:53 -0800319 }
320
321 @GuardedBy("mBlobsLock")
322 private void addBlobForUserLocked(BlobMetadata blobMetadata, int userId) {
323 addBlobForUserLocked(blobMetadata, getUserBlobsLocked(userId));
324 }
325
326 @GuardedBy("mBlobsLock")
327 private void addBlobForUserLocked(BlobMetadata blobMetadata,
328 ArrayMap<BlobHandle, BlobMetadata> userBlobs) {
329 userBlobs.put(blobMetadata.getBlobHandle(), blobMetadata);
Sudheer Shanka60803032020-02-13 12:47:59 -0800330 addActiveBlobIdLocked(blobMetadata.getBlobId());
331 }
332
333 @GuardedBy("mBlobsLock")
334 private void addActiveBlobIdLocked(long id) {
335 mActiveBlobIds.add(id);
336 mKnownBlobIds.add(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800337 }
338
Sudheer Shanka07717c92020-06-23 14:06:47 -0700339 @GuardedBy("mBlobsLock")
340 private int getSessionsCountLocked(int uid, String packageName) {
341 // TODO: Maintain a counter instead of traversing all the sessions
342 final AtomicInteger sessionsCount = new AtomicInteger(0);
343 forEachSessionInUser(session -> {
344 if (session.getOwnerUid() == uid && session.getOwnerPackageName().equals(packageName)) {
345 sessionsCount.getAndIncrement();
346 }
347 }, UserHandle.getUserId(uid));
348 return sessionsCount.get();
349 }
350
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800351 private long createSessionInternal(BlobHandle blobHandle,
352 int callingUid, String callingPackage) {
353 synchronized (mBlobsLock) {
Sudheer Shanka07717c92020-06-23 14:06:47 -0700354 final int sessionsCount = getSessionsCountLocked(callingUid, callingPackage);
355 if (sessionsCount >= getMaxActiveSessions()) {
356 throw new LimitExceededException("Too many active sessions for the caller: "
357 + sessionsCount);
358 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800359 // TODO: throw if there is already an active session associated with blobHandle.
360 final long sessionId = generateNextSessionIdLocked();
361 final BlobStoreSession session = new BlobStoreSession(mContext,
362 sessionId, blobHandle, callingUid, callingPackage,
363 mSessionStateChangeListener);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800364 addSessionForUserLocked(session, UserHandle.getUserId(callingUid));
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800365 if (LOGV) {
366 Slog.v(TAG, "Created session for " + blobHandle
367 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
368 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800369 writeBlobSessionsAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800370 return sessionId;
371 }
372 }
373
374 private BlobStoreSession openSessionInternal(long sessionId,
375 int callingUid, String callingPackage) {
376 final BlobStoreSession session;
377 synchronized (mBlobsLock) {
378 session = getUserSessionsLocked(
379 UserHandle.getUserId(callingUid)).get(sessionId);
380 if (session == null || !session.hasAccess(callingUid, callingPackage)
381 || session.isFinalized()) {
382 throw new SecurityException("Session not found: " + sessionId);
383 }
384 }
385 session.open();
386 return session;
387 }
388
Sudheer Shanka0b872152020-02-27 10:13:08 -0800389 private void abandonSessionInternal(long sessionId,
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800390 int callingUid, String callingPackage) {
391 synchronized (mBlobsLock) {
392 final BlobStoreSession session = openSessionInternal(sessionId,
393 callingUid, callingPackage);
394 session.open();
395 session.abandon();
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800396 if (LOGV) {
Sudheer Shanka0b872152020-02-27 10:13:08 -0800397 Slog.v(TAG, "Abandoned session with id " + sessionId
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800398 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
399 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800400 writeBlobSessionsAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800401 }
402 }
403
404 private ParcelFileDescriptor openBlobInternal(BlobHandle blobHandle, int callingUid,
405 String callingPackage) throws IOException {
406 synchronized (mBlobsLock) {
407 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
408 .get(blobHandle);
409 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
410 callingPackage, callingUid)) {
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700411 if (blobMetadata == null) {
412 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
413 INVALID_BLOB_ID, INVALID_BLOB_SIZE,
414 FrameworkStatsLog.BLOB_OPENED__RESULT__BLOB_DNE);
415 } else {
416 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
417 blobMetadata.getBlobId(), blobMetadata.getSize(),
418 FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
419 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800420 throw new SecurityException("Caller not allowed to access " + blobHandle
421 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
422 }
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700423
424 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_OPENED, callingUid,
425 blobMetadata.getBlobId(), blobMetadata.getSize(),
426 FrameworkStatsLog.BLOB_OPENED__RESULT__SUCCESS);
427
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800428 return blobMetadata.openForRead(callingPackage);
429 }
430 }
431
Sudheer Shanka07717c92020-06-23 14:06:47 -0700432 @GuardedBy("mBlobsLock")
433 private int getCommittedBlobsCountLocked(int uid, String packageName) {
434 // TODO: Maintain a counter instead of traversing all the blobs
435 final AtomicInteger blobsCount = new AtomicInteger(0);
436 forEachBlobInUser((blobMetadata) -> {
437 if (blobMetadata.isACommitter(packageName, uid)) {
438 blobsCount.getAndIncrement();
439 }
440 }, UserHandle.getUserId(uid));
441 return blobsCount.get();
442 }
443
444 @GuardedBy("mBlobsLock")
445 private int getLeasedBlobsCountLocked(int uid, String packageName) {
446 // TODO: Maintain a counter instead of traversing all the blobs
447 final AtomicInteger blobsCount = new AtomicInteger(0);
448 forEachBlobInUser((blobMetadata) -> {
449 if (blobMetadata.isALeasee(packageName, uid)) {
450 blobsCount.getAndIncrement();
451 }
452 }, UserHandle.getUserId(uid));
453 return blobsCount.get();
454 }
455
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800456 private void acquireLeaseInternal(BlobHandle blobHandle, int descriptionResId,
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800457 CharSequence description, long leaseExpiryTimeMillis,
458 int callingUid, String callingPackage) {
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800459 synchronized (mBlobsLock) {
Sudheer Shanka07717c92020-06-23 14:06:47 -0700460 final int leasesCount = getLeasedBlobsCountLocked(callingUid, callingPackage);
461 if (leasesCount >= getMaxLeasedBlobs()) {
462 throw new LimitExceededException("Too many leased blobs for the caller: "
463 + leasesCount);
464 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800465 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
466 .get(blobHandle);
467 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
468 callingPackage, callingUid)) {
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700469 if (blobMetadata == null) {
470 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
471 INVALID_BLOB_ID, INVALID_BLOB_SIZE,
472 FrameworkStatsLog.BLOB_LEASED__RESULT__BLOB_DNE);
473 } else {
474 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
475 blobMetadata.getBlobId(), blobMetadata.getSize(),
476 FrameworkStatsLog.BLOB_LEASED__RESULT__ACCESS_NOT_ALLOWED);
477 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800478 throw new SecurityException("Caller not allowed to access " + blobHandle
479 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
480 }
Sudheer Shanka51a6c6e2020-02-18 17:28:12 -0800481 if (leaseExpiryTimeMillis != 0 && blobHandle.expiryTimeMillis != 0
482 && leaseExpiryTimeMillis > blobHandle.expiryTimeMillis) {
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700483
484 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
485 blobMetadata.getBlobId(), blobMetadata.getSize(),
486 FrameworkStatsLog.BLOB_LEASED__RESULT__LEASE_EXPIRY_INVALID);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800487 throw new IllegalArgumentException(
488 "Lease expiry cannot be later than blobs expiry time");
489 }
Sudheer Shankaa80a9972020-03-20 00:12:14 -0700490 if (blobMetadata.getSize()
491 > getRemainingLeaseQuotaBytesInternal(callingUid, callingPackage)) {
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700492
493 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
494 blobMetadata.getBlobId(), blobMetadata.getSize(),
495 FrameworkStatsLog.BLOB_LEASED__RESULT__DATA_SIZE_LIMIT_EXCEEDED);
Sudheer Shankaa80a9972020-03-20 00:12:14 -0700496 throw new LimitExceededException("Total amount of data with an active lease"
Sudheer Shanka364364b2020-02-19 17:56:09 -0800497 + " is exceeding the max limit");
498 }
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700499
500 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_LEASED, callingUid,
501 blobMetadata.getBlobId(), blobMetadata.getSize(),
502 FrameworkStatsLog.BLOB_LEASED__RESULT__SUCCESS);
503
Sudheer Shanka0d348262020-03-25 11:54:16 -0700504 blobMetadata.addOrReplaceLeasee(callingPackage, callingUid,
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800505 descriptionResId, description, leaseExpiryTimeMillis);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800506 if (LOGV) {
507 Slog.v(TAG, "Acquired lease on " + blobHandle
508 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
509 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800510 writeBlobsInfoAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800511 }
512 }
513
Sudheer Shanka364364b2020-02-19 17:56:09 -0800514 @VisibleForTesting
515 @GuardedBy("mBlobsLock")
516 long getTotalUsageBytesLocked(int callingUid, String callingPackage) {
517 final AtomicLong totalBytes = new AtomicLong(0);
518 forEachBlobInUser((blobMetadata) -> {
519 if (blobMetadata.isALeasee(callingPackage, callingUid)) {
520 totalBytes.getAndAdd(blobMetadata.getSize());
521 }
522 }, UserHandle.getUserId(callingUid));
523 return totalBytes.get();
524 }
525
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800526 private void releaseLeaseInternal(BlobHandle blobHandle, int callingUid,
527 String callingPackage) {
528 synchronized (mBlobsLock) {
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700529 final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
530 getUserBlobsLocked(UserHandle.getUserId(callingUid));
531 final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800532 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
533 callingPackage, callingUid)) {
534 throw new SecurityException("Caller not allowed to access " + blobHandle
535 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
536 }
537 blobMetadata.removeLeasee(callingPackage, callingUid);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800538 if (LOGV) {
539 Slog.v(TAG, "Released lease on " + blobHandle
540 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
541 }
Sudheer Shankafbda8d72020-06-17 05:01:38 -0700542 if (!blobMetadata.hasLeases()) {
543 mHandler.postDelayed(() -> {
544 synchronized (mBlobsLock) {
545 // Check if blobMetadata object is still valid. If it is not, then
546 // it means that it was already deleted and nothing else to do here.
547 if (!Objects.equals(userBlobs.get(blobHandle), blobMetadata)) {
548 return;
549 }
550 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
551 deleteBlobLocked(blobMetadata);
552 userBlobs.remove(blobHandle);
553 }
554 writeBlobsInfoAsync();
555 }
556 }, getDeletionOnLastLeaseDelayMs());
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -0700557 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800558 writeBlobsInfoAsync();
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800559 }
560 }
561
Sudheer Shankaa80a9972020-03-20 00:12:14 -0700562 private long getRemainingLeaseQuotaBytesInternal(int callingUid, String callingPackage) {
563 synchronized (mBlobsLock) {
564 final long remainingQuota = BlobStoreConfig.getAppDataBytesLimit()
565 - getTotalUsageBytesLocked(callingUid, callingPackage);
566 return remainingQuota > 0 ? remainingQuota : 0;
567 }
568 }
569
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800570 private List<BlobInfo> queryBlobsForUserInternal(int userId) {
571 final ArrayList<BlobInfo> blobInfos = new ArrayList<>();
572 synchronized (mBlobsLock) {
573 final ArrayMap<String, WeakReference<Resources>> resources = new ArrayMap<>();
574 final Function<String, Resources> resourcesGetter = (packageName) -> {
575 final WeakReference<Resources> resourcesRef = resources.get(packageName);
576 Resources packageResources = resourcesRef == null ? null : resourcesRef.get();
577 if (packageResources == null) {
578 packageResources = getPackageResources(mContext, packageName, userId);
579 resources.put(packageName, new WeakReference<>(packageResources));
580 }
581 return packageResources;
582 };
583 getUserBlobsLocked(userId).forEach((blobHandle, blobMetadata) -> {
Sudheer Shankac6c79942020-03-12 13:20:46 -0700584 final ArrayList<LeaseInfo> leaseInfos = new ArrayList<>();
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800585 blobMetadata.forEachLeasee(leasee -> {
586 final int descriptionResId = leasee.descriptionResEntryName == null
587 ? Resources.ID_NULL
588 : getDescriptionResourceId(resourcesGetter.apply(leasee.packageName),
589 leasee.descriptionResEntryName, leasee.packageName);
Sudheer Shankaf25dd772020-06-05 00:47:26 -0700590 final long expiryTimeMs = leasee.expiryTimeMillis == 0
591 ? blobHandle.getExpiryTimeMillis() : leasee.expiryTimeMillis;
592 leaseInfos.add(new LeaseInfo(leasee.packageName, expiryTimeMs,
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800593 descriptionResId, leasee.description));
594 });
595 blobInfos.add(new BlobInfo(blobMetadata.getBlobId(),
Sudheer Shanka65310792020-06-05 02:03:57 -0700596 blobHandle.getExpiryTimeMillis(), blobHandle.getLabel(),
597 blobMetadata.getSize(), leaseInfos));
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800598 });
599 }
600 return blobInfos;
601 }
602
603 private void deleteBlobInternal(long blobId, int callingUid) {
604 synchronized (mBlobsLock) {
605 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
606 UserHandle.getUserId(callingUid));
607 userBlobs.entrySet().removeIf(entry -> {
608 final BlobMetadata blobMetadata = entry.getValue();
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700609 if (blobMetadata.getBlobId() == blobId) {
610 deleteBlobLocked(blobMetadata);
611 return true;
612 }
613 return false;
Sudheer Shanka130d79c2020-03-04 13:54:36 -0800614 });
615 writeBlobsInfoAsync();
616 }
617 }
618
Sudheer Shankac6c79942020-03-12 13:20:46 -0700619 private List<BlobHandle> getLeasedBlobsInternal(int callingUid,
620 @NonNull String callingPackage) {
621 final ArrayList<BlobHandle> leasedBlobs = new ArrayList<>();
622 forEachBlobInUser(blobMetadata -> {
623 if (blobMetadata.isALeasee(callingPackage, callingUid)) {
624 leasedBlobs.add(blobMetadata.getBlobHandle());
625 }
626 }, UserHandle.getUserId(callingUid));
627 return leasedBlobs;
628 }
629
630 private LeaseInfo getLeaseInfoInternal(BlobHandle blobHandle,
631 int callingUid, @NonNull String callingPackage) {
632 synchronized (mBlobsLock) {
633 final BlobMetadata blobMetadata = getUserBlobsLocked(UserHandle.getUserId(callingUid))
634 .get(blobHandle);
635 if (blobMetadata == null || !blobMetadata.isAccessAllowedForCaller(
636 callingPackage, callingUid)) {
637 throw new SecurityException("Caller not allowed to access " + blobHandle
638 + "; callingUid=" + callingUid + ", callingPackage=" + callingPackage);
639 }
640 return blobMetadata.getLeaseInfo(callingPackage, callingUid);
641 }
642 }
643
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800644 private void verifyCallingPackage(int callingUid, String callingPackage) {
645 if (mPackageManagerInternal.getPackageUid(
646 callingPackage, 0, UserHandle.getUserId(callingUid)) != callingUid) {
647 throw new SecurityException("Specified calling package [" + callingPackage
648 + "] does not match the calling uid " + callingUid);
649 }
650 }
651
652 class SessionStateChangeListener {
653 public void onStateChanged(@NonNull BlobStoreSession session) {
654 mHandler.post(PooledLambda.obtainRunnable(
655 BlobStoreManagerService::onStateChangedInternal,
Sudheer Shankab76f7662020-02-27 15:17:43 -0800656 BlobStoreManagerService.this, session).recycleOnUse());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800657 }
658 }
659
660 private void onStateChangedInternal(@NonNull BlobStoreSession session) {
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800661 switch (session.getState()) {
662 case STATE_ABANDONED:
663 case STATE_VERIFIED_INVALID:
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800664 synchronized (mBlobsLock) {
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700665 deleteSessionLocked(session);
Sudheer Shankabda89c12020-01-30 15:19:24 -0800666 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
667 .remove(session.getSessionId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800668 if (LOGV) {
669 Slog.v(TAG, "Session is invalid; deleted " + session);
670 }
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800671 }
672 break;
673 case STATE_COMMITTED:
Sudheer Shankab76f7662020-02-27 15:17:43 -0800674 mBackgroundHandler.post(() -> {
675 session.computeDigest();
676 mHandler.post(PooledLambda.obtainRunnable(
677 BlobStoreSession::verifyBlobData, session).recycleOnUse());
678 });
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800679 break;
680 case STATE_VERIFIED_VALID:
681 synchronized (mBlobsLock) {
Sudheer Shanka07717c92020-06-23 14:06:47 -0700682 final int committedBlobsCount = getCommittedBlobsCountLocked(
683 session.getOwnerUid(), session.getOwnerPackageName());
684 if (committedBlobsCount >= getMaxCommittedBlobs()) {
685 Slog.d(TAG, "Failed to commit: too many committed blobs. count: "
686 + committedBlobsCount + "; blob: " + session);
687 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700688 deleteSessionLocked(session);
Sudheer Shanka07717c92020-06-23 14:06:47 -0700689 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
690 .remove(session.getSessionId());
691 break;
692 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800693 final int userId = UserHandle.getUserId(session.getOwnerUid());
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800694 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(
695 userId);
Sudheer Shankabda89c12020-01-30 15:19:24 -0800696 BlobMetadata blob = userBlobs.get(session.getBlobHandle());
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800697 if (blob == null) {
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700698 blob = new BlobMetadata(mContext, session.getSessionId(),
699 session.getBlobHandle(), userId);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800700 addBlobForUserLocked(blob, userBlobs);
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800701 }
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700702 final Committer existingCommitter = blob.getExistingCommitter(
703 session.getOwnerPackageName(), session.getOwnerUid());
704 final long existingCommitTimeMs =
705 (existingCommitter == null) ? 0 : existingCommitter.getCommitTimeMs();
Sudheer Shankabda89c12020-01-30 15:19:24 -0800706 final Committer newCommitter = new Committer(session.getOwnerPackageName(),
Sudheer Shankaaf3f8872020-03-24 11:26:23 -0700707 session.getOwnerUid(), session.getBlobAccessMode(),
708 getAdjustedCommitTimeMs(existingCommitTimeMs,
709 System.currentTimeMillis()));
Sudheer Shanka0d348262020-03-25 11:54:16 -0700710 blob.addOrReplaceCommitter(newCommitter);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800711 try {
712 writeBlobsInfoLocked();
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700713 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
714 session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
715 FrameworkStatsLog.BLOB_COMMITTED__RESULT__SUCCESS);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800716 session.sendCommitCallbackResult(COMMIT_RESULT_SUCCESS);
717 } catch (Exception e) {
Sudheer Shanka0d348262020-03-25 11:54:16 -0700718 if (existingCommitter == null) {
719 blob.removeCommitter(newCommitter);
720 } else {
721 blob.addOrReplaceCommitter(existingCommitter);
722 }
Sudheer Shanka07717c92020-06-23 14:06:47 -0700723 Slog.d(TAG, "Error committing the blob: " + session, e);
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -0700724 FrameworkStatsLog.write(FrameworkStatsLog.BLOB_COMMITTED,
725 session.getOwnerUid(), blob.getBlobId(), blob.getSize(),
726 FrameworkStatsLog.BLOB_COMMITTED__RESULT__ERROR_DURING_COMMIT);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800727 session.sendCommitCallbackResult(COMMIT_RESULT_ERROR);
Sudheer Shanka36b25a12020-06-13 22:27:28 -0700728 // If the commit fails and this blob data didn't exist before, delete it.
729 // But if it is a recommit, just leave it as is.
730 if (session.getSessionId() == blob.getBlobId()) {
731 deleteBlobLocked(blob);
732 userBlobs.remove(blob.getBlobHandle());
733 }
734 }
735 // Delete redundant data from recommits.
736 if (session.getSessionId() != blob.getBlobId()) {
Sudheer Shanka9ed72492020-06-24 13:33:18 -0700737 deleteSessionLocked(session);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800738 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800739 getUserSessionsLocked(UserHandle.getUserId(session.getOwnerUid()))
740 .remove(session.getSessionId());
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800741 if (LOGV) {
742 Slog.v(TAG, "Successfully committed session " + session);
743 }
Sudheer Shankaab4a8ae2020-02-18 15:11:42 -0800744 }
745 break;
746 default:
747 Slog.wtf(TAG, "Invalid session state: "
748 + stateToString(session.getState()));
749 }
750 synchronized (mBlobsLock) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800751 try {
752 writeBlobSessionsLocked();
753 } catch (Exception e) {
754 // already logged, ignore.
755 }
Sudheer Shankaab1d4162020-01-07 10:37:50 -0800756 }
Sudheer Shankaf5b36962019-10-04 16:16:13 -0700757 }
758
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800759 @GuardedBy("mBlobsLock")
760 private void writeBlobSessionsLocked() throws Exception {
761 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
762 if (sessionsIndexFile == null) {
763 Slog.wtf(TAG, "Error creating sessions index file");
764 return;
765 }
766 FileOutputStream fos = null;
767 try {
768 fos = sessionsIndexFile.startWrite(SystemClock.uptimeMillis());
769 final XmlSerializer out = new FastXmlSerializer();
770 out.setOutput(fos, StandardCharsets.UTF_8.name());
771 out.startDocument(null, true);
772 out.startTag(null, TAG_SESSIONS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800773 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800774
775 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
776 final LongSparseArray<BlobStoreSession> userSessions =
777 mSessions.valueAt(i);
778 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
779 out.startTag(null, TAG_SESSION);
780 userSessions.valueAt(j).writeToXml(out);
781 out.endTag(null, TAG_SESSION);
782 }
783 }
784
785 out.endTag(null, TAG_SESSIONS);
786 out.endDocument();
787 sessionsIndexFile.finishWrite(fos);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800788 if (LOGV) {
789 Slog.v(TAG, "Finished persisting sessions data");
790 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800791 } catch (Exception e) {
792 sessionsIndexFile.failWrite(fos);
793 Slog.wtf(TAG, "Error writing sessions data", e);
794 throw e;
795 }
796 }
797
798 @GuardedBy("mBlobsLock")
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800799 private void readBlobSessionsLocked(SparseArray<SparseArray<String>> allPackages) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800800 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
801 return;
802 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800803 final AtomicFile sessionsIndexFile = prepareSessionsIndexFile();
804 if (sessionsIndexFile == null) {
805 Slog.wtf(TAG, "Error creating sessions index file");
806 return;
Sudheer Shanka9c7eed42020-02-19 11:45:59 -0800807 } else if (!sessionsIndexFile.exists()) {
808 Slog.w(TAG, "Sessions index file not available: " + sessionsIndexFile.getBaseFile());
809 return;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800810 }
811
812 mSessions.clear();
813 try (FileInputStream fis = sessionsIndexFile.openRead()) {
814 final XmlPullParser in = Xml.newPullParser();
815 in.setInput(fis, StandardCharsets.UTF_8.name());
816 XmlUtils.beginDocument(in, TAG_SESSIONS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800817 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800818 while (true) {
819 XmlUtils.nextElement(in);
820 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
821 break;
822 }
823
824 if (TAG_SESSION.equals(in.getName())) {
825 final BlobStoreSession session = BlobStoreSession.createFromXml(
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800826 in, version, mContext, mSessionStateChangeListener);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800827 if (session == null) {
828 continue;
829 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800830 final SparseArray<String> userPackages = allPackages.get(
Sudheer Shankabda89c12020-01-30 15:19:24 -0800831 UserHandle.getUserId(session.getOwnerUid()));
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800832 if (userPackages != null
Sudheer Shankabda89c12020-01-30 15:19:24 -0800833 && session.getOwnerPackageName().equals(
834 userPackages.get(session.getOwnerUid()))) {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800835 addSessionForUserLocked(session,
836 UserHandle.getUserId(session.getOwnerUid()));
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800837 } else {
838 // Unknown package or the session data does not belong to this package.
839 session.getSessionFile().delete();
840 }
Sudheer Shankabda89c12020-01-30 15:19:24 -0800841 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, session.getSessionId());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800842 }
843 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800844 if (LOGV) {
845 Slog.v(TAG, "Finished reading sessions data");
846 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800847 } catch (Exception e) {
848 Slog.wtf(TAG, "Error reading sessions data", e);
849 }
850 }
851
852 @GuardedBy("mBlobsLock")
853 private void writeBlobsInfoLocked() throws Exception {
854 final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
855 if (blobsIndexFile == null) {
856 Slog.wtf(TAG, "Error creating blobs index file");
857 return;
858 }
859 FileOutputStream fos = null;
860 try {
861 fos = blobsIndexFile.startWrite(SystemClock.uptimeMillis());
862 final XmlSerializer out = new FastXmlSerializer();
863 out.setOutput(fos, StandardCharsets.UTF_8.name());
864 out.startDocument(null, true);
865 out.startTag(null, TAG_BLOBS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800866 XmlUtils.writeIntAttribute(out, ATTR_VERSION, XML_VERSION_CURRENT);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800867
868 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
869 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
870 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
871 out.startTag(null, TAG_BLOB);
872 userBlobs.valueAt(j).writeToXml(out);
873 out.endTag(null, TAG_BLOB);
874 }
875 }
876
877 out.endTag(null, TAG_BLOBS);
878 out.endDocument();
879 blobsIndexFile.finishWrite(fos);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800880 if (LOGV) {
881 Slog.v(TAG, "Finished persisting blobs data");
882 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800883 } catch (Exception e) {
884 blobsIndexFile.failWrite(fos);
885 Slog.wtf(TAG, "Error writing blobs data", e);
886 throw e;
887 }
888 }
889
890 @GuardedBy("mBlobsLock")
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800891 private void readBlobsInfoLocked(SparseArray<SparseArray<String>> allPackages) {
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800892 if (!BlobStoreConfig.getBlobStoreRootDir().exists()) {
893 return;
894 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800895 final AtomicFile blobsIndexFile = prepareBlobsIndexFile();
896 if (blobsIndexFile == null) {
897 Slog.wtf(TAG, "Error creating blobs index file");
898 return;
Sudheer Shanka9c7eed42020-02-19 11:45:59 -0800899 } else if (!blobsIndexFile.exists()) {
900 Slog.w(TAG, "Blobs index file not available: " + blobsIndexFile.getBaseFile());
901 return;
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800902 }
903
904 mBlobsMap.clear();
905 try (FileInputStream fis = blobsIndexFile.openRead()) {
906 final XmlPullParser in = Xml.newPullParser();
907 in.setInput(fis, StandardCharsets.UTF_8.name());
908 XmlUtils.beginDocument(in, TAG_BLOBS);
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800909 final int version = XmlUtils.readIntAttribute(in, ATTR_VERSION);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800910 while (true) {
911 XmlUtils.nextElement(in);
912 if (in.getEventType() == XmlPullParser.END_DOCUMENT) {
913 break;
914 }
915
916 if (TAG_BLOB.equals(in.getName())) {
Sudheer Shanka1406bc82020-02-03 12:27:24 -0800917 final BlobMetadata blobMetadata = BlobMetadata.createFromXml(
918 in, version, mContext);
Sudheer Shankaae53d112020-01-31 14:20:53 -0800919 final SparseArray<String> userPackages = allPackages.get(
920 blobMetadata.getUserId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800921 if (userPackages == null) {
922 blobMetadata.getBlobFile().delete();
923 } else {
Sudheer Shankaae53d112020-01-31 14:20:53 -0800924 addBlobForUserLocked(blobMetadata, blobMetadata.getUserId());
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800925 blobMetadata.removeInvalidCommitters(userPackages);
926 blobMetadata.removeInvalidLeasees(userPackages);
927 }
Sudheer Shankaae53d112020-01-31 14:20:53 -0800928 mCurrentMaxSessionId = Math.max(mCurrentMaxSessionId, blobMetadata.getBlobId());
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800929 }
930 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -0800931 if (LOGV) {
932 Slog.v(TAG, "Finished reading blobs data");
933 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800934 } catch (Exception e) {
935 Slog.wtf(TAG, "Error reading blobs data", e);
936 }
937 }
938
939 private void writeBlobsInfo() {
940 synchronized (mBlobsLock) {
941 try {
942 writeBlobsInfoLocked();
943 } catch (Exception e) {
944 // Already logged, ignore
945 }
946 }
947 }
948
949 private void writeBlobsInfoAsync() {
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800950 if (!mHandler.hasCallbacks(mSaveBlobsInfoRunnable)) {
951 mHandler.post(mSaveBlobsInfoRunnable);
952 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800953 }
954
955 private void writeBlobSessions() {
956 synchronized (mBlobsLock) {
957 try {
958 writeBlobSessionsLocked();
959 } catch (Exception e) {
960 // Already logged, ignore
961 }
962 }
963 }
964
965 private void writeBlobSessionsAsync() {
Sudheer Shanka5caeed52020-02-01 12:54:33 -0800966 if (!mHandler.hasCallbacks(mSaveSessionsRunnable)) {
967 mHandler.post(mSaveSessionsRunnable);
968 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800969 }
970
Sudheer Shanka22f0b162020-01-21 13:32:04 -0800971 private int getPackageUid(String packageName, int userId) {
972 final int uid = mPackageManagerInternal.getPackageUid(
973 packageName,
974 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE | MATCH_UNINSTALLED_PACKAGES,
975 userId);
976 return uid;
977 }
978
979 private SparseArray<SparseArray<String>> getAllPackages() {
980 final SparseArray<SparseArray<String>> allPackages = new SparseArray<>();
981 final int[] allUsers = LocalServices.getService(UserManagerInternal.class).getUserIds();
982 for (int userId : allUsers) {
983 final SparseArray<String> userPackages = new SparseArray<>();
984 allPackages.put(userId, userPackages);
985 final List<ApplicationInfo> applicationInfos = mPackageManagerInternal
986 .getInstalledApplications(
987 MATCH_DIRECT_BOOT_AWARE | MATCH_DIRECT_BOOT_UNAWARE
988 | MATCH_UNINSTALLED_PACKAGES,
989 userId, Process.myUid());
990 for (int i = 0, count = applicationInfos.size(); i < count; ++i) {
991 final ApplicationInfo applicationInfo = applicationInfos.get(i);
992 userPackages.put(applicationInfo.uid, applicationInfo.packageName);
993 }
994 }
995 return allPackages;
996 }
997
Sudheer Shankaf6e23b92020-01-15 01:51:15 -0800998 AtomicFile prepareSessionsIndexFile() {
999 final File file = BlobStoreConfig.prepareSessionIndexFile();
1000 if (file == null) {
1001 return null;
1002 }
1003 return new AtomicFile(file, "session_index" /* commitLogTag */);
1004 }
1005
1006 AtomicFile prepareBlobsIndexFile() {
1007 final File file = BlobStoreConfig.prepareBlobsIndexFile();
1008 if (file == null) {
1009 return null;
1010 }
1011 return new AtomicFile(file, "blobs_index" /* commitLogTag */);
1012 }
1013
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001014 @VisibleForTesting
Sudheer Shankabda89c12020-01-30 15:19:24 -08001015 void handlePackageRemoved(String packageName, int uid) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001016 synchronized (mBlobsLock) {
1017 // Clean up any pending sessions
1018 final LongSparseArray<BlobStoreSession> userSessions =
1019 getUserSessionsLocked(UserHandle.getUserId(uid));
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001020 userSessions.removeIf((sessionId, blobStoreSession) -> {
1021 if (blobStoreSession.getOwnerUid() == uid
1022 && blobStoreSession.getOwnerPackageName().equals(packageName)) {
Sudheer Shanka9ed72492020-06-24 13:33:18 -07001023 deleteSessionLocked(blobStoreSession);
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001024 return true;
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001025 }
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001026 return false;
1027 });
Sudheer Shankabda89c12020-01-30 15:19:24 -08001028 writeBlobSessionsAsync();
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001029
1030 // Remove the package from the committer and leasee list
1031 final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
1032 getUserBlobsLocked(UserHandle.getUserId(uid));
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001033 userBlobs.entrySet().removeIf(entry -> {
1034 final BlobMetadata blobMetadata = entry.getValue();
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001035 final boolean isACommitter = blobMetadata.isACommitter(packageName, uid);
1036 if (isACommitter) {
1037 blobMetadata.removeCommitter(packageName, uid);
1038 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001039 blobMetadata.removeLeasee(packageName, uid);
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001040 // Regardless of when the blob is committed, we need to delete
1041 // it if it was from the deleted package to ensure we delete all traces of it.
1042 if (blobMetadata.shouldBeDeleted(isACommitter /* respectLeaseWaitTime */)) {
1043 deleteBlobLocked(blobMetadata);
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001044 return true;
Sudheer Shankabda89c12020-01-30 15:19:24 -08001045 }
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001046 return false;
1047 });
Sudheer Shankabda89c12020-01-30 15:19:24 -08001048 writeBlobsInfoAsync();
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001049
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001050 if (LOGV) {
1051 Slog.v(TAG, "Removed blobs data associated with pkg="
1052 + packageName + ", uid=" + uid);
1053 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001054 }
1055 }
1056
1057 private void handleUserRemoved(int userId) {
1058 synchronized (mBlobsLock) {
1059 final LongSparseArray<BlobStoreSession> userSessions =
1060 mSessions.removeReturnOld(userId);
1061 if (userSessions != null) {
1062 for (int i = 0, count = userSessions.size(); i < count; ++i) {
1063 final BlobStoreSession session = userSessions.valueAt(i);
Sudheer Shanka9ed72492020-06-24 13:33:18 -07001064 deleteSessionLocked(session);
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001065 }
1066 }
1067
1068 final ArrayMap<BlobHandle, BlobMetadata> userBlobs =
1069 mBlobsMap.removeReturnOld(userId);
1070 if (userBlobs != null) {
1071 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
1072 final BlobMetadata blobMetadata = userBlobs.valueAt(i);
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001073 deleteBlobLocked(blobMetadata);
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001074 }
1075 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001076 if (LOGV) {
1077 Slog.v(TAG, "Removed blobs data in user " + userId);
1078 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001079 }
1080 }
1081
Sudheer Shankaae53d112020-01-31 14:20:53 -08001082 @GuardedBy("mBlobsLock")
1083 @VisibleForTesting
1084 void handleIdleMaintenanceLocked() {
1085 // Cleanup any left over data on disk that is not part of index.
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001086 final ArrayList<Long> deletedBlobIds = new ArrayList<>();
Sudheer Shankaae53d112020-01-31 14:20:53 -08001087 final ArrayList<File> filesToDelete = new ArrayList<>();
1088 final File blobsDir = BlobStoreConfig.getBlobsDir();
1089 if (blobsDir.exists()) {
1090 for (File file : blobsDir.listFiles()) {
1091 try {
1092 final long id = Long.parseLong(file.getName());
Sudheer Shanka60803032020-02-13 12:47:59 -08001093 if (mActiveBlobIds.indexOf(id) < 0) {
Sudheer Shankaae53d112020-01-31 14:20:53 -08001094 filesToDelete.add(file);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001095 deletedBlobIds.add(id);
Sudheer Shankaae53d112020-01-31 14:20:53 -08001096 }
1097 } catch (NumberFormatException e) {
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001098 Slog.wtf(TAG, "Error parsing the file name: " + file, e);
Sudheer Shankaae53d112020-01-31 14:20:53 -08001099 filesToDelete.add(file);
1100 }
1101 }
1102 for (int i = 0, count = filesToDelete.size(); i < count; ++i) {
1103 filesToDelete.get(i).delete();
1104 }
1105 }
1106
1107 // Cleanup any stale blobs.
1108 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1109 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1110 userBlobs.entrySet().removeIf(entry -> {
Sudheer Shankaae53d112020-01-31 14:20:53 -08001111 final BlobMetadata blobMetadata = entry.getValue();
Sudheer Shankaae53d112020-01-31 14:20:53 -08001112
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001113 if (blobMetadata.shouldBeDeleted(true /* respectLeaseWaitTime */)) {
1114 deleteBlobLocked(blobMetadata);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001115 deletedBlobIds.add(blobMetadata.getBlobId());
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001116 return true;
Sudheer Shankaae53d112020-01-31 14:20:53 -08001117 }
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001118 return false;
Sudheer Shankaae53d112020-01-31 14:20:53 -08001119 });
1120 }
1121 writeBlobsInfoAsync();
1122
1123 // Cleanup any stale sessions.
Sudheer Shankaae53d112020-01-31 14:20:53 -08001124 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1125 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
Sudheer Shanka4acba542020-02-16 13:04:18 -08001126 userSessions.removeIf((sessionId, blobStoreSession) -> {
Sudheer Shankaae53d112020-01-31 14:20:53 -08001127 boolean shouldRemove = false;
1128
1129 // Cleanup sessions which haven't been modified in a while.
Sudheer Shankab2a17b72020-05-28 01:14:10 -07001130 if (blobStoreSession.isExpired()) {
Sudheer Shankaae53d112020-01-31 14:20:53 -08001131 shouldRemove = true;
1132 }
1133
1134 // Cleanup sessions with already expired data.
1135 if (blobStoreSession.getBlobHandle().isExpired()) {
1136 shouldRemove = true;
1137 }
1138
1139 if (shouldRemove) {
Sudheer Shanka9ed72492020-06-24 13:33:18 -07001140 deleteSessionLocked(blobStoreSession);
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001141 deletedBlobIds.add(blobStoreSession.getSessionId());
Sudheer Shankaae53d112020-01-31 14:20:53 -08001142 }
Sudheer Shanka4acba542020-02-16 13:04:18 -08001143 return shouldRemove;
1144 });
Sudheer Shankaae53d112020-01-31 14:20:53 -08001145 }
Sudheer Shanka75351542020-06-05 22:42:56 -07001146 Slog.d(TAG, "Completed idle maintenance; deleted "
1147 + Arrays.toString(deletedBlobIds.toArray()));
Sudheer Shankaae53d112020-01-31 14:20:53 -08001148 writeBlobSessionsAsync();
1149 }
1150
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001151 @GuardedBy("mBlobsLock")
Sudheer Shanka9ed72492020-06-24 13:33:18 -07001152 private void deleteSessionLocked(BlobStoreSession blobStoreSession) {
1153 blobStoreSession.destroy();
1154 mActiveBlobIds.remove(blobStoreSession.getSessionId());
1155 }
1156
1157 @GuardedBy("mBlobsLock")
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001158 private void deleteBlobLocked(BlobMetadata blobMetadata) {
Sudheer Shanka9ed72492020-06-24 13:33:18 -07001159 blobMetadata.destroy();
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001160 mActiveBlobIds.remove(blobMetadata.getBlobId());
1161 }
1162
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001163 void runClearAllSessions(@UserIdInt int userId) {
1164 synchronized (mBlobsLock) {
Sudheer Shanka07717c92020-06-23 14:06:47 -07001165 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1166 final int sessionUserId = mSessions.keyAt(i);
1167 if (userId != UserHandle.USER_ALL && userId != sessionUserId) {
1168 continue;
1169 }
1170 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
1171 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
1172 mActiveBlobIds.remove(userSessions.valueAt(j).getSessionId());
1173 }
1174 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001175 if (userId == UserHandle.USER_ALL) {
1176 mSessions.clear();
1177 } else {
1178 mSessions.remove(userId);
1179 }
1180 writeBlobSessionsAsync();
1181 }
1182 }
1183
1184 void runClearAllBlobs(@UserIdInt int userId) {
1185 synchronized (mBlobsLock) {
Sudheer Shanka07717c92020-06-23 14:06:47 -07001186 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1187 final int blobUserId = mBlobsMap.keyAt(i);
1188 if (userId != UserHandle.USER_ALL && userId != blobUserId) {
1189 continue;
1190 }
1191 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1192 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1193 mActiveBlobIds.remove(userBlobs.valueAt(j).getBlobId());
1194 }
1195 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001196 if (userId == UserHandle.USER_ALL) {
1197 mBlobsMap.clear();
1198 } else {
1199 mBlobsMap.remove(userId);
1200 }
1201 writeBlobsInfoAsync();
1202 }
1203 }
1204
Sudheer Shanka285848d2020-02-16 19:46:17 -08001205 void deleteBlob(@NonNull BlobHandle blobHandle, @UserIdInt int userId) {
1206 synchronized (mBlobsLock) {
1207 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1208 final BlobMetadata blobMetadata = userBlobs.get(blobHandle);
1209 if (blobMetadata == null) {
1210 return;
1211 }
Sudheer Shankac0fd5fa2020-03-15 23:31:37 -07001212 deleteBlobLocked(blobMetadata);
Sudheer Shanka285848d2020-02-16 19:46:17 -08001213 userBlobs.remove(blobHandle);
Sudheer Shanka285848d2020-02-16 19:46:17 -08001214 writeBlobsInfoAsync();
1215 }
1216 }
1217
Sudheer Shankab96c18b2020-03-05 01:03:50 -08001218 void runIdleMaintenance() {
1219 synchronized (mBlobsLock) {
1220 handleIdleMaintenanceLocked();
1221 }
1222 }
1223
Sudheer Shanka8992c052020-05-26 18:10:09 -07001224 boolean isBlobAvailable(long blobId, int userId) {
1225 synchronized (mBlobsLock) {
1226 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1227 for (BlobMetadata blobMetadata : userBlobs.values()) {
1228 if (blobMetadata.getBlobId() == blobId) {
1229 return true;
1230 }
1231 }
1232 return false;
1233 }
1234 }
1235
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001236 @GuardedBy("mBlobsLock")
1237 private void dumpSessionsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
1238 for (int i = 0, userCount = mSessions.size(); i < userCount; ++i) {
1239 final int userId = mSessions.keyAt(i);
1240 if (!dumpArgs.shouldDumpUser(userId)) {
1241 continue;
1242 }
1243 final LongSparseArray<BlobStoreSession> userSessions = mSessions.valueAt(i);
1244 fout.println("List of sessions in user #"
1245 + userId + " (" + userSessions.size() + "):");
1246 fout.increaseIndent();
1247 for (int j = 0, sessionsCount = userSessions.size(); j < sessionsCount; ++j) {
1248 final long sessionId = userSessions.keyAt(j);
1249 final BlobStoreSession session = userSessions.valueAt(j);
1250 if (!dumpArgs.shouldDumpSession(session.getOwnerPackageName(),
1251 session.getOwnerUid(), session.getSessionId())) {
1252 continue;
1253 }
1254 fout.println("Session #" + sessionId);
1255 fout.increaseIndent();
1256 session.dump(fout, dumpArgs);
1257 fout.decreaseIndent();
1258 }
1259 fout.decreaseIndent();
1260 }
1261 }
1262
1263 @GuardedBy("mBlobsLock")
1264 private void dumpBlobsLocked(IndentingPrintWriter fout, DumpArgs dumpArgs) {
1265 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1266 final int userId = mBlobsMap.keyAt(i);
1267 if (!dumpArgs.shouldDumpUser(userId)) {
1268 continue;
1269 }
1270 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1271 fout.println("List of blobs in user #"
1272 + userId + " (" + userBlobs.size() + "):");
1273 fout.increaseIndent();
1274 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1275 final BlobMetadata blobMetadata = userBlobs.valueAt(j);
Sudheer Shankaae53d112020-01-31 14:20:53 -08001276 if (!dumpArgs.shouldDumpBlob(blobMetadata.getBlobId())) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001277 continue;
1278 }
Sudheer Shankaae53d112020-01-31 14:20:53 -08001279 fout.println("Blob #" + blobMetadata.getBlobId());
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001280 fout.increaseIndent();
1281 blobMetadata.dump(fout, dumpArgs);
1282 fout.decreaseIndent();
1283 }
1284 fout.decreaseIndent();
1285 }
1286 }
1287
Sudheer Shankae9232d62020-01-23 16:55:34 -08001288 private class BlobStorageStatsAugmenter implements StorageStatsAugmenter {
1289 @Override
1290 public void augmentStatsForPackage(@NonNull PackageStats stats, @NonNull String packageName,
1291 @UserIdInt int userId, boolean callerHasStatsPermission) {
1292 final AtomicLong blobsDataSize = new AtomicLong(0);
1293 forEachSessionInUser(session -> {
1294 if (session.getOwnerPackageName().equals(packageName)) {
1295 blobsDataSize.getAndAdd(session.getSize());
1296 }
1297 }, userId);
1298
1299 forEachBlobInUser(blobMetadata -> {
1300 if (blobMetadata.isALeasee(packageName)) {
1301 if (!blobMetadata.hasOtherLeasees(packageName) || !callerHasStatsPermission) {
1302 blobsDataSize.getAndAdd(blobMetadata.getSize());
1303 }
1304 }
1305 }, userId);
1306
1307 stats.dataSize += blobsDataSize.get();
1308 }
1309
1310 @Override
1311 public void augmentStatsForUid(@NonNull PackageStats stats, int uid,
1312 boolean callerHasStatsPermission) {
1313 final int userId = UserHandle.getUserId(uid);
1314 final AtomicLong blobsDataSize = new AtomicLong(0);
1315 forEachSessionInUser(session -> {
1316 if (session.getOwnerUid() == uid) {
1317 blobsDataSize.getAndAdd(session.getSize());
1318 }
1319 }, userId);
1320
1321 forEachBlobInUser(blobMetadata -> {
1322 if (blobMetadata.isALeasee(uid)) {
1323 if (!blobMetadata.hasOtherLeasees(uid) || !callerHasStatsPermission) {
1324 blobsDataSize.getAndAdd(blobMetadata.getSize());
1325 }
1326 }
1327 }, userId);
1328
1329 stats.dataSize += blobsDataSize.get();
1330 }
1331 }
1332
1333 private void forEachSessionInUser(Consumer<BlobStoreSession> consumer, int userId) {
1334 synchronized (mBlobsLock) {
1335 final LongSparseArray<BlobStoreSession> userSessions = getUserSessionsLocked(userId);
1336 for (int i = 0, count = userSessions.size(); i < count; ++i) {
1337 final BlobStoreSession session = userSessions.valueAt(i);
1338 consumer.accept(session);
1339 }
1340 }
1341 }
1342
1343 private void forEachBlobInUser(Consumer<BlobMetadata> consumer, int userId) {
1344 synchronized (mBlobsLock) {
1345 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = getUserBlobsLocked(userId);
1346 for (int i = 0, count = userBlobs.size(); i < count; ++i) {
1347 final BlobMetadata blobMetadata = userBlobs.valueAt(i);
1348 consumer.accept(blobMetadata);
1349 }
1350 }
1351 }
1352
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001353 private class PackageChangedReceiver extends BroadcastReceiver {
1354 @Override
1355 public void onReceive(Context context, Intent intent) {
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001356 if (LOGV) {
1357 Slog.v(TAG, "Received " + intent);
1358 }
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001359 switch (intent.getAction()) {
1360 case Intent.ACTION_PACKAGE_FULLY_REMOVED:
1361 case Intent.ACTION_PACKAGE_DATA_CLEARED:
1362 final String packageName = intent.getData().getSchemeSpecificPart();
1363 if (packageName == null) {
1364 Slog.wtf(TAG, "Package name is missing in the intent: " + intent);
1365 return;
1366 }
1367 final int uid = intent.getIntExtra(Intent.EXTRA_UID, -1);
1368 if (uid == -1) {
1369 Slog.wtf(TAG, "uid is missing in the intent: " + intent);
1370 return;
1371 }
1372 handlePackageRemoved(packageName, uid);
1373 break;
Sudheer Shankafd2611e2020-03-05 02:18:15 -08001374 default:
1375 Slog.wtf(TAG, "Received unknown intent: " + intent);
1376 }
1377 }
1378 }
1379
1380 private class UserActionReceiver extends BroadcastReceiver {
1381 @Override
1382 public void onReceive(Context context, Intent intent) {
1383 if (LOGV) {
1384 Slog.v(TAG, "Received: " + intent);
1385 }
1386 switch (intent.getAction()) {
Sudheer Shanka22f0b162020-01-21 13:32:04 -08001387 case Intent.ACTION_USER_REMOVED:
1388 final int userId = intent.getIntExtra(Intent.EXTRA_USER_HANDLE,
1389 USER_NULL);
1390 if (userId == USER_NULL) {
1391 Slog.wtf(TAG, "userId is missing in the intent: " + intent);
1392 return;
1393 }
1394 handleUserRemoved(userId);
1395 break;
1396 default:
1397 Slog.wtf(TAG, "Received unknown intent: " + intent);
1398 }
1399 }
1400 }
1401
Sudheer Shankaf5b36962019-10-04 16:16:13 -07001402 private class Stub extends IBlobStoreManager.Stub {
Sudheer Shankad2da0672019-12-20 15:51:20 -08001403 @Override
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001404 @IntRange(from = 1)
1405 public long createSession(@NonNull BlobHandle blobHandle,
1406 @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001407 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1408 blobHandle.assertIsValid();
1409 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001410
1411 final int callingUid = Binder.getCallingUid();
1412 verifyCallingPackage(callingUid, packageName);
1413
1414 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1415 packageName, UserHandle.getUserId(callingUid))) {
1416 throw new SecurityException("Caller not allowed to create session; "
1417 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1418 }
1419
Sudheer Shanka07717c92020-06-23 14:06:47 -07001420 try {
1421 return createSessionInternal(blobHandle, callingUid, packageName);
1422 } catch (LimitExceededException e) {
1423 throw new ParcelableException(e);
1424 }
Sudheer Shankad2da0672019-12-20 15:51:20 -08001425 }
1426
1427 @Override
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001428 @NonNull
1429 public IBlobStoreSession openSession(@IntRange(from = 1) long sessionId,
1430 @NonNull String packageName) {
1431 Preconditions.checkArgumentPositive(sessionId,
1432 "sessionId must be positive: " + sessionId);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001433 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001434
1435 final int callingUid = Binder.getCallingUid();
1436 verifyCallingPackage(callingUid, packageName);
1437
1438 return openSessionInternal(sessionId, callingUid, packageName);
1439 }
1440
1441 @Override
Sudheer Shanka0b872152020-02-27 10:13:08 -08001442 public void abandonSession(@IntRange(from = 1) long sessionId,
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001443 @NonNull String packageName) {
1444 Preconditions.checkArgumentPositive(sessionId,
1445 "sessionId must be positive: " + sessionId);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001446 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001447
1448 final int callingUid = Binder.getCallingUid();
1449 verifyCallingPackage(callingUid, packageName);
1450
Sudheer Shanka0b872152020-02-27 10:13:08 -08001451 abandonSessionInternal(sessionId, callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001452 }
1453
1454 @Override
1455 public ParcelFileDescriptor openBlob(@NonNull BlobHandle blobHandle,
1456 @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001457 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1458 blobHandle.assertIsValid();
1459 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001460
1461 final int callingUid = Binder.getCallingUid();
1462 verifyCallingPackage(callingUid, packageName);
1463
1464 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1465 packageName, UserHandle.getUserId(callingUid))) {
1466 throw new SecurityException("Caller not allowed to open blob; "
1467 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1468 }
1469
1470 try {
1471 return openBlobInternal(blobHandle, callingUid, packageName);
1472 } catch (IOException e) {
1473 throw ExceptionUtils.wrap(e);
1474 }
Sudheer Shankad2da0672019-12-20 15:51:20 -08001475 }
1476
1477 @Override
1478 public void acquireLease(@NonNull BlobHandle blobHandle, @IdRes int descriptionResId,
Sudheer Shanka1406bc82020-02-03 12:27:24 -08001479 @Nullable CharSequence description,
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001480 @CurrentTimeSecondsLong long leaseExpiryTimeMillis, @NonNull String packageName) {
1481 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1482 blobHandle.assertIsValid();
Sudheer Shanka1406bc82020-02-03 12:27:24 -08001483 Preconditions.checkArgument(
1484 ResourceId.isValid(descriptionResId) || description != null,
1485 "Description must be valid; descriptionId=" + descriptionResId
1486 + ", description=" + description);
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001487 Preconditions.checkArgumentNonnegative(leaseExpiryTimeMillis,
1488 "leaseExpiryTimeMillis must not be negative");
1489 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001490
1491 final int callingUid = Binder.getCallingUid();
1492 verifyCallingPackage(callingUid, packageName);
1493
Sudheer Shankac6c79942020-03-12 13:20:46 -07001494 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1495 packageName, UserHandle.getUserId(callingUid))) {
1496 throw new SecurityException("Caller not allowed to open blob; "
1497 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1498 }
1499
Sudheer Shanka1b6b8252020-03-04 22:19:21 -08001500 try {
1501 acquireLeaseInternal(blobHandle, descriptionResId, description,
1502 leaseExpiryTimeMillis, callingUid, packageName);
1503 } catch (Resources.NotFoundException e) {
1504 throw new IllegalArgumentException(e);
Sudheer Shankaa80a9972020-03-20 00:12:14 -07001505 } catch (LimitExceededException e) {
1506 throw new ParcelableException(e);
Sudheer Shanka1b6b8252020-03-04 22:19:21 -08001507 }
Sudheer Shankad2da0672019-12-20 15:51:20 -08001508 }
1509
1510 @Override
1511 public void releaseLease(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001512 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1513 blobHandle.assertIsValid();
1514 Objects.requireNonNull(packageName, "packageName must not be null");
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001515
1516 final int callingUid = Binder.getCallingUid();
1517 verifyCallingPackage(callingUid, packageName);
1518
Sudheer Shankac6c79942020-03-12 13:20:46 -07001519 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1520 packageName, UserHandle.getUserId(callingUid))) {
1521 throw new SecurityException("Caller not allowed to open blob; "
1522 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1523 }
1524
Sudheer Shankaab1d4162020-01-07 10:37:50 -08001525 releaseLeaseInternal(blobHandle, callingUid, packageName);
Sudheer Shankad2da0672019-12-20 15:51:20 -08001526 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001527
1528 @Override
Sudheer Shankaa80a9972020-03-20 00:12:14 -07001529 public long getRemainingLeaseQuotaBytes(@NonNull String packageName) {
1530 final int callingUid = Binder.getCallingUid();
1531 verifyCallingPackage(callingUid, packageName);
1532
1533 return getRemainingLeaseQuotaBytesInternal(callingUid, packageName);
1534 }
1535
1536 @Override
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001537 public void waitForIdle(@NonNull RemoteCallback remoteCallback) {
Sudheer Shanka4824e3d2020-01-29 23:50:24 -08001538 Objects.requireNonNull(remoteCallback, "remoteCallback must not be null");
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001539
1540 mContext.enforceCallingOrSelfPermission(android.Manifest.permission.DUMP,
1541 "Caller is not allowed to call this; caller=" + Binder.getCallingUid());
Sudheer Shankafb11dc72020-03-19 11:56:50 -07001542 // We post messages back and forth between mHandler thread and mBackgroundHandler
1543 // thread while committing a blob. We need to replicate the same pattern here to
1544 // ensure pending messages have been handled.
1545 mHandler.post(() -> {
1546 mBackgroundHandler.post(() -> {
1547 mHandler.post(PooledLambda.obtainRunnable(remoteCallback::sendResult, null)
1548 .recycleOnUse());
1549 });
1550 });
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001551 }
1552
1553 @Override
Sudheer Shanka130d79c2020-03-04 13:54:36 -08001554 @NonNull
1555 public List<BlobInfo> queryBlobsForUser(@UserIdInt int userId) {
1556 if (Binder.getCallingUid() != Process.SYSTEM_UID) {
1557 throw new SecurityException("Only system uid is allowed to call "
1558 + "queryBlobsForUser()");
1559 }
1560
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001561 final int resolvedUserId = userId == USER_CURRENT
1562 ? ActivityManager.getCurrentUser() : userId;
1563 // Don't allow any other special user ids apart from USER_CURRENT
1564 final ActivityManagerInternal amInternal = LocalServices.getService(
1565 ActivityManagerInternal.class);
1566 amInternal.ensureNotSpecialUser(resolvedUserId);
1567
1568 return queryBlobsForUserInternal(resolvedUserId);
Sudheer Shanka130d79c2020-03-04 13:54:36 -08001569 }
1570
1571 @Override
1572 public void deleteBlob(long blobId) {
1573 final int callingUid = Binder.getCallingUid();
1574 if (callingUid != Process.SYSTEM_UID) {
1575 throw new SecurityException("Only system uid is allowed to call "
1576 + "deleteBlob()");
1577 }
1578
1579 deleteBlobInternal(blobId, callingUid);
1580 }
1581
1582 @Override
Sudheer Shankac6c79942020-03-12 13:20:46 -07001583 @NonNull
1584 public List<BlobHandle> getLeasedBlobs(@NonNull String packageName) {
1585 Objects.requireNonNull(packageName, "packageName must not be null");
1586
1587 final int callingUid = Binder.getCallingUid();
1588 verifyCallingPackage(callingUid, packageName);
1589
1590 return getLeasedBlobsInternal(callingUid, packageName);
1591 }
1592
1593 @Override
1594 @Nullable
1595 public LeaseInfo getLeaseInfo(@NonNull BlobHandle blobHandle, @NonNull String packageName) {
1596 Objects.requireNonNull(blobHandle, "blobHandle must not be null");
1597 blobHandle.assertIsValid();
1598 Objects.requireNonNull(packageName, "packageName must not be null");
1599
1600 final int callingUid = Binder.getCallingUid();
1601 verifyCallingPackage(callingUid, packageName);
1602
1603 if (Process.isIsolated(callingUid) || mPackageManagerInternal.isInstantApp(
1604 packageName, UserHandle.getUserId(callingUid))) {
1605 throw new SecurityException("Caller not allowed to open blob; "
1606 + "callingUid=" + callingUid + ", callingPackage=" + packageName);
1607 }
1608
1609 return getLeaseInfoInternal(blobHandle, callingUid, packageName);
1610 }
1611
1612 @Override
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001613 public void dump(@NonNull FileDescriptor fd, @NonNull PrintWriter writer,
1614 @Nullable String[] args) {
1615 // TODO: add proto-based version of this.
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001616 if (!DumpUtils.checkDumpAndUsageStatsPermission(mContext, TAG, writer)) return;
1617
1618 final DumpArgs dumpArgs = DumpArgs.parse(args);
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001619
1620 final IndentingPrintWriter fout = new IndentingPrintWriter(writer, " ");
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001621 if (dumpArgs.shouldDumpHelp()) {
1622 writer.println("dumpsys blob_store [options]:");
1623 fout.increaseIndent();
1624 dumpArgs.dumpArgsUsage(fout);
1625 fout.decreaseIndent();
1626 return;
1627 }
1628
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001629 synchronized (mBlobsLock) {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001630 if (dumpArgs.shouldDumpAllSections()) {
1631 fout.println("mCurrentMaxSessionId: " + mCurrentMaxSessionId);
1632 fout.println();
1633 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001634
1635 if (dumpArgs.shouldDumpSessions()) {
1636 dumpSessionsLocked(fout, dumpArgs);
1637 fout.println();
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001638 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001639 if (dumpArgs.shouldDumpBlobs()) {
1640 dumpBlobsLocked(fout, dumpArgs);
1641 fout.println();
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001642 }
1643 }
Sudheer Shanka364364b2020-02-19 17:56:09 -08001644
1645 if (dumpArgs.shouldDumpConfig()) {
1646 fout.println("BlobStore config:");
1647 fout.increaseIndent();
1648 BlobStoreConfig.dump(fout, mContext);
1649 fout.decreaseIndent();
1650 fout.println();
1651 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001652 }
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001653
1654 @Override
1655 public int handleShellCommand(@NonNull ParcelFileDescriptor in,
1656 @NonNull ParcelFileDescriptor out, @NonNull ParcelFileDescriptor err,
1657 @NonNull String[] args) {
Sudheer Shanka36b25a12020-06-13 22:27:28 -07001658 return new BlobStoreManagerShellCommand(BlobStoreManagerService.this).exec(this,
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001659 in.getFileDescriptor(), out.getFileDescriptor(), err.getFileDescriptor(), args);
1660 }
1661 }
1662
1663 static final class DumpArgs {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001664 private static final int FLAG_DUMP_SESSIONS = 1 << 0;
1665 private static final int FLAG_DUMP_BLOBS = 1 << 1;
1666 private static final int FLAG_DUMP_CONFIG = 1 << 2;
1667
1668 private int mSelectedSectionFlags;
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001669 private boolean mDumpUnredacted;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001670 private final ArrayList<String> mDumpPackages = new ArrayList<>();
1671 private final ArrayList<Integer> mDumpUids = new ArrayList<>();
1672 private final ArrayList<Integer> mDumpUserIds = new ArrayList<>();
1673 private final ArrayList<Long> mDumpBlobIds = new ArrayList<>();
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001674 private boolean mDumpHelp;
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001675 private boolean mDumpAll;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001676
1677 public boolean shouldDumpSession(String packageName, int uid, long blobId) {
1678 if (!CollectionUtils.isEmpty(mDumpPackages)
1679 && mDumpPackages.indexOf(packageName) < 0) {
1680 return false;
1681 }
1682 if (!CollectionUtils.isEmpty(mDumpUids)
1683 && mDumpUids.indexOf(uid) < 0) {
1684 return false;
1685 }
1686 if (!CollectionUtils.isEmpty(mDumpBlobIds)
1687 && mDumpBlobIds.indexOf(blobId) < 0) {
1688 return false;
1689 }
1690 return true;
1691 }
1692
Sudheer Shanka364364b2020-02-19 17:56:09 -08001693 public boolean shouldDumpAllSections() {
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001694 return mDumpAll || (mSelectedSectionFlags == 0);
Sudheer Shanka364364b2020-02-19 17:56:09 -08001695 }
1696
1697 public void allowDumpSessions() {
1698 mSelectedSectionFlags |= FLAG_DUMP_SESSIONS;
1699 }
1700
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001701 public boolean shouldDumpSessions() {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001702 if (shouldDumpAllSections()) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001703 return true;
1704 }
Sudheer Shanka364364b2020-02-19 17:56:09 -08001705 return (mSelectedSectionFlags & FLAG_DUMP_SESSIONS) != 0;
1706 }
1707
1708 public void allowDumpBlobs() {
1709 mSelectedSectionFlags |= FLAG_DUMP_BLOBS;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001710 }
1711
1712 public boolean shouldDumpBlobs() {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001713 if (shouldDumpAllSections()) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001714 return true;
1715 }
Sudheer Shanka364364b2020-02-19 17:56:09 -08001716 return (mSelectedSectionFlags & FLAG_DUMP_BLOBS) != 0;
1717 }
1718
1719 public void allowDumpConfig() {
1720 mSelectedSectionFlags |= FLAG_DUMP_CONFIG;
1721 }
1722
1723 public boolean shouldDumpConfig() {
1724 if (shouldDumpAllSections()) {
1725 return true;
1726 }
1727 return (mSelectedSectionFlags & FLAG_DUMP_CONFIG) != 0;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001728 }
1729
1730 public boolean shouldDumpBlob(long blobId) {
1731 return CollectionUtils.isEmpty(mDumpBlobIds)
1732 || mDumpBlobIds.indexOf(blobId) >= 0;
1733 }
1734
1735 public boolean shouldDumpFull() {
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001736 return mDumpUnredacted;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001737 }
1738
1739 public boolean shouldDumpUser(int userId) {
1740 return CollectionUtils.isEmpty(mDumpUserIds)
1741 || mDumpUserIds.indexOf(userId) >= 0;
1742 }
1743
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001744 public boolean shouldDumpHelp() {
1745 return mDumpHelp;
1746 }
1747
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001748 private DumpArgs() {}
1749
1750 public static DumpArgs parse(String[] args) {
1751 final DumpArgs dumpArgs = new DumpArgs();
1752 if (args == null) {
1753 return dumpArgs;
1754 }
1755
1756 for (int i = 0; i < args.length; ++i) {
1757 final String opt = args[i];
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001758 if ("--all".equals(opt) || "-a".equals(opt)) {
1759 dumpArgs.mDumpAll = true;
1760 } else if ("--unredacted".equals(opt) || "-u".equals(opt)) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001761 final int callingUid = Binder.getCallingUid();
1762 if (callingUid == Process.SHELL_UID || callingUid == Process.ROOT_UID) {
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001763 dumpArgs.mDumpUnredacted = true;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001764 }
1765 } else if ("--sessions".equals(opt)) {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001766 dumpArgs.allowDumpSessions();
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001767 } else if ("--blobs".equals(opt)) {
Sudheer Shanka364364b2020-02-19 17:56:09 -08001768 dumpArgs.allowDumpBlobs();
1769 } else if ("--config".equals(opt)) {
1770 dumpArgs.allowDumpConfig();
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001771 } else if ("--package".equals(opt) || "-p".equals(opt)) {
1772 dumpArgs.mDumpPackages.add(getStringArgRequired(args, ++i, "packageName"));
Sudheer Shanka7d4bd022020-05-20 13:49:17 -07001773 } else if ("--uid".equals(opt)) {
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001774 dumpArgs.mDumpUids.add(getIntArgRequired(args, ++i, "uid"));
1775 } else if ("--user".equals(opt)) {
1776 dumpArgs.mDumpUserIds.add(getIntArgRequired(args, ++i, "userId"));
1777 } else if ("--blob".equals(opt) || "-b".equals(opt)) {
1778 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, ++i, "blobId"));
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001779 } else if ("--help".equals(opt) || "-h".equals(opt)) {
1780 dumpArgs.mDumpHelp = true;
Sudheer Shanka5caeed52020-02-01 12:54:33 -08001781 } else {
1782 // Everything else is assumed to be blob ids.
1783 dumpArgs.mDumpBlobIds.add(getLongArgRequired(args, i, "blobId"));
1784 }
1785 }
1786 return dumpArgs;
1787 }
1788
1789 private static String getStringArgRequired(String[] args, int index, String argName) {
1790 if (index >= args.length) {
1791 throw new IllegalArgumentException("Missing " + argName);
1792 }
1793 return args[index];
1794 }
1795
1796 private static int getIntArgRequired(String[] args, int index, String argName) {
1797 if (index >= args.length) {
1798 throw new IllegalArgumentException("Missing " + argName);
1799 }
1800 final int value;
1801 try {
1802 value = Integer.parseInt(args[index]);
1803 } catch (NumberFormatException e) {
1804 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1805 }
1806 return value;
1807 }
1808
1809 private static long getLongArgRequired(String[] args, int index, String argName) {
1810 if (index >= args.length) {
1811 throw new IllegalArgumentException("Missing " + argName);
1812 }
1813 final long value;
1814 try {
1815 value = Long.parseLong(args[index]);
1816 } catch (NumberFormatException e) {
1817 throw new IllegalArgumentException("Invalid " + argName + ": " + args[index]);
1818 }
1819 return value;
1820 }
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001821
1822 private void dumpArgsUsage(IndentingPrintWriter pw) {
1823 pw.println("--help | -h");
1824 printWithIndent(pw, "Dump this help text");
1825 pw.println("--sessions");
1826 printWithIndent(pw, "Dump only the sessions info");
1827 pw.println("--blobs");
1828 printWithIndent(pw, "Dump only the committed blobs info");
Sudheer Shanka364364b2020-02-19 17:56:09 -08001829 pw.println("--config");
1830 printWithIndent(pw, "Dump only the config values");
Sudheer Shankae53e1ed2020-02-03 17:15:24 -08001831 pw.println("--package | -p [package-name]");
1832 printWithIndent(pw, "Dump blobs info associated with the given package");
1833 pw.println("--uid | -u [uid]");
1834 printWithIndent(pw, "Dump blobs info associated with the given uid");
1835 pw.println("--user [user-id]");
1836 printWithIndent(pw, "Dump blobs info in the given user");
1837 pw.println("--blob | -b [session-id | blob-id]");
1838 printWithIndent(pw, "Dump blob info corresponding to the given ID");
1839 pw.println("--full | -f");
1840 printWithIndent(pw, "Dump full unredacted blobs data");
1841 }
1842
1843 private void printWithIndent(IndentingPrintWriter pw, String str) {
1844 pw.increaseIndent();
1845 pw.println(str);
1846 pw.decreaseIndent();
1847 }
Sudheer Shankaf5b36962019-10-04 16:16:13 -07001848 }
Sudheer Shankabda89c12020-01-30 15:19:24 -08001849
Michael Wachenschwanz1a7f7ab2020-06-09 20:16:27 -07001850 private void registerBlobStorePuller() {
1851 mStatsManager.setPullAtomCallback(
1852 FrameworkStatsLog.BLOB_INFO,
1853 null, // use default PullAtomMetadata values
1854 BackgroundThread.getExecutor(),
1855 mStatsCallbackImpl
1856 );
1857 }
1858
1859 private class StatsPullAtomCallbackImpl implements StatsManager.StatsPullAtomCallback {
1860 @Override
1861 public int onPullAtom(int atomTag, List<StatsEvent> data) {
1862 switch (atomTag) {
1863 case FrameworkStatsLog.BLOB_INFO:
1864 return pullBlobData(atomTag, data);
1865 default:
1866 throw new UnsupportedOperationException("Unknown tagId=" + atomTag);
1867 }
1868 }
1869 }
1870
1871 private int pullBlobData(int atomTag, List<StatsEvent> data) {
1872 synchronized (mBlobsLock) {
1873 for (int i = 0, userCount = mBlobsMap.size(); i < userCount; ++i) {
1874 final ArrayMap<BlobHandle, BlobMetadata> userBlobs = mBlobsMap.valueAt(i);
1875 for (int j = 0, blobsCount = userBlobs.size(); j < blobsCount; ++j) {
1876 final BlobMetadata blob = userBlobs.valueAt(j);
1877 data.add(blob.dumpAsStatsEvent(atomTag));
1878 }
1879 }
1880 }
1881 return StatsManager.PULL_SUCCESS;
1882 }
1883
Sudheer Shankaae53d112020-01-31 14:20:53 -08001884 private class LocalService extends BlobStoreManagerInternal {
1885 @Override
1886 public void onIdleMaintenance() {
Sudheer Shankab96c18b2020-03-05 01:03:50 -08001887 runIdleMaintenance();
Sudheer Shankaae53d112020-01-31 14:20:53 -08001888 }
1889 }
1890
Sudheer Shankabda89c12020-01-30 15:19:24 -08001891 @VisibleForTesting
1892 static class Injector {
1893 public Handler initializeMessageHandler() {
1894 return BlobStoreManagerService.initializeMessageHandler();
1895 }
Sudheer Shankab76f7662020-02-27 15:17:43 -08001896
1897 public Handler getBackgroundHandler() {
1898 return BackgroundThread.getHandler();
1899 }
Sudheer Shankabda89c12020-01-30 15:19:24 -08001900 }
Sudheer Shankaf6e23b92020-01-15 01:51:15 -08001901}