Process outgoing calls

Problem

You want to block certain calls, or alter the phone number about to be called.

Solution

Listen for the Intent.ACTION_NEW_OUTGOING_CALL broadcast action and set the result data of the broadcast receiver to the new number.

Discussion

If you want to intercept a call before it is about to be placed you can implement a broadcast receiver and listen for the Intent.ACTION_NEW_OUTGOING_CALL action. This recipe is in essence similar to the recipeDo something when the phone rings, but it is more interesting since we can actually manipulate the phone number in this case!

Ok, let’s do it.

1. Create an OutgoingCallInterceptor class which extends the BroadcastReceiver

2. Override the onReceive method.

3. Extract the phone number which the user originally intended to call via the Intent.EXTRA_PHONE_NUMBER intent extra.

4. Replace this number by calling setResultData with the new number as the String argument.

Once the broadcast is finished, the result data is used as the actual number to call. If the result data is null, no call will be placed at all!

package nl.codestone.cookbook.outgoingcallinterceptor;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.widget.Toast;

public class OutgoingCallInterceptor extends BroadcastReceiver {                            // 1

    @Override
    public void onReceive(Context context, Intent intent) {                                 // 2
        final String oldNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);          // 3
        this.setResultData("0123456789");                                                   // 4
        final String newNumber = this.getResultData();
        String msg = "Intercepted outgoing call. Old number " + oldNumber + ", new number " + newNumber;
        Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
    }

}

5. We have to register our OutgoingCallInterceptor as a <receiver> within the <application> element in the AndroidManifest.xml file.

6. We add an <intent-filter> element within this <receiver> declaration and add a android:priority of 1.

7. We add an <action> element within the <intent-filter> to only receive Intent.ACTION_NEW_OUTGOING_CALL intent actions.

8. And we have to hold the PROCESS_OUTGOING_CALLS permission to receive this intent so we register a <uses-permission> to PROCESS_OUTGOING_CALLS right below the <application> element.

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="nl.codestone.cookbook.outgoingcallinterceptor"
    android:versionCode="1" android:versionName="1.0">
    <uses-sdk android:minSdkVersion="3" />

    <application android:icon="@drawable/icon" android:label="Outgoing Call Interceptor">

        <receiver android:name="OutgoingCallInterceptor">                          <!-- 5 -->
            <intent-filter android:priority="1">                                   <!-- 6 -->
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />  <!-- 7 -->
            </intent-filter>
        </receiver>

    </application>

    <uses-permission android:name="android.permission.PROCESS_OUTGOING_CALLS" />   <!-- 8 -->

</manifest>

Now, when you try to call any number you will actually be forwarded to 0123456789 instead!

What happens if two receivers process outgoing calls?

As was stated before the Intent.ACTION_NEW_OUTGOING_CALL is an ordered broadcast and is a protected intent that can only be sent by the system. Ordered broadcast messages come with three additional features compared to normal broadcast messages.

1. You can use the <intent-filter> element’s android:priority attribute to influence your position in the sending mechanism. The android:priority is an integer indicating which parent (receiver) has higher priority in processing the incoming broadcast message. The higher the number, the higher the priority and the sooner that receiver can process the broadcast message.

2. You can propagate a result to the next receiver by calling the setResultData method, and

3. You can completely abort the broadcast by calling the abortBroadcast() method so that it won’t be passed to other receivers.

Note: According to the API any BroadcastReceiver receiving the Intent.ACTION_NEW_OUTGOING_CALL must not abort the broadcast by calling the abortBroadcast() method. Doing so does not present any errors, but apparrently some system receivers still want to have a go at the broadcast message. Emergency calls cannot be intercepted using this mechanism, and other calls cannot be modified to call emergency numbers using this mechanism.

It is perfectly acceptable for multiple receivers to process the outgoing call in turn: for example, a parental control application might verify that the user is authorized to place the call at that time, then a number-rewriting application might add an area code if one was not specified.

In case two receivers are defined with an equal android:priority attribute they will be run in an arbitrary order (according to the API). However, in practice, when they both reside in the sameAndroidManifest.xml file, it looks like the order in which the receivers are defined determines the order in which they will receive the broadcast message.

Furthermore, if two receivers are defined with an equal android:priority attribute but they are defined in a different AndroidManifest.xml file (i.e. they belong to a different application) it looks like the broadcast receiver, which was installed first is registered first and thus will be the one which is allowed to process the message first. But again, don’t count on it!

If you want to have a shot at being the very first to process a message you can use the maximum integer value (2147483647). Even though the API this still does not guarantee you will be first, you will have a pretty good change though!

Other applications could have intercepted the phone number before us. If you are pretty sure you want to take action on the original number you can use the EXTRA_PHONE_NUMBER intent extra as described above and completely ignore the result from the receiver before you. If you simply want to fall in line and pick up where another broadcast receiver has left off you can retrieve the intermediary phone number via the getResultData() method.

For consistency, any receiver whose purpose is to prohibit phone calls should have a priority of 0, to ensure it will see the final phone number to be dialed. Any receiver whose purpose is to rewrite phone numbers to be called should have a positive priority. Negative priorities are reserved for the system for this broadcast; using them may cause problems.