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>