• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright 2024 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 android.adservices.ondevicepersonalization;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import static org.junit.Assert.assertEquals;
21 import static org.junit.Assert.assertFalse;
22 import static org.junit.Assert.assertNotNull;
23 import static org.junit.Assert.assertTrue;
24 
25 import android.adservices.ondevicepersonalization.OnDevicePersonalizationManager.ExecuteResult;
26 import android.adservices.ondevicepersonalization.aidl.IExecuteCallback;
27 import android.adservices.ondevicepersonalization.aidl.IIsFeatureEnabledCallback;
28 import android.adservices.ondevicepersonalization.aidl.IOnDevicePersonalizationManagingService;
29 import android.adservices.ondevicepersonalization.aidl.IRegisterMeasurementEventCallback;
30 import android.adservices.ondevicepersonalization.aidl.IRequestSurfacePackageCallback;
31 import android.content.ComponentName;
32 import android.content.Context;
33 import android.content.pm.PackageManager;
34 import android.os.Bundle;
35 import android.os.IBinder;
36 import android.os.PersistableBundle;
37 import android.os.RemoteException;
38 import android.os.SystemClock;
39 import android.util.Log;
40 
41 import androidx.test.core.app.ApplicationProvider;
42 
43 import com.android.compatibility.common.util.ShellUtils;
44 import com.android.federatedcompute.internal.util.AbstractServiceBinder;
45 import com.android.modules.utils.build.SdkLevel;
46 import com.android.ondevicepersonalization.internal.util.ByteArrayParceledSlice;
47 import com.android.ondevicepersonalization.internal.util.ExceptionInfo;
48 import com.android.ondevicepersonalization.internal.util.LoggerFactory;
49 import com.android.ondevicepersonalization.internal.util.PersistableBundleUtils;
50 import com.android.ondevicepersonalization.testing.utils.ResultReceiver;
51 
52 import org.junit.Before;
53 import org.junit.Test;
54 import org.junit.runner.RunWith;
55 import org.junit.runners.Parameterized;
56 import org.mockito.MockitoAnnotations;
57 
58 import java.util.Arrays;
59 import java.util.Collection;
60 import java.util.concurrent.Executor;
61 import java.util.concurrent.Executors;
62 
63 @RunWith(Parameterized.class)
64 public final class OnDevicePersonalizationManagerTest {
65     private static final LoggerFactory.Logger sLogger = LoggerFactory.getLogger();
66     private static final String TAG = "OnDevicePersonalizationManagerTest";
67     private static final String KEY_OP = "op";
68     private static final String KEY_STATUS_CODE = "status";
69     private static final String KEY_SERVICE_ERROR_CODE = "serviceerror";
70     private static final String KEY_ERROR_MESSAGE = "errormessage";
71     private static final int BEST_VALUE = 10;
72     private static final ComponentName TEST_SERVICE_COMPONENT_NAME =
73             ComponentName.createRelative("com.example.service", ".Example");
74     private final Context mContext = ApplicationProvider.getApplicationContext();
75     private final TestServiceBinder mTestBinder =
76             new TestServiceBinder(
77                     IOnDevicePersonalizationManagingService.Stub.asInterface(new TestService()));
78     private final OnDevicePersonalizationManager mManager =
79             new OnDevicePersonalizationManager(mContext, mTestBinder);
80 
81     private volatile boolean mLogApiStatsCalled = false;
82 
83     @Parameterized.Parameter(0)
84     public boolean mRunExecuteInIsolatedService;
85 
86     @Parameterized.Parameters
data()87     public static Collection<Object[]> data() {
88         return Arrays.asList(
89                 new Object[][] {
90                         {true}, {false}
91                 }
92         );
93     }
94     @Before
setUp()95     public void setUp() throws Exception {
96         MockitoAnnotations.initMocks(this);
97         ShellUtils.runShellCommand(
98                 "device_config put on_device_personalization "
99                         + "shared_isolated_process_feature_enabled "
100                         + SdkLevel.isAtLeastU());
101     }
102 
103     @Test
testExecuteSuccess()104     public void testExecuteSuccess() throws Exception {
105         PersistableBundle params = new PersistableBundle();
106         params.putString(KEY_OP, "ok");
107         var receiver = new ResultReceiver();
108 
109         runExecute(params, receiver);
110 
111         assertTrue(receiver.isSuccess());
112         assertFalse(receiver.isError());
113         assertNotNull(receiver.getResult());
114         if (mRunExecuteInIsolatedService) {
115             ExecuteInIsolatedServiceResponse response =
116                     (ExecuteInIsolatedServiceResponse) receiver.getResult();
117             assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
118             assertThat(response.getBestValue()).isEqualTo(-1);
119         } else {
120             ExecuteResult response = (ExecuteResult) receiver.getResult();
121             assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
122             assertThat(response.getOutputData()).isNull();
123         }
124         assertTrue(mLogApiStatsCalled);
125     }
126 
127     @Test
testExecuteSuccessWithBestValueSpec()128     public void testExecuteSuccessWithBestValueSpec() throws Exception {
129         PersistableBundle bundle = new PersistableBundle();
130         bundle.putString(KEY_OP, "best_value");
131         var receiver = new ResultReceiver<ExecuteInIsolatedServiceResponse>();
132         ExecuteInIsolatedServiceRequest request =
133                 new ExecuteInIsolatedServiceRequest.Builder(TEST_SERVICE_COMPONENT_NAME)
134                         .setAppParams(bundle)
135                         .setOutputSpec(
136                                 ExecuteInIsolatedServiceRequest.OutputSpec.buildBestValueSpec(100))
137                         .build();
138 
139         mManager.executeInIsolatedService(request, Executors.newSingleThreadExecutor(), receiver);
140 
141         assertTrue(receiver.isSuccess());
142         assertFalse(receiver.isError());
143         assertNotNull(receiver.getResult());
144 
145         ExecuteInIsolatedServiceResponse response = receiver.getResult();
146         assertThat(response.getSurfacePackageToken().getTokenString()).isEqualTo("aaaa");
147         assertThat(response.getBestValue()).isEqualTo(BEST_VALUE);
148         assertTrue(mLogApiStatsCalled);
149     }
150 
151     @Test
testExecuteUnknownError()152     public void testExecuteUnknownError() throws Exception {
153         PersistableBundle params = new PersistableBundle();
154         params.putString(KEY_OP, "error");
155         var receiver = new ResultReceiver();
156 
157         runExecute(params, receiver);
158 
159         assertFalse(receiver.isSuccess());
160         assertTrue(receiver.isError());
161         assertTrue(receiver.getException() instanceof IllegalStateException);
162         assertTrue(mLogApiStatsCalled);
163     }
164 
165     @Test
testExecuteServiceError()166     public void testExecuteServiceError() throws Exception {
167         PersistableBundle params = new PersistableBundle();
168         params.putString(KEY_OP, "error");
169         params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
170         var receiver = new ResultReceiver();
171 
172         runExecute(params, receiver);
173 
174         assertFalse(receiver.isSuccess());
175         assertTrue(receiver.isError());
176         assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
177         assertTrue(mLogApiStatsCalled);
178     }
179 
180     @Test
testExecuteErrorWithCode()181     public void testExecuteErrorWithCode() throws Exception {
182         int isolatedServiceErrorCode = 42;
183         PersistableBundle params = new PersistableBundle();
184         params.putString(KEY_OP, "error");
185         params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
186         params.putInt(KEY_SERVICE_ERROR_CODE, isolatedServiceErrorCode);
187         var receiver = new ResultReceiver();
188 
189         runExecute(params, receiver);
190 
191         assertFalse(receiver.isSuccess());
192         assertTrue(receiver.isError());
193         assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
194         assertEquals(
195                 OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
196                 ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
197         assertTrue(receiver.getException().getCause() instanceof IsolatedServiceException);
198         assertEquals(
199                 isolatedServiceErrorCode,
200                 ((IsolatedServiceException) receiver.getException().getCause()).getErrorCode());
201         assertTrue(mLogApiStatsCalled);
202     }
203 
204     @Test
testExecuteErrorWithMessage()205     public void testExecuteErrorWithMessage() throws Exception {
206         PersistableBundle params = new PersistableBundle();
207         params.putString(KEY_OP, "error");
208         params.putInt(KEY_STATUS_CODE, Constants.STATUS_SERVICE_FAILED);
209         params.putString(KEY_ERROR_MESSAGE, "TestErrorMessage");
210         var receiver = new ResultReceiver();
211 
212         runExecute(params, receiver);
213 
214         assertFalse(receiver.isSuccess());
215         assertTrue(receiver.isError());
216         assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
217         assertEquals(
218                 OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
219                 ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
220         Throwable cause = receiver.getException().getCause();
221         assertNotNull(cause);
222         assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*TestErrorMessage.*");
223         assertTrue(mLogApiStatsCalled);
224     }
225 
226     @Test
testExecuteManifestParsingError()227     public void testExecuteManifestParsingError() throws Exception {
228         // The manifest parsing failure gets translated back to PackageManager.NameNotFound
229         // when the legacy execute API is called. The new execute API returns targeted error code.
230         PersistableBundle params = new PersistableBundle();
231         params.putString(KEY_OP, "error");
232         params.putInt(KEY_STATUS_CODE, Constants.STATUS_MANIFEST_PARSING_FAILED);
233         params.putString(KEY_ERROR_MESSAGE, "Failed parsing manifest");
234         var receiver = new ResultReceiver();
235 
236         runExecute(params, receiver);
237 
238         assertFalse(receiver.isSuccess());
239         assertTrue(receiver.isError());
240         Throwable cause = receiver.getException().getCause();
241         assertNotNull(cause);
242         assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*parsing.*");
243         assertTrue(mLogApiStatsCalled);
244         if (mRunExecuteInIsolatedService) {
245             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
246             assertEquals(
247                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
248                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
249         } else {
250             assertTrue(receiver.getException() instanceof PackageManager.NameNotFoundException);
251         }
252     }
253 
254     @Test
testExecuteManifestMisconfigurationError()255     public void testExecuteManifestMisconfigurationError() throws Exception {
256         // The manifest misconfigured failure gets  translated back to Class not found
257         // when the legacy execute API is used. The new execute API returns the targeted error code.
258         PersistableBundle params = new PersistableBundle();
259         params.putString(KEY_OP, "error");
260         params.putInt(KEY_STATUS_CODE, Constants.STATUS_MANIFEST_MISCONFIGURED);
261         params.putString(KEY_ERROR_MESSAGE, "Failed parsing manifest");
262         var receiver = new ResultReceiver();
263 
264         runExecute(params, receiver);
265 
266         assertFalse(receiver.isSuccess());
267         assertTrue(receiver.isError());
268         Throwable cause = receiver.getException().getCause();
269         assertNotNull(cause);
270         assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*parsing.*");
271         assertTrue(mLogApiStatsCalled);
272         if (mRunExecuteInIsolatedService) {
273             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
274             assertEquals(
275                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_MANIFEST_PARSING_FAILED,
276                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
277         } else {
278             assertTrue(receiver.getException() instanceof ClassNotFoundException);
279         }
280     }
281 
282     @Test
testExecuteServiceTimeoutError()283     public void testExecuteServiceTimeoutError() throws Exception {
284         // The service timeout failure gets exposed via corresponding OdpException
285         // when the new execute API is used.
286         PersistableBundle params = new PersistableBundle();
287         params.putString(KEY_OP, "error");
288         params.putInt(KEY_STATUS_CODE, Constants.STATUS_ISOLATED_SERVICE_TIMEOUT);
289         params.putString(KEY_ERROR_MESSAGE, "Service timeout");
290         var receiver = new ResultReceiver<ExecuteResult>();
291 
292         runExecute(params, receiver);
293 
294         assertFalse(receiver.isSuccess());
295         assertTrue(receiver.isError());
296         Throwable cause = receiver.getException().getCause();
297         assertNotNull(cause);
298         assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*timeout.*");
299         assertTrue(mLogApiStatsCalled);
300         if (mRunExecuteInIsolatedService) {
301             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
302             assertEquals(
303                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_TIMEOUT,
304                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
305         } else {
306             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
307             assertEquals(
308                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
309                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
310         }
311     }
312 
313     @Test
testExecuteServiceLoadingError()314     public void testExecuteServiceLoadingError() throws Exception {
315         // The service loading failure gets exposed via corresponding OdpException
316         // when the new execute API is used.
317         PersistableBundle params = new PersistableBundle();
318         params.putString(KEY_OP, "error");
319         params.putInt(KEY_STATUS_CODE, Constants.STATUS_ISOLATED_SERVICE_LOADING_FAILED);
320         params.putString(KEY_ERROR_MESSAGE, "Service loading failed.");
321         var receiver = new ResultReceiver<ExecuteResult>();
322 
323         runExecute(params, receiver);
324 
325         assertFalse(receiver.isSuccess());
326         assertTrue(receiver.isError());
327         Throwable cause = receiver.getException().getCause();
328         assertNotNull(cause);
329         assertThat(cause.getMessage()).containsMatch(".*RuntimeException.*loading.*");
330         assertTrue(mLogApiStatsCalled);
331         if (mRunExecuteInIsolatedService) {
332             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
333             assertEquals(
334                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_LOADING_FAILED,
335                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
336         } else {
337             assertTrue(receiver.getException() instanceof OnDevicePersonalizationException);
338             assertEquals(
339                     OnDevicePersonalizationException.ERROR_ISOLATED_SERVICE_FAILED,
340                     ((OnDevicePersonalizationException) receiver.getException()).getErrorCode());
341         }
342     }
343 
344     @Test
testExecuteCatchesIaeFromService()345     public void testExecuteCatchesIaeFromService() throws Exception {
346         PersistableBundle params = new PersistableBundle();
347         params.putString(KEY_OP, "iae");
348         var receiver = new ResultReceiver();
349 
350         runExecute(params, receiver);
351 
352         assertFalse(receiver.isSuccess());
353         assertTrue(receiver.isError());
354         assertTrue(receiver.getException() instanceof IllegalArgumentException);
355         assertTrue(mLogApiStatsCalled);
356     }
357 
358     @Test
testExecuteCatchesNpeFromService()359     public void testExecuteCatchesNpeFromService() throws Exception {
360         PersistableBundle params = new PersistableBundle();
361         params.putString(KEY_OP, "npe");
362         var receiver = new ResultReceiver();
363 
364         runExecute(params, receiver);
365 
366         assertFalse(receiver.isSuccess());
367         assertTrue(receiver.isError());
368         assertTrue(receiver.getException() instanceof NullPointerException);
369         assertTrue(mLogApiStatsCalled);
370     }
371 
372     @Test
testExecuteCatchesOtherExceptions()373     public void testExecuteCatchesOtherExceptions() throws Exception {
374         PersistableBundle params = new PersistableBundle();
375         params.putString(KEY_OP, "ise");
376         var receiver = new ResultReceiver();
377 
378         runExecute(params, receiver);
379 
380         assertFalse(receiver.isSuccess());
381         assertTrue(receiver.isError());
382         assertTrue(receiver.getException() instanceof IllegalStateException);
383         assertTrue(mLogApiStatsCalled);
384     }
385 
runExecute(PersistableBundle params, ResultReceiver receiver)386     private void runExecute(PersistableBundle params, ResultReceiver receiver) {
387         if (mRunExecuteInIsolatedService) {
388             ExecuteInIsolatedServiceRequest request =
389                     new ExecuteInIsolatedServiceRequest.Builder(TEST_SERVICE_COMPONENT_NAME)
390                             .setAppParams(params)
391                             .build();
392             mManager.executeInIsolatedService(
393                     request, Executors.newSingleThreadExecutor(), receiver);
394         } else {
395             mManager.execute(
396                     TEST_SERVICE_COMPONENT_NAME,
397                     params,
398                     Executors.newSingleThreadExecutor(),
399                     receiver);
400         }
401     }
402 
403     private class TestService extends IOnDevicePersonalizationManagingService.Stub {
404         @Override
getVersion()405         public String getVersion() {
406             return "1.0";
407         }
408 
409         @Override
execute( String callingPackageName, ComponentName handler, Bundle wrappedParams, CallerMetadata metadata, ExecuteOptionsParcel options, IExecuteCallback callback)410         public void execute(
411                 String callingPackageName,
412                 ComponentName handler,
413                 Bundle wrappedParams,
414                 CallerMetadata metadata,
415                 ExecuteOptionsParcel options,
416                 IExecuteCallback callback) {
417             try {
418                 PersistableBundle params;
419                 String op;
420                 try {
421                     ByteArrayParceledSlice paramsBuffer =
422                             wrappedParams.getParcelable(
423                                     Constants.EXTRA_APP_PARAMS_SERIALIZED,
424                                     ByteArrayParceledSlice.class);
425                     params = PersistableBundleUtils.fromByteArray(paramsBuffer.getByteArray());
426                     op = params.getString(KEY_OP);
427                 } catch (Exception e) {
428                     Log.e(TAG, "error extracting params", e);
429                     throw new IllegalStateException(e);
430                 }
431                 if (op.equals("ok")) {
432                     Bundle bundle = new Bundle();
433                     bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, "aaaa");
434                     callback.onSuccess(
435                             bundle,
436                             new CalleeMetadata.Builder()
437                                     .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
438                                     .build());
439                 } else if (options.getOutputType()
440                         == ExecuteInIsolatedServiceRequest.OutputSpec.OUTPUT_TYPE_BEST_VALUE) {
441                     Bundle bundle = new Bundle();
442                     bundle.putString(Constants.EXTRA_SURFACE_PACKAGE_TOKEN_STRING, "aaaa");
443                     bundle.putInt(Constants.EXTRA_OUTPUT_BEST_VALUE, BEST_VALUE);
444                     callback.onSuccess(
445                             bundle,
446                             new CalleeMetadata.Builder()
447                                     .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
448                                     .build());
449                 } else if (op.equals("error")) {
450                     int statusCode =
451                             params.getInt(KEY_STATUS_CODE, Constants.STATUS_INTERNAL_ERROR);
452                     int serviceErrorCode = params.getInt(KEY_SERVICE_ERROR_CODE, 0);
453                     String errorMessage = params.getString(KEY_ERROR_MESSAGE);
454                     callback.onError(
455                             statusCode,
456                             serviceErrorCode,
457                             ExceptionInfo.toByteArray(new RuntimeException(errorMessage), 3),
458                             new CalleeMetadata.Builder()
459                                     .setCallbackInvokeTimeMillis(SystemClock.elapsedRealtime())
460                                     .build());
461                 } else if (op.equals("iae")) {
462                     throw new IllegalArgumentException();
463                 } else if (op.equals("npe")) {
464                     throw new NullPointerException();
465                 } else if (op.equals("ise")) {
466                     throw new IllegalStateException();
467                 } else {
468                     throw new UnsupportedOperationException();
469                 }
470             } catch (RemoteException e) {
471                 Log.e(TAG, "callback error", e);
472             }
473         }
474 
475         @Override
requestSurfacePackage( String surfacePackageToken, IBinder hostToken, int displayId, int width, int height, CallerMetadata metadata, IRequestSurfacePackageCallback callback)476         public void requestSurfacePackage(
477                 String surfacePackageToken,
478                 IBinder hostToken,
479                 int displayId,
480                 int width,
481                 int height,
482                 CallerMetadata metadata,
483                 IRequestSurfacePackageCallback callback) {
484             throw new UnsupportedOperationException();
485         }
486 
487         @Override
registerMeasurementEvent( int eventType, Bundle params, CallerMetadata metadata, IRegisterMeasurementEventCallback callback)488         public void registerMeasurementEvent(
489                 int eventType,
490                 Bundle params,
491                 CallerMetadata metadata,
492                 IRegisterMeasurementEventCallback callback) {
493             throw new UnsupportedOperationException();
494         }
495 
496         @Override
isFeatureEnabled( String featureName, CallerMetadata metadata, IIsFeatureEnabledCallback callback)497         public void isFeatureEnabled(
498                 String featureName,
499                 CallerMetadata metadata,
500                 IIsFeatureEnabledCallback callback) {
501             throw new UnsupportedOperationException();
502         }
503 
504         @Override
logApiCallStats( String sdkPackageName, int apiName, long latencyMillis, long rpcCallLatencyMillis, long rpcReturnLatencyMillis, int responseCode)505         public void logApiCallStats(
506                 String sdkPackageName,
507                 int apiName,
508                 long latencyMillis,
509                 long rpcCallLatencyMillis,
510                 long rpcReturnLatencyMillis,
511                 int responseCode) {
512             if (!sdkPackageName.equals("com.example.service")) {
513                 return;
514             }
515             mLogApiStatsCalled = true;
516         }
517     }
518 
519     private static class TestServiceBinder
520             extends AbstractServiceBinder<IOnDevicePersonalizationManagingService> {
521         private final IOnDevicePersonalizationManagingService mService;
522 
TestServiceBinder(IOnDevicePersonalizationManagingService service)523         TestServiceBinder(IOnDevicePersonalizationManagingService service) {
524             mService = service;
525         }
526 
527         @Override
getService(Executor executor)528         public IOnDevicePersonalizationManagingService getService(Executor executor) {
529             return mService;
530         }
531 
532         @Override
unbindFromService()533         public void unbindFromService() {}
534     }
535 }
536