That time I failed at the basics

A tale of woe, despair, persistence and the eventual glorious victory of the human spirit

Earlier this year, I released my first (only?) book called Android Development for Gifted Primates. It was always meant to be a light (but informative) read aimed at beginners, so I didn’t hold back on the scato-jokes.

While trying to revise some chapters, I came across a section that I had forgotten (read: banished to the deepest recesses of my subconscious) I had written:

At the time, I had an unhealthy obsession with phones making farting sounds for some weird reason.

Anyway — perhaps I had forgotten writing this section, but upon reading it again the thought stuck.

Never one to neglect my duties as a developer, I decided to create an app that makes a farting sound when the user changes their wallpaper, as the final paragraph on page 168 suggested.

Da faq

The architecture of the app was going to be simple.

I would have a MainActivity that the user would launch after installation.

Starting with Android 3.1, all newly installed applications are put in a stopped state. The user has to manually launch the app for any BroadcastReceivers declared by the app to be invoked. Hence the need for an Activity.

I would then create a BroadcastReceiver subclass and register it in the Manifest, with an Intent Filter of

<action android:name=”android.intent.action.WALLPAPER_CHANGED” />

The final step in my devilishly clever plan was to create a MediaPlayer instance in the .onReceive() method of the BroadcastReceiver subclass and play the sound from in there.

It should have taken me 5 minutes.

It didn’t.

The Long and Winding Road

Here is the BroadcastReceiver code

package co.cutecomputers.inyourgeneraldirection; import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.media.AudioManager;

import android.media.MediaPlayer; public class FartReceiver extends BroadcastReceiver { @Override

public void onReceive(Context context, Intent intent) { MediaPlayer mp = MediaPlayer.create(context, R.raw.farty); mp.start();

mp.release(); }

}

Pretty straightforward, no?

Turns out, the fart sound wouldn’t play.

What does anyone do when a fart sound won’t play? They hit StackOverflow, of course.

Over at SO, I found out that the Context instance that .onReceive() is passed is not your garden variety Context. Instead, it is something more exotic called a ReceiverRestrictedContext. Whatever the hell that is.

Perplexed, I decided to take a different approach. Maybe the problem was the Context subclass passed to .onReceive(), so I decided to create an IntentService and play the sound in its .onHandleIntent() method.

And since an IntentService is a subclass of Service, which is a subclass of Context, there shouldn’t be any problem.

package co.cutecomputers.inyourgeneraldirection; import android.app.IntentService;

import android.content.Context;

import android.content.Intent;

import android.media.AudioManager;

import android.media.MediaPlayer;

import android.widget.Toast;

public class FartService extends IntentService { public FartService() {

super(“something”);

} @Override

protected void onHandleIntent(Intent intent) {

MediaPlayer mp = MediaPlayer.create(this, R.raw.farty);

mp.start();

mp.release();



}

}

Meanwhile, back in the BroadcastReceiver, I removed everything that had to do with the playback and just started the FartService

Intent intent = new Intent(context, FartService.class); context.startService(intent);

I tried changing the wallpaper again.

Still no fart!

After scratching my head for a while, I decided to investigate if the volume of the sound had anything to do with the fart not being audible.

So I went ahead and added these lines before attempting to play the sound in the IntentService

AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 20, 0);

And BAM!

More than a little excited, I decided to take this experiment a little further. I decided to stop using the IntentService and go back to trying to play the sound in the BroadcastReceiver

package co.cutecomputers.inyourgeneraldirection; import android.content.BroadcastReceiver;

import android.content.Context;

import android.content.Intent;

import android.media.AudioManager;

import android.media.MediaPlayer; public class FartReceiver extends BroadcastReceiver { @Override

public void onReceive(Context context, Intent intent) { MediaPlayer mp = MediaPlayer.create(context, R.raw.farty); AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);

audioManager.setStreamVolume(AudioManager.STREAM_MUSIC, 20, 0); mp.start();

// mp.release(); }

}

After changing the wallpaper, the fart sounded! And there was much rejoicing! So clearly, the problem was not the ReceiverRestrictedContext passed to .onReceive().

Below, you can watch a video of the result:

Witness! The Android SDK bending to my will!

The mystery

Here’s the thing: I’ve used MediaPlayer many times before in the past and I’ve never needed to adjust the volume of the stream to make the sound file audible. Not once. It just worked. What the hell was going on?

The plot twist

Savvy developers must have caught it by now: I am calling mp.release() immediately after mp.start(). This immediately releases the MediaPlayer instance and does not allow the sound to be played.

Then how did it work after changing the stream volume?

Well, it did not. In my confusion, I commented out mp.release() in the IntentService and that’s why the sound started playing. The AudioManager trick had nothing to do with it.

After copying and pasting the code from the Service to the BroadcastReceiver, I copied the commented out mp.release() call over, so it also started playing in the BroadcastReceiver.

What a dumbass

In summary, here is what went on

Damn, no fart!

It must have something to do with the ReceiverRestrictedContext! To the IntentServiceMobile!

Damn, doesn’t work again! Let’s try raising the volume of the media stream!

Comment mp.release() out for some reason

Nice! It worked. My AudioManager trick worked!

But how? Why?

Oh, mp.release() is commented out. Let’s uncomment it.

Doesn’t work again.

A-HA!

https://youtu.be/djV11Xbc914

Obviously, the MediaPlayer instance has to be released at some point. To have the sound play and release the MediaPlayer, you have to add an OnCompletionListener to the MediaPlayer instance and release the MediaPlayer in its .onComplete() method.

MediaPlayer mp = MediaPlayer.create(context, R.raw.farty); mp.start(); mp.setOnCompletionListener(new MediaPlayer.OnCompletionListener() {



@Override

public void onCompletion(MediaPlayer mp) {

mp.release();

} });

This way, as soon as the sound stops playing, the MediaPlayer object will be released.

— — — — — — — — — — — — — — — — — — — — — — — — — — — — — —

If you liked this article, click on the Heart icon to recommend it and immediately bring ten kittens back to life. Not Pet Sematary-style kittens. Totally normal kittens. Fluffy ones.

Also, there’s not a whole lot of code there but you can get the source for Fartitude by going HERE.