How do you detect a user pressing the call button on a connected Bluetooth Headset?
While multiple posts address the topic I think I have tried most (if not all) and still have not found a solution which works
The following simplified code connects to the Bluetooth headset in onResume()
It then logs a message if ANY intent is received (which it never does)
class MainActivity : AppCompatActivity() {
private lateinit var audioManager : AudioManager
private var headsetDevice: AudioDeviceInfo? = null // device used for audio input
private lateinit var mediaSession: MediaSession
private lateinit var headsetButtonIntentReceiver: HeadsetButtonIntentReceiver
private lateinit var headsetButtonIntentFilter: IntentFilter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
window.addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON)
audioManager = getSystemService(Context.AUDIO_SERVICE) as AudioManager
mediaSession = MediaSession(this, packageName)
headsetButtonIntentFilter = IntentFilter(Intent.ACTION_CALL_BUTTON)
headsetButtonIntentReceiver = HeadsetButtonIntentReceiver()
}
class HeadsetButtonIntentReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
Log.d(TAG, "HeadsetButtonIntentReceiver() !!!!")
abortBroadcast()
}
}
override fun onResume() {
super.onResume()
val devices = audioManager.availableCommunicationDevices
Log.d(TAG, "audioManager.availableCommunicationDevices found ${devices.size} devices")
headsetDevice = devices.firstOrNull { it.type == AudioDeviceInfo.TYPE_BLUETOOTH_SCO }
if (headsetDevice != null) {
Log.d(TAG, "setCommunicationsDevice ${headsetDevice!!.productName}")
if (!audioManager.setCommunicationDevice(headsetDevice!!)) Log.d(TAG, "Failed to connect to headset")
}
mediaSession.isActive = true
this.registerReceiver(headsetButtonIntentReceiver, headsetButtonIntentFilter)
}
override fun onStop() {
super.onStop()
if (headsetDevice != null)
audioManager.clearCommunicationDevice() // disconnect from the headset
mediaSession.release()
mediaSession.isActive = false
this.unregisterReceiver(headsetButtonIntentReceiver)
}
companion object {
const val TAG = "MediaButton"
}
}
I have tried adding different Button actions to the intent filter - also without success
headsetButtonIntentFilter = IntentFilter()
headsetButtonIntentFilter.addAction(Intent.ACTION_MEDIA_BUTTON)
headsetButtonIntentFilter.addAction(Intent.ACTION_CALL_BUTTON)
I have tried adding the intent into the Manifest (although several posts suggest this is not required)
<intent-filter>
<action android:name="android.intent.action.CALL_BUTTON" />
</intent-filter>
I have tried specifying the receiver in the Manifest - but also nothing
<receiver android:name=".MainActivity$HeadsetButtonIntentReceiver" android:exported="false">
<intent-filter>
<action android:name="android.intent.action.CALL_BUTTON"/>
</intent-filter>
</receiver>
For completeness I even tried overriding the onKeyDown() and dispatchKeyEvent() but these are also not being triggered
override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean {
Log.d(TAG, "onKeyDown() KEYCODE $keyCode Event ${event?.action}")
return super.onKeyDown(keyCode, event)
}
override fun dispatchKeyEvent(event: KeyEvent?): Boolean {
Log.d(TAG, "dispatchKeyEvent() KEYCODE ${event?.keyCode} Event ${event?.action}")
return super.dispatchKeyEvent(event)
}
Frustratingly there are indications the code is close to working. Specifically a double press of the headset call button dials the previous number but when the above code is running that no longer occurs indicating the App is consuming the call button press. Unfortunately the App is not being notified of this action!
Happy to try solutions posted in previous questions I may have missed!
Related
I want to receive an Android Shutdown Event in my app to revert a config change when I start the app. For that, I have register a context registered receiver in my main Activity onCreate as shown below
override fun onCreate(..)
{
...
val bootIntentFilter = IntentFilter(Intent.ACTION_SHUTDOWN);
registerReceiver(shutDownReceiver, bootIntentFilter)
}
private val shutDownReceiver = object : BroadcastReceiver()
{
override fun onReceive(context: Context?, intent: Intent?) {
if(Intent.ACTION_SHUTDOWN == intent?.action)
{
Log.e(TAG, "Shutdown Called")
//Run your logic
}
}
}
and I also added the following permission in manifest.xml
I still don't receive the broadcast is there anything I am missing? target API is 29
Any information to help is really appreciated.
Thanks,
Asma
I have a started service app. It intent to activity from another app, but still running in foreground. After a button click in that activity, I want to send data (for example a string "potato") to service without startService() in order to continue, not restart. That's how service keeps running till get the data, while(requiredData != "potato"){}.start. How can I send it, or return response ? I think to use Messenger or Broadcast, but I'm not sure it fits well and how to do.
Note: Service App connected to an activity from another app.
Service App
class RegistryService : Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val i = packageManager.getLaunchIntentForPackage("com.myexample.potatoactivity")
if (i!=null) {
i.flags = Intent.FLAG_ACTIVITY_NEW_TASK
startActivity(i)
} else {
Toast.makeText(this,"Fail",Toast.LENGTH_SHORT).show()
}
while (true) { // requiredData != "potato"
//Log.d("MyService", "Wait for potato")
}
return START_STICKY
}
}
Potato Activity
class PotatoActivity : AppCompatActivity() {
private lateinit var binding: ActivityPotatoBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityPotatoBinding.inflate(layoutInflater)
val view = binding.root
setContentView(view)
binding.buttonSendData.setOnClickListener {
//it.putExtra("REQUIRED_DATA", "potato")
}
}
}
When registering a BroadcastReceiver with the IntentFilter Intent.ACTION_CLOSE_SYSTEM_DIALOGS, the homekey pressed event is received twice, however the other events like recentapps is received only once. I'm not registering any receivers in the AndroidManifest.xml file.
Following is the code snippet I'm trying to use:
MainActivity.kt
private const val HOME_KEY = "homekey"
private const val RECENT_APPS = "recentapps"
private const val REASON = "reason"
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.press_me).setOnClickListener {
registerReceiver(
navigationEventReceiver,
IntentFilter(Intent.ACTION_CLOSE_SYSTEM_DIALOGS)
)
}
}
private val navigationEventReceiver = object : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if (intent?.action != Intent.ACTION_CLOSE_SYSTEM_DIALOGS) return
val pressedKey = intent.getStringExtra(REASON)
if (pressedKey == HOME_KEY) {
Log.d("Event ", "home key")
}
if (pressedKey == RECENT_APPS) {
Log.d("Event ", "recent apps")
}
}
}
}
The logs that get printed in the logcat after this are as follows:
When pressing home button
D/Event: home key
D/Event: home key
When pressing recent apps button
D/Event: recent apps
I was wondering how to make the music play automatically when starting the app and how to make it stop playing in the background when pressing the home button. Right now, it starts and stops by pressing the toggle button. I was also wondering if its possible to automatically switch to other music when going to another activity?
MainActivity.kt
private lateinit var player: MediaPlayer
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val secondActivity = findViewById<Button>(R.id.secondActivity)
secondActivity.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
startActivity(intent)
}
val toggle: ToggleButton = findViewById(R.id.toggleButton)
toggle.setOnCheckedChangeListener { _, isChecked ->
val svc = Intent(this, MusicService::class.java)
if (isChecked) {
startService(svc)
} else {
stopService(svc)
}
}
}
MusicService.kt
class MusicService : Service() {
private lateinit var player: MediaPlayer
override fun onBind(intent: Intent?): IBinder? {
TODO("Return the communication channel to the service.")
return null
}
override fun onCreate() {
super.onCreate()
player = MediaPlayer.create(this, R.raw.music)
player.setLooping(true)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
player.start()
return START_STICKY
}
override fun onDestroy() {
super.onDestroy()
player.stop()
}
}
Depending on your specific requirements, you should or shouldn't use a MediaService as part of your solution.
To be more clear, a Service should only be used if you want the audio to keep going whenever you're outside the app. This solution will usually be accompanied by a media Notification which you should populate with controls, image assets, etc. (Think of Spotify or SoundCloud) If this is the solution you're looking for, take a look at this doc page from Google and follow it through. Beware that this is a longer and tougher process to maintain.
On the other hand, if all you want to do is play music/sounds while your user is inside your app, then a simple
private lateinit var localMedia: MediaPlayer
override fun onCreate() {
...
localMedia = MediaPlayer.create(context, R.raw.your_audio_file)
}
override fun onResume() {
...
localMedia.start()
}
override fun onPause() {
...
localMedia.release()
}
Furthermore, if you want different audio files to be played on different Activities/Fragments, you might want to abstract the code I provided above into it's own Manager class or so and access it the same but changing the specific .mp3 file (or whatever format) as you see fit.
EDIT:
For a Manager class, you'll have to create your own functions and handle the MediaPlayer inside of it
private class MediaPlayerManager(private val context: Context) {
private lateinit var mediaPlayer: MediaPlayer
fun setupPlayer() {
mediaPlayer = MediaPlayer.create(context, R.raw.your_audio_file)
}
fun play() {
mediaPlayer.start()
}
fun stop() {
mediaPlayer.stop()
}
}
And call these functions from their respective lifecycle method inside your Activity/Fragment, depending on your specific needs
class YourActivity {
val mediaPlayerManager = MediaPlayerManager(context)
override onCreate() {
...
mediaPlayerManager.setupPlayer()
}
override fun onResume() {
...
mediaPlayerManager.play()
}
override fun onPause() {
...
mediaPlayerManager.stop()
}
}
I should add that I'm not necessarily providing a fully-fledged answer here, but a starting point for you to massage to your own needs. The Manager class is nothing but an abstraction of the concept I'm trying to communicate. Lastly, if you want to use a different audio resource file in another Activity/Fragment, you would have to create a method to re-assign the MediaPlayer object inside it with the appropriate file.
E.g.
fun setupPlayer(audioRes: Int) {
mediaPlayer = MediaPlayer.create(context, audioRes)
}
I have done this before
create a Class and named it (for example I named it C) and extend that from Application like following (don't forget put android:name=".C" in <application> tag in manifest.xml):
class C:Application() {
fun onCreate() {
super.onCreate()
context = getApplicationContext()
app = this
}
companion object {
private val context:Context
var currentActivity:Activity
var currentActivities:ArrayList<Activity> = ArrayList()
var handler:Handler
var app:C
fun get():C {
return app
}
fun getContext():Context {
if (currentActivity != null)
{
return currentActivity
}
return context
}
}
}
I created a class for parent of AppCompatActivity and (named UAppCompactActivity)extend it form AppCompatActivity then extend all activities from UAppCompactActivity:
abstract class UAppCompatActivity:AppCompatActivity() {
fun onCreate(#Nullable savedInstanceState:Bundle, #Nullable persistentState:PersistableBundle) {
super.onCreate(savedInstanceState, persistentState)
}
protected fun onResume() {
super.onResume()
C.setCurrentActivity(this)
C.currentActivities.add(this)
/* you can play your music here or do any action you desired */
}
protected fun onPause() {
super.onPause()
C.currentActivities.remove(this)
/*
you can stop your music here or do any action you desired
if (UBase.currentActivities.size() === 0)
G.backgroundMusics.get(G.app.musicNumberBackAndNowPlay).pause()
else
play music or switch to new music
*/
}
}
I am building an app using the NotificationListenerService. But always when I run the app in debug mode the Service is not started. I reduced my code to the following:
My Acticity:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val intent = Intent("android.settings.ACTION_NOTIFICATION_LISTENER_SETTINGS")
startActivity(intent)
}
override fun onResume() {
super.onResume()
val isServiceRunning = isMyServiceRunning(NLService::class.java)
Log.i("MainActivity", "service running: " + isServiceRunning)
}
private fun isMyServiceRunning(serviceClass: Class<*>): Boolean {
val manager = getSystemService(Context.ACTIVITY_SERVICE) as ActivityManager
for (service in manager.getRunningServices(Integer.MAX_VALUE)) {
if (serviceClass.name == service.service.className) {
return true
}
}
return false
}
}
The Service:
class NLService : NotificationListenerService() {
private val TAG: String? = "NLService"
override fun onBind(intent: Intent): IBinder? {
Log.i(TAG, "onBind()")
return super.onBind(intent)
}
override fun onCreate() {
super.onCreate()
Log.i(TAG, "onCreate()")
}
override fun onDestroy() {
super.onDestroy()
}
override fun onNotificationPosted(sbn: StatusBarNotification?) {
Log.i(TAG, "onNotificationPosted() sbn: $sbn")
super.onNotificationPosted(sbn)
}
}
Of course I added this in manifest:
<service
android:name=".NLService"
android:label="MyNLService"
android:permission="android.permission.BIND_NOTIFICATION_LISTENER_SERVICE">
<intent-filter>
<action android:name="android.service.notification.NotificationListenerService" />
</intent-filter>
</service>
When starting the app in debug mode then I always get the log output service running: false in onResume. The value is true when starting normally, without debug. What is going wrong and how to fix that?
Okay, finally I don't have a complete solution but a kind of improvement which is close to a working solution.
So what are actually the technical problems in my original app code? And how did I solve them?
I made some initialization in NLService class' onConnect() method. I moved all this initialization to onListenerConnected(), adding a handler.postDelayed(runnable, 500);.
I created an object of a class (Let's call it MyHandlerClass) within the NLService class and passed a reference to the Service into it. This is not a good solution because Android documentation says something about many methods within the NotificationListenerService:
The service should wait for the onListenerConnected() event before performing this operation.
So in MyHandlerClass I called a nlService.getActiveNotifications(). This call was made maybe before Android called NLServices' onListenerConnected. So I made wrappers for methods inside NLService, like e.g.:
fun getActiveNotifications(): Array<StatusBarNotification>?
{
return if (isConnected)
{
super.getActiveNotifications()
}
else
{
null
}
}
And I toggled my boolean variable isConnected within onListenerConnected()and onListenerDisconnected()
Now the service still crashes when running app in debug mode. But running in normal mode the amount of crashes could be reduced by the described improvements.