Code Repo    |     RSS
MD's Technical Sharing



Saturday, November 22, 2008

Connect WM6 SDK Emulators to GSM Modem

Is it possible to connect the device WM6 emulator to the outside world by mapping its serial port 0 to the COM port of a real GSM modem (instead of that of the cellular emulator)? Once connected, will the emulator be able to call/SMS the real world?


The “Device Emulator” has to be connected before attempting any registry changes using “Windows CE Remote Registry Editor”.


The default entry in the registry is to work with the Cellular emulator using Virtual Serial Port. [ex: VEI1:]


[HKEY_LOCAL_MACHINE\Software\Microsoft\RIL\ComPort]


This port can be configured to work with the actual Serial port by making this change in the registry.

[ex: COM1:]


In the device emulator configure/Peripherals, set the Serial Port to COM1:.


Restart the Device Emulator , Network and Signal Strength will be displayed. Then make a call


Source: http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=3078284&SiteID=1

Read More »

Internet connectivity in WM6 standard emulator

Method 1: Via NE2000 network card


Assuming you have the sdk installed and can get the phone emulator running:


1. Emulator menu: File - Configure - Network - Enable NE2000 and bind to Connected network card (or your device that is listed).

2. Phone menu: Start - Settings - Connections - Proxy - Add new proxy. Connects From WORK. Connects To Internet. Leave the rest BLANK!

3. Emulator menu: Reset — Soft. Emulator can now connect to the Internet


Reference:

http://www.musicalscientist.com/pub/internet-connection-in-wm6-emulator/


Method 2: via ActiveSync


1. In ActiveSync: Open File->Connection Settings, tick the checkbox "Allow connections to…" and select DMA


2. In Device Emulator Manager, right click the desired emulator and select Dradle. Provided that the compute running ActiveSync can connect to the Internet, the emulator will be able to.


Method 3: via an emulated GPRS connection (for both Smartphone and Pocket PC)


The Cellular Emulator will also provide GPRS connection simulation. Users can create simulated GPRS connections just like common ones. The access point name and user name/password are not checked and thus may be anything. Once the data connection is made, the emulator will just act as if it is connected to the network of the host machine. If some proxy settings are needed for the host machine to set up connections to remote machines, they are also needed on the emulator. Assume a network environment where HTTP proxy is needed to access external web sites. Setting samples on Windows Mobile 6 Standard and Professional are provided in the following sections.


Windows Mobile 6 Standard


  1. Enter Start-Settings-Connections-GPRS-Menu-Add. Create a GPRS connection using the settings as follows.
    • Name: PPP
    • Connects to: WAP
    • Network Access Point: Some Access Point Name
    • User Name: Just leave blank for anonymous configuration
  2. (Optional) Enter Start-Settings-Connections-Proxy-Menu-Add. Create a proxy using the following settings.
    • Description: Proxy
    • Connects from: WAP Network
    • Connects to: The Internet
    • Address: The proxy in the specified user's network
  3. Enter Start-Settings-Connections-Menu-Advanced.
    • Set Internet connection to PPP
    • Set WAP connection to PPP
    • Leave the others as automatic
  4. Enter Internet Explorer Mobile, and choose Menu-Tools-Options-Connections, then configure the Internet Explorer Mobile settings:
    • Automatically detect settings: CHECKED
    • Select network: The Internet
  5. Choose the Internet Explorer Mobile icon using the arrow key on the keyboard.
  6. Browse freely using Internet Explorer Mobile.

Windows Mobile 6 Professional

  1. If the user's computer uses a proxy to access the Internet, then the user needs to setup a Work Connection on the Pocket PC, otherwise the user can setup an Internet Connection.
  2. Setup a GPRS connection. Access point and user name can be empty.
  3. (Optional) Setup a proxy. This should be the full name of a working proxy required by the corporate network.
  4. Open Internet Explorer Mobile and browse to a web site.
Reference:


http://blogs.msdn.com/fzandona/archive/2007/04/11/wm-6-sdk-and-cellular-emulator-can-you-hear-me-now.aspx

Read More »

Friday, November 21, 2008

Sending MMS via MAPI

The following code snippet can place an MMS into MMS Outbox folder and cause the

phone to attempt to send the MMS, but a failure (error message: Unable to send MMS. Your message will remain in Outbox folder.) is reported just after the MMS data connection is established. The native composer can send and receive MMS successfully.


// pointer to folder having PR_CE_IPM_DRAFTS_ENTRYID in message store with
PR_DISPLAY_NAME
= "MMS", e.g. drafts folder of MMS account


// This has been retrieved (code not shown)
IMAPIFolder
* pfldrDrafts = NULL;

//pointer to message being created
LPMESSAGE pmsg
;

//create the message to be sent
hr
= pfldrDrafts->CreateMessage(NULL, 0, &pmsg);

//add recipients
SPropValue propRecipient
[3];
ZeroMemory(&propRecipient, sizeof(propRecipient));
propRecipient
[0].ulPropTag = PR_RECIPIENT_TYPE;
propRecipient
[0].Value.l = MAPI_TO;
propRecipient
[1].ulPropTag = PR_ADDRTYPE;
propRecipient
[1].Value.lpszW = L"MMS"; //originally this is L"SMS", I changed
to MMS to point to MMS account
propRecipient
[2].ulPropTag = PR_EMAIL_ADDRESS;
propRecipient
[2].Value.lpszW = L"111111"; //recipient phone number
ADRLIST adrlist
;
adrlist
.cEntries = 1;
adrlist
.aEntries[0].cValues = 3;
adrlist
.aEntries[0].rgPropVals = (LPSPropValue)(&propRecipient);
hr
= pmsg->ModifyRecipients(MODRECIP_ADD, &adrlist);

//change other properties
SPropValue props
[4];
ZeroMemory(&props, sizeof(props));

//PR_SUBJECT points to MMS contents. For simplicity, the MMS body and attachments

(pictures, audio has not yet been set)
props
[0].ulPropTag = PR_SUBJECT;
props
[0].Value.lpszW = L"Hello World"; //message subject

//mark message as being sent out
props
[1].ulPropTag = PR_MSG_STATUS;
props
[1].Value.ul = MSGSTATUS_RECTYPE_SMS; //is it OK to use this flag (designed

for SMS)? or is there a similiar flag for MMS?
props
[2].ulPropTag = PR_MESSAGE_FLAGS;
props
[2].Value.ul = MSGFLAG_FROMME | MSGFLAG_UNSENT;

//assign the property to the message
hr
= pmsg->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props,
NULL
);

//send the message
hr
= pmsg->SubmitMessage(0);


The above code can send SMS successfully just by modifying pfldrDrafts to point to SMS message store and change propRecipient[1].Value.lpszW as commented. How to make the code send an MMS successfully?

Read More »

Tuesday, November 11, 2008

Format source code and export to various file format

See Code formatting & refactoring in visual C++, http://social.msdn.microsoft.com/Forums/en-US/vcgeneral/thread/ed45a521-4de4-462e-861e-a7e5ceb26dd8/


Tools:

  1. C# code export, Java code export, etc: http://www.cpptalk.net/cpp-code-export/
  2. Syntax Highlight, http://www.andre-simon.de/zip/download.html. Standalone packages and plugins for Notepad++ are available.
  3. Online code formatter: http://codeconverter.sharpdevelop.net/FormatCode.aspx
    Note: if you copy & paste any formatted source code from a web page to OneNote, remember to include some of the text above and below the formatted source code when copying, otherwise OneNote will remove all of the formatting.
  4. Another online code formatter: http://www.manoli.net/csharpformat/format.aspx
  5. Note: for the formatting to be retained when pasted into OneNote, tick the "Embed StyleSheet" checkbox.
Read More »

Thursday, November 6, 2008

Setting recipient name when creating a message via IMAPIFolder::CreateMessage

The following code will create a message in MAPI Sent Items folder. Pocket Outlook will show the recipient name (instead of the recipient number) when the message is displayed.


//create an empty message first
LPMESSAGE pmsg
;
hr
= currentFolder->CreateMessage(NULL, 0, &pmsg);

//create a single recipient
SPropValue propRecipient
[4];
ZeroMemory(&propRecipient, sizeof(propRecipient));
propRecipient
[0].ulPropTag = PR_RECIPIENT_TYPE;
propRecipient
[0].Value.l = MAPI_TO;
propRecipient
[1].ulPropTag = PR_ADDRTYPE;
propRecipient
[1].Value.lpszW = _T("SMS");

//This will show when the message is open in Outlook. Without this, only the number is displayed
propRecipient
[2].ulPropTag = PR_DISPLAY_NAME;
propRecipient
[2].Value.lpszW = L"Message Recipient Name";
propRecipient
[3].ulPropTag = PR_EMAIL_ADDRESS;
propRecipient
[3].Value.lpszW = msgRecipient;

//array of recipients
ADRLIST adrlist
;
adrlist
.cEntries = 1; //1 recipient
adrlist
.aEntries[0].cValues = 4; //each recipient has 4 properties
adrlist
.aEntries[0].rgPropVals = (LPSPropValue)(&propRecipient);

//attach recipient list to message
hr
= pmsg->ModifyRecipients(MODRECIP_ADD, &adrlist);

//change other properties
SPropValue props
[4];
ZeroMemory(&props, sizeof(props));
props
[0].ulPropTag = PR_SUBJECT; //PR_SUBJECT points to SMS contents
props
[0].Value.lpszW = msgSubject;
props
[1].ulPropTag = PR_MESSAGE_CLASS;//type of message. Without this it'll be an email
props
[1].Value.lpszW = TEXT("IPM.SMStext");
props
[2].ulPropTag = PR_MESSAGE_FLAGS; //mark message as read
props
[2].Value.ul = MSGFLAG_READ | MSGFLAG_FROMME | MSGFLAG_UNSENT;

//set delivery time to system time
SYSTEMTIME stNow
;
FILETIME ftNow
;
GetSystemTime(&stNow);
SystemTimeToFileTime(&stNow, &ftNow)
props
[3].ulPropTag = PR_MESSAGE_DELIVERY_TIME;
props
[3].Value.ft = ftNow;

//assign the property to the message
hr
= pmsg->SetProps(sizeof(props) / sizeof(props[0]), (LPSPropValue)&props, NULL);


To find a recipient name given the number, use FindMatchingContact or IPOutlookItemCollection.Find API.


Reference: http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3852632&SiteID=1

Read More »

Floating popup menu on a Smartphone

As there is no touchscreen on a smartphone and therefore no such thing as "tap and hold", the .NET compact framework for WM6 standard doesn't support the ContextMenu property for textbox controls. Simulate this behaviour by displaying a popup menu whenever the user presses ENTER when the textbox is having focus. This is possible in C++ by using TrackPopupMenu API.


Method 1: Retrieve all the sub-menus under the left soft key menu of the application (or in general, associated with the MainMenu property of the application) and display as pop-up menu on the top left corner of the form:


IntPtr parentWindow = GetActiveWindow()
IntPtr hMenuWnd
= SHFindMenuBar(parentWindow);
IntPtr hMainMenu
= (IntPtr)SendMessage(hMenuWnd, SHCMBM_GETMENU, 0, 0);
IntPtr hMenu
= GetSubMenu(hMainMenu, 0);
TrackPopupMenu(hMenu, TPM_LEFTALIGN, 0, 0, parentWindow, IntPtr.Zero);

[DllImport("coredll.dll", EntryPoint = "TrackPopupMenuEx", SetLastError = true)]
private static extern int TrackPopupMenu(IntPtr hMenu, int wFlags, int x, int y, IntPtr hwnd, IntPtr lprc);

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetActiveWindow();

[DllImport("aygshell.dll", SetLastError=true)]
public static extern IntPtr SHFindMenuBar(IntPtr hWindow);

[DllImport("coredll.dll", SetLastError = true)]
public static extern int SendMessage(IntPtr hWnd, int msg, int wParam, int lParam);

[DllImport("coredll.dll", SetLastError = true)]
public static extern IntPtr GetSubMenu(IntPtr hMenu, int nPos);


The OnClick event of the menu items can be declared the usual way:


this.menuItem3.Click += new System.EventHandler(this.menuItem3_Click);
private void menuItem3_Click(object sender, EventArgs e)
{
}


and menuItem3_Click will be called when the associated menu item is clicked, regardless of whether it comes from the pop-up menu or from the main menu.


This approach has some limitations:


(1) It can only display a sub-menu (via GetSubMenu) but cannot display the entire menu. Removing the GetSubMenu line and passing hMainMenu to TrackPopupMenu immediately causes TrackPopupMenu to return 0 and GetLastError returns ERROR_INVALID_HANDLE. How to display the entire menu?


(2) The menu can only be displayed as pop-up if it is associated with the MainMenu property of the form (otherwise SHFindMenuBar would not work). How to display the pop-up menu without first associating it with the form? One approach is to use reflection to retrieve the private properties of the MainMenu data type, which hopefully contains the IntPtr handle.


Method 2: Create our own menu and pass the handle to TrackPopupMenu:


IntPtr hMenu = CreatePopupMenu();
int index = 100;
AppendMenu(hMenu, MF_STRING, index, "Item1");index++;
AppendMenu(hMenu, MF_STRING, index, "Item2");index++;
AppendMenu(hMenu, MF_STRING, index, "Item3");index++;


It works this way (the popup menu is displayed) but handling the click event is difficult. To trace which item is clicked we must use:


TrackPopupMenu(hMenu, TPM_LEFTALIGN | TPM_RETURNCMD, 0, 0, parentWindow, IntPtr.Zero);


which make the call to TrackPopupMenu a blocking call and will return the index of the selected menu item.


Reference:

http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=3556499&SiteID=1

Read More »

DirectShow & OEM Camera API

Most 3rd party camera applications relies on DirectShow to control the camera. However, on some devices, DirectShow doesn't expose the full camera capabilities. For example, on a Dopod 838Pro, DirectShow only allows recording up to 176x144 7.5fps while the built-in camera can do much better. Further investigation shows that the built-in camera app (and some 3rd party apps such as CoolCamera) do not use DirectShow but instead relies on a proprietary camera API to control the camera. This API exposes the full capabilities of the camera. This web page lists which devices expose usable camera modes via DirectShow and those which cause problems.

On a Dopod 838Pro device, the camera API is hidden in \Windows\HTCCamera1.dll. Calling several functions inside this dll could turn on the camera and the flashlight. However, getting the camera to work properly without proper API documentation seems to be an impossible task. Attempt was made to reverse engineering the HTCCamera1.dll and CoolCamera using Interactive Disassembler (IDA). However, it was not clear from the disassembly how the camera raw frame buffer was retrieved. The part of the code which retrieves the camera data has probably been obfuscated.

The following code shows how to init the camera, turn on the flashlight and then turn it off:

test = 1
result = Camera_Init(test)
test = 0
result = Camera_Begin(test)
result = Camera_FlashLight(CameraPropertyState.STATE_ON)
MessageBox.Show("Flashligh ON. Click OK to turn off.")
result = Camera_FlashLight(CameraPropertyState.STATE_OFF)
result = Camera_Deinit()

<DllImport("HTCCamera1.dll")> _
Private Shared Function Camera_Init(ByRef input As Int32) As Boolean
End Function

<DllImport("HTCCamera1.dll")> _
Private Shared Function Camera_Begin(ByRef input As Int32) As Boolean
End Function

<DllImport("HTCCamera1.dll")> _
Private Shared Function Camera_End(ByRef input As Int32) As Boolean
End Function

Enum CameraPropertyState As Integer
STATE_ON = 1
STATE_OFF = 2
End Enum

<DllImport("HTCCamera1.dll")> _
Private Shared Function Camera_FlashLight(ByVal state As CameraPropertyState) As Boolean
End Function

<DllImport("HTCCamera1.dll")> _
Private Shared Function Camera_Deinit() As Boolean
End Function

Another way to control the flashlight is to use DeviceIOControl. This does not rely on HTCCamera1.dll but is still device-specific

DWORD dwDIOC=0x90002024; //device io (device specific)
int Mode_int = 1;
DeviceIoControl(hCam, dwDIOC, LPVOID(&Mode_int), sizeof(Mode_int), 0, NULL, NULL, NULL);
MessageBox(GetActiveWindow(), L"OK to turn off", L"Flash Light", MB_OK);
Mode_int = 2;
DeviceIoControl(hCam, dwDIOC, LPVOID(&Mode_int), sizeof(Mode_int), 0, NULL, NULL, NULL);
Read More »

Wednesday, November 5, 2008

Read Write Call History

Objective: Retrieve and modify call history from a Windows Mobile device.

Read-only access

The CallLog API (http://msdn.microsoft.com/en-us/library/ms894745.aspx) provides read-only access to call history via its PhoneOpenCallLog and PhoneGetCallLogEntry function. For a C# implementation via P/Invoke, the OpenNetCF has a nice wrapper via its OpenNETCF.Phone namespace. Refer to OpenNETCF 1.4 for full source code.

Read-write access

The Call Log database is in CEDB/EDB format. If you want to modify it you'll have to do it manually using CEDB or EDB API's. For Windows Mobile 5 and higher, use EDB to open the volume \pim.vol in the clog.db database.

To use EDB, the following declaration

#define EDB

must be added on top on the source code (or stdafx.h for apps that use pre-compiled headers).

This code demonstrates how to modify the call log using EDB API. The necessary steps are as follows:

1. Call history is stored in the EDB database pim.vol. Mount the database file first.

CeMountDBVolEx(&m_ceguidDB, L"\\pim.vol", NULL, OPEN_EXISTING);

2. Open the call history table clog.db. Return INVALID_HANDLE_VALUE if error. CEDB_AUTOINCREMENT makes sure that each call to CeReadRecordPropsEx will move the cursor forward to the next record so that no manual seeking in the database is required

HANDLE m_hDBCallLog = CeOpenDatabaseEx2( &m_ceguidDB, &m_ceoidCallLog, TEXT("clog.db"), 0, CEDB_AUTOINCREMENT, NULL);

3. Seek to the beginning/end of the table. Return non-zero if OK, 0 if error.

CEOID ceOIDTemp = CeSeekDatabase(m_hDBCallLog, CEDB_SEEK_BEGINNING, 0, NULL);

4.1. Read the records repeatedly (forward cursor) until there are no records left. 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. Refer to [3] for a description of possible format. We need to define every fields that the record contains:

//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
CEPROPID propCallFlags = MAKELONG(CEVT_I4, 4); //Identify call property (connected, roaming, incoming, outgoing, missed, etc.), every bit has a certain meaning
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
CEPROPID propsToRead[5] = {propStartTime, propEndTime, propCallNumber, propCallName, propCallFlags}; //array of property to retrieve

After that, call CeReadRecordPropsEx to read the record. This function returns non-zero if OK, 0 if failed:

CEOID readRecord = CeReadRecordPropsEx(m_hDBCallLog, CEDB_ALLOWREALLOC, &wProps, propsToRead, reinterpret_cast(&pPropVals), &dwBuf, NULL);

To retrieve the read properties,

  1. check pPropVals[i].wFlags to make sure that the field has been read properly
  2. Check LOWORD(pPropVals[i].propid) for the field data type
  3. According to the field data type, retrieve data from pPropVals[i].val

Deleting a record is straightforward once you have its OID:

CeDeleteRecord(m_hDBCallLog, ceOIDTemp);

Creating a new record is possible via CeWriteRecordProps. This function return the ID of the created record if successful, and zero if failed:

CEPROPVAL propsToWrite[5]; //array of properties to write for the current record
WORD length = sizeof(propsToWrite)/sizeof(CEPROPVAL);
CEOID newRec = CeWriteRecordProps(m_hDBCallLog, 0, length, propsToWrite);

Finally, the volume needs to be unmounted:

CeUnmountDBVol(&m_ceguidDB);

Migrating to C# via P/Invoke:

Most functions are available by dllimporting from coredll.dll. Unsafe code in C# needs to be used as CeReadRecordPropsEx works with pointer to byte buffer, which cannot be simulated via the .NET class IntPtr.

[DllImport("coredll.dll", SetLastError=true)]
static unsafe extern int CeReadRecordPropsEx(IntPtr hDbase, int dwFlags, ref int lpcPropID, IntPtr rgPropID, byte** lplpBuffer, ref int lpcbBuffer, IntPtr hHeap);

As in C++, we have to first mount, open the database and seek to the beginning

CeMountDBVolEx(ref m_ceguidDB, "\\pim.vol", IntPtr.Zero, OPEN_EXISTING);
int m_ceoidCallLog = 0;
IntPtr m_hDBCallLog = CeOpenDatabaseEx2(ref m_ceguidDB, ref m_ceoidCallLog, "clog.db", IntPtr.Zero, CEDB_AUTOINCREMENT, IntPtr.Zero);
int ceOIDTemp = CeSeekDatabase(m_hDBCallLog, CEDB_SEEK_BEGINNING, 0, IntPtr.Zero);

To identify the fields of each records to read, we need to define an array of CEPROPID. Luckily, CEPROPID is just an integer which has to be calculated by providing the column index and the data type to the MAKELONG macro. In C#, however, we can simplify this by pre-calculating the values:

//array containing the property to be retrieved
const int colStartTime = 131136;
const int colEndTime = 196672;
const int colCallNumber = 393247;
const int colCallName = 458783;
const int colCallFlags = 262147;
int[] propsToRead = new int[5] { colStartTime, colEndTime, colCallNumber, colCallName, colCallFlags };
We can proceed to read the record as follows:
int wProps = 5; //length of property array
int dwBuf = 0; //size of read buffer is returned here
byte* retVal; //pointer to returned buffer
//perform actual reading of the record
fixed (int* pArray = propsToRead)
{
int readRecordID = CeReadRecordPropsEx(m_hDBCallLog, CEDB_ALLOWREALLOC, ref wProps, new IntPtr((void*)pArray), &retVal, ref dwBuf, IntPtr.Zero);
}

The record is read properly (the return value readRecordID is valid and retVal seemingly contains data) but retrieving the read data is difficult since the returned value (retVal) is an array of unions which cannot be declared via managed code. To simulate union in .NET, we have tried to explicitly declare the start address of each member in the structure to be the same, but this result in a TypeLoadException

unsafe struct CEBLOB {
int dwCount;
byte* lpb;
};
[StructLayout(LayoutKind.Explicit)]
struct CEVALUNION
{
[FieldOffset(0)]
Int16 iVal;
[FieldOffset(0)]
Int16 uiVal;
[FieldOffset(0)]
long lVal;
[FieldOffset(0)]
Int32 ulVal;
[FieldOffset(0)]
FILETIME filetime;
[FieldOffset(0)]
string lpwstr;
[FieldOffset(0)]
CEBLOB blob;
[FieldOffset(0)]
bool boolVal;
[FieldOffset(0)]
Int64 dblVal;
}

There is no solution for this as of now.


References:

  1. http://www.codeproject.com/KB/mobile/wm_callhistory.aspx?display=Print - Retrieving and Storing Call History
  2. http://blogs.msdn.com/windowsmobile/archive/2005/05/10/416117.aspx - Porting applications from CEDB to EDB
  3. http://forums.microsoft.com/MSDN/ShowPost.aspx?PostID=1838730&SiteID=1 - Structure of clog.db table
  4. http://forums.microsoft.com/msdn/ShowPost.aspx?PostID=192592&SiteID=1 - EDB samples for WM5
Read More »