10

I have an audio file playing in a foreground Service using MediaPlayer. When a user taps the notification associated with the foreground Service, I launch an Activity using the Intent like so:

Intent audioPlayIntent = new Intent(context, AudioPlayActivity.class);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
audioPlayIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
PendingIntent contentIntent = PendingIntent.getActivity(context, 0, audioPlayIntent, 0);

This Activity then binds to the service to show a MediaController to the user.

Here is the binding code in the Service:

public class AudioPlayerServiceBinder extends Binder{

    public AudioPlayerService getAudioService(){
        return AudioPlayerService.this; //this class is declared in AudioPlayerService.java, so it has access to the Service instance.
    }

}

..and in the Activity's onStart I have a call to this method:

private void bindAudioService()
    {
        Intent i = new Intent(this, AudioPlayerService.class);
        serviceConnection = new AudioServiceConnection();
        bindService(i, serviceConnection, 0);
    }

I'm getting an exception on the mediaController.show(5000) line below:

private class AudioServiceConnection implements ServiceConnection{

    AudioPlayerServiceBinder audioServiceBinder;
@Override
        public void onServiceConnected(ComponentName name, IBinder serviceBinder)
        {
            serviceConnected = true;
            Log.i(TAG, "Connected to audio player service.");
            audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
            AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();
            mediaController.show(5000);
        }

The exception being:

android.view.WindowManager$BadTokenException: Unable to add window -- token null is not valid; is your activity running?
at android.view.ViewRoot.setView(ViewRoot.java:527)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:177)
at android.view.WindowManagerImpl.addView(WindowManagerImpl.java:91)
at android.view.Window$LocalWindowManager.addView(Window.java:424)
at android.widget.MediaController.show(MediaController.java:304)
at android.widget.MediaController.show(MediaController.java:249)
at com.myapp.AudioPlayActivity$AudioServiceConnection.onServiceConnected(AudioPlayActivity.java:295)

I can recreate the same exception by:

  1. Clicking the notification to open the Activity
  2. Pressing back to close the activity.
  3. Clicking the notification to open a new version of the activity.

This led me to believe that the mediaController is somehow leaking and trying to show itself in the original Activity instance. I couldn't find any reason for that to happen though, as the mediaController is instantiated within the Activity's onCreate() and only tied to the activity itself. (The activity then handles passing commands through to the service).

you786
  • 3,659
  • 5
  • 48
  • 74
  • have you tried mymediaplayer.setDisplay(mysurface) – sschrass Aug 27 '13 at 07:22
  • @SatelliteSD No, but this is just an audio file. Also, there is no surface because the MediaPlayer instance is running in a Service. – you786 Aug 27 '13 at 07:25
  • oops seems like I have misread your question :( – sschrass Aug 27 '13 at 07:27
  • " It's especially hard because I can't recreate it!" just because of your device prepare media player first and then show media controller please try to replicate in lower configuration devices, might this will help you to nail this issue. – Varun Aug 27 '13 at 09:17
  • @njzk2 mediaController is defined as a member variable of AudioPlayActivity and is instantiated in the Activity's onCreate. – you786 Aug 30 '13 at 11:32
  • apparently you try to show the mediacontroller but your activity is not ready or not able to display it. – njzk2 Aug 30 '13 at 11:33
  • Your exception is in a `run` method, not in the `onServiceConnected` – njzk2 Aug 30 '13 at 11:34
  • @njzk2 Sorry, that stacktrace is old. I'll update it. I had tried to put mediaController.show() in a Handler to see if it would help, which it didn't. – you786 Aug 30 '13 at 11:36
  • According to the error log you have some null reference or token running in your code. Please point out the null reference first by surrounding every object reference in an if statement that will indicate the null by printing it to the log or some other way if possible. –  Sep 01 '13 at 06:35
  • @yekhezelyovel the null pointer is from something in the mediacontroller class, not my code. – you786 Sep 01 '13 at 11:29

5 Answers5

7

I think you are calling show() too early, before the previous activity completes the lifecycle. BadTokenException can be avoided by delaying call to show() until all the lifecycle methods are called. You may post a delayed runnable for this. Or you can try following,

if (!((Activity)your_context).isFinishing()) {
    mediaController.show(5000);
}
Ritesh Gune
  • 16,629
  • 6
  • 44
  • 72
  • 2
    Effectively this seems like it is the issue but putting an arbitrary delay on a runnable seems like a Bad Thing. This seems like it might be a bug in the OS then, as there are no documentation about this limitation anywhere. I don't think your isFinishing() method will work though because it is a different Activity instance. – you786 Sep 02 '13 at 07:42
  • 2
    Ritesh Gune is right, but to avoid the delay, you can place the show inside the onAttachedToWindow method that is called after all the lifecycle methods are called. – jonathanrz Sep 05 '14 at 14:20
6

Fixed the issue

I too was having the same issue and fixed it by doing the following,

@Override
public void onAttachedToWindow() {
    super.onAttachedToWindow();
    try{
        mediaController.show(0);
    }catch(Exception e){
        e.printStackTrace();
    }
}

Now it works like a charm.

gprathour
  • 14,813
  • 5
  • 66
  • 90
1

I believe the Problem is in this line.

AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();

You can look here for the details.Read all the comments in it, not just the answer.

Community
  • 1
  • 1
Dileep
  • 5,362
  • 3
  • 22
  • 38
0

Inside AudioPlayActivity's onCreate(Bundle):

Instead of using setContentView(int), inflate the layout (if you are already doing this, skip ahead):

Declare a global View variable:

View mContentView;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mContentView = getLayoutInflater().inflate(R.layout.your_activitys_layout, null);       

    // initialize widgets
    Button b = (Button) mContentView.findViewById(...);

    ....
    ....

    // Finally
    setContentView(mContentView);
}

Change AudioServiceConnection to the following:

private class AudioServiceConnection implements ServiceConnection{

    AudioPlayerServiceBinder audioServiceBinder;
    @Override
    public void onServiceConnected(ComponentName name, IBinder serviceBinder)
    {
        serviceConnected = true;
        Log.i(TAG, "Connected to audio player service.");
        audioServiceBinder = ((AudioPlayerServiceBinder) serviceBinder);
        AudioPlayActivity.this.audioService = audioServiceBinder.getAudioService();

        mContentView.post(new Runnable() {

            @Override
            public void run() {
                mediaController.show(5000);
            }
        });
    }

This should get rid of the WindowManager$BadTokenException.

Apologies if I totally misunderstood the question.

Vikram
  • 51,313
  • 11
  • 93
  • 122
0

From the steps you have mentioned, It seems onConnected() is being invoked on a leaked instance of previous activity created in step 1. If the service is on demand (bound service) then you should bind/unbind in onResume()/onPause() respectively.

To confirm instance leaks, place :

log.i("LEAKTEST", "Connected to instance " + this.toString());

inside onConnected().

Now, re-create the scenario, and note the object id's in logcat, it'd be like "@1246464". Check that its called only once , on a new object id, every time the activity is started.

S.D.
  • 29,290
  • 3
  • 79
  • 130