65.9K
CodeProject 正在变化。 阅读更多。
Home

Windows Mobile 通话静音器

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.91/5 (11投票s)

2008 年 12 月 4 日

CPOL

9分钟阅读

viewsIcon

61103

downloadIcon

1006

用于在电脑上显示来电显示信息并在通话时静音电脑声音的程序。

引言

通过一点C#代码,我为我的Windows Mobile手机构建了一个解决方案,当手机响铃时,它会在我的桌面上显示来电信息,并在手机通话时静音我的电脑,或者口头播报来电者的姓名。通话结束后,电脑会自动取消静音。

此程序的背后故事

我的电脑上有很多程序会出于各种原因发出声音。有即时消息或电子邮件的通知,有计划的提醒,有媒体播放器,还有偶尔意外发出声音的网络广告。无论声音的目的是什么,我都不希望在通话时播放。如果我忘记静音电脑,然后收到了一个意外的、非常响亮的通知,这会影响到电话的另一端。当我静音电脑时,我可能会忘记取消静音,从而错过新消息和提醒的通知。为了解决这个问题,我决定让我的手机在通话时静音我的电脑,并在通话结束后取消静音。

我认为有必要展示手机状态信息的使用,使其不仅限于静音电脑,由此产生了两个附加功能。我为该系统的桌面客户端添加了功能,可以在通知气球中显示来电信息。我还添加了一个程序,该程序利用Vista的语音合成功能来口头播报来电者的姓名。

要求

运行在手机上的代码部分需要Windows Mobile Professional设备(带触摸屏的设备)。运行在桌面上的程序需要Windows Vista。此代码适用于Windows XP。

支持文章

为了处理电脑音频系统的静音和取消静音,我使用了Ray M.关于Vista Core Audio API主音量控制的文章中的代码。如果您计划使用此代码并拥有64位系统,请阅读他文章的评论部分;其中列出了您必须对代码进行的一项更改,否则代码将无法正常工作。

查询手机状态

对于这个程序,我们必须能够查询手机的状态。这包括检测来电、知道来电者是谁、检测是否提供数据连接等等。所有这些信息都通过状态和通知代理进行跟踪。在.NET Compact Framework中,状态和通知代理可以通过SystemState类进行访问。如果您查看SystemState类的成员,您会发现它可用于获取数据连接是否存在的信息、有关当前和上一次通话的信息以及大量的其他信息。

SystemState类易于使用。创建该类的实例时,开发者必须指定需要通知更改的元素。然后,每次该值发生更改时,都会引发一个事件。本文档附带一个名为PhoneStatus的示例程序。它使用SystemState类来检测手机是否处于活动状态,是否存在数据连接,并将显示来电者的姓名。

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;
}

发送静音和取消静音消息

除了自动静音电脑外,用户还可以使用手机来静音或取消静音电脑。发送静音和取消静音消息的界面只需一张图片即可说明。

将状态发送到桌面

我使用的Windows Mobile Professional手机有两个适配器可以与我的电脑通信;一个是WiFi适配器,另一个是蓝牙适配器。该应用程序通过UDP进行通信。因此,它可以在带有WiFi适配器的手机上运行,也可以在蓝牙个人区域网络上的设备上运行。但是我的电脑没有蓝牙功能,所以我只使用手机的WiFi适配器测试过这个程序。最初,我只需要程序发送四条消息。为了表示手机的活动,有“active”和“inactive”消息。程序还允许发送“mute”和“unmute”消息来更改计算机音频硬件的状态。后来我决定添加“CallerID”消息。

选择接收机器

有三种方法可以通过UDP将消息发送到计算机组。可以将消息广播到网络节点上的所有计算机,可以将其发送到组播组(在这种情况下,对某些消息感兴趣的计算机可以注册以接收它们),或者可以将消息单独发送到每台计算机。UDP广播已被弃用;它在IPv4上仍然有效,但它不是IPv6标准的一部分。组播本应更理想,但我发现我连接的一些路由器不允许我的机器发送组播流量。与其说服路由器所有者为我的个人需求重新配置路由器,不如直接将信息发送到应该接收它的机器。下面的UI允许用户输入应该更新手机状态信息的设备的名称或IP地址,并指定所有设备将监听的端口。

Device Selection API

消息安全性

在实现此程序时,我花了很多时间考虑安全性。最初,这个程序只需要在我手机激活时静音和取消静音我的电脑。我对所使用的网络的安全性和网络用户的良性意图很有信心。但在开发此程序的过程中,职业责任促使我考虑其他因素。当来电显示信息被添加到此程序传输的消息中时,安全性变得更加重要。通过加密消息,我得以解决消息安全问题。我使用的是.NET Compact Framework实现的Rijndael加密算法(也称为AES)。程序启动时,它会在其目录下查找已保存的加密密钥。如果找不到,它将创建一个新的加密密钥。该密钥必须从设备复制并导入到手机将共享状态的所有计算机中。该系统的桌面部分在文件菜单中有一个导入加密密钥的选项。

void SendMessage(byte[] message)
{
    for (int i = 0; i < _clientList.Count; ++i)
    {
        //attempt to send each message to the clients individually
        try
        {
            UdpClient client = _clientList[i];
            byte[] encryptedMessage = Encrypt(message);
            client.Send(encryptedMessage, encryptedMessage.Length);
        }
        catch (Exception )
        {
            //If a message could not be sent simply do nothing. 
        }
    }
}

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();
} 

接收消息

此解决方案的桌面部分将等待要处理的手机活动消息。使用UdpClient类接收消息将涉及调用其Read方法。该类的Read方法是一个阻塞方法;调用它的线程将暂停,直到有消息可读。在单线程应用程序中,在UI线程上调用UdpClient会使程序在收到消息之前看起来处于冻结状态。为防止这种情况发生,必须在单独的线程上执行对UdpClient.Read的调用。

监听线程的管理以及接收消息的功能被打包在一个实现了IMessageListener接口的类中。该接口列出了两个方法(ActivateDeactivate)用于启动和停止监听消息,一个属性用于分配ICryptoTransform对象以解密消息,以及事件处理程序以提供传入消息和实现类监听状态变化的通知。该接口定义如下:

public interface IMessageListener: IDisposable
{
    ICryptoTransform CryptoProvider { get; set; }
    void Activate();
    void Deactivate();
    event EventHandler MessageReceived ;
    event EventHandler ListenerStateChanged ;
}

ActivateDeactivate的实现是在IMessageListener接口的实现中可能变化的部分。MessageListener abstract类包含了监听器实现中不太可能变化的部分。监听UDP端口的细节在UdpMessageListener类中定义。如果您想实现另一个监听器,您需要:

  • 创建一个继承自MessageListener的类
  • 定义ActivateDeactivate方法
  • 适当地调用MessageReceivedListenerStateChanged事件

一旦正确实现,该类将通过MessageReceived事件处理程序的MessageReceivedEventArgs参数传递未加密的消息。

处理消息

消息的处理由一个switch语句处理。该语句通常会更改计算机声音设备的静音状态,并更新状态文本以指示手机的状态。如果消息是CallerID消息,则设备将通过通知气球显示CallerID信息。

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;
}

来电播报

“来电播报”程序将口头播报来电者,并维护一个已接收电话的列表。此程序背后的实际代码非常简单。该项目使用了相同的MessageListener类。为了口头播报来电者的姓名,该程序使用了.NET 3.0 Framework的SpeechSynthesizer类。该类依赖于Windows Vista附带的原生功能,但是Windows XP的一部分。要测试此程序,您必须使用运行Vista的计算机。该类有两种渲染口头播报名称的方法:SpeakSpeakAsync。顾名思义,Speak是一个阻塞调用,而SpeakAsync允许您的代码在语音播放时继续执行。

将调用封送到UI线程

在代码中,您偶尔会看到通过Control.Invoke调用自身的方法。出现这种模式是因为除了主线程之外,任何线程都不能修改UI元素。在其他线程上发生的调用将无法更改textbox中显示的文本、checkbox的选中状态或任何其他视觉元素。控件具有Control.InvokeRequired属性,当由辅助线程调用时返回true,当在UI线程上调用时返回false。我使用Control.InvokeRequired来确定是否需要将调用封送到主线程。如果需要封送,则使用Control.Invoke来确保调用在正确的线程上发生。以下是使用此模式更新状态栏文本的UpdateStatus方法的示例:

void UpdateStatus(String statusMsg)
{
    if (this.InvokeRequired)
    {
        this.Invoke(_updateText, new object[] { statusMsg });
    }
    else
    {
        txtFeedback.Text = statusMsg;
    }
}

未来的增强

对于此代码的初始版本,我的目标很简单;我只是希望代码能够正常工作。现在我已经实现了这个目标,有一些可用性增强功能可以添加,例如在手机连接到计算机时自动传输加密密钥。几周后,我应该会收到一个蓝牙适配器和一个Windows Mobile Standard手机,可能会修改程序使其在没有触摸屏的手机上以及通过蓝牙(除了UDP)工作。

历史

  • 2008年12月4日 - 首次发布
© . All rights reserved.