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) {