1 /* 2 * Copyright 2020 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.server.blob; 17 18 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doCallRealMethod; 19 import static com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn; 20 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mock; 21 import static com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession; 22 import static com.android.server.blob.BlobStoreConfig.DeviceConfigProperties.SESSION_EXPIRY_TIMEOUT_MS; 23 24 import static com.google.common.truth.Truth.assertThat; 25 26 import static org.mockito.ArgumentMatchers.anyBoolean; 27 import static org.mockito.ArgumentMatchers.anyLong; 28 import static org.mockito.Mockito.never; 29 import static org.mockito.Mockito.verify; 30 31 import android.app.blob.BlobHandle; 32 import android.content.Context; 33 import android.os.Handler; 34 import android.os.Looper; 35 import android.os.Message; 36 import android.os.UserHandle; 37 import android.platform.test.annotations.Presubmit; 38 import android.util.LongSparseArray; 39 40 import androidx.test.InstrumentationRegistry; 41 import androidx.test.filters.SmallTest; 42 import androidx.test.runner.AndroidJUnit4; 43 44 import com.android.server.blob.BlobStoreManagerService.Injector; 45 46 import org.junit.After; 47 import org.junit.Before; 48 import org.junit.Test; 49 import org.junit.runner.RunWith; 50 import org.mockito.Mock; 51 import org.mockito.MockitoSession; 52 import org.mockito.quality.Strictness; 53 54 import java.io.File; 55 56 @RunWith(AndroidJUnit4.class) 57 @SmallTest 58 @Presubmit 59 public class BlobStoreManagerServiceTest { 60 private Context mContext; 61 private Handler mHandler; 62 private BlobStoreManagerService mService; 63 64 private MockitoSession mMockitoSession; 65 66 @Mock 67 private File mBlobsDir; 68 69 private LongSparseArray<BlobStoreSession> mUserSessions; 70 71 private static final String TEST_PKG1 = "com.example1"; 72 private static final String TEST_PKG2 = "com.example2"; 73 private static final String TEST_PKG3 = "com.example3"; 74 75 private static final int TEST_UID1 = 10001; 76 private static final int TEST_UID2 = 10002; 77 private static final int TEST_UID3 = 10003; 78 79 @Before setUp()80 public void setUp() { 81 // Share classloader to allow package private access. 82 System.setProperty("dexmaker.share_classloader", "true"); 83 84 mMockitoSession = mockitoSession() 85 .initMocks(this) 86 .strictness(Strictness.LENIENT) 87 .mockStatic(BlobStoreConfig.class) 88 .startMocking(); 89 90 doReturn(mBlobsDir).when(() -> BlobStoreConfig.getBlobsDir()); 91 doReturn(true).when(mBlobsDir).exists(); 92 doReturn(new File[0]).when(mBlobsDir).listFiles(); 93 doReturn(true).when(() -> BlobStoreConfig.hasLeaseWaitTimeElapsed(anyLong())); 94 doCallRealMethod().when(() -> BlobStoreConfig.hasSessionExpired(anyLong())); 95 96 mContext = InstrumentationRegistry.getTargetContext(); 97 mHandler = new TestHandler(Looper.getMainLooper()); 98 mService = new BlobStoreManagerService(mContext, new TestInjector()); 99 mUserSessions = new LongSparseArray<>(); 100 101 mService.addUserSessionsForTest(mUserSessions, UserHandle.myUserId()); 102 } 103 104 @After tearDown()105 public void tearDown() { 106 if (mMockitoSession != null) { 107 mMockitoSession.finishMocking(); 108 } 109 } 110 111 @Test testHandlePackageRemoved()112 public void testHandlePackageRemoved() throws Exception { 113 // Setup sessions 114 final File sessionFile1 = mock(File.class); 115 final long sessionId1 = 11; 116 final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, 117 sessionId1, sessionFile1); 118 mUserSessions.append(sessionId1, session1); 119 120 final File sessionFile2 = mock(File.class); 121 final long sessionId2 = 25; 122 final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, 123 sessionId2, sessionFile2); 124 mUserSessions.append(sessionId2, session2); 125 126 final File sessionFile3 = mock(File.class); 127 final long sessionId3 = 37; 128 final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, 129 sessionId3, sessionFile3); 130 mUserSessions.append(sessionId3, session3); 131 132 final File sessionFile4 = mock(File.class); 133 final long sessionId4 = 48; 134 final BlobStoreSession session4 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, 135 sessionId4, sessionFile4); 136 mUserSessions.append(sessionId4, session4); 137 138 // Setup blobs 139 final long blobId1 = 978; 140 final File blobFile1 = mock(File.class); 141 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), 142 "label1", System.currentTimeMillis() + 10000, "tag1"); 143 final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, 144 blobHandle1, true /* hasLeases */); 145 doReturn(true).when(blobMetadata1).isACommitter(TEST_PKG1, TEST_UID1); 146 addBlob(blobHandle1, blobMetadata1); 147 148 final long blobId2 = 347; 149 final File blobFile2 = mock(File.class); 150 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), 151 "label2", System.currentTimeMillis() + 20000, "tag2"); 152 final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, 153 blobHandle2, false /* hasLeases */); 154 doReturn(false).when(blobMetadata2).isACommitter(TEST_PKG1, TEST_UID1); 155 addBlob(blobHandle2, blobMetadata2); 156 157 final long blobId3 = 49875; 158 final File blobFile3 = mock(File.class); 159 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), 160 "label3", System.currentTimeMillis() - 1000, "tag3"); 161 final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, 162 blobHandle3, true /* hasLeases */); 163 doReturn(true).when(blobMetadata3).isACommitter(TEST_PKG1, TEST_UID1); 164 addBlob(blobHandle3, blobMetadata3); 165 166 mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3, sessionId4, 167 blobId1, blobId2, blobId3); 168 169 // Invoke test method 170 mService.handlePackageRemoved(TEST_PKG1, TEST_UID1); 171 172 // Verify sessions are removed 173 verify(session1).destroy(); 174 verify(session2, never()).destroy(); 175 verify(session3, never()).destroy(); 176 verify(session4).destroy(); 177 178 assertThat(mUserSessions.size()).isEqualTo(2); 179 assertThat(mUserSessions.get(sessionId1)).isNull(); 180 assertThat(mUserSessions.get(sessionId2)).isNotNull(); 181 assertThat(mUserSessions.get(sessionId3)).isNotNull(); 182 assertThat(mUserSessions.get(sessionId4)).isNull(); 183 184 // Verify blobs are removed 185 verify(blobMetadata1).removeCommitter(TEST_PKG1, TEST_UID1); 186 verify(blobMetadata1).removeLeasee(TEST_PKG1, TEST_UID1); 187 verify(blobMetadata2, never()).removeCommitter(TEST_PKG1, TEST_UID1); 188 verify(blobMetadata2).removeLeasee(TEST_PKG1, TEST_UID1); 189 verify(blobMetadata3).removeCommitter(TEST_PKG1, TEST_UID1); 190 verify(blobMetadata3).removeLeasee(TEST_PKG1, TEST_UID1); 191 192 verify(blobMetadata1, never()).destroy(); 193 verify(blobMetadata2).destroy(); 194 verify(blobMetadata3).destroy(); 195 196 assertThat(mService.getBlobsCountForTest()).isEqualTo(1); 197 assertThat(mService.getBlobForTest(blobHandle1)).isNotNull(); 198 assertThat(mService.getBlobForTest(blobHandle2)).isNull(); 199 assertThat(mService.getBlobForTest(blobHandle3)).isNull(); 200 201 assertThat(mService.getActiveIdsForTest()).containsExactly( 202 sessionId2, sessionId3, blobId1); 203 assertThat(mService.getKnownIdsForTest()).containsExactly( 204 sessionId1, sessionId2, sessionId3, sessionId4, blobId1, blobId2, blobId3); 205 } 206 207 @Test testHandleIdleMaintenance_deleteUnknownBlobs()208 public void testHandleIdleMaintenance_deleteUnknownBlobs() throws Exception { 209 // Setup blob files 210 final long testId1 = 286; 211 final File file1 = mock(File.class); 212 doReturn(String.valueOf(testId1)).when(file1).getName(); 213 final long testId2 = 349; 214 final File file2 = mock(File.class); 215 doReturn(String.valueOf(testId2)).when(file2).getName(); 216 final long testId3 = 7355; 217 final File file3 = mock(File.class); 218 doReturn(String.valueOf(testId3)).when(file3).getName(); 219 220 doReturn(new File[] {file1, file2, file3}).when(mBlobsDir).listFiles(); 221 mService.addActiveIdsForTest(testId1, testId3); 222 223 // Invoke test method 224 mService.handleIdleMaintenanceLocked(); 225 226 // Verify unknown blobs are deleted 227 verify(file1, never()).delete(); 228 verify(file2).delete(); 229 verify(file3, never()).delete(); 230 } 231 232 @Test testHandleIdleMaintenance_deleteStaleSessions()233 public void testHandleIdleMaintenance_deleteStaleSessions() throws Exception { 234 // Setup sessions 235 final File sessionFile1 = mock(File.class); 236 doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS + 1000) 237 .when(sessionFile1).lastModified(); 238 final long sessionId1 = 342; 239 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), 240 "label1", System.currentTimeMillis() - 1000, "tag1"); 241 final BlobStoreSession session1 = createBlobStoreSessionMock(TEST_PKG1, TEST_UID1, 242 sessionId1, sessionFile1, blobHandle1); 243 mUserSessions.append(sessionId1, session1); 244 245 final File sessionFile2 = mock(File.class); 246 doReturn(System.currentTimeMillis() - 20000) 247 .when(sessionFile2).lastModified(); 248 final long sessionId2 = 4597; 249 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), 250 "label2", System.currentTimeMillis() + 20000, "tag2"); 251 final BlobStoreSession session2 = createBlobStoreSessionMock(TEST_PKG2, TEST_UID2, 252 sessionId2, sessionFile2, blobHandle2); 253 mUserSessions.append(sessionId2, session2); 254 255 final File sessionFile3 = mock(File.class); 256 doReturn(System.currentTimeMillis() - SESSION_EXPIRY_TIMEOUT_MS - 2000) 257 .when(sessionFile3).lastModified(); 258 final long sessionId3 = 9484; 259 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), 260 "label3", System.currentTimeMillis() + 30000, "tag3"); 261 final BlobStoreSession session3 = createBlobStoreSessionMock(TEST_PKG3, TEST_UID3, 262 sessionId3, sessionFile3, blobHandle3); 263 mUserSessions.append(sessionId3, session3); 264 265 mService.addActiveIdsForTest(sessionId1, sessionId2, sessionId3); 266 267 // Invoke test method 268 mService.handleIdleMaintenanceLocked(); 269 270 // Verify stale sessions are removed 271 verify(session1).destroy(); 272 verify(session2, never()).destroy(); 273 verify(session3).destroy(); 274 275 assertThat(mUserSessions.size()).isEqualTo(1); 276 assertThat(mUserSessions.get(sessionId2)).isNotNull(); 277 278 assertThat(mService.getActiveIdsForTest()).containsExactly(sessionId2); 279 assertThat(mService.getKnownIdsForTest()).containsExactly( 280 sessionId1, sessionId2, sessionId3); 281 } 282 283 @Test testHandleIdleMaintenance_deleteStaleBlobs()284 public void testHandleIdleMaintenance_deleteStaleBlobs() throws Exception { 285 // Setup blobs 286 final long blobId1 = 3489; 287 final File blobFile1 = mock(File.class); 288 final BlobHandle blobHandle1 = BlobHandle.createWithSha256("digest1".getBytes(), 289 "label1", System.currentTimeMillis() - 2000, "tag1"); 290 final BlobMetadata blobMetadata1 = createBlobMetadataMock(blobId1, blobFile1, blobHandle1, 291 true /* hasLeases */); 292 addBlob(blobHandle1, blobMetadata1); 293 294 final long blobId2 = 78974; 295 final File blobFile2 = mock(File.class); 296 final BlobHandle blobHandle2 = BlobHandle.createWithSha256("digest2".getBytes(), 297 "label2", System.currentTimeMillis() + 30000, "tag2"); 298 final BlobMetadata blobMetadata2 = createBlobMetadataMock(blobId2, blobFile2, blobHandle2, 299 true /* hasLeases */); 300 addBlob(blobHandle2, blobMetadata2); 301 302 final long blobId3 = 97; 303 final File blobFile3 = mock(File.class); 304 final BlobHandle blobHandle3 = BlobHandle.createWithSha256("digest3".getBytes(), 305 "label3", System.currentTimeMillis() + 4400000, "tag3"); 306 final BlobMetadata blobMetadata3 = createBlobMetadataMock(blobId3, blobFile3, blobHandle3, 307 false /* hasLeases */); 308 addBlob(blobHandle3, blobMetadata3); 309 310 mService.addActiveIdsForTest(blobId1, blobId2, blobId3); 311 312 // Invoke test method 313 mService.handleIdleMaintenanceLocked(); 314 315 // Verify stale blobs are removed 316 verify(blobMetadata1).destroy(); 317 verify(blobMetadata2, never()).destroy(); 318 verify(blobMetadata3).destroy(); 319 320 assertThat(mService.getBlobsCountForTest()).isEqualTo(1); 321 assertThat(mService.getBlobForTest(blobHandle2)).isNotNull(); 322 323 assertThat(mService.getActiveIdsForTest()).containsExactly(blobId2); 324 assertThat(mService.getKnownIdsForTest()).containsExactly(blobId1, blobId2, blobId3); 325 } 326 327 @Test testGetTotalUsageBytes()328 public void testGetTotalUsageBytes() throws Exception { 329 // Setup blobs 330 final BlobMetadata blobMetadata1 = mock(BlobMetadata.class); 331 final long size1 = 4567; 332 doReturn(size1).when(blobMetadata1).getSize(); 333 doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG1, TEST_UID1); 334 doReturn(true).when(blobMetadata1).isALeasee(TEST_PKG2, TEST_UID2); 335 addBlob(mock(BlobHandle.class), blobMetadata1); 336 337 final BlobMetadata blobMetadata2 = mock(BlobMetadata.class); 338 final long size2 = 89475; 339 doReturn(size2).when(blobMetadata2).getSize(); 340 doReturn(false).when(blobMetadata2).isALeasee(TEST_PKG1, TEST_UID1); 341 doReturn(true).when(blobMetadata2).isALeasee(TEST_PKG2, TEST_UID2); 342 addBlob(mock(BlobHandle.class), blobMetadata2); 343 344 final BlobMetadata blobMetadata3 = mock(BlobMetadata.class); 345 final long size3 = 328732; 346 doReturn(size3).when(blobMetadata3).getSize(); 347 doReturn(true).when(blobMetadata3).isALeasee(TEST_PKG1, TEST_UID1); 348 doReturn(false).when(blobMetadata3).isALeasee(TEST_PKG2, TEST_UID2); 349 addBlob(mock(BlobHandle.class), blobMetadata3); 350 351 // Verify usage is calculated correctly 352 assertThat(mService.getTotalUsageBytesLocked(TEST_UID1, TEST_PKG1)) 353 .isEqualTo(size1 + size3); 354 assertThat(mService.getTotalUsageBytesLocked(TEST_UID2, TEST_PKG2)) 355 .isEqualTo(size1 + size2); 356 } 357 createBlobStoreSessionMock(String ownerPackageName, int ownerUid, long sessionId, File sessionFile)358 private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, 359 long sessionId, File sessionFile) { 360 return createBlobStoreSessionMock(ownerPackageName, ownerUid, sessionId, sessionFile, 361 mock(BlobHandle.class)); 362 } createBlobStoreSessionMock(String ownerPackageName, int ownerUid, long sessionId, File sessionFile, BlobHandle blobHandle)363 private BlobStoreSession createBlobStoreSessionMock(String ownerPackageName, int ownerUid, 364 long sessionId, File sessionFile, BlobHandle blobHandle) { 365 final BlobStoreSession session = mock(BlobStoreSession.class); 366 doReturn(ownerPackageName).when(session).getOwnerPackageName(); 367 doReturn(ownerUid).when(session).getOwnerUid(); 368 doReturn(sessionId).when(session).getSessionId(); 369 doReturn(sessionFile).when(session).getSessionFile(); 370 doReturn(blobHandle).when(session).getBlobHandle(); 371 doCallRealMethod().when(session).isExpired(); 372 return session; 373 } 374 createBlobMetadataMock(long blobId, File blobFile, BlobHandle blobHandle, boolean hasValidLeases)375 private BlobMetadata createBlobMetadataMock(long blobId, File blobFile, 376 BlobHandle blobHandle, boolean hasValidLeases) { 377 final BlobMetadata blobMetadata = mock(BlobMetadata.class); 378 doReturn(blobId).when(blobMetadata).getBlobId(); 379 doReturn(blobFile).when(blobMetadata).getBlobFile(); 380 doReturn(hasValidLeases).when(blobMetadata).hasValidLeases(); 381 doReturn(blobHandle).when(blobMetadata).getBlobHandle(); 382 doCallRealMethod().when(blobMetadata).shouldBeDeleted(anyBoolean()); 383 doReturn(true).when(blobMetadata).hasLeaseWaitTimeElapsedForAll(); 384 return blobMetadata; 385 } 386 addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata)387 private void addBlob(BlobHandle blobHandle, BlobMetadata blobMetadata) { 388 doReturn(blobHandle).when(blobMetadata).getBlobHandle(); 389 mService.addBlobLocked(blobMetadata); 390 } 391 392 private class TestHandler extends Handler { TestHandler(Looper looper)393 TestHandler(Looper looper) { 394 super(looper); 395 } 396 397 @Override dispatchMessage(Message msg)398 public void dispatchMessage(Message msg) { 399 // Ignore all messages 400 } 401 } 402 403 private class TestInjector extends Injector { 404 @Override initializeMessageHandler()405 public Handler initializeMessageHandler() { 406 return mHandler; 407 } 408 409 @Override getBackgroundHandler()410 public Handler getBackgroundHandler() { 411 return mHandler; 412 } 413 } 414 } 415