package permissions.dispatcher;
import com.android.tools.lint.detector.api.Category;
import com.android.tools.lint.detector.api.Detector;
import com.android.tools.lint.detector.api.Implementation;
import com.android.tools.lint.detector.api.Issue;
import com.android.tools.lint.detector.api.JavaContext;
import com.android.tools.lint.detector.api.Scope;
import com.android.tools.lint.detector.api.Severity;
import com.intellij.psi.JavaElementVisitor;
import com.intellij.psi.JavaRecursiveElementVisitor;
import com.intellij.psi.PsiAnnotation;
import com.intellij.psi.PsiCallExpression;
import com.intellij.psi.PsiClass;
import com.intellij.psi.PsiCodeBlock;
import com.intellij.psi.PsiElement;
import com.intellij.psi.PsiExpression;
import com.intellij.psi.PsiExpressionStatement;
import com.intellij.psi.PsiMethod;
import com.intellij.psi.PsiStatement;
import java.util.Arrays;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
public class CallOnRequestPermissionsResultDetector extends Detector
implements Detector.JavaPsiScanner {
public static final Issue ISSUE = Issue.create("NeedOnRequestPermissionsResult",
"Call the \"onRequestPermissionsResult\" method of the generated PermissionsDispatcher class in the respective method of your Activity or Fragment",
"You are required to inform the generated PermissionsDispatcher class about the results of a permission request. In your class annotated with @RuntimePermissions, override the \"onRequestPermissionsResult\" method and call through to the generated PermissionsDispatcher method with the same name.",
Category.CORRECTNESS,
5,
Severity.ERROR,
new Implementation(CallOnRequestPermissionsResultDetector.class,
EnumSet.of(Scope.JAVA_FILE)));
static final Set<String> RUNTIME_PERMISSIONS_NAME = new HashSet<String>() {{
add("RuntimePermissions");
add("permissions.dispatcher.RuntimePermissions");
}};
public CallOnRequestPermissionsResultDetector() {
// No-op
}
@Override
public List<Class<? extends PsiElement>> getApplicablePsiTypes() {
return Arrays.asList(PsiAnnotation.class, PsiClass.class);
}
@Override
public JavaElementVisitor createPsiVisitor(final JavaContext context) {
return new JavaElementVisitor() {
@Override
public void visitClass(PsiClass node) {
node.accept(new OnRequestPermissionsResultChecker(context, node));
}
};
}
private static class OnRequestPermissionsResultChecker extends JavaRecursiveElementVisitor {
private final JavaContext context;
private boolean hasRuntimePermissionAnnotation;
private PsiClass psiClass;
private OnRequestPermissionsResultChecker(JavaContext context, PsiClass psiClass) {
this.context = context;
this.psiClass = psiClass;
}
@Override
public void visitAnnotation(PsiAnnotation annotation) {
String type = annotation.getQualifiedName();
if (!RUNTIME_PERMISSIONS_NAME.contains(type)) {
super.visitAnnotation(annotation);
return;
}
hasRuntimePermissionAnnotation = true;
super.visitAnnotation(annotation);
}
@Override
public void visitMethod(PsiMethod method) {
if (!hasRuntimePermissionAnnotation) {
super.visitMethod(method);
return;
}
if (!"onRequestPermissionsResult".equals(method.getName())) {
super.visitMethod(method);
return;
}
if (hasRuntimePermissionAnnotation && !checkMethodCall(method, psiClass)) {
PsiCodeBlock codeBlock = method.getBody();
context.report(ISSUE, context.getLocation(method),
codeBlock.getText()
+ "Generated onRequestPermissionsResult method not called");
}
super.visitMethod(method);
}
}
private static boolean checkMethodCall(PsiMethod method, PsiClass psiClass) {
PsiCodeBlock codeBlock = method.getBody();
if (codeBlock == null) {
return false;
}
PsiStatement[] statements = codeBlock.getStatements();
for (PsiStatement statement : statements) {
if (!(statement instanceof PsiExpressionStatement)) {
continue;
}
PsiExpression expression = ((PsiExpressionStatement) statement)
.getExpression();
if (!(expression instanceof PsiCallExpression)) {
continue;
}
PsiCallExpression callExpression = (PsiCallExpression) expression;
String targetClassName = psiClass.getName() + "PermissionsDispatcher";
PsiMethod resolveMethod = callExpression.resolveMethod();
if (resolveMethod == null) {
continue;
}
PsiClass containingClass = resolveMethod.getContainingClass();
if (containingClass == null) {
continue;
}
if (targetClassName.equals(containingClass.getName())
&& "onRequestPermissionsResult".equals(resolveMethod
.getName())) {
return true;
}
}
return false;
}
}