Throw an error instead of dropping NOT_EXPORTED broadcasts on earlier
OSes

Adds code that checks that we have the permission we need to receive an
unexported broadcast from the same app. Also adds code that checks that
we haven't added the NOT_EXPORTED and RECEIVER_VISIBLE_TO_INSTANT_APPS
flag at the same time since they conflict.
RECEIVER_VISIBLE_TO_INSTANT_APPS will also add the EXPORTED flag since
it can be safely assumed.

Test: atest ContextCompatTest
Bug: 199978728
Change-Id: Ied1f634c6e1c79f7e97a5b5406cb0e7ac64bbaf7
diff --git a/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
index 1644add..b3b3c62 100644
--- a/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/ContextCompatTest.java
@@ -148,6 +148,7 @@
 import androidx.core.test.R;
 import androidx.test.filters.LargeTest;
 import androidx.test.filters.SdkSuppress;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Before;
 import org.junit.Test;
@@ -468,9 +469,6 @@
     public void testRegisterReceiver_noExportStateFlagThrowsException() {
         assertThrows(IllegalArgumentException.class, () -> ContextCompat.registerReceiver(mContext,
                 mTestReceiver, mTestFilter, 0));
-
-        assertThrows(IllegalArgumentException.class, () -> ContextCompat.registerReceiver(mContext,
-                mTestReceiver, mTestFilter, Context.RECEIVER_VISIBLE_TO_INSTANT_APPS));
     }
 
     @Test
@@ -515,6 +513,16 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = 29, maxSdkVersion = 32)
+    public void testRegisterReceiverPermissionNotGrantedApi26() {
+        InstrumentationRegistry
+                .getInstrumentation().getUiAutomation().adoptShellPermissionIdentity();
+        assertThrows(RuntimeException.class,
+                () -> ContextCompat.registerReceiver(mContext,
+                        mTestReceiver, mTestFilter, ContextCompat.RECEIVER_NOT_EXPORTED));
+    }
+
+    @Test
     @SdkSuppress(maxSdkVersion = 25)
     public void testRegisterReceiver() {
         Context spyContext = spy(mContext);
diff --git a/core/core/src/main/java/androidx/core/content/ContextCompat.java b/core/core/src/main/java/androidx/core/content/ContextCompat.java
index c6aa167..9882966 100644
--- a/core/core/src/main/java/androidx/core/content/ContextCompat.java
+++ b/core/core/src/main/java/androidx/core/content/ContextCompat.java
@@ -815,6 +815,16 @@
             @Nullable BroadcastReceiver receiver, @NonNull IntentFilter filter,
             @Nullable String broadcastPermission,
             @Nullable Handler scheduler, @RegisterReceiverFlags int flags) {
+        if (((flags & RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) && ((flags & RECEIVER_NOT_EXPORTED)
+                != 0)) {
+            throw new IllegalArgumentException("Cannot specify both "
+                    + "RECEIVER_VISIBLE_TO_INSTANT_APPS and RECEIVER_NOT_EXPORTED");
+        }
+
+        if ((flags & RECEIVER_VISIBLE_TO_INSTANT_APPS) != 0) {
+            flags |= RECEIVER_EXPORTED;
+        }
+
         if (((flags & RECEIVER_EXPORTED) == 0) && ((flags & RECEIVER_NOT_EXPORTED) == 0)) {
             throw new IllegalArgumentException("One of either RECEIVER_EXPORTED or "
                     + "RECEIVER_NOT_EXPORTED is required");
@@ -834,8 +844,7 @@
                     scheduler, flags);
         }
         if (((flags & RECEIVER_NOT_EXPORTED) != 0) && (broadcastPermission == null)) {
-            String permission =
-                    context.getPackageName() + DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION_SUFFIX;
+            String permission = obtainAndCheckReceiverPermission(context);
             return context.registerReceiver(receiver, filter, permission, scheduler /* handler */);
         }
         return context.registerReceiver(receiver, filter, broadcastPermission,
@@ -859,6 +868,25 @@
         return LegacyServiceMapHolder.SERVICES.get(serviceClass);
     }
 
+    /**
+     * Gets the name of the permission required to unexport receivers on pre Tiramisu versions of
+     * Android, and then asserts that the app registering the receiver also has that permission
+     * so it can receiver its own broadcasts.
+     *
+     * @param obj   Context to check the permission in.
+     * @return The name of the permission
+     */
+    static String obtainAndCheckReceiverPermission(Context obj) {
+        String permission =
+                obj.getPackageName() + DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION_SUFFIX;
+        if (PermissionChecker.checkSelfPermission(obj, permission)
+                != PermissionChecker.PERMISSION_GRANTED) {
+            throw new RuntimeException("Permission " + permission + " is required by your "
+                    + "application to receive broadcasts, please add it to your manifest");
+        }
+        return permission;
+    }
+
     /** Nested class provides lazy initialization only when needed. */
     private static final class LegacyServiceMapHolder {
         static final HashMap<Class<?>, String> SERVICES = new HashMap<>();
@@ -1045,8 +1073,7 @@
         static Intent registerReceiver(Context obj, @Nullable BroadcastReceiver receiver,
                 IntentFilter filter, String broadcastPermission, Handler scheduler, int flags) {
             if ((flags & RECEIVER_NOT_EXPORTED) != 0 && broadcastPermission == null) {
-                String permission =
-                        obj.getPackageName() + DYNAMIC_RECEIVER_NOT_EXPORTED_PERMISSION_SUFFIX;
+                String permission = obtainAndCheckReceiverPermission(obj);
                 // receivers that are not exported should also not be visible to instant apps
                 return obj.registerReceiver(receiver, filter, permission, scheduler);
             }