Introduction
With a bit of C# code, I've constructed a solution for my Windows Mobile phone that will display called ID information on my desktop when the phone rings and mute my computer when the phone is active or verbally state the name of the caller. When the phone call completes, the computer is automatically unmuted.
The Story Behind this Program
There are quite a few programs on my computer that make sound for various reasons. There's notifications of incoming instant messages or e-mails, scheduled reminders, media players, and the occasional Web advertisement that unexpectedly makes sounds. Regardless of the purpose of the sound, I don't want it to play when I'm using the phone. If I forget to mute the computer, then an unexpected message comes through and is distractingly loud to the person on the other side of the phone line. When I mute my computer, I may forget to unmute it and miss notifications for newer messages and reminders. To solve this problem, I decided to have my phone mute my computer when it was in use and unmute the computer upon completion of the call.
I felt it was necessary to demonstrate using the phone status information for a purpose other than muting the computer which resulted in two additions. I added functionality to the desktop client for this system to display the caller information in a notification balloon. I also added a program that makes use of Vista's speech synthesis functionality to verbally announce the name of the person calling.
Requirements
The portion of code that runs on the phone requires a Windows Mobile Professional device (device with a touch screen). The programs that run on the desktop require Windows Vista. This code will not work on Windows XP.
Supporting Article
To handle the muting and unmuting of the computer's audio system, I've made use of code from Ray M.'s article on Vista Core Audio API Master Volume Control. If you plan to use this code and have a 64-bit system, then read the comments section of his article; it lists a change to the code that you must make otherwise the code will not work.
Querying the Phone State
For this program, we must be able to query the state of the phone. This includes detecting incoming calls, knowing who is making the incoming call, detecting whether or not a data connection is provided, and so on. All of this information is tracked through the state and notifications broker. Within the compact framework, the state and notifications broker is accessible through the SystemState
class. If you glance over the members of the SystemState
class, you will see that it can be used to get information on the presence or absence of a data connection, information on the current and previous phone call, and a wealth of other information.
The SystemState
class is easy to use. When an instance of this class is created, the developer must specify for which element change notification is needed. Then every time that value changes, an event is raised. Included with this article is an example program named PhoneStatus
. It uses the SystemState
class to detect whether or not the phone is active, whether a data connection is present, and will show the name of a calling person.
SystemState _statePhoneIncoming;
SystemState _StatePhoneActiveCount;
SystemState _phoneCaller;
SystemState _localData;
public MainForm()
{
InitializeComponent();
_statePhoneIncoming = new SystemState(SystemProperty.PhoneIncomingCall, true);
_statePhoneIncoming.Changed += new ChangeEventHandler(_statePhoneIncoming_Changed);
_StatePhoneActiveCount = new SystemState(SystemProperty.PhoneActiveCallCount, true);
_StatePhoneActiveCount.Changed += new ChangeEventHandler
(_StatePhoneActiveCount_Changed);
_localData = new SystemState(SystemProperty.ConnectionsCount, true);
_localData.Changed += new ChangeEventHandler(_localData_Changed);
_phoneCaller = new SystemState(SystemProperty.PhoneIncomingCallerNumber, true);
_phoneCaller.Changed += new ChangeEventHandler(_phoneCaller_Changed);
}
void _phoneCaller_Changed(object sender, ChangeEventArgs args)
{
lblLastCallerName.Text = SystemState.PhoneIncomingCallerName;
lblLastCallerNumber.Text = SystemState.PhoneIncomingCallerNumber;
}
void _localData_Changed(object sender, ChangeEventArgs args)
{
chkLocalData.Checked = ((int)args.NewValue) > 0;
}
void _StatePhoneActiveCount_Changed(object sender, ChangeEventArgs args)
{
chkCallActive.Checked = (SystemState.PhoneActiveCallCount > 0) ||
SystemState.PhoneIncomingCall;
}
void _statePhoneIncoming_Changed(object sender, ChangeEventArgs args)
{
chkCallActive.Checked = SystemState.PhoneIncomingCall ||
SystemState.PhoneActiveCallCount > 0;
}
Sending Mute and Unmute Messages
In addition to automatically muting the computer, the user can use her/his phone to mute or unmute the computer. The interface for sending mute and unmute messages requires no description beyond a picture.
Sending Status to the Desktop
The Windows Mobile Professional phone that I am using has two adapters that can be used to communicate with my computers; a WiFi adapter and a Bluetooth adapter. The application performs its communication over UDP. So it could work on either a phone with a WiFi adapter or on a device that is on a bluetooth personal area network. However the computers I have don't have bluetooth capabilities, so I have only tested this program using the phone's WiFi adapter. Originally I only needed for the program to send four messages. To represent the phone's activity, there are the "active" and "inactive" messages. The program also allows one to send "mute" and "unmute" messages to change the state of a computer's audio hardware. Later I decided to add the "CallerID
" message.
Selecting Recipient Machines
Three methods are available for sending messages to groups of computers over UDP. A message could be broadcast to all the computers on a network node, it could be sent to a multicast group (in which case the machines that are interested in certain messages can register to receive them) or the messages can be individually sent to each machine. UDP Broadcast is deprecated; it will still work on IPv4 but it is not part of the IPv6 standard. Multicasting was more ideal but I found that some of the routers to which I connect would not allow my machine to send multicast traffic. Rather than convince the owners of the routers to reconfigure the routers for my personal needs, I decided to just send information directly to the machines that should receive it. The following UI allows the user to enter the name(s) or IP address(es) of the devices that should be updated with information on the phone's status along with specifying the port on which all the devices will listen.
Message Security
I thought about security for quite some time when implementing this program. Originally all this program was to do was mute and unmute my computer when my phone became active. I feel confident in the security of the networks that I use and the benign intent of the users on those networks. But professional responsibility inclines me to consider otherwise during the development of this program. Security became even more important when the caller-ID information was added to messages that this program transmits. By encrypting the messages, I was able to address my concerns with message security. I am using the compact framework implementation of the Rijndael encryption algorithm (also known as AES). Upon starting, the program will look for a saved encryption key in its directory. If one isn't found, then it will create a new encryption key. The key must be copied off of the device and imported into all of the computers with which the phone will share status. The desktop portions of this system have an option in the file menu to import the encryption key.
void SendMessage(byte[] message)
{
for (int i = 0; i < _clientList.Count; ++i)
{
try
{
UdpClient client = _clientList[i];
byte[] encryptedMessage = Encrypt(message);
client.Send(encryptedMessage, encryptedMessage.Length);
}
catch (Exception )
{
}
}
}
byte[] Encrypt(byte[] message)
{
MemoryStream ms = new MemoryStream();
CryptoStream encStream = new CryptoStream(ms, _transform, CryptoStreamMode.Write);
encStream.Write(message, 0, message.Length);
encStream.FlushFinalBlock();
encStream.Close();
return ms.ToArray();
}
Receiving A Message
The desktop portion of this solution will sit waiting for messages of phone activity to process. Receiving messages on the UdpClient
class will involve a call to its Read
method. The Read
method on this class is a blocking method; the thread on which it is called will be halted until there is a message to read. In a single threaded application trying to read from a UdpClient
on the UI thread would result in the program appearing to be in a frozen state until a message arrives. To prevent this from happening, the calls to UdpClient.Read
must be performed on a different thread.
The management of the listening thread and the functionality to receive messages is packaged in its own class which implements the IMessageListener
interface. The interface lists two methods (Activate
and Deactivate
) to start and stop listening for messages, a property to assign an ICryptoTransform
object for decrypting the messages, and event handlers to provide notification for incoming messages and changes in the listening state of the implementing class. The interface is defined below:
public interface IMessageListener: IDisposable
{
ICryptoTransform CryptoProvider { get; set; }
void Activate();
void Deactivate();
event EventHandler<messagereceivedeventargs> MessageReceived ;
event EventHandler<listenerstatechangedeventargs> ListenerStateChanged ;
}</listenerstatechangedeventargs></messagereceivedeventargs>
The implementation of Activate
and Deactivate
are the only methods whose definitions are likely to vary in implementations of the IMessageListener
interface. The MessageListener abstract
class contains the parts of the listener implementation that are not likely to vary. The details that are specific to listening on a UDP port are defined in the UdpMessageListener
class. If you wanted to implement another listener, you would need to:
- Create a class that inherits from
MessageListener
- Define the
Activate
and Deactivate
methods
- Appropriately call the
MessageReceived
and ListenerStateChanged
events
Once properly implemented, this class will pass back the unencrypted messages through the MessageReceivedEventArgs
argument of the MessageReceived
event handler.
Processing the Messages
The processing of the messages is handled by a switch
statement. The statement will usually change the mute state of the computer's sound device and update status text to indicate the state of the phone. If the message is a CallerID
message, then the device will show the CallerID
information through a notification balloon.
switch (e.MessageType)
{
case MessageType.Active:
if (!_senderActive)
previouslyMuted = device.AudioEndpointVolume.Mute;
_senderActive = true;
device.AudioEndpointVolume.Mute = true;
UpdateStatus("Phone Active");
break;
case MessageType.Inactive:
_senderActive = false;
device.AudioEndpointVolume.Mute = previouslyMuted;
UpdateStatus("Phone Inactive");
break;
case MessageType.Mute:
device.AudioEndpointVolume.Mute = previouslyMuted = true;
UpdateStatus("Mute request received");
break;
case MessageType.Unmute:
previouslyMuted = false;
device.AudioEndpointVolume.Mute = false;
UpdateStatus("Unmute request received");
break;
case MessageType.CallerID:
callerIdNotification.Visible = true;
callerIdNotification.ShowBalloonTip(9000,"Incoming Call",String.Format
("Call received from {0} ({1})", e.Name, e.Number),ToolTipIcon.Info);
break;
default:
break;
}
Call Announcer
The "Call Announcer" program will verbally announce the caller and keep a list of the calls that were received. The actual code behind this program is incredibly simple. The project uses the same MessageListener
class. To verbalize the name of the caller, the program makes use of the SpeechSynthsizer
class from the .NET 3.0 Framework. This class relies on native functionality that is included with Windows Vista, but is not a part of Windows XP. To test this program, you must use a computer that is running Vista. The class has two methods for rendering verbalized text names Speak
and SpeakAsync
. As their names suggest, Speak
is a blocking call whereas SpeakAsync
will allow your code to continue executing while the speaking occurs.
Marshaling Calls to UI Thread
Throughout the code, you will occasionally see methods that call themselves through Control.Invoke
. This pattern is present because no thread other than the main thread may modify a UI element. Calls that occur on other thread will be unable to change the text displayed in a textbox
, the checked state of a checkbox
, or any other visual element. Controls have the property Control.InvokeRequired
that returns true
when called by a secondary thread and false
when called on the UI thread. I use Control.InvokeRequired
to determine whether or not a call needs to be marshalled to the main thread. If marshalling is necessary, then Control.Invoke
is used to make the call occur on the proper thread. The following shows the UpdateStatus
method using this pattern to update the text on a status bar:
void UpdateStatus(String statusMsg)
{
if (this.InvokeRequired)
{
this.Invoke(_updateText, new object[] { statusMsg });
}
else
{
txtFeedback.Text = statusMsg;
}
}
Future Enhancements
With the initial version of this code, my goal was simple; I simply wanted the code to perform its function. Now that I've accomplished this goal, there are some usability enhancements that could be added such as automatically transferring the encryption key when the phone is docked to the machine. In a few weeks, I should be receiving a Bluetooth adapter and a Windows Mobile Standard phone and may modify the program to work on phones without touch screens and over Bluetooth in addition to UDP.
History
- 2008 December 4 - Initial publication