[CTS] Validate granting URI permissions to NotificationListenerServices
Bug: 162233630
Test: android.app.cts.NotificationManagerTest
Change-Id: Ie8d1f0a00291839c0385d13fec6d52a8addc458f
(cherry picked from commit a8014f80e8fc3b62aaf51b7a84e78741c572ebc1)
diff --git a/tests/app/AndroidTest.xml b/tests/app/AndroidTest.xml
index 645d233..46cf4cc 100644
--- a/tests/app/AndroidTest.xml
+++ b/tests/app/AndroidTest.xml
@@ -33,6 +33,7 @@
<option name="test-file-name" value="CtsCantSaveState1.apk" />
<option name="test-file-name" value="CtsCantSaveState2.apk" />
<option name="test-file-name" value="NotificationDelegator.apk" />
+ <option name="test-file-name" value="NotificationProvider.apk" />
<option name="test-file-name" value="StorageDelegator.apk" />
<option name="test-file-name" value="CtsActivityManagerApi29.apk" />
</target_preparer>
diff --git a/tests/app/NotificationProvider/Android.bp b/tests/app/NotificationProvider/Android.bp
new file mode 100644
index 0000000..26e69d7
--- /dev/null
+++ b/tests/app/NotificationProvider/Android.bp
@@ -0,0 +1,27 @@
+// Copyright (C) 2020 The Android Open Source Project
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+android_test_helper_app {
+ name: "NotificationProvider",
+ defaults: ["cts_support_defaults"],
+ srcs: ["**/*.java", "**/*.kt"],
+ // Tag this module as a cts test artifact
+ test_suites: [
+ "cts",
+ "vts10",
+ "general-tests",
+ ],
+ platform_apis: true,
+ sdk_version: "current",
+}
diff --git a/tests/app/NotificationProvider/AndroidManifest.xml b/tests/app/NotificationProvider/AndroidManifest.xml
new file mode 100644
index 0000000..09ae4b0
--- /dev/null
+++ b/tests/app/NotificationProvider/AndroidManifest.xml
@@ -0,0 +1,36 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+ package="com.android.test.notificationprovider">
+ <application android:label="Notification Provider">
+ <activity android:name=".RichNotificationActivity" android:exported="true">
+ <intent-filter>
+ <action android:name="android.intent.action.MAIN"/>
+ <category android:name="android.intent.category.DEFAULT"/>
+ <category android:name="android.intent.category.LAUNCHER"/>
+ </intent-filter>
+ </activity>
+
+ <provider
+ android:name=".AssetFileProvider"
+ android:authorities="com.android.test.notificationprovider.provider"
+ android:exported="false"
+ android:grantUriPermissions="true"
+ />
+
+ </application>
+</manifest>
diff --git a/tests/app/NotificationProvider/assets/background7.png b/tests/app/NotificationProvider/assets/background7.png
new file mode 100644
index 0000000..20c22f7
--- /dev/null
+++ b/tests/app/NotificationProvider/assets/background7.png
Binary files differ
diff --git a/tests/app/NotificationProvider/assets/background8.png b/tests/app/NotificationProvider/assets/background8.png
new file mode 100644
index 0000000..a7a593d
--- /dev/null
+++ b/tests/app/NotificationProvider/assets/background8.png
Binary files differ
diff --git a/tests/app/NotificationProvider/res/layout/activity.xml b/tests/app/NotificationProvider/res/layout/activity.xml
new file mode 100644
index 0000000..f001f29
--- /dev/null
+++ b/tests/app/NotificationProvider/res/layout/activity.xml
@@ -0,0 +1,31 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!-- Copyright (C) 2020 The Android Open Source Project
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
+-->
+
+<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ android:layout_width="match_parent"
+ android:layout_height="match_parent"
+ android:orientation="vertical"
+>
+
+ <TextView
+ android:layout_width="match_parent"
+ android:layout_height="wrap_content"
+ android:layout_marginTop="25dp"
+ android:textAppearance="?android:attr/textAppearanceLarge"
+ android:text="@string/activity_description"
+ />
+
+</LinearLayout>
diff --git a/tests/app/NotificationProvider/res/values/strings.xml b/tests/app/NotificationProvider/res/values/strings.xml
new file mode 100644
index 0000000..266ea53
--- /dev/null
+++ b/tests/app/NotificationProvider/res/values/strings.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+ ~ Copyright (C) 2020 The Android Open Source Project
+ ~
+ ~ Licensed under the Apache License, Version 2.0 (the "License");
+ ~ you may not use this file except in compliance with the License.
+ ~ You may obtain a copy of the License at
+ ~
+ ~ http://www.apache.org/licenses/LICENSE-2.0
+ ~
+ ~ Unless required by applicable law or agreed to in writing, software
+ ~ distributed under the License is distributed on an "AS IS" BASIS,
+ ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ ~ See the License for the specific language governing permissions and
+ ~ limitations under the License.
+ -->
+<resources>
+ <string name="activity_description">This app has a provider and posts notifications</string>
+</resources>
\ No newline at end of file
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt
new file mode 100644
index 0000000..af10f1b
--- /dev/null
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/AssetFileProvider.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.notificationprovider
+
+import android.content.ContentProvider
+import android.content.ContentValues
+import android.content.res.AssetFileDescriptor
+import android.database.Cursor
+import android.net.Uri
+
+class AssetFileProvider : ContentProvider() {
+ override fun onCreate(): Boolean {
+ return true
+ }
+
+ override fun openAssetFile(uri: Uri, mode: String): AssetFileDescriptor? {
+ val assets = context?.assets
+ val filename = uri.lastPathSegment
+ if (mode == "r" && assets != null && filename != null) {
+ return assets.openFd(filename)
+ }
+ return super.openAssetFile(uri, mode)
+ }
+
+ override fun query(
+ uri: Uri,
+ projection: Array<String>?,
+ selection: String?,
+ selectionArgs: Array<String>?,
+ sortOrder: String?
+ ): Cursor? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun getType(uri: Uri): String? {
+ return null
+ }
+
+ override fun insert(uri: Uri, values: ContentValues?): Uri? {
+ throw UnsupportedOperationException()
+ }
+
+ override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?): Int {
+ throw UnsupportedOperationException()
+ }
+
+ override fun update(
+ uri: Uri,
+ values: ContentValues?,
+ selection: String?,
+ selectionArgs: Array<String>?
+ ): Int {
+ throw UnsupportedOperationException()
+ }
+}
\ No newline at end of file
diff --git a/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
new file mode 100644
index 0000000..4197760
--- /dev/null
+++ b/tests/app/NotificationProvider/src/com/android/test/notificationprovider/RichNotificationActivity.kt
@@ -0,0 +1,82 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.android.test.notificationprovider
+
+import android.app.Activity
+import android.app.Notification
+import android.app.NotificationChannel
+import android.app.NotificationManager
+import android.content.Context
+import android.os.Bundle
+
+/**
+ * Used by NotificationManagerTest for testing policy around content uris.
+ */
+class RichNotificationActivity : Activity() {
+ companion object {
+ const val NOTIFICATION_CHANNEL_ID = "NotificationManagerTest"
+ const val EXTRA_ACTION = "action"
+ const val ACTION_SEND_7 = "send-7"
+ const val ACTION_SEND_8 = "send-8"
+ const val ACTION_CANCEL_7 = "cancel-7"
+ const val ACTION_CANCEL_8 = "cancel-8"
+ }
+
+ enum class NotificationPreset(val id: Int) {
+ Preset7(7),
+ Preset8(8);
+
+ fun build(context: Context): Notification {
+ val extras = Bundle()
+ extras.putString(Notification.EXTRA_BACKGROUND_IMAGE_URI,
+ "content://com.android.test.notificationprovider.provider/background$id.png")
+ return Notification.Builder(context, NOTIFICATION_CHANNEL_ID)
+ .setContentTitle("Rich Notification #$id")
+ .setSmallIcon(android.R.drawable.sym_def_app_icon)
+ .addExtras(extras)
+ .build()
+ }
+ }
+
+ public override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ setContentView(R.layout.activity)
+ when (intent?.extras?.getString(EXTRA_ACTION)) {
+ ACTION_SEND_7 -> sendNotification(NotificationPreset.Preset7)
+ ACTION_SEND_8 -> sendNotification(NotificationPreset.Preset8)
+ ACTION_CANCEL_7 -> cancelNotification(NotificationPreset.Preset7)
+ ACTION_CANCEL_8 -> cancelNotification(NotificationPreset.Preset8)
+ else -> {
+ // reset both
+ cancelNotification(NotificationPreset.Preset7)
+ cancelNotification(NotificationPreset.Preset8)
+ }
+ }
+ finish()
+ }
+
+ private val notificationManager by lazy { getSystemService(NotificationManager::class.java)!! }
+
+ private fun sendNotification(preset: NotificationPreset) {
+ notificationManager.createNotificationChannel(NotificationChannel(NOTIFICATION_CHANNEL_ID,
+ "Notifications", NotificationManager.IMPORTANCE_DEFAULT))
+ notificationManager.notify(preset.id, preset.build(this))
+ }
+
+ private fun cancelNotification(preset: NotificationPreset) {
+ notificationManager.cancel(preset.id)
+ }
+}
\ No newline at end of file
diff --git a/tests/app/src/android/app/cts/NotificationManagerTest.java b/tests/app/src/android/app/cts/NotificationManagerTest.java
index 90d64a1..f5721dd 100644
--- a/tests/app/src/android/app/cts/NotificationManagerTest.java
+++ b/tests/app/src/android/app/cts/NotificationManagerTest.java
@@ -75,6 +75,7 @@
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.ContentProviderOperation;
+import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
@@ -82,6 +83,7 @@
import android.content.pm.PackageManager;
import android.content.pm.ShortcutInfo;
import android.content.pm.ShortcutManager;
+import android.content.res.AssetFileDescriptor;
import android.database.Cursor;
import android.graphics.Bitmap;
import android.graphics.drawable.Icon;
@@ -111,6 +113,7 @@
import android.util.Log;
import android.widget.RemoteViews;
+import androidx.annotation.NonNull;
import androidx.test.InstrumentationRegistry;
import com.android.compatibility.common.util.FeatureUtil;
@@ -138,6 +141,9 @@
/* This tests NotificationListenerService together with NotificationManager, as you need to have
* notifications to manipulate in order to test the listener service. */
public class NotificationManagerTest extends AndroidTestCase {
+ public static final String NOTIFICATIONPROVIDER = "com.android.test.notificationprovider";
+ public static final String RICH_NOTIFICATION_ACTIVITY =
+ "com.android.test.notificationprovider.RichNotificationActivity";
final String TAG = NotificationManagerTest.class.getSimpleName();
final boolean DEBUG = false;
static final String NOTIFICATION_CHANNEL_ID = "NotificationManagerTest";
@@ -222,8 +228,7 @@
suspendPackage(mContext.getPackageName(), InstrumentationRegistry.getInstrumentation(),
false);
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), false);
+ toggleListenerAccess(false);
toggleNotificationPolicyAccess(mContext.getPackageName(),
InstrumentationRegistry.getInstrumentation(), false);
@@ -237,17 +242,17 @@
setBubblesGlobal(mBubblesEnabledSettingToRestore);
}
- private boolean isNotificationCancelled(int id, boolean all) {
+ private void assertNotificationCancelled(int id, boolean all) {
for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
- StatusBarNotification sbn = findPostedNotification(id, all);
- if (sbn == null) return true;
+ StatusBarNotification sbn = findNotificationNoWait(id, all);
+ if (sbn == null) return;
try {
Thread.sleep(SHORT_WAIT_TIME);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
- return false;
+ assertNull(findNotificationNoWait(id, all));
}
private void insertSingleContact(String name, String phone, String email, boolean starred) {
@@ -320,25 +325,28 @@
private StatusBarNotification findPostedNotification(int id, boolean all) {
// notification is a bit asynchronous so it may take a few ms to appear in
// getActiveNotifications()
- // we will check for it for up to 300ms before giving up
- StatusBarNotification n = null;
- for (int tries = 3; tries-- > 0; ) {
- final StatusBarNotification[] sbns = getActiveNotifications(all);
- for (StatusBarNotification sbn : sbns) {
- Log.d(TAG, "Found " + sbn.getKey());
- if (sbn.getId() == id) {
- n = sbn;
- break;
- }
+ // we will check for it for up to 1000ms before giving up
+ for (long totalWait = 0; totalWait < MAX_WAIT_TIME; totalWait += SHORT_WAIT_TIME) {
+ StatusBarNotification n = findNotificationNoWait(id, all);
+ if (n != null) {
+ return n;
}
- if (n != null) break;
try {
- Thread.sleep(100);
+ Thread.sleep(SHORT_WAIT_TIME);
} catch (InterruptedException ex) {
// pass
}
}
- return n;
+ return findNotificationNoWait(id, all);
+ }
+
+ private StatusBarNotification findNotificationNoWait(int id, boolean all) {
+ for (StatusBarNotification sbn : getActiveNotifications(all)) {
+ if (sbn.getId() == id) {
+ return sbn;
+ }
+ }
+ return null;
}
private StatusBarNotification[] getActiveNotifications(boolean all) {
@@ -449,8 +457,7 @@
private void setUpNotifListener() {
try {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
mListener = TestNotificationListener.getInstance();
mListener.resetData();
assertNotNull(mListener);
@@ -599,8 +606,8 @@
runCommand(command, instrumentation);
NotificationManager nm = mContext.getSystemService(NotificationManager.class);
- Assert.assertEquals("Notification Policy Access Grant is " +
- nm.isNotificationPolicyAccessGranted() + " not " + on, on,
+ assertEquals("Notification Policy Access Grant is "
+ + nm.isNotificationPolicyAccessGranted() + " not " + on, on,
nm.isNotificationPolicyAccessGranted());
}
@@ -613,18 +620,17 @@
runCommand(command, instrumentation);
}
- private void toggleListenerAccess(String componentName, Instrumentation instrumentation,
- boolean on) throws IOException {
+ private void toggleListenerAccess(boolean on) throws IOException {
String command = " cmd notification " + (on ? "allow_listener " : "disallow_listener ")
- + componentName;
+ + TestNotificationListener.getId();
- runCommand(command, instrumentation);
+ runCommand(command, InstrumentationRegistry.getInstrumentation());
final NotificationManager nm = mContext.getSystemService(NotificationManager.class);
final ComponentName listenerComponent = TestNotificationListener.getComponentName();
- assertTrue(listenerComponent + " has not been granted access",
- nm.isNotificationListenerAccessGranted(listenerComponent) == on);
+ assertEquals(listenerComponent + " has incorrect listener access",
+ on, nm.isNotificationListenerAccessGranted(listenerComponent));
}
private void setBubblesGlobal(boolean enabled)
@@ -1380,8 +1386,7 @@
}
public void testSuspendPackage() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1422,8 +1427,7 @@
}
public void testSuspendedPackageSendsNotification() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1475,8 +1479,7 @@
assertEquals(1, Settings.Global.getInt(
mContext.getContentResolver(), Settings.Global.NOTIFICATION_BUBBLES));
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1520,8 +1523,7 @@
assertEquals(1, Settings.Secure.getInt(
mContext.getContentResolver(), Settings.Secure.NOTIFICATION_BADGING));
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1563,8 +1565,7 @@
}
public void testGetSuppressedVisualEffectsOff_ranking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1592,8 +1593,7 @@
final int originalFilter = mNotificationManager.getCurrentInterruptionFilter();
NotificationManager.Policy origPolicy = mNotificationManager.getNotificationPolicy();
try {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -1641,8 +1641,7 @@
}
public void testKeyChannelGroupOverrideImportanceExplanation_ranking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2436,7 +2435,7 @@
mNotificationManager.notifyAsPackage(DELEGATOR, "toBeCanceled", 10000, n);
assertNotNull(findPostedNotification(10000, false));
mNotificationManager.cancelAsPackage(DELEGATOR, "toBeCanceled", 10000);
- assertTrue(isNotificationCancelled(10000, false));
+ assertNotificationCancelled(10000, false);
final Intent revokeIntent = new Intent();
revokeIntent.setClassName(DELEGATOR, REVOKE_CLASS);
revokeIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
@@ -2446,8 +2445,7 @@
public void testNotificationDelegate_cannotCancelNotificationsPostedByDelegator()
throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2614,8 +2612,7 @@
// pass
}
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
// no exception this time
mNotificationManager.shouldHideSilentStatusBarIcons();
}
@@ -2677,9 +2674,127 @@
listener.onListenerDisconnected();
}
+ private void performNotificationProviderAction(@NonNull String action) {
+ // Create an intent to launch an activity which just posts or cancels notifications
+ Intent activityIntent = new Intent();
+ activityIntent.setClassName(NOTIFICATIONPROVIDER, RICH_NOTIFICATION_ACTIVITY);
+ activityIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
+ activityIntent.putExtra("action", action);
+ mContext.startActivity(activityIntent);
+ }
+
+ public void testNotificationUriPermissionsGranted() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+ Uri background8Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background8.png");
+
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertNotificationCancelled(8, true);
+ assertAccessible(background7Uri);
+ assertInaccessible(background8Uri);
+
+ // Post #8
+ performNotificationProviderAction("send-8");
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
+ assertAccessible(background7Uri);
+ assertAccessible(background8Uri);
+
+ // Cancel #7
+ performNotificationProviderAction("cancel-7");
+
+ assertNotificationCancelled(7, true);
+ assertEquals(background8Uri, getNotificationBackgroundImageUri(8));
+ assertInaccessible(background7Uri);
+ assertAccessible(background8Uri);
+
+ // Cancel #8
+ performNotificationProviderAction("cancel-8");
+
+ assertNotificationCancelled(7, true);
+ assertNotificationCancelled(8, true);
+ assertInaccessible(background7Uri);
+ assertInaccessible(background8Uri);
+
+ } finally {
+ // Clean up -- reset any remaining notifications
+ performNotificationProviderAction("reset");
+ Thread.sleep(500);
+ }
+ }
+
+ public void testNotificationUriPermissionsGrantedToNewListeners() throws Exception {
+ Uri background7Uri = Uri.parse(
+ "content://com.android.test.notificationprovider.provider/background7.png");
+
+ try {
+ // Post #7
+ performNotificationProviderAction("send-7");
+
+ Thread.sleep(500);
+ // Don't have access the notification yet, but we can test the URI
+ assertInaccessible(background7Uri);
+
+ toggleListenerAccess(true);
+ Thread.sleep(500); // wait for listener to be allowed
+
+ mListener = TestNotificationListener.getInstance();
+ assertNotNull(mListener);
+
+ assertEquals(background7Uri, getNotificationBackgroundImageUri(7));
+ assertAccessible(background7Uri);
+
+ } finally {
+ // Clean Up -- Cancel #7
+ performNotificationProviderAction("cancel-7");
+ Thread.sleep(500);
+ }
+ }
+
+ private void assertAccessible(Uri uri)
+ throws IOException {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
+ assertNotNull(fd);
+ } catch (SecurityException e) {
+ throw new AssertionError("URI should be accessible: " + uri, e);
+ }
+ }
+
+ private void assertInaccessible(Uri uri)
+ throws IOException {
+ ContentResolver contentResolver = mContext.getContentResolver();
+ try (AssetFileDescriptor fd = contentResolver.openAssetFile(uri, "r", null)) {
+ fail("URI should be inaccessible: " + uri);
+ } catch (SecurityException e) {
+ // pass
+ }
+ }
+
+ @NonNull
+ private Uri getNotificationBackgroundImageUri(int notificationId) {
+ StatusBarNotification sbn = findPostedNotification(notificationId, true);
+ assertNotNull(sbn);
+ String imageUriString = sbn.getNotification().extras
+ .getString(Notification.EXTRA_BACKGROUND_IMAGE_URI);
+ assertNotNull(imageUriString);
+ return Uri.parse(imageUriString);
+ }
+
public void testNotificationListener_setNotificationsShown() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2695,8 +2810,7 @@
StatusBarNotification sbn2 = findPostedNotification(notificationId2, false);
mListener.setNotificationsShown(new String[]{ sbn1.getKey() });
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), false);
+ toggleListenerAccess(false);
Thread.sleep(500); // wait for listener to be disallowed
try {
mListener.setNotificationsShown(new String[]{ sbn2.getKey() });
@@ -2707,8 +2821,7 @@
}
public void testNotificationListener_getNotificationChannels() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2723,8 +2836,7 @@
}
public void testNotificationListener_getNotificationChannelGroups() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2738,8 +2850,7 @@
}
public void testNotificationListener_updateNotificationChannel() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2758,8 +2869,7 @@
}
public void testNotificationListener_getActiveNotifications() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2786,8 +2896,7 @@
public void testNotificationListener_getCurrentRanking() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();
@@ -2800,8 +2909,7 @@
}
public void testNotificationListener_cancelNotifications() throws Exception {
- toggleListenerAccess(TestNotificationListener.getId(),
- InstrumentationRegistry.getInstrumentation(), true);
+ toggleListenerAccess(true);
Thread.sleep(500); // wait for listener to be allowed
mListener = TestNotificationListener.getInstance();