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