private void DataChannel_Connected(object sender, EventArgs e)
        {
            try
            {
                // lock to prevent multiple simultaneous calls to the data channel from different threads
                lock (_locker)
                {
                    _logger.Trace("Received Connected event from data channel");

                    // first thing to do is to send the info about this controller
                    var controllerInfo = DataSource.GetControllerInfoData();
                    _dataChannel.Send(controllerInfo);

                    // check state
                    if (_dataChannel.IsConnected && _controlChannel.IsConnected)
                    {
                        State = PhoneControllerState.Ready;
                    }
                }
            }
            catch (Exception ex)
            {
                State = PhoneControllerState.Error;
                RaiseErrorEvent("Error while sending controller info to server: " + ex.Message, ex);
            }
        }
        private void DataChannel_Error(object sender, NetworkErrorEventArgs e)
        {
            _logger.Trace("Received Error event from data channel");

            var state = State;
            if (state == PhoneControllerState.Closed || state == PhoneControllerState.Closing || state == PhoneControllerState.Error)
            {
                // ignore follow-up errors after we're closed or already in error state
                return;
            }

            State = PhoneControllerState.Error;
            RaiseErrorEvent(e);
        }
        private void ControlChannel_ControlCommandReceived(object sender, ControlCommandReceivedEventArgs e)
        {
            _logger.Trace("Control command received: {0}, {1}", e.ControlCommand.DataType, e.ControlCommand.Action);

            if (e.ControlCommand.DataType == DataType.Configuration)
            {
                var command = e.ControlCommand as ConfigurationControlCommand;
                if (command != null && command.Configuration != null)
                {
                    Configure(command.Configuration);
                }
            }
            else
            {
                try
                {
                    // lock to prevent multiple simultaneous calls to the data source and channel from different threads
                    lock (_locker)
                    {
                        if (e.ControlCommand.Action == ControlCommandAction.Start)
                        {
                            DataSource.StartAcquisition(e.ControlCommand.DataType);
                        }
                        else
                        {
                            DataSource.StopAcquisition(e.ControlCommand.DataType);
                            _dataChannel.PurgeFromQueue(e.ControlCommand.DataType);
                        }
                    }
                }
                catch (Exception ex)
                {
                    State = PhoneControllerState.Error;
                    RaiseErrorEvent("Error while processing start/stop control command: " + ex.Message, ex);
                }
            }
        }
        private void CreatePhoneControllerClient(IDataSource dataSource)
        {
            if (dataSource == null)
            {
                throw new ArgumentNullException("dataSource");
            }

            // capture the current synchronization context
            _synchronizationContext = SynchronizationContext.Current;
            if (_synchronizationContext == null)
            {
                // create a default implementation
                _synchronizationContext = new SynchronizationContext();
            }

            // store and set up data source
            DataSource = dataSource;
            DataSource.DataAcquired += DataSource_DataAcquired;

            // create all required networking objects
            _broadCaster = new MulticastClient();
            _broadCaster.ServerDiscovered += BroadCaster_ServerDiscovered;
            _broadCaster.TimeoutElapsed += BroadCaster_TimeoutElapsed;

            _controlChannel = _communicationFactory.CreateControlChannel();
            _controlChannel.Error += ControlChannel_Error;
            _controlChannel.ControlCommandReceived += ControlChannel_ControlCommandReceived;
            _controlChannel.Connected += ControlChannel_Connected;
            _controlChannel.ConnectionClosed += ControlChannel_ConnectionClosed;

            _dataChannel = _communicationFactory.CreateDataChannel();
            _dataChannel.Error += DataChannel_Error;
            _dataChannel.Connected += DataChannel_Connected;
            _dataChannel.ConnectionClosed += DataChannel_ConnectionClosed;

            // configure some default values for the configuration here
            // these typically are overridden by a configuration sent down from the server side
            var configuration = new ControllerConfiguration();
            configuration.AutoReconnectOnActivation = false;
            configuration.TouchInputMargin = new Thickness(90, 20, 20, 20);
            configuration.MinAccelerometerDataRate = 0;
            configuration.MinCompassDataRate = 0;
            configuration.MinGyroscopeDataRate = 0;
            configuration.MinMotionDataRate = 0;
            configuration.MinMillisecondsBetweenMessages = 0; // as fast as possible
            configuration.EnableTracing = false;
            configuration.TracingEndpointAddress = null;
            Configure(configuration);

            State = PhoneControllerState.Created;
        }
        private void BroadCaster_TimeoutElapsed(object sender, EventArgs e)
        {
            lock (_locker)
            {
                _logger.Trace("Received TimeoutElapsed event from broadcaster");

                var state = State;
                // broadcasting is also possible from the closed state (during re-initialization)
                // so we only check for the error state here
                if (state == PhoneControllerState.Error)
                {
                    // ignore follow-up errors after we're closed or already in error state
                    return;
                }

                State = PhoneControllerState.Error;

                // create new error event args
                var eventArgs = new NetworkErrorEventArgs("Timeout while broadcasting.", "TimeOut", null);
                RaiseErrorEvent(eventArgs);
            }
        }
        private void ControlChannel_Connected(object sender, EventArgs e)
        {
            // lock to prevent overlapping operations on the channels
            lock (_locker)
            {
                _logger.Trace("Received Connected event from the control channel");

                // check state
                if (_dataChannel.IsConnected && _controlChannel.IsConnected)
                {
                    State = PhoneControllerState.Ready;
                }
            }
        }
        /// <summary>
        /// Shuts down the controller. After a successful shutdown, the controller transitions to the <c>Closed</c>
        /// state and can be re-initialized later (e.g. re-connected by using the respective <c>ConnectAsync</c> methods).
        /// </summary>
        public void Shutdown()
        {
            try
            {
                // lock to prevent multiple simultaneous calls to any of the involved objects from different threads
                lock (_locker)
                {
                    var state = State;

                    // only proceed when valid (we allow calling shutdown when the state is "Closed" or "Created" etc.
                    // because certain actions like broadcasting are allowed in these states too)
                    if (state == PhoneControllerState.Closing)
                    {
                        return;
                    }

                    _logger.Trace("Shutting down");

                    // transition from closing to close, shutting down everything
                    State = PhoneControllerState.Closing;

                    if (DataSource != null)
                    {
                        DataSource.Shutdown();
                    }

                    if (_broadCaster != null)
                    {
                        _broadCaster.Shutdown();
                    }

                    if (_dataChannel != null)
                    {
                        _dataChannel.Shutdown();
                    }

                    if (_controlChannel != null)
                    {
                        _controlChannel.Shutdown();
                    }

                    State = PhoneControllerState.Closed;
                }
            }
            catch (Exception ex)
            {
                throw new PhoneControllerException("Error while shutting down the phone controller client: " + ex.Message, ex);
            }
        }
        /// <summary>
        /// Shuts down the controller. After a successful shutdown, the controller transitions to the <c>Closed</c>
        /// state and can be re-initialized later.
        /// </summary>
        public void Shutdown()
        {
            try
            {
                // avoid that multiple threads invoke this in parallel
                lock (_locker)
                {
                    var state = State;

                    // shortcut
                    if (state == PhoneControllerState.Created)
                    {
                        State = PhoneControllerState.Closed;
                        return;
                    }

                    // only proceed when valid
                    if (state == PhoneControllerState.Closing || state == PhoneControllerState.Closed)
                    {
                        return;
                    }

                    _logger.Trace("Shutting down");

                    State = PhoneControllerState.Closing;

                    if (_broadCastListener != null)
                    {
                        _broadCastListener.Shutdown();
                    }

                    if (_dataChannel != null)
                    {
                        _dataChannel.Shutdown();
                    }

                    if (_controlChannel != null)
                    {
                        _controlChannel.Shutdown();
                    }

                    State = PhoneControllerState.Closed;
                }
            }
            catch (Exception ex)
            {
                throw new PhoneControllerException("Error while shutting down the phone controller client: " + ex.Message, ex);
            }
        }
        private void DataChannel_DataMessageReceived(object sender, DataMessageEventArgs e)
        {
            // do not report data if we are not ready
            // (e.g. data may still drop in if the client version does not match the
            // expected version and we've transitioned to Error state already,
            // and until the consumer of the library does something like shutting down).
            if (State != PhoneControllerState.Ready)
            {
                return;
            }

            _logger.Trace("Received DataMessageReceived event from data channel");

            // check if we have a controller info data message
            if (e.DataMessage.DataType == DataType.ControllerInfo)
            {
                // make sure that the version information fits what we expect
                var controllerInfoData = e.DataMessage as ControllerInfoData;
                if (controllerInfoData.ClientVersion != Constants.ControllerVersion)
                {
                    _logger.Trace("Client controller version {0} does not match expected version {1}.", controllerInfoData.ClientVersion, Constants.ControllerVersion);

                    State = PhoneControllerState.Error;
                    var error = new ControllerVersionMismatchException(Constants.ControllerVersion, controllerInfoData.ClientVersion);
                    RaiseErrorEvent(error);
                    return;
                }
            }

            // simply pass through
            RaiseDataMessageReceivedEvent(e.DataMessage);
        }
        /// <summary>
        /// Asynchronously connects to a server using the given server address.
        /// </summary>
        /// <param name="ipAddress">The IP address of the server to connect to.</param>
        public void ConnectAsync(IPAddress ipAddress)
        {
            try
            {
                // lock to prevent multiple simultaneous calls to the channels from different threads
                lock (_locker)
                {
                    var state = State;
                    if (state != PhoneControllerState.Created && state != PhoneControllerState.Closed)
                    {
                        throw new InvalidOperationException("Can only initialize from Created or Closed state (current state " + State + ").");
                    }

                    _logger.Trace("Trying to connect to server using IP address {0}", ipAddress);

                    ServerAddress = ipAddress;

                    _controlChannel.ConnectAsync(ServerAddress.ToString(), Constants.CommunicationControlChannelPort);
                    _dataChannel.ConnectAsync(ServerAddress.ToString(), Constants.CommunicationDataChannelPort);

                    State = PhoneControllerState.Initialized;
                }
            }
            catch (Exception ex)
            {
                throw new PhoneControllerException("Error while starting async connection: " + ex.Message, ex);
            }
        }
        private void DataChannel_ClientAccepted(object sender, EventArgs e)
        {
            // avoid multiple threads accessing the channels
            lock (_locker)
            {
                _logger.Trace("Received ClientAccepted event from data channel");

                if (_dataChannel.IsConnected && _controlChannel.IsConnected)
                {
                    State = PhoneControllerState.Ready;
                }
            }
        }
        private void CreatePhoneControllerServer()
        {
            // capture the current synchronization context
            _synchronizationContext = SynchronizationContext.Current;
            if (_synchronizationContext == null)
            {
                // create a default implementation
                _synchronizationContext = new SynchronizationContext();
            }

            // create all required networking objects
            _broadCastListener = new MulticastListener();

            _controlChannel = _communicationFactory.CreateControlChannel();
            _controlChannel.ClientAccepted += ControlChannel_ClientAccepted;
            _controlChannel.ConnectionClosed += ControlChannel_ConnectionClosed;
            _controlChannel.ControlCommandSent += ControlChannel_ControlCommandSent;
            _controlChannel.Error += ControlChannel_Error;

            _dataChannel = _communicationFactory.CreateDataChannel();
            _dataChannel.ClientAccepted += DataChannel_ClientAccepted;
            _dataChannel.ConnectionClosed += DataChannel_ConnectionClosed;
            _dataChannel.Error += DataChannel_Error;
            _dataChannel.DataMessageReceived += DataChannel_DataMessageReceived;

            // set initial state
            State = PhoneControllerState.Created;
        }
        private void ControlChannel_ClientAccepted(object sender, EventArgs e)
        {
            lock (_locker)
            {
                _logger.Trace("Received ClientAccepted event from control channel");

                if (_dataChannel.IsConnected && _controlChannel.IsConnected)
                {
                    State = PhoneControllerState.Ready;
                }
            }
        }
        private void DataSource_DataAcquired(object sender, DataMessageEventArgs e)
        {
            try
            {
                // lock to prevent multiple simultaneous calls to the data channel from different threads
                lock (_locker)
                {
                    _logger.Trace("Received data from the data source: {0}", e.DataMessage.DataType);

                    if (_dataChannel != null && _dataChannel.IsConnected)
                    {
                        // for some messages, we need to do some clean-up here.
                        // explanation: we use two separate sockets to transfer data: TCP and UDP.
                        // some data messages require a logical sequence, e.g. PinchComplete must always occur after the last Pinch.
                        // the problem is that for performance reasons, data messages like Pinch are sent using UDP, whereas
                        // all "Complete" messages are sent using TCP (because their delivery must be guaranteed). This can lead
                        // to the situation that Pinch messages arrive _after_ PinchComplete messages on the server side.
                        // we avoid this here by purging all queued messages of a certain kind if we are about to send a corresponding
                        // "Complete" message.
                        switch (e.DataMessage.DataType)
                        {
                            case DataType.CustomDragComplete:
                                _dataChannel.PurgeFromQueue(DataType.CustomDrag);
                                break;
                            case DataType.DragComplete:
                                _dataChannel.PurgeFromQueue(DataType.FreeDrag);
                                _dataChannel.PurgeFromQueue(DataType.HorizontalDrag);
                                _dataChannel.PurgeFromQueue(DataType.VerticalDrag);
                                break;
                            case DataType.PinchComplete:
                                _dataChannel.PurgeFromQueue(DataType.Pinch);
                                break;
                        }

                        // now actually send the message
                        _dataChannel.Send(e.DataMessage);
                    }
                }
            }
            catch (Exception ex)
            {
                State = PhoneControllerState.Error;
                RaiseErrorEvent("Error while processing and sending acquired data: " + ex.Message, ex);
            }
        }
        private void BroadCaster_ServerDiscovered(object sender, ServerDiscoveredEventArgs e)
        {
            try
            {
                // lock to prevent multiple simultaneous calls to the channels from different threads
                lock (_locker)
                {
                    _logger.Trace("Discovered a server at {0}", e.RemoteAddress);

                    _controlChannel.ConnectAsync(e.RemoteAddress, Constants.CommunicationControlChannelPort);
                    _dataChannel.ConnectAsync(e.RemoteAddress, Constants.CommunicationDataChannelPort);

                    ServerAddress = IPAddress.Parse(e.RemoteAddress);

                    State = PhoneControllerState.Initialized;
                }
            }
            catch (Exception ex)
            {
                State = PhoneControllerState.Error;
                RaiseErrorEvent("Error while connecting control and data channels: " + ex.Message, ex);
            }
        }
 /// <summary>
 /// Initializes a new instance of the <see cref="PhoneControllerStateEventArgs"/> class.
 /// </summary>
 /// <param name="state">The new state of the controller.</param>
 public PhoneControllerStateEventArgs(PhoneControllerState state)
 {
     State = state;
 }
        /// <summary>
        /// Initializes the controller with the given local IP address and starts waiting for incoming client connections.
        /// </summary>
        /// <param name="localAddress">The local IP address to use for the controller.</param>
        public void Initialize(IPAddress localAddress)
        {
            try
            {
                // make sure the state transitioning happens atomically
                lock (_locker)
                {
                    var state = State;
                    if (state != PhoneControllerState.Created && state != PhoneControllerState.Closed)
                    {
                        throw new InvalidOperationException("Can only initialize from Created or Closed state (current state " + State + ").");
                    }

                    _logger.Trace("Initializing with local address {0}", localAddress);

                    _broadCastListener.StartListening();

                    _controlChannel.ListenAsync(localAddress.ToString(), Constants.CommunicationControlChannelPort);
                    _dataChannel.ListenAsync(localAddress.ToString(), Constants.CommunicationDataChannelPort);

                    State = PhoneControllerState.Initialized;
                }
            }
            catch (Exception ex)
            {
                throw new PhoneControllerException("Error while starting the control and data channels: " + ex.Message, ex);
            }
        }