2

i have created a broadcast receiver for Phone state change. but the broadcast is not working. i have been trying from couple of hours and tried 2,3 solutions but still its not working. other guys over internet have same code and the is working fine for them. i don't know where i am making mistake. need your help! Here are my manifest File

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="veclar.map.callandsmsblocking">
    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    <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/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />

            </intent-filter>
        </activity>
        <receiver android:name=".PhoneCallReceiver" >
            <intent-filter>
                <action android:name="android.intent.action.PHONE_STATE"/>
            </intent-filter>
            <intent-filter>
                <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
            </intent-filter>
        </receiver>
    </application>
</manifest> 

and here is my PhoneCallReceiver class

public abstract class PhoneCallReceiver extends BroadcastReceiver {

    //The receiver will be recreated whenever android feels like it.  We need a static variable to remember data between instantiations

    private static int lastState = TelephonyManager.CALL_STATE_IDLE;
    private static Date callStartTime;
    private static boolean isIncoming;
    private static String savedNumber;  //because the passed incoming is only valid in ringing

    @Override
    public void 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")) {
            savedNumber = intent.getExtras().getString("android.intent.extra.PHONE_NUMBER");
        }
        else{
            String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
            String number = intent.getExtras().getString(TelephonyManager.EXTRA_INCOMING_NUMBER);
            int state = 0;
            if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
                state = TelephonyManager.CALL_STATE_IDLE;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
                state = TelephonyManager.CALL_STATE_OFFHOOK;
            }
            else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
                state = TelephonyManager.CALL_STATE_RINGING;
            }

            onCallStateChanged(context, state, number);
        }
    }

    //Incoming call-  goes from IDLE to RINGING when it rings, to OFFHOOK when it's answered, to IDLE when its hung up
    //Outgoing call-  goes from IDLE to OFFHOOK when it dials out, to IDLE when hung up
    public void onCallStateChanged(Context context, int state, String number) {
        if(lastState == state){
            //No change, debounce extras
            return;
        }
        switch (state) {
            case TelephonyManager.CALL_STATE_RINGING:
                isIncoming = true;
                callStartTime = new Date();
                savedNumber = number;

                Toast.makeText(context, "Incoming Call Ringing" , Toast.LENGTH_SHORT).show();
                break;
            case TelephonyManager.CALL_STATE_OFFHOOK:
                //Transition of ringing->offhook are pickups of incoming calls.  Nothing done on them
                if(lastState != TelephonyManager.CALL_STATE_RINGING){
                    isIncoming = false;
                    callStartTime = new Date();
                    Toast.makeText(context, "Outgoing Call Started" , Toast.LENGTH_SHORT).show();
                }

                break;
            case TelephonyManager.CALL_STATE_IDLE:
                //Went to idle-  this is the end of a call.  What type depends on previous state(s)
                if(lastState == TelephonyManager.CALL_STATE_RINGING){
                    //Ring but no pickup-  a miss
                    Toast.makeText(context, "Ringing but no pickup" + savedNumber + " Call time " + callStartTime +" Date " + new Date() , Toast.LENGTH_SHORT).show();
                }
                else if(isIncoming){

                    Toast.makeText(context, "Incoming " + savedNumber + " Call time " + callStartTime  , Toast.LENGTH_SHORT).show();
                }
                else{

                    Toast.makeText(context, "outgoing " + savedNumber + " Call time " + callStartTime +" Date " + new Date() , Toast.LENGTH_SHORT).show();

                }

                break;
        }

        lastState = state;
    }
}
Junaid Bashir
  • 152
  • 3
  • 15

3 Answers3

14

You canno longer receive PHONE_STATE_CHANGED broadcast this way.

From official Android developer guide https://developer.android.com/guide/components/broadcasts :

Beginning with Android 8.0 (API level 26), the system imposes additional restrictions on manifest-declared receivers.

If your app targets Android 8.0 or higher, you cannot use the manifest to declare a receiver for most implicit broadcasts (broadcasts that don't target your app specifically). You can still use a context-registered receiver when the user is actively using your app.

You must use explicit broadcast receivers (registered from your activity) to receive PHONE_STATE_CHANGED broadcast.

public class ToastDisplay extends Activity {

    private BroadcastReceiver receiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
            Toast.makeText(getApplicationContext(), "received", Toast.LENGTH_SHORT);
        }
    };

    @Override
    protected void onResume() {
        IntentFilter filter = new IntentFilter();
        filter.addAction("android.intent.action.PHONE_STATE");
        registerReceiver(receiver, filter);
        super.onResume();
    }

    @Override
    protected void onPause() {
        unregisterReceiver(receiver);
        super.onPause();
    }
}

Also, you in addition to declare required permission like android.permission.READ_PHONE_STATE , android.permission.PROCESS_OUTGOING_CALLS in the manifest , you must obtain those permissions explicitly from user at run-time. Otherwise you will not receive some(most) system broadcasts. Android developer guide has a nice explanation on requesting permissions from user and code sample. https://developer.android.com/training/permissions/requesting

Vishnuprasad R
  • 1,682
  • 13
  • 23
  • the link you provided is no longer available. – Junaid Bashir Sep 17 '18 at 14:26
  • i have tried explicit broadcast receiver too but it also didn't work. i registered this way. LocalBroadcastManager.getInstance(this).registerReceiver(mRegistrationPhoneStateReceiver, new IntentFilter(TelephonyManager.ACTION_PHONE_STATE_CHANGED)); – Junaid Bashir Sep 17 '18 at 14:34
  • let me try your way then i'll get back to you. – Junaid Bashir Sep 17 '18 at 14:34
  • @JunaidBashir In new versions of Android. You also need to ask for permissions explicitly from user. Just declaring them in manifest file is not enough. If you are not obtaining those permissions like android.permission.READ_PHONE_STATE , android.permission.PROCESS_OUTGOING_CALLS etc. somewhere else in your code explicitly, you won't receive those broadcasts – Vishnuprasad R Sep 17 '18 at 14:39
  • the broadcast is working now but i got another error. i am checking number and if number matched then i end the call. but the end call method giving exception. – Junaid Bashir Sep 18 '18 at 09:49
  • W/System.err: Caused by: java.lang.SecurityException: Neither user 10133 nor current process has android.permission.CALL_PHONE. – Junaid Bashir Sep 18 '18 at 09:50
  • now i am requesting CALL_PHONE permission but android is not granting permission. can you help me with this? – Junaid Bashir Sep 18 '18 at 09:52
  • @JunaidBashir , did you update the code to obtain permissions dynamically at run time? – Vishnuprasad R Sep 18 '18 at 10:48
  • Requesting permission in the manifest file is no longer enough. From Android M and up, you have to request permission dynamically run time. I have updated the answer to include that too. Read the last portion of the answer – Vishnuprasad R Sep 18 '18 at 10:50
  • 1
    yes i am requesting dynamically too but phone is not granting this permission – Junaid Bashir Sep 18 '18 at 11:04
  • 1
    @Junaid Bashir Please post that as a separate question and include relevant code samples. – Vishnuprasad R Sep 18 '18 at 12:24
  • @Vishniprasad i have posted the other question – Junaid Bashir Sep 18 '18 at 12:53
  • @VishnuprasadR In the link you mentioned in your answer (Android developer guide), they say that there are exceptions for some actions for which an implicit receiver still can be registered. https://developer.android.com/guide/components/broadcast-exceptions ACTION_PHONE_STATE_CHANGED appears in that list. Are you sure that this is not true and implicit broadcast receiver still won't work? The problem with explicitly registered receiver is that it stops working when my app is not active. Do you think there is some solution to use in this scenario? Thanks! – Grisha Oct 07 '18 at 19:17
  • @VishnuprasadR I can't understand. why did you post the answer like above? however, the broadcast receiver will be going to work in the background. so why did you override the `onResume()` method? – FGH Jul 29 '20 at 18:26
  • 1
    @Prasath Since API level 26, for most implicit broadcasts, you can receive them when the user is actively using your app. That is the restriction imposed by Android. Unless you have a Foreground Service running or user is using one of your Activities , you can not receive most implicit broadcasts. So we register broadcast receiver when the user is using the Activity (onResume) and de-register it when our Activity is not in focus (onPause). – Vishnuprasad R Jul 30 '20 at 19:36
  • @VishnuprasadR Thank you, I posted below coding, I created CallReceiver class which extends BroadcastReceiver, in Mainactivity I asking runtime permission only, so how can I implement from that, can you edit, my below answer? – FGH Jul 31 '20 at 07:32
  • @VishnuprasadR can you answer this question https://stackoverflow.com/q/63227315/5359340 , thank you – FGH Aug 04 '20 at 07:17
5

I my case phone state permission (android.permission.READ_PHONE_STATE) defined in manifest was not enough so when I give permission to app manually from app setting it start receiving Phone_State broadcast. I think run time permission from user is required.

Asad
  • 81
  • 1
  • 7
  • 1
    After spending 5hrs and ignoring this solution, I reluctantly gave this a shot... and it worked! thanks – Yo Apps Jun 04 '19 at 08:36
1
  1. First, define permission in the manifest

    <uses-permission android:name="android.permission.RECEIVE_SMS" />
    <uses-permission android:name="android.permission.READ_PHONE_STATE" />
    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS"/>
    
  2. inside the application tag register the receiver

    <receiver android:name=".PhoneCallReceiver" >
         <intent-filter>
             <action android:name="android.intent.action.PHONE_STATE"/>
         </intent-filter>
         <intent-filter>
             <action android:name="android.intent.action.NEW_OUTGOING_CALL"/>
         </intent-filter>
     </receiver>
    
  3. Ask the permission the startup Activity/Runtime Permission Usually in the MainActivity

    @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
         setContentView(R.layout.activity_main);
    
           checkAndRequestPermissions();
    
          }
        private  boolean checkAndRequestPermissions() {
     int readPhoneState = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_PHONE_STATE);
     int read_call_log = ContextCompat.checkSelfPermission(this, Manifest.permission.READ_CALL_LOG);
    
     List listPermissionsNeeded = new ArrayList<>();
    
     if (readPhoneState != PackageManager.PERMISSION_GRANTED) {
         listPermissionsNeeded.add(Manifest.permission.READ_PHONE_STATE);
     }
    
     if (read_call_log != PackageManager.PERMISSION_GRANTED) {
         listPermissionsNeeded.add(Manifest.permission.READ_CALL_LOG);
     }
    
     if (read_call_log != PackageManager.PERMISSION_GRANTED) {
         listPermissionsNeeded.add(Manifest.permission.PROCESS_OUTGOING_CALLS);
     }
    
     if (read_call_log != PackageManager.PERMISSION_GRANTED) {
         listPermissionsNeeded.add(Manifest.permission.INTERNET);
     }
    
     if (!listPermissionsNeeded.isEmpty()) {
         ActivityCompat.requestPermissions(this,
                 (String[]) listPermissionsNeeded.toArray(new String[listPermissionsNeeded.size()]),
                 REQUEST_ID_MULTIPLE_PERMISSIONS);
    
         return false;
     }
     return true;
    }
    
  4. inside the PhoneCallReciver class

     public class CallReceiver extends BroadcastReceiver{
    
    
     @Override
         public void onReceive(Context context, Intent intent) {
    
             try {
    
                 runfirstTime(context,intent);
    
             } catch (Exception ex) {
                 try {
    
                 }
                 catch (Exception e)
                 {
    
                 }
             }
         }
    
    
     private void runfirstTime(Context context, Intent intent) {
    
         TelephonyManager telephony = (TelephonyManager)context.getSystemService(Context.TELEPHONY_SERVICE);
         MyPhoneStateListener customPhoneListener = new MyPhoneStateListener();
    
         telephony.listen(customPhoneListener, PhoneStateListener.LISTEN_CALL_STATE);
    
         Bundle bundle = intent.getExtras();
         String phone_number = bundle.getString("incoming_number");
    
         String stateStr = intent.getExtras().getString(TelephonyManager.EXTRA_STATE);
         int state = 0;
    
         if(stateStr.equals(TelephonyManager.EXTRA_STATE_IDLE)){
             state = TelephonyManager.CALL_STATE_IDLE;
         }
         else if(stateStr.equals(TelephonyManager.EXTRA_STATE_OFFHOOK)){
             state = TelephonyManager.CALL_STATE_OFFHOOK;
         }
         else if(stateStr.equals(TelephonyManager.EXTRA_STATE_RINGING)){
             state = TelephonyManager.CALL_STATE_RINGING;
         }
    
         if (phone_number == null || "".equals(phone_number)) {
             return;
         }
         customPhoneListener.onCallStateChanged(context, state, phone_number);
        // Here customPhoneListener is a object of the  use context,state,phone_number to get the MyCustomPhonestatelistener class Which extends PhoneStateListener class.
    
     }
    }
    
FGH
  • 2,900
  • 6
  • 26
  • 59