1 /* 2 * Copyright (C) 2021 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.bedstead.remoteframeworkclasses.processor; 18 19 import com.android.bedstead.remoteframeworkclasses.processor.annotations.RemoteFrameworkClasses; 20 21 import com.google.android.enterprise.connectedapps.annotations.CrossUser; 22 import com.google.auto.service.AutoService; 23 import com.google.common.collect.ImmutableSet; 24 import com.google.common.io.Resources; 25 import com.squareup.javapoet.AnnotationSpec; 26 import com.squareup.javapoet.ClassName; 27 import com.squareup.javapoet.JavaFile; 28 import com.squareup.javapoet.MethodSpec; 29 import com.squareup.javapoet.ParameterSpec; 30 import com.squareup.javapoet.TypeSpec; 31 32 import java.io.IOException; 33 import java.io.PrintWriter; 34 import java.net.URL; 35 import java.nio.charset.StandardCharsets; 36 import java.util.ArrayList; 37 import java.util.HashMap; 38 import java.util.HashSet; 39 import java.util.List; 40 import java.util.Map; 41 import java.util.Objects; 42 import java.util.Optional; 43 import java.util.Set; 44 import java.util.stream.Collectors; 45 46 import javax.annotation.processing.AbstractProcessor; 47 import javax.annotation.processing.RoundEnvironment; 48 import javax.annotation.processing.SupportedAnnotationTypes; 49 import javax.lang.model.SourceVersion; 50 import javax.lang.model.element.ExecutableElement; 51 import javax.lang.model.element.Modifier; 52 import javax.lang.model.element.TypeElement; 53 import javax.lang.model.element.VariableElement; 54 import javax.lang.model.type.DeclaredType; 55 import javax.lang.model.type.TypeKind; 56 import javax.lang.model.type.TypeMirror; 57 import javax.lang.model.util.Elements; 58 import javax.tools.JavaFileObject; 59 60 /** 61 * Processor for generating {@code RemoteSystemService} classes. 62 * 63 * <p>This is started by including the {@link RemoteFrameworkClasses} annotation. 64 * 65 * <p>For each entry in {@code FRAMEWORK_CLASSES} this will generate an interface including all 66 * public 67 * and test APIs with the {@code CrossUser} annotation. This interface will be named the same as 68 * the 69 * framework class except with a prefix of "Remote", and will be in the same package. 70 * 71 * <p>This will also generate an implementation of the interface which takes an instance of the 72 * framework class in the constructor, and each method proxying calls to the framework class. 73 */ 74 @SupportedAnnotationTypes({ 75 "com.android.bedstead.remoteframeworkclasses.processor.annotations.RemoteFrameworkClasses", 76 }) 77 @AutoService(javax.annotation.processing.Processor.class) 78 public final class Processor extends AbstractProcessor { 79 80 private static final ImmutableSet<String> FRAMEWORK_CLASSES = 81 loadList("/apis/framework-classes.txt"); 82 83 private static final String PARENT_PROFILE_INSTANCE = 84 "public android.app.admin.DevicePolicyManager getParentProfileInstance(android" 85 + ".content.ComponentName)"; 86 private static final String GET_CONTENT_RESOLVER = 87 "public android.content.ContentResolver getContentResolver()"; 88 private static final String GET_ADAPTER = 89 "public android.bluetooth.BluetoothAdapter getAdapter()"; 90 private static final String GET_DEFAULT_ADAPTER = 91 "public static android.bluetooth.BluetoothAdapter getDefaultAdapter()"; 92 93 private static final ImmutableSet<String> BLOCKLISTED_TYPES = 94 loadList("/apis/type-blocklist.txt"); 95 private static final ImmutableSet<String> ALLOWLISTED_METHODS = 96 loadList("/apis/allowlisted-methods.txt"); 97 98 // TODO(b/332847045): get resource from TestApisReflection target 99 static final ImmutableSet<String> ALLOWLISTED_TEST_CLASSES = 100 loadList("/apis/allowlisted-test-classes.txt"); loadList(String filename)101 private static ImmutableSet<String> loadList(String filename) { 102 try { 103 return ImmutableSet.copyOf(Resources.toString( 104 Processor.class.getResource(filename), 105 StandardCharsets.UTF_8).split("\n")); 106 } catch (IOException e) { 107 throw new IllegalStateException("Could not read file", e); 108 } 109 } 110 111 /** 112 * The TestApisReflection module generates proxy classes used to access TestApi classes and 113 * methods through reflection. These proxy classes are then processed like other framework 114 * classes in this processor. 115 */ 116 static final String TEST_APIS_REFLECTION_PACKAGE = "android.cts.testapisreflection"; 117 private static final String TEST_APIS_REFLECTION_FILE = 118 TEST_APIS_REFLECTION_PACKAGE + ".TestApisReflectionKt"; 119 120 private static final ClassName NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME = 121 ClassName.get("com.android.bedstead.remoteframeworkclasses", 122 "NullParcelableRemoteDevicePolicyManager"); 123 private static final ClassName NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME = 124 ClassName.get("com.android.bedstead.remoteframeworkclasses", 125 "NullParcelableRemoteContentResolver"); 126 private static final ClassName NULL_PARCELABLE_REMOTE_BLUETOOTH_ADAPTER_CLASSNAME = 127 ClassName.get("com.android.bedstead.remoteframeworkclasses", 128 "NullParcelableRemoteBluetoothAdapter"); 129 130 // TODO(b/205562849): These only support passing null, which is fine for existing tests but 131 // will be misleading 132 private static final ClassName NULL_PARCELABLE_ACTIVITY_CLASSNAME = 133 ClassName.get("com.android.bedstead.remoteframeworkclasses", 134 "NullParcelableActivity"); 135 private static final ClassName NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME = 136 ClassName.get("com.android.bedstead.remoteframeworkclasses", 137 "NullParcelableAccountManagerCallback"); 138 private static final ClassName NULL_HANDLER_CALLBACK_CLASSNAME = 139 ClassName.get("com.android.bedstead.remoteframeworkclasses", 140 "NullParcelableHandler"); 141 142 private static final ClassName COMPONENT_NAME_CLASSNAME = 143 ClassName.get("android.content", "ComponentName"); 144 145 private static final ClassName ACCOUNT_MANAGE_FUTURE_WRAPPER_CLASSNAME = 146 ClassName.get( 147 "com.android.bedstead.remoteframeworkclasses", "AccountManagerFutureWrapper"); 148 149 @Override getSupportedSourceVersion()150 public SourceVersion getSupportedSourceVersion() { 151 return SourceVersion.latest(); 152 } 153 154 @Override process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv)155 public boolean process(Set<? extends TypeElement> annotations, 156 RoundEnvironment roundEnv) { 157 if (!roundEnv.getElementsAnnotatedWith(RemoteFrameworkClasses.class).isEmpty()) { 158 Set<MethodSignature> allowListedMethods = ALLOWLISTED_METHODS.stream() 159 .map(i -> MethodSignature.forApiString(i, processingEnv.getTypeUtils(), 160 processingEnv.getElementUtils())) 161 .collect(Collectors.toUnmodifiableSet()); 162 163 for (String systemService : FRAMEWORK_CLASSES) { 164 TypeElement typeElement = 165 processingEnv.getElementUtils().getTypeElement(systemService); 166 generateRemoteSystemService( 167 typeElement, allowListedMethods, processingEnv.getElementUtils()); 168 } 169 170 generateWrappers(); 171 } 172 173 return true; 174 } 175 generateWrappers()176 private void generateWrappers() { 177 generateWrapper(NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME); 178 generateWrapper(NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME); 179 generateWrapper(NULL_PARCELABLE_REMOTE_BLUETOOTH_ADAPTER_CLASSNAME); 180 generateWrapper(NULL_PARCELABLE_ACTIVITY_CLASSNAME); 181 generateWrapper(NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME); 182 generateWrapper(NULL_HANDLER_CALLBACK_CLASSNAME); 183 } 184 generateWrapper(ClassName className)185 private void generateWrapper(ClassName className) { 186 String contents = null; 187 try { 188 URL url = Processor.class.getResource( 189 "/parcelablewrappers/" + className.simpleName() + ".java.txt"); 190 contents = Resources.toString(url, StandardCharsets.UTF_8); 191 } catch (IOException e) { 192 throw new IllegalStateException("Could not parse wrapper " + className, e); 193 } 194 195 JavaFileObject builderFile; 196 try { 197 builderFile = processingEnv.getFiler() 198 .createSourceFile(className.packageName() + "." + className.simpleName()); 199 } catch (IOException e) { 200 throw new IllegalStateException( 201 "Could not write parcelablewrapper for " + className, e); 202 } 203 204 try (PrintWriter out = new PrintWriter(builderFile.openWriter())) { 205 out.write(contents); 206 } catch (IOException e) { 207 throw new IllegalStateException( 208 "Could not write parcelablewrapper for " + className, e); 209 } 210 } 211 generateRemoteSystemService( TypeElement frameworkClass, Set<MethodSignature> allowListedMethods, Elements elements)212 private void generateRemoteSystemService( 213 TypeElement frameworkClass, 214 Set<MethodSignature> allowListedMethods, 215 Elements elements) { 216 Set<Api> apis = filterMethods(frameworkClass, 217 getMethods(frameworkClass, processingEnv.getElementUtils()), 218 Apis.forClass(frameworkClass.getQualifiedName().toString(), 219 processingEnv.getTypeUtils(), processingEnv.getElementUtils()), elements) 220 .stream() 221 .filter(t -> t.isTestApi || 222 !usesBlocklistedType(t.method, allowListedMethods, elements)) 223 .collect(Collectors.toSet()); 224 225 generateFrameworkInterface(frameworkClass, apis); 226 generateFrameworkImpl(frameworkClass, apis); 227 228 if (frameworkClass.getSimpleName().contentEquals("DevicePolicyManager")) { 229 // Special case, we need to support the .getParentProfileInstance method 230 generateDpmParent(frameworkClass, apis); 231 } 232 } 233 removeTypeArguments(TypeMirror type)234 private static String removeTypeArguments(TypeMirror type) { 235 if (type instanceof DeclaredType) { 236 return ((DeclaredType) type).asElement().asType().toString().split("<", 2)[0]; 237 } 238 return type.toString(); 239 } 240 extractTypeArguments(TypeMirror type)241 public static List<TypeMirror> extractTypeArguments(TypeMirror type) { 242 if (!(type instanceof DeclaredType)) { 243 return new ArrayList<>(); 244 } 245 246 return new ArrayList<>(((DeclaredType) type).getTypeArguments()); 247 } 248 isBlocklistedType(TypeMirror typeMirror)249 private boolean isBlocklistedType(TypeMirror typeMirror) { 250 if (BLOCKLISTED_TYPES.contains(removeTypeArguments(typeMirror))) { 251 return true; 252 } 253 254 for (TypeMirror t : extractTypeArguments(typeMirror)) { 255 if (isBlocklistedType(t)) { 256 return true; 257 } 258 } 259 260 return false; 261 } 262 usesBlocklistedType(ExecutableElement method, Set<MethodSignature> allowListedMethods, Elements elements)263 private boolean usesBlocklistedType(ExecutableElement method, Set<MethodSignature> allowListedMethods, Elements elements) { 264 if (allowListedMethods.contains(MethodSignature.forMethod(method, elements))) { 265 return false; // Special case hacked in methods 266 } 267 268 if (isBlocklistedType(method.getReturnType())) { 269 return true; 270 } 271 272 for (VariableElement parameter : method.getParameters()) { 273 if (isBlocklistedType(parameter.asType())) { 274 return true; 275 } 276 } 277 278 for (TypeMirror exception : method.getThrownTypes()) { 279 if (isBlocklistedType(exception)) { 280 return true; 281 } 282 } 283 284 return false; 285 } 286 generateFrameworkInterface(TypeElement frameworkClass, Set<Api> apis)287 private void generateFrameworkInterface(TypeElement frameworkClass, Set<Api> apis) { 288 MethodSignature parentProfileInstanceSignature = 289 MethodSignature.forApiString(PARENT_PROFILE_INSTANCE, processingEnv.getTypeUtils(), 290 processingEnv.getElementUtils()); 291 MethodSignature getContentResolverSignature = 292 MethodSignature.forApiString(GET_CONTENT_RESOLVER, processingEnv.getTypeUtils(), 293 processingEnv.getElementUtils()); 294 MethodSignature getAdapterSignature = 295 MethodSignature.forApiString(GET_ADAPTER, processingEnv.getTypeUtils(), 296 processingEnv.getElementUtils()); 297 MethodSignature getDefaultAdapterSignature = 298 MethodSignature.forApiString(GET_DEFAULT_ADAPTER, processingEnv.getTypeUtils(), 299 processingEnv.getElementUtils()); 300 301 Map<MethodSignature, ClassName> signatureReturnOverrides = new HashMap<>(); 302 signatureReturnOverrides.put(parentProfileInstanceSignature, 303 ClassName.get("android.app.admin", "RemoteDevicePolicyManager")); 304 signatureReturnOverrides.put(getContentResolverSignature, 305 ClassName.get("android.content", "RemoteContentResolver")); 306 signatureReturnOverrides.put(getAdapterSignature, 307 ClassName.get("android.bluetooth", "RemoteBluetoothAdapter")); 308 signatureReturnOverrides.put(getDefaultAdapterSignature, 309 ClassName.get("android.bluetooth", "RemoteBluetoothAdapter")); 310 311 String packageName = frameworkClass.getEnclosingElement().toString(); 312 ClassName className = ClassName.get(packageName, 313 "Remote" + frameworkClass.getSimpleName().toString()); 314 ClassName implClassName = ClassName.get(packageName, 315 "Remote" + frameworkClass.getSimpleName().toString() + "Impl"); 316 TypeSpec.Builder classBuilder = 317 TypeSpec.interfaceBuilder(className) 318 .addModifiers(Modifier.PUBLIC); 319 320 classBuilder.addJavadoc("Public, test, and system interface for {@link $T}.\n\n", 321 frameworkClass); 322 classBuilder.addJavadoc("<p>All methods are annotated {@link $T} for compatibility with the" 323 + " Connected Apps SDK.\n\n", CrossUser.class); 324 classBuilder.addJavadoc("<p>For implementation see {@link $T}.\n", implClassName); 325 326 327 classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class) 328 .addMember("parcelableWrappers", 329 "{$T.class, $T.class, $T.class, $T.class, $T.class, $T.class}", 330 NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME, 331 NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME, 332 NULL_PARCELABLE_REMOTE_BLUETOOTH_ADAPTER_CLASSNAME, 333 NULL_PARCELABLE_ACTIVITY_CLASSNAME, 334 NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME, 335 NULL_HANDLER_CALLBACK_CLASSNAME) 336 .addMember("futureWrappers", "$T.class", 337 ACCOUNT_MANAGE_FUTURE_WRAPPER_CLASSNAME) 338 .build()); 339 340 for (Api api : apis) { 341 ExecutableElement method = api.method; 342 343 MethodSpec.Builder methodBuilder = 344 MethodSpec.methodBuilder(method.getSimpleName().toString()) 345 .returns(ClassName.get(method.getReturnType())) 346 .addModifiers(Modifier.PUBLIC, Modifier.ABSTRACT) 347 .addAnnotation(CrossUser.class); 348 349 MethodSignature signature = MethodSignature.forMethod(method, 350 processingEnv.getElementUtils()); 351 352 if (signatureReturnOverrides.containsKey(signature)) { 353 methodBuilder.returns(signatureReturnOverrides.get(signature)); 354 } 355 356 methodBuilder.addJavadoc("See {@link $T#$L}.", 357 ClassName.get(frameworkClass.asType()), method.getSimpleName()); 358 359 for (TypeMirror thrownType : method.getThrownTypes()) { 360 methodBuilder.addException(ClassName.get(thrownType)); 361 } 362 363 List<? extends VariableElement> parameters; 364 if (api.isTestApi) { 365 // This is a kotlin extension method. Kotlin extension methods when converted to 366 // java code have the receiver as the first argument. We need to drop this argument. 367 parameters = method.getParameters().subList(1, method.getParameters().size()); 368 } else { 369 parameters = method.getParameters(); 370 } 371 372 for (VariableElement param : parameters) { 373 ParameterSpec parameterSpec = 374 ParameterSpec.builder(ClassName.get(param.asType()), 375 param.getSimpleName().toString()).build(); 376 377 methodBuilder.addParameter(parameterSpec); 378 } 379 380 classBuilder.addMethod(methodBuilder.build()); 381 } 382 383 writeClassToFile(packageName, classBuilder.build()); 384 } 385 generateDpmParent(TypeElement frameworkClass, Set<Api> apis)386 private void generateDpmParent(TypeElement frameworkClass, Set<Api> apis) { 387 MethodSignature parentProfileInstanceSignature = MethodSignature.forApiString( 388 PARENT_PROFILE_INSTANCE, processingEnv.getTypeUtils(), 389 processingEnv.getElementUtils()); 390 String packageName = frameworkClass.getEnclosingElement().toString(); 391 ClassName className = 392 ClassName.get(packageName, "Remote" + frameworkClass.getSimpleName() + "Parent"); 393 TypeSpec.Builder classBuilder = 394 TypeSpec.classBuilder(className).addModifiers(Modifier.FINAL, Modifier.PUBLIC); 395 396 classBuilder.addAnnotation(AnnotationSpec.builder(CrossUser.class) 397 .addMember("parcelableWrappers", 398 "{$T.class, $T.class, $T.class, $T.class, $T.class, $T.class}", 399 NULL_PARCELABLE_REMOTE_DEVICE_POLICY_MANAGER_CLASSNAME, 400 NULL_PARCELABLE_REMOTE_CONTENT_RESOLVER_CLASSNAME, 401 NULL_PARCELABLE_REMOTE_BLUETOOTH_ADAPTER_CLASSNAME, 402 NULL_PARCELABLE_ACTIVITY_CLASSNAME, 403 NULL_PARCELABLE_ACCOUNT_MANAGER_CALLBACK_CLASSNAME, 404 NULL_HANDLER_CALLBACK_CLASSNAME) 405 .build()); 406 407 classBuilder.addField(ClassName.get(frameworkClass), 408 "mFrameworkClass", Modifier.PRIVATE, Modifier.FINAL); 409 410 classBuilder.addMethod( 411 MethodSpec.constructorBuilder() 412 .addModifiers(Modifier.PUBLIC) 413 .addParameter(ClassName.get(frameworkClass), "frameworkClass") 414 .addCode("mFrameworkClass = frameworkClass;") 415 .build() 416 ); 417 418 for (Api api : apis) { 419 ExecutableElement method = api.method; 420 421 MethodSpec.Builder methodBuilder = 422 MethodSpec.methodBuilder(method.getSimpleName().toString()) 423 .returns(ClassName.get(method.getReturnType())) 424 .addModifiers(Modifier.PUBLIC) 425 .addAnnotation(CrossUser.class); 426 427 MethodSignature signature = MethodSignature.forMethod(method, 428 processingEnv.getElementUtils()); 429 430 for (TypeMirror thrownType : method.getThrownTypes()) { 431 methodBuilder.addException(ClassName.get(thrownType)); 432 } 433 434 methodBuilder.addParameter(COMPONENT_NAME_CLASSNAME, "profileOwnerComponentName"); 435 436 List<? extends VariableElement> parameters; 437 if (api.isTestApi) { 438 // This is a kotlin extension method. Kotlin extension methods when converted to 439 // java code have the receiver as the first argument. We need to drop this argument. 440 parameters = method.getParameters().subList(1, method.getParameters().size()); 441 } else { 442 parameters = method.getParameters(); 443 } 444 445 List<String> paramNames = new ArrayList<>(parameters.size()); 446 for (VariableElement param : parameters) { 447 String paramName = param.getSimpleName().toString(); 448 ParameterSpec parameterSpec = 449 ParameterSpec.builder(ClassName.get(param.asType()), paramName).build(); 450 451 paramNames.add(paramName); 452 453 methodBuilder.addParameter(parameterSpec); 454 } 455 456 if (signature.equals(parentProfileInstanceSignature)) { 457 // Special case, we want to return a RemoteDevicePolicyManager instead 458 methodBuilder.returns(ClassName.get( 459 "android.app.admin", "RemoteDevicePolicyManager")); 460 methodBuilder.addStatement( 461 "mFrameworkClass.getParentProfileInstance(profileOwnerComponentName).$L" 462 + "($L)", 463 method.getSimpleName(), String.join(", ", paramNames)); 464 methodBuilder.addStatement("throw new $T($S)", UnsupportedOperationException.class, 465 "TestApp does not support calling .getParentProfileInstance() on a parent" 466 + "."); 467 } else if (method.getReturnType().getKind().equals(TypeKind.VOID)) { 468 if (api.isTestApi) { 469 if (paramNames.isEmpty()) { 470 methodBuilder.addStatement( 471 "$L.$L(mFrameworkClass.getParentProfileInstance(profileOwnerComponentName))", 472 TEST_APIS_REFLECTION_FILE, method.getSimpleName()); 473 } else { 474 methodBuilder.addStatement( 475 "$L.$L(mFrameworkClass.getParentProfileInstance(profileOwnerComponentName), $L)", 476 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 477 String.join(", ", paramNames)); 478 } 479 } else { 480 methodBuilder.addStatement( 481 "mFrameworkClass.getParentProfileInstance(profileOwnerComponentName).$L($L)", 482 method.getSimpleName(), String.join(", ", paramNames)); 483 } 484 } else { 485 if (api.isTestApi) { 486 if (paramNames.isEmpty()) { 487 methodBuilder.addStatement( 488 "return $L.$L(mFrameworkClass.getParentProfileInstance(profileOwnerComponentName))", 489 TEST_APIS_REFLECTION_FILE, method.getSimpleName()); 490 } else { 491 methodBuilder.addStatement( 492 "return $L.$L(mFrameworkClass.getParentProfileInstance(profileOwnerComponentName), $L)", 493 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 494 String.join(", ", paramNames)); 495 } 496 } else { 497 methodBuilder.addStatement( 498 "return mFrameworkClass.getParentProfileInstance" 499 + "(profileOwnerComponentName).$L($L)", 500 method.getSimpleName(), String.join(", ", paramNames)); 501 } 502 } 503 504 classBuilder.addMethod(methodBuilder.build()); 505 } 506 507 writeClassToFile(packageName, classBuilder.build()); 508 } 509 generateFrameworkImpl(TypeElement frameworkClass, Set<Api> apis)510 private void generateFrameworkImpl(TypeElement frameworkClass, Set<Api> apis) { 511 MethodSignature parentProfileInstanceSignature = 512 MethodSignature.forApiString(PARENT_PROFILE_INSTANCE, processingEnv.getTypeUtils(), 513 processingEnv.getElementUtils()); 514 MethodSignature getContentResolverSignature = 515 MethodSignature.forApiString(GET_CONTENT_RESOLVER, processingEnv.getTypeUtils(), 516 processingEnv.getElementUtils()); 517 MethodSignature getAdapterSignature = 518 MethodSignature.forApiString(GET_ADAPTER, processingEnv.getTypeUtils(), 519 processingEnv.getElementUtils()); 520 MethodSignature getDefaultAdapterSignature = 521 MethodSignature.forApiString(GET_DEFAULT_ADAPTER, processingEnv.getTypeUtils(), 522 processingEnv.getElementUtils()); 523 524 Map<MethodSignature, ClassName> signatureReturnOverrides = new HashMap<>(); 525 signatureReturnOverrides.put(parentProfileInstanceSignature, 526 ClassName.get("android.app.admin", "RemoteDevicePolicyManager")); 527 signatureReturnOverrides.put(getContentResolverSignature, 528 ClassName.get("android.content", "RemoteContentResolver")); 529 signatureReturnOverrides.put(getAdapterSignature, 530 ClassName.get("android.bluetooth", "RemoteBluetoothAdapter")); 531 signatureReturnOverrides.put(getDefaultAdapterSignature, 532 ClassName.get("android.bluetooth", "RemoteBluetoothAdapter")); 533 534 String packageName = frameworkClass.getEnclosingElement().toString(); 535 ClassName interfaceClassName = ClassName.get(packageName, 536 "Remote" + frameworkClass.getSimpleName().toString()); 537 ClassName className = ClassName.get(packageName, 538 "Remote" + frameworkClass.getSimpleName().toString() + "Impl"); 539 TypeSpec.Builder classBuilder = 540 TypeSpec.classBuilder( 541 className) 542 .addSuperinterface(interfaceClassName) 543 .addModifiers(Modifier.PUBLIC); 544 545 classBuilder.addField(ClassName.get(frameworkClass), 546 "mFrameworkClass", Modifier.PRIVATE, Modifier.FINAL); 547 548 classBuilder.addMethod( 549 MethodSpec.constructorBuilder() 550 .addModifiers(Modifier.PUBLIC) 551 .addParameter(ClassName.get(frameworkClass), "frameworkClass") 552 .addCode("mFrameworkClass = frameworkClass;") 553 .build() 554 ); 555 556 for (Api api : apis) { 557 ExecutableElement method = api.method; 558 559 MethodSpec.Builder methodBuilder = 560 MethodSpec.methodBuilder(method.getSimpleName().toString()) 561 .returns(ClassName.get(method.getReturnType())) 562 .addModifiers(Modifier.PUBLIC) 563 .addAnnotation(Override.class); 564 565 MethodSignature signature = MethodSignature.forMethod(method, 566 processingEnv.getElementUtils()); 567 568 for (TypeMirror thrownType : method.getThrownTypes()) { 569 methodBuilder.addException(ClassName.get(thrownType)); 570 } 571 572 List<? extends VariableElement> parameters; 573 if (api.isTestApi) { 574 // This is a kotlin extension method. Kotlin extension methods when converted to 575 // java code have the receiver as the first argument. We need to drop this argument. 576 parameters = method.getParameters().subList(1, method.getParameters().size()); 577 } else { 578 parameters = method.getParameters(); 579 } 580 581 List<String> paramNames = new ArrayList<>(parameters.size()); 582 for (VariableElement param : parameters) { 583 String paramName = param.getSimpleName().toString(); 584 585 ParameterSpec parameterSpec = 586 ParameterSpec.builder(ClassName.get(param.asType()), paramName).build(); 587 588 paramNames.add(paramName); 589 590 methodBuilder.addParameter(parameterSpec); 591 } 592 593 String frameworkClassName = "mFrameworkClass"; 594 595 if (method.getModifiers().contains(Modifier.STATIC)) { 596 frameworkClassName = frameworkClass.getQualifiedName().toString(); 597 } 598 599 if (signatureReturnOverrides.containsKey(signature)) { 600 methodBuilder.returns(signatureReturnOverrides.get(signature)); 601 602 ClassName iClassName = signatureReturnOverrides.get(signature); 603 ClassName implClassName = ClassName.get(iClassName.packageName(), iClassName.simpleName() + "Impl"); 604 605 methodBuilder.addStatement( 606 "$1T ret = new $1T($2L.$3L($4L))", 607 implClassName, frameworkClassName, 608 method.getSimpleName(), String.join(", ", paramNames)); 609 // We assume all replacements are null-only 610 methodBuilder.addStatement("return null"); 611 } else if (method.getReturnType().getKind().equals(TypeKind.VOID)) { 612 if (api.isTestApi) { 613 if (paramNames.isEmpty()) { 614 methodBuilder.addStatement( 615 "$L.$L($L)", 616 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 617 "mFrameworkClass"); 618 } else { 619 methodBuilder.addStatement( 620 "$L.$L($L, $L)", 621 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 622 "mFrameworkClass", 623 String.join(", ", paramNames)); 624 } 625 } else { 626 methodBuilder.addStatement( 627 "$L.$L($L)", 628 frameworkClassName, method.getSimpleName(), 629 String.join(", ", paramNames)); 630 } 631 } else { 632 if (api.isTestApi) { 633 if (paramNames.isEmpty()) { 634 methodBuilder.addStatement( 635 "return $L.$L($L)", 636 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 637 "mFrameworkClass"); 638 } else { 639 methodBuilder.addStatement( 640 "return $L.$L($L, $L)", 641 TEST_APIS_REFLECTION_FILE, method.getSimpleName(), 642 "mFrameworkClass", 643 String.join(", ", paramNames)); 644 } 645 } else { 646 methodBuilder.addStatement( 647 "return $L.$L($L)", 648 frameworkClassName, method.getSimpleName(), 649 String.join(", ", paramNames)); 650 } 651 } 652 653 classBuilder.addMethod(methodBuilder.build()); 654 } 655 656 writeClassToFile(packageName, classBuilder.build()); 657 } 658 filterMethods(TypeElement frameworkClass, Set<ExecutableElement> allMethods, Apis validApis, Elements elements)659 private Set<Api> filterMethods(TypeElement frameworkClass, 660 Set<ExecutableElement> allMethods, Apis validApis, Elements elements) { 661 Set<Api> filteredMethods = new HashSet<>(); 662 663 for (ExecutableElement method : allMethods) { 664 MethodSignature methodSignature = MethodSignature.forMethod(method, elements); 665 if (validApis.methods().contains(methodSignature)) { 666 if (method.getModifiers().contains(Modifier.PROTECTED)) { 667 System.out.println(methodSignature + " is protected. Dropping"); 668 } else { 669 filteredMethods.add(new Api(method, /* isTestApi= */ false)); 670 } 671 } else { 672 System.out.println("No matching public API for " + methodSignature); 673 } 674 } 675 676 filterValidTestApis(filteredMethods, frameworkClass, validApis, elements); 677 678 return filteredMethods; 679 } 680 filterValidTestApis(Set<Api> filteredMethods, TypeElement frameworkClass, Apis validApis, Elements elements)681 private void filterValidTestApis(Set<Api> filteredMethods, 682 TypeElement frameworkClass, Apis validApis, Elements elements) { 683 Set<ExecutableElement> testMethods = new HashSet<>(); 684 TypeElement testApisReflectionTypeElement = 685 processingEnv.getElementUtils().getTypeElement(TEST_APIS_REFLECTION_FILE); 686 687 testApisReflectionTypeElement.getEnclosedElements().stream() 688 .filter(e -> e instanceof ExecutableElement) 689 .map(e -> (ExecutableElement) e) 690 .filter(e -> e.getModifiers().contains(Modifier.PUBLIC)) 691 .forEach(e -> testMethods.add(e)); 692 693 for (ExecutableElement method : testMethods) { 694 MethodSignature methodSignature = MethodSignature.forMethod(method, elements); 695 696 if (!methodSignature.mParameterTypes.get(0).equals( 697 frameworkClass.getQualifiedName().toString())) { 698 continue; 699 } 700 701 Optional<MethodSignature> validTestApi = validApis.methods().stream().filter(a -> 702 methodSignature.getName().equals(a.getName()) && 703 methodSignature.getReturnType().equals(a.getReturnType()) && 704 methodSignature.mExceptions.equals(a.mExceptions) && 705 methodSignature.mParameterTypes.containsAll(a.mParameterTypes)) 706 .findFirst(); 707 708 if (validTestApi.isPresent()) { 709 if (method.getModifiers().contains(Modifier.PROTECTED)) { 710 System.out.println(methodSignature + " is protected. Dropping"); 711 } else { 712 filteredMethods.add(new Api(method, /* isTestApi= */ true)); 713 } 714 } else { 715 System.out.println("No matching public API for TestApi " + methodSignature); 716 } 717 } 718 } 719 writeClassToFile(String packageName, TypeSpec clazz)720 private void writeClassToFile(String packageName, TypeSpec clazz) { 721 String qualifiedClassName = 722 packageName.isEmpty() ? clazz.name : packageName + "." + clazz.name; 723 724 JavaFile javaFile = JavaFile.builder(packageName, clazz).build(); 725 try { 726 JavaFileObject builderFile = 727 processingEnv.getFiler().createSourceFile(qualifiedClassName); 728 try (PrintWriter out = new PrintWriter(builderFile.openWriter())) { 729 javaFile.writeTo(out); 730 } 731 } catch (IOException e) { 732 throw new IllegalStateException("Error writing " + qualifiedClassName + " to file", e); 733 } 734 } 735 getMethods(TypeElement interfaceClass, Elements elements)736 private Set<ExecutableElement> getMethods(TypeElement interfaceClass, Elements elements) { 737 Map<String, ExecutableElement> methods = new HashMap<>(); 738 getMethods(methods, interfaceClass, elements); 739 return new HashSet<>(methods.values()); 740 } 741 getMethods(Map<String, ExecutableElement> methods, TypeElement interfaceClass, Elements elements)742 private void getMethods(Map<String, ExecutableElement> methods, TypeElement interfaceClass, 743 Elements elements) { 744 745 interfaceClass.getEnclosedElements().stream() 746 .filter(e -> e instanceof ExecutableElement) 747 .map(e -> (ExecutableElement) e) 748 .filter(e -> !methods.containsKey(e.getSimpleName().toString())) 749 .filter(e -> e.getModifiers().contains(Modifier.PUBLIC)) 750 .forEach(e -> methods.put(methodHash(e), e)); 751 752 interfaceClass.getInterfaces().stream() 753 .map(m -> elements.getTypeElement(m.toString())) 754 .forEach(m -> getMethods(methods, m, elements)); 755 756 757 TypeElement superclassElement = (TypeElement) processingEnv.getTypeUtils() 758 .asElement(interfaceClass.getSuperclass()); 759 760 if (superclassElement != null) { 761 getMethods(methods, superclassElement, elements); 762 } 763 } 764 methodHash(ExecutableElement method)765 private String methodHash(ExecutableElement method) { 766 return method.getSimpleName() + "(" + method.getParameters().stream() 767 .map(p -> p.asType().toString()).collect( 768 Collectors.joining(",")) + ")"; 769 } 770 private static class Api { 771 private final ExecutableElement method; 772 private final boolean isTestApi; 773 Api(ExecutableElement method, boolean isTestApi)774 private Api(ExecutableElement method, boolean isTestApi) { 775 this.method = method; 776 this.isTestApi = isTestApi; 777 } 778 779 @Override equals(Object o)780 public boolean equals(Object o) { 781 if (this == o) return true; 782 if (!(o instanceof Api)) return false; 783 Api api = (Api) o; 784 return isTestApi == api.isTestApi && Objects.equals(method, api.method); 785 } 786 787 @Override hashCode()788 public int hashCode() { 789 return Objects.hash(method, isTestApi); 790 } 791 } 792 } 793