Merge "Annotation-driven content resolver and intent permission checks" into studio-1.4-dev
automerge: b9b58db
* commit 'b9b58db7842df0c1cb3534ab581404dd56bc45d6':
Annotation-driven content resolver and intent permission checks
diff --git a/android/annotations/android/app/annotations.xml b/android/annotations/android/app/annotations.xml
index 5f455f2..3c5879b 100644
--- a/android/annotations/android/app/annotations.xml
+++ b/android/annotations/android/app/annotations.xml
@@ -203,18 +203,22 @@
<annotation name="android.support.annotation.Nullable" />
</item>
<item name="android.app.Activity boolean startActivityIfNeeded(android.content.Intent, int) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.app.Activity boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.app.Activity boolean startActivityIfNeeded(android.content.Intent, int, android.os.Bundle) 2">
<annotation name="android.support.annotation.Nullable" />
</item>
<item name="android.app.Activity boolean startNextMatchingActivity(android.content.Intent) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.app.Activity boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.app.Activity boolean startNextMatchingActivity(android.content.Intent, android.os.Bundle) 1">
@@ -368,24 +372,42 @@
<item name="android.app.Activity void startActivity(android.content.Intent, android.os.Bundle) 1">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.app.Activity void startActivityForResult(android.content.Intent, int) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.app.Activity void startActivityForResult(android.content.Intent, int, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.app.Activity void startActivityForResult(android.content.Intent, int, android.os.Bundle) 2">
<annotation name="android.support.annotation.Nullable" />
</item>
<item name="android.app.Activity void startActivityFromChild(android.app.Activity, android.content.Intent, int) 0">
<annotation name="android.support.annotation.NonNull" />
</item>
+ <item name="android.app.Activity void startActivityFromChild(android.app.Activity, android.content.Intent, int) 1">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.app.Activity void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle) 0">
<annotation name="android.support.annotation.NonNull" />
</item>
+ <item name="android.app.Activity void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle) 1">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.app.Activity void startActivityFromChild(android.app.Activity, android.content.Intent, int, android.os.Bundle) 3">
<annotation name="android.support.annotation.Nullable" />
</item>
<item name="android.app.Activity void startActivityFromFragment(android.app.Fragment, android.content.Intent, int) 0">
<annotation name="android.support.annotation.NonNull" />
</item>
+ <item name="android.app.Activity void startActivityFromFragment(android.app.Fragment, android.content.Intent, int) 1">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.app.Activity void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle) 0">
<annotation name="android.support.annotation.NonNull" />
</item>
+ <item name="android.app.Activity void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle) 1">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.app.Activity void startActivityFromFragment(android.app.Fragment, android.content.Intent, int, android.os.Bundle) 3">
<annotation name="android.support.annotation.Nullable" />
</item>
diff --git a/android/annotations/android/content/annotations.xml b/android/annotations/android/content/annotations.xml
index 1b1ed09..fe0fc49 100644
--- a/android/annotations/android/content/annotations.xml
+++ b/android/annotations/android/content/annotations.xml
@@ -211,12 +211,15 @@
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Read" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver android.database.Cursor query(android.net.Uri, java.lang.String[], java.lang.String, java.lang.String[], java.lang.String, android.os.CancellationSignal) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Read" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver android.net.Uri insert(android.net.Uri, android.content.ContentValues) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Write" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver android.os.Bundle call(android.net.Uri, java.lang.String, java.lang.String, android.os.Bundle) 2">
@@ -229,12 +232,15 @@
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver int bulkInsert(android.net.Uri, android.content.ContentValues[]) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Write" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver int delete(android.net.Uri, java.lang.String, java.lang.String[]) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Write" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver int update(android.net.Uri, android.content.ContentValues, java.lang.String, java.lang.String[]) 0">
+ <annotation name="android.support.annotation.RequiresPermission.Write" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.ContentResolver java.io.InputStream openInputStream(android.net.Uri) 0">
@@ -382,6 +388,9 @@
<item name="android.content.Context android.graphics.drawable.Drawable getDrawable(int)">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context boolean bindService(android.content.Intent, android.content.ServiceConnection, int) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context boolean bindService(android.content.Intent, android.content.ServiceConnection, int) 1">
<annotation name="android.support.annotation.NonNull" />
</item>
@@ -587,22 +596,44 @@
<val name="flag" val="true" />
</annotation>
</item>
+ <item name="android.content.Context void removeStickyBroadcast(android.content.Intent) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void removeStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void revokeUriPermission(android.net.Uri, int) 1">
<annotation name="android.support.annotation.IntDef">
<val name="value" val="{android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION, android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION}" />
<val name="flag" val="true" />
</annotation>
</item>
+ <item name="android.content.Context void sendBroadcast(android.content.Intent) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void sendBroadcast(android.content.Intent, java.lang.String) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendBroadcast(android.content.Intent, java.lang.String) 1">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String) 2">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context void sendOrderedBroadcast(android.content.Intent, java.lang.String) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendOrderedBroadcast(android.content.Intent, java.lang.String) 1">
<annotation name="android.support.annotation.Nullable" />
</item>
<item name="android.content.Context void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
<annotation name="android.support.annotation.NonNull" />
</item>
<item name="android.content.Context void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 1">
@@ -617,6 +648,9 @@
<item name="android.content.Context void sendOrderedBroadcast(android.content.Intent, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 6">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 2">
<annotation name="android.support.annotation.Nullable" />
</item>
@@ -629,6 +663,15 @@
<item name="android.content.Context void sendOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, java.lang.String, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 7">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context void sendStickyBroadcast(android.content.Intent) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void sendStickyBroadcastAsUser(android.content.Intent, android.os.UserHandle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 2">
<annotation name="android.support.annotation.Nullable" />
</item>
@@ -638,6 +681,9 @@
<item name="android.content.Context void sendStickyOrderedBroadcast(android.content.Intent, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 5">
<annotation name="android.support.annotation.Nullable" />
</item>
+ <item name="android.content.Context void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void sendStickyOrderedBroadcastAsUser(android.content.Intent, android.os.UserHandle, android.content.BroadcastReceiver, android.os.Handler, int, java.lang.String, android.os.Bundle) 3">
<annotation name="android.support.annotation.Nullable" />
</item>
@@ -650,6 +696,18 @@
<item name="android.content.Context void setTheme(int) 0">
<annotation name="android.support.annotation.StyleRes" />
</item>
+ <item name="android.content.Context void startActivities(android.content.Intent[]) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void startActivities(android.content.Intent[], android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void startActivity(android.content.Intent) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
+ <item name="android.content.Context void startActivity(android.content.Intent, android.os.Bundle) 0">
+ <annotation name="android.support.annotation.RequiresPermission" />
+ </item>
<item name="android.content.Context void startActivity(android.content.Intent, android.os.Bundle) 1">
<annotation name="android.support.annotation.Nullable" />
</item>
diff --git a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
index d5bd02f..7ac9e6e 100644
--- a/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
+++ b/android/src/org/jetbrains/android/inspections/ResourceTypeInspection.java
@@ -23,7 +23,6 @@
import com.android.tools.lint.checks.PermissionFinder;
import com.android.tools.lint.checks.PermissionHolder;
import com.android.tools.lint.checks.PermissionRequirement;
-import com.android.tools.lint.checks.SupportAnnotationDetector;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
@@ -76,7 +75,6 @@
import java.util.concurrent.atomic.AtomicInteger;
import static com.android.SdkConstants.*;
-import static com.android.tools.lint.checks.CleanupDetector.CONTENT_RESOLVER_CLS;
import static com.android.tools.lint.checks.PermissionFinder.Operation.*;
import static com.android.tools.lint.checks.SupportAnnotationDetector.*;
import static com.intellij.psi.CommonClassNames.DEFAULT_PACKAGE;
@@ -352,34 +350,6 @@
}
}
}
-
- // Check other permission calls (intents, content resolvers etc: the annotation is not on the method itself
- // (e.g. Activity#startActivity) but rather on the various intent actions and content resolver URIs being
- // passed to the method (usually indirectly)
- String name = method.getName();
- PermissionFinder.Operation operation = SupportAnnotationDetector.getPermissionOperation(name);
- if (operation != null) {
- int index = Math.max(0, SupportAnnotationDetector.getIntentMethodParameterIndex(name));
- PsiExpressionList argumentList = methodCall.getArgumentList();
- if (argumentList != null) {
- PsiExpression argument = argumentList.getExpressions()[index];
- if (operation == ACTION) {
- PsiClass c = PsiUtil.resolveClassInType(argument.getType());
- if (c == null || !CLASS_INTENT.equals(c.getQualifiedName())) {
- return;
- }
- } else if ((operation == READ || operation == WRITE)) {
- if (!InheritanceUtil.isInheritor(method.getContainingClass(), CONTENT_RESOLVER_CLS)) {
- return;
- }
- }
-
- PermissionFinder.Result result = search(argument, operation);
- if (result != null) {
- checkPermissionRequirement(methodCall, holder, method, result, result.requirement);
- }
- }
- }
}
@Nullable
@@ -414,14 +384,11 @@
PsiElement resolved = ((PsiJavaReference)node).resolve();
if (resolved instanceof PsiField) {
PsiField field = (PsiField)resolved;
- PsiModifierList modifierList = field.getModifierList();
- if (modifierList == null) {
- return null;
- }
if (operation == PermissionFinder.Operation.ACTION) {
- PsiAnnotation annotation = modifierList.findAnnotation(PERMISSION_ANNOTATION);
- if (annotation != null) {
- return getPermissionRequirement(field, annotation, operation);
+ for (PsiAnnotation annotation : getAllAnnotations(field)) {
+ if (PERMISSION_ANNOTATION.equals(annotation.getQualifiedName())) {
+ return getPermissionRequirement(field, annotation, operation);
+ }
}
}
else if (operation == PermissionFinder.Operation.READ || operation == PermissionFinder.Operation.WRITE) {
@@ -1473,6 +1440,15 @@
}
}
+ static class IndirectPermission extends Constraints {
+ public final String signature;
+ @Nullable public PermissionFinder.Result result;
+
+ public IndirectPermission(String signature) {
+ this.signature = signature;
+ }
+ }
+
@Nullable
private static Constraints getAllowedValuesFromTypedef(@NotNull PsiType type,
@NotNull PsiAnnotation magic,
@@ -1541,6 +1517,13 @@
else if (COLOR_INT_ANNOTATION.equals(qualifiedName)) {
return new ResourceTypeAllowedValues(Collections.<ResourceType>emptyList());
}
+ else if (qualifiedName.startsWith(PERMISSION_ANNOTATION)) {
+ // PERMISSION_ANNOTATION, PERMISSION_ANNOTATION_READ, PERMISSION_ANNOTATION_WRITE
+ // When specified on a parameter, that indicates that we're dealing with
+ // a permission requirement on this *method* which depends on the value
+ // supplied by this parameter
+ return new IndirectPermission(qualifiedName);
+ }
else if (qualifiedName.endsWith(RES_SUFFIX)) {
ResourceType resourceType = getResourceTypeFromAnnotation(qualifiedName);
if (resourceType != null) {
@@ -1595,7 +1578,18 @@
}
}
- private static void registerProblem(@NotNull PsiExpression argument, @NotNull Constraints allowedValues, @NotNull ProblemsHolder holder) {
+ private static void registerProblem(@NotNull PsiExpression argument, @NotNull Constraints allowedValues,
+ @NotNull ProblemsHolder holder) {
+ if (allowedValues instanceof IndirectPermission) {
+ PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(argument, PsiMethodCallExpression.class);
+ IndirectPermission ip = (IndirectPermission)allowedValues;
+ if (call != null && ip.result != null) {
+ checkPermissionRequirement(call, holder, null, ip.result, ip.result.requirement);
+ }
+
+ return;
+ }
+
if (allowedValues instanceof ResourceTypeAllowedValues) {
List<ResourceType> types = ((ResourceTypeAllowedValues)allowedValues).types;
String message;
@@ -1653,6 +1647,8 @@
return isResourceTypeAllowed(scope, argument, (ResourceTypeAllowedValues)allowedValues, manager, visited);
} else if (allowedValues instanceof RangeAllowedValues) {
return isInRange(scope, argument, (RangeAllowedValues)allowedValues, manager, visited);
+ } else if (allowedValues instanceof IndirectPermission) {
+ return isGrantedPermission(argument, (IndirectPermission)allowedValues);
}
assert allowedValues instanceof AllowedValues;
@@ -2005,6 +2001,34 @@
return UNCERTAIN;
}
+ private static boolean isGrantedPermission(@NotNull PsiExpression argument, @NotNull IndirectPermission permission) {
+ PsiMethodCallExpression call = PsiTreeUtil.getParentOfType(argument, PsiMethodCallExpression.class);
+ if (call != null) {
+ String signature = permission.signature;
+ PermissionFinder.Operation operation;
+ if (signature.equals(PERMISSION_ANNOTATION_READ)) {
+ operation = READ;
+ }
+ else if (signature.equals(PERMISSION_ANNOTATION_WRITE)) {
+ operation = WRITE;
+ }
+ else {
+ PsiType type = argument.getType();
+ if (type == null || !CLASS_INTENT.equals(type.getCanonicalText())) {
+ return true;
+ }
+ operation = ACTION;
+ }
+ permission.result = search(argument, operation);
+ if (permission.result != null) {
+ // Finish check in registerProblem
+ return false;
+ }
+ }
+ // No unsatisfied permission requirement found
+ return true;
+ }
+
// Would be nice to reuse the MagicConstantInspection's cache for this, but it's not accessible
private static final Key<Map<String, PsiExpression>> LITERAL_EXPRESSION_CACHE = Key.create("TYPE_DEF_LITERAL_EXPRESSION");
private static PsiExpression getLiteralExpression(@NotNull PsiExpression context, @NotNull PsiManager manager, @NotNull String text) {