android 蓝牙 pbap 获取通话时间
之前,我们利用 pbap 协议获取到了用户的通讯录。那自然而然产生了衍生需求:能否获取通话记录?包括打电话记录(DIALED)、接电话记录(RECEIVED)、未接电话记录(MISSED)?
答案是肯定的。类比获取通讯录的方式,我们可以通过修改client.pullPhoneBook(BluetoothPbapClient.PB_PATH);中的PB_PATH参数,来获取其他的信息。比如:
/**
* Path to local incoming calls history object
*/
public static final String ICH_PATH = "telecom/ich.vcf";
/**
* Path to local outgoing calls history object
*/
public static final String OCH_PATH = "telecom/och.vcf";
/**
* Path to local missed calls history object
*/
public static final String MCH_PATH = "telecom/mch.vcf";
/**
* Path to local combined calls history object
*/
public static final String CCH_PATH = "telecom/cch.vcf";
/**
* Path to local main phone book object
*/
public static final String PB_PATH = "telecom/pb.vcf";
但是,在替换PB_PATH为其他的各种 PATH 之后,我们发现获取的信息不完全。我们从控制台中输出的信息只有按时间顺序倒叙排列的一堆联系人信息(其实是VCardEntry对象,控制台输出的时候调用了VCardEntry#toString())。每一个联系人信息如下:
[[
hash: 427457895
NAME: [family: nameOfThePerson, given: null, middle: null, prefix: null, suffix: null]
PHONE: [type: 2, data: 1-111-111-1111, label: null, isPrimary: false]
]]
问题来了,怎么没有时间信息?对于通话记录而言,时间信息还是比较重要的。那么,是 pbap 协议就不会给我们返回时间?还是我们解析时间数据的时候出了错误?或者是手机厂商在实现 pbap server 端的时候偷工减料了呢?
首先,先去蓝牙 pbap 协议中查看是否有通话记录对应的描述:
3.1.4.1 Call History extension
The time of each call found in och, ich, mch and cch folder, can be shown using the
IrMC [13] defined X-IRMC-CALL-DATETIME property that extends the vCard
specification. This attribute can be used in combination with three newly created
property parameters:
• MISSED
• RECEIVED
• DIALED
These are used to indicate the nature of the call that is time-stamped with X-IRMCCALL-DATETIME.
For instance, a call that was missed on March 20th, 2005 at 10 am would be stamped:
X-IRMC-CALL-DATETIME;MISSED:20050320T100000
It is strongly recommended to use this property parameter whenever possible. They are
especially useful in vCards that are retrieved from the cch folder ( see Section 3.1.2 ).
Note that it is legal to use this property with no data; i.e,
X-IRMC-CALL-DATETIME;MISSED:
看来,关于通话记录,PBAP 协议是有所规定的。所以,问题可能出在手机厂商的 pbap server 端或者是出在我们解析数据的时候。通过对VCardParserImp_V21进行深层次的调试,我们发现,X-IRMC-CALL-DATETIME参数是有输出的。那就说明是手机端的 pbap server 是没有问题的。问题出在我们的 pbap client 上。更具体的定位一下,是我们解析这个参数的时候出了问题,或者是根据解析出的参数构造VCardEntry的时候出了问题。
继续调试,在 VCardEntry#addProperty(VCardProperty property) 函数中,可以输出 property 并且也是有 X-IRMC-CALL-DATETIME 对应的数据的。那么,最终锁定了问题:我们构造 VCardEntry 的时候,没有把 X-IRMC-CALL-DATETIME 加进来。更本质的原因是,android sdk 23 的相关源码中,没有定义 X-IRMC-CALL-DATETIME 相关的字段。
找到了问题所在,改的时候就非常好改了。
首先,在 VCardConstants.java 中添加
public static final String PROPERTY_X_IRMC_CALL_DATETIME = "X-IRMC-CALL-DATETIME"; // added
然后,在 VCardEntry.java 中添加:
public enum EntryLabel {
NAME,
PHONE,
EMAIL,
POSTAL_ADDRESS,
ORGANIZATION,
IM,
PHOTO,
WEBSITE,
SIP,
NICKNAME,
NOTE,
BIRTHDAY,
ANNIVERSARY,
ANDROID_CUSTOM,
CALL_DATETIME // added
}
然后,添加 CALL_DATETIME 对应的类:
public static class CallDatetimeData implements EntryElement {
private final String mCallDatetime;
public CallDatetimeData(String datetime) {
mCallDatetime = datetime;
}
/** start implementing EntryElement */
@Override
public EntryLabel getEntryLabel() {
return EntryLabel.CALL_DATETIME;
}
@Override
public boolean isEmpty() {
return TextUtils.isEmpty(mCallDatetime);
}
@Override
public void constructInsertOperation(List operationList, int backReferenceIndex) {
// 我没看懂这个函数要干嘛,时间有限,先不改了。
}
/** end implementing EntryElement */
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof CallDatetimeData)) {
return false;
}
CallDatetimeData datetimeData = (CallDatetimeData) obj;
return TextUtils.equals(mCallDatetime, datetimeData.mCallDatetime);
}
@Override
public int hashCode() {
return mCallDatetime != null ? mCallDatetime.hashCode() : 0;
}
@Override
public String toString() {
return "call datetime: " + mCallDatetime;
}
public String getCallDatetime() {
return mCallDatetime;
}
}
最后是在构造 VCardEntry 对象的时候,将这个字段添加进来:
在 VCardEntry#addProperty() 中,添加:
if (propertyName.equals(VCardConstants.PROPERTY_X_IRMC_CALL_DATETIME)) {
mCallDatetimeData = new CallDatetimeData(propValue);
}
然后在 VCardEntry 中添加一个 getter 就完事了:
public final String getCallDatetime() {
return mCallDatetimeData != null ? mCallDatetimeData.mCallDatetime : null;
}