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.
Johan Pelgrim
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.
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
{
@Override
public
void
onReceive
(
Context
context
,
Intent
intent
)
{
String
state
=
intent
.
getStringExtra
(
TelephonyManager
.
EXTRA_STATE
)
;
String
msg
=
"Phone state changed to "
+
state
;
if
(
TelephonyManager
.
EXTRA_STATE_RINGING
.
equals
(
state
)
)
{
String
incomingNumber
=
intent
.
getStringExtra
(
TelephonyManager
.
EXTRA_INCOMING_NUMBER
)
;
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
(
)
;
}
}
}
Create an IncomingCallInterceptor
class that extends BroadcastReceiver
.
Override the onReceive()
method to handle incoming broadcast messages.
The EXTRA_STATE
Intent extra in this case indicates the new call state.
If (and only if) the new state is RINGING
, a second Intent extra, EXTRA_INCOMING_NUMBER
, provides the incoming phone number as a string.
Extract the number information from the EXTRA_INCOMING_NUMBER
Intent extra.
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.
<?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"
>
<intent-filter
>
<action
android:name=
"android.intent.action.PHONE_STATE"
/>
</intent-filter>
</receiver>
</application>
<uses-permission
android:name=
"android.permission.READ_PHONE_STATE"
/>
</manifest>
We register our IncomingCallInterceptor
as a receiver
within the application
element.
We register an intent-filter
…
And an action
value that registers our receiver to listen for TelephonyManager.ACTION_PHONE_STATE_CHANGED
broadcast messages.
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.
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.
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).
Recipe 11.2, the developer documentation on BroadcastReceiver
and ACTION_PHONE_STATE_CHANGED
.
The source code for this project is in the Android Cookbook repository, in the subdirectory CallInterceptorIncoming (see “Getting and Using the Code Examples”).
Johan Pelgrim
Listen for the Intent.ACTION_NEW_OUTGOING_CALL
broadcast action and set the result data of the broadcast receiver to the new number.
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!
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
{
@Override
public
void
onReceive
(
Context
context
,
Intent
intent
)
{
final
String
oldNumber
=
intent
.
getStringExtra
(
Intent
.
EXTRA_PHONE_NUMBER
)
;
this
.
setResultData
(
"0123456789"
)
;
final
String
newNumber
=
this
.
getResultData
(
)
;
String
msg
=
"Intercepted outgoing call. Old number "
+
oldNumber
+
", new number "
+
newNumber
;
Toast
.
makeText
(
context
,
msg
,
Toast
.
LENGTH_LONG
)
.
show
(
)
;
}
}
Create an OutgoingCallInterceptor
class that extends BroadcastReceiver
.
Override the onReceive()
method.
Extract the phone number that the user originally intended to call via the Intent.EXTRA_PHONE_NUMBER
Intent extra.
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.
<?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"
>
<intent-filter
android:priority=
"1"
>
<action
android:name=
"android.intent.action.NEW_OUTGOING_CALL"
/>
</intent-filter>
</receiver>
</application>
<uses-permission
android:name=
"android.permission.PROCESS_OUTGOING_CALLS"
/>
</manifest>
We register our OutgoingCallInterceptor
as a receiver
within the application
element.
We add an intent-filter
element within this receiver
declaration and set an android:priority
of 1
.
We add an action
element within the intent-filter
, to only receive Intent.ACTION_NEW_OUTGOING_CALL
Intent actions.
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.)
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.
Recipe 11.1; , the developer documentation on ACTION_NEW_OUTGOING_CALL
.
The source code for this project is in the Android Cookbook repository, in the subdirectory CallInterceptorOutgoing (see “Getting and Using the Code Examples”).
Ian Darwin
Start an Intent to dial the phone.
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.
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.
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.
The source code for this example is in the Android Cookbook repository, in the subdirectory SimpleDialer (see “Getting and Using the Code Examples”).
Colin Wilcox
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.
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.
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.
For information on the SmsManager
, see the official documentation.
The source code for this example is in the Android Cookbook repository, in the subdirectory SendSMS (see “Getting and Using the Code Examples”).
Rachee Singh
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.
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>
The source code for this project is in the Android Cookbook repository, in the subdirectory SMSReceiver (see “Getting and Using the Code Examples”).
Rachee Singh
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.
Pratik Rupwal
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.
...
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
.
...
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.
The source code for this example is in the Android Cookbook repository, in the subdirectory TelephonyManager (see “Getting and Using the Code Examples”).
3.147.66.178