Listening to MTP events

MTP devices can fire events as well. These events are used to educate the PC (or the initiator) about any change in device status. Think of a scenario where you connect a camera extension to an MTP cellphone. The configuration of the cellphone has now changed since it supports camera settings. The device can inform the PC about this change by firing the MTP DeviceInfoChanged event. Similarly if an object was acquired by the device, it can fire the MTP ObjectAdded event. These events are defined in the MTP specification. Devices are also free to generate their custom events.

The MTP driver forwards the MTP events as WPD events and so listeningĀ for MTP events requires the same implementation as listening for WPD events. Be sure to refer back to the WPD eventing post for the rest of the callback class implementation below.

 // Main OnEvent handler called for events
HRESULT __stdcall CPortableDeviceEventsCallback::OnEvent(
    IPortableDeviceValues* pEventParameters)
{
    HRESULT hr = S_OK;

    if (pEventParameters == NULL)
    {
        hr = E_POINTER;
    }

    // Display the event that was fired
    GUID EventId;
    if (hr == S_OK)
    {
      hr = pEventParameters->GetGuidValue(WPD_EVENT_PARAMETER_EVENT_ID, &EventId);       
    }

    // MTP event IDs match the Data2, Data3 and Data4 fields of 
    // WPD_EVENT_MTP_VENDOR_EXTENDED_EVENTS, while Data1 contains
    // the actual event code in the upper WORD
    if (hr == S_OK)
    {
        if ((EventId.Data2 == WPD_EVENT_MTP_VENDOR_EXTENDED_EVENTS.Data2) &&
            (EventId.Data3 == WPD_EVENT_MTP_VENDOR_EXTENDED_EVENTS.Data3) &&
            (memcmp(EventId.Data4, WPD_EVENT_MTP_VENDOR_EXTENDED_EVENTS.Data4, 8) == 0))
        {
            // Right-shift Data1 to get the actual event code
            printf("Received MTP event: 0x%X\n", EventId.Data1 >> 16);

            // MTP event parameters may be present as well in WPD_PROPERTY_MTP_EXT_EVENT_PARAMS
            HRESULT hr2 = S_OK;
            CComPtr spMtpEventParams;
            if (hr2 == S_OK)
            {
                hr2 = pEventParameters->GetIPortableDevicePropVariantCollectionValue(
                                        WPD_PROPERTY_MTP_EXT_EVENT_PARAMS, &spMtpEventParams);
            }

            // Display the event parameters (this is a regular PropVariantCollection)
            ULONG celtParams = 0;
            if (hr2 == S_OK)
            {
                hr2 = spMtpEventParams->GetCount(&celtParams);
            }

            // The order of the parameters match the order the device supplied them in
            for (ULONG ndxParams = 0; ndxParams < celtParams && hr2 == S_OK; ndxParams++)
            {
                PROPVARIANT pvParam = {0};
                
                hr2 = spMtpEventParams->GetAt(ndxParams, &pvParam);

                if (hr2 == S_OK)
                {
                    printf("\tParam[%d]: 0x%X\n", ndxParams + 1, pvParam.ulVal);
                }

                // Not really required to clear the PROPVAR since it is VT_UI4, 
                // but we do it for completeness
                PropVariantClear(&pvParam); 
            }            
        }
    }

    return hr;
}

If the event was an MTP device generated one, the event ID is encoded with WPD_EVENT_MTP_VENDOR_EXTENDED_EVENTS as a mask. The actual event code is in the upper WORD of the Data1 field. The MTP specification also allows upto three parameters to be provided in an event. The MTP driver exposes these through the WPD_PROPERTY_MTP_EXT_EVENT_PARAMS value in the event's parameter collection. This is an IPortableDevicePropVariantCollection interface and we can walk through it using the standard IPortableDevicePropVariantCollection methods. The collection contains PROPVARIANTs with the vartype equal to VT_UI4, so we can de-reference the ulVal member to get to actual MTP parameter.

Now for the caveatĀ :) - the standard MTP events such as ObjectAdded, DeviceInfoChanged, etc. are mapped by the MTP driver to WPD events. So you'll never see these MTP events, instead you'll see equivalent WPD_EVENT_OBJECT_ADDED, WPD_EVENT_OBJECT_UPDATED, WPD_EVENT_DEVICE_CAPABILITIES_UPDATED, etc. events. The event parameters are converted to useful parameters such as WPD_OBJECT_ID indicating which object the device was referring to.

So what is this custom MTP event plumbing mechanism good for? This is useful when the device is capable of generating custom vendor-extended events which your application (probably device-specific) is aware of and is ready to handle. As an example, a cellphone vendor developer may define an SMS_RECEIVED event at the MTP level for the device. The developer can then check for that event in his custom application so that he can retrieve the new SMS.