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 com.google.common.collect.Iterables.getOnlyElement; 20 import static dagger.internal.codegen.extension.DaggerCollectors.toOptional; 21 import static dagger.internal.codegen.extension.DaggerStreams.toImmutableSet; 22 import static dagger.internal.codegen.xprocessing.XElements.getSimpleName; 23 24 import androidx.room.compiler.processing.JavaPoetExtKt; 25 import androidx.room.compiler.processing.XFiler; 26 import androidx.room.compiler.processing.XMethodElement; 27 import androidx.room.compiler.processing.XProcessingEnv; 28 import androidx.room.compiler.processing.XType; 29 import androidx.room.compiler.processing.XTypeElement; 30 import androidx.room.compiler.processing.XTypeParameterElement; 31 import com.google.common.collect.ImmutableSet; 32 import com.squareup.javapoet.ClassName; 33 import com.squareup.javapoet.JavaFile; 34 import com.squareup.javapoet.MethodSpec; 35 import com.squareup.javapoet.ParameterSpec; 36 import com.squareup.javapoet.TypeSpec; 37 import dagger.hilt.android.processor.internal.AndroidClassNames; 38 import dagger.hilt.processor.internal.ClassNames; 39 import dagger.hilt.processor.internal.Processors; 40 import dagger.internal.codegen.xprocessing.XExecutableTypes; 41 import java.io.IOException; 42 import java.util.Optional; 43 import javax.lang.model.element.Modifier; 44 45 /** Generates an Hilt BroadcastReceiver class for the @AndroidEntryPoint annotated class. */ 46 public final class BroadcastReceiverGenerator { 47 private static final String ON_RECEIVE_DESCRIPTOR = 48 "onReceive(Landroid/content/Context;Landroid/content/Intent;)V"; 49 50 private final XProcessingEnv env; 51 private final AndroidEntryPointMetadata metadata; 52 private final ClassName generatedClassName; 53 BroadcastReceiverGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata)54 public BroadcastReceiverGenerator(XProcessingEnv env, AndroidEntryPointMetadata metadata) { 55 this.env = env; 56 this.metadata = metadata; 57 generatedClassName = metadata.generatedClassName(); 58 } 59 60 // @Generated("BroadcastReceiverGenerator") 61 // abstract class Hilt_$CLASS extends $BASE { 62 // ... 63 // } generate()64 public void generate() throws IOException { 65 TypeSpec.Builder builder = 66 TypeSpec.classBuilder(generatedClassName.simpleName()) 67 .superclass(metadata.baseClassName()) 68 .addModifiers(metadata.generatedClassModifiers()) 69 .addMethod(onReceiveMethod()); 70 71 // Add an annotation used as a marker to let the bytecode injector know this receiver 72 // will need to be injected with a super.onReceive call. This is only necessary if no concrete 73 // onReceive call is implemented in any of the super classes. 74 if (metadata.requiresBytecodeInjection() && !isOnReceiveImplemented(metadata.baseElement())) { 75 builder.addAnnotation(ClassNames.ON_RECEIVE_BYTECODE_INJECTION_MARKER); 76 } 77 78 JavaPoetExtKt.addOriginatingElement(builder, metadata.element()); 79 Generators.addGeneratedBaseClassJavadoc(builder, AndroidClassNames.ANDROID_ENTRY_POINT); 80 Processors.addGeneratedAnnotation(builder, env, getClass()); 81 Generators.copyConstructors(metadata.baseElement(), builder, metadata.element()); 82 83 metadata.baseElement().getTypeParameters().stream() 84 .map(XTypeParameterElement::getTypeVariableName) 85 .forEachOrdered(builder::addTypeVariable); 86 87 Generators.addInjectionMethods(metadata, builder); 88 Generators.copyLintAnnotations(metadata.element(), builder); 89 Generators.copySuppressAnnotations(metadata.element(), builder); 90 91 env.getFiler() 92 .write( 93 JavaFile.builder(generatedClassName.packageName(), builder.build()).build(), 94 XFiler.Mode.Isolating); 95 } 96 isOnReceiveImplemented(XTypeElement typeElement)97 private static boolean isOnReceiveImplemented(XTypeElement typeElement) { 98 boolean isImplemented = 99 typeElement.getDeclaredMethods().stream() 100 .filter(method -> !method.isAbstract()) 101 .anyMatch(method -> method.getJvmDescriptor().equals(ON_RECEIVE_DESCRIPTOR)); 102 if (isImplemented) { 103 return true; 104 } else if (typeElement.getSuperClass() != null) { 105 return isOnReceiveImplemented(typeElement.getSuperClass().getTypeElement()); 106 } else { 107 return false; 108 } 109 } 110 111 // @Override 112 // public void onReceive(Context context, Intent intent) { 113 // inject(context); 114 // super.onReceive(); 115 // } onReceiveMethod()116 private MethodSpec onReceiveMethod() throws IOException { 117 MethodSpec.Builder method = 118 MethodSpec.methodBuilder("onReceive") 119 .addAnnotation(Override.class) 120 .addAnnotation(AndroidClassNames.CALL_SUPER) 121 .addModifiers(Modifier.PUBLIC) 122 .addParameter(ParameterSpec.builder(AndroidClassNames.CONTEXT, "context").build()) 123 .addParameter(ParameterSpec.builder(AndroidClassNames.INTENT, "intent").build()) 124 .addStatement("inject(context)"); 125 126 if (metadata.overridesAndroidEntryPointClass()) { 127 // We directly call super.onReceive here because we know Hilt base classes have a 128 // non-abstract onReceive method. However, because the Hilt base class may not be generated 129 // already we cannot fall down to the below logic to find it. 130 method.addStatement("super.onReceive(context, intent)"); 131 } else { 132 // Get the onReceive method element from BroadcastReceiver. 133 XMethodElement onReceiveMethod = 134 getOnlyElement( 135 findMethodsByName( 136 env.requireTypeElement(AndroidClassNames.BROADCAST_RECEIVER), "onReceive")); 137 138 // If the base class or one of its super classes implements onReceive, call super.onReceive() 139 findMethodBySubsignature(metadata.baseElement(), onReceiveMethod) 140 .filter(onReceive -> !onReceive.isAbstract()) 141 .ifPresent(onReceive -> method.addStatement("super.onReceive(context, intent)")); 142 } 143 return method.build(); 144 } 145 findMethodBySubsignature( XTypeElement typeElement, XMethodElement method)146 private Optional<XMethodElement> findMethodBySubsignature( 147 XTypeElement typeElement, XMethodElement method) { 148 String methodName = getSimpleName(method); 149 XType currentType = typeElement.getType(); 150 Optional<XMethodElement> match = Optional.empty(); 151 while (!match.isPresent() && currentType != null) { 152 match = 153 findMethodsByName(currentType.getTypeElement(), methodName).stream() 154 .filter(m -> XExecutableTypes.isSubsignature(m, method)) 155 .collect(toOptional()); 156 currentType = currentType.getTypeElement().getSuperClass(); 157 } 158 return match; 159 } 160 findMethodsByName( XTypeElement typeElement, String name)161 private static ImmutableSet<XMethodElement> findMethodsByName( 162 XTypeElement typeElement, String name) { 163 return typeElement.getDeclaredMethods().stream() 164 .filter(m -> getSimpleName(m).contentEquals(name)) 165 .collect(toImmutableSet()); 166 } 167 } 168