• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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 }