On Windows Mobile, The
Call Log API provides read-only access to the device call history via its
PhoneOpenCallLog and
PhoneGetCallLogEntry function. The .NET Compact Framework does not provide methods to access these APIs but OpenNetCF has a nice wrapper via its
OpenNETCF.Phone namespace.
However, there will be no official APIs to modify the call history (e.g. add/edit/delete records).If you want to do so, you will need to manipulate the call history database manually using
EDB. The format of the database is not well documented so it may take a bit of trial and error to make things work.
For Windows Mobile 5 and higher, the call history is in EDB format, stored in a database named "clog.db" in \pim.vol.
The following demonstrates how to use EDB to perform basic read/write on the call history database.
1. First add the declaration:
#define EDB
#include <windbase.h>
must be added on top on the source code (or stdafx.h for apps that use pre-compiled headers).
2. Mount the file:
CeMountDBVolEx(&m_ceguidDB, L"\\pim.vol", NULL, OPEN_EXISTING);
3. Open the call history database clog.db:
HANDLE m_hDBCallLog = CeOpenDatabaseEx2( &m_ceguidDB, &m_ceoidCallLog,
TEXT("clog.db"), 0, CEDB_AUTOINCREMENT, NULL);
Before proceeding, check for a return value of INVALID_HANDLE_VALUE, which indicates an error.
3. Seek to the beginning of the table. A non-zero return value indicate success:
CEOID ceOIDTemp = CeSeekDatabase(m_hDBCallLog, CEDB_SEEK_BEGINNING, 0, NULL);
4.1. Read the records. This is the most tricky part as we need to guess the database format, e.g. how many columns each record has and what each column is for.
#define propID_CallStartTime 131136
#define propID_CallEndTime 196672
#define propID_CallNumber 393247
#define propID_CallName 458783
#define propID_CallFlags 262147
CEPROPID propReserved1 MAKELONG(CEVT_I2, 1); CEPROPID propStartTime = MAKELONG(CEVT_FILETIME, 2); CEPROPID propEndTime = MAKELONG(CEVT_FILETIME, 3); CEPROPID propCallFlags = MAKELONG(CEVT_I4, 4);
CEPROPID propSomeText = MAKELONG(CEVT_LPWSTR, 5); CEPROPID propCallNumber = MAKELONG(CEVT_LPWSTR, 6); CEPROPID propCallName = MAKELONG(CEVT_LPWSTR, 7); CEPROPID propReserved2 = MAKELONG(CEVT_I4, 8); CEPROPID propID = MAKELONG(CEVT_AUTO_I4, 9); DWORD dwBuf = 0; WORD wProps = 5; CEPROPVAL *pPropVals = NULL; CEPROPID propsToRead[5] = {propStartTime, propEndTime, propCallNumber, propCallName,
propCallFlags};
CEOID readRecord = CeReadRecordPropsEx(hCallLog, CEDB_ALLOWREALLOC, &wProps,
propsToRead, reinterpret_cast<LPBYTE*>(&pPropVals),
&dwBuf, NULL);
4.2. Retrieve the properties that we have read. To retrieve the read properties,
a. check pPropVals[i].wFlags to make sure that the field has been read properly
b. Check LOWORD(pPropVals[i].propid) for the field data type
c. According to the field data type, retrieve data from pPropVals[i].val
Example:
for (int i = 0; i < wProps; i++)
{
printf("Property %d propid=%d Length=%d Value=", i, pPropVals[i].propid,
pPropVals[i].wLenData);
if (pPropVals[i].wFlags == CEDB_PROPNOTFOUND)
{
printf("[CEDB_PROPNOTFOUND] Not available.");
}
else
{
printf("[Flag=%d]", pPropVals[i].wFlags);
switch(LOWORD(pPropVals[i].propid))
{
case CEVT_LPWSTR:
printf("[CEVT_LPWSTR] ");
OutputDebugString(pPropVals[i].val.lpwstr);
break;
case CEVT_BOOL:
printf("[CEVT_BOOL] %d", pPropVals[i].val.boolVal);
break;
case CEVT_I2:
printf("[CEVT_I2] %d", pPropVals[i].val.iVal);
break;
case CEVT_I4:
printf("[CEVT_I4] %d", pPropVals[i].val.lVal);
break;
case CEVT_R8:
printf("[CEVT_R8] %d", pPropVals[i].val.dblVal);
break;
case CEVT_UI2:
printf("[CEVT_UI2] %d", pPropVals[i].val.uiVal);
break;
case CEVT_UI4:
printf("[CEVT_UI4] %d", pPropVals[i].val.ulVal);
break;
case CEVT_FILETIME:
printf("[CEVT_FILETIME] ");
FILETIME ft, ftLocal;
SYSTEMTIME st;
ft = pPropVals[i].val.filetime;
FileTimeToLocalFileTime(&ft, &ftLocal);
FileTimeToSystemTime(&ftLocal, &st);
printf("%d:%d:%d %d/%d/%d", st.wHour, st.wMinute, st.wSecond,
st.wDay, st.wMonth, st.wYear);
break;
default:
printf("[%d] Non-printable.", LOWORD(pPropVals[i].propid));
break;
}
}
printf("\n");
}
4.3. Deleting a record is straightforward once you have its OID:
CeDeleteRecord(m_hDBCallLog, ceOIDTemp);
4.4. Creating a new record is possible too. The following code creates a new entry in the call history with various status:
#define FLAG_NONAME_MISSED 4
#define FLAG_NONAME_OUTGOING 5
#define FLAG_NONAME_ANSWERED 6
#define FLAG_WITHNAME_MISSED 2052
#define FLAG_WITHNAME_OUTGOING 2053
#define FLAG_WITHNAME_ANSWERED 2054
enum CallStatus
{
Answered = 0, Outgoing = 1, Missed = 2, Unknown = 3
};
BOOL WriteCallLogEntry(HANDLE hCallLog, LPWSTR contactName, LPWSTR contactNumber,
CallStatus callStatus, SYSTEMTIME startTime, SYSTEMTIME endTime)
{
CEPROPVAL propsToWrite[5];
propsToWrite[0].propid = propID_CallStartTime;
propsToWrite[0].wFlags = 0;
FILETIME ftStart;
SystemTimeToFileTime(&startTime, &ftStart);
propsToWrite[0].val.filetime = ftStart;
propsToWrite[1].propid = propID_CallEndTime;
propsToWrite[1].wFlags = 0;
FILETIME ftEnd;
SystemTimeToFileTime(&endTime, &ftEnd);
propsToWrite[1].val.filetime = ftEnd;
propsToWrite[2].propid = propID_CallNumber;
propsToWrite[2].wFlags = 0;
propsToWrite[2].val.lpwstr = contactNumber;
propsToWrite[3].propid = propID_CallName;
propsToWrite[3].wFlags = 0;
propsToWrite[4].propid = propID_CallFlags;
propsToWrite[4].wFlags = 0;
if (contactName == NULL || wcscmp(contactName, L"") == 0)
{
propsToWrite[3].val.lpwstr = contactNumber;
}
else
{
propsToWrite[3].val.lpwstr = contactName;
}
switch(callStatus)
{
case Answered:
propsToWrite[4].val.lVal = FLAG_WITHNAME_ANSWERED;
break;
case Outgoing:
propsToWrite[4].val.lVal = FLAG_WITHNAME_OUTGOING;
break;
case Missed:
propsToWrite[4].val.lVal = FLAG_WITHNAME_MISSED;
break;
}
WORD length = sizeof(propsToWrite)/sizeof(CEPROPVAL);
CEOID newRec = 0;
newRec = CeWriteRecordProps(hCallLog, 0, length, propsToWrite);
if (newRec == 0)
{
printf("CeWriteRecordProps FAILS. HRESULT=%d\n",
HRESULT_FROM_WIN32(GetLastError()));
}
return (newRec!=0);
}
4.5. Finally, close the database after you finished using it:
CeUnmountDBVol(&m_ceguidDB);
I hope these code snippets will help those who want to modify the call history. Keep in mind that the device security levels may prohibit write access to the call history totally, and since the database structure is not documented, the above code is not guaranteed to work with all devices.