I need to intercept the events of a outgoing call made by the device framework.
Following the android guide, i'm stopped at point 3 The telecom subsystem binds to your app's ConnectionService implementation., that is i have come to this point:
Call flow
val telecomManager :TelecomManager= getSystemService(
TELECOM_SERVICE
) as TelecomManager
if (ContextCompat.checkSelfPermission(this, Manifest.permission.CALL_PHONE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(this, arrayOf(Manifest.permission.CALL_PHONE),
2333)
} else {
try {
val phoneAccountHandle = PhoneAccountHandle(ComponentName(
applicationContext,
MyConnectionService::class.java
), "ID999")
telecomManager.registerPhoneAccount(PhoneAccount.builder(
phoneAccountHandle,
"label"
).setCapabilities(PhoneAccount.CAPABILITY_CONNECTION_MANAGER) .build())
val extras = Bundle()
extras.putParcelable(TelecomManager.EXTRA_PHONE_ACCOUNT_HANDLE, phoneAccountHandle)
telecomManager.placeCall(Uri.parse("tel:$phoneNumber"), extras)
} catch (e: SecurityException) {
e.printStackTrace()
}
}
ConnectionService
class MyConnectionService : ConnectionService() {
private val TAG = "mycnnser"
override fun onCreate() {
super.onCreate()
Log.d(TAG, "onCreate: ")
}
override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
Log.d(TAG, "onStartCommand: ")
return super.onStartCommand(intent, flags, startId)
}
override fun onCreateIncomingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingConnection: ")
return super.onCreateIncomingConnection(connectionManagerPhoneAccount, request)
}
override fun onCreateIncomingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateIncomingConnectionFailed: ")
super.onCreateIncomingConnectionFailed(connectionManagerPhoneAccount, request)
}
override fun onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest) {
Log.d(TAG, "onCreateOutgoingConnectionFailed: ")
super.onCreateOutgoingConnectionFailed(connectionManagerPhoneAccount, request)
}
override fun onCreateOutgoingConnection(connectionManagerPhoneAccount: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingConnection: ")
return super.onCreateOutgoingConnection(connectionManagerPhoneAccount, request)
}
override fun onCreateOutgoingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateOutgoingHandoverConnection: ")
return super.onCreateOutgoingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onCreateIncomingHandoverConnection(fromPhoneAccountHandle: PhoneAccountHandle, request: ConnectionRequest): Connection {
Log.d(TAG, "onCreateIncomingHandoverConnection: ")
return super.onCreateIncomingHandoverConnection(fromPhoneAccountHandle, request)
}
override fun onHandoverFailed(request: ConnectionRequest, error: Int) {
super.onHandoverFailed(request, error)
Log.d(TAG, "onHandoverFailed: ")
}
override fun onConference(connection1: Connection, connection2: Connection) {
super.onConference(connection1, connection2)
Log.d(TAG, "onConference: ")
}
override fun onRemoteConferenceAdded(conference: RemoteConference) {
super.onRemoteConferenceAdded(conference)
Log.d(TAG, "onRemoteConferenceAdded: ")
}
override fun onRemoteExistingConnectionAdded(connection: RemoteConnection) {
super.onRemoteExistingConnectionAdded(connection)
Log.d(TAG, "onRemoteExistingConnectionAdded: ")
}
override fun onConnectionServiceFocusLost() {
super.onConnectionServiceFocusLost()
Log.d(TAG, "onConnectionServiceFocusLost: ")
}
override fun onConnectionServiceFocusGained() {
super.onConnectionServiceFocusGained()
Log.d(TAG, "onConnectionServiceFocusGained: ")
}}
Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.thorny.myapplication">
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS"/>
<uses-permission android:name="android.permission.READ_CALL_LOG"/>
<uses-permission android:name="android.permission.READ_PHONE_STATE"
android:maxSdkVersion="29"/>
<uses-permission android:name="android.permissions.READ_PHONE_NUMBERS"/>
<uses-permission
android:name="android.permission.CALL_PHONE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.MyApplication">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MyConnectionService"
android:permission="android.permission.BIND_TELECOM_CONNECTION_SERVICE">
<intent-filter>
<action android:name="android.telecom.ConnectionService" />
</intent-filter>
</service>
</application>
</manifest>
Problem: the call starts through the device framework but all the service logs are never triggered.
My Android Version is 10.
Thanks
Related
I have read multiple threads on this issue but none of them solved my problem, so I gave up and decided to write this.
I have a service (right now it's a background service but I am going to turn it into a foreground service) which monitors the battery level so it can notify the user when it reaches a certain percentage.
abstract class MonitoringService : Service() {
private var maxPercentage: Int = -1
private var thread: Thread? = null
private lateinit var batteryManager: BatteryManager
override fun onBind(intent: Intent?): IBinder? {
return null
}
override fun onCreate() {
batteryManager = getSystemService(BATTERY_SERVICE) as BatteryManager
Log.d(null, "created service")
}
override fun onDestroy() {
thread?.interrupt()
updateServiceState(false)
Log.d(null, "destroyed service")
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
println("started service")
if (intent == null) {
stopSelf(startId)
updateServiceState(false)
report(applicationContext, "service started with intent null")
return START_NOT_STICKY
}
when (intent.action) {
ACTION_START_SERVICE -> {
// if a thread is already active interrupt it
thread?.interrupt()
if (updateMaxPercentage(intent, startId) == 1) return START_NOT_STICKY
thread = Thread(Runnable {
while (true) {
if (batteryManager.isCharging && batteryManager.getIntProperty(
BatteryManager.BATTERY_PROPERTY_CAPACITY
) == maxPercentage
) {
Log.d(null, "time to do it")
}
Log.d(null, "time to not do it")
try {
// sleep for 1 minute
Thread.sleep(60000)
} catch (exception: InterruptedException) {
stopSelf(startId)
updateServiceState(false)
return#Runnable
}
}
})
thread?.start()
updateServiceState(true)
}
ACTION_STOP_SERVICE -> {
thread?.interrupt()
stopSelf(startId)
updateServiceState(false)
return START_NOT_STICKY
}
ACTION_UPDATE_SERVICE -> if (updateMaxPercentage(intent, startId) == 1) return START_NOT_STICKY
else -> {
stopSelf(startId)
updateServiceState(false)
report(applicationContext, "service started with action null")
return START_NOT_STICKY
}
}
return START_REDELIVER_INTENT
}
}
I start the service like so from my activity
private fun start() {
val serviceIntent = Intent(applicationContext, MonitoringService::class.java)
serviceIntent.action = ACTION_START_SERVICE
serviceIntent.putExtra("maxPercentage", maxPercentage)
println(startService(intent))
Log.d(null, "startService")
}
This is my AndroidManifest.xml
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.segv.batconv">
<application
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:theme="#style/Theme.Batconv"
tools:targetApi="31">
<service
android:name=".MonitoringService"
android:enabled="true"/>
<activity
android:name=".MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>
I am testing this on an Android 11 device. The logs show no errors but the service doesn't start.
Thanks.
Remove the abstract keyword from your MonitoringService declaration:
class MonitoringService : Service()
By declaring it abstract, you are saying that nothing can create an instance of that class.
I`m trying to build a service that detects if the screen of a device is locked/unlocked (which I will later user as a native module in React). However, it seems like my service is not starting, and I don't receive the expected logs. Where is my mistake? (It's my first time dealing with native android & Kotlin, so apologies if this is a dumb question, and duplicates were related to java code)..
I have defined a Broadcast Receiver for each event here:
ScreenOnReceiver.kt
class screenOnReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent!!.action == Intent.ACTION_SCREEN_ON) {
val screenOff = false
val i = Intent(context, PowerButtonService::class.java)
i.putExtra("screenState", screenOff)
context!!.startService(i)
}
}
}
ScreenOffReceiver.kt
class screenOffReceiver: BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent?) {
if(intent!!.action == Intent.ACTION_SCREEN_ON) {
val screenOff = false
val i = Intent(context, PowerButtonService::class.java)
i.putExtra("screenState", screenOff)
context!!.startService(i)
}
}
}
ScreenChangeService.kt:
class ScreenChangeService: Service() {
override fun onBind(p0: Intent?): IBinder? = null
override fun onCreate() {
val screenOnReceiver = screenONReceiver()
val screenOnFilter = IntentFilter(Intent.ACTION_SCREEN_ON)
registerReceiver(screenOnReceiver, screenOnFilter)
val screenOffReceiver = screenOffReceiver()
val screenOffFilter = IntentFilter(Intent.ACTION_SCREEN_OFF)
registerReceiver(screenOffreceiver, screenOffFilter)
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
val screenState = intent!!.getBooleanExtra("screenState", false)
if (screenState == true) {
Log.d("TAG", "Screen On")
} else {
Log.d("TAG", "Screen Off")
}
return START_NOT_STICKY
}
}
Manifest.xml
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/Theme.Test">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".PowerButtonService"/>
</application>
I creating softphone application using Android SIP API (https://developer.android.com/guide/topics/connectivity/sip) and I have successfully registered to my Asterisk server and I can call another peer on another softphone. I can hear voice on both sides and everything works perfect.
Now, I have problem receiving call from another softphone. onRinging method is never executed. On the other softphone I am getting status RINGING, but in my app that call is never received.
I have created IncomingCallReceiver class and also WalkieTalkieActivity, but none of them is doing its job (most probably I am doing something wrong, or I did not initiate some of them, or call or whatever...).
I am stuck on receiving the calls. Also the official documentation is not clear to me.
If there is anyone here to help me and guide me to the solution, I would be very grateful.
Here is my MainActivity class:
class MainActivity: FlutterActivity() {
var mSipProfile: SipProfile? = null
var username: String? = "..." //I have it correct in my code
var password: String? = "..." //I have it correct in my code
var domain: String? = "..." //I have it correct in my code
var builder: SipProfile.Builder? = SipProfile.Builder(username, domain).setPassword(password)
private val CHANNEL = "samples.flutter.dev/registration"
private val mSipManager: SipManager? by lazy(LazyThreadSafetyMode.NONE) {
SipManager.newInstance(this)
}
override fun onStart() {
super.onStart()
println("SSSSSTTTTTTTTAAAAAAAAAAARRRRRRRRRRRRRTTTTTTTTTTTTTTTTTTT")
}
override fun onCreate(savedInstanceState: Bundle?, persistentState: PersistableBundle?) {
super.onCreate(savedInstanceState, persistentState)
println("CCCCCCCCRRRRRREEEEEEEEEAAAAAAATTTTTTTTEEEEEEEEEEEEEEEEEEEEEE")
}
private var listenSipSession: SipSession.Listener = object : SipSession.Listener() {
override fun onRinging(session: SipSession?, caller: SipProfile?, sessionDescription: String?) {
super.onRinging(session, caller, sessionDescription)
println("EVVVVVVVOOOOOOOOOOOOOOOO GAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA")
}
}
private var listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {
override fun onCalling(call: SipAudioCall?) {
super.onCalling(call)
println("111111 CALLING CALLING CALLING")
}
override fun onChanged(call: SipAudioCall?) {
super.onChanged(call)
println("CHANGED")
println(call?.state)
}
override fun onError(call: SipAudioCall?, errorCode: Int, errorMessage: String?) {
super.onError(call, errorCode, errorMessage)
println("ERROR ERROR ERROR")
}
override fun onRingingBack(call: SipAudioCall?) {
super.onRingingBack(call)
println("RINGING BACK")
}
/*override fun onRinging(call: SipAudioCall, caller: SipProfile) {
println("RINGING")
try {
call.answerCall(30)
} catch (e: Exception) {
println("ERROR 2: $e")
e.printStackTrace()
}
}*/
override fun onCallEstablished(call: SipAudioCall) {
println("Call established")
call.apply {
startAudio()
setSpeakerMode(false)
// toggleMute()
}
}
override fun onRinging(call: SipAudioCall?, caller: SipProfile?) {
super.onRinging(call, caller)
println("Ringing")
}
override fun onCallEnded(call: SipAudioCall) {
println("Call ended " + call.state)
}
}
override fun onDestroy() {
super.onDestroy()
closeLocalProfile()
}
override fun configureFlutterEngine(#NonNull flutterEngine: FlutterEngine) {
super.configureFlutterEngine(flutterEngine)
MethodChannel(flutterEngine.dartExecutor.binaryMessenger, CHANNEL).setMethodCallHandler { call, result ->
// Note: this method is invoked on the main thread.
mSipProfile = builder?.build()
val intent = Intent("com.example.softphone.INCOMING_CALL")
val pendingIntent: PendingIntent = PendingIntent.getBroadcast(this, 0, intent, Intent.FILL_IN_DATA)
mSipManager?.open(mSipProfile, pendingIntent, null)
if (call.method == "register") {
val registrationStatus = register()
result.success(registrationStatus)
}
if (call.method == "call") {
call()
}
}
}
private fun closeLocalProfile() {
try {
mSipManager?.close(mSipProfile?.uriString)
println("Local profile closed.")
} catch (ee: Exception) {
println("WalkieTalkieActivity/onDestroy. Failed to close local profile.")
}
}
///Method for making call
private fun call(): String {
var status = "Unknown"
try {
println("Making call")
status = "In Call"
val call: SipAudioCall? = mSipManager?.makeAudioCall(
mSipProfile?.uriString,
"...", //I have it correct in my code
listener,
30
)
println(call)
} catch (e: SipException) {
e.printStackTrace()
println(e)
}
return status
}
///Method for registering user
private fun register(): String {
var status = "Unknown"
try {
mSipManager?.setRegistrationListener(mSipProfile?.uriString, object : SipRegistrationListener {
override fun onRegistering(localProfileUri: String) {
status = "Registering with SIP Server..."
println(status)
}
override fun onRegistrationDone(localProfileUri: String, expiryTime: Long) {
status = "Ready"
println(status)
}
override fun onRegistrationFailed(
localProfileUri: String,
errorCode: Int,
errorMessage: String
) {
status = "Registration failed. Please check settings."
println(status)
}
})
} catch (e: SipException) {
e.printStackTrace()
println(e)
}
return status
}
}
Here is my IncomingCallReceiver class:
class IncomingCallReceiver : BroadcastReceiver() {
override fun onReceive(context: Context, intent: Intent?) {
var incomingCall: SipAudioCall? = null
try {
val listener: SipAudioCall.Listener = object : SipAudioCall.Listener() {
override fun onRinging(call: SipAudioCall, caller: SipProfile) {
try {
call.answerCall(30)
} catch (e: Exception) {
e.printStackTrace()
}
}
}
val wtActivity: WalkieTalkieActivity = context as WalkieTalkieActivity
incomingCall = wtActivity.manager?.takeAudioCall(intent, listener)
incomingCall?.setListener(listener)
incomingCall?.answerCall(30)
incomingCall?.startAudio()
incomingCall?.setSpeakerMode(true)
if (incomingCall?.isMuted!!) {
incomingCall?.toggleMute()
}
} catch (e: Exception) {
incomingCall?.close()
}
}
}
Here is my WalkieTalkieActivity class:
class WalkieTalkieActivity : Activity(), OnTouchListener {
var sipAddress: String? = null
var manager: SipManager? = null
var me: SipProfile? = null
var call: SipAudioCall? = null
lateinit var callReceiver: IncomingCallReceiver
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val filter = IntentFilter().apply {
addAction("com.example.softphone.INCOMING_CALL")
}
callReceiver = IncomingCallReceiver()
this.registerReceiver(callReceiver, filter)
}
override fun onTouch(p0: View?, p1: MotionEvent?): Boolean {
TODO("Not yet implemented")
}
}
Here is my AndroidManifest file:
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.softphone">
<!-- io.flutter.app.FlutterApplication is an android.app.Application that
calls FlutterMain.startInitialization(this); in its onCreate method.
In most cases you can leave this as-is, but you if you want to provide
additional functionality it is fine to subclass or reimplement
FlutterApplication and put your custom class here. -->
<uses-permission android:name="android.permission.USE_SIP" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.RECORD_AUDIO" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
<uses-permission android:name="android.permission.WAKE_LOCK" />
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-feature android:name="android.software.sip.voip" android:required="true" />
<uses-feature android:name="android.hardware.wifi" android:required="true" />
<uses-feature android:name="android.hardware.microphone" android:required="true" />
<application
android:name="io.flutter.app.FlutterApplication"
android:label="softphone"
android:icon="#mipmap/ic_launcher">
<receiver android:name=".IncomingCallReceiver" android:label="Call Receiver" />
<activity
android:name=".MainActivity"
android:launchMode="singleTop"
android:theme="#style/LaunchTheme"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|smallestScreenSize|locale|layoutDirection|fontScale|screenLayout|density|uiMode"
android:hardwareAccelerated="true"
android:windowSoftInputMode="adjustResize">
<!-- Specifies an Android theme to apply to this Activity as soon as
the Android process has started. This theme is visible to the user
while the Flutter UI initializes. After that, this theme continues
to determine the Window background behind the Flutter UI. -->
<meta-data
android:name="io.flutter.embedding.android.NormalTheme"
android:resource="#style/NormalTheme"
/>
<!-- Displays an Android View that continues showing the launch screen
Drawable until Flutter paints its first frame, then this splash
screen fades out. A splash screen is useful to avoid any visual
gap between the end of Android's launch screen and the painting of
Flutter's first frame. -->
<meta-data
android:name="io.flutter.embedding.android.SplashScreenDrawable"
android:resource="#drawable/launch_background"
/>
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
<!-- Don't delete the meta-data below.
This is used by the Flutter tool to generate GeneratedPluginRegistrant.java -->
<meta-data
android:name="flutterEmbedding"
android:value="2" />
</application>
</manifest>
Thank you all in advance!
Here is my fragment -
#SuppressLint("HardwareIds")
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
mPresenter = VerifyOtpPresenter(this)
androidId = Settings.Secure.getString(activity?.contentResolver, Settings.Secure.ANDROID_ID)
mPresenter.requestOtp(phoneNumber)
initClickAndTextListeners()
initOtpCountdownTimer()
}
/**
* Requesting OTP password for our phone number
*/
override fun requestOtp(phoneNumber: Long) {
OtpNetworking.requestOtp(phoneNumber, object : OtpNetworking.RequestOtpCallback {
override fun onSuccess() {
Toast.makeText(context, getString(R.string.verify_otp_fragment_sms_arrived), Toast.LENGTH_SHORT).show()
startSmsRetriever()
}
override fun onError(reason: String) {
Toast.makeText(context, reason, Toast.LENGTH_SHORT).show()
}
})
}
private fun startSmsRetriever() {
val client = SmsRetriever.getClient(context!!)
val task = client.startSmsRetriever()
task.addOnSuccessListener {
Toast.makeText(context, "Successfully added retriever", Toast.LENGTH_SHORT).show()
}
task.addOnFailureListener {
Toast.makeText(context, "Failed to get SMS", Toast.LENGTH_SHORT).show()
}
}
here is my OtpBroadcastReceiver -
class OtpBroadcastReceiver : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
Toast.makeText(context, "onReceive", Toast.LENGTH_SHORT).show()
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status: Status? = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
val message: String? = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
Toast.makeText(context, message, Toast.LENGTH_SHORT).show()
}
CommonStatusCodes.TIMEOUT -> {
Toast.makeText(context, "Timeout", Toast.LENGTH_SHORT).show()
}
}
}
}
}
and my manifest file -
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_SMS"/>
<uses-permission android:name="android.permission.RECEIVE_SMS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="false"
android:theme="#style/AppTheme">
<activity android:name=".startup.StartupActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<receiver android:name=".otp.service.OtpBroadcastReceiver" android:exported="true"
android:permission="com.google.android.gms.auth.api.phone.permission.SEND">
<intent-filter>
<action android:name="com.google.android.gms.auth.api.phone.SMS_RETRIEVED"/>
</intent-filter>
</receiver>
<meta-data
android:name="preloaded_fonts"
android:resource="#array/preloaded_fonts" />
</application>
I can't seem to get any information from my broadcast receiver, eventhough the toast message of the sms retriever does say Successfully added retriever
I think I am missing the connection between the fragment and the broadcast receiver but I am not sure - does anyone have an idea what I am missing?
You could try to pass some onOtpReceived function into your OtpBroadcastReceiver and see if that helps.
class OtpBroadcastReceiver(onOtpReceived: (String) -> Unit, onOtpTimeout: () -> Unit) : BroadcastReceiver() {
override fun onReceive(context: Context?, intent: Intent) {
if (SmsRetriever.SMS_RETRIEVED_ACTION == intent.action) {
val extras = intent.extras
val status: Status? = extras!![SmsRetriever.EXTRA_STATUS] as Status?
when (status?.statusCode) {
CommonStatusCodes.SUCCESS -> {
val message: String? = extras[SmsRetriever.EXTRA_SMS_MESSAGE] as String?
onOtpReceived(message)
}
CommonStatusCodes.TIMEOUT -> {
onOtpTimeout()
}
}
}
}
}
I'm trying to receive media button events from Wired/Bluetooth headsets
I'm receiving media button events on onMediaButtonEvent(mediaButtonEvent: Intent?) method in MediaSessionCallback
but nothing happens to the music playback.
Here is my MediaPlayerService
private const val LOG_TAG = "LOG_TAG"
private const val MY_EMPTY_MEDIA_ROOT_ID = "empty_root_id"
private const val AUDIO_URL_1 = "https://www.listennotes.com/e/p/94051189e660408b861be9ee28f17f06/"
class MediaPlaybackService : MediaBrowserServiceCompat() {
private val TAG = "MediaPlaybackService"
private lateinit var context: Context
private lateinit var mediaSession: MediaSessionCompat
private lateinit var stateBuilder: PlaybackStateCompat.Builder
private lateinit var exoPlayer: SimpleExoPlayer
private lateinit var dataSourceFactory: DefaultDataSourceFactory
private val audioAttributes = AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_MUSIC)
.setUsage(C.USAGE_MEDIA)
.build()
override fun onCreate() {
super.onCreate()
context = this
initExoPlayer()
initDataSourceFactory()
initMediaSession()
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
Log.i(TAG, "onStartCommand ${intent?.getParcelableExtra<KeyEvent>
(Intent.EXTRA_KEY_EVENT)?.keyCode}")
MediaButtonReceiver.handleIntent(mediaSession, intent)
return super.onStartCommand(intent, flags, startId)
}
private fun initExoPlayer() {
exoPlayer = ExoPlayerFactory.newSimpleInstance(context)
exoPlayer.setAudioAttributes(audioAttributes, true)
}
private fun initDataSourceFactory() {
val httpDataSourceFactory = DefaultHttpDataSourceFactory(
Util.getUserAgent(context, "media-player"),
null,
DefaultHttpDataSource.DEFAULT_CONNECT_TIMEOUT_MILLIS,
DefaultHttpDataSource.DEFAULT_READ_TIMEOUT_MILLIS,
true
)
dataSourceFactory = DefaultDataSourceFactory(context, null, httpDataSourceFactory)
}
private fun initMediaSession() {
mediaSession = MediaSessionCompat(context, LOG_TAG).apply {
setFlags(
MediaSessionCompat.FLAG_HANDLES_MEDIA_BUTTONS
or MediaSessionCompat.FLAG_HANDLES_TRANSPORT_CONTROLS
)
stateBuilder = PlaybackStateCompat.Builder()
.setActions(
PlaybackStateCompat.ACTION_PLAY_PAUSE
or PlaybackStateCompat.ACTION_PLAY
or PlaybackStateCompat.ACTION_PAUSE
or PlaybackStateCompat.ACTION_SKIP_TO_NEXT
or PlaybackStateCompat.ACTION_SKIP_TO_PREVIOUS
)
setPlaybackState(stateBuilder.build())
setCallback(mediaSessionCallback())
setSessionToken(sessionToken)
isActive = true
}
}
private fun mediaSessionCallback() = object : MediaSessionCompat.Callback() {
override fun onPlay() {
super.onPlay()
play()
}
override fun onPause() {
super.onPause()
pause()
}
override fun onSkipToNext() {
super.onSkipToNext()
skipToNext()
}
override fun onSkipToPrevious() {
super.onSkipToPrevious()
skipToPrevious()
}
override fun onMediaButtonEvent(mediaButtonEvent: Intent?): Boolean {
Log.i(TAG, "MediaButtonEvent: ${mediaButtonEvent?.getParcelableExtra<KeyEvent>
(Intent.EXTRA_KEY_EVENT)?.keyCode}")
return super.onMediaButtonEvent(mediaButtonEvent)
}
}
fun play() {
Log.i(TAG, "Playback State: Playing")
if (mediaSession.controller.playbackState.state != PlaybackStateCompat.STATE_PAUSED) {
val mediaSource = getMediaSource(AUDIO_URL_1)
exoPlayer.prepare(mediaSource)
}
setPlaybackState(PlaybackStateCompat.STATE_PLAYING)
exoPlayer.playWhenReady = true
setMediaMetadata(title = "Episode 131: Bourne Wild")
}
fun pause() {
Log.i(TAG, "Playback State: Paused")
setPlaybackState(PlaybackStateCompat.STATE_PAUSED)
exoPlayer.playWhenReady = false
}
fun skipToNext() {
if (exoPlayer.hasNext()) {
Log.i(TAG, "ExoPLayer: Skip to Next")
exoPlayer.next()
}
}
fun skipToPrevious() {
if (exoPlayer.hasPrevious()) {
Log.i(TAG, "ExoPLayer: Skip to Previous")
exoPlayer.previous()
}
}
override fun onLoadChildren(parentId: String, result:
Result<MutableList<MediaBrowserCompat.MediaItem>>) {
result.sendResult(null)
}
override fun onGetRoot(lientPackageName: String, clientUid: Int,
rootHints: Bundle?): BrowserRoot? {
return BrowserRoot(MY_EMPTY_MEDIA_ROOT_ID, null)
}
override fun onDestroy() {
exoPlayer.release()
mediaSession.run {
isActive = false
release()
}
super.onDestroy()
}
}
Here is my Manifest
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.example.media_player">
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<application
android:allowBackup="true"
android:icon="#mipmap/ic_launcher"
android:label="#string/app_name"
android:usesCleartextTraffic="true"
android:roundIcon="#mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="#style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".MediaPlaybackService">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</service>
<receiver android:name="androidx.media.session.MediaButtonReceiver">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</receiver>
</application>
</manifest>
It looks like I'm missing something, after many days of struggling, I couldn't find what's wrong.
Any Help will be very much appreciated
Thanks