Cellular telephony
- The public API offers only limited features
- It allows to access to the cellular communication state and to be informed about changes
- Accessing to the low layers of the Radio Layer Interface to create ones customized dialer is challenging
Dialing a call
First we must declare the required permissions in the manifest :<uses-permission android:name="android.permission.CALL_PHONE" /> <uses-permission android:name="android.permission.READ_PHONE_STATE" />
-
Indirect dialing is possible without permission with Intent.ACTION_DIAL
-
We use the installed dialer by sending it an Intent:
- Intent callIntent = new Intent(Intent.ACTION_CALL);
- callIntent.setData(Uri.parse("tel:+33160957500"));
- startActivity(callIntent);
-
We use the installed dialer by sending it an Intent:
- On enregistre un PhoneCallListener pour être informé de l'état de l'appel (par exemple dans un service) :
// We create the listener
PhoneCallListener phoneListener = new PhoneCallListener() {
private boolean calling = false ;
@Override public void onCallStateChanged(int state, String incomingNumber) {
switch (state)
{
case TelephonyManager.CALL_STATE_RINGING :
// Phone is ringing
break ;
case TelephonyManager.CALL_STATE_OFFHOOK :
// Phone call is starting
calling = true ;
break ;
case TelephonyManager.CALL_STATE_IDLE :
// Either the phone has not dialed yet (!calling), or the call is finished (calling == true)
break ;
}
}
// We register the listener
TelephonyManager telephonyManager = (TelephonyManager) this.getSystemService(Context.TELEPHONY_SERVICE);
telephonyManager.listen(phoneListener,PhoneStateListener.LISTEN_CALL_STATE);
Information about the cellular network with TelephonyManager
- int getDataActivity(): activity of the data connection (DATA_ACTIVITY_{NONE, IN, OUT, INOUT, DORMANT})
- int getDataState(): status of the data connection (DATA_{DISCONNECTED, CONNECTING, CONNECTED, SUSPENDED})
-
String getDeviceId(): to obtain the IMEI number (requires the permission READ_PHONE_STATE)
- ⚠ Since Android 10, the IMEI number is no more available for standard apps (only system apps can access it with permission READ_PRIVILEGED_PHONE_STATE)
- boolean isNetworkRoaming(): specifies if we are connected on a roaming network (more fees may be applied)
- List<NeighboringCellInfo> getNeighboringCellInfo(): information about the cells of the neighborhood (CID, RSSI)
- String getNeworkOperator(): returns the MCC+MNC code of the cell operator
- int getNetworkType(): specifies the kind of network that is used for the data transmissions (GPRS, EDGE, UTMS, LTE, NR...)
PhoneStateListener
- We register the listener with TelephonyManager.listen(PhoneStateListener listener, int events)
-
Supported events:
- LISTEN_NONE: no event
- LISTEN_CALL_FORWARDING_INDICATOR: change about the state of call forwarding
- LISTEN_CALL_STATE: status of the call
- LISTEN_CELL_INFO: information about the cell
- LISTEN_CELL_LOCATION: change related to the location computed from the cell proximity
- LISTEN_DATA_ACTIVITY
- LISTEN_DATA_CONNECTIVITY_STATE
- LISTEN_SERVICE_STATE : state of the cell coverage (available network, emergencies only...)
- LISTEN_SIGNAL_STRENGTHS: power of the received signal
Incoming calls
- We are informed about incoming calls when we receive the broadcast TelephonyManager.ACTION_READ_PHONE_STATE_CHANGED (requires the permission READ_PHONE_STATE)
- EXTRA_STATE : state of the call (EXTRA_STATE_RINGING can be interesting)
- EXTRA_INCOMING_NUMBER : caller id
Action possible :
- Answering by sending the intent ACTION_ANSWER
Managing text messages
Sending SMS
- We get the SMS manager : SMSManager.getDefault()
-
This permissions is required to send SMS : android.permission.SEND_SMS
- This is a dangerous permission that requires explicit agreement
- Even if the permission is granted, the system will warn and ask the user if a SMS is sent to a premium number
-
How to send text messages?
- sendTextMessage(String destination, String scAddress, String message, PendingIntent sentIntent, PendingIntent deliveryIntent)
- sendDataMessage(String destination, String scAddress, short destinationPort, byte[] data, PendingIntent sentIntent, PendingIntent deliveryIntent)
- sendMultipartTextMessage(String destination, String scAddress, ArrayList<String> parts, ArrayList<PendingIntent> sentIntent, ArrayList<PendingIntent> deliveryIntent) : permet d'envoyer un SMS en plusieurs morceaux ; on divise auparavant le message en morceaux avec divideMessage
An activity to send SMS
Content not available
SMS receival
- Required dangerous permission: android.permission.RECEIVE_SMS
-
When a text message is received, an ordered broadcast android.provider.Telephony.SMS_RECEIVED is issued
- The specified bundle contains a byte[][] (getExtras().get("pdus")) with the PDUs (binary data of the text message)
- The PDU can be converted to a SmsMessage: SmsMessage.createFromPdu(pdu)
- It is possible to destroy the text message and not propagate it to next broadcast receivers: BroadcastReceiver.abortBroadcast()
BroadcastReceiver to receive SMS
package fr.upem.coursand.sms;
import java.util.regex.Pattern;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.telephony.SmsMessage;
import android.util.Log;
/**
* Intercept SMSs containing a given regular expression.
*/
public class SMSInterceptor extends BroadcastReceiver
{
@Override
public void onReceive(Context context, Intent intent)
{
Log.v("SMSInterceptor", "Receiving intent " + intent);
Object[] pdus = (Object[])intent.getExtras().get("pdus");
for (Object pdu: pdus)
{
SmsMessage message = SmsMessage.createFromPdu((byte[])pdu);
Log.v("SMSInterceptor", "Content of the message: " + message.getDisplayMessageBody());
if (isDiscardable(message))
{
Log.v("SMSInterceptor", "Discarding message " + message); // The message is not propagated: it is lost
abortBroadcast(); // this SMS is not propagated on the chain of receivers (the SMS app should not receive it)
} else
{
// communicate the intercepted message to the SMSSpyService
Intent i = new Intent(context, SMSSpyService.class).setAction(SMSSpyService.GIVE_MESSAGE_ACTION);
i.putExtra("message", message.getDisplayMessageBody());
i.putExtra("sender", message.getOriginatingAddress());
context.startService(i);
}
}
}
public static Pattern DISCARD_PATTERN = Pattern.compile("^destroy:.*$");
public boolean isDiscardable(SmsMessage message)
{
return DISCARD_PATTERN.matcher(message.getDisplayMessageBody()).matches();
}
}
We declare the BroadcastReceiver in the manifest to instantiate it automatically when a new SMS arrives:
<receiver android:name="fr.upem.coursand.sms.SMSInterceptor"> <intent-filter android:priority="1000"> <action android:name="android.provider.Telephony.SMS_RECEIVED" /> </intent-filter> </receiver>