19

Is there any way to listen to the event of volume change on Android, without just taking over the volume buttons?

The only thing I've found that works is here, but it works only after the volume control has disappeared.

Not all devices have volume buttons, and I need to capture the volume changes as soon as they occur, and not after the volume dialog is gone.

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • Use ContentObserver and get the volumes from audiomanager. I recently used it and it works great.[Check this Solution in stackoverflow](http://stackoverflow.com/questions/6896746/android-is-there-a-broadcast-action-for-volume-changes) – user2503849 Jul 11 '13 at 23:52
  • it´s an older post, but it´s paltry that there isn´t a broadcat implemented until now in Android....5 years later, still no broadcast... – Opiatefuchs May 26 '17 at 21:00
  • 1
    @Opiatefuchs Maybe create a request for it, here: https://issuetracker.google.com/issues . I will star it :) – android developer May 27 '17 at 09:36
  • you are right. Done...https://issuetracker.google.com/issues/62158875 But it´s my first created suggestion, I have no code sample for something like a broadcast. I hope they will accept it... – Opiatefuchs May 28 '17 at 10:36

5 Answers5

30

Better, you can register a ContentObserver as follows:

  getApplicationContext().getContentResolver().registerContentObserver(android.provider.Settings.System.CONTENT_URI, true, new ContentObserver(){...} );

Your ContentObserver might look like this:

public class SettingsContentObserver extends ContentObserver {
    private AudioManager audioManager;

    public SettingsContentObserver(Context context, Handler handler) {
        super(handler);
        audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
    }

    @Override
    public boolean deliverSelfNotifications() {
        return false;
    }

    @Override
    public void onChange(boolean selfChange) {
        int currentVolume = audioManager.getStreamVolume(AudioManager.STREAM_MUSIC);

        Log.d(TAG, "Volume now " + currentVolume);
    }
}

When done:

getApplicationContext().getContentResolver().unregisterContentObserver(mContentObserver);

One caution, though - sometimes the notifications seem to be delayed if there are lots of button presses quickly.

Tad
  • 4,668
  • 34
  • 35
4

ok , for now , what i do is to listen to the volume buttons using onKeyDown (and check for KEYCODE_VOLUME_DOWN,KEYCODE_VOLUME_MUTE,KEYCODE_VOLUME_UP ) , and using a handler i've posted a new runnable that checks the volume level .

also , since some devices have a volume dialog , i've added a listener to when it is being disappeared , according to this link.

Community
  • 1
  • 1
android developer
  • 114,585
  • 152
  • 739
  • 1,270
  • 1
    Since [android.media.VOLUME_CHANGED_ACTION](http://stackoverflow.com/a/8974510/725417) is not a viable option and since registerMediaButtonEventReceiver doesn't even work, your approach seems the only way (so far). Thanks +1. – uTubeFan Mar 05 '13 at 22:16
  • Are you sure it works if the display is dimmed? I've tested it on an emulator and I was only able to intercept the event if the display wasn't dimmed. I asked a similar question https://stackoverflow.com/questions/53359340/onkeydown-doesnt-work-if-display-is-dimmed but nobody answered. I don't understand why the events cannot be intercepted if the display is dimmed... – ka3ak Apr 06 '19 at 13:42
  • @ka3ak Have you also tested it on real device? If not, and you don't have one, you can show me a sample project (via Github please) and I will test it for you. – android developer Apr 06 '19 at 16:49
  • @androiddeveloper I've only tested it on a real device for the event KeyEvent.KEYCODE_HEADSETHOOK which is a button on a head set that can be used to accept calls, but I assume that onKeyDown() doesn't work for any key press if the display is dimmed. I've already run against a question in this forum that discusses the problem, but I can't find it. I'll post it if I find it. In the meanwhile you can test it. I don't think it's too hard. Just try to reproduce it with the events you mentioned in your answer while the display of your phone is dimmed. – ka3ak Apr 06 '19 at 17:09
  • @ka3ak If the device is dimmed, the front app is not your app, as far as I know, so it shouldn't take get the events. – android developer Apr 06 '19 at 17:30
  • @androiddeveloper It may be so. But your app may still do some work and have the need to be notified about this event. So to me it's a not reliable way to use onKeyDown() callback because it's only called when display isn't dimmed. – ka3ak Apr 06 '19 at 18:15
  • @ka3ak I think that other than accessibility service, you are out of options, then, because you want to handle it globally instead of when your app is truly visible and in the foreground. – android developer Apr 06 '19 at 22:44
2

Use broadcast receiver VOLUME_CHANGED_ACTION then use AudioManager to obtain current volume.

<receiver android:name="VolumeChangeReceiver" >
    <intent-filter>
         <action android:name="android.media.VOLUME_CHANGED_ACTION" />
    </intent-filter>
</receiver>
Apirak Lunla
  • 647
  • 7
  • 5
  • are you sure it works? have you read this: http://stackoverflow.com/a/8974510/878126 ? – android developer Feb 13 '14 at 20:10
  • @androiddeveloper it works in many cases, there's just no guarantee since it is system-private and therefore subject to change/removal without notice. – CCJ Mar 03 '17 at 18:40
  • @CCJ Simple enough: one gets internal/hidden APIs on the SDK and when/if that constant disappears from the classes (Android Studio won't find a reference and will throw an error), it's time to think on another solution. Btw, for anyone looking, the constant is AudioManager.VOLUME_CHANGED_ACTION. – Edw590 May 25 '21 at 18:50
0

You can use : registerMediaButtonEventReceiver (ComponentName eventReceiver) which registers a component to be the sole receiver of MEDIA_BUTTON intents.

//  in your activity.
MediaButtonReceiver receiver = new MediaButtonReceiver();

// in onCreate put
registerMediaButtonEventReceiver(receiver); 

class MediaButtonReceiver implements BroadcastReceiver {
     void onReceive(Intent intent) {
          KeyEvent ke = (KeyEvent)intent.getExtra(Intent.EXTRA_KEY_EVENT); 
          if (ke .getKeyCode() == KeyEvent.KEYCODE_VOLUME_DOWN) {
            //action when volume goes down
          }
           if (ke .getKeyCode() == KeyEvent.KEYCODE_VOLUME_UP) {
              //action when volume goes up
          }
     } 
}

   //In both onStop and onPause put :
   unregisterMediaButtonEventReceiver(receiver);

what we are doing here is defining a BroadcastReceiver that deals with ACTION_MEDIA_BUTTON. and use EXTRA_KEY_EVENT which is containing the key event that caused the broadcast to get what was pressed and act upon that.

Mouna Cheikhna
  • 38,870
  • 10
  • 48
  • 69
  • 5
    but isn't it just capturing the volume buttons? what would occur for devices that don't have them? also , what about devices that have them , but the user has touched on the volume seekbar after it was shown ? in such cases , i won't be able to capture the events... – android developer Jul 03 '12 at 22:15
  • 1
    registerMediaButtonEventReceiver works for headset buttons but not the phones hardware buttons. I just tried it. – uTubeFan Mar 05 '13 at 22:13
  • This is deprecated in API 21+ – Christopher Perry Jul 01 '19 at 21:11
0

2023 Solution with a BroadcastReceiver

Works with both volume buttons and through Android System UI. I'm using BroadcastReceiver as an Inner Class in this example. I'm using a similar implementation as this to update seek bar in a fragment.

public class AFragment extends Fragment {
    private Context context;
    private AudioManager audioManager;
    // THE STREAM TYPE YOU WANT VOLUME FROM
    private final int streamType = AudioManager.STREAM_MUSIC;
    private VolumeChangeListener volumeChangeListener;
    private IntentFilter intentFilter;
    private final String VOLUME_CHANGED_ACTION = "android.media.VOLUME_CHANGED_ACTION";

    public AFragment(Context context) {
        this.context = context;
        this.audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
        this.volumeChangeListener = new VolumeChangeListener();
        this.intentFilter = new IntentFilter(VOLUME_CHANGED_ACTION);
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        context.unregisterReceiver(volumeChangeListener);
    }

    @Override
    public void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        context.registerReceiver(volumeChangeListener, intentFilter);
    }

    private class VolumeChangeListener extends BroadcastReceiver {
        // VOLUME CHANGED ACTION triggers 3 times
        // Making use of increment variable to make sure to skip the extra calls
        private int callCount = 0;

        @Override
        public void onReceive(Context context, Intent intent) {
            callCount++;
            if (intent.getAction().equals(VOLUME_CHANGED_ACTION)) {
                if (callCount % 3 == 0) {
                    int SYSTEM_currentVolume = audioManager.getStreamVolume(streamType);

                    // DO WHATEVER YOU WANT WITH THE NEW VOLUME VALUE

                }
            }
        }
    }
}

IntentFilter android.media.VOLUME_CHANGED_ACTION can be found here: Android Google Source

dot_cr2
  • 69
  • 1
  • 5