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