Creating Call Events in an Emulation Environment

 

Reed Robison
Microsoft

April 2004

Applies to:
   Windows Mobile-based Smartphones
   Windows Mobile-based Pocket PC Phone Editions
   Windows Mobile 2003 Second Edition software for Pocket PCs
   Windows Mobile 2003 Second Edition software for Smartphones
   Microsoft® Visual Studio® .NET 2003
   Microsoft eMbedded Visual C++® 4.0 SP3 (or later)
   Microsoft ActiveSync®

Summary: The Smartphone 2003 and Pocket PC 2003 SDKs include an emulation environment with virtual radio support. In this article, we take advantage of the emulator's virtual radio to create events like incoming SMS messages and phone calls. This is a useful way to help develop applications that respond to these events without using an actual device. (19 printed pages)

Download CallEvents.msi from the Microsoft Download Center.

Contents

Overview
Requirements
Fake Radio Interface Layer
Sending an SMS Message
Intercepting an SMS Message
Creating a Phone Call Event
Sending Events from the Desktop
Conclusion
Appendix

Overview

A powerful new feature of the Windows Mobile development platform is the ability to programmatically intercept SMS message. SMS messages are a simple and convenient way to provide notifications and create custom actions for all kind of events. Unfortunately, not everyone has a surplus of devices and data plans for their developers to experiment with SMS. Since some of the new 2003 emulators include a Virtual Radio, this begs the question, "How can I send SMS messages to my emulation environment?" This is a good question that did not have a good answer… until now.

The Windows Mobile team has encapsulated the emulator's fake radio interface layer. In this article, we will take advantage of this library to send SMS messages to our emulation environment and perform some other useful tricks.

Requirements

Included with this article are managed and unmanaged source samples (SendSMS) for Smartphone 2003 and Pocket PC Phone Edition 2003. For the unmanaged samples written in C++, you will need Microsoft® eMbedded Visual C++® 4.0 SP2. For the managed C# samples, you will need Microsoft Visual Studio® .NET 2003. In both cases, you need to have the 2003 Platform SDKs installed. Most of these tools and SDKs can be downloaded from the Microsoft download center:

Smartphone Emulator Security

The virtual radio APIs on the Smartphone emulator require privileged access. Before you can run these samples on your Smartphone emulator, you must sign them with a privileged certificate that the emulator recognizes. The following steps are not required for the Pocket PC.

To learn more about the Windows Mobile-based Smartphone Application security model, read A Practical Guide to the Smartphone Application Security and Code Signing Model for Developers.

For instructions on configuring your development environment to sign components of FakeRIL please refer to the appendix.

Fake Radio Interface Layer

The fake radio interface layer is encapsulated into FRilDLL.dll and included with the sample download for this article. The unmanaged samples require FRilDll.dll to be on the device so be sure to copy the file over (using Remote File Viewer) before attempting to run the SendSMS sample. For the managed samples, it is included with the project and will automatically be deployed with the executable.

For the purpose of this article, we will look at three functions exported by FrilDll:

// Init() initializes the fake radio for use
void Init();  
// ReceiveSMS simulates receiving an SMS message
void ReceiveSMS(LPTSTR pszMessage, LPTSTR pszPhoneNumber);
// ReceiveCall simulates receiving an incoming phone call
void ReceiveCall(LPTSTR pszPhoneNumber, DWORD dwAddressType,   DWORD dwRestriction,   DWORD dwDuration);

To take advantage of these APIs, we simply need to load FRilDll into our process, call the Init() function, and then call ReceiveSMS or ReceiveCall.

To load the library dynamically in C++ we use the LoadLibrary API:

typedef int (STDAPICALLTYPE *PReceiveSMS)(LPTSTR , LPTSTR );
PReceiveSMS lpReceiveSMS;

// Dynamically load and initialize fake radio interface layer
HINSTANCE hFRIL =  LoadLibrary(_T("FRilDll.dll"));
if (hFRIL)
    lpReceiveSMS = (PReceiveSMS)GetProcAddress(hFRIL,_T("ReceiveSMS"));
// … Do something impressive!
if (hFRIL) 
   FreeLibrary(hFRIL);   

With C#, we'll use P/Invoke to call down into native code, so add System.Runtime.InteropServices and then simply add a reference to the exports using DllImport:

using System.Runtime.InteropServices;

[DllImport("FRilDll.dll", EntryPoint="Init", SetLastError=true)] 
private extern static void FakeRILInit();

[DllImport("FRilDll.dll", EntryPoint="ReceiveSMS", SetLastError=true,  CharSet=CharSet.Unicode)]
private extern static void ReceiveSMS(string Message, string PhoneNumber);

[DllImport("FRilDll.dll", EntryPoint="ReceiveCall", SetLastError=true,  CharSet=CharSet.Unicode)]
private extern static void ReceiveCall(string PhoneNumber,int AddressType,int Restriction,int Duration);

Note The first call to ReceiveSMS will automatically call Init() if the radio has not yet been initialized.

Sending an SMS Message

Once the fake radio has been loaded and initialized, all you have to do is call ReceiveSMS.

In C++ this would look like:

   lpReceiveSMS(szMsg, szFrom);

In C#, it simply looks like:

   ReceiveSMS(Message, PhoneNumber);

Figure 1 shows the display for sending an SMS message, and Figure 2 shows the display for receiving an SMS message.

Figure 1. Sending an SMS message to the emulator

Figure 2. Receiving an SMS message on the emulator

Intercepting an SMS Message

Now that you can send SMS messages, let's try to intercept one. Load up the MAPIRule sample (included in the 2003 SDK Win32 Samples directory). This sample demonstrates how to implement a MAPI Rule Client, a COM object which can handle / filter incoming MAPI messages. SMS is supported as a transport for Rule Clients and by default, this sample is designed to intercept any message with "zzz" in the contents.

Build this sample and then send yourself a message with "zzz" in the subject. Notice how this message is intercepted and a MessageBox is displayed upon receipt, as shown in Figure 3.

Note The MAPIRule sample requires privileged access.

Figure 3. Intercepting an SMS message

Creating a Phone Call Event

SendCallEvents is another sample that demonstrates using the fake radio interface to generate both SMS message and Phone Call events on the emulator. We could simply use the APIs discussed in the previous section and make direct calls from an application on the emulator, but why not have fun and learn something new?

Let's send events from your desktop to the emulator, as shown in Figure 4!

Figure 4. Desktop Call Events Application

Sending Events from the Desktop

To accomplish this, SendCallEvents uses some handy libraries, included as a component of the Visual Studio.NET 2003 development environment. With a little help from the DeviceConnectivity namespace, we are able to push files to the emulator and startup our worker process (FrilStub.exe) which handles the fake radio calls.

The first step is to add project references to the appropriate libraries — ConMan.dll, ConManServer.dll, and ConManDataStore.dll. These files are typically located in the Microsoft Visual Studio .NET 2003\CompactFrameworkSDK\ConnectionManager\Bin directory. Once you have added a reference to your project, and then just include the DeviceConnectivity namespace. We create a simple class and few key methods to do most of the work:

using Microsoft.VisualStudio.DeviceConnectivity;

namespace Microsoft.VisualStudio.DeviceConnectivity
{
   public class RemoteToolHelper
   {
      private Microsoft.VisualStudio.DeviceConnectivity.ConMan m_conman = null;      
      private IServer m_deviceAPI = null;
      private IConManDataStore m_ds = null;
      private IPlatform m_platform = null;
      private IDevice m_device = null;

      public RemoteToolHelper() {}

      // Setup a connection to the device/emulator
      public IServer ConnectToDevice(String platformName, String deviceName)
      {
         m_conman = new Microsoft.VisualStudio.DeviceConnectivity.ConMan();
         m_conman.SetLocale(1033);  // English
         m_conman.Initialize();

         m_ds = m_conman.DataStore;         
         m_platform= m_ds.Platforms.FindPlatformByInvariantName(platformName);
         m_device = m_platform.FindDeviceByInvariantName(deviceName);
      
         m_deviceAPI = (IServer)(m_conman.Connect(m_device));
         if (!m_deviceAPI.EstablishConnection())
         {
            throw new Exception("Could not connect to - " + deviceName + ".  Please retry and reset the device if the problem pesists");
         }
         return m_deviceAPI;
      }


      // Deploy a file to the connected device
      public void DeployRuntimePieceToDevice(String remoteDirectory,  String fileName)
      {
         String [] aszTemp = new String[1];
         aszTemp[0] = fileName;
         DeployRuntimePieceToDevice(remoteDirectory, aszTemp);
      }


      // Deploy files to the connected device
      public void DeployRuntimePieceToDevice(String remoteDirectory,  String [] fileNames)
      {
         bool bPiecesExist = true;
         if (m_deviceAPI == null)
         {
            throw new Exception("You must connect to the device before deploying files, call ConnectToDevice first");
         }
         
         for(int i = 0; i < fileNames.Length; i++) // Check to see if files already exist
         {
            bPiecesExist &= m_deviceAPI.FileExists(remoteDirectory + "\\" + fileNames[i]);
         }

         //Deploy the files
         if (!bPiecesExist)
         {
            //Create the directory, if it's not there the file copy fails
            try
            {
               m_deviceAPI.CreateDirectory(remoteDirectory);
            } 
            catch {}

            //Copy the files
            try
            {
               String localPath = System.Reflection.Assembly.GetExecutingAssembly().Location;
               localPath = localPath.Substring(0, localPath.LastIndexOf("\\"));

               for(int i = 0; i < fileNames.Length; i++)
               {
                  m_deviceAPI.SendFile(localPath + "\\" + fileNames[i],  remoteDirectory + "\\" + fileNames[i], QUEUING_ACTION.CREATE, CUSTOM_ATTRIBUTE.None,null);
               }
            }
            catch
            {
               throw new Exception("Could not deploy the device components of the tool, please make sure the files are available and not already in-use.");
            }
         }
      }


      public TcpClient StartProgramAndConnect(String path, String program)
      {
         //Get Desktop IP Address to pass to the emulator
         System.Net.IPHostEntry iphost = System.Net.Dns.GetHostByName(System.Net.Dns.GetHostName());
         if (iphost.AddressList.Length < 1)
         {
            throw new Exception("Need an IP address to conenct to the emulator");
         }
         System.Net.IPAddress ipAddress = System.Net.Dns.Resolve(System.Net.Dns.GetHostName()).AddressList[0];

         //Start the program on the emulator and pass the Desktop IP as a command line param
         m_deviceAPI.StartProcess( path + "\\" + program, ipAddress.ToString());
         
         //Start a listener and wait for the emulator process to connect
         TcpListener tcpListener =  new TcpListener(ipAddress, 8001);
         tcpListener.Start();

         return tcpListener.AcceptTcpClient();
      }
   }
}

With a supporting RemoteToolHelp class in place, now we just need to connect to the device and send some instructions to our remote process:

// Connect to an emulator and setup the communication stream
private void btnConnect_Click(object sender, System.EventArgs e)
{
   //Show the wait cursor
   this.stpConnected.Text = "Connecting";
   Cursor currentCursor = this.Cursor;
   this.Cursor = Cursors.WaitCursor;

   //Connect to the device and copy supporting files over
   try
   {
      string selectedPlatform = cmbDevices.Items[cmbDevices.SelectedIndex].ToString();
      String [] aszFiles = new String[2];
      String szDestDir;

      if ( selectedPlatform.IndexOf("Smartphone") >= 0 )
      {
         m_remoteHelper.ConnectToDevice("Smartphone", "Smartphone 2003 Emulator (Virtual Radio)");
         szDestDir = "\\Storage\\FrilStub";
      }
      else
      {
         m_remoteHelper.ConnectToDevice("Pocket PC", "Pocket PC 2003 Phone Edition (Virtual Radio) Emulator");
         szDestDir = "\\Program Files\\FrilStub";
      }

      aszFiles[0] = "frilstub.exe"; // Emulator stub program
      aszFiles[1] = "frildll.dll"; // Fake radio interface layer

      Application.DoEvents();
      m_remoteHelper.DeployRuntimePieceToDevice(szDestDir, aszFiles);

      Application.DoEvents();
      m_deviceTcpClient = m_remoteHelper.StartProgramAndConnect(szDestDir,  "frilstub.exe");
   }
   catch (Exception ex)
   {
      MessageBox.Show(ex.Message,"Error", MessageBoxButtons.OK,  MessageBoxIcon.Error);
      this.Cursor = currentCursor;
      return;
   }

   // Enable/Disable applicable controls in the UI
   this.Cursor = currentCursor;
   btnSendMessage.Enabled = true;
   btnCall.Enabled = true;
   btnConnect.Enabled = false;
   cmbDevices.Enabled = false;

   this.stpConnected.Text = "Connected";
   this.stpConnectedIcon.Icon = this.m_icConnected;
   this.Cursor = currentCursor;
}

In order to send a call event to our remote stub process, we simply send a call (or SMS) message and parameters via the TcpClient stream that we established after deploying to the emulator.

// Send Incoming Call Event to emulator
private void btnCall_Click(object sender, System.EventArgs e)
{
   try
   {
      Int32.Parse(this.txtDuration.Text);
   }
   catch
   {
      MessageBox.Show("Call duration must be a valid integer", "Message", MessageBoxButtons.OK, MessageBoxIcon.Information);
      return;
   }

   try
   {
      // Get the reader/writer stream to the emulator
      ParamReader reader = new ParamReader(m_deviceTcpClient.GetStream());
      ParamWriter writer = new ParamWriter(m_deviceTcpClient.GetStream());

      // Write the event information to the stream
      writer.WriteInt((int)(RILFunctionCall.ReceiveCall));
      writer.WriteString(this.txtCallNumber.Text);      
      writer.WriteInt(this.cmbAddressType.SelectedIndex);
      writer.WriteInt(this.cmbRestriction.SelectedIndex);
      writer.WriteInt(Int32.Parse(this.txtDuration.Text));

      // Get the result
      if (reader.ReadInt() > 0)
         this.stpMessageBar.Text = "Voice Call Initiated";
      else
         this.stpMessageBar.Text = "Voice Call Failed";
   }
   catch 
   {
      MessageBox.Show("Communication with the emulator has failed.   If you have closed the emulator, please restart this utility and re-connect.");
   }
}

Now let's take a look at what is happening on the emulator, as shown in Figure 5. FrilStub is a simple program that establishes a communication session with our desktop over a TcpClient stream and leverages the APIs we learned about in the first example to send events to the fake radio. On the desktop, we can then use this stream to send commands to the emulation environment.

Figure 5. FRilStub status display

private void Form1_Load(object sender, System.EventArgs ea)
{
   lblHost.Text = ipHost.ToString();
   statusMsg = "Initializing...";

   // Setup a communication thread back to the desktop
   ThreadStart del1 = new ThreadStart(ConnectToDesktop);
   Thread commThread = new Thread(del1);
   commThread.Start();
}

private void ConnectToDesktop()
{
   statusMsg = "Connecting...";
   lblStatus.Invoke(new EventHandler(UpdateUI));

   try 
   {
      int nCommand = 0;
      IPAddress ipDesktop = IPAddress.Parse(ipHost);
      TcpClient client = new TcpClient();
      client.Connect( new IPEndPoint(ipDesktop, 8001));
      statusMsg = "Ready";
      ParamReader reader = new ParamReader(client.GetStream());
      do 
      {
         lblStatus.Invoke(new EventHandler(UpdateUI));
         Application.DoEvents();

         nCommand = reader.ReadInt();
         switch ( nCommand )
         {
            case -1:
               statusMsg = "[Shutting Down]";
               break;
            case 1:
               statusMsg = "[SMS]";
               ParseReceiveMessage(client.GetStream());
               break;
            case 2:
               statusMsg = "[Phone Call]";
               ParseReceiveCall(client.GetStream());
               break;
            default:
               statusMsg = "[Unknown: " + nCommand.ToString() + "]";
               break;
         }
      }
      while ( nCommand > 0 );
      client.Close();         
   } 
   catch (InvalidOperationException)
   {
      MessageBox.Show("Unable to connect to Desktop at : {0}", ipHost);
   }
   catch (SocketException exc)
   {
      StringBuilder strB = new StringBuilder("");
      strB.Append(String.Format("Cannot connect to Desktop:  {0}\r\n",ipHost));
      strB.Append(exc.Message+"\r\n");
      strB.Append("Socket Error Code: " + exc.ErrorCode.ToString());
      MessageBox.Show( strB.ToString() );

   }
   statusMsg = "Disconnecting...";
   lblStatus.Invoke(new EventHandler(UpdateUI));
   Application.DoEvents();
   this.Close();
}

private void UpdateUI(object sender, EventArgs e)
{
   lblStatus.Text = statusMsg;
}


// Read call information from Desktop and initiate phone call
private void ParseReceiveCall(NetworkStream s) 
{
   ParamReader reader;
   ParamWriter writer;
   string callNumber;
   int restriction;
   int addressType;
   int duration;

   reader = new ParamReader(s);
   writer = new ParamWriter(s);
   try
   {
      callNumber = reader.ReadString();  // Read params from the stream
      addressType = reader.ReadInt();
      restriction = reader.ReadInt();
      duration = reader.ReadInt();
      // Call the fake radio interface
      fakeRil.receiveCall(callNumber,addressType,restriction,duration);
      writer.WriteInt(1);  // Write a success result
   }
   catch (Exception)
   {
      writer.WriteInt(-1);
      MessageBox.Show("Failed");
   }
}

// Read SMS message information from Desktop and send SMS
private  void ParseReceiveMessage(NetworkStream s) 
{
   ParamReader reader;
   ParamWriter writer;
   string smsMessage;
   string smsNumber;

   reader = new ParamReader(s);
   writer = new ParamWriter(s);
   try
   {
      smsMessage = reader.ReadString();  // Read params from the stream
      smsNumber = reader.ReadString();
      fakeRil.receiveSMS(smsMessage, smsNumber);
      writer.WriteInt(1);  // Write a success result
   }
   catch (Exception)
   {
      writer.WriteInt(-1);
   }
}

As you can see, the code running on the emulator just intercepts a message to identify the action (Call, SMS, etc.), reads the parameters off the stream, and passes the call down to the fake radio interface, as shown in Figure 6.

Figure 6. Call Event on the emulator

You may find yourself wanting to use SendCallEvents to test SMS applications you develop with eMbedded Visual C++. A convenient way to share an emulation environment is to use a tool like EmuAsCfg. EmuAsCfg is included with both the 2003 SDKs and also as a Windows Mobile Developer Power Toys. It enables you to connect to an emulator through Microsoft ActiveSync® which happens to be a convenient way to share an emulator between tools like eMbedded Visual C++ and Visual Studio .NET. For example, you build the MapiRule SDK sample to test intercepting SMS messages and use SendCallEvents to generate the test messages.

Conclusion

Virtual Radio support on the 2003 emulators can provide a convenient environment for developing SMS and call based applications. Be creative but most of all, Have Fun!

Appendix

Configuring Your Development Environment for Privileged Signing

  1. Double click on the TestCert_Privileged.pfx found in your SDK\Tools directory and choose "Next" through all of the default Import Wizard prompts. This will add the privileged test certificate to your certificate store used by eMbedded Visual C++ and the Signcode utility. By default, the SDK is installed into C:\Program Files\Windows CE Tools\wce420\SMARTPHONE 2003.

  2. To configure Visual Studio to automatically sign managed binaries with the emulator's privileged test certificate, change the references in Microsoft.smartphone2003.1.0.xsl (typically found in \Documents and Settings\All Users\Application Data\Microsoft\visualstudio\devices\addon) from TestCert_UnPrivileged to Test_CertPrivileged. If Visual Studio is open, you will need to restart and rebuild each application for these changes to take affect.

    Note This will sign all executables with a privileged cert.

Signing the Managed Sample

  1. For the managed desktop sample, run signCode.msi from the SDK\Tools directory and sign frildll.dll as well as frilstub.exe found in the sample directory Desktop\SendCallEvents\bin\debug directory. Use all of the default prompts, choosing "Smartphone 2003 Privileged Test Signing Authority" on the Signature Certificate step.
  2. For the managed Emulation sample, run signCode.msi from the SDK\Tools directory and sign frildll.dll found in the Emulation\Samples\Smartphone\Managed\SendSMS directory and rebuild the managed project.

Signing the Native Sample

For the native Smartphone samples, run signCode.msi from the SDK\Tools directory and sign frildll.dll found in the Emulation\FrilDll directory.

Before building each native eMbedded Visual C++ project, be sure to sign the application by specifying the "Smartphone 2003 Privileged Test Signing Authority" certificate from the Project's security settings and rebuild.

To verify is a file is signed correctly, open the file properties and examine the "Digital Signatures" tab to verify the privileged certificate.

Note Be sure that when you deploy your project, you remember to choose a version of the 2003 emulator that includes the Virtual Radio.