IOHIDDFREventFilter.mm   [plain text]


/*
 *  IOHIDDFREventFilter.cpp
 *  IOHIDEventSystemPlugIns
 *
 *  Created by dekom on 08/16/2016.
 *  Copyright 2016 Apple Inc. All rights reserved.
 *
 */

#include "IOHIDDFREventFilter.hpp"
#include "IOHIDDebug.h"
#include "IOHIDProperties.h"
#include <new>
#include <IOKit/hid/IOHIDService.h>
#include <IOKit/hid/IOHIDSession.h>
#include <IOKit/hid/IOHIDUsageTables.h>
#include <IOKit/hid/IOHIDPrivateKeys.h>
#include <IOKit/hid/IOHIDEventData.h>
#include <IOKit/hid/IOHIDEventSystemKeys.h>
#include <IOKit/hid/AppleHIDUsageTables.h>
#include <mach/mach_time.h>

#define kKeyboardCancelThresholdMS      100
#define kDFRTouchCancelThresholdMS      100
#define kBioCancelThresholdMS           100

#define ABSOLUTE_TO_MS(abs)         ((abs) * sDFREventFilterTimebaseInfo.numer / sDFREventFilterTimebaseInfo.denom / NSEC_PER_MSEC)
#define DELTA_IN_MS(cur, prev)      ABSOLUTE_TO_MS(cur - prev)

static mach_timebase_info_data_t    sDFREventFilterTimebaseInfo;

// 4F2A35AF-A17D-4020-B62A-0E64B825F069
#define kIOHIDDFREventFilterFactor CFUUIDGetConstantUUIDWithBytes(kCFAllocatorSystemDefault, 0x4F, 0x2A, 0x35, 0xAF, 0xA1, 0x7D, 0x40, 0x20, 0xB6, 0x2A, 0x0E, 0x64, 0xB8, 0x25, 0xF0, 0x69)

extern "C" void * IOHIDDFREventFilterFactory(CFAllocatorRef allocator, CFUUIDRef typeUUID);

//------------------------------------------------------------------------------
// IOHIDDFREventFilterFactory
//------------------------------------------------------------------------------
void *IOHIDDFREventFilterFactory(CFAllocatorRef allocator __unused, CFUUIDRef typeUUID)
{
    if (CFEqual(typeUUID, kIOHIDSessionFilterPlugInTypeID)) {
        void *p = CFAllocatorAllocate(kCFAllocatorDefault, sizeof(IOHIDDFREventFilter), 0);
        return new(p) IOHIDDFREventFilter(kIOHIDDFREventFilterFactor);
    }
    return NULL;
}

// The IOHIDDFREventFilter function table.
IOHIDSessionFilterPlugInInterface IOHIDDFREventFilter::sIOHIDDFREventFilterFtbl =
{
    // Required padding for COM
    NULL,
    // These three are the required COM functions
    IOHIDDFREventFilter::QueryInterface,
    IOHIDDFREventFilter::AddRef,
    IOHIDDFREventFilter::Release,
    // IOHIDSimpleSessionFilterPlugInInterface functions
    IOHIDDFREventFilter::filter,
    NULL,
    NULL,
    // IOHIDSessionFilterPlugInInterface functions
    IOHIDDFREventFilter::open,
    IOHIDDFREventFilter::close,
    NULL,
    NULL,
    IOHIDDFREventFilter::registerService,
    IOHIDDFREventFilter::unregisterService,
    IOHIDDFREventFilter::scheduleWithDispatchQueue,
    IOHIDDFREventFilter::unscheduleFromDispatchQueue,
    IOHIDDFREventFilter::getPropertyForClient,
    IOHIDDFREventFilter::setPropertyForClient,
};

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::IOHIDDFREventFilter
//------------------------------------------------------------------------------
IOHIDDFREventFilter::IOHIDDFREventFilter(CFUUIDRef factoryID):
_sessionInterface(&sIOHIDDFREventFilterFtbl),
_factoryID(static_cast<CFUUIDRef>(CFRetain(factoryID))),
_refCount(1),
_dispatchQueue(0),
_keyboard(NULL),
_dfr(NULL),
_session(NULL),
_lastDFREvent(NULL),
_keyboardFilterEnabled(true),
_touchIDFilterEnabled(true),
_cancelledTouchInProgress(false),
_bioInProgress(false),
_cancel(false),
_lastDFREventTime(0),
_lastKeyboardEventTime(0),
_cancelledEventCount(0),
_keyboardCancelThresholdMS(kKeyboardCancelThresholdMS),
_dfrTouchCancelThresholdMS(kDFRTouchCancelThresholdMS),
_bioCancelThresholdMS(kBioCancelThresholdMS),
_eventCancelTimer(0)
{
    CFPlugInAddInstanceForFactory(factoryID);
    
    if (sDFREventFilterTimebaseInfo.denom == 0) {
        (void) mach_timebase_info(&sDFREventFilterTimebaseInfo);
    }
};

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::~IOHIDDFREventFilter
//------------------------------------------------------------------------------
IOHIDDFREventFilter::~IOHIDDFREventFilter()
{
    CFPlugInRemoveInstanceForFactory(_factoryID);
    CFRelease(_factoryID);
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::QueryInterface
//------------------------------------------------------------------------------
HRESULT IOHIDDFREventFilter::QueryInterface(void *self, REFIID iid, LPVOID *ppv)
{
    return static_cast<IOHIDDFREventFilter *>(self)->QueryInterface(iid, ppv);
}

HRESULT IOHIDDFREventFilter::QueryInterface(REFIID iid, LPVOID *ppv)
{
    // Create a CoreFoundation UUIDRef for the requested interface.
    CFUUIDRef interfaceID = CFUUIDCreateFromUUIDBytes(NULL, iid);
    // Test the requested ID against the valid interfaces.
    if (CFEqual(interfaceID, kIOHIDSimpleSessionFilterPlugInInterfaceID) || CFEqual(interfaceID, kIOHIDSessionFilterPlugInInterfaceID)) {
        AddRef();
        *ppv = this;
        CFRelease(interfaceID);
        return S_OK;
    }
    if (CFEqual(interfaceID, IUnknownUUID)) {
        // If the IUnknown interface was requested, same as above.
        AddRef();
        *ppv = this;
        CFRelease(interfaceID);
        return S_OK;
    }
    // Requested interface unknown, bail with error.
    *ppv = NULL;
    CFRelease(interfaceID);
    return E_NOINTERFACE;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::AddRef
//------------------------------------------------------------------------------
ULONG IOHIDDFREventFilter::AddRef(void *self)
{
    return static_cast<IOHIDDFREventFilter *>(self)->AddRef();
}

ULONG IOHIDDFREventFilter::AddRef()
{
    _refCount += 1;
    return _refCount;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::Release
//------------------------------------------------------------------------------
ULONG IOHIDDFREventFilter::Release(void *self)
{
    return static_cast<IOHIDDFREventFilter *>(self)->Release();
}

ULONG IOHIDDFREventFilter::Release()
{
    _refCount -= 1;
    if (_refCount == 0) {
        delete this;
        return 0;
    }
    return _refCount;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::open
//------------------------------------------------------------------------------
boolean_t IOHIDDFREventFilter::open(void *self, IOHIDSessionRef session, IOOptionBits options)
{
    return static_cast<IOHIDDFREventFilter *>(self)->open(session, options);
}

boolean_t IOHIDDFREventFilter::open(IOHIDSessionRef session, IOOptionBits options __unused)
{
    _session = session;
    
    return true;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::close
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::close(void *self, IOHIDSessionRef session, IOOptionBits options)
{
    static_cast<IOHIDDFREventFilter *>(self)->close(session, options);
}

void IOHIDDFREventFilter::close(IOHIDSessionRef session __unused, IOOptionBits options __unused)
{
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::registerService
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::registerService(void *self, IOHIDServiceRef service)
{
    static_cast<IOHIDDFREventFilter *>(self)->registerService(service);
}

void IOHIDDFREventFilter::registerService(IOHIDServiceRef service)
{
    if (IOHIDServiceConformsTo(service, kHIDPage_GenericDesktop, kHIDUsage_GD_Keyboard)) {
        CFBooleanRef builtin = (CFBooleanRef)IOHIDServiceGetProperty(service, CFSTR(kIOHIDBuiltInKey));
        if (builtin == kCFBooleanTrue) {
            _keyboard = service;
        }
    } else if (IOHIDServiceConformsTo(service, kHIDPage_AppleVendor, kHIDUsage_AppleVendor_DFR)) {
        _dfr = service;
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::unregisterService
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::unregisterService(void *self, IOHIDServiceRef service)
{
    static_cast<IOHIDDFREventFilter *>(self)->unregisterService(service);
}

void IOHIDDFREventFilter::unregisterService(IOHIDServiceRef service)
{
    if (service == _keyboard) {
        _keyboard = NULL;
    } else if (service == _dfr) {
        _dfr = NULL;
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::scheduleWithDispatchQueue
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::scheduleWithDispatchQueue(void *self, dispatch_queue_t queue)
{
    static_cast<IOHIDDFREventFilter *>(self)->scheduleWithDispatchQueue(queue);
}

void IOHIDDFREventFilter::scheduleWithDispatchQueue(dispatch_queue_t queue)
{
    _dispatchQueue = queue;
    
    _eventCancelTimer = dispatch_source_create(DISPATCH_SOURCE_TYPE_TIMER, 0, 0, _dispatchQueue);
    if (_eventCancelTimer != NULL) {
        dispatch_source_set_event_handler(_eventCancelTimer, ^(void) {
            dispatch_source_set_timer(_eventCancelTimer, DISPATCH_TIME_FOREVER, 0, 0);
            _cancel = false;
            if (_bioInProgress) {
                // We're not guaranteed a finger off event, so set this to false
                _bioInProgress = false;
            }
        });
        dispatch_source_set_timer(_eventCancelTimer, DISPATCH_TIME_FOREVER, 0, 0);
        dispatch_resume(_eventCancelTimer);
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::unscheduleFromDispatchQueue
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::unscheduleFromDispatchQueue(void *self, dispatch_queue_t queue)
{
    static_cast<IOHIDDFREventFilter *>(self)->unscheduleFromDispatchQueue(queue);
}

void IOHIDDFREventFilter::unscheduleFromDispatchQueue(dispatch_queue_t queue __unused)
{
    if (_lastDFREvent) {
        CFRelease(_lastDFREvent);
        _lastDFREvent = NULL;
    }
    
    if (_eventCancelTimer) {
        dispatch_source_cancel(_eventCancelTimer);
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::filter
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDDFREventFilter::filter(void *self, IOHIDServiceRef sender, IOHIDEventRef event)
{
    return static_cast<IOHIDDFREventFilter *>(self)->filter(sender, event);
}

IOHIDEventRef IOHIDDFREventFilter::filter(IOHIDServiceRef sender, IOHIDEventRef event)
{
    if (!event) {
        goto exit;
    }
    
    if (sender != _dfr && sender != _keyboard && IOHIDEventGetType(event) != kIOHIDEventTypeBiometric) {
        goto exit;
    }
    
    if (IOHIDEventGetType(event) == kIOHIDEventTypeKeyboard) {
        handleKeyboardEvent(event);
        goto exit;
    }
    
    if (IOHIDEventGetType(event) == kIOHIDEventTypeBiometric) {
        handleBiometricEvent(event);
        goto exit;
    }
    
    if (IOHIDEventGetType(event) == kIOHIDEventTypeDigitizer) {
        if ((IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerEventMask) & kIOHIDDigitizerEventCancel) != 0) {
            goto exit;
        }
        
        if (!handleDFREvent(event)) {
            // keep track of active touches during cancellation phase, so we know to continue to cancel them
            _cancelledTouchInProgress = (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerRange) == 1);
            
            HIDLogDebug("Event cancelled due to %s. touch: %d flags = %x", _bioInProgress ? "touch ID" : "active keys", _cancelledTouchInProgress,  (unsigned int)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerEventMask));
            
            _cancelledEventCount++;
            CFRelease(event);
            event = NULL;
        } else if (_cancelledTouchInProgress) {
            // Touch has ended on DFR, allow events
            _cancelledTouchInProgress = (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerRange) == 1);

            HIDLogDebug("Event cancelled due to touch in progress.");
            CFRelease(event);
            event = NULL;
        } else {
            // end cancellation phase
            _cancel = false;
        }
    }
    
exit:
    return event;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::getPropertyForClient
//------------------------------------------------------------------------------
CFTypeRef IOHIDDFREventFilter::getPropertyForClient(void *self, CFStringRef key, CFTypeRef client)
{
    return static_cast<IOHIDDFREventFilter *>(self)->getPropertyForClient(key,client);
}

CFTypeRef IOHIDDFREventFilter::getPropertyForClient(CFStringRef key, CFTypeRef client __unused)
{
    CFTypeRef result = NULL;
    
    if (CFEqual(key, CFSTR(kIOHIDDFRKeyboardEventFilterEnabledKey))) {
        result = _keyboardFilterEnabled ? kCFBooleanTrue : kCFBooleanFalse;
    } else if (CFEqual(key, CFSTR(kIOHIDDFRTouchIDEventFilterEnabledKey))) {
        result = _touchIDFilterEnabled ? kCFBooleanTrue : kCFBooleanFalse;
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterKeyboardCancelThreshold))) {
        result = CFNumberRefWrap((SInt32)_keyboardCancelThresholdMS);
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterDFRTouchCancelThreshold))) {
        result = CFNumberRefWrap((SInt32)_dfrTouchCancelThresholdMS);
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterBiometricCancelThreshold))) {
        result = CFNumberRefWrap((SInt32)_bioCancelThresholdMS);
    } else if (CFEqual(key, CFSTR(kIOHIDSessionFilterDebugKey))) {
        CFMutableDictionaryRefWrap serializer;
        serialize(serializer);
        
        if (serializer) {
            result = CFRetain(serializer.Reference());
        }
    }
    
    return result;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::setPropertyForClient
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::setPropertyForClient(void *self, CFStringRef key, CFTypeRef property, CFTypeRef client)
{
    static_cast<IOHIDDFREventFilter *>(self)->setPropertyForClient(key, property, client);
}

void IOHIDDFREventFilter::setPropertyForClient(CFStringRef key, CFTypeRef property, CFTypeRef client __unused)
{
    if (CFEqual(key, CFSTR(kIOHIDDFRKeyboardEventFilterEnabledKey))) {
        _keyboardFilterEnabled = CFBooleanGetValue((CFBooleanRef)property);
    } else if (CFEqual(key, CFSTR(kIOHIDDFRTouchIDEventFilterEnabledKey))) {
        _touchIDFilterEnabled = CFBooleanGetValue((CFBooleanRef)property);
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterKeyboardCancelThreshold))) {
        if (property && CFGetTypeID(property) == CFNumberGetTypeID()) {
            CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &_keyboardCancelThresholdMS);
        }
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterDFRTouchCancelThreshold))) {
        if (property && CFGetTypeID(property) == CFNumberGetTypeID()) {
            CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &_dfrTouchCancelThresholdMS);
        }
    } else if (CFEqual(key, CFSTR(kIOHIDDFREventFilterBiometricCancelThreshold))) {
        if (property && CFGetTypeID(property) == CFNumberGetTypeID()) {
            CFNumberGetValue((CFNumberRef)property, kCFNumberSInt32Type, &_bioCancelThresholdMS);
        }
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleKeyboardEvent
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::handleKeyboardEvent(IOHIDEventRef event)
{
    uint32_t    usage;
    uint32_t    usagePage;
    uint32_t    keyDown;
    uint32_t    flags;
    Key         key;
    
    usage       = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsage);
    usagePage   = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardUsagePage);
    keyDown     = (uint32_t)IOHIDEventGetIntegerValue(event, kIOHIDEventFieldKeyboardDown);
    flags       = (uint32_t)IOHIDEventGetEventFlags(event);
    
    if ((IOHIDEventGetIntegerValue (event, kIOHIDEventFieldKeyboardSlowKeyPhase) == kIOHIDKeyboardSlowKeyPhaseStart ||
         IOHIDEventGetIntegerValue (event, kIOHIDEventFieldKeyboardSlowKeyPhase) == kIOHIDKeyboardSlowKeyPhaseAbort)) {
        return;
    }
    
    key = Key(usagePage, usage);
    if (keyDown) {
        uint64_t current = mach_absolute_time();
        uint64_t deltaMS = _lastDFREventTime ? DELTA_IN_MS(current, _lastDFREventTime) : 0;
        
        _activeKeys.insert(std::make_pair(key, KeyAttribute(flags)));
        if (!modifierPressed() && key.isTopRow() && !(flags & kIOHIDKeyboardIsRepeat) && deltaMS <= _dfrTouchCancelThresholdMS) {
            _lastKeyboardEventTime = IOHIDEventGetTimeStamp(event);
            startTouchCancellation();
        }
    } else {
        auto iter = _activeKeys.find(key);
        if (iter!=_activeKeys.end()) {
            _activeKeys.erase(iter);
        }
        
        if (!topRowPressed() && !_bioInProgress && !_cancelledTouchInProgress) {
            _cancel = false;
        }
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleBiometicEvent
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::handleBiometricEvent(IOHIDEventRef event)
{
    if (!_touchIDFilterEnabled) {
        return;
    }
    
    if (IOHIDEventGetType(event) == kIOHIDEventTypeBiometric) {
        if (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldBiometricLevel) == 1) {
            _bioInProgress = true;
            startTouchCancellation();
            
            dispatch_source_set_timer(_eventCancelTimer,
                                      dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_MSEC * _bioCancelThresholdMS),
                                      DISPATCH_TIME_FOREVER, 0);
        } else {
            _bioInProgress = false;
            _cancel = false;
            dispatch_source_set_timer(_eventCancelTimer, DISPATCH_TIME_FOREVER, 0, 0);
        }
    }
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::modifierPressed
//------------------------------------------------------------------------------
bool IOHIDDFREventFilter::modifierPressed() {
    auto iter = _activeKeys.begin();
    for (; iter != _activeKeys.end(); ++iter) {
        if (iter->first.isModifier()) {
            return true;
        }
    }
    return false;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::topRowPressed
//------------------------------------------------------------------------------
bool IOHIDDFREventFilter::topRowPressed() {
    auto iter = _activeKeys.begin();
    for (; iter != _activeKeys.end(); ++iter) {
        if (iter->first.isTopRow()) {
            return true;
        }
    }
    return false;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::handleDFREvent
//------------------------------------------------------------------------------
IOHIDEventRef IOHIDDFREventFilter::handleDFREvent(IOHIDEventRef event)
{
    IOHIDEventRef   result  = event;
    uint64_t        current = mach_absolute_time();
    uint64_t        deltaMS = DELTA_IN_MS(current, _lastKeyboardEventTime);
    
    // Cancel events when a non-modifier top row key is pressed within then cancel threshold
    // or biometric event is in progress
    
    if ((topRowPressed() && !modifierPressed() && _keyboardFilterEnabled && deltaMS <= _keyboardCancelThresholdMS) ||
        (_bioInProgress)) {
        result = NULL;
        goto exit;
    }
    
    if (_lastDFREvent) {
        CFRelease(_lastDFREvent);
        _lastDFREvent = NULL;
    }
    
    // keep track of how long we're touching for
    if (IOHIDEventGetIntegerValue(event, kIOHIDEventFieldDigitizerRange) == 1) {
        if (_lastDFREventTime == 0) {
            _lastDFREventTime = IOHIDEventGetTimeStamp(event);
        }
        // save last event to dispatch on next cancellation
        _lastDFREvent = event;
        CFRetain(event);
    } else {
        _lastDFREventTime = 0;
    }
    
exit:
    return result;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::startTouchCancellation
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::startTouchCancellation()
{
    if (_cancel) {
        return;
    }
    
    if (_lastDFREvent) {
        CFArrayRef children = NULL;
        
        // Set cancel flag for event and all child digitizer events
        IOHIDEventSetIntegerValue(_lastDFREvent, kIOHIDEventFieldDigitizerEventMask, kIOHIDDigitizerEventCancel);
        
        children = IOHIDEventGetChildren(_lastDFREvent);
        for (CFIndex index = 0, count = children ? CFArrayGetCount(children) : 0; index < count; index++) {
            IOHIDEventRef child = (IOHIDEventRef)CFArrayGetValueAtIndex(children, index);
            if (IOHIDEventGetType(child) == kIOHIDEventTypeDigitizer) {
                IOHIDEventSetIntegerValue(child, kIOHIDEventFieldDigitizerEventMask, kIOHIDDigitizerEventCancel);
            }
        }
        
        IOHIDEventSetTimeStamp(_lastDFREvent, mach_absolute_time());
        _IOHIDSessionDispatchEvent(_session, _lastDFREvent);
        HIDLogDebug("Dispatch touch cancel event");
        
        // a touch is in progress, continue to cancel events until it is lifted
        _cancelledTouchInProgress = true;
        CFRelease(_lastDFREvent);
        _lastDFREvent = NULL;
    }
    
    _cancel = true;
}

//------------------------------------------------------------------------------
// IOHIDDFREventFilter::serialize
//------------------------------------------------------------------------------
void IOHIDDFREventFilter::serialize(CFMutableDictionaryRef dict) const {
    CFMutableDictionaryRefWrap serializer (dict);
    serializer.SetValueForKey(CFSTR("Class"), CFSTR("IOHIDDFREventFilter"));
    serializer.SetValueForKey(CFSTR("Keyboard Filter Enabled"), CFNumberRefWrap(_keyboardFilterEnabled));
    serializer.SetValueForKey(CFSTR("TouchID Filter Enabled"), CFNumberRefWrap(_touchIDFilterEnabled));
    serializer.SetValueForKey(CFSTR("Touch in Progress"), CFNumberRefWrap(_cancelledTouchInProgress));
    serializer.SetValueForKey(CFSTR("Biometry in Progress"), CFNumberRefWrap(_bioInProgress));
    serializer.SetValueForKey(CFSTR("Cancellation in Progress"), CFNumberRefWrap(_cancel));
    serializer.SetValueForKey(CFSTR("Cancelled Event Count"), CFNumberRefWrap(_cancelledEventCount));
    serializer.SetValueForKey(CFSTR("Keyboard Cancel Threshold (ms)"), CFNumberRefWrap(_keyboardCancelThresholdMS));
    serializer.SetValueForKey(CFSTR("DFR Touch Cancel Threshold (ms)"), CFNumberRefWrap(_dfrTouchCancelThresholdMS));
    serializer.SetValueForKey(CFSTR("Biometric Cancel Threshold (ms)"), CFNumberRefWrap(_bioCancelThresholdMS));
}