Click here to Skip to main content
13,002,399 members (64,296 online)
Click here to Skip to main content
Add your own
alternative version

Tagged as


1 bookmarked
Posted 24 Mar 2010

Modifying the Call History on Windows Mobile

, 24 Mar 2010
Rate this:
Please Sign up or sign in to vote.
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:
#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( &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:
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.
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,
//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
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,
	if (pPropVals[i].wFlags == CEDB_PROPNOTFOUND) 
		//this flag in ON when the property is not available
		printf("[CEDB_PROPNOTFOUND] Not available.");
		printf("[Flag=%d]", pPropVals[i].wFlags);
		//print the property depending on the datatype
			printf("[CEVT_LPWSTR] ");
		case CEVT_BOOL:
			printf("[CEVT_BOOL] %d", pPropVals[i].val.boolVal);
		case CEVT_I2:
			printf("[CEVT_I2] %d", pPropVals[i].val.iVal);
		case CEVT_I4:
			printf("[CEVT_I4] %d", pPropVals[i].val.lVal);
		case CEVT_R8:
			printf("[CEVT_R8] %d", pPropVals[i].val.dblVal);
		case CEVT_UI2:
			printf("[CEVT_UI2] %d", pPropVals[i].val.uiVal);
		case CEVT_UI4:
			printf("[CEVT_UI4] %d", pPropVals[i].val.ulVal);
			printf("[CEVT_FILETIME] ");
			FILETIME ft, ftLocal;
			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);
			printf("[%d] Non-printable.", LOWORD(pPropVals[i].propid));

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:
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
//outgoing call to a number not in phonebook
//incoming call was answered, caller not in phonebook
//incoming call was missed. Caller number is a mobile number of an existing
//phonebook contact
//outgoing call to a mobile number of an existing phonebook contact
//incoming call was answered. Caller number is a mobile number of an
//existing phonebook contact
/* 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;
	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;
		//contact name is available
		propsToWrite[3].val.lpwstr = contactName;
	//different flags depending on status of calls
	case Answered:
		propsToWrite[4].val.lVal = FLAG_WITHNAME_ANSWERED;
	case Outgoing:
		propsToWrite[4].val.lVal = FLAG_WITHNAME_OUTGOING;
	case Missed:
		propsToWrite[4].val.lVal = FLAG_WITHNAME_MISSED;
	//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",
	return (newRec!=0);

4.5. Finally, close the database after you finished using it:

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.


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


About the Author

Singapore Singapore
No Biography provided

You may also be interested in...

Comments and Discussions

QuestionDoes this holds true for the current version of the WP? Pin
Vlad Michnik14-Jan-13 9:32
memberVlad Michnik14-Jan-13 9:32 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Terms of Use | Mobile
Web01 | 2.8.170626.1 | Last Updated 24 Mar 2010
Article Copyright 2010 by mdanh2002
Everything else Copyright © CodeProject, 1999-2017
Layout: fixed | fluid