text: //*********************************************************
  //
  // Copyright (c) Microsoft. All rights reserved.
  //
  //*********************************************************
  
  //
  // Scenario1_DeviceConnect.xaml.cpp
  // Implementation of the Scenario1 class
  //
  
  #include "pch.h"
  #include "Scenario1_DeviceConnect.xaml.h"
  #include "EventHandlerForDevice.h"
  #include "MainPage.xaml.h"
  
  using namespace Platform;
  using namespace Platform::Collections;
  using namespace Concurrency;
  using namespace Windows::ApplicationModel;
  using namespace Windows::Devices::Enumeration;
  using namespace Windows::Devices::HumanInterfaceDevice;
  using namespace Windows::Foundation;
  using namespace Windows::UI::Core;
  using namespace Windows::UI::Xaml;
  using namespace Windows::UI::Xaml::Navigation;
  using namespace SDKSample::Common;
  using namespace SDKSample::CustomHidDeviceAccess;
  
  Platform::String^ DeviceConnect::ButtonNameDisconnectFromDevice = "Disconnect from device";
  Platform::String^ DeviceConnect::ButtonNameDisableReconnectToDevice = "Do not automatically reconnect to device that was just closed";
  
  DeviceConnect::DeviceConnect(void) :
      listOfDevices(ref new Vector<DeviceListEntry^>()),
      deviceWatchers(ref new Vector<DeviceWatcher^>()),
      watchersStarted(false),
      watchersSuspended(false),
      isAllDevicesEnumerated(false)
  {
      InitializeComponent();
  }
  
  /// <summary>
  /// Invoked when this page is about to be displayed in a Frame.
  /// 
  /// Create the DeviceWatcher objects when the user navigates to this page so the UI list of devices is populated.
  /// </summary>
  /// <param name="eventArgs">Event data that describes how this page was reached.  The Parameter
  /// property is typically used to configure the page.</param>
  void DeviceConnect::OnNavigatedTo(NavigationEventArgs^ /* eventArgs */)
  {
      // If we are connected to the device or planning to reconnect, we should disable the list of devices
      // to prevent the user from opening a device without explicitly closing or disabling the auto reconnect
      if (EventHandlerForDevice::Current->IsDeviceConnected 
          || (EventHandlerForDevice::Current->IsEnabledAutoReconnect
          && EventHandlerForDevice::Current->DeviceInformation != nullptr))
      {
          UpdateConnectDisconnectButtonsAndList(false);
  
          // These notifications will occur if we are waiting to reconnect to device when we start the page
          EventHandlerForDevice::Current->OnDeviceConnected = ref new TypedEventHandler<EventHandlerForDevice^, DeviceInformation^>(this, &DeviceConnect::OnDeviceConnected);
          EventHandlerForDevice::Current->OnDeviceClose = ref new TypedEventHandler<EventHandlerForDevice^, DeviceInformation^>(this, &DeviceConnect::OnDeviceClosing);
      }
      else
      {
          UpdateConnectDisconnectButtonsAndList(true);
      }
  
      // Begin watching out for app events
      StartHandlingAppEvents();
  
      // Initalize the desired device watchers so that we can watch for when devices are connected/removed
      InitializeDeviceWatchers();
      StartDeviceWatchers();
  
      DeviceListSource->Source = listOfDevices;
  }
  
  /// <summary>
  /// Unregister from App events and DeviceWatcher events because this page will be unloaded.
  /// </summary>
  /// <param name="eventArgs"></param>
  void DeviceConnect::OnNavigatedFrom(NavigationEventArgs^ /* eventArgs */)
  {
      StopDeviceWatchers();
      StopHandlingAppEvents();
  
      // We no longer care about the device being connected
      EventHandlerForDevice::Current->OnDeviceConnected = nullptr;
      EventHandlerForDevice::Current->OnDeviceClose = nullptr;
  }
  
  void DeviceConnect::ConnectToDevice_Click(Object^ /* sender */, RoutedEventArgs^ /* eventArgs */)
  {
      auto selection = ConnectDevices->SelectedItems;
      DeviceListEntry^ entry = nullptr;
  
      if (selection->Size > 0)
      {
          auto obj = selection->GetAt(0);
          entry = safe_cast<DeviceListEntry^>(obj);
  
          // Create an EventHandlerForDevice to watch for the device we are connecting to
          EventHandlerForDevice::CreateNewEventHandlerForDevice();
  
          // Get notified when the device was successfully connected to or about to be closed
          EventHandlerForDevice::Current->OnDeviceConnected = ref new TypedEventHandler<EventHandlerForDevice^, DeviceInformation^>(this, &DeviceConnect::OnDeviceConnected);
          EventHandlerForDevice::Current->OnDeviceClose = ref new TypedEventHandler<EventHandlerForDevice^, DeviceInformation^>(this, &DeviceConnect::OnDeviceClosing);
  
          // It is important that the FromIdAsync call is made on the UI thread because the consent prompt can only be displayed
          // on the UI thread. Since this method is invoked by the UI, we are already in the UI thread.
          create_task(EventHandlerForDevice::Current->OpenDeviceAsync(entry->DeviceInformation, entry->DeviceSelector)).then([this] (task<bool> openDeviceTask)
          {
              auto openSuccess = openDeviceTask.get();
  
              // Disable connect button if we connected to the device
              UpdateConnectDisconnectButtonsAndList(!openSuccess);
          });
      }
  }
  
  void DeviceConnect::DisconnectFromDevice_Click(Object^ /* sender */, RoutedEventArgs^ /* eventArgs */)
  {
      auto selection = ConnectDevices->SelectedItems;
      DeviceListEntry^ entry = nullptr;
  
      // Prevent auto reconnect because we are voluntarily closing it
      // Re-enable the ConnectDevice list and ConnectToDevice button if the connected/opened device was removed
      EventHandlerForDevice::Current->IsEnabledAutoReconnect = false;
  
      if (selection->Size > 0)
      {
          auto obj = selection->GetAt(0);
          entry = safe_cast<DeviceListEntry^>(obj);
  
          if (entry != nullptr)
          {
              EventHandlerForDevice::Current->CloseDevice();
          }
      }
  
      UpdateConnectDisconnectButtonsAndList(true);
  }
  
  /// <summary>
  /// Initialize device watchers to watch for the SuperMUTT.
  /// Several other ways of using a device selector to get the desired devices (the devices are really DeviceInformation objects):
  /// 1) Using only the Usage Page and the Usage Id of the device (enumerates any device with the same usage page and usage id)
  /// 2) Using the Usage Page, Usage Id, Vendor Id, Product Id (Same as above except it also matches the VIDs and PIDs)
  ///
  /// GetDeviceSelector return an AQS string that can be passed directly into DeviceWatcher.createWatcher() or  DeviceInformation.createFromIdAsync(). 
  ///
  /// In this sample, a DeviceWatcher will be used to watch for devices because we can detect surprise device removals.
  /// </summary>
  void DeviceConnect::InitializeDeviceWatchers(void)
  {
      // SuperMUTT
      auto superMuttSelector = HidDevice::GetDeviceSelector(SuperMutt::Device::UsagePage, SuperMutt::Device::UsageId, SuperMutt::Device::Vid, SuperMutt::Device::Pid);
      
      // Create a device watcher to look for instances of the OSRFX2 device interface and SuperMUTT device
      auto superMuttWatcher = DeviceInformation::CreateWatcher(superMuttSelector);
  
      // Allow the EventHandlerForDevice to handle device watcher events that relate to or affect our device (i.e. device removal, addition, app suspension/resume)
      AddDeviceWatcher(superMuttWatcher, superMuttSelector);
  }
  
  void DeviceConnect::StartHandlingAppEvents(void)
  {
      // This event is raised when the app is exited and when the app is suspended
      appSuspendEventToken = App::Current->Suspending += ref new SuspendingEventHandler(this, &DeviceConnect::OnAppSuspension);
      appResumeEventToken = App::Current->Resuming += ref new EventHandler<Object^>(this, &DeviceConnect::OnAppResume);
  }
  
  void DeviceConnect::StopHandlingAppEvents(void)
  {
      // This event is raised when the app is exited and when the app is suspended
      App::Current->Suspending -= appSuspendEventToken;
      appSuspendEventToken.Value = 0;
  
      App::Current->Resuming -= appResumeEventToken;
      appResumeEventToken.Value = 0;
  }
  
  /// <summary>
  /// Registers for Added, Removed, and Enumerated events on the provided deviceWatcher before adding it to an internal list.
  /// </summary>
  /// <param name="deviceWatcher"></param>
  /// <param name="deviceSelector">The AQS used to create the device watcher</param>  
  void DeviceConnect::AddDeviceWatcher(DeviceWatcher^ deviceWatcher, String^ deviceSelector)
  {
      deviceWatcher->Added += ref new TypedEventHandler<DeviceWatcher^, DeviceInformation^>([this, deviceSelector](DeviceWatcher^ deviceWatcher, DeviceInformation^ deviceInformation)
      {
          // Forward this event to a more generic event handler for added devices, but provide it with a deviceSelector that is
          // created that device watcher
          OnDeviceAdded(deviceWatcher, deviceInformation, deviceSelector);
      });
      deviceWatcher->Removed += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>(this, &DeviceConnect::OnDeviceRemoved);
      deviceWatcher->EnumerationCompleted += ref new TypedEventHandler<DeviceWatcher^, Object^>(this, &DeviceConnect::OnDeviceEnumerationComplete);
  
      deviceWatchers->Append(deviceWatcher);
  }
  
  /// <summary>
  /// Starts all device watchers including ones that have been individually stopped.
  /// </summary>
  void DeviceConnect::StartDeviceWatchers(void)
  {
      // Start all device watchers
      watchersStarted = true;
      isAllDevicesEnumerated = false;
  
      for (auto deviceWatcher : deviceWatchers)
      {
          if ((deviceWatcher->Status != DeviceWatcherStatus::Started)
              && (deviceWatcher->Status != DeviceWatcherStatus::EnumerationCompleted))
          {
              deviceWatcher->Start();
          }
      }
  }
  
  /// <summary>
  /// Stops all device watchers.
  /// </summary>
  void DeviceConnect::StopDeviceWatchers(void)
  {
      // Stop all device watchers
      for (auto deviceWatcher : deviceWatchers)
      {
          if ((deviceWatcher->Status == DeviceWatcherStatus::Started)
              || (deviceWatcher->Status == DeviceWatcherStatus::EnumerationCompleted))
          {
              deviceWatcher->Stop();
          }
      }
  
      // Clear the list of devices so we don't have potentially disconnected devices around
      ClearDeviceEntries();
  
      watchersStarted = false;
  }
  
  /// <summary>
  /// Creates a DeviceListEntry for a device and adds it to the list of devices in the UI
  /// </summary>
  /// <param name="deviceInformation">DeviceInformation on the device to be added to the list</param>
  /// <param name="deviceSelector">The AQS used to find this device</param>
  void DeviceConnect::AddDeviceToList(DeviceInformation^ deviceInformation, String^ deviceSelector)
  {
      // search the device list for a device with a matching interface ID
      auto match = FindDevice(deviceInformation->Id);
  
      // Add the device if it's new
      if (match == nullptr) 
      {
          // Create a new element for this device interface, and queue up the query of its
          // device information
          match = ref new DeviceListEntry(deviceInformation, deviceSelector);
  
          // Add the new element to the end of the list of devices
          listOfDevices->Append(match);
      }   
  }
  
  void DeviceConnect::RemoveDeviceFromList(String^ deviceId)
  {
      // Search the list of devices for one with a matching ID.  Move the matched 
      // item to the end of the list.
      for (uint32 entryIndex = 0; entryIndex < listOfDevices->Size; entryIndex++)
      {
          DeviceListEntry^ entry = listOfDevices->GetAt(entryIndex);
  
          if (entry->DeviceInformation->Id == deviceId)
          {
              listOfDevices->RemoveAt(entryIndex);
  
              break;
          }
      }
  }
  
  void DeviceConnect::ClearDeviceEntries(void)
  {
      listOfDevices->Clear();
  }
  
  /// <summary>
  /// Searches through the existing list of devices for the first DeviceListEntry that has
  /// the specified Id.
  /// </summary>
  /// <param name="Id">Id of the device that is being searched for</param>
  /// <returns>DeviceListEntry that has the provided Id; else a nullptr</returns>
  DeviceListEntry^ DeviceConnect::FindDevice(String^ deviceId)
  {
      if (deviceId != nullptr)
      {
          for (DeviceListEntry^ entry : listOfDevices)
          {
              if (entry->DeviceInformation->Id == deviceId)
              {
                  return entry;
              }
          }
      }
      
      return nullptr;
  }
  
  /// <summary>
  /// We must stop the DeviceWatchers because device watchers will continue to raise events even if
  /// the app is in suspension, which is not desired (drains battery). We resume the device watcher once the app resumes again.
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="eventArgs"></param>
  void DeviceConnect::OnAppSuspension(Object^ /* sender */, SuspendingEventArgs^ /* args */)
  {
      if (watchersStarted)
      {
          watchersSuspended = true;
          StopDeviceWatchers();
      }
      else
      {
          watchersSuspended = false;
      }
  }
  
  /// <summary>
  /// See OnAppSuspension for why we are starting the device watchers again
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="arg"></param>
  void DeviceConnect::OnAppResume(Object^ /* sender */, Object^ /* args */)
  {
      if (watchersSuspended)
      {
          watchersSuspended = false;
          StartDeviceWatchers();
      }
  }
  
  /// <summary>
  /// We will remove the device from the UI
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="deviceInformationUpdate"></param>
  void DeviceConnect::OnDeviceRemoved(DeviceWatcher^ /* sender */, DeviceInformationUpdate^ deviceInformationUpdate)
  {
      MainPage::Current->Dispatcher->RunAsync(
          CoreDispatcherPriority::Normal,
          ref new DispatchedHandler([this, deviceInformationUpdate]()
      {
          MainPage::Current->NotifyUser(deviceInformationUpdate->Id + " was removed.", NotifyType::StatusMessage);
  
          RemoveDeviceFromList(deviceInformationUpdate->Id);
      }));
      
  }
  
  /// <summary>
  /// This function will add the device to the listOfDevices so that it shows up in the UI
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="deviceInformation"></param>
  /// <param name="deviceSelector">AQS used to create the device watcher (the sender)</param>
  void DeviceConnect::OnDeviceAdded(DeviceWatcher^ /* sender */, DeviceInformation^ deviceInformation, String^ deviceSelector)
  {
      MainPage::Current->Dispatcher->RunAsync(
          CoreDispatcherPriority::Normal,
          ref new DispatchedHandler([this, deviceInformation, deviceSelector]()
      {
          MainPage::Current->NotifyUser(deviceInformation->Id + " was added.", NotifyType::StatusMessage);
  
          AddDeviceToList(deviceInformation, deviceSelector);
      }));
  }
  
  /// <summary>
  /// Notifies the UI that we are done enumerating devices
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="args"></param>
  void DeviceConnect::OnDeviceEnumerationComplete(DeviceWatcher^ /* sender */, Object^ /* args */)
  {
      MainPage::Current->Dispatcher->RunAsync(
          CoreDispatcherPriority::Normal,
          ref new DispatchedHandler([this]()
      {
          isAllDevicesEnumerated = true;
  
          // If we finished enumerating devices and the device has not been connected yet, the OnDeviceConnected method
          // is responsible for selecting the device in the device list (UI); otherwise, this method does that
          if (EventHandlerForDevice::Current->IsDeviceConnected)
          {
              SelectDeviceInList(EventHandlerForDevice::Current->DeviceInformation->Id);
  
              ButtonDisconnectFromDevice->Content = ButtonNameDisconnectFromDevice;
  
              MainPage::Current->NotifyUser("Currently connected to: " + EventHandlerForDevice::Current->DeviceInformation->Id, NotifyType::StatusMessage);
          }
          else if (EventHandlerForDevice::Current->IsEnabledAutoReconnect && EventHandlerForDevice::Current->DeviceInformation != nullptr)
          {
              // We will be reconnecting to a device
              ButtonDisconnectFromDevice->Content = ButtonNameDisableReconnectToDevice;
  
              MainPage::Current->NotifyUser("Waiting to reconnect to device: " + EventHandlerForDevice::Current->DeviceInformation->Id, NotifyType::StatusMessage);
          }
          else
          {
              MainPage::Current->NotifyUser("No devices currently selected", NotifyType::StatusMessage);
          }
      }));
  }
  
  /// <summary>
  /// If all the devices have been enumerated, select the device in the list we connected to. Otherwise let the EnumerationComplete event
  /// from the device watcher handle the device selection
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="deviceInformation"></param>
  void DeviceConnect::OnDeviceConnected(EventHandlerForDevice^ /* sender */, DeviceInformation^ /* deviceInformation */)
  {
      // Find and select our connected device
      if (isAllDevicesEnumerated)
      {
          SelectDeviceInList(EventHandlerForDevice::Current->DeviceInformation->Id);
  
          ButtonDisconnectFromDevice->Content = ButtonNameDisconnectFromDevice;
      }
  
      MainPage::Current->NotifyUser("Currently connected to: " + EventHandlerForDevice::Current->DeviceInformation->Id, NotifyType::StatusMessage);
  }
  
  /// <summary>
  /// The device was closed. If we will autoreconnect to the device, reflect that in the UI
  /// </summary>
  /// <param name="sender"></param>
  /// <param name="deviceInformation"></param>
  void DeviceConnect::OnDeviceClosing(EventHandlerForDevice^ /* sender */, DeviceInformation^ /* deviceInformation */)
  {
      MainPage::Current->Dispatcher->RunAsync(
          CoreDispatcherPriority::Normal,
          ref new DispatchedHandler([this]()
      {
          // We were connected to the device that was unplugged, so change the "Disconnect from device" button
          // to "Do not reconnect to device"
          if (ButtonDisconnectFromDevice->IsEnabled && EventHandlerForDevice::Current->IsEnabledAutoReconnect)
          {
              ButtonDisconnectFromDevice->Content = ButtonNameDisableReconnectToDevice;
          }
      }));
  }
  
  /// <summary>
  /// Selects the item in the UI's listbox that corresponds to the provided device id. If there are no
  /// matches, we will deselect anything that is selected.
  /// </summary>
  /// <param name="deviceIdToSelect">The device id of the device to select on the list box</param>
  void DeviceConnect::SelectDeviceInList(String^ deviceIdToSelect)
  {
      // Don't select anything by default.
      ConnectDevices->SelectedIndex = -1;
  
      for (uint32 deviceListIndex = 0; deviceListIndex < listOfDevices->Size; deviceListIndex++)
      {
          if (listOfDevices->GetAt(deviceListIndex)->DeviceInformation->Id == deviceIdToSelect)
          {
              ConnectDevices->SelectedIndex = deviceListIndex;
  
              break;
          }
      }
  }
  
  
  /// <summary>
  /// When ButtonConnectToDevice is disabled, ConnectDevices list will also be disabled.
  /// </summary>
  /// <param name="enableConnectButton">The state of ButtonConnectToDevice</param>
  void DeviceConnect::UpdateConnectDisconnectButtonsAndList(bool enableConnectButton)
  {
      ButtonConnectToDevice->IsEnabled = enableConnectButton;
      ButtonDisconnectFromDevice->IsEnabled = !ButtonConnectToDevice->IsEnabled;
  
      ConnectDevices->IsEnabled = ButtonConnectToDevice->IsEnabled;
  }
  
  