【SC1】短彩信功能设计修改说明
Version 0.1 (2013-01-22)
Author liuwei
目录
3.3. MessageBoxDeleteActivity 24
1. 信息发送流程
点击发送按钮触发发送,短信是个ImageButton,彩信是个带图片的TextView
在confirmSendMessageIfNeeded方法中,粗略的判断了联系人号码合法性,有数字就是合法的。合法就调用了sendMessage(true)方法,在sendMessage方法中调用了mWorkingMessage.send(mDebugRecipients),在send方法中对短彩信发送做了区分。
发送前流程如下:
1.1. 发送短信流程
调用了preSendSmsWorker(conv, msgText, recipientsInUI);方法,在此方法中调用了sendSmsWorker(msgText, semiSepRecipients, threadId);方法。
接着调用了SmsMessageSender.java类的sendMessage方法发送信息,SmsMessageSender.java在构造时通过getOutgoingServiceCenter(mThreadId)方法试图从收件箱中获取服务中心号码,留给子类所用。sendMessage方法调用了queueMessage(token)方法,在此方法中把短信存到sms数据库中,每一个联系人存一条,type=0。接着发广播到SmsReceiver.class中,收到广播后启动SmsReceiverService服务。
SmsReceiverService中调用hander处理信息,发短信的Action为ACTION_SEND_MESSAGE,调用了handleSendMessage()方法,接着sendFirstQueuedMessage()被调用,在sendFirstQueuedMessage()方法中取出了Uri.parse("content://sms/queued")的待发送数据,构建了SmsMessageSender的子类SmsSingleRecipientSender并调用其的sendMessage方法发送消息。sendMessage方法主要是使用SmsManager对短信进行长短信拆分并一个个发送。
发送成功或者失败都要走SmsReceiverService的handleSmsSent(intent, error);方法。
framework流程:
在SmsManagger的sendMultipartTextMessage方法中判断messages的size,等于1为短信,大于1为长短信,分别走不同流程。
sendTextMessage(destinationAddress, scAddress, parts.get(0),
sentIntent, deliveryIntent);
在此方法中调用
通过远程调用,被调用的类为IccSmsInterfaceManagerProxy.java,实际处理的类为IccSmsInterfaceManager.java。在IccSmsInterfaceManager的sendText方法中调用了
mDispatcher.sendText(destAddr, scAddr, text, sentIntent, deliveryIntent);
其中mDispatcher为类SMSDispatcher的对象。此处SMSDispatcher为子类GsmSMSDispatcher,IccSmsInterfaceManager为子类SimSmsInterfaceManager。
在GsmSMSDispatcher的sendText方法里调用了接着调用了sendSms(tracker)方法,最后调到了mCm是CommandsInterface接口的对象,在GsmSMSDispatcher构造时传进来了PhoneBase对象,PhoneBase对象构造时传进去了CommandsInterface对象。PhoneBase对象里的CommandsInterface接口其实就是Ril.java所实现的接口。也就是说Ril.java里的sendSMS方法最后会被调到。
短信发送流程图如下:
短信发送后的反馈流程图如下:
1.2. 发送彩信流程
调用了发送彩信,主要流程为:把一些基本信息存到发件箱内
接着查询彩信发件箱,所有发件箱内彩信的m_size字段的相加和如果大于4*300*1024,则unDiscard()使得mDiscarded为false,并调用mStatusListener.onMaxPendingMessagesReached()存草稿。
如果待发送的彩信没超出限制,则
获取uri,如果传进去的uri有id,则不变,否则返回带id的uri,主要通过
得到uri,persist方法把这个带id的uri进行数据更新,如果是不带id的uri,则是插入返回带id的uri。
获取uri后构建发送类,接着发送(sender.sendMessage(threadId))
在sendMessage方法里存了一条数据到pending_msgs数据表内。接着以id为键thread_id为值存到SendingProgressTokenManager中,然后启动TransactionService.class服务。如果发送的彩信是从草稿箱里去的,则通过触发器更新pending_msgs。
TransactionService.class服务中用hander处理消息
在onNewIntent方法里
判断了网络状态noNetwork,intent.getAction()得到的值是null,所以走if流程,以时间为条件获取pending_msgs表中所有时间(due_time)小于或等于当前时间并且err_type字段值小于等于10的数据。如果没网络,则给出提示、stopSelf(serviceId)并返回。
如果网络正常则构建TransactionBundle对象,此对象存储以msg_id与mms构成的uri及msg_type变换后的int值。调用launchTransaction(serviceId, transactionBundle, false);方法。接着又调用了hander的处理,key为EVENT_TRANSACTION_REQUEST。接着根据msg_type不同构建不同类型的Transaction子类对象。
开始发送彩信,先排除掉短信正在发送或者已发送的情况后再判断网络,如果网络良好则往下走,否则保存要发送的信息返回
如果没返回最后会走到开始发送:
正式开始处理发送,process()方法里启动一个线程,在线的run方法里构建了SendReq对象,调用
发送信息,每次发送不管成功与否都会更新data字段,根据response更新resp_st字段,如果发送成功了更新m_id(信息中心的?)字段,最后
把信息由发件箱移到 已发送。
发送彩信采用的是http的方式,sendPdu一直到最后调用了
发送。
对于发送前判断的网络没准备好而保存的数据,在onCreate中注册了广播接收者,当网络恢复时接收广播,最后调用
在processPendingTransaction方法中取出未发送的数据,继续走processTransaction流程。
彩信发送结果:
在SendTransaction中发送完彩信后返回结果数组 byte[] response ,解析返回值后更新mms数据,最后调用notifyObservers()方法,而在此类构建时 把observer类 RetryScheduler注册到此类中,最后在RetryScheduler的update方法中对返回结果处理(弹Toast)。
彩信发送流程图:
TransactionService内流程:
2. 新增功能说明
短彩信应用除了支持原有功能外,额外增加了一些功能。
2.1. 对网址链接的保存与打开
长按有网址的信息,在浮出来的列表中选择“加入书签”,选中要加入的网址后把内容交给browser处理,调用方法为:
Intent i = new Intent(ACTION_SAVE_BOOKMARK);
i.putExtra("url", items[which]);
i.putExtra("retainIcon", false);
startActivity(i);
长按有网址的信息,在浮出来的列表中选择“将http://XXX 添加到联系人”,则根据联系人提供的接口调用:
intent = ConversationList.createAddContactIntent(uriString);
intent.putExtra("phone_only", true);
String addContactString = getString(
R.string.menu_add_address_to_contacts, uriString);
menu.add(0, MENU_ADD_ADDRESS_TO_CONTACTS, 0, addContactString)
.setOnMenuItemClickListener(l).setIntent(intent);
单击有网址的信息,提示将会在浏览器中打开网址,如果确定则调用URLSpan的onClick方法打开网页,调用浏览器方法为:
Uri uri = Uri.parse(getURL());
Context context = widget.getContext();
Intent intent = new Intent(Intent.ACTION_VIEW, uri);
intent.putExtra(Browser.EXTRA_APPLICATION_ID, context.getPackageName());
context.startActivity(intent);
2.2. 对号码的保存与拨号、发短信
长按有电话号码的信息,如果号码在联系人中不存在,则浮出来的列表中有“将XXX添加到联系人”选项,选择此项后将调用:
Intent intent = new Intent(Intent.ACTION_INSERT_OR_EDIT);
intent.setType(Contacts.CONTENT_ITEM_TYPE);
if (Mms.isEmailAddress(address)) {
intent.putExtra(ContactsContract.Intents.Insert.EMAIL, address);
} else {
intent.putExtra(ContactsContract.Intents.Insert.PHONE, address);
intent.putExtra(ContactsContract.Intents.Insert.PHONE_TYPE,
ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE);
}
intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
长按有电话号码的信息,选择“呼叫XXX”后将调用:
String callBackString = getString(R.string.menu_call_back,
uriString);
Intent intentCall = new Intent(Intent.ACTION_CALL,
Uri.parse("tel:" + uriString));
intentCall.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
menu.add(0, MENU_CALL_BACK, 0, callBackString)
.setOnMenuItemClickListener(l)
.setIntent(intentCall);
长按有电话号码的信息,选择“发送信息至XXX”后将调用:
Intent intentSend = new Intent(Intent.ACTION_SENDTO,
Uri.parse("smsto:" + uriString));
intentSend.setFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET);
intentSend.putExtra(KEY_SMS_SENDTO, true);
2.3. 添加与插入联系人
在信息中按menu键,选择插入联系人,则会把选择的联系人以文本方式插入到信息框,调用:
Intent intent = new Intent(UI.MULTI_PICK_ACTION).
putExtra("cascading",new Intent(UI.MULTI_PICK_ACTION).setType(Phone.CONTENT_ITEM_TYPE).
putExtra("cascading",new Intent(UI.MULTI_PICK_ACTION).setType(Email.CONTENT_ITEM_TYPE)));
intent.putExtra("return_number", true);
startActivityForResult(intent,REQUESET_CODE_SELECT_CONTACTS_AS_TEXT);
新建信息,点击右上的联系人图标则会把选择的联系人添加到联系人框,调用:
Intent intent = new Intent(UI.MULTI_PICK_ACTION).
putExtra("cascading",new Intent(UI.MULTI_PICK_ACTION).setType(Phone.CONTENT_ITEM_TYPE));
ArrayList<Account> numbers = new ArrayList<Account>();
ContactList contacts = mRecipientsEditor.constructContactsFromInput(true);
for (Contact contact : contacts) {
Account p = new Account(contact.getName().replace(" ", ""), contact.getNumber().replace(" ", ""));
numbers.add(p);
}
intent.putExtra("exsit_contact", numbers);
startActivityForResult(intent, REQUESET_CODE_SELECT_CONTACTS);
2.4. 添加Vcard附件功能
点击信息中右上角的附件按钮,选择“联系人”,则从Contacts应用中选择一个联系人由Contacts应用把生成Vcard文件的联系人Uri返回给彩信,由彩信根据联系人uri转换成查询Vcard的Uri,调用如下代码请求选择联系人:
Intent intent = new Intent(Intent.ACTION_PICK);
intent.setType(ContactsContract.Contacts.CONTENT_TYPE);
((Activity)context).startActivityForResult(intent, requestCode);
当得到联系人的Uri后调用如下方法得到获取Vcard的Uri:
当得到Vcard的Uri后,保存附件到彩信本地时会根据此Uri调用openFileInputStream方法获取Vcard的数据流,此时Vcard还没生成文件,这样ContentProvider2中的openAssetFile方法将被调用,最后根据match id 值调用如下(生成Vcard采用google提供的库):
生成Vcard流程如下:
2.5. 对任何一种文件的附件支持
mms没有直接提供添加的功能,但用户可以在其他应用中通过分享文件方式调用此功能。对于不识别的文件接收后只能做保存操作,其中Vcalendar附件提供了载入日历功能但不提供直接添加Vcalender功能,需要去日历应用分享。
发送新增文件在smil中采用类型与标签如下:
文件类型 | 文件分类 | smil标签 |
Vcard | text/x-vCard | text |
Vcalendar | text/x-vCalendar | text |
不识别文件 | text/file | text |
2.6. 合并转发
进入一个至少有两封短信的会话,按menu键,选择“合并转发”,将会进入选择需合并的信息界面,选择完信息后,所选择的信息将会组合成一个新的正在编辑的信息。
选择“合并转发”后将会调用:
Intent intent = new Intent(this, SmsMergeForwardActivity.class);
long threadId = mConversation.getThreadId();
intent.putExtra("thread_id", threadId);
startActivity(intent);
此时将会进入SmsMergeForwardActivity这个Activity,如果,打开的会话不足两封短信,则
如果满足条件,用户选择要转发的信息后:
2.7. 转发号码
按menu进入设置,勾上“转发号码”后,转发短信时会在转发的信息开始部位添加“转发自:XXX”。转发最后都会调用handleForwardedMessage()方法,在此方法中获取设置并在message body中添加号码。
在handleForwardedMessage方法中获取被转发信息的发送者流程如下:
转发流程为:
2.8. 复制彩信第一页文字
长按彩信信息,选择“复制文字”,对于未接收的彩信,会复制彩信的下载地址,对于已经接收或者本地发送的彩信,会复制彩信第一页的文本。复制文字调用的方法为:
ClipboardManager clipboard = (ClipboardManager)getSystemService(Context.CLIPBOARD_SERVICE);
clipboard.setPrimaryClip(ClipData.newPlainText(null, str));
2.9. 重新发送
长按发送失败的信息,选择“重新发送”,失败的信息将会重新走发送流程。对于短信,先删除数据库中对应的数据,接着把长按短信的内容作为新信息的内容,调用sendMessage(true)发送信息。对于彩信,则把信息移到草稿箱,再调用sendMessage(true)发送信息。
2.10. 复制短信到sim卡
长按短信息,选择“复制到SIM卡”,则会将信息在SIM卡中保存一份。复制到SIM中的短信status主要分SmsManager.STATUS_ON_ICC_SENT、SmsManager.STATUS_ON_ICC_UNSENT、SmsManager.STATUS_ON_ICC_READ。对应短信中的发件箱信息、正在发送与发送失败信息、收件箱信息。
代码流程如下:
复制信息调用SmsManager的接口方法为:
此接口方法一直对应到ril.java中的copyMessageToIccEfWithResult(status, pdu, smsc)方法。
2.11. 单页查看彩信
点击彩信幻灯片附件,原本将进入幻灯片播放流程,新增加一个类ScrollSlideShowActivity,此类主要是提供单页浏览幻灯片功能,点击幻灯片后将进入此类,在menu菜单中选择播放能进入幻灯片播放流程。
通过Uri获取SlideshowModel对象:
mSlideshowModel = SlideshowModel.createFromMessageUri(this, mUri);
这是一个List容器,里面主要存储了SlideModel对象,而一个SlideModel对象代表着一页幻灯片,于是Uri对应的彩信附件信息就能得到了。
为了让一个窗口能显示所有附件,基层View用的是ScrollView,使显示的内容超出一屏幕时可以上下滑动。
在屏幕底部增加了一组ZoomControls控件,用于控制文字的缩放大小。
对于gif图片,用继承于ImageView的XImageView来显示,XImageView中实现了GifDecodeInterface接口。
2.12. 彩信发送重试机制
在发送彩信的service启动时注册了广播ConnectivityBroadcastReceiver,主要用于监听mms网络变化,action为ConnectivityManager.CONNECTIVITY_ACTION_IMMEDIATE,在调用ConnectivityManager的startUsingNetworkFeature激活网络mms网络通道时,得到返回值为APN_REQUEST_STARTED时,将会返回,当通道准备好后ConnectivityBroadcastReceiver将收到广播,接着从新调用startUsingNetworkFeature,得到返回值为APN_ALREADY_ACTIVE时走发送流程,但是有时出现通道准备好了,但是一直未收到广播的情况,故而增加了ConnectivityTimer用于定时询问网络状态,防止彩信不走发送流程。
SendTransaction.java主要负责彩信内容的发送,如果设置了彩信重试机制,则在此对象构建时attch到Observer实现类MmsSendRetry上,当发送彩信过程结束时,MmsSendRetry中的updata方法将被调用,如果发送不成功,调用scheduleRetry方法更新数据库内数据,再调用setRetryAlarm方法通过AlarmManager设置一个定时发送的广播,广播在RetryReceiver.java中接收,接到广播后如果有网络,则再走一次发送流程,没网络则继续设定下一个广播。
重试流程图如下:
3. 箱体模式简介
箱体模式主要是依据sms的 type 与 mms的 msg_box 把信息分为发件箱、收件箱、草稿箱以及已发送信箱。其中对应类型为:收件箱(1)、已发送(2)、草稿箱(3)及发件箱(4)。其中短信的5为发送失败,6为正在发送,而彩信的状态需要判断另一组数据,发送中与发送失败的类型都为4。
3.1. MessageFolderActivity
此类只要用于显示箱体模式的第一个界面,使用msg_folder_screen.xml 文件作为布局文件,布局以线性布局为最外层结构,内层以一个相对布局为一个单位,相对布局内有一个显示图片的view及两个显示文字的view,一个单位代表一个文件夹,总共四个文件夹加上SIM卡项。
在onCreate方法中,对每一个文件夹视图设置监听,当点击不同文件夹时对MessageBoxActivity传递不同参数。
3.2. MessageBoxActivity
此类主要用于列举选中文件夹内所有的信息,继承了ListActivity。在onCreate方法中构建异步查询数据库对象
在onStart方法里把此对象传递给BoxMsgListAdapter的startQuery静态方法,在此方法里根据上一个Activity传递过来的boxType来选择查询的uri。
当点击选中一条信息时,将会调用viewBoxMesssgeItem()方法打开信息内容
对于显示SIM卡内的信息时,点击信息不会打开查看,但是可以长按保存到本地,调用的方法如下:
查看SIM卡内短信时可以按menu键查看SIM卡当前容量,构建显示的Dialog方法如下:
3.3. MessageBoxDeleteActivity
此类主要用于批量删除箱体模式中的信息,支持多个删除与全部选中,其在onCreate放过中就构造异常线程开始查询
查询结束后根据查询结果来做不同显示行为