If the user drags a widget normally to the home screen, then the configuration activity is started (if set). If the activity returns a result code of RESULT_CANCELED, then the widget creation is cancelled.
This is documented here.
Alternativly, to add a widget from an activity, one can use AppWidgetManager.requestPinAppWidget().
But then the configuration activity is not started, and the program should explicitly start it, as documented here.
This is my code that does it:
object StartWidgetConfigureReceiver : BroadcastReceiver() {
const val ACTION_NEW_WIDGET = "ACTION_NEW_WIDGET"
override fun onReceive(context: Context?, intent: Intent?) {
Intent(context, MyWidgetConfigureActivity::class.java).apply {
action = ACTION_APPWIDGET_CONFIGURE
putExtras(intent)
context?.startActivity(this)
}
}
}
val appWidgetManager = AppWidgetManager.getInstance(context)
val myProvider = ComponentName(applicationContext, MyWidgetProvider::class.java)
val successIntent = PendingIntent.getBroadcast(applicationContext, 0,
Intent(StartWidgetConfigureReceiver.ACTION_NEW_WIDGET),
PendingIntent.FLAG_MUTABLE)
appWidgetManager.requestPinAppWidget(myProvider,null, successIntent)
But how to cancel the widget if needed?
Adding resultCode = Activity.RESULT_CANCELED in onReceive does not seem to do it.
(I would need to wait for the result from the configuration activity, but that is not the question. Unless the way of starting the activity is relevant.)
I created a floating widget that works even when the app closed. In addition I have a class that is responsible for detecting incoming and outgoing calls on the device And send a suitable toast. it also works when the app is closed.
Now I want the floating widget to appear on the screen when a call on the device ends.
I tried to do this but the widget does not appear. Both work separately, but not together.
There are two classs:
-Creates the widget : FloatingViewService extends Service.
-Call Recognition: CallReceiver extends BroadcastReceiver.
CallReceiver class
public class CallReceiver extends BroadcastReceiver {
// ^ detecting incoming and outgoing calls ^
#Override
public void onReceive(Context context, Intent intent) {
if(intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
showToast(context,"Call started...");
}
else if(intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_IDLE)){
showToast(context,"Call ended...");
//*****Activate the floating widget after the call ends.
startWidget( context );
}
else if(intent.getStringExtra(TelephonyManager.EXTRA_STATE).equals(TelephonyManager.EXTRA_STATE_RINGING)){
showToast(context,"Incoming call...");
}
}
//*****Activate widget
void startWidget(Context cotx) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Permission checks
if (Settings.canDrawOverlays(cotx)) {
// Start widget service
startService( new Intent( cotx, FloatingViewService.class));
}
}
}
//*****Action: Start a particular service
public ComponentName startService(Intent service) {
throw new RuntimeException("Stub!");
}
//Another action unrelated to my question
}
I believe the problem is related to the "startService" operation.
I was able to solve the problem for myself in a relatively simple way.
All I had to do was delete "startService" and update "startWidget" as follows:
void startWidget(Context cotx) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { //Permission checks
if (Settings.canDrawOverlays(cotx)) {
Intent intent = new Intent(cotx,FloatingViewService.class);
cotx.startService(intent);
Log.i("Autostart", "started");
}
}
}
How to open the default handler page in the android setting via intent?
or How we can open directly the default SMS page?
it seems from android Q to upper we can not Telephony.Sms.Intents.ACTION_CHANGE_DEFAULT
Found the answer, you can open this page by this code:
//This code will work only on android version N and above
//Add if condition to prevent crash on older devices
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
Intent intent = new Intent(Settings.ACTION_MANAGE_DEFAULT_APPS_SETTINGS);
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if (requestCode != -1)
act.startActivityForResult(intent, requestCode);
else
act.startActivity(intent);
}
Another way with this code to open an intent default app,
in this case, I try to set the default launcher app
// dialog set default app
#RequiresApi(Build.VERSION_CODES.Q)
private fun showLauncherSelection() {
val roleManager = this.getSystemService(Context.ROLE_SERVICE)
as RoleManager
if (roleManager.isRoleAvailable(RoleManager.ROLE_HOME) &&
!roleManager.isRoleHeld(RoleManager.ROLE_HOME)
) {
val intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_HOME)
startActivityForResult(intent,0)
}
}
You can set the other Default app with change the ROLE_HOME following this Role Manager. If you want to change SMS default app, try to change the ROLE_HOME to ROLE_SMS
I know how to listen to volume buttons in an activity. But can I do that in a background service? If yes, how to do that?
It is possible. Use code below (for newer Android versions, especially Marshmallow, see bottom of the answer):
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("Ściszył!"); // volume decreased.
previousVolume=currentVolume;
}
else if(delta<0)
{
Logger.d("Zrobił głośniej!"); // volume 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);
Note that this example judges by change of media volume, if you want to use other volume, change it!
UPDATE:
Above method supposedly doesn't work on Marshmallow, BUT there's much better way now since MediaSession was introduced! So first you have to migrate your code to MediaController/MediaSession pattern and then use this code:
private VolumeProviderCompat myVolumeProvider = null;
myVolumeProvider = new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
#Override
public void onAdjustVolume(int direction) {
// <0 volume down
// >0 volume up
}
};
mSession.setPlaybackToRemote(myVolumeProvider);
Somehow volume button presses are detected even with screen off (just be sure to register proper media button intent receiver if applicable for your platform!)
UPDATE 2 since GalDude requested some more info on getting media MediaSession/MediaController. Sorry, but since I stopped using Java it will be in Kotlin:
lateinit var mediaSession: MediaSessionCompat // you have to initialize it in your onCreate method
val kontroler: MediaControllerCompat
get() = mediaSession.controller // in Java it's just getController() on mediaSession
// in your onCreate/start method:
mediaSession = MediaSessionCompat(this, "YourPlayerName", receiver, null)
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS)
mediaSession.isActive = true
if (ratingIsWorking) // note: rating crashes on some machines you have to check it!
mediaSession.setRatingType(RatingCompat.RATING_5_STARS)
mediaSession.setCallback(object : MediaSessionCompat.Callback() {
...
// here you have to implement what happens with your player when play/pause/stop/ffw etc. is requested - see exaples elsewhere
})
// onDestroy/exit method:
mediaSession.isActive = false
mediaSession.release()
Unfortunately, this is another area of Android where there are like five different ways to "solve the problem", but most of them don't work very well. For my own sanity, I'll attempt to list all the different approaches below.
Solutions
1) MediaSession (from Service)
Answer by Denis Kniazhev: https://stackoverflow.com/a/43304591/2441655
Drawbacks:
Requires Android API level 21+ (Android 5.0+).
2) android.media.VOLUME_CHANGED_ACTION (from Service)
Answer by Nikhil: https://stackoverflow.com/a/44040282/2441655
Drawbacks:
Not an official part of the SDK: https://stackoverflow.com/a/8974510/2441655
Ignores first-press of volume-key (since it only shows the volume-bar).
Ignores volume-up key when at 100%, and volume-down key when at 0%.
3) ContentObserver (from Service)
Answer by ssuukk: https://stackoverflow.com/a/15292255/2441655 (first part)
Drawbacks:
Doesn't work in newer versions of Android: comment by dsemi
Ignores first-press of volume-key (since it only shows the volume-bar).
Ignores volume-up key when at 100%, and volume-down key when at 0%.
4) AudioManager.registerMediaButtonEventReceiver (from Service)
Answer by Joe: https://stackoverflow.com/a/11510564/2441655
Drawbacks:
Doesn't work on most roms: comment by elgui
5) onKeyDown (from Activity)
Answer by dipali: https://stackoverflow.com/a/21086563/2441655
Drawbacks:
Doesn't work if screen is off, in different app, etc.
6) dispatchKeyEvent (from Activity)
Answer by Maurice Gavin: https://stackoverflow.com/a/11462962/2441655
Drawbacks:
Doesn't work if screen is off, in different app, etc.
Conclusion
The solution I'm currently using is #1, because:
It's an official part of the SDK.
It is usable from a service. (ie. regardless of what app you're in)
It captures every volume-key press, regardless of current-volume/ui-state.
It works when the screen is off.
Let me know if you find any others -- or if you've found more drawbacks to some of them!
The AOSP Music app has a Service (MediaPlaybackService) that responds to volume key events by registering a BroadcastReceiver (MediaButtonIntentReceiver).
Here's the code snippet where it registers the receiver:
mAudioManager = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
ComponentName rec = new ComponentName(getPackageName(),
MediaButtonIntentReceiver.class.getName());
mAudioManager.registerMediaButtonEventReceiver(rec);
Also, don't forget about manifest:
<receiver android:name="com.android.music.MediaButtonIntentReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
<action android:name="android.media.AUDIO_BECOMING_NOISY" />
</intent-filter>
</receiver>
This works even if the Music app is not in the foreground. Isn't that what you want?
I was able to make it work on android 5+ devices using MediaSession. However,ContentObserver suggested by #ssuukk didn't work for me on both 4.4 and 7.0 devices (at least on ROMs that I've been testing on).
Here is a full example which works on android 5+.
Service:
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.v4.media.VolumeProviderCompat;
import android.support.v4.media.session.MediaSessionCompat;
import android.support.v4.media.session.PlaybackStateCompat;
public class PlayerService extends Service {
private MediaSessionCompat mediaSession;
#Override
public void onCreate() {
super.onCreate();
mediaSession = new MediaSessionCompat(this, "PlayerService");
mediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mediaSession.setPlaybackState(new PlaybackStateCompat.Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0) //you simulate a player which plays something.
.build());
//this will only work on Lollipop and up, see https://code.google.com/p/android/issues/detail?id=224134
VolumeProviderCompat myVolumeProvider =
new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, /*max volume*/100, /*initial volume level*/50) {
#Override
public void onAdjustVolume(int direction) {
/*
-1 -- volume down
1 -- volume up
0 -- volume button released
*/
}
};
mediaSession.setPlaybackToRemote(myVolumeProvider);
mediaSession.setActive(true);
}
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
super.onDestroy();
mediaSession.release();
}
}
In AndroidManifest.xml:
<application ...>
...
<service android:name=".PlayerService"/>
</application>
In your activity:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
...
startService(new Intent(this, PlayerService.class));
}
There are several things to be aware of:
It intercepts volume buttons completely so while this code is running you won't be able to adjust ring volume using volume buttons. This might be possible to fix, I just didn't try.
If you run the example as-is the volume buttons will remain controlled by the app even when the screen is off and the app has been removed from "Recent Apps" list. You'll have to go to Settings->Applications, find the app and force stop it to get volume buttons back.
Judging by the couple of other questions about this topic, no.
Other question 1,
Other question 2
Services simply do not receive KeyEvent callbacks.
You need to play blank sound from service then only you can listen to volume changes. Following worked for me
Steps
1. Put blank.mp3 in raw folder (Download from here)
2. Start media at onStartCommand()
private MediaPlayer mediaPlayer;
public MyService() {
}
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
........
mediaPlayer = MediaPlayer.create(this, R.raw.blank);
mediaPlayer.setLooping(true);
mediaPlayer.start();
.......
return START_STICKY;
}
3. You must choose to stop and release mediaplayer. It's better to do so in onDestroy()
#Override
public void onDestroy() {
mediaPlayer.stop();
mediaPlayer.release();
super.onDestroy();
}
4. Create Broadcast receiver that will listen for volume changes
int volumePrev = 0;
private BroadcastReceiver broadcastReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
if ("android.media.VOLUME_CHANGED_ACTION".equals(intent.getAction())) {
int volume = intent.getIntExtra("android.media.EXTRA_VOLUME_STREAM_VALUE",0);
Log.i(TAG, "volume = " + volume);
if (volumePrev < volume) {
Log.i(TAG, "You have pressed volume up button");
} else {
Log.i(TAG, "You have pressed volume down button");
}
volumePrev = volume;
}
}
};
5. Register the broadcast receiver in onStartCommand()
#Override
public int onStartCommand(Intent intent, int flags, int startId) {
.....
IntentFilter filter = new IntentFilter();
filter.addAction("android.media.VOLUME_CHANGED_ACTION");
registerReceiver(broadcastReceiver, filter);
....
return START_STICKY;
}
6. Unregister broadccast receiver in onDestroy()
#Override
public void onDestroy() {
.....
unregisterReceiver(broadcastReceiver);
.....
super.onDestroy();
}
That's all
This requires Lollipop (v5.0/API 21) or higher
My goal was to adjust system volume from a Service. Any action can be taken on press though.
public class VolumeKeyController {
private MediaSessionCompat mMediaSession;
private final Context mContext;
public VolumeKeyController(Context context) {
mContext = context;
}
private void createMediaSession() {
mMediaSession = new MediaSessionCompat(mContext, KeyUtil.log);
mMediaSession.setFlags(MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS |
MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS);
mMediaSession.setPlaybackState(new Builder()
.setState(PlaybackStateCompat.STATE_PLAYING, 0, 0)
.build());
mMediaSession.setPlaybackToRemote(getVolumeProvider());
mMediaSession.setActive(true);
}
private VolumeProviderCompat getVolumeProvider() {
final AudioManager audio = mContext.getSystemService(Context.AUDIO_SERVICE);
int STREAM_TYPE = AudioManager.STREAM_MUSIC;
int currentVolume = audio.getStreamVolume(STREAM_TYPE);
int maxVolume = audio.getStreamMaxVolume(STREAM_TYPE);
final int VOLUME_UP = 1;
final int VOLUME_DOWN = -1;
return new VolumeProviderCompat(VolumeProviderCompat.VOLUME_CONTROL_RELATIVE, maxVolume, currentVolume) {
#Override
public void onAdjustVolume(int direction) {
// Up = 1, Down = -1, Release = 0
// Replace with your action, if you don't want to adjust system volume
if (direction == VOLUME_UP) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_RAISE, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
else if (direction == VOLUME_DOWN) {
audio.adjustStreamVolume(STREAM_TYPE,
AudioManager.ADJUST_LOWER, AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE);
}
setCurrentVolume(audio.getStreamVolume(STREAM_TYPE));
}
};
}
// Call when control needed, add a call to constructor if needed immediately
public void setActive(boolean active) {
if (mMediaSession != null) {
mMediaSession.setActive(active);
return;
}
createMediaSession();
}
// Call from Service's onDestroy method
public void destroy() {
if (mMediaSession != null) {
mMediaSession.release();
}
}
}
#venryx: Solution 1 no longer works in Android 12
#ssuukk: I can confirm #venryx's comment that SettingsContentObserver does not get triggered if the volume is already at min or max.
#bikram: I created a VolumeButtonHelper class that uses this approach. Although it does use an undocumented SDK feature, it still works in 2022. I have extensively researched this topic and this was the only solution I could find.
class VolumeButtonHelper(private var context: Context,
private var stream: Int? = null,
enabledScreenOff: Boolean)
{
companion object
{
const val VOLUME_CHANGE_ACTION = "android.media.VOLUME_CHANGED_ACTION"
const val EXTRA_VOLUME_STREAM_TYPE = "android.media.EXTRA_VOLUME_STREAM_TYPE"
}
enum class Direction
{
Up,
Down,
Release
}
private lateinit var mediaPlayer: MediaPlayer
private var volumeBroadCastReceiver: VolumeBroadCastReceiver? = null
private var volumeChangeListener: VolumeChangeListener? = null
private val audioManager: AudioManager? =
context.getSystemService(Context.AUDIO_SERVICE) as? AudioManager
private var priorVolume = -1
private var volumePushes = 0.0
private var longPressReported = false
var doublePressTimeout = 350L
var buttonReleaseTimeout = 100L
var minVolume = -1
private set
var maxVolume = -1
private set
var halfVolume = -1
private set
var currentVolume = -1
private set
init
{
if (audioManager != null)
{
minVolume = audioManager.getStreamMinVolume(STREAM_MUSIC)
maxVolume = audioManager.getStreamMaxVolume(STREAM_MUSIC)
halfVolume = (minVolume + maxVolume) / 2
/*************************************
* BroadcastReceiver does not get triggered for VOLUME_CHANGE_ACTION
* if the screen is off and no media is playing.
* Playing a silent media file solves that.
*************************************/
if (enabledScreenOff)
{
mediaPlayer =
MediaPlayer.create(context,
R.raw.silence)
.apply {
isLooping = true
setWakeMode(context, PARTIAL_WAKE_LOCK)
start()
}
}
}
else
Log.e(TAG, "Unable to initialize AudioManager")
}
fun registerVolumeChangeListener(volumeChangeListener: VolumeChangeListener)
{
if (volumeBroadCastReceiver == null)
{
this.volumeChangeListener = volumeChangeListener
volumeBroadCastReceiver = VolumeBroadCastReceiver()
if (volumeBroadCastReceiver != null)
{
val filter = IntentFilter()
filter.addAction(VOLUME_CHANGE_ACTION)
context.registerReceiver(volumeBroadCastReceiver, filter)
}
else
Log.e(TAG, "Unable to initialize BroadCastReceiver")
}
}
fun unregisterReceiver()
{
if (volumeBroadCastReceiver != null)
{
context.unregisterReceiver(volumeBroadCastReceiver)
volumeBroadCastReceiver = null
}
}
fun onVolumePress(count: Int)
{
when (count)
{
1 -> volumeChangeListener?.onSinglePress()
2 -> volumeChangeListener?.onDoublePress()
else -> volumeChangeListener?.onVolumePress(count)
}
}
interface VolumeChangeListener
{
fun onVolumeChange(direction: Direction)
fun onVolumePress(count: Int)
fun onSinglePress()
fun onDoublePress()
fun onLongPress()
}
inner class VolumeBroadCastReceiver : BroadcastReceiver()
{
override fun onReceive(context: Context, intent: Intent)
{
if (stream == null ||
intent.getIntExtra(EXTRA_VOLUME_STREAM_TYPE, -1) == stream)
{
currentVolume = audioManager?.getStreamVolume(STREAM_MUSIC) ?: -1
if (currentVolume != -1)
{
if (currentVolume != priorVolume)
{
if (currentVolume > priorVolume)
volumeChangeListener?.onVolumeChange(Up)
else if (currentVolume < priorVolume)
volumeChangeListener?.onVolumeChange(Down)
priorVolume = currentVolume
}
volumePushes += 0.5 // For some unknown reason (to me), onReceive gets called twice for every button push
if (volumePushes == 0.5)
{
CoroutineScope(Dispatchers.Main).launch {
delay(doublePressTimeout - buttonReleaseTimeout)
buttonDown()
}
}
}
}
}
private fun buttonDown()
{
val startVolumePushes = volumePushes
CoroutineScope(Dispatchers.Main).launch {
delay(buttonReleaseTimeout)
val currentVolumePushes = volumePushes
if (startVolumePushes != currentVolumePushes)
{
if (volumePushes > 2 && !longPressReported)
{
longPressReported = true
volumeChangeListener?.onLongPress()
}
buttonDown()
}
else
{
onVolumePress(volumePushes.toInt())
volumeChangeListener?.onVolumeChange(Release)
volumePushes = 0.0
longPressReported = false
}
}
}
}
}
Instantiate that class in a Service (with the appropriate wake lock):
class ForegroundService : Service()
{
private lateinit var volumeButtonHelper: VolumeButtonHelper
companion object
{
var wakeLock: WakeLock? = null
const val TAG = "VolumeButtonHelper"
const val ACTION_FOREGROUND_WAKELOCK = "com.oliverClimbs.volumeButtonHelper.FOREGROUND_WAKELOCK"
const val ACTION_FOREGROUND = "com.oliverClimbs.volumeButtonHelper.FOREGROUND"
const val WAKELOCK_TAG = "com.oliverClimbs.volumeButtonHelper:wake-service"
const val CHANNEL_ID = "Running in background"
}
override fun onBind(p0: Intent?): IBinder?
{
return null
}
override fun onCreate()
{
super.onCreate()
volumeButtonHelper = VolumeButtonHelper(this,
STREAM_MUSIC,
enabledScreenOff = true)
volumeButtonHelper.registerVolumeChangeListener(
object : VolumeButtonHelper.VolumeChangeListener
{
override fun onVolumeChange(direction: VolumeButtonHelper.Direction)
{
Log.i(TAG, "onVolumeChange: $direction")
}
override fun onVolumePress(count: Int)
{
Log.i(TAG, "onVolumePress: $count")
}
override fun onSinglePress()
{
Log.i(TAG, "onSinglePress")
}
override fun onDoublePress()
{
Log.i(TAG, "onDoublePress")
}
override fun onLongPress()
{
Log.i(TAG, "onLongPress")
}
})
}
#SuppressLint("WakelockTimeout")
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int
{
super.onStartCommand(intent, flags, startId)
if (intent?.action == ACTION_FOREGROUND || intent?.action == ACTION_FOREGROUND_WAKELOCK)
startForeground(R.string.foreground_service_started,
Notification.Builder(this, CHANNEL_ID).build())
if (intent?.action == ACTION_FOREGROUND_WAKELOCK)
{
if (wakeLock == null)
{
wakeLock = getSystemService(PowerManager::class.java)?.newWakeLock(
PARTIAL_WAKE_LOCK,
WAKELOCK_TAG)
wakeLock?.acquire()
}
else
{
releaseWakeLock()
}
}
return START_STICKY
}
private fun releaseWakeLock()
{
wakeLock?.release()
wakeLock = null
}
override fun onDestroy()
{
super.onDestroy()
releaseWakeLock()
stopForeground(STOP_FOREGROUND_REMOVE)
volumeButtonHelper.unregisterReceiver()
}
}
Start the Service from your Activity:
class MainActivity : AppCompatActivity()
{
private var configurationChange = false
override fun onCreate(savedInstanceState: Bundle?)
{
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
if (!configurationChange)
startService(Intent(ForegroundService.ACTION_FOREGROUND_WAKELOCK).setClass(this,
ForegroundService::class.java))
}
override fun onDestroy()
{
Log.d(TAG, "MainActivity: onDestroy")
configurationChange =
if (isChangingConfigurations)
true
else
{
stopService(Intent(this, ForegroundService::class.java))
false
}
super.onDestroy()
}
}
I have shared the full project at github.com/oliverClimbs/VolumeButtonDemo.
As for me accessibility service only works as expected
class KeyService : AccessibilityService() {
override fun onServiceConnected() {}
override fun onAccessibilityEvent(event: AccessibilityEvent) {}
override fun onKeyEvent(event: KeyEvent): Boolean {
when (event.keyCode) {
KeyEvent.KEYCODE_VOLUME_UP -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
KeyEvent.KEYCODE_VOLUME_DOWN -> {
when (event.action) {
KeyEvent.ACTION_DOWN -> {
}
KeyEvent.ACTION_UP -> {
}
}
}
}
return super.onKeyEvent(event)
}
override fun onInterrupt() {}
}
<?xml version="1.0" encoding="utf-8"?>
<accessibility-service xmlns:android="http://schemas.android.com/apk/res/android"
android:accessibilityFlags="flagRequestFilterKeyEvents"
android:canRequestFilterKeyEvents="true"
android:description="#string/app_name" />
<service
android:name=".KeySrvice"
android:exported="true"
android:label="#string/app_name"
android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE">
<meta-data
android:name="android.accessibilityservice"
android:resource="#xml/key" />
<intent-filter>
<action android:name="android.accessibilityservice.AccessibilityService" />
</intent-filter>
</service>
inline fun <reified T : Service> Context.hasAccessibility(): Boolean {
var enabled = 1
try {
enabled = Secure.getInt(contentResolver, Secure.ACCESSIBILITY_ENABLED)
} catch (ignored: Throwable) {
}
if (enabled == 1) {
val name = ComponentName(applicationContext, T::class.java).flattenToString()
val services = Secure.getString(contentResolver, Secure.ENABLED_ACCESSIBILITY_SERVICES)
return services?.contains(name) ?: false
}
return false
}
if (!hasAccessibility<KeyService>()) {
startActivity(Intent(Settings.ACTION_ACCESSIBILITY_SETTINGS))
}
Android doesn't document APIs on interacting with volume buttons in that case. So I guess the answer is no…
checkout Controlling Your App’s Volume and Playback ...This will help to solve your problem... multiple applications might want to listen for button presses from background, this may be the reason why KeyEvents can only be handled by Activities as they are the interface to the user pressing the keys.
Note: this only works for Activities, and not Services as the question states.
Depending on the context in which the callback is required an alternative solution might be available.
To be capable of detecting the volume button an Activity would need to override the dispatchKeyEvent function. For this to be present in multiple activities could could write a superclass containing the overridden function which is extended by all subsequent activities.
Here is the code for detecting Volume Up/Down key presses:
// Over-ride this function to define what should happen when keys are pressed (e.g. Home button, Back button, etc.)
#Override
public boolean dispatchKeyEvent(KeyEvent event)
{
if (event.getAction() == KeyEvent.ACTION_DOWN)
{
switch (event.getKeyCode())
{
case KeyEvent.KEYCODE_VOLUME_UP:
// Volume up key detected
// Do something
return true;
case KeyEvent.KEYCODE_VOLUME_DOWN:
// Volume down key detected
// Do something
return true;
}
}
return super.dispatchKeyEvent(event);
}
Been googling around this problem 10 years later, and I dug out this here:
https://stackoverflow.com/a/59064820
By providing an AccessibilityService, it is possible to listen to the volume buttons, outside the activity, even when the phone is locked, even before it is sent down to the specific apps (just tested it). Only downside: The user must activate this service manually in the settings.
MyService.java
public class MyService extends Service {
private BroadcastReceiver vReceiver;
#Override
public void onCreate() {
super.onCreate();
vReceiver = new BroadcastReceiver() {
#Override
public void onReceive(Context context, Intent intent) {
FileLog.e("Something just happens");
}
};
registerReceiver(vReceiver, new IntentFilter("android.media.VOLUME_CHANGED_ACTION"));
}
#Nullable
#Override
public IBinder onBind(Intent intent) {
return null;
}
#Override
public void onDestroy() {
unregisterReceiver(vReceiver);
}
}
AndroidManifest.xml
<application>
...
<service android:name=".MyService" android:exported="true"/>
</application>
onCreate || onStartActivity
public void onCreate(){
....
startService(new Intent(this, MyService.class));
}
that solution like little bit same as Answer
but working on Android 33+