Click here to Skip to main content
65,938 articles
CodeProject is changing. Read more.
Articles / Mobile

Modifying the Call History on Windows Mobile

5.00/5 (4 votes)
24 Mar 2010CPOL2 min read 1  
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...
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:
C++
#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:
C++
CeMountDBVolEx(&m_ceguidDB, L"\\pim.vol", NULL, OPEN_EXISTING);

3. Open the call history database clog.db:
C++
HANDLE m_hDBCallLog = CeOpenDatabaseEx2( &amp;m_ceguidDB, &amp;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:
C++
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.
C++
/*
Mysterious PropID to set properties for a call. 
*/
#define propID_CallStartTime 131136
#define propID_CallEndTime 196672
#define propID_CallNumber 393247
#define propID_CallName 458783
//identify if the call is incoming, outgoing, missed, answered, roaming, etc.
#define propID_CallFlags 262147 
/* 
Indentify the column indexes (in the database) of the properties to be retrieved
*/
CEPROPID propReserved1 MAKELONG(CEVT_I2, 1); //Always 1
CEPROPID propStartTime = MAKELONG(CEVT_FILETIME, 2); //call start time
CEPROPID propEndTime = MAKELONG(CEVT_FILETIME, 3); //call end time
//Identify call property (connected, roaming, incoming, outgoing, missed, etc.), 
//every bit has a certain meaning
CEPROPID propCallFlags = MAKELONG(CEVT_I4, 4); 
CEPROPID propSomeText = MAKELONG(CEVT_LPWSTR, 5); //Some texts to display
CEPROPID propCallNumber = MAKELONG(CEVT_LPWSTR, 6); //called/incoming number
CEPROPID propCallName = MAKELONG(CEVT_LPWSTR, 7); //name from address book
CEPROPID propReserved2 = MAKELONG(CEVT_I4, 8); //always 0
CEPROPID propID = MAKELONG(CEVT_AUTO_I4, 9); //Sequence number, auto-increase
DWORD dwBuf = 0; //returned size of buffer to hold record properties
WORD wProps = 5; //length of property array, e.g. number of properties to receive 
CEPROPVAL *pPropVals = NULL; //properties of records are returned in this array
//array of property to retrieve
CEPROPID propsToRead[5] = {propStartTime, propEndTime, propCallNumber, propCallName,
    propCallFlags}; 
//Actual reading of the record. Return non-zero if OK, 0 if error (e.g. no records left). 
CEOID readRecord = CeReadRecordPropsEx(hCallLog, CEDB_ALLOWREALLOC, &amp;wProps,
    propsToRead, reinterpret_cast&lt;LPBYTE*&gt;(&amp;pPropVals),
    &amp;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:
C++
for (int i = 0; i < wProps; i++)
{
	//the lower word of 'propid' determines the datatype of the property
	//(found in windbase.h)
	//the higher word of 'propid' determine what the property is for
	printf("Property %d propid=%d Length=%d Value=", i, pPropVals[i].propid,
	    pPropVals[i].wLenData);
	if (pPropVals[i].wFlags == CEDB_PROPNOTFOUND) 
	{
		//this flag in ON when the property is not available
		printf("[CEDB_PROPNOTFOUND] Not available.");
	}
	else
	{
		printf("[Flag=%d]", pPropVals[i].wFlags);
		//print the property depending on the datatype
		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;
			//take into account device timezone settings
			FileTimeToLocalFileTime(&ft, &ftLocal); 
			//convert to system time for printing
			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:
C++
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:
C++
/* 
Common values for propID_CallFlags
Flag values vary slightly if the number exists in phonebook in fields other than Mobile 
(Company, Fax, etc.). 
For simplicity, we only use FLAG_WITHNAME_xxxx. If a contact name is not available, we will
set the Name field to the number
*/
//incoming call was missed, caller not in phonebook
#define FLAG_NONAME_MISSED 4		
//outgoing call to a number not in phonebook
#define FLAG_NONAME_OUTGOING 5		
//incoming call was answered, caller not in phonebook
#define FLAG_NONAME_ANSWERED 6		
//incoming call was missed. Caller number is a mobile number of an existing
//phonebook contact
#define FLAG_WITHNAME_MISSED 2052	
//outgoing call to a mobile number of an existing phonebook contact
#define FLAG_WITHNAME_OUTGOING 2053 
//incoming call was answered. Caller number is a mobile number of an
//existing phonebook contact
#define FLAG_WITHNAME_ANSWERED 2054 
/* Specify the status of the call */
enum CallStatus
{
    Answered = 0, //the incoming call was answered
    Outgoing = 1, //indicate an outgoing call
    Missed = 2, //the incoming call was missed
    Unknown = 3
};
/* Write a new record into the calllog database. Return TRUE if OK, FALSE if FAILED. */
BOOL WriteCallLogEntry(HANDLE hCallLog, LPWSTR contactName, LPWSTR contactNumber,
    CallStatus callStatus, SYSTEMTIME startTime, SYSTEMTIME endTime)
{
	//array of properties to write for the current record
	CEPROPVAL propsToWrite[5];
	//start time
	propsToWrite[0].propid = propID_CallStartTime;
	propsToWrite[0].wFlags = 0;
	FILETIME ftStart;
	SystemTimeToFileTime(&startTime, &ftStart);
	propsToWrite[0].val.filetime = ftStart;
	//end time
	propsToWrite[1].propid = propID_CallEndTime;
	propsToWrite[1].wFlags = 0;
	FILETIME ftEnd;
	SystemTimeToFileTime(&endTime, &ftEnd);
	propsToWrite[1].val.filetime = ftEnd;
	//contact number 
	propsToWrite[2].propid = propID_CallNumber;
	propsToWrite[2].wFlags = 0;
	propsToWrite[2].val.lpwstr = contactNumber;
	//contact name
	propsToWrite[3].propid = propID_CallName;
	propsToWrite[3].wFlags = 0;
	//call flag, indicating the status of the call
	propsToWrite[4].propid = propID_CallFlags;
	propsToWrite[4].wFlags = 0;
	if (contactName == NULL || wcscmp(contactName, L"") == 0)
	{
		//contact name unavailable, use mobile number as contact name
		//for simplicity
		propsToWrite[3].val.lpwstr = contactNumber;
	}
	else
	{
		//contact name is available
		propsToWrite[3].val.lpwstr = contactName;
	}
	//different flags depending on status of calls
	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;
	}
	//write the record using CeWriteRecordProps. Return the
	//record ID if OK, FALSE on error.
	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:
C++
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.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)