Is there a broadcast action for volume changes?
Asked Answered
G

9

58

I'm programming a small widget that needs to be updated whenever the user changes the ringer volume or the vibrate settings.

Capturing android.media.VIBRATE_SETTING_CHANGED works just fine for the vibrate settings, but I haven't found any way of getting notified when the ringer volume changes and although I could try to capture when the user presses the volume up/volume down physical keys, there are many other options for changing the volume without using these keys.

Do you know if there's any broadcast action defined for this or any way to create one or to solve the problem without it?

Gametophore answered 1/8, 2011 at 9:54 Comment(0)
O
87

There is no broadcast action, but I did find you can hook up a content observer to get notified when the settings change, volume of streams being some of those settings. Register for the android.provider.Settings.System.CONTENT_URI to be notified of all settings changes:

mSettingsContentObserver = new SettingsContentObserver( new Handler() ); 
this.getApplicationContext().getContentResolver().registerContentObserver( 
    android.provider.Settings.System.CONTENT_URI, true, 
    mSettingsContentObserver );

The content observer might look something like this:

public class SettingsContentObserver extends ContentObserver {

   public SettingsContentObserver(Handler handler) {
      super(handler);
   } 

   @Override
   public boolean deliverSelfNotifications() {
      return super.deliverSelfNotifications(); 
   }

   @Override
   public void onChange(boolean selfChange) {
      super.onChange(selfChange);
      Log.v(LOG_TAG, "Settings change detected");
      updateStuff();
   }
}

And be sure to unregister the content observer at some point.

Octodecimo answered 10/8, 2011 at 20:41 Comment(7)
Thanks for your answer, it's a bit extreme for what I'm trying to do but it does indeed seem like the closest option...Gametophore
@NathanTotura: Will volume keys be detected when the screen is off? OR does you solution work for just the case when the screen is ON?Multifid
The volume keys are also detected using this method when the screen is off. At least for AudioManager.STREAM_MUSIC, I didn't test for other stream types but I assume they behave the same.Boloney
As the Content_URI is the generic URI, I am getting the onChange callback multiple times. I only want to monitor the change in Ringtone Volume. Tried with different URIs for Ringtone but none of them works. Can anyone help ?Tret
where put, unregister observer?Wooldridge
@Boloney I was able to monitor only music volume using Settings.System.getUriFor("music_volume"). It works on Android 4.4. Also I think it will work for other audio streams types, such as ringtone, notifications, etc.Irremediable
@Boloney You can find them in Settings.System source code as Settings.System.VOLUME_NOTIFICATION, Settings.System.VOLUME_RING. They are removed from public API, but I it seems that we can use these corresponding strings.Irremediable
N
66

Nathan's code works but gives two notifications for each change system settings. To avoid that, use the following

public class SettingsContentObserver extends ContentObserver {
    int previousVolume;
    Context context;

    public SettingsContentObserver(Context c, Handler handler) {
        super(handler);
        context=c;

        AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        previousVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);
    }

    @Override
    public boolean deliverSelfNotifications() {
        return super.deliverSelfNotifications();
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);

        AudioManager audio = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        int currentVolume = audio.getStreamVolume(AudioManager.STREAM_MUSIC);

        int delta=previousVolume-currentVolume;

        if(delta>0)
        {
            Logger.d("Decreased");
            previousVolume=currentVolume;
        }
        else if(delta<0)
        {
            Logger.d("Increased");
            previousVolume=currentVolume;
        }
    }
}

Then in your service onCreate register it with:

mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );

Then unregister in onDestroy:

getApplicationContext().getContentResolver().unregisterContentObserver(mSettingsContentObserver);
Nicolettenicoli answered 1/7, 2013 at 6:47 Comment(3)
As the Content_URI is the generic URI, I am getting the onChange callback multiple times. I only want to monitor the change in Ringtone Volume. Tried with different URIs for Ringtone but none of them works. Can anyone help ?Tret
FYI: the observer will trigger if the volume is changed by code, or by the buttons.Whelk
@Whelk how to fix this? So that observer is not triggered when changed by code.Vtarj
A
14

Yes, you can register a receiver for a volume change(this is kind of a hack, but works), I managed to do it this way (does not involve a ContentObserver): In manifest xml file:

<receiver android:name="com.example.myproject.receivers.MyReceiver" >
     <intent-filter>
          <action android:name="android.media.VOLUME_CHANGED_ACTION" />
     </intent-filter>
</receiver>

BroadcastReceiver:

public class MyReceiver extends BroadcastReceiver {

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            Log.d("Music Stream", "has changed");       
        }
    }
}

hope it helps!

Aegina answered 9/2, 2015 at 12:6 Comment(4)
Worked for me. Thank you.Tiercel
Be warned this is unsupported and can stop working in future versions: https://mcmap.net/q/89634/-how-to-receive-volume-changed-events-for-the-voice-in-call-stream-typeDeerstalker
Stop sending Callbacks on Receiver if the volume reach a limit ( ex. high volume or mute ) . This does happening in some LG devicesAsuncion
This works but why is it triggering onRecieve multiple times ? The log printed like 5 times!Hoofbound
S
11

Based into Nathan's, adi's and swooby's code I created a full working example with some minor improvements.

Looking to the AudioFragment class we can see how easy is to listen for volume changes with our custom ContentObserver.

public class AudioFragment extends Fragment implements OnAudioVolumeChangedListener {

    private AudioVolumeObserver mAudioVolumeObserver;

    @Override
    public void onResume() {
        super.onResume();
        // initialize audio observer
        if (mAudioVolumeObserver == null) {
            mAudioVolumeObserver = new AudioVolumeObserver(getActivity());
        }
        /*
         * register audio observer to identify the volume changes
         * of audio streams for music playback.
         *
         * It is also possible to listen for changes in other audio stream types:
         * STREAM_RING: phone ring, STREAM_ALARM: alarms, STREAM_SYSTEM: system sounds, etc.
         */
        mAudioVolumeObserver.register(AudioManager.STREAM_MUSIC, this);
    }

    @Override
    public void onPause() {
        super.onPause();
        // release audio observer
        if (mAudioVolumeObserver != null) {
            mAudioVolumeObserver.unregister();
        }
    }

    @Override
    public void onAudioVolumeChanged(int currentVolume, int maxVolume) {
        Log.d("Audio", "Volume: " + currentVolume + "/" + maxVolume);
        Log.d("Audio", "Volume: " + (int) ((float) currentVolume / maxVolume) * 100 + "%");
    }
}


public class AudioVolumeContentObserver extends ContentObserver {

    private final OnAudioVolumeChangedListener mListener;
    private final AudioManager mAudioManager;
    private final int mAudioStreamType;
    private int mLastVolume;

    public AudioVolumeContentObserver(
            @NonNull Handler handler,
            @NonNull AudioManager audioManager,
            int audioStreamType,
            @NonNull OnAudioVolumeChangedListener listener) {

        super(handler);
        mAudioManager = audioManager;
        mAudioStreamType = audioStreamType;
        mListener = listener;
        mLastVolume = audioManager.getStreamVolume(mAudioStreamType);
    }

    /**
     * Depending on the handler this method may be executed on the UI thread
     */
    @Override
    public void onChange(boolean selfChange, Uri uri) {
        if (mAudioManager != null && mListener != null) {
            int maxVolume = mAudioManager.getStreamMaxVolume(mAudioStreamType);
            int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);
            if (currentVolume != mLastVolume) {
                mLastVolume = currentVolume;
                mListener.onAudioVolumeChanged(currentVolume, maxVolume);
            }
        }
    }

    @Override
    public boolean deliverSelfNotifications() {
        return super.deliverSelfNotifications();
    }
}


public class AudioVolumeObserver {

    private final Context mContext;
    private final AudioManager mAudioManager;
    private AudioVolumeContentObserver mAudioVolumeContentObserver;

    public AudioVolumeObserver(@NonNull Context context) {
        mContext = context;
        mAudioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    public void register(int audioStreamType, 
                         @NonNull OnAudioVolumeChangedListener listener) {

        Handler handler = new Handler();
        // with this handler AudioVolumeContentObserver#onChange() 
        //   will be executed in the main thread
        // To execute in another thread you can use a Looper
        // +info: https://mcmap.net/q/89635/-contentobserver-onchange

        mAudioVolumeContentObserver = new AudioVolumeContentObserver(
                handler,
                mAudioManager,
                audioStreamType,
                listener);

        mContext.getContentResolver().registerContentObserver(
                android.provider.Settings.System.CONTENT_URI,
                true,
                mAudioVolumeContentObserver);
    }

    public void unregister() {
        if (mAudioVolumeContentObserver != null) {
            mContext.getContentResolver().unregisterContentObserver(mAudioVolumeContentObserver);
            mAudioVolumeContentObserver = null;
        }
    }
}


public interface OnAudioVolumeChangedListener {

    void onAudioVolumeChanged(int currentVolume, int maxVolume);
}

Hope it's still useful for someone! :)

Sentinel answered 19/7, 2017 at 22:44 Comment(5)
Great answer and a nice summary of how to cope with volume changes of all sorts of streams. There is, however, a small mistake in the calculation of the percentage in the example given (onAudioVolumeChanged()). The closing brace after maxVolume should be placed after the value 100. So: (int)((float) currentVolume / maxVolume * 100) instead. Otherwise, you will always get 0% (if currentVolume != maxVolume) or 100% (if currentVolume == maxVolume) for the volume percentage. I took the freedom to correct the article, hope you don't mind...Hallucinatory
I could not just change one character (at least 6 characters have to be changed), so I added some comments too...Hallucinatory
There's an inaccuracy in the AudioVolumeObservers snippet. The comment says "with this handler AudioVolumeContentObserver#onChange() will be executed in the main thread", but new Handler() is used, so it actually will use the same thread method register(...) is called in.Borghese
@Borghese The snippet is accurate because the question is about observing volume changes and not about threading. For the sake of simplicity of the example I just used new Handler() which creates a handler for the current thread's looper (as you can read in the another SO answer I reference in comments).Sentinel
@RyanAmaral yes, I agree with you on the part where you say "I just used new Handler() which creates a handler for the current thread's looper" and believe that it clearly contradicts your comment in the snippet "with this handler onChange() will be executed in the main thread"Borghese
C
10

Nathan's and adi's code works, but can be cleaned up and self-contained to:

public class AudioStreamVolumeObserver
{
    public interface OnAudioStreamVolumeChangedListener
    {
        void onAudioStreamVolumeChanged(int audioStreamType, int volume);
    }

    private static class AudioStreamVolumeContentObserver
            extends ContentObserver
    {
        private final AudioManager                       mAudioManager;
        private final int                                mAudioStreamType;
        private final OnAudioStreamVolumeChangedListener mListener;

        private int mLastVolume;

        public AudioStreamVolumeContentObserver(
                @NonNull
                Handler handler,
                @NonNull
                AudioManager audioManager, int audioStreamType,
                @NonNull
                OnAudioStreamVolumeChangedListener listener)
        {
            super(handler);

            mAudioManager = audioManager;
            mAudioStreamType = audioStreamType;
            mListener = listener;

            mLastVolume = mAudioManager.getStreamVolume(mAudioStreamType);
        }

        @Override
        public void onChange(boolean selfChange)
        {
            int currentVolume = mAudioManager.getStreamVolume(mAudioStreamType);

            if (currentVolume != mLastVolume)
            {
                mLastVolume = currentVolume;

                mListener.onAudioStreamVolumeChanged(mAudioStreamType, currentVolume);
            }
        }
    }

    private final Context mContext;

    private AudioStreamVolumeContentObserver mAudioStreamVolumeContentObserver;

    public AudioStreamVolumeObserver(
            @NonNull
            Context context)
    {
        mContext = context;
    }

    public void start(int audioStreamType,
                      @NonNull
                      OnAudioStreamVolumeChangedListener listener)
    {
        stop();

        Handler handler = new Handler();
        AudioManager audioManager = (AudioManager) mContext.getSystemService(Context.AUDIO_SERVICE);

        mAudioStreamVolumeContentObserver = new AudioStreamVolumeContentObserver(handler, audioManager, audioStreamType, listener);

        mContext.getContentResolver()
                .registerContentObserver(System.CONTENT_URI, true, mAudioStreamVolumeContentObserver);
    }

    public void stop()
    {
        if (mAudioStreamVolumeContentObserver == null)
        {
            return;
        }

        mContext.getContentResolver()
                .unregisterContentObserver(mAudioStreamVolumeContentObserver);
        mAudioStreamVolumeContentObserver = null;
    }
}
Communicable answered 17/5, 2016 at 22:33 Comment(1)
Why the downvote with no comment? How is someone suppose to learn why something got downvoted?Communicable
M
4

If its only ringer mode change you can use Brodcast receiver with "android.media.RINGER_MODE_CHANGED" as the action. It will easy to implement

Marguerita answered 11/7, 2013 at 23:56 Comment(1)
can you be something more specific ?Regiment
F
3

Hi i tried the code above and it did not work for me. But when i tried to add this line

getActivity().setVolumeControlStream(AudioManager.STREAM_MUSIC);

and put

mSettingsContentObserver = new SettingsContentObserver(this,new Handler());
getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, mSettingsContentObserver );

It works now. My concern is how to hide the volume dialog onchange. See this image.

Flux answered 1/12, 2014 at 6:33 Comment(0)
R
2
  private const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
  private const val VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION"

    val filter = IntentFilter(VOLUME_CHANGED_ACTION)
    filter.addAction(RINGER_MODE_CHANGED_ACTION)

      val receiver = object : BroadcastReceiver() {
            override fun onReceive(context1: Context, intent: Intent) {

                val stream = intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, UNKNOWN)
                val mode = intent.getIntExtra(EXTRA_RINGER_MODE, UNKNOWN)
                val volumeLevel = audioManager.getStreamVolume(stream)
            }
        }
Roadway answered 1/6, 2018 at 9:43 Comment(0)
S
1

100% working way in all cases

  public class SettingsContentObserver extends ContentObserver {

        SettingsContentObserver(Handler handler) {
            super(handler);
        }

        @Override
        public boolean deliverSelfNotifications() {
            return super.deliverSelfNotifications();
        }

        @Override
        public void onChange(boolean selfChange) {
            super.onChange(selfChange);
            volumeDialogContract.updateMediaVolume(getMediaVolume());


    }

    int getMediaVolume() {
        return audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);
    }



    void unRegisterVolumeChangeListener() {
        volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().
                unregisterContentObserver(settingsContentObserver);
    }

    void registerVolumeChangeListener() {
        settingsContentObserver = new VolumeDialogPresenter.SettingsContentObserver(new Handler());
        volumeDialogContract.getAppContext().getApplicationContext().getContentResolver().registerContentObserver(
                android.provider.Settings.System.CONTENT_URI, true,
                settingsContentObserver);
    }
Serendipity answered 16/12, 2018 at 7:31 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.