Is there a way to retry a missed or unsuccessful outbound phone call after a period of time? I am initiating a phone call using the ACTION_CALL intent and have it connected to a PhoneStateListener.
class PlaceCall : AppCompatActivity() {
private fun outboundCall() {
val telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.listen(CallListener(context), PhoneStateListener.LISTEN_CALL_STATE)
val callIntent = Intent(Intent.ACTION_CALL, Uri.parse("tel:" + phoneNumber))
startActivity(callIntent)
}
}
CallListener is setup like this:
class CallListener(cont: Context) : PhoneStateListener() {
private var context: Context = cont
private var incoming: Boolean = false
private var prevState: Int = TelephonyManager.CALL_STATE_IDLE
override fun onCallStateChanged(state: Int, phoneNumber: String?) {
val audioManager = context.getSystemService(Context.AUDIO_SERVICE) as AudioManager
when(state) {
TelephonyManager.CALL_STATE_RINGING -> { incoming = true }
TelephonyManager.CALL_STATE_IDLE -> {
if(prevState == TelephonyManager.CALL_STATE_RINGING) { //Missed call?
}
TelephonyManager.CALL_STATE_OFFHOOK -> { Log.d("DEBUG", "calling $phoneNumber") }
}
}
prevState = state
}
}
How can I wait a determined interval and try the call again if it is anything other than a successful phone call connection? Also, why is the value of phoneNumber in the listener always empty?
Unlike an incoming call which goes through IDLE -> RINGING -> OFFHOOK,
in outbound calls it always jumps directly from IDLE -> OFFHOOK even while it's ringing (on the other side).
So if by "successful" you mean to say a phone call that had been picked up by the other side, PhoneStateListener won't help you, as there's no additional state sent when the other side picks up.
Also, why is the value of phoneNumber in the listener always empty?
phoneNumber is populated for incoming calls only, and only if you app has both READ_CALL_LOG and READ_PHONE_STATE permissions, see here.
If you need to last called number you can use the Calls.getLastOutgoingCall API a few seconds after an outgoing call has ended.
Related
How to detect the time when a phone starts ringing for outgoing calls.I am developing an application in which i am trying to make a call programatically and when call is connected to internet like whats app audio call app.i didnt found the solution how to detect call is ringing or busy to reciever side.
i connect my call using token key and session key through internet. my code is below for passing intent to call activity.
val intent = Intent(this#AstroDetailsActivity, CallActivity::class.java)
intent.putExtra(SESSION_ID_KEY, res!!.session_id)
intent.putExtra(TOKEN_KEY, res.token)
intent.putExtra("userId", MyApplication.sharedPreference?.userId.toString())
intent.putExtra("astroId", astroId)
intent.addCategory(Intent.ACTION_CALL)
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
startActivity(intent)
The example code doesn't make use of intent extras so I am unsure if you have made changes to this code to actually initialise.
But if you look in this activity you will see a PhoneStateListener. This object is attached to a call using:
private fun registerPhoneListener() {
val telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE)
}
The PhoneStateListener then has a method onCallStateChanged which gets called when the current state of a phone call is changed. This can be overidden to carry out custom logic as below:
private val phoneStateListener: PhoneStateListener = object : PhoneStateListener() {
override fun onCallStateChanged(state: Int, incomingNumber: String) {
super.onCallStateChanged(state, incomingNumber)
when (state) {
TelephonyManager.CALL_STATE_IDLE -> {
publisher?.publishVideo = true
publisher?.publishAudio = true
}
TelephonyManager.CALL_STATE_RINGING -> Log.d("onCallStateChanged", "CALL_STATE_RINGING")
TelephonyManager.CALL_STATE_OFFHOOK -> {
Log.d("onCallStateChanged", "CALL_STATE_OFFHOOK")
publisher?.publishVideo = false
publisher?.publishAudio = false
}
else -> Log.d("onCallStateChanged", "Unknown Phone State !")
}
}
}
PhoneStateListener's onCallStateChanged took the state of the phone call and the number being called as parameters:
val telephonyManager =
context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager.listen(
object : PhoneStateListener() {
override fun onCallStateChanged(state: Int, phoneNumber: String) {
super.onCallStateChanged(state, phoneNumber)
}
},
PhoneStateListener.LISTEN_CALL_STATE
)
After the deprecation of listen() and PhoneStateListener, the suggested way to listen to phone calls is through registerTelephonyCallback(), that takes an Executor and a TelephonyCallback as parameters, the problem is that TelephonyCallback.CallStateListener's onCallStateChanged only takes the call state as parameter:
telephonyManager.registerTelephonyCallback(
context.mainExecutor,
object : TelephonyCallback(), TelephonyCallback.CallStateListener {
override fun onCallStateChanged(state: Int) {
// WHERE IS PHONE NUMBER?
}
}
)
I absolutely need to know the phone number being called in order to make my app work properly.
Does someone know how to obtain it using TelephonyCallback or, at least, without using deprecated methods?
I solved it using CallScreeningService, that is available from API 24, but unusable until API 29 because of callDirection:
#RequiresApi(Build.VERSION_CODES.N)
class PhoneCallScreening : CallScreeningService() {
override fun onScreenCall(callDetails: Call.Details) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
if (callDetails.callDirection == Call.Details.DIRECTION_OUTGOING) {
val phoneNumber = callDetails.handle.schemeSpecificPart
PreferenceManager
.getDefaultSharedPreferences(this)
.edit()
.putString(CALLED_PHONE_NUMBER_KEY, phoneNumber)
.apply()
}
}
}
}
In order to use this, your app has to become the default one for call screening:
#RequiresApi(api = Build.VERSION_CODES.Q)
public void requestRole() {
RoleManager roleManager = (RoleManager) getSystemService(ROLE_SERVICE);
Intent intent = roleManager.createRequestRoleIntent(RoleManager.ROLE_CALL_SCREENING);
startActivityForResult(intent, PHONE_SCREENING_REQUEST_ID);
}
I don't know if you can access any other information about the call besides the phone number, I currently save the phone number in SharedPreferences and then access it in the new PhoneStateListener's onCallStateChanged.
As I said before, this solution is only possible from API 29, for lower API versions you have to use the deprecated way, I think is the only one to achieve this.
I have accessed it using intent:
val incomingNumber: String? = intent?.getStringExtra(TelephonyManager.EXTRA_INCOMING_NUMBER)
https://github.com/gulsenkeskin/phone_call_demo/blob/main/android/app/src/main/kotlin/com/example/phone_call_demo/MainActivity.kt
I working in a internal chat application with video calls, when someone call, the phone must show the "ringing" screen. I need to wake up the phone screen and show the full screen notification with the calling screen when the app receives a push notification that someone is calling.
How can I do that?
Thank you.
You need to use the TelephonyManager to give you access to a subset of API's available natively.
Once you create an instance of this, you must register to listen() passing in a class that implements the PhoneStateListener. From here you can listen to a numerous states when listening for the LISTEN_CALL_STATE
Something like this:
private var yourListenerClass: ListenerClass? = null
override fun onCreate(savedInstanceState: Bundle?) {
var telephonyManager = getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
telephonyManager?.listen(yourListenerClass, PhoneStateListener.LISTEN_CALL_STATE)
}
private inner class ListenerClass : PhoneStateListener() {
override fun onCallStateChanged(state: Int, number: String?) {
super.onCallStateChanged(state, number)
when (state) {
TelephonyManager.CALL_STATE_RINGING-> {
//incoming call detected, do something
}
TelephonyManager.CALL_STATE_IDLE-> {
}
}
}
}
Here is the resource to the documentation that can give you more API's available for this listener: onCallStateChanged()
i want to update the local ip of the android system every time it changes in a textview, this is my code.
The function to obtain the ip is this
fun getIpv4HostAddress(): String {
NetworkInterface.getNetworkInterfaces()?.toList()?.map { networkInterface ->
networkInterface.inetAddresses?.toList()?.find {
!it.isLoopbackAddress && it is Inet4Address
}?.let { return it.hostAddress }
}
return ""
}
and the code inside the onCreate of the MainActivity.tk is this
val textView: TextView = findViewById(R.id.getIP)
textView.setText("IP local: " + getIpv4HostAddress())
textView.invalidate()
I want it to update and show it in real time in the texview, for example after setting and removing airplane mode, or changing networks wifi-> mobile mobile-> wifi
here I leave as seen in the application, someone to help me please
I've happened to have almost ready to use solution for this problem except extracting IPv4 address so I'll post it here so you could make use of it.
Basically, the solution consists of two main components: a "service" that listens to network changes and an RX subject to which you subscribe and post updates about network changes.
Step 0: Preparation
Make sure your AndroidManifest.xml file has next permissions included:
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
Your app has to enable compatibility options to allow the use of Java 8 features. Add the next lines in your build.gradle file:
android {
...
compileOptions {
targetCompatibility = "8"
sourceCompatibility = "8"
}
}
In order to make use of RX Kotlin add next dependencies:
implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
implementation 'io.reactivex.rxjava3:rxkotlin:3.0.0'
Step 1: Implement network change listener service
Imports are omitted to make code as concise as possible. NetworkReachabilityService is not a conventional Android service that you can start and it will run even when then the app is killed. It is a class that sets a listener to ConnectivityManager and handles all updates related to the network state.
Any type of update is handled similarly: something changed -> post NetworkState object with an appropriate value. On every change, we can request IPv4 to display in the UI (see on step 3).
sealed class NetworkState {
data class Available(val type: NetworkType) : NetworkState()
object Unavailable : NetworkState()
object Connecting : NetworkState()
object Losing : NetworkState()
object Lost : NetworkState()
}
sealed class NetworkType {
object WiFi : NetworkType()
object CELL : NetworkType()
object OTHER : NetworkType()
}
class NetworkReachabilityService private constructor(context: Application) {
private val connectivityManager: ConnectivityManager =
context.getSystemService(Context.CONNECTIVITY_SERVICE) as ConnectivityManager
private val networkCallback = object : ConnectivityManager.NetworkCallback() {
// There are more functions to override!
override fun onLost(network: Network) {
super.onLost(network)
postUpdate(NetworkState.Lost)
}
override fun onUnavailable() {
super.onUnavailable()
postUpdate(NetworkState.Unavailable)
}
override fun onLosing(network: Network, maxMsToLive: Int) {
super.onLosing(network, maxMsToLive)
postUpdate(NetworkState.Losing)
}
override fun onAvailable(network: Network) {
super.onAvailable(network)
updateAvailability(connectivityManager.getNetworkCapabilities(network))
}
override fun onCapabilitiesChanged(
network: Network,
networkCapabilities: NetworkCapabilities
) {
super.onCapabilitiesChanged(network, networkCapabilities)
updateAvailability(networkCapabilities)
}
}
companion object {
// Subscribe to this subject to get updates on network changes
val NETWORK_REACHABILITY: BehaviorSubject<NetworkState> =
BehaviorSubject.createDefault(NetworkState.Unavailable)
private var INSTANCE: NetworkReachabilityService? = null
#RequiresPermission(android.Manifest.permission.ACCESS_NETWORK_STATE)
fun getService(context: Application): NetworkReachabilityService {
if (INSTANCE == null) {
INSTANCE = NetworkReachabilityService(context)
}
return INSTANCE!!
}
}
private fun updateAvailability(networkCapabilities: NetworkCapabilities?) {
if (networkCapabilities == null) {
postUpdate(NetworkState.Unavailable)
return
}
var networkType: NetworkType = NetworkType.OTHER
if (networkCapabilities.hasTransport(TRANSPORT_CELLULAR)) {
networkType = NetworkType.CELL
}
if (networkCapabilities.hasTransport(TRANSPORT_WIFI)) {
networkType = NetworkType.WiFi
}
postUpdate(NetworkState.Available(networkType))
}
private fun postUpdate(networkState: NetworkState) {
NETWORK_REACHABILITY.onNext(networkState)
}
fun pauseListeningNetworkChanges() {
try {
connectivityManager.unregisterNetworkCallback(networkCallback)
} catch (e: IllegalArgumentException) {
// Usually happens only once if: "NetworkCallback was not registered"
}
}
fun resumeListeningNetworkChanges() {
pauseListeningNetworkChanges()
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
connectivityManager.registerDefaultNetworkCallback(networkCallback)
} else {
connectivityManager.registerNetworkCallback(
NetworkRequest.Builder().build(),
networkCallback
)
}
}
}
Step 2: Implement a method to extract IPv4 (bonus IPv6)
I had to modify your IPv4 extraction a little as it did not return any IPv4 addresses while a device clearly had one. These are two methods to extract IPv4 and IPv6 addresses respectively. Methods were modified using this SO answer on how to extract IP addresses. Overall, it is 90% the same mapping of inetAddresses to the IP address values.
Add these two methods to NetworkReachabilityService class:
fun getIpv4HostAddress(): String? =
NetworkInterface.getNetworkInterfaces()?.toList()?.mapNotNull { networkInterface ->
networkInterface.inetAddresses?.toList()
?.filter { !it.isLoopbackAddress && it.hostAddress.indexOf(':') < 0 }
?.mapNotNull { if (it.hostAddress.isNullOrBlank()) null else it.hostAddress }
?.firstOrNull { it.isNotEmpty() }
}?.firstOrNull()
fun getIpv6HostAddress(): String? =
NetworkInterface.getNetworkInterfaces()?.toList()?.mapNotNull { networkInterface ->
networkInterface.inetAddresses?.toList()
?.filter { !it.isLoopbackAddress && it is Inet6Address }
?.mapNotNull { if (it.hostAddress.isNullOrBlank()) null else it.hostAddress }
?.firstOrNull { it.isNotEmpty() }
}?.firstOrNull()
Step 3: Update UI
The simples solution related to UI is a direct subscription to NETWORK_REACHABILITY subject and on each change received through that subject, we pull out IPv4 data from NetworkReachabilityService and display it in the UI. Two main methods you want to look at are subscribeToUpdates and updateIPv4Address. And do not forget to unsubscribe by using unsubscribeFromUpdates to prevent memory leaks.
class MainActivity : AppCompatActivity() {
private val compositeDisposable = CompositeDisposable()
private lateinit var textView: TextView
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textView = findViewById(R.id.text_view)
val service = NetworkReachabilityService.getService(application)
service.resumeListeningNetworkChanges()
subscribeToUpdates()
}
override fun onDestroy() {
super.onDestroy()
unsubscribeFromUpdates()
}
private fun unsubscribeFromUpdates() {
compositeDisposable.dispose()
compositeDisposable.clear()
}
private fun subscribeToUpdates() {
val disposableSubscription =
NetworkReachabilityService.NETWORK_REACHABILITY
.observeOn(AndroidSchedulers.mainThread())
.subscribe({ networkState ->
// We do not care about networkState right now
updateIPv4Address()
}, {
// Handle the error
it.printStackTrace()
})
compositeDisposable.addAll(disposableSubscription)
}
private fun updateIPv4Address() {
val service = NetworkReachabilityService.getService(application)
textView.text = service.getIpv4HostAddress()
}
}
Recap
Using a ConnectivityManager instance we set a listener which reacts on any network change. Each change triggers an update which posts value to RX subject holding the latest network state. By subscribing to the subject we can track network state changes and assume the device had its address changed and thus we refresh IPv4 value displayed in a TextView.
I decided this code was good to go on GitHub, so here is the link to the project.
To receive event information at real time you can use different ways depending upon if your app is in foreground or background when the info is needed.
Since in your case the app seems to be in foreground, you make use of application.class to write code for receiving network changes using broadcast receiver( programatically registered) or some other way. And then in the function that receives that event change info , make a call to your getIpv4HostAddress() that would set the ip string and use it in the set the textview in another calss.
so I am currently implementing a call forwarding feature in Android, for dual SIM devices. In order to read the current state of the call forwarding (enabled/disabled) for a SIM card, I do the following:
I create a TelephonyManager object:
val telephonyManager = getSystemService(TELEPHONY_SERVICE) as TelephonyManager
I create a PhoneStateListener object and override the onCallForwardingIndicatorChanged method:
val myPhoneStateListener = object: PhoneStateListener() {
override fun onCallForwardingIndicatorChanged(isCallForwardingEnabled: Boolean) {
if(isCallForwardingEnabled) println("Call forwarding enabled!")
else println("Call forwarding disabled!")
}
}
I registered the PhoneStateListener:
telephonyManager.listen(myPhoneStateListener, LISTEN_CALL_FORWARDING_INDICATOR)
This works perfectly fine for the primary (the first) SIM card.
But I am having trouble doing the same for the second SIM card. Here is how I am trying to do it:
I use a SubscriptionManager object to retrieve the subscriptionId of the second SIM card:
val subscriptionManager = getSystemService(TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
val subscriptionIdOfSimCard2 = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(1).subscriptionId
I create a separate TelephonyManager for the second SIM card, with the correct subscriptionId:
val secondTelephonyManager = (getSystemService(TELEPHONY_SERVICE) as TelephonyManager).createForSubscriptionId(subscriptionIdOfSimCard2)
I create a second PhoneStateListener, just like the one for the first SIM card, lets call it mySecondPhoneStateListener and register it with the second TelephonyManager:
secondTelephonyManager.listen(mySecondPhoneStateListener, LISTEN_CALL_FORWARDING_INDICATOR)
The problem now is, that in mySecondPhoneStateListener I don't get callbacks for the second SIM card, but still the primary, first one. After digging around in the Android source code, I found out why that is: In the listen(PhoneStateListener listener, int events) method of the TelephonyManager the wrong subscriptionId is used, i.e. not the one set in the TelephonyManager but the one in the PhoneStateListener object, which is the subscriptionId of the first SIM card, by default:
public void listen(PhoneStateListener listener, int events) {
if (mContext == null) return;
try {
Boolean notifyNow = (getITelephony() != null);
sRegistry.listenForSubscriber(listener.mSubId, */ HERE: listener.mSubId is used instead of this.mSubId */
getOpPackageName(), listener.callback, events, notifyNow);
} catch (RemoteException ex) {
// system process dead
} catch (NullPointerException ex) {
// system process dead
}
}
This problem could be solved by setting the correct subscriptionId for the PhoneStateListener object, however the appropriate constructor is hidden:
/**
* Create a PhoneStateListener for the Phone using the specified subscription.
* This class requires Looper.myLooper() not return null. To supply your
* own non-null Looper use PhoneStateListener(int subId, Looper looper) below.
* #hide */<-- HIDDEN, NOT ACCESSIBLE*/
*/
public PhoneStateListener(int subId) {
this(subId, Looper.myLooper());
}
I was able to "solve" this with reflection, by setting the mSubId field of the PhoneStateListener object to the appropriate subscriptionId of the second SIM card.
But there has to be a better way to do this, am I missing something?
I make simple ArrayList with Listeners and it's work for me fine (it's Kotlin btw)
In my activity:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.sim_selector)
checkForForwarding()
}
fun getSimsCount(): Int {
val subscriptionManager = SubscriptionManager.from(this)
val activeSubscriptionInfoList = subscriptionManager.activeSubscriptionInfoList
return activeSubscriptionInfoList.size
}
class SimForwardListeners{
var subscriptionId: Int = 0
lateinit var manager: TelephonyManager
lateinit var phoneStateListener: MyPhoneStateListener
}
private val simsForwardListeners: ArrayList<SimForwardListeners> = arrayListOf()
fun checkForForwarding() {
val subscriptionManager = getSystemService(TELEPHONY_SUBSCRIPTION_SERVICE) as SubscriptionManager
for (slotIndex in 0 until getSimsCount()) {
val z = SimForwardListeners()
z.phoneStateListener = MyPhoneStateListener(slotIndex)
z.phoneStateListener.setView(this)
z.subscriptionId = subscriptionManager.getActiveSubscriptionInfoForSimSlotIndex(slotIndex).subscriptionId
z.manager = (getSystemService(TELEPHONY_SERVICE) as TelephonyManager).createForSubscriptionId(z.subscriptionId)
z.manager.listen(z.phoneStateListener, PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR)
simsForwardListeners.add(z)
}
}