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!
Related
I'm trying to implement the Android Media3 MediaSessionService and MediaController but for some reason the playback doesn't start. What am I doing wrong? I think I did everything exactly as described in Play media in the background.
PlaybackService.kt
class PlaybackService : MediaSessionService() {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
mediaSession
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
mediaSession = null
}
super.onDestroy()
}
}
MainActivity.kt
class MainActivity : ComponentActivity() {
private lateinit var controllerFuture: ListenableFuture<MediaController>
private lateinit var controller: MediaController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
log("onCreate MainActivity")
setContent {
TestMediaTheme {
Box(contentAlignment = Alignment.Center, modifier = Modifier.fillMaxSize()) {
Button(onClick = {
//val url = "android.resource://$packageName/${R.raw.test}"
val url = "https://download.samplelib.com/mp3/sample-15s.mp3"
play(url)
}) {
Text(text = "Play")
}
}
}
}
}
override fun onStart() {
super.onStart()
val sessionToken = SessionToken(this, ComponentName(this, PlaybackService::class.java))
controllerFuture = MediaController.Builder(this, sessionToken).buildAsync()
controllerFuture.addListener(
{
controller = controllerFuture.get()
initController()
},
MoreExecutors.directExecutor()
)
}
override fun onStop() {
MediaController.releaseFuture(controllerFuture)
super.onStop()
}
private fun initController() {
//controller.playWhenReady = true
controller.addListener(object : Player.Listener {
override fun onMediaMetadataChanged(mediaMetadata: MediaMetadata) {
super.onMediaMetadataChanged(mediaMetadata)
log("onMediaMetadataChanged=$mediaMetadata")
}
override fun onIsPlayingChanged(isPlaying: Boolean) {
super.onIsPlayingChanged(isPlaying)
log("onIsPlayingChanged=$isPlaying")
}
override fun onPlaybackStateChanged(playbackState: Int) {
super.onPlaybackStateChanged(playbackState)
log("onPlaybackStateChanged=${getStateName(playbackState)}")
}
override fun onPlayerError(error: PlaybackException) {
super.onPlayerError(error)
log("onPlayerError=${error.stackTraceToString()}")
}
override fun onPlayerErrorChanged(error: PlaybackException?) {
super.onPlayerErrorChanged(error)
log("onPlayerErrorChanged=${error?.stackTraceToString()}")
}
})
log("start=${getStateName(controller.playbackState)}")
log("COMMAND_PREPARE=${controller.isCommandAvailable(COMMAND_PREPARE)}")
log("COMMAND_SET_MEDIA_ITEM=${controller.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)}")
log("COMMAND_PLAY_PAUSE=${controller.isCommandAvailable(COMMAND_PLAY_PAUSE)}")
}
private fun play(url: String) {
log("play($url)")
log("before=${getStateName(controller.playbackState)}")
controller.setMediaItem(MediaItem.fromUri(url))
controller.prepare()
controller.play()
log("after=${getStateName(controller.playbackState)}")
}
private fun getStateName(i: Int): String? {
return when (i) {
1 -> "STATE_IDLE"
2 -> "STATE_BUFFERING"
3 -> "STATE_READY"
4 -> "STATE_ENDED"
else -> null
}
}
private fun log(message: String) {
Log.e("=====[TestMedia]=====", message)
}
}
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">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.INTERNET" />
<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.TestMedia"
tools:targetApi="33">
<activity
android:name=".MainActivity"
android:exported="true"
android:theme="#style/Theme.TestMedia">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
<meta-data
android:name="android.app.lib_name"
android:value="" />
</activity>
<service
android:name=".PlaybackService"
android:exported="true"
android:foregroundServiceType="mediaPlayback">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService" />
<action android:name="android.media.browse.MediaBrowserService" />
</intent-filter>
</service>
</application>
</manifest>
And here's the debug log:
01:51:22.004 E onCreate MainActivity
01:51:22.544 E start=STATE_IDLE
01:51:22.544 E COMMAND_PREPARE=true
01:51:22.544 E COMMAND_SET_MEDIA_ITEM=true
01:51:22.544 E COMMAND_PLAY_PAUSE=true
//click 1
01:51:24.027 E play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:24.027 E before=STATE_IDLE
01:51:24.029 E onPlaybackStateChanged=STATE_BUFFERING
01:51:24.029 E after=STATE_BUFFERING
01:51:24.053 E onPlaybackStateChanged=STATE_ENDED
//click 2
01:51:25.715 E play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:25.715 E before=STATE_ENDED
01:51:25.716 E onPlaybackStateChanged=STATE_BUFFERING
01:51:25.716 E after=STATE_BUFFERING
//click 3
01:51:26.749 E play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:26.749 E before=STATE_BUFFERING
01:51:26.750 E after=STATE_BUFFERING
//click 4
01:51:30.172 E play(https://download.samplelib.com/mp3/sample-15s.mp3)
01:51:30.172 E before=STATE_BUFFERING
01:51:30.173 E after=STATE_BUFFERING
So it looks as if after the first click the player buffers and then immediately ends, and after the second click it just buffers indefinitely. Anyone got an idea what might be the problem?
Finally I found the solution thanks to this issue and this question. It seems like the Media3 Guide is missing a very crucial part.
From the onAddMediaItems documentation: Note that the requested media items don't have a MediaItem.LocalConfiguration (for example, a URI) and need to be updated to make them playable by the underlying Player.
In the end I solved it by overriding MediaSession.Callback.onAddMediaItems
class PlaybackService : MediaSessionService(), MediaSession.Callback {
private var mediaSession: MediaSession? = null
override fun onCreate() {
super.onCreate()
val player = ExoPlayer.Builder(this).build()
mediaSession = MediaSession.Builder(this, player).setCallback(this).build()
}
override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaSession? =
mediaSession
override fun onDestroy() {
mediaSession?.run {
player.release()
release()
mediaSession = null
}
super.onDestroy()
}
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: MutableList<MediaItem>
): ListenableFuture<MutableList<MediaItem>> {
val updatedMediaItems = mediaItems.map { it.buildUpon().setUri(it.mediaId).build() }.toMutableList()
return Futures.immediateFuture(updatedMediaItems)
}
}
and then replacing
controller.setMediaItem(MediaItem.fromUri(url))
by
val media = MediaItem.Builder().setMediaId(url).build()
controller.setMediaItem(media)
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 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
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()
}
}
}
}
}
On Button Click i could make a call using Intent -> Action Call. Later in the code i implemented Broadcast Received. I found that once BroadcasteReceiver is registered Calls using Intent->action.Call is not process. Kindly help.
// This function is called on button click
fun callPerson(position: Int) {
val callObject = allCallList?.get(position)
try {
var startIndent = Intent(Intent.ACTION_CALL)
startIndent.data = Uri.parse("tel:" + callObject?.number)
ContextCompat.startActivity(mContext as Context, startIndent, null)
}
catch(ex : Exception) {
Toast.makeText(mContext,Toast.message.toString(),Toast.LENGTH_LONG).show()
}
}
//Permission taken
<uses-permission android:name="android.permission.MANAGE_OWN_CALLS" />
<uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />
<uses-permission android:name="android.permission.READ_CALL_LOG" />
<uses-permission android:name="android.permission.READ_CONTACTS" />
<uses-permission android:name="android.permission.READ_PHONE_STATE" />
<uses-permission android:name="android.permission.CALL_PHONE" />
<uses-permission android:name="android.permission.SEND_SMS" />
//Service
class CallCaptureService : Service()
{
private var callCapture = CallCapture()
override fun onBind(p0: Intent?): IBinder? {
return null
}
override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int {
return START_STICKY
}
override fun onCreate() {
super.onCreate()
var intentFilter = IntentFilter()
intentFilter.addAction("android.intent.action.PHONE_STATE");
intentFilter.addAction("android.intent.action.NEW_OUTGOING_CALL");
intentFilter?.priority = 100
registerReceiver(callCapture, intentFilter)
}
override fun onDestroy() {
super.onDestroy()
unregisterReceiver(callCapture)
}
}
//CallCapture is receiver
class CallCapture : BroadcastReceiver(){
override fun onReceive(context: Context?, intent: Intent?) {
//We listen to two intents. The new outgoing call only tells us of an outgoing call. We use it to get the number.
if (intent?.getAction().equals("android.intent.action.NEW_OUTGOING_CALL")) {
Staticated.savedNumber = intent?.getExtras()?.getString("android.intent.extra.PHONE_NUMBER")
} else {
val stateStr = intent?.getExtras()?.getString(TelephonyManager.EXTRA_STATE)
val number = intent?.getExtras()?.getString(TelephonyManager.EXTRA_INCOMING_NUMBER)
var state = 0
if (stateStr == TelephonyManager.EXTRA_STATE_IDLE) {
state = TelephonyManager.CALL_STATE_IDLE
} else if (stateStr == TelephonyManager.EXTRA_STATE_OFFHOOK) {
state = TelephonyManager.CALL_STATE_OFFHOOK
} else if (stateStr == TelephonyManager.EXTRA_STATE_RINGING) {
state = TelephonyManager.CALL_STATE_RINGING
}
onCallStateChanged(context, state, number)
}
}