Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Creating notification from InstrumentationTestCase

I want to test from a unit test whether a notification is able to play a custom sound from assets. The test is not meant to verify anything, I wrote it as a quick way of demonstrating a feature without cluttering the main app code.

So In the test project, I added a wav file inside /res/raw. I'll be using this URL with the notification builder:

Uri path = Uri.parse("android.resource://<main app package name>/testsound.wav");

That URL should work according to the questions I've been reading in SO. Let's assume it works.

Now because I didn't want to include the test wav file in the main project's /res/raw folder but in the test project one, I'm forced to make my unit test extend from InstrumentationTestCase so that I can access the resources in the test project.

Here's the code:

    NotificationCompat.Builder builder = new NotificationCompat.Builder(getInstrumentation().getContext());
    ...
    builder.setSound(path, AudioManager.STREAM_NOTIFICATION);
    ...
    NotificationManager notificationManager = (NotificationManager) getInstrumentation().getContext().getSystemService(Context.NOTIFICATION_SERVICE);
    notificationManager.notify(NOTIFICATION_ID, builder.build());

The notify call is throwing the following exception:

    java.lang.SecurityException: Calling uid 10198 gave package <main app package name> which is owned by uid 10199
    at android.os.Parcel.readException(Parcel.java:1540)
    at android.os.Parcel.readException(Parcel.java:1493)
    at android.app.INotificationManager$Stub$Proxy.enqueueNotificationWithTag(INotificationManager.java:611)
    at android.app.NotificationManager.notify(NotificationManager.java:187)
    at android.app.NotificationManager.notify(NotificationManager.java:140)
    ... 
    at android.app.Instrumentation$InstrumentationThread.run(Instrumentation.java:1873)

I've tracked this exception down to the NotificationManagerService class:

    void checkCallerIsSystemOrSameApp(String pkg) {
        int uid = Binder.getCallingUid();
        if (UserHandle.getAppId(uid) == Process.SYSTEM_UID || uid == 0) {
            return;
        }
        try {
            ApplicationInfo ai = AppGlobals.getPackageManager().getApplicationInfo(
                    pkg, 0, UserHandle.getCallingUserId());
            if (!UserHandle.isSameApp(ai.uid, uid)) {
                throw new SecurityException("Calling uid " + uid + " gave package"
                        + pkg + " which is owned by uid " + ai.uid);
            }
        } catch (RemoteException re) {
            throw new SecurityException("Unknown package " + pkg + "\n" + re);
        }
    }

Apparently the exception doesn't have anything to do with the custom sound, but with the fact that we're creating a notification from an InstrumentationTestCase.

Is there a way of testing this? I remember having created notifications from AndroidTestCase in the past, but if I do that then I wont be able to access the test wav file. I could create a jar with the wav and drop the jar in the test project's lib folder, but that would be hiding the file, and other programmers might have a hard time looking for it if they need to replace it in the future.

like image 677
Mister Smith Avatar asked Dec 11 '15 10:12

Mister Smith


1 Answers

Actually, I've been a bit puzzled by the question. So I wrote small Instrumentation test.

For the sake of asserting, I marked the test to run only on API 23 (getActiveNotifications seems to be unavailable before), but it works fine on older APIs as well.

The trick is to use getTargetContext() instead of getContext():

public final class MainActivityTest extends ActivityUnitTestCase<MainActivity> {

    public MainActivityTest() {
        super(MainActivity.class);
    }

    @TargetApi(Build.VERSION_CODES.M)
    public void testSendNotification() {
        final NotificationManager manager = (NotificationManager) getInstrumentation().getTargetContext().getSystemService(Context.NOTIFICATION_SERVICE);

        manager.cancel(42);
        assertEquals(0, manager.getActiveNotifications().length);

        final NotificationCompat.Builder builder = new NotificationCompat.Builder(getInstrumentation().getTargetContext());
        builder.setContentTitle("Notification test")
                .setAutoCancel(true)
                .setContentText("Hello, Mister Smith")
                .setSmallIcon(R.drawable.ic_launcher_notification);

        manager.notify(42, builder.build());

        assertEquals(1, manager.getActiveNotifications().length);
    }
}

It works like a charm:

enter image description here

Hope, it helps.

like image 70
Konstantin Loginov Avatar answered Sep 27 '22 15:09

Konstantin Loginov