WM有约II(五):区别对待不同的手机号码
Written by Allen Lee
不要删除我的短信息!
试想一下,某天你的朋友给你发了下面这条短信息:
在干嘛呢?早上收到你的东西了,今晚有空出来聊聊吗?
而此时应用程序正在运行,将会发生什么事呢?没错,你的朋友将会收到自动回复,但是,你却永远看不到这条短信息,因为它已经被删除了。能够想象吗,在某些情况下,错过上面这条短信息可能会引发一些不必要的误会……
为此,我们需要区分默认的查询短信息和自定义的查询短信息,前者在处理后应该自动删除,而后者则应该保留。因为默认的查询短信息都是以"{Trombone:"开头的,所以我们只需一个MessageInterceptor就可以截获所有默认的查询短信息了:
代码 1
在继续之前,我要就某些类型的命名变更说明一下,首先是IInterceptionProcessor接口重命名为ISmsProcessor,它的Process方法的sms参数类型改为SmsMessage;接着是InterceptionManager类重命名为InterceptorManager,里面的m_InterceptionProcessors私有成员重命名为m_SmsProcessors。
在代码1里,我们需要从截获的短信息中提取出短信息处理器的名字,获取对应的短信息处理器对象,然后把短信息交由它处理,其中,提取名字的工作由ExtractSmsProcessorName方法负责:
代码 2
至于自定义的查询短信息,我们仍然通过配置文件来存放MessageInterceptor的配置信息(旧的配置信息可以扔了):
代码 3
这些MessageInterceptor的创建和之前的(参见《WM有约II(二):持续改进》的代码2)一样,除了InterceptionAction属性的值需要改为InterceptionAction.Notify,这样,自定义的查询短信息就不会被删除了。
好了,现在用Cellular Emulator发送一个短信息看看:
图 1
嗯,自动回复功能正常,再来看看短信息有没有保留下来:
图 2
很好!这样我们就不会错过任何重要的信息了。
这是谁的手机号码?
到目前为止&#xff0c;无论谁发送查询短信息&#xff0c;应用程序都会自动回复&#xff0c;这样不好&#xff0c;我希望应用程序忽略陌生的手机号码&#xff0c;即如果发送查询短信息的人不在我的联系人里&#xff0c;就不要回复了。那么&#xff0c;如何判断某个人是否在我的联系人里呢&#xff1f;我们可以通过SmsMessage.From属性获取一个Recipient对象&#xff0c;它存储了发送方的信息&#xff0c;包括发送方的姓名&#xff08;Recipient.Name属性&#xff09;和地址&#xff08;Recipient.Address属性&#xff09;。Recipient.Address属性的值可能是一个手机号码&#xff08;例如&#43;8613713149394&#xff09;&#xff0c;也可能是一个由姓名和手机号码组合而成的字符串&#xff08;例如"Stephen Chou" <&#43;8613713149394>&#xff09;&#xff0c;这取决于发送方是否在联系人里&#xff0c;所以你不能简单地把它的值和Contact.MobileTelephoneNumber属性的值进行比较。Recipient.Name属性的值可能是一个手机号码&#xff0c;也可能是一个姓名&#xff0c;当发送方不在联系人里时&#xff0c;它的值就是发送方的手机号码&#xff0c;而当发送方已在联系人里时&#xff0c;它的值则和Contact.FileAs属性的值相等&#xff0c;所以我们可以考虑把Recipient.Name属性的值和Contact.FileAs属性的值进行比较。据此&#xff0c;我们可以通过如下代码判断发送方是否在联系人里&#xff1a;
代码 4
当应用程序截获一条查询短信息时&#xff0c;它首先要判断发送方是否有权执行查询操作&#xff0c;如果没有&#xff0c;忽略请求&#xff0c;否则&#xff0c;获取查询结果&#xff0c;然后回复发送方&#xff0c;整个过程如下图所示&#xff1a;
图 3
不同的查询操作只有中间那步是不同的&#xff0c;于是&#xff0c;我们不妨考虑在ISmsProcessor接口和PingStatusProcessor类、PingScheduleProcessor类之间添加一个SmsProcessorBase抽象类&#xff1a;
图 4
SmsProcessorBase.Process方法负责定义逻辑框架以及实现IsAuthorized和SendResponse两个方法&#xff1a;
代码 5
剩下的就是处理PingStatusProcessor类、PingScheduleProcessor类和SmsProcessorBase抽象类之间的继承关系了……
不知不觉又到了测试的时候了&#xff0c;首先准备若干联系人&#xff1a;
图 5
接着&#xff0c;通过Cellular Emulator使用如下手机号码发送查询短信息&#xff1a;
- 15933449394
- 13122113344
只有第一个手机号码得到自动回复&#xff0c;第二个已被忽略了&#xff1a;
图 6
谁查询过我的状态/安排&#xff1f;
到目前为止&#xff0c;查询短信息的截获和状态/安排的自动回复都是应用程序悄悄完成的&#xff0c;我们无从得知背后究竟发生了什么事情。人有时候很矛盾&#xff0c;一方面希望事情最好能够自动完成&#xff0c;另一方面又害怕自动化会把控制权夺走。自动化可以解放我们的注意力&#xff0c;但同时也会产生认知空白&#xff0c;从而导致情绪上的焦虑。于是&#xff0c;我们不难想象&#xff0c;故事的发展会产生一个新的需求——告诉我谁查询过我的状态/安排以及应用程序如何处理每个查询请求。不用我说你也知道&#xff0c;这个需求会导致用户界面的改变&#xff08;下面那个灰色方框是DataGrid控件&#xff09;&#xff1a;
图 7
这些历史纪录将会存储在InterceptionHistory.xml里&#xff0c;这个文件的内容结构如下所示&#xff1a;
代码 6
其中&#xff0c;每个interception元素将会对应一个Interception对象&#xff0c;它的定义如下&#xff1a;
代码 7
历史纪录的管理是由InterceptionHistory类来负责的&#xff0c;因为它的实现方式基本上是照搬StatusTextManager的&#xff08;包括应用Singleton模式和使用BindingList
那么&#xff0c;我们应该在什么时候添加历史纪录&#xff1f;想想看&#xff0c;什么时候我们能够得到一条纪录所需的全部数据&#xff1f;没错&#xff0c;答案就在SmsProcessorBase.Process方法里&#xff1a;
代码 8
最后&#xff0c;我们只需要把InterceptionHistory.Interceptions属性&#xff08;类型为BindingList
代码 9
现在&#xff0c;我们再做一次刚才那个测试&#xff0c;不过这此我们把目光投向应用程序的主界面上&#xff1a;
图 8
嗯&#xff0c;很好&#xff0c;不过有个小小的问题&#xff0c;就是Time那列只显示日期&#xff0c;没有时间&#xff0c;我不知道如何控制DataGrid按照特定格式显示DateTime&#xff0c;有人知道怎样做吗&#xff1f;
你还想要什么&#xff1f;
你上次不是说还有个什么白名单吗&#xff1f;怎么我没看到&#xff1f;噢&#xff0c;是的&#xff0c;我没有忘记&#xff0c;因为那个需求将会引出一连串其它需求&#xff0c;比如说&#xff0c;执行某个查询请求需要什么权限&#xff0c;如何指定&#xff0c;发送方拥有什么权限&#xff0c;这些权限又是如何界定的等等。另外&#xff0c;陌生人似乎也比较无奈&#xff0c;因为他/她什么也不能做&#xff0c;是否应该考虑给他/她一个"注册"的机会呢&#xff1f;