Chapter 11. Telephone Applications

Android began as a platform for cellular telephone handsets, so it is no surprise that Android apps are very capable of dealing with the phone. You can write apps that dial the phone, or that guide the user to do so. You can write apps that verify or modify the number the user is calling (e.g., to add a long-distance dialing prefix). You can also write apps that send and receive SMS (Short Message Service) messages, a.k.a. text messages, assuming the device is telephony-equipped. Nowadays, a great many Android tablets are WiFi-only, and do not have 4G, 3G, or even 2G telephone/SMS capabilities. For these devices, other capabilities such as SMS via internet and VoIP (Voice over IP, usually using SIP) have to be used.

This chapter covers most of these topics; a few are discussed elsewhere in this book.

11.1 Doing Something When the Phone Rings

Johan Pelgrim

Problem

You want to act on an incoming phone call and do something with the incoming number.

Solution

You can implement a broadcast receiver and then listen for a TelephonyManager.ACTION_PHONE_STATE_CHANGED action.

Discussion

If you want to do something when the phone rings you have to implement a broadcast receiver, which listens for the TelephonyManager.ACTION_PHONE_STATE_CHANGED Intent action. This is a broadcast Intent action indicating that the call state (cellular) on the device has changed. Example 11-1 shows the code for the incoming call interceptor, and Example 11-2 shows the incoming call interceptor’s layout file.

Example 11-1. The incoming call interceptor
package nl.codestone.cookbook.incomingcallinterceptor;

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

public class IncomingCallInterceptor extends BroadcastReceiver {            1

    @Override
    public void onReceive(Context context, Intent intent) {                 2
        String state = intent.getStringExtra(TelephonyManager.EXTRA_STATE); 3
        String msg = "Phone state changed to " + state;

        if (TelephonyManager.EXTRA_STATE_RINGING.equals(state)) {           4
            String incomingNumber = intent.getStringExtra(
           TelephonyManager.EXTRA_INCOMING_NUMBER); 5
            msg += ". Incoming number is " + incomingNumber;

            // This is where you have to "Do something when the phone rings" ;-)

            Toast.makeText(context, msg, Toast.LENGTH_LONG).show();
        }

    }
}
1

Create an IncomingCallInterceptor class that extends BroadcastReceiver.

2

Override the onReceive() method to handle incoming broadcast messages.

3

The EXTRA_STATE Intent extra in this case indicates the new call state.

4

If (and only if) the new state is RINGING, a second Intent extra, EXTRA_INCOMING_NUMBER, provides the incoming phone number as a string.

5

Extract the number information from the EXTRA_INCOMING_NUMBER Intent extra.

Note

Additionally, you can act on a state change to OFFHOOK or IDLE when the user picks up the phone or ends/rejects the phone call, respectively.

Example 11-2. The incoming call interceptor’s AndroidManifest() file
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
          package="nl.codestone.cookbook.incomingcallinterceptor"
          android:versionCode="1"
          android:versionName="1.0">
    <uses-sdk android:minSdkVersion="3" />

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

        <receiver android:name="IncomingCallInterceptor">                   1
            <intent-filter>                                                 2
                 <action android:name="android.intent.action.PHONE_STATE"/> 3
            </intent-filter>
        </receiver>

    </application>

    <uses-permission android:name="android.permission.READ_PHONE_STATE"/>   4

</manifest>
1

We register our IncomingCallInterceptor as a receiver within the application element.

2

We register an intent-filter

3

And an action value that registers our receiver to listen for TelephonyManager.ACTION_PHONE_STATE_CHANGED broadcast messages.

4

Finally, we register a uses-permission so that we are allowed to listen to phone state changes.

If all is well, you should see something like Figure 11-1 when the phone rings.

ack2 1101
Figure 11-1. Incoming call intercepted

What happens if two receivers listen for phone state changes?

In general, a broadcast message is just that: a message that is sent out to many receivers at the same time. This is the case for a normal broadcast, which is used to send out the ACTION_PHONE_STATE_CHANGED Intent as well. All receivers of the broadcast are run in an undefined order, often at the same time, and for that reason order is not applicable.

In other cases the system sends out an ordered broadcast, which is described in more detail in Recipe 11.2.

Final notes

When your BroadcastReceiver does not finish the processing in its onMessage() method within 10 seconds, the Android framework will show the infamous Application Not Responding (ANR) dialog, giving your users the ability to kill your program.

It is common for a BroadcastReceiver to simply start a Service. Since a BroadcastReceiver has no user interface, it can either start an Activity (using the inherited startActivity() method) or create and show a Notification (see Recipe 7.13).

See Also

Recipe 11.2, the developer documentation on BroadcastReceiver and ACT⁠ION_​PH⁠ONE_STA⁠TE_CHAN⁠GED.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory CallInterceptorIncoming (see “Getting and Using the Code Examples”).

11.2 Processing Outgoing Phone Calls

Johan Pelgrim

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 placed, you can implement a broadcast receiver and listen for the Intent.ACTION_NEW_OUTGOING_CALL action. This recipe is similar to Recipe 11.1, but it is more interesting since we can actually manipulate the phone number in this case!

Example 11-3 shows the code.

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!

Example 11-3. The outgoing call interceptor (a BroadcastReceiver)
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();
    }

}
1

Create an OutgoingCallInterceptor class that extends BroadcastReceiver.

2

Override the onReceive() method.

3

Extract the phone number that 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.

Example 11-4 shows the code in the outgoing call interceptor’s AndroidManifest.xml file.

Example 11-4. The outgoing call interceptor’s AndroidManifest.xml file
<?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">                          1
            <intent-filter android:priority="1">                                   2
                <action android:name="android.intent.action.NEW_OUTGOING_CALL" />  3
            </intent-filter>
        </receiver>

    </application>

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

</manifest>
1

We register our OutgoingCallInterceptor as a receiver within the application element.

2

We add an intent-filter element within this receiver declaration and set an android:priority of 1.

3

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

4

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.

Now, when you try to dial the number 11111 you will actually be forwarded to 0123456789 instead! (See Figure 11-2.)

ack2 1102
Figure 11-2. Outgoing call intercepted

What happens if two receivers process outgoing calls?

The Intent.ACTION_NEW_OUTGOING_CALL is an ordered broadcast and is a protected intent that can only be sent by the system. Compared to normal broadcast messages, ordered broadcast messages have three additional features:

  • 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.

  • You can propagate a result to the next receiver by calling the setResultData() method.

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

Note that, 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 apparently 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, and then a number-rewriting application might add an area code if one was not specified.

If 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 same AndroidManifest.xml file it appears that 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 different AndroidManifest.xml files (i.e., they belong to different applications), it appears that the broadcast receiver that was installed first is registered first and thus will be the one that 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 set the priority to the maximum integer value (2147483647). Even though using this feature of the API still does not guarantee you will be first, you will have a pretty good chance!

Also, other applications could have intercepted the phone number before your app. 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 earlier 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 that 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.

See Also

Recipe 11.1; , the developer documentation on ACTION_NEW_OUTGOING_CALL.

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory CallInterceptorOutgoing (see “Getting and Using the Code Examples”).

11.3 Dialing the Phone

Ian Darwin

Problem

You want to dial the phone from within an application, without worrying about details of telephony.

Solution

Start an Intent to dial the phone.

Discussion

One of the beauties of Android is the ease with which applications can reuse other applications, without being tightly coupled to the details (or even names) of the other programs, using the Intent mechanism. For example, to dial the phone, you only need to create and start an Intent with an action of DIAL and a URI of “tel:” + the number you want to dial. Thus, a basic dialer can be as simple as Example 11-5.

Example 11-5. Simple dialer Activity
public class Main extends Activity {
    String phoneNumber = "555-1212";
    String intentStr = "tel:" + phoneNumber;

    /** Standard creational callback.
     * Just dial the phone.
     */
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.main);

        Intent intent = new Intent("android.intent.action.DIAL",
                Uri.parse(intentStr));

        startActivity(intent);
    }
}

You need to have the permission android.permission.CALL_PHONE to use this code. The user will see the screen shown in Figure 11-3; users know to press the green phone button to let the call proceed.

ack2 1103
Figure 11-3. Simple dialer

Typically, in real life, you would not hardcode the number. In other circumstances you might want the user to call a number from the phone’s Contacts list.

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory SimpleDialer (see “Getting and Using the Code Examples”).

11.4 Sending Single-part or Multipart SMS Messages

Colin Wilcox

Problem

You want a simple way to send either a single-part or a multipart SMS/text message from a single entry point.

Solution

Use SmsManager.

Discussion

SMS messages, also called text messages, have been part of cellular technology for years. The Android API allows you to send an SMS message either by an Intent or in code; we’re only covering the code approach here.

SMS messages are limited to about 160 characters, depending on the carrier (in case you ever wondered where Twitter got the idea for 140-character messages). Text messages above this size must be broken into chunks. To give you control over this, the SmsManager class allows you to break a message into “parts,” and returns a list of them.

Note

For information about how the division of longer messages into parts works “under the hood,” see https://en.wikipedia.org/wiki/Concatenated_SMS.

If there is only one part, the message is short enough to send directly, so we use the sendTextMessage() method. Otherwise, we have to send the list of parts, so we pass the list back into the sendMultipartTextMessage() method. The actual sending code is shown in Example 11-6. The downloadable code features a trivial Activity to invoke the sending code.

Example 11-6. The SMS sender
package com.example.sendsms;
import java.util.ArrayList;

import android.telephony.SmsManager;
import android.util.Log;

/** The code for dealing with the SMS manager;
 * called from the GUI code.
 */
public class SendSMS {
    static String TAG = "SendSMS";
    SmsManager mSMSManager = null;
    /* The list of message parts our message
     * gets broken up into by SmsManager */
    ArrayList<String> mFragmentList = null;
    /* Service Center - not used */
    String mServiceCentreAddr = null;

    SendSMS() {
        mSMSManager = SmsManager.getDefault();
    }

    /* Called from the GUI to send one message to one destination */
    public boolean sendSMSMessage(
            String aDestinationAddress,
            String aMessageText) {

        if (mSMSManager == null) {
            return (false);
        }

        mFragmentList = mSMSManager.divideMessage(aMessageText);
        int fragmentCount = mFragmentList.size();
        if (fragmentCount > 1) {
            Log.d(TAG, "Sending " + fragmentCount + " parts");
            mSMSManager.sendMultipartTextMessage(aDestinationAddress,
                    mServiceCentreAddr,
                    mFragmentList, null, null);
        } else {
            Log.d(TAG, "Sending one part");
            mSMSManager.sendTextMessage(aDestinationAddress,
                    mServiceCentreAddr,
                    aMessageText, null, null);
        }

        return true;
    }
}

Although sent as three parts, it arrives as a single message, as shown in Figure 11-4.

As you might expect, the application needs the android.permission.SEND_SMS permission in its AndroidManifest.xml file.

See Also

For information on the SmsManager, see the official documentation.

ack2 1104
Figure 11-4. The multipart message arrived

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory SendSMS (see “Getting and Using the Code Examples”).

11.5 Receiving an SMS Message

Rachee Singh

Problem

You wish to enable your application to receive incoming SMS messages.

Solution

Use a broadcast receiver to listen for incoming SMS messages and then extract the messages.

Discussion

When an Android device receives a message, a broadcast Intent is fired (the Intent also includes the SMS message that was received). The application can register to receive these Intents.

The Intent has an action, android.provider.Telephony.SMS_RECEIVED. The application designed to receive SMS messages should include the RECEIVE_SMS permission in the manifest:

<uses-permission android:name="android.permission.RECEIVE_SMS"/>

When a message is received, the onReceive() method is called. Within this method, you can process the message. From the Intent that is received, the SMS message has to be extracted using the get() method. The BroadcastReceiver with the code for extracting the message part looks like Example 11-7. The code makes a Toast to display the contents of the received SMS message.

Example 11-7. The SMS BroadcastReceiver
public class InvitationSmsReceiver extends BroadcastReceiver {

    public void onReceive(Context context, Intent intent) {

        Bundle bundle = intent.getExtras();
        SmsMessage[] msgs = null;
        String message = "";
        if (bundle != null) {
            Object[] pdus = (Object[]) bundle.get("pdus");
            msgs = new SmsMessage[pdus.length];

            for (int i=0; i<msgs.length;i++) {
                msgs[i] = SmsMessage.createFromPdu((byte[]) pdus[i]);
                message = msgs[i].getMessageBody();
                Toast.makeText(context,message,Toast.LENGTH_SHORT).show();
            }
        }
    }
}

To register the InvitationSmsReceiver class for receiving the SMS messages, add the following to the manifest:

<receiver android:name=".InvitationSmsReceiver"
          android:enabled="true">
        <intent-filter>
            <action android:name="android.provider.Telephony.SMS_RECEIVED"/>
            <category android:name="android.intent.category.DEFAULT"/>
        </intent-filter>
</receiver>

Source Download URL

The source code for this project is in the Android Cookbook repository, in the subdirectory SMSReceiver (see “Getting and Using the Code Examples”).

11.6 Using Emulator Controls to Send SMS Messages to the Emulator

Rachee Singh

Problem

To interactively test an SMS message–based application before loading it onto a device, you need to be able to send an SMS message to the emulator.

Solution

Emulator control in the DDMS perspective of Eclipse allows sending SMS messages to the emulator.

Discussion

To test whether your application responds to incoming SMS messages, you need to send an SMS message to the emulator. The DDMS perspective of Eclipse or the Android Device Monitor of Android Studio provides this function. (You may wish to maximize the Emulator Control window as otherwise the important parts of it may be hidden and require both vertical and horizontal scrolling to access.) In the Emulator Control tab, go to Telephony Actions and provide a phone number. This number can be any number that you want the message to appear to come from. Select the SMS radio button. In the Message box, type the message you wish to send. Finally, press the Send button below the message text. See Figure 11-5.

ack2 1105
Figure 11-5. Emulator control sending an SMS message

11.7 Using Android’s TelephonyManager to Obtain Device Information

Pratik Rupwal

Problem

You want to obtain network-related and telephony information about the user’s device.

Solution

Use Android’s standard TelephonyManager to obtain statistics about network status and telephony information.

Discussion

Android’s TelephonyManager provides information about the Android telephony system. It assists in collecting information such as cell location, International Mobile Equipment Identity (IMEI) number, and network provider.

The program in Example 11-8 is long and covers most of the facilities provided by the Android TelephonyManager. It is unlikely you will need all of these in a single application, but they are consolidated here to provide a comprehensive example.

Example 11-8. The phone state sample Activity
...
import android.telephony.CellLocation;
import android.telephony.NeighboringCellInfo;
import android.telephony.PhoneStateListener;
import android.telephony.ServiceState;
import android.telephony.TelephonyManager;
import android.telephony.gsm.GsmCellLocation;

public class PhoneStateSample extends Activity {

        private static final String APP_NAME = "SignalLevelSample";
        private static final int EXCELLENT_LEVEL = 75;
        private static final int GOOD_LEVEL = 50;
        private static final int MODERATE_LEVEL = 25;
        private static final int WEAK_LEVEL = 0;

        // These are used to store Strings into an array for display
        private static final int INFO_SERVICE_STATE_INDEX = 0;
        private static final int INFO_CELL_LOCATION_INDEX = 1;
        private static final int INFO_CALL_STATE_INDEX = 2;
        private static final int INFO_CONNECTION_STATE_INDEX = 3;
        private static final int INFO_SIGNAL_LEVEL_INDEX = 4;
        private static final int INFO_SIGNAL_LEVEL_INFO_INDEX = 5;
        private static final int INFO_DATA_DIRECTION_INDEX = 6;
        private static final int INFO_DEVICE_INFO_INDEX = 7;

        // These are the IDs of the displays; must keep in sync with above constants
        private static final int[] info_ids= {
                R.id.serviceState_info,
                R.id.cellLocation_info,
                R.id.callState_info,
                R.id.connectionState_info,
                R.id.signalLevel,
                R.id.signalLevelInfo,
                R.id.dataDirection,
                R.id.device_info
        };

        @Override
        public void onCreate(Bundle savedInstanceState) {
            super.onCreate(savedInstanceState);
            setContentView(R.layout.main);
            startSignalLevelListener();
            displayTelephonyInfo();
        }

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

        @Override
        protected void onResume() {
                super.onResume();
                startSignalLevelListener();
        }

        @Override
        protected void onDestroy() {
                stopListening();
                super.onDestroy();
        }

        private void setTextViewText(int id,String text) {
                ((TextView)findViewById(id)).setText(text);
        }
        private void setSignalLevel(int id,int infoid,int level) {
                int progress = (int) ((((float)level)/31.0) * 100);
                String signalLevelString =getSignalLevelString(progress);
                ((ProgressBar)findViewById(id)).setProgress(progress);
                ((TextView)findViewById(infoid)).setText(signalLevelString);
                Log.i("signalLevel ","" + progress);
        }

        private String getSignalLevelString(int level) {
                String signalLevelString = "Weak";
                if(level > EXCELLENT_LEVEL)     signalLevelString = "Excellent";
                else if(level > GOOD_LEVEL)     signalLevelString = "Good";
                else if(level > MODERATE_LEVEL) signalLevelString = "Moderate";
                else if(level > WEAK_LEVEL)     signalLevelString = "Weak";
                return signalLevelString;
        }

        private void stopListening() {
                TelephonyManager tm =
                    (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
                tm.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
        }

        private void setDataDirection(int id, int direction) {
                int resid = getDataDirectionRes(direction);
                ((ImageView)findViewById(id)).setImageResource(resid);
        }
        private int getDataDirectionRes(int direction) {
                int resid = R.drawable.data_none;

                switch(direction) {
                        case TelephonyManager.DATA_ACTIVITY_IN:
                            resid = R.drawable.data_in; break;
                        case TelephonyManager.DATA_ACTIVITY_OUT:
                            resid = R.drawable.data_out; break;
                        case TelephonyManager.DATA_ACTIVITY_INOUT:
                            resid = R.drawable.data_both; break;
                        case TelephonyManager.DATA_ACTIVITY_NONE:
                            resid = R.drawable.data_none; break;
                        default: resid = R.drawable.data_none; break;
                }
                return resid;
        }
        private void startSignalLevelListener() {
        TelephonyManager tm =
            (TelephonyManager) getSystemService(TELEPHONY_SERVICE);
        int events = PhoneStateListener.LISTEN_SIGNAL_STRENGTH |
                     PhoneStateListener.LISTEN_DATA_ACTIVITY |
                     PhoneStateListener.LISTEN_CELL_LOCATION|
                     PhoneStateListener.LISTEN_CALL_STATE |
                     PhoneStateListener.LISTEN_CALL_FORWARDING_INDICATOR |
                     PhoneStateListener.LISTEN_DATA_CONNECTION_STATE |
                     PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR |
                     PhoneStateListener.LISTEN_SERVICE_STATE;
        tm.listen(phoneStateListener, events);
    }
    ...

Much of the information-gathering in this program is done by the various listeners. One exception is the method displayTelephonyInfo(), shown in Example 11-9, which simply gathers a large number of information bits directly from the TelephonyManager and adds them to a long string, which is displayed in the TextView.

Example 11-9. The phone state Activity (continued)
...

private void displayTelephonyInfo() {
        TelephonyManager tm = (TelephonyManager)getSystemService(TELEPHONY_SERVICE);
        GsmCellLocation loc = (GsmCellLocation)tm.getCellLocation();
        int cellid = loc.getCid();
        int lac = loc.getLac();
        String deviceid = tm.getDeviceId();
        String phonenumber = tm.getLine1Number();
        String softwareversion = tm.getDeviceSoftwareVersion();
        String operatorname = tm.getNetworkOperatorName();
        String simcountrycode = tm.getSimCountryIso();
        String simoperator = tm.getSimOperatorName();
        String simserialno = tm.getSimSerialNumber();
        String subscriberid = tm.getSubscriberId();
        String networktype = getNetworkTypeString(tm.getNetworkType());
        String phonetype = getPhoneTypeString(tm.getPhoneType());
        logString("CellID: " + cellid);
        logString("LAC: " + lac);
        logString("Device ID: " + deviceid);
        logString("Phone Number: " + phonenumber);
        logString("Software Version: " + softwareversion);
        logString("Operator Name: " + operatorname);
        logString("SIM Country Code: " + simcountrycode);
        logString("SIM Operator: " + simoperator);
        logString("SIM Serial No.: " + simserialno);
        logString("Sibscriber ID: " + subscriberid);
        String deviceinfo = "";
        deviceinfo += ("CellID: " + cellid + "
");
        deviceinfo += ("LAC: " + lac + "
");
        deviceinfo += ("Device ID: " + deviceid + "
");
        deviceinfo += ("Phone Number: " + phonenumber + "
");
        deviceinfo += ("Software Version: " + softwareversion + "
");
        deviceinfo += ("Operator Name: " + operatorname + "
");
        deviceinfo += ("SIM Country Code: " + simcountrycode + "
");
        deviceinfo += ("SIM Operator: " + simoperator + "
");
        deviceinfo += ("SIM Serial No.: " + simserialno + "
");
        deviceinfo += ("Subscriber ID: " + subscriberid + "
");
        deviceinfo += ("Network Type: " + networktype + "
");
        deviceinfo += ("Phone Type: " + phonetype + "
");
        List<NeighboringCellInfo> cellinfo =tm.getNeighboringCellInfo();
        if(null != cellinfo) {
                for(NeighboringCellInfo info: cellinfo) {
                        deviceinfo += ("	CellID: " +
                        info.getCid() +", RSSI: " + info.getRssi() + "
");
                }
        }
        setTextViewText(info_ids[INFO_DEVICE_INFO_INDEX],deviceinfo);
 }

 private String getNetworkTypeString(int type) {
         String typeString = "Unknown";
         switch(type) {
                 case TelephonyManager.NETWORK_TYPE_EDGE:
                     typeString = "EDGE"; break;
                 case TelephonyManager.NETWORK_TYPE_GPRS:
                     typeString = "GPRS"; break;
                 case TelephonyManager.NETWORK_TYPE_UMTS:
                     typeString = "UMTS"; break;
                 default:
                     typeString = "UNKNOWN"; break;
         }
         return typeString;
}

private String getPhoneTypeString(int type) {
        String typeString = "Unknown";
        switch(type) {
                case TelephonyManager.PHONE_TYPE_GSM:
                    typeString = GSM"; break;
                case TelephonyManager.PHONE_TYPE_NONE:
                    typeString = UNKNOWN"; break;
                default:typeString = "UNKNOWN"; break;
        }
        return typeString;
}

private int logString(String message) {
        return Log.i(APP_NAME,message);
}

private final PhoneStateListener phoneStateListener = new PhoneStateListener() {

        @Override
        public void onCallForwardingIndicatorChanged(boolean cfi) {
                Log.i(APP_NAME, "onCallForwardingIndicatorChanged " +cfi);
                super.onCallForwardingIndicatorChanged(cfi);
        }

        @Override
        public void onCallStateChanged(int state, String incomingNumber) {
                String callState = "UNKNOWN";
                switch(state) {
                        case TelephonyManager.CALL_STATE_IDLE:
                            callState = "IDLE"; break;
                        case TelephonyManager.CALL_STATE_RINGING:
                            callState = "Ringing (" + incomingNumber + ")"; break;
                        case TelephonyManager.CALL_STATE_OFFHOOK:
                            callState = "Offhook"; break;
                }
                setTextViewText(info_ids[INFO_CALL_STATE_INDEX],callState);
                Log.i(APP_NAME, "onCallStateChanged " + callState);
                super.onCallStateChanged(state, incomingNumber);
        }
        @Override
        public void onCellLocationChanged(CellLocation location) {
                String locationString = location.toString();
                setTextViewText(
                    info_ids[INFO_CELL_LOCATION_INDEX],locationString);

                Log.i(APP_NAME, "onCellLocationChanged " + locationString);
                super.onCellLocationChanged(location);
        }

        @Override
        public void onDataActivity(int direction) {
                String directionString = "none";
                switch (direction) {
                        case TelephonyManager.DATA_ACTIVITY_IN:
                            directionString = "IN"; break;
                        case TelephonyManager.DATA_ACTIVITY_OUT:
                            directionString = "OUT"; break;
                        case TelephonyManager.DATA_ACTIVITY_INOUT:
                            directionString = "INOUT"; break;
                        case TelephonyManager.DATA_ACTIVITY_NONE:
                            directionString = "NONE"; break;
                        default: directionString = "UNKNOWN: " + direction; break;
                }
                setDataDirection(info_ids[INFO_DATA_DIRECTION_INDEX],direction);
                Log.i(APP_NAME, "onDataActivity " + directionString);
                super.onDataActivity(direction);
        }

        @Override
        public void onDataConnectionStateChanged(int state) {
                String connectionState = "Unknown";
                switch(state) {
                       case TelephonyManager.DATA_CONNECTED:
                           connectionState = "Connected"; break;
                       case TelephonyManager.DATA_CONNECTING:
                           connectionState = "Connecting"; break;
                       case TelephonyManager.DATA_DISCONNECTED:
                           connectionState = "Disconnected"; break;
                       case TelephonyManager.DATA_SUSPENDED:
                           connectionState = "Suspended"; break;
                       default:
                           connectionState = "Unknown: " + state; break;
                }

                setTextViewText(
                    info_ids[INFO_CONNECTION_STATE_INDEX], connectionState);

                Log.i(APP_NAME,
                    "onDataConnectionStateChanged " + connectionState);

                super.onDataConnectionStateChanged(state);
        }

        @Override
        public void onMessageWaitingIndicatorChanged(boolean mwi) {
                Log.i(APP_NAME, "onMessageWaitingIndicatorChanged " + mwi);
                super.onMessageWaitingIndicatorChanged(mwi);
        }

        @Override
        public void onServiceStateChanged(ServiceState serviceState) {
                String serviceStateString = "UNKNOWN";
                switch(serviceState.getState()) {
                        case ServiceState.STATE_IN_SERVICE:
                            serviceStateString = "IN SERVICE"; break;
                        case ServiceState.STATE_EMERGENCY_ONLY:
                            serviceStateString = "EMERGENCY ONLY"; break;
                        case ServiceState.STATE_OUT_OF_SERVICE:
                            serviceStateString = "OUT OF SERVICE"; break;
                        case ServiceState.STATE_POWER_OFF:
                            serviceStateString = "POWER OFF"; break;
                        default:
                            serviceStateString = "UNKNOWN"; break;
                }

                setTextViewText(
                    info_ids[INFO_SERVICE_STATE_INDEX], serviceStateString);

                Log.i(APP_NAME, "onServiceStateChanged " + serviceStateString);

                super.onServiceStateChanged(serviceState);
        }

        @Override
        public void onSignalStrengthChanged(int asu) {
                Log.i(APP_NAME, "onSignalStrengthChanged " + asu);
                setSignalLevel(info_ids[INFO_SIGNAL_LEVEL_INDEX],
                   info_ids[INFO_SIGNAL_LEVEL_INFO_INDEX],asu);
                super.onSignalStrengthChanged(asu);
        }
   };
}

The main.xml layout, shown next, consists of a variety of nested linear layouts so that all the information gathered in the preceding code can be displayed neatly:

<?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
            android:layout_width="fill_parent"
            android:layout_height="wrap_content"
            android:orientation="vertical"
            android:scrollbarStyle="insideOverlay"
            android:scrollbarAlwaysDrawVerticalTrack="false">
        <LinearLayout
                android:orientation="vertical"
                android:layout_width="fill_parent"
                android:layout_height="fill_parent">
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Service State"
                                  style="@style/labelStyleRight"/>
                        <TextView android:id="@+id/serviceState_info"
                                  style="@style/textStyle"/>
                </LinearLayout>
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Cell Location"
                                  style="@style/labelStyleRight"/>
                        <TextView android:id="@+id/cellLocation_info"
                                  style="@style/textStyle"/>
                </LinearLayout>
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Call State"
                                  style="@style/labelStyleRight"/>
                        <TextView android:id="@+id/callState_info"
                                  style="@style/textStyle"/>
                </LinearLayout>
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Connection State"
                                  style="@style/labelStyleRight"/>
                        <TextView android:id="@+id/connectionState_info"
                                  style="@style/textStyle"/>
                </LinearLayout>
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Signal Level"
                                  style="@style/labelStyleRight"/>
                        <LinearLayout
                                android:layout_width="fill_parent"
                                android:layout_height="wrap_content"
                                android:layout_weight="0.5"
                                android:orientation="horizontal">
                                <ProgressBar android:id="@+id/signalLevel"
                                             style="@style/progressStyle"/>
                                <TextView android:id="@+id/signalLevelInfo"
                                          style="@style/textSmallStyle"/>
                        </LinearLayout>
                </LinearLayout>
                <LinearLayout
                        android:layout_width="fill_parent"
                        android:layout_height="wrap_content"
                        android:orientation="horizontal">
                        <TextView android:text="Data"
                                  style="@style/labelStyleRight"/>
                        <ImageView android:id="@+id/dataDirection"
                                   style="@style/imageStyle"/>
                </LinearLayout>
                <TextView android:id="@+id/device_info"
                          style="@style/labelStyleLeft"/>
        </LinearLayout>
</ScrollView>

Our code uses some UI styles, which are declared in this file, named styles.xml:

<?xml version="1.0" encoding="utf-8"?>
<resources>
        <style name="labelStyleRight">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:textSize">15dip</item>
                <item name="android:textStyle">bold</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:gravity">center_vertical|right</item>
        </style>

        <style name="labelStyleLeft">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:textSize">15dip</item>
                <item name="android:textStyle">bold</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:gravity">center_vertical|left</item>
        </style>

        <style name="textStyle">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:textSize">15dip</item>
                <item name="android:textStyle">bold</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:gravity">center_vertical|left</item>
        </style>

        <style name="textSmallStyle">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">fill_parent</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:textSize">10dip</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:gravity">center_vertical|left</item>
        </style>

        <style name="progressStyle">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:indeterminateOnly">false</item>
                <item name="android:minHeight">20dip</item>
                <item name="android:maxHeight">20dip</item>
                <item name="android:progress">15</item>
                <item name="android:max">100</item>
                <item name="android:gravity">center_vertical|left</item>
                <item name="android:progressDrawable">
                    @android:drawable/progress_horizontal</item>
                <item name="android:indeterminateDrawable">
                    @android:drawable/progress_indeterminate_horizontal</item>
        </style>

        <style name="imageStyle">
                <item name="android:layout_width">fill_parent</item>
                <item name="android:layout_height">wrap_content</item>
                <item name="android:layout_weight">0.5</item>
                <item name="android:src">@drawable/icon</item>
                <item name="android:scaleType">fitStart</item>
                <item name="android:layout_margin">10dip</item>
                <item name="android:gravity">center_vertical|left</item>
        </style>
</resources>

The application uses the ACCESS_COARSE_LOCATION permission (to get the approximate location from the cell radio service), which needs to be added to your project’s AndroidManifest.xml file:

<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />

The application also uses some images for indicating the data communication state as no data communication, incoming data communication, outgoing data communication, or both-ways data communication. These images are, respectively, named data_none.png, data_in.png, data_out.png, and data_both.png. Please add some icons with the aforementioned names in the res/drawable folder of your project structure.

Figure 11-6 shows the result.

ack2 1106
Figure 11-6. TelephonyManagerDemo in action

Source Download URL

The source code for this example is in the Android Cookbook repository, in the subdirectory TelephonyManager (see “Getting and Using the Code Examples”).

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
3.147.66.178