Is there a broadcast action for volume changes?
AndroidAndroid IntentVolumeBroadcastAndroid Problem Overview
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?
Android Solutions
Solution 1 - Android
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.
Solution 2 - Android
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);
Solution 3 - Android
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!
Solution 4 - Android
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://stackoverflow.com/a/35261443/904907
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! :)
Solution 5 - Android
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;
}
}
Solution 6 - Android
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
Solution 7 - Android
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.
Solution 8 - Android
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)
}
}
Solution 9 - Android
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);
}