1 /*
2  * Copyright (C) 2019 The Dagger Authors.
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 dagger.hilt.android.processor.internal.androidentrypoint;
18 
19 import static dagger.internal.codegen.langmodel.DaggerElements.getMethodDescriptor;
20 
21 import com.google.common.collect.Iterables;
22 import com.squareup.javapoet.ClassName;
23 import com.squareup.javapoet.FieldSpec;
24 import com.squareup.javapoet.JavaFile;
25 import com.squareup.javapoet.MethodSpec;
26 import com.squareup.javapoet.ParameterSpec;
27 import com.squareup.javapoet.TypeName;
28 import com.squareup.javapoet.TypeSpec;
29 import com.squareup.javapoet.TypeVariableName;
30 import dagger.hilt.android.processor.internal.AndroidClassNames;
31 import dagger.hilt.android.processor.internal.MoreTypes;
32 import dagger.hilt.processor.internal.Processors;
33 import java.io.IOException;
34 import javax.annotation.processing.ProcessingEnvironment;
35 import javax.lang.model.element.ExecutableElement;
36 import javax.lang.model.element.Modifier;
37 import javax.lang.model.element.TypeElement;
38 import javax.lang.model.type.TypeKind;
39 import javax.lang.model.util.ElementFilter;
40 
41 /** Generates an Hilt BroadcastReceiver class for the @AndroidEntryPoint annotated class. */
42 public final class BroadcastReceiverGenerator {
43 
44   private static final String ON_RECEIVE_DESCRIPTOR =
45       "onReceive(Landroid/content/Context;Landroid/content/Intent;)V";
46 
47   private final ProcessingEnvironment env;
48   private final AndroidEntryPointMetadata metadata;
49   private final ClassName generatedClassName;
50 
BroadcastReceiverGenerator( ProcessingEnvironment env, AndroidEntryPointMetadata metadata)51   public BroadcastReceiverGenerator(
52       ProcessingEnvironment env, AndroidEntryPointMetadata metadata) {
53     this.env = env;
54     this.metadata = metadata;
55 
56     generatedClassName = metadata.generatedClassName();
57   }
58 
59   // @Generated("BroadcastReceiverGenerator")
60   // abstract class Hilt_$CLASS extends $BASE {
61   //   ...
62   // }
generate()63   public void generate() throws IOException {
64     TypeSpec.Builder builder =
65         TypeSpec.classBuilder(generatedClassName.simpleName())
66             .addOriginatingElement(metadata.element())
67             .superclass(metadata.baseClassName())
68             .addModifiers(metadata.generatedClassModifiers())
69             .addMethod(onReceiveMethod());
70 
71     Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT);
72     Processors.addGeneratedAnnotation(builder, env, getClass());
73     Generators.copyConstructors(metadata.baseElement(), builder);
74 
75     metadata.baseElement().getTypeParameters().stream()
76         .map(TypeVariableName::get)
77         .forEachOrdered(builder::addTypeVariable);
78 
79     Generators.addInjectionMethods(metadata, builder);
80     Generators.copyLintAnnotations(metadata.element(), builder);
81 
82     // Add an unused field used as a marker to let the bytecode injector know this receiver will
83     // need to be injected with a super.onReceive call. This is only necessary if no concrete
84     // onReceive call is implemented in any of the super classes.
85     if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) {
86       builder.addField(
87           FieldSpec.builder(
88                   TypeName.BOOLEAN,
89                   "onReceiveBytecodeInjectionMarker",
90                   Modifier.PRIVATE,
91                   Modifier.FINAL)
92               .initializer("false")
93               .build());
94     }
95 
96     JavaFile.builder(generatedClassName.packageName(),
97         builder.build()).build().writeTo(env.getFiler());
98   }
99 
isOnReceiveImplemented(TypeElement typeElement)100   private static boolean isOnReceiveImplemented(TypeElement typeElement) {
101     boolean isImplemented =
102         ElementFilter.methodsIn(typeElement.getEnclosedElements()).stream()
103             .anyMatch(
104                 methodElement ->
105                     getMethodDescriptor(methodElement).equals(ON_RECEIVE_DESCRIPTOR)
106                         && !methodElement.getModifiers().contains(Modifier.ABSTRACT));
107     if (isImplemented) {
108       return true;
109     } else if (typeElement.getSuperclass().getKind() != TypeKind.NONE) {
110       return isOnReceiveImplemented(MoreTypes.asTypeElement(typeElement.getSuperclass()));
111     } else {
112       return false;
113     }
114   }
115 
116   // @Override
117   // public void onReceive(Context context, Intent intent) {
118   //   inject(context);
119   //   super.onReceive();
120   // }
onReceiveMethod()121   private MethodSpec onReceiveMethod() throws IOException {
122     MethodSpec.Builder method =
123         MethodSpec.methodBuilder("onReceive")
124             .addAnnotation(Override.class)
125             .addAnnotation(AndroidClassNames.CALL_SUPER)
126             .addModifiers(Modifier.PUBLIC)
127             .addParameter(ParameterSpec.builder(AndroidClassNames.CONTEXT, "context").build())
128             .addParameter(ParameterSpec.builder(AndroidClassNames.INTENT, "intent").build())
129             .addStatement("inject(context)");
130 
131     if (metadata.overridesAndroidEntryPointClass()) {
132       // We directly call super.onReceive here because we know Hilt base classes have a
133       // non-abstract onReceive method. However, because the Hilt base class may not be generated
134       // already we cannot fall down to the below logic to find it.
135       method.addStatement("super.onReceive(context, intent)");
136     } else {
137       // Get the onReceive method element from BroadcastReceiver.
138       ExecutableElement onReceiveElement =
139           Iterables.getOnlyElement(
140               MoreTypes.findMethods(
141                   env.getElementUtils()
142                       .getTypeElement(AndroidClassNames.BROADCAST_RECEIVER.toString()),
143                   "onReceive"));
144 
145       // If the base class or one of its super classes implements onReceive, call super.onReceive()
146       MoreTypes.findInheritedMethod(env.getTypeUtils(), metadata.baseElement(), onReceiveElement)
147           .filter(onReceive -> !onReceive.getModifiers().contains(Modifier.ABSTRACT))
148           .ifPresent(onReceive -> method.addStatement("super.onReceive(context, intent)"));
149     }
150 
151     return method.build();
152   }
153 }
154