/**
*
*/
package org.orange.familylink.data;
import org.orange.familylink.data.MessageLogRecord.Status;
import org.orange.familylink.database.Contract;
import org.orange.familylink.database.Contract.Messages;
import org.orange.familylink.util.Objects;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.net.Uri;
import android.telephony.PhoneNumberUtils;
import android.util.Log;
import com.google.gson.Gson;
import com.google.gson.JsonSyntaxException;
/**
* 在受顾方与监护方之间传送的消息。
* <p><em>本类的Setters允许链式调用(Method chaining)</em></p>
* @author Team Orange
*/
public abstract class Message implements Cloneable{
/**
* 消息代码。指明消息主体的语义,明确命令。
* @author Team Orange
*/
public abstract static class Code {
/**
* 通告。如定时通告、紧急通知、应答命令等
* @see Extra.Inform
*/
public static final int INFORM = 0x000;
/**
* 命令。如现在定位等。
* @see Extra.Command
*/
public static final int COMMAND = 0x100;
/**
* {@link Code}的附加信息。如具体命令,通告原因等。
* <p><strong>使用方法</strong>:用“|”与主命令连接。如
* <code>Code.INFORM | Code.Extra.Inform.PULSE</code></p>
* <p><em>可以用“|”连接多个Extra</em></p>
* @author Team Orange
*/
public abstract static class Extra {
/**
* {@link Code#INFORM}的额外信息,指明通告原因等
* @author Team Orange
* @see Extra
*/
public abstract static class Inform {
/**
* 应答命令
*/
public static final int RESPONSE = 0x01;
/**
* 脉冲通告,定时报告
*/
public static final int PULSE = 0x02;
/**
* 紧急消息
*/
public static final int URGENT = 0x04;
/**
* 检测指定code是不是设置了{@link #RESPONSE}位
* @param code 待检测code
* @return 如果code是{@link Code#INFORM}并且设置了{@link #RESPONSE}位,返回true;否则返回false
*/
public static boolean hasSetRespond(int code) {
if(!isInform(code))
return false;
return (code & RESPONSE) == RESPONSE;
}
/**
* 检测指定code是不是设置了{@link #PULSE}位
* @param code 待检测code
* @return 如果code是{@link Code#INFORM}并且设置了{@link #PULSE}位,返回true;否则返回false
*/
public static boolean hasSetPulse(int code) {
if(!isInform(code))
return false;
return (code & PULSE) == PULSE;
}
/**
* 检测指定code是不是设置了{@link #URGENT}位
* @param code 待检测code
* @return 如果code是{@link Code#INFORM}并且设置了{@link #URGENT}位,返回true;否则返回false
*/
public static boolean hasSetUrgent(int code) {
if(!isInform(code))
return false;
return (code & URGENT) == URGENT;
}
}
/**
* {@link Code#COMMAND}的额外信息,指明具体命令
* @author Team Orange
* @see Extra
*/
public abstract static class Command {
/**
* 回显。主要用于测试
*/
public static final int ECHO = 0x01;
/**
* 现在定位
*/
public static final int LOCATE_NOW = 0x02;
/**
* 检测指定code是不是设置了{@link #ECHO}位
* @param code 待检测code
* @return 如果code是{@link Code#COMMAND}并且设置了{@link #ECHO}位,返回true;否则返回false
*/
public static boolean hasSetEcho(int code) {
if(!isCommand(code))
return false;
return (code & ECHO) == ECHO;
}
/**
* 检测指定code是不是设置了{@link #LOCATE_NOW}位
* @param code 待检测code
* @return 如果code是{@link Code#COMMAND}并且设置了{@link #LOCATE_NOW}位,返回true;否则返回false
*/
public static boolean hasSetLocateNow(int code) {
if(!isCommand(code))
return false;
return (code & LOCATE_NOW) == LOCATE_NOW;
}
}
}
/** {@link Extra}所在位置 */
public static final int EXTRA_BITS = 0xff;
/** 可能的最小{@link Code} */
public static final int MINIMUM = INFORM;
/** 可能的最大{@link Code} */
public static final int MAXIMUM = COMMAND | EXTRA_BITS;
/**
* 检查code的合法性。
* @param code 待检测的代码
* @return 如果合法,返回true;如果非法,返回false
*/
public static boolean isLegalCode (int code) {
if(code >= MINIMUM && code <= MAXIMUM)
return true;
else
return false;
}
/**
* 检测指定code是不是{@link #INFORM} code
* @param code 待检测code
* @return 如果是{@link #INFORM},返回true;如果不是,返回false
*/
public static boolean isInform(int code) {
if(!isLegalCode(code))
return false;
return (code & (~EXTRA_BITS)) == INFORM;
}
/**
* 检测指定code是不是{@link #COMMAND} code
* @param code 待检测code
* @return 如果是{@link #COMMAND},返回true;如果不是,返回false
*/
public static boolean isCommand(int code) {
if(!isLegalCode(code))
return false;
return (code & (~EXTRA_BITS)) == COMMAND;
}
}
/**
* 消息代码
* @see Code
*/
private Integer code = null;
/**
* 消息主体
*/
private String body = null;
/**
* 使用默认值构造本类
* <p>
* Tips:可以类似这样使用链式调用
* <pre><code>new Message().setBody("Hello")
* .setCode(Message.Code.INFORM | Message.Code.Extra.Inform.PULSE);</code></pre>
*/
public Message() {
super();
}
/**
* 取得消息代码
* @return 消息代码
* @see Code
*/
public Integer getCode() {
return code;
}
/**
* 设置消息代码
* @param code 消息代码
* @return this(为了链式调用)
* @see Code
*/
public Message setCode(Integer code) {
if((code != null) && (!Code.isLegalCode(code)))
throw new IllegalArgumentException("Illegal Code :" + code);
this.code = code;
return this;
}
/**
* 取得消息主体
* @return 消息主体
*/
public String getBody() {
return body;
}
/**
* 设置消息主体
* @param body 消息主体
* @return this(为了链式调用)
*/
public Message setBody(String body) {
this.body = body;
return this;
}
/**
* 把本对象序列化为Json表示法的字符串。
* @return 与本对象对应的Json
* @see Gson#toJson(Object)
*/
public String toJson() {
return new Gson().toJson(this);
}
/**
* 把Json反序列化。结果保存到本对象中。
* @param json Json表示法的本类的对象
* @return this(为了链式调用)
* @throws JsonSyntaxException 当给定的Json不表示本类时
*/
public Message fromJson(String json) {
Message m = new Gson().fromJson(json, getClass());
setCode(m.getCode()).setBody(m.getBody());
return this;
}
/**
* 发送本消息
* <p>
* <strong>注意:</strong><em>不</em> 应在UI线程调用本方法
* @param context 应用全局信息
* @param contactId 联系人{@link Messages#COLUMN_NAME_CONTACT_ID ID}
* @param dest 发送目的{@link Messages#COLUMN_NAME_ADDRESS 地址}
* @return 保存后的本消息的{@link Uri}
* @throws IllegalArgumentException 当dest == null || dest.isEmpty()时
* @see #receiveAndSave(Context, String)
*/
public Uri sendAndSave(Context context, Long contactId , String dest) {
return sendAndSave(context, contactId, dest, Settings.getPassword(context));
}
/**
* 发送本消息
* <p>
* <strong>注意:</strong><em>不</em> 应在UI线程调用本方法
* @param context 应用全局信息
* @param contactId 联系人{@link Messages#COLUMN_NAME_CONTACT_ID ID}
* @param dest 发送目的{@link Messages#COLUMN_NAME_ADDRESS 地址}
* @param password 要发送信息的加密密码
* @return 保存后的本消息的{@link Uri}
* @throws IllegalArgumentException 当dest == null || dest.isEmpty()时
* @see #receiveAndSave(String, String)
*/
public Uri sendAndSave(Context context, Long contactId , String dest, String password) {
if(dest == null || dest.isEmpty())
throw new IllegalArgumentException("dest address shouldn't be empty");
Uri newUri = saveMessage(context, contactId, dest, Status.SENDING);
if (Code.isCommand(getCode())) beforeSendCommandMessage(context, newUri);
send(context, newUri, dest, password);
return newUri;
}
/**
* 发送Command Message之前,更新body的ID
* @param messageUri 此{@link Message}的{@link Uri}
*/
private void beforeSendCommandMessage(Context context, Uri messageUri) {
if (getBody() != null) throw new IllegalStateException("have setBody");
final long id = ContentUris.parseId(messageUri);
CommandMessageBody body = new CommandMessageBody();
body.setId(id);
setBody(body.toJson());
// 更新Content Provider中的记录发送日志
ContentValues message = new ContentValues();
message.put(Messages.COLUMN_NAME_TIME, System.currentTimeMillis());
message.put(Messages.COLUMN_NAME_BODY, getBody());
final int rowsUpdated = context.getContentResolver().update(
ContentUris.withAppendedId(Messages.MESSAGES_URI, id),
message, null, null);
assert rowsUpdated == 1;
}
/**
* 发送本消息
* @param context 应用包环境信息
* @param messageUri 本消息的存储{@link Uri}
* @param dest 发送目的地址
* @param password 发送时的加密密钥
*/
public abstract void send(Context context, Uri messageUri, String dest, String password);
/**
* 接收并保存消息。接收到的消息存到本{@link Message}对象
* <p>
* <strong>注意:</strong><em>不</em> 应在UI线程调用本方法
* @param context 应用全局信息
* @param receivedMessage 接收到的消息原始内容
* @param srcAddr 消息来源地址
* @return 保存后的本消息的{@link Uri}
* @throws JsonSyntaxException 当给定的receivedMessage与本类不对应时
* @see #sendAndSave(Context, Long, String)
*/
public Uri receiveAndSave(Context context, String receivedMessage, String srcAddr) {
// remove spaces and dashes from destination number
// (e.g. "801 555 1212" -> "8015551212")
// (e.g. "+8211-123-4567" -> "+82111234567")
srcAddr = PhoneNumberUtils.stripSeparators(srcAddr);
return receiveAndSave(context, receivedMessage, queryContactId(context, srcAddr),
srcAddr, Settings.getPassword(context));
}
/**
* 接收并保存消息。接收到的消息存到本{@link Message}对象
* <p>
* <strong>注意:</strong><em>不</em> 应在UI线程调用本方法
* @param context 应用全局信息
* @param receivedMessage 接收到的消息原始内容
* @param contactId 消息来源联系人ID
* @param srcAddr 消息来源地址
* @param password 解密密钥
* @return 保存后的本消息的{@link Uri}
* @throws JsonSyntaxException 当给定的receivedMessage与本类不对应时
* @see #sendAndSave(Context, Long, String, String)
* @see #receive(String, String)
*/
public Uri receiveAndSave(Context context, String receivedMessage,
Long contactId, String srcAddr, String password) {
receive(receivedMessage, password);
return saveMessage(context, contactId, srcAddr, Status.UNREAD);
}
protected Long queryContactId(Context context, String contactAddress) {
Long contactId = null;
Uri baseUri = Contract.Contacts.CONTACTS_URI;
String[] projection = {Contract.Contacts._ID};
String selection = Contract.Contacts.COLUMN_NAME_PHONE_NUMBER + " = ?";
contactAddress = removePrefix(contactAddress);
String[] args = {contactAddress};
Log.w("cont", "" + contactAddress);
Cursor c = context.getContentResolver()
.query(baseUri, projection, selection, args, null);
if(c.moveToFirst()) {
int column = c.getColumnIndex(Contract.Contacts._ID);
if(!c.isNull(column))
contactId = c.getLong(column);
}
return contactId;
}
/**
* 移除电话号码的前缀,如“+86”
* @param contactAddress 电话号码
* @return 去除前缀之后的电话号码
*/
private String removePrefix(String contactAddress){
String newContactAddress = null;
//手机号的长度为11位
int phoneDigits = 11;
//手机号的前缀“+”的位置为0
final int PLUS_SIGN_POSITION = 0;
if(contactAddress.charAt(PLUS_SIGN_POSITION) == '+'){
int prefixDigits = contactAddress.length() - phoneDigits;
newContactAddress = contactAddress.substring(prefixDigits);
}else{
newContactAddress = contactAddress;
}
return newContactAddress;
}
/**
* 接收消息。接收到的消息存到本{@link Message}对象
* @param receivedMessage 接收到的消息原始内容
* @param password 解密密钥
* @throws JsonSyntaxException 当给定的receivedMessage与本类不对应时
*/
public abstract void receive(String receivedMessage, String password);
/**
* 保存消息并返回其{@link Uri}
* @param context 应用上下文环境
* @param contactId 联系人ID
* @param address 地址
* @param status 状态
* @return 保存的消息的{@link Uri}
*/
protected Uri saveMessage(Context context, Long contactId , String address, Status status) {
Log.w("smsfamily", "sms5");
// 在Content Provider中记录发送日志
ContentValues newMessage = new ContentValues();
newMessage.put(Messages.COLUMN_NAME_CONTACT_ID, contactId);
newMessage.put(Messages.COLUMN_NAME_ADDRESS, address);
newMessage.put(Messages.COLUMN_NAME_TIME, System.currentTimeMillis());
newMessage.put(Messages.COLUMN_NAME_STATUS, status.name());
newMessage.put(Messages.COLUMN_NAME_BODY, getBody());
newMessage.put(Messages.COLUMN_NAME_CODE, getCode());
return context.getContentResolver().insert(Messages.MESSAGES_URI, newMessage);
}
/**
* 深拷贝
*/
@Override
public Message clone() {
Message clone = null;
try {
clone = (Message) super.clone();
} catch (CloneNotSupportedException e) {
throw new RuntimeException("can't clone Message", e);
}
return clone;
}
/**
* 判断指定对象是否与本对象内容相同。
* <p><em>会调用{@link #isSameClass(Object)}判断是否是本类的实例,
* 调用{@link Objects#compare(Object, Object)}比较{@link String}等对象</em></p>
* @param o 待比较对象
* @return 如果内容与本对象相同,返回true;不同,返回false
*/
@Override
public boolean equals(Object o) {
if(o == null)
return false;
else if(!isSameClass(o))
return false;
else {
Message other = (Message) o;
return Objects.compare(code, other.code) && Objects.compare(body, other.body);
}
}
/**
* 判断指定对象是否是本类(或{@link #mDefaultValue})的实例
* @param o 待测试对象
* @return 如果是本类(或{@link #mDefaultValue})的实例,返回true;不是,返回false
*/
protected boolean isSameClass(Object o) {
return getClass() == o.getClass();
}
}