14

I'd like to listen if there's a phone call happening while my app is in the foreground.

It was like this before but now listen() is deprecated:

val tm = context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager
            tm.listen(object : PhoneStateListener() {
                override fun onCallStateChanged(state: Int, phoneNumber: String?) {
                    super.onCallStateChanged(state, phoneNumber)
                    when (state) {
                        TelephonyManager.CALL_STATE_RINGING -> transcribingAudioConsumer.stopTranscription(null)
                        else -> {}
                    }
                }
            }, PhoneStateListener.LISTEN_CALL_STATE)

I tried something like this but I couldn't find the correct way to implement it.

         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
                        tm.registerTelephonyCallback({ it.run() }, MyPhoneStateListener())
                    }
  @RequiresApi(Build.VERSION_CODES.S)
    class MyPhoneStateListener : TelephonyCallback(), TelephonyCallback.CallStateListener {
        override fun onCallStateChanged(state: Int) {
            when (state) {
                TelephonyManager.CALL_STATE_RINGING -> {
                    Timber.e("omg RING")
                }

                TelephonyManager.CALL_STATE_OFFHOOK -> {
                    Timber.e("omg hook")
                }
                TelephonyManager.CALL_STATE_IDLE -> {
                    Timber.e("omg idle")
                }
            }
        }
    }
Orcun Sevsay
  • 1,310
  • 1
  • 14
  • 44

3 Answers3

13

Since the listen method is deprecated since api 31 android 12 I made a simple way to listen to the telephony callbacks.

val telephonyManager: TelephonyManager =
    context.getSystemService(Context.TELEPHONY_SERVICE) as TelephonyManager

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) {
    telephonyManager.registerTelephonyCallback(
        context.mainExecutor,
        object : TelephonyCallback(), TelephonyCallback.CallStateListener {
            override fun onCallStateChanged(state: Int) {
            }
        })
} else {
    telephonyManager.listen(object : PhoneStateListener() {
        override fun onCallStateChanged(state: Int, phoneNumber: String?) {
        }
    }, PhoneStateListener.LISTEN_CALL_STATE)
}

Note that the new callback does not include a phone number. At least for broadcast receiver the phone number can be retrieved via

intent.extras.getString("incoming_number")
xdbas
  • 663
  • 6
  • 19
  • after adding, i am receiving security exception in Android 12, Do we need to add any permissions in Android 12? – Ravi Gadipudi Feb 11 '22 at 14:21
  • 1
    @RaviGadipudi, you need to check READ_PHONE_STATE permission `private fun isReadPhoneStateGranted() = ActivityCompat.checkSelfPermission(this.context, Manifest.permission.READ_PHONE_STATE) == PackageManager.PERMISSION_GRANTED` – Orcun Sevsay Feb 14 '22 at 12:32
  • Yes, Found it thru behavioural changes of Android 12 document. Thanks for the reply. @OrcunSevsay – Ravi Gadipudi Feb 15 '22 at 06:18
  • 1
    can anyone convert this code to java? This is because i do not understand kotlin. Thanks in advance. – user1090751 Feb 28 '22 at 14:51
4

ServiceReceiver(Java)

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.telephony.TelephonyCallback;
import android.telephony.TelephonyManager;

public class ServiceReceiver extends BroadcastReceiver {

@RequiresApi(api = Build.VERSION_CODES.S)
@Override
public void onReceive(Context context, Intent intent) {
    registerCustomTelephonyCallback(context);
}

@RequiresApi(Build.VERSION_CODES.S)
class CustomTelephonyCallback extends TelephonyCallback implements TelephonyCallback.CallStateListener {
    private CallBack mCallBack;

    public CustomTelephonyCallback(CallBack callBack) {
        mCallBack = callBack;
    }

    @Override
    public void onCallStateChanged(int state) {

        mCallBack.callStateChanged(state);

    }
}


@RequiresApi(Build.VERSION_CODES.S)
public void registerCustomTelephonyCallback(Context context) {
    TelephonyManager telephony = (TelephonyManager) context
            .getSystemService(Context.TELEPHONY_SERVICE);

    telephony.registerTelephonyCallback(context.getMainExecutor(), new CustomTelephonyCallback(new CallBack() {
        @Override
        public void callStateChanged(int state) {

            int myState=state;

        }
    }));


}

interface CallBack {
    void callStateChanged(int state);
}

}

  • Thanks but onReceive never called.I am testing it on android emulator. – Saurabh Nov 16 '21 at 16:49
  • The written code only works on android 12. Make sure your device is android 12. – Enes BAŞKAYA Nov 17 '21 at 07:30
  • it works, thank you! But test on physical device, using emulator appear an error! – kaiv Mar 31 '22 at 23:17
  • I am not able to get onRecieve on my android 12 device, could someone please share the full code in java - starting from creating a ServiceRequestor object. Thanks. – RRR Jun 14 '22 at 03:21
2

I used what you did and android 12 emulator also worked.I used this for versions less than android 12 I hope it works.

    class MyPhoneStateListener() : PhoneStateListener() {
    override fun onCallStateChanged(state: Int, incomingNumber: String) {
        when (state) {
            TelephonyManager.CALL_STATE_IDLE -> {
                Log.e("MyPhoneStateListener", "IDLE")
            
            }
            TelephonyManager.CALL_STATE_OFFHOOK -> {
                Log.e("MyPhoneStateListener", "OFFHOOK")
            }
            TelephonyManager.CALL_STATE_RINGING -> {
                Log.e("MyPhoneStateListener", "RINGING")
            }
        }
    }


}

I used this to listen to the call


 phoneListener = MyPhoneStateListener()
             var telephony: TelephonyManager? = null
                telephony = context
                    .getSystemService(TELEPHONY_SERVICE) as TelephonyManager?

                telephony!!.listen(phoneListener, PhoneStateListener.LISTEN_CALL_STATE)

finally you should make a ServiceReceiver class like here. Add PhoneStateListener

Android 12 :I found an example here. I hope it works for you. https://note.com/koh_49/n/n94a5c2ae3aa2

  @RequiresApi(Build.VERSION_CODES.S)
class CustomTelephonyCallback(private val func: (state: Int) -> Unit) :
TelephonyCallback(),
TelephonyCallback.CallStateListener {
    override fun onCallStateChanged(state: Int) {
        func(state)
    }
}
@RequiresApi(Build.VERSION_CODES.S)
fun registerCustomTelephonyCallback(){

     var callback: CustomTelephonyCallback? = null


        (getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager)?.
        registerTelephonyCallback(
            mainExecutor,
            CustomTelephonyCallback {
                //
                var state=it
             

            }.also {
                callback = it
            }
        )


}

  

Manifest

    <receiver android:name=".data.service.receiver.ServiceReceiver"
        android:exported="false">
        <intent-filter>
            <action android:name="android.intent.action.PHONE_STATE" />
        </intent-filter>
    </receiver>

ServiceReceiver

import android.content.BroadcastReceiver
import android.content.Context
import android.content.Intent
import android.os.Build
import android.telephony.TelephonyCallback
import android.telephony.TelephonyManager
import androidx.annotation.RequiresApi
import androidx.core.content.ContextCompat.getMainExecutor


class ServiceReceiver: BroadcastReceiver() {
@RequiresApi(Build.VERSION_CODES.S)
override fun onReceive(context: Context?, intent: Intent?) {

    registerCustomTelephonyCallback(context)


}

@RequiresApi(Build.VERSION_CODES.S)
class CustomTelephonyCallback(private val func: (state: Int) -> Unit) :
    TelephonyCallback(),
    TelephonyCallback.CallStateListener {
    override fun onCallStateChanged(state: Int) {
        func(state)
    }
}

@RequiresApi(Build.VERSION_CODES.S)
fun registerCustomTelephonyCallback(context: Context?){

    var callback: CustomTelephonyCallback? = null

    (context?.getSystemService(Context.TELEPHONY_SERVICE) as? TelephonyManager)?.
    registerTelephonyCallback(
        getMainExecutor(context),
        CustomTelephonyCallback {
            //
            var state=it


        }.also {
            callback = it
        }
    )


}

}

  • `telephony.listen()` and PhoneStateListener() are deprecated. I know it's still working on Android 12, but I'd like to integrate with `registerTelephonyCallback(Executor, TelephonyCallback)`. – Orcun Sevsay Nov 08 '21 at 17:33
  • `Deprecated Use registerTelephonyCallback(Executor, TelephonyCallback). Params: listener – The PhoneStateListener object to register (or unregister) events – The telephony state(s) of interest to the listener, as a bitwise-OR combination of PhoneStateListener LISTEN_ flags.` You may see Deprecated and suggestion message under TelephonyManager.java `listen(..)` class – Orcun Sevsay Nov 08 '21 at 17:36
  • Thank you for your answer.yes I saw it deprecated "telephony.listen()" while using it in my project, then I started researching. I found your question and solution. This use "registerTelephonyCallback(Executor, TelephonyCallback" is not correct?. I m use your solution now for android 12 – Enes BAŞKAYA Nov 08 '21 at 20:10
  • I'm trying to understand how to build it with `TelephonyCallback` instead of `telephony.listen()` and `PhoneStateListener`. If you can help me to find how to integrate with `TelephonyCallback`, I'll be appreciated – Orcun Sevsay Nov 09 '21 at 09:17
  • What's `mainExecutor` that you've shared? @Enes BAŞKAYA – Orcun Sevsay Nov 09 '21 at 10:47
  • ı m use "mainExecutor" in my class. class MyCallScreeningService : CallScreeningService() . I call it from within this class of android. package android.content; .... /** * Proxying implementation of Context that simply delegates all of its calls to * another Context. Can be subclassed to modify behavior without changing * the original Context. */ public class ContextWrapper extends Context { .... @Override public Executor getMainExecutor() { return mBase.getMainExecutor(); } – Enes BAŞKAYA Nov 09 '21 at 12:08
  • When I import it in my receiver class, it imports from this package."import androidx.core.content.ContextCompat.getMainExecutor" – Enes BAŞKAYA Nov 09 '21 at 12:18
  • Could you also share your Receiver class and how you call it in Manifest, please? – Orcun Sevsay Nov 09 '21 at 13:08
  • 1
    I updated my answer.There was no receiver class in my project. I wrote this and tried it now it works.@Orcun Sevsay – Enes BAŞKAYA Nov 09 '21 at 14:00
  • Greate Answer. Thanks a lot! It works pretty good! – Orcun Sevsay Nov 10 '21 at 12:08
  • You are welcome.I'm glad it was useful. I wish you good work. – Enes BAŞKAYA Nov 10 '21 at 19:26
  • @EnesBAŞKAYA Can you provide the same snippet for JAVA? – Saurabh Nov 12 '21 at 12:52
  • 1
    I shared an example for Java. Hope it helps you @Saurabh – Enes BAŞKAYA Nov 12 '21 at 14:15
  • @EnesBAŞKAYA Thanks a lot mate. Can we use this like registering inside activity instead of manifest? – Saurabh Nov 16 '21 at 10:52
  • @Saurabh You're welcome .I've never done this before but you can send the data in the receiver class to the relevant activity – Enes BAŞKAYA Nov 17 '21 at 07:34