6

I know highly similar questions have been asked, yet there are differences. Most questions want to detect single press. Some long press but with the screen enabled. I want to detect long presses in the background also while the screen is off. I want to use it when a media player is active so deep sleep or doze mode is not relevant.

I tried most answers from here: Listen to volume buttons in background service? All of them don't fulfil my requirements. I also tried to use the accessibilityservice, but that does not work when the screen is off.

I am 100% sure it is possible. There is an app on the play store that does it on my Pixel 2. I did some testing and it seems it only actually requires the NotificationListener

Robin Dijkhof
  • 18,665
  • 11
  • 65
  • 116
  • Have you tried `PowerManager.PARTIAL_WAKE_LOCK`? – C.F.G Aug 24 '23 at 08:59
  • The author replied to a review (irrelevant) but it _maybe_ gives a potential hint on what they are using: `accessibility access is required to detect volume button press. Notification access is needed to detect that a music player is active. More details can be found on my webpage.` – Martin Marconcini Aug 24 '23 at 11:21
  • And then in the app description: `As of Android 12, Volumee requires the Always On Display option to be enabled in order to work when the screen is OFF. Unfortunately, Android 12 was released with a bug and currently it doesn't behave as the documentation says. The problem has been reported to Google - let's hope they fix it in the next Android version.` – Martin Marconcini Aug 24 '23 at 11:25
  • Maybe this is also affecting your code. I'd check the google issue tracker as well. – Martin Marconcini Aug 24 '23 at 11:25
  • @Robin can you please confirm that you have already tried using `Bind Service` with `Broadcast Receiver` or `WorkManager`? – A S M Sayem Aug 24 '23 at 15:24
  • @C.F.G yes, I have. – Robin Dijkhof Aug 25 '23 at 19:48
  • @MartinMarconcini I am pretty sure that is not the problem since that other app does work on my phone. – Robin Dijkhof Aug 25 '23 at 19:50
  • @ASMSayem yes, except for the `Workmanager` – Robin Dijkhof Aug 25 '23 at 19:50
  • @RobinDijkhof: I am 51% sure that the linked app is using something like `getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);` due to its permission doc in playstore. `prevent device from sleeping, run at startup, reorder running apps` – C.F.G Aug 26 '23 at 06:01
  • @RobinDijkhof: Also according to the second picture of the app in palystore, it says that this app needs a notification access. – C.F.G Aug 26 '23 at 06:12

3 Answers3

1

You can capture the event easily with a couple of snippets

Register a broadcast reciever

<receiver android:name=".VolumeButtonReceiver">
    <intent-filter>
        <action android:name="android.media.VOLUME_CHANGED_ACTION" />
    </intent-filter>
</receiver>

Bind the event

public class VolumeButtonReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction() != null && intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            // Handle volume key event here
        }
    }
}

Getting longpress usually needs some extra work the idea I like is to detect the keydown event and use a counter ( event.startTracking() ) to decide if it's a longpress.

described well in this answer:

onKeyDown and onKeyLongPress

(a very old answer but seems to keep on working with every android update)

This gist is that you starting by tracking the onKeyDown event, set a flag when the longpress event is hit, set a flag to determine if it was longpressed or not, and read that flag in the onKeyUp event.

Additionally you asked about doing this while the screen is off. For that you'll want a "Foreground" Service and Partial wake lock enabled, the docs give better examples than I ever could:

https://developer.android.com/training/scheduling/wakelock

https://developer.android.com/guide/components/foreground-services

danielRicado
  • 795
  • 8
1

i come from javascript background, IMO the basic idea is we need something like debouncing. use the register broadcaster as mentioned by @danielricardo:

<receiver android:name=".VolumeButtonReceiver">
    <intent-filter>
        <action android:name="android.media.VOLUME_CHANGED_ACTION" />
    </intent-filter>
</receiver>

paste this in as receiver class, rename variables acc to your needs:

public class VolumeButtonReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            int volumeState = intent.getIntExtra("android.media.EXTRA_VOLUME_STATE", -1);
            if (volumeState == 1) { // Volume button down
                Intent i = new Intent(context, LongPressService.class);
                i.putExtra("volumeButtonAction", 0);
                context.startService(i);
            } else if (volumeState == 0) { // Volume button up
                Intent i = new Intent(context, LongPressService.class);
                i.putExtra("volumeButtonAction", 1);
                context.startService(i);
            }
        }
    }
}

create a long press service:

public class LongPressService extends Service {
    private boolean isVolumeButtonLongPress = false;
    private Handler handler = new Handler();
    private long pressStartTime = 0;

    private Runnable longPressRunnable = new Runnable() {
        @Override
        public void run() {
            if (isVolumeButtonLongPress) {
                long pressDuration = System.currentTimeMillis() - pressStartTime;
                if (pressDuration >= 1000) {
                    int volumeButtonAction = getIntent().getIntExtra("volumeButtonAction", -1);
                    if (volumeButtonAction == 0) {
                        myCustomFunctionDown();
                    } else if (volumeButtonAction == 1) {
                        myCustomFunctionUp();
                    }
                }
            }
        }
    };

    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (intent != null) {
            int volumeButtonAction = intent.getIntExtra("volumeButtonAction", -1);
            if (volumeButtonAction != -1) {
                isVolumeButtonLongPress = true;
                pressStartTime = System.currentTimeMillis();
                handler.postDelayed(longPressRunnable, 1000); // Debounce threshold
            }
        } else {
            handler.removeCallbacks(longPressRunnable);
            isVolumeButtonLongPress = false;
        }

        return START_STICKY;
    }

    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }

    private void myCustomFunctionDown() {
        // Call your custom function for volume down long press here
    }

    private void myCustomFunctionUp() {
        // Call your custom function for volume up long press here
    }
}
nikhil swami
  • 2,360
  • 5
  • 15
0

As I understand your question, you want to:

  1. Detect long presses in the background (some other apps are still running, and the screen is ON?)
  2. Detect long presses while the SCREEN is OFF

for booth case, a MEDIA PLAYER is ACTIVE

As it is writen in Android documentation

In previous version of Android, detecting touch events while the screen is off is not possible in Android due to security reasons.

But there are some exceptions, like music players apps, not all media players.

But actually, Touch event is easily detected in some phone like Pixel 5.

it is possible to detect touch events while the screen is off using a Service and a WakeLock. So you have to check the docs, for what version of android you are working on.

If you are using recent android, You may try this code:

public class VolumeButtonReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().equals("android.media.VOLUME_CHANGED_ACTION")) {
            int streamType = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_TYPE", -1);
            if (streamType == AudioManager.STREAM_MUSIC) {
                int oldVolume = intent.getIntExtra("android.media.EXTRA_PREV_VOLUME_STREAM_VALUE", -1);
                int newVolume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE", -1);
                if (newVolume < oldVolume) {
                    // Volume button was long-pressed
                }
            }
        }
    }
}

This code creates a BroadcastReceiver that listens for the android.media.VOLUME_CHANGED_ACTION intent. When this intent is received, it checks whether the volume stream type is STREAM_MUSIC, which corresponds to the media volume.