1 /* 2 * Copyright (C) 2023 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 17 package com.android.ondevicepersonalization.services.federatedcompute; 18 19 import static android.federatedcompute.common.ClientConstants.EXAMPLE_STORE_ACTION; 20 import static android.federatedcompute.common.ClientConstants.EXTRA_EXAMPLE_ITERATOR_RESULT; 21 import static android.federatedcompute.common.ClientConstants.EXTRA_EXAMPLE_ITERATOR_RESUMPTION_TOKEN; 22 23 import static com.android.ondevicepersonalization.services.PhFlags.KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED; 24 25 import static org.junit.Assert.assertArrayEquals; 26 import static org.junit.Assert.assertFalse; 27 import static org.junit.Assert.assertNotNull; 28 import static org.junit.Assert.assertThrows; 29 import static org.junit.Assert.assertTrue; 30 import static org.junit.Assume.assumeTrue; 31 import static org.mockito.ArgumentMatchers.eq; 32 import static org.mockito.Mockito.doReturn; 33 import static org.mockito.Mockito.when; 34 import static org.mockito.MockitoAnnotations.initMocks; 35 36 import android.content.ComponentName; 37 import android.content.Context; 38 import android.content.Intent; 39 import android.content.pm.PackageManager; 40 import android.federatedcompute.aidl.IExampleStoreCallback; 41 import android.federatedcompute.aidl.IExampleStoreIterator; 42 import android.federatedcompute.aidl.IExampleStoreIteratorCallback; 43 import android.federatedcompute.aidl.IExampleStoreService; 44 import android.federatedcompute.common.ClientConstants; 45 import android.os.Bundle; 46 import android.os.IBinder; 47 import android.os.RemoteException; 48 49 import androidx.test.core.app.ApplicationProvider; 50 51 import com.android.compatibility.common.util.ShellUtils; 52 import com.android.dx.mockito.inline.extended.ExtendedMockito; 53 import com.android.modules.utils.build.SdkLevel; 54 import com.android.modules.utils.testing.ExtendedMockitoRule; 55 import com.android.odp.module.common.Clock; 56 import com.android.odp.module.common.MonotonicClock; 57 import com.android.ondevicepersonalization.services.Flags; 58 import com.android.ondevicepersonalization.services.FlagsFactory; 59 import com.android.ondevicepersonalization.services.StableFlags; 60 import com.android.ondevicepersonalization.services.data.OnDevicePersonalizationDbHelper; 61 import com.android.ondevicepersonalization.services.data.events.EventState; 62 import com.android.ondevicepersonalization.services.data.events.EventsDao; 63 import com.android.ondevicepersonalization.services.data.user.UserPrivacyStatus; 64 import com.android.ondevicepersonalization.testing.utils.DeviceSupportHelper; 65 66 import org.junit.After; 67 import org.junit.Before; 68 import org.junit.Rule; 69 import org.junit.Test; 70 import org.junit.runner.RunWith; 71 import org.junit.runners.JUnit4; 72 import org.mockito.InjectMocks; 73 import org.mockito.Mock; 74 import org.mockito.quality.Strictness; 75 76 import java.util.concurrent.CountDownLatch; 77 import java.util.concurrent.TimeUnit; 78 79 @RunWith(JUnit4.class) 80 public class OdpExampleStoreServiceTests { 81 private static final String SERVICE_CLASS = "com.test.TestPersonalizationService"; 82 private static final Context APPLICATION_CONTEXT = ApplicationProvider.getApplicationContext(); 83 private static final ComponentName ISOLATED_SERVICE_COMPONENT = 84 new ComponentName(APPLICATION_CONTEXT.getPackageName(), SERVICE_CLASS); 85 private static final ContextData TEST_CONTEXT_DATA = 86 new ContextData( 87 ISOLATED_SERVICE_COMPONENT.getPackageName(), 88 ISOLATED_SERVICE_COMPONENT.getClassName()); 89 private static final String TEST_POPULATION_NAME = "PopulationName"; 90 private static final String TEST_TASK_NAME = "TaskName"; 91 private static final String TEST_COLLECTION_URI = "CollectionUri"; 92 private static final int LATCH_LONG_TIMEOUT_MILLIS = 10000; 93 private static final int LATCH_SHORT_TIMEOUT_MILLIS = 1000; 94 95 @Mock Context mMockContext; 96 @InjectMocks OdpExampleStoreService mService; 97 98 @Mock UserPrivacyStatus mMockUserPrivacyStatus; 99 100 @Mock Clock mMockClock; 101 102 private Flags mStubFlags = new Flags() { 103 @Override public boolean getGlobalKillSwitch() { 104 return false; 105 } 106 }; 107 108 @Rule 109 public final ExtendedMockitoRule mExtendedMockitoRule = 110 new ExtendedMockitoRule.Builder(this) 111 .spyStatic(UserPrivacyStatus.class) 112 .mockStatic(FlagsFactory.class) 113 .spyStatic(StableFlags.class) 114 .spyStatic(MonotonicClock.class) 115 .setStrictness(Strictness.LENIENT) 116 .build(); 117 118 private CountDownLatch mLatch; 119 120 121 122 private boolean mIteratorCallbackOnSuccessCalled = false; 123 private boolean mIteratorCallbackOnFailureCalled = false; 124 125 private boolean mQueryCallbackOnSuccessCalled = false; 126 private boolean mQueryCallbackOnFailureCalled = false; 127 128 private final EventsDao mEventsDao = EventsDao.getInstanceForTest(APPLICATION_CONTEXT); 129 130 @Before setUp()131 public void setUp() throws Exception { 132 assumeTrue(DeviceSupportHelper.isDeviceSupported()); 133 initMocks(this); 134 when(mMockContext.getApplicationContext()).thenReturn(APPLICATION_CONTEXT); 135 ExtendedMockito.doReturn(mMockUserPrivacyStatus).when(UserPrivacyStatus::getInstance); 136 ExtendedMockito.doReturn(mMockClock).when(MonotonicClock::getInstance); 137 doReturn(true).when(mMockUserPrivacyStatus).isMeasurementEnabled(); 138 doReturn(true).when(mMockUserPrivacyStatus).isProtectedAudienceEnabled(); 139 doReturn(200L).when(mMockClock).currentTimeMillis(); 140 doReturn(1000L).when(mMockClock).elapsedRealtime(); 141 mQueryCallbackOnSuccessCalled = false; 142 mQueryCallbackOnFailureCalled = false; 143 mLatch = new CountDownLatch(1); 144 145 ExtendedMockito.doReturn(mStubFlags).when(FlagsFactory::getFlags); 146 ExtendedMockito.doReturn(SdkLevel.isAtLeastU()).when( 147 () -> StableFlags.get(KEY_SHARED_ISOLATED_PROCESS_FEATURE_ENABLED)); 148 ShellUtils.runShellCommand("settings put global hidden_api_policy 1"); 149 } 150 151 @Test testStartQuery_lessThanMinExample_failure()152 public void testStartQuery_lessThanMinExample_failure() throws Exception { 153 mEventsDao.updateOrInsertEventState( 154 new EventState.Builder() 155 .setTaskIdentifier(TEST_POPULATION_NAME) 156 .setService(ISOLATED_SERVICE_COMPONENT) 157 .setToken() 158 .build()); 159 mService.onCreate(); 160 Intent intent = new Intent(); 161 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 162 IExampleStoreService binder = 163 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 164 assertNotNull(binder); 165 TestQueryCallback callback = new TestQueryCallback(); 166 Bundle input = getTestInputBundle(/* eligibilityMinExample= */ 4); 167 168 binder.startQuery(input, callback); 169 170 assertTrue( 171 "timeout reached while waiting for countdownlatch!", 172 mLatch.await(LATCH_LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 173 assertFalse(mQueryCallbackOnSuccessCalled); 174 assertTrue(mQueryCallbackOnFailureCalled); 175 } 176 177 @Test testStartQuery_moreThanMinExample_success()178 public void testStartQuery_moreThanMinExample_success() throws Exception { 179 mEventsDao.updateOrInsertEventState( 180 new EventState.Builder() 181 .setTaskIdentifier(TEST_POPULATION_NAME) 182 .setService(ISOLATED_SERVICE_COMPONENT) 183 .setToken() 184 .build()); 185 mService.onCreate(); 186 Intent intent = new Intent(); 187 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 188 IExampleStoreService binder = 189 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 190 assertNotNull(binder); 191 TestQueryCallback callback = new TestQueryCallback(); 192 Bundle input = getTestInputBundle(/* eligibilityMinExample= */ 2); 193 194 binder.startQuery(input, callback); 195 196 assertTrue( 197 "timeout reached while waiting for countdownlatch!", 198 mLatch.await(LATCH_LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 199 assertTrue(mQueryCallbackOnSuccessCalled); 200 assertFalse(mQueryCallbackOnFailureCalled); 201 } 202 203 @Test testWithStartQuery()204 public void testWithStartQuery() throws Exception { 205 mEventsDao.updateOrInsertEventState( 206 new EventState.Builder() 207 .setTaskIdentifier(TEST_POPULATION_NAME) 208 .setService(ISOLATED_SERVICE_COMPONENT) 209 .setToken() 210 .build()); 211 mService.onCreate(); 212 Intent intent = new Intent(); 213 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 214 IExampleStoreService binder = 215 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 216 assertNotNull(binder); 217 TestQueryCallback callback = new TestQueryCallback(); 218 Bundle input = new Bundle(); 219 input.putByteArray( 220 ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(TEST_CONTEXT_DATA)); 221 input.putString(ClientConstants.EXTRA_POPULATION_NAME, TEST_POPULATION_NAME); 222 input.putString(ClientConstants.EXTRA_TASK_ID, TEST_TASK_NAME); 223 input.putString(ClientConstants.EXTRA_COLLECTION_URI, TEST_COLLECTION_URI); 224 225 binder.startQuery(input, callback); 226 227 assertTrue( 228 "timeout reached while waiting for countdownlatch!", 229 mLatch.await(LATCH_LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 230 assertTrue(mQueryCallbackOnSuccessCalled); 231 assertFalse(mQueryCallbackOnFailureCalled); 232 233 IExampleStoreIterator iterator = callback.getIterator(); 234 TestIteratorCallback iteratorCallback = new TestIteratorCallback(); 235 mLatch = new CountDownLatch(1); 236 iteratorCallback.setExpected(new byte[] {10}, "token1".getBytes()); 237 iterator.next(iteratorCallback); 238 239 assertTrue( 240 "timeout reached while waiting for countdownlatch!", 241 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 242 assertTrue(mIteratorCallbackOnSuccessCalled); 243 assertFalse(mIteratorCallbackOnFailureCalled); 244 245 mIteratorCallbackOnSuccessCalled = false; 246 mLatch = new CountDownLatch(1); 247 iteratorCallback.setExpected(new byte[] {20}, "token2".getBytes()); 248 iterator.next(iteratorCallback); 249 250 assertTrue( 251 "timeout reached while waiting for countdownlatch!", 252 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 253 assertTrue(mIteratorCallbackOnSuccessCalled); 254 assertFalse(mIteratorCallbackOnFailureCalled); 255 } 256 257 @Test testWithStartQueryMeasurementControlRevoked()258 public void testWithStartQueryMeasurementControlRevoked() throws Exception { 259 doReturn(false).when(mMockUserPrivacyStatus).isMeasurementEnabled(); 260 mEventsDao.updateOrInsertEventState( 261 new EventState.Builder() 262 .setTaskIdentifier(TEST_POPULATION_NAME) 263 .setService(ISOLATED_SERVICE_COMPONENT) 264 .setToken() 265 .build()); 266 mService.onCreate(); 267 Intent intent = new Intent(); 268 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 269 IExampleStoreService binder = 270 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 271 assertNotNull(binder); 272 TestQueryCallback callback = new TestQueryCallback(); 273 Bundle input = new Bundle(); 274 input.putByteArray( 275 ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(TEST_CONTEXT_DATA)); 276 input.putString(ClientConstants.EXTRA_POPULATION_NAME, TEST_POPULATION_NAME); 277 input.putString(ClientConstants.EXTRA_TASK_ID, TEST_TASK_NAME); 278 279 binder.startQuery(input, callback); 280 281 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 282 assertFalse(mQueryCallbackOnSuccessCalled); 283 assertTrue(mQueryCallbackOnFailureCalled); 284 } 285 286 @Test testWithStartQueryNotValidJob()287 public void testWithStartQueryNotValidJob() throws Exception { 288 mService.onCreate(); 289 Intent intent = new Intent(); 290 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 291 IExampleStoreService binder = 292 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 293 assertNotNull(binder); 294 TestQueryCallback callback = new TestQueryCallback(); 295 Bundle input = new Bundle(); 296 input.putByteArray( 297 ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(TEST_CONTEXT_DATA)); 298 input.putString(ClientConstants.EXTRA_POPULATION_NAME, TEST_POPULATION_NAME); 299 input.putString(ClientConstants.EXTRA_TASK_ID, TEST_TASK_NAME); 300 301 ((IExampleStoreService.Stub) binder).startQuery(input, callback); 302 303 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 304 assertFalse(mQueryCallbackOnSuccessCalled); 305 assertTrue(mQueryCallbackOnFailureCalled); 306 } 307 308 @Test testWithStartQueryBadInput()309 public void testWithStartQueryBadInput() throws Exception { 310 mService.onCreate(); 311 Intent intent = new Intent(); 312 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 313 IExampleStoreService binder = 314 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 315 assertNotNull(binder); 316 TestQueryCallback callback = new TestQueryCallback(); 317 318 binder.startQuery(Bundle.EMPTY, callback); 319 320 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 321 assertFalse(mQueryCallbackOnSuccessCalled); 322 assertTrue(mQueryCallbackOnFailureCalled); 323 } 324 325 @Test testFailedPermissionCheck()326 public void testFailedPermissionCheck() throws Exception { 327 when(mMockContext.checkCallingOrSelfPermission( 328 eq("android.permission.BIND_EXAMPLE_STORE_SERVICE"))) 329 .thenReturn(PackageManager.PERMISSION_DENIED); 330 mService.onCreate(); 331 Intent intent = new Intent(); 332 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 333 IExampleStoreService binder = 334 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 335 336 assertThrows( 337 SecurityException.class, 338 () -> binder.startQuery(Bundle.EMPTY, new TestQueryCallback())); 339 340 mLatch.await(LATCH_SHORT_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS); 341 assertFalse(mQueryCallbackOnSuccessCalled); 342 assertFalse(mQueryCallbackOnFailureCalled); 343 } 344 345 @Test testStartQuery_isolatedServiceThrowsException()346 public void testStartQuery_isolatedServiceThrowsException() throws Exception { 347 mEventsDao.updateOrInsertEventState( 348 new EventState.Builder() 349 .setTaskIdentifier("throw_exception") 350 .setService(ISOLATED_SERVICE_COMPONENT) 351 .setToken() 352 .build()); 353 mService.onCreate(); 354 Intent intent = new Intent(); 355 intent.setAction(EXAMPLE_STORE_ACTION).setPackage(APPLICATION_CONTEXT.getPackageName()); 356 IExampleStoreService binder = 357 IExampleStoreService.Stub.asInterface(mService.onBind(intent)); 358 assertNotNull(binder); 359 TestQueryCallback callback = new TestQueryCallback(); 360 Bundle input = new Bundle(); 361 input.putByteArray( 362 ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(TEST_CONTEXT_DATA)); 363 input.putString(ClientConstants.EXTRA_POPULATION_NAME, "throw_exception"); 364 input.putString(ClientConstants.EXTRA_TASK_ID, TEST_TASK_NAME); 365 input.putString(ClientConstants.EXTRA_COLLECTION_URI, TEST_COLLECTION_URI); 366 input.putInt(ClientConstants.EXTRA_ELIGIBILITY_MIN_EXAMPLE, 4); 367 368 binder.startQuery(input, callback); 369 370 assertTrue( 371 "timeout reached while waiting for countdownlatch!", 372 mLatch.await(LATCH_LONG_TIMEOUT_MILLIS, TimeUnit.MILLISECONDS)); 373 assertFalse(mQueryCallbackOnSuccessCalled); 374 assertTrue(mQueryCallbackOnFailureCalled); 375 } 376 getTestInputBundle(int eligibilityMinExample)377 private static Bundle getTestInputBundle(int eligibilityMinExample) throws Exception { 378 Bundle input = new Bundle(); 379 input.putByteArray( 380 ClientConstants.EXTRA_CONTEXT_DATA, ContextData.toByteArray(TEST_CONTEXT_DATA)); 381 input.putString(ClientConstants.EXTRA_POPULATION_NAME, TEST_POPULATION_NAME); 382 input.putString(ClientConstants.EXTRA_TASK_ID, TEST_TASK_NAME); 383 input.putString(ClientConstants.EXTRA_COLLECTION_URI, TEST_COLLECTION_URI); 384 input.putInt(ClientConstants.EXTRA_ELIGIBILITY_MIN_EXAMPLE, eligibilityMinExample); 385 return input; 386 } 387 388 private class TestIteratorCallback implements IExampleStoreIteratorCallback { 389 byte[] mExpectedExample; 390 byte[] mExpectedResumptionToken; 391 setExpected(byte[] expectedExample, byte[] expectedResumptionToken)392 public void setExpected(byte[] expectedExample, byte[] expectedResumptionToken) { 393 mExpectedExample = expectedExample; 394 mExpectedResumptionToken = expectedResumptionToken; 395 } 396 397 @Override onIteratorNextSuccess(Bundle result)398 public void onIteratorNextSuccess(Bundle result) throws RemoteException { 399 assertArrayEquals(mExpectedExample, result.getByteArray(EXTRA_EXAMPLE_ITERATOR_RESULT)); 400 assertArrayEquals( 401 mExpectedResumptionToken, 402 result.getByteArray(EXTRA_EXAMPLE_ITERATOR_RESUMPTION_TOKEN)); 403 mIteratorCallbackOnSuccessCalled = true; 404 mLatch.countDown(); 405 } 406 407 @Override onIteratorNextFailure(int i)408 public void onIteratorNextFailure(int i) throws RemoteException { 409 mIteratorCallbackOnFailureCalled = true; 410 mLatch.countDown(); 411 } 412 413 @Override asBinder()414 public IBinder asBinder() { 415 return null; 416 } 417 } 418 419 private class TestQueryCallback implements IExampleStoreCallback { 420 private IExampleStoreIterator mIterator; 421 422 @Override onStartQuerySuccess(IExampleStoreIterator iExampleStoreIterator)423 public void onStartQuerySuccess(IExampleStoreIterator iExampleStoreIterator) 424 throws RemoteException { 425 mQueryCallbackOnSuccessCalled = true; 426 mIterator = iExampleStoreIterator; 427 mLatch.countDown(); 428 } 429 430 @Override onStartQueryFailure(int errorCode)431 public void onStartQueryFailure(int errorCode) { 432 mQueryCallbackOnFailureCalled = true; 433 mLatch.countDown(); 434 } 435 436 @Override asBinder()437 public IBinder asBinder() { 438 return null; 439 } 440 getIterator()441 public IExampleStoreIterator getIterator() { 442 return mIterator; 443 } 444 } 445 446 @After cleanup()447 public void cleanup() { 448 OnDevicePersonalizationDbHelper dbHelper = 449 OnDevicePersonalizationDbHelper.getInstanceForTest(APPLICATION_CONTEXT); 450 dbHelper.getWritableDatabase().close(); 451 dbHelper.getReadableDatabase().close(); 452 dbHelper.close(); 453 } 454 } 455