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