-
Notifications
You must be signed in to change notification settings - Fork 14
/
Copy pathEventHandlerForDevice.cpp
473 lines (407 loc) · 16.6 KB
/
EventHandlerForDevice.cpp
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
#include "pch.h"
#include "EventHandlerForDevice.h"
#include "App.xaml.h"
using namespace VBA10;
using namespace Platform;
using namespace Concurrency;
using namespace Windows::ApplicationModel;
using namespace Windows::Devices;
using namespace Windows::Devices::Enumeration;
using namespace Windows::Devices::HumanInterfaceDevice;
using namespace Windows::Foundation;
using namespace Windows::Storage;
using namespace Windows::UI::Core;
using namespace Windows::UI::Xaml;
EventHandlerForDevice^ EventHandlerForDevice::eventHandlerForDevice = nullptr;
SRWLOCK EventHandlerForDevice::srwSingletonCreationLock = SRWLOCK_INIT;
/// <summary>
/// Enforces the singleton pattern so that there is only one object handling app events
/// as it relates to the UsbDevice because this sample app only supports communicating with one device at a time.
///
/// An instance of EventHandlerForDevice is globally available because the device needs to persist across scenario pages.
///
/// If there is no instance of EventHandlerForDevice created before this property is called,
/// an EventHandlerForDevice will be created.
/// </summary>
EventHandlerForDevice^ EventHandlerForDevice::Current::get(void)
{
// Acquiring Shared lock is much quicker than an exclusive lock, so avoid exclusive lock unless it's necessary
AcquireSRWLockShared(&srwSingletonCreationLock);
if (eventHandlerForDevice == nullptr)
{
// Multiple threads could have entered here, so acquire exclusive lock to make sure only one instance is created.
ReleaseSRWLockShared(&srwSingletonCreationLock);
AcquireSRWLockExclusive(&srwSingletonCreationLock);
if (eventHandlerForDevice == nullptr)
{
CreateNewEventHandlerForDevice();
}
ReleaseSRWLockExclusive(&srwSingletonCreationLock);
}
else
{
ReleaseSRWLockShared(&srwSingletonCreationLock);
}
return eventHandlerForDevice;
}
/// <summary>
/// Creates a new instance of EventHandlerForDevice, enables auto reconnect, and uses it as the Current instance.
/// </summary>
void EventHandlerForDevice::CreateNewEventHandlerForDevice(void)
{
eventHandlerForDevice = ref new EventHandlerForDevice();
}
/// <summary>
/// This method opens the device using the WinRT Hid API. After the device is opened, we will save the device
/// so that it can be used across scenarios.
///
/// 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.
///
/// This method is used to reopen the device after the device reconnects to the computer and when the app resumes.
/// </summary>
/// <param name="deviceInfo">Device information of the device to be opened</param>
/// <param name="deviceSelector">The AQS used to find this device</param>
/// <returns>True if the device was successfully opened, false if the device could not be opened for well known reasons.
/// An exception may be thrown if the device could not be opened for extraordinary reasons.</returns>
IAsyncOperation<bool>^ EventHandlerForDevice::OpenDeviceAsync(Enumeration::DeviceInformation^ deviceInfo)
{
// This sample uses FileAccessMode.ReadWrite to open the device because we do not want other apps opening our device and
// changing the state of our device. FileAccessMode.Read can be used instead.
return create_async([this, deviceInfo]()
{
String^ test = deviceInfo->Id;
return create_task(HidDevice::FromIdAsync(deviceInfo->Id, FileAccessMode::Read))
.then([this, deviceInfo](task<HidDevice^> deviceTask)
{
bool successfullyOpenedDevice = false;
//NotifyType notificationStatus;
//String^ notificationMessage = nullptr;
// This may throw an exception or return null if we could not open the device
try
{
device = deviceTask.get();
}
catch (Platform::Exception^)
{
device = nullptr;
}
// Device could have been blocked by user or the device has already been opened by another app.
if (device != nullptr)
{
successfullyOpenedDevice = true;
deviceInformation = deviceInfo;
//this->deviceSelector = deviceSelector;
//notificationStatus = NotifyType::StatusMessage;
//notificationMessage = "Device " + deviceInformation->Id + " opened";
if (appSuspendEventToken.Value == 0 || appResumeEventToken.Value == 0)
{
RegisterForAppEvents();
}
// User can block the device after it has been opened in the Settings charm. We can detect this by registering for the
// DeviceAccessInformation->AccessChanged event
if (deviceAccessInformation == nullptr)
{
RegisterForDeviceAccessStatusChange();
}
// Create and register device watcher events for the device to be opened unless we're reopening the device
if (deviceWatcher == nullptr)
{
deviceWatcher = Enumeration::DeviceInformation::CreateWatcher(this->deviceSelector);
RegisterForDeviceWatcherEvents();
}
if (!watcherStarted)
{
// Start the device watcher after we made sure that the device is opened.
StartDeviceWatcher();
}
}
else
{
successfullyOpenedDevice = false;
//notificationStatus = NotifyType::ErrorMessage;
auto deviceAccessStatus = Enumeration::DeviceAccessInformation::CreateFromId(deviceInfo->Id)->CurrentStatus;
if (deviceAccessStatus == DeviceAccessStatus::DeniedByUser)
{
//notificationMessage = "Access to the device was blocked by the user : " + deviceInfo->Id;
}
else if (deviceAccessStatus == DeviceAccessStatus::DeniedBySystem)
{
// This status is most likely caused by app permissions (did not declare the device in the app's package.appxmanifest)
// This status does not cover the case where the device is already opened by another app.
//notificationMessage = "Access to the device was blocked by the system : " + deviceInfo->Id;
}
else
{
// Most likely the device is opened by another app, but cannot be sure
//notificationMessage = "Unknown error, possibly opened by another app : " + deviceInfo->Id;
}
}
//MainPage::Current->NotifyUser(notificationMessage, notificationStatus);
// Notify registered callback handle whether or not the device has been opened
if (deviceConnectedCallback != nullptr)
{
auto deviceConnectedEventArgs = ref new OnDeviceConnectedEventArgs(successfullyOpenedDevice, deviceInfo);
deviceConnectedCallback(this, deviceConnectedEventArgs);
}
return successfullyOpenedDevice;
});
});
}
/// <summary>
/// Closes the device, stops the device watcher, stops listening for app events, and resets object state to before a device
/// was ever connected.
/// </summary>
void EventHandlerForDevice::CloseDevice(void)
{
if (IsDeviceConnected)
{
CloseCurrentlyConnectedDevice();
}
if (deviceWatcher != nullptr)
{
if (watcherStarted)
{
StopDeviceWatcher();
UnregisterFromDeviceWatcherEvents();
}
deviceWatcher = nullptr;
}
if (deviceAccessInformation != nullptr)
{
UnregisterFromDeviceAccessStatusChange();
deviceAccessInformation = nullptr;
}
if (appSuspendEventToken.Value != 0 || appResumeEventToken.Value != 0)
{
UnregisterFromAppEvents();
}
deviceInformation = nullptr;
deviceSelector = nullptr;
deviceConnectedCallback = nullptr;
deviceCloseCallback = nullptr;
isEnabledAutoReconnect = true;
}
EventHandlerForDevice::EventHandlerForDevice(void) :
watcherStarted(false),
watcherSuspended(false),
isEnabledAutoReconnect(true)
{
}
EventHandlerForDevice::~EventHandlerForDevice()
{
if (IsDeviceConnected)
{
CloseDevice();
}
}
/// <summary>
/// This method demonstrates how to close the device properly using the WinRT Hid API.
///
/// When the HidDevice is closing, it will cancel all IO operations that are still pending (not complete).
/// The close will not wait for any IO completion callbacks to be called, so the close call may complete before any of
/// the IO completion callbacks are called.
/// The pending IO operations will still call their respective completion callbacks with either a task
/// cancelled error or the operation completed.
/// </summary>
void EventHandlerForDevice::CloseCurrentlyConnectedDevice(void)
{
if (device != nullptr)
{
if (deviceCloseCallback != nullptr)
{
deviceCloseCallback(this, deviceInformation);
}
// This closes the handle to the device
delete device;
device = nullptr;
// Save the deviceInformation->Id in case deviceInformation is set to null when closing the
// device
String^ deviceId = deviceInformation->Id;
//MainPage::Current->Dispatcher->RunAsync(
// CoreDispatcherPriority::Normal,
// ref new DispatchedHandler([deviceId]()
//{
// MainPage::Current->NotifyUser(deviceId + " is closed", NotifyType::StatusMessage);
//}));
}
}
/// <summary>
/// Register for app suspension/resume events. See the comments
/// for the event handlers for more information on what is being done to the device.
///
/// We will also register for when the app exists so that we may close the device handle.
/// </summary>
void EventHandlerForDevice::RegisterForAppEvents()
{
// This event is raised when the app is exited and when the app is suspended
appSuspendEventToken = App::Current->Suspending += ref new SuspendingEventHandler(this, &EventHandlerForDevice::OnAppSuspension);
appResumeEventToken = App::Current->Resuming += ref new EventHandler<Object^>(this, &EventHandlerForDevice::OnAppResume);
}
void EventHandlerForDevice::UnregisterFromAppEvents()
{
App::Current->Suspending -= appSuspendEventToken;
appSuspendEventToken.Value = 0;
App::Current->Resuming -= appResumeEventToken;
appResumeEventToken.Value = 0;
}
/// <summary>
/// Register for Added and Removed events
/// Note that, when disconnecting the device, the device may be closed by the system before the OnDeviceRemoved callback is invoked.
/// </summary>
void EventHandlerForDevice::RegisterForDeviceWatcherEvents()
{
deviceAddedEventToken = deviceWatcher->Added += ref new TypedEventHandler<DeviceWatcher^, Enumeration::DeviceInformation^>(
this, &EventHandlerForDevice::OnDeviceAdded);
deviceRemovedEventToken = deviceWatcher->Removed += ref new TypedEventHandler<DeviceWatcher^, DeviceInformationUpdate^>(
this, &EventHandlerForDevice::OnDeviceRemoved);
}
void EventHandlerForDevice::UnregisterFromDeviceWatcherEvents()
{
deviceWatcher->Added -= deviceAddedEventToken;
deviceAddedEventToken.Value = 0;
deviceWatcher->Removed -= deviceRemovedEventToken;
deviceRemovedEventToken.Value = 0;
}
/// <summary>
/// Listen for any changed in device access permission. The user can block access to the device while the device is in use.
/// If the user blocks access to the device while the device is opened, the device's handle will be closed automatically by
/// the system; it is still a good idea to close the device explicitly so that resources are cleaned up.
///
/// Note that by the time the AccessChanged event is raised, the device handle may already be closed by the system.
/// </summary>
void EventHandlerForDevice::RegisterForDeviceAccessStatusChange()
{
deviceAccessInformation = Enumeration::DeviceAccessInformation::CreateFromId(deviceInformation->Id);
deviceAccessChangedEventToken = deviceAccessInformation->AccessChanged
+= ref new TypedEventHandler<Enumeration::DeviceAccessInformation^, DeviceAccessChangedEventArgs^>(this, &EventHandlerForDevice::OnDeviceAccessChanged);
}
void EventHandlerForDevice::UnregisterFromDeviceAccessStatusChange()
{
deviceAccessInformation->AccessChanged -= deviceAccessChangedEventToken;
deviceAccessChangedEventToken.Value = 0;
}
void EventHandlerForDevice::StartDeviceWatcher(void)
{
watcherStarted = true;
if ((deviceWatcher->Status != DeviceWatcherStatus::Started)
&& (deviceWatcher->Status != DeviceWatcherStatus::EnumerationCompleted))
{
deviceWatcher->Start();
}
}
void EventHandlerForDevice::StopDeviceWatcher(void)
{
if ((deviceWatcher->Status == DeviceWatcherStatus::Started)
|| (deviceWatcher->Status == DeviceWatcherStatus::EnumerationCompleted))
{
deviceWatcher->Stop();
}
watcherStarted = false;
}
/// <summary>
/// If a HidDevice object has been instantiated (a handle to the device is opened), we must close it before the app
/// goes into suspension because the API automatically closes it for us if we don't. When resuming, the API will
/// not reopen the device automatically, so we need to explicitly open the device in the app (Scenario1_DeviceConnect).
///
/// Since we have to reopen the device ourselves when the app resumes, it is good practice to explicitly call the close
/// in the app as well (For every open there is a close).
///
/// We must stop the DeviceWatcher because it 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 EventHandlerForDevice::OnAppSuspension(Object^ /* sender */, SuspendingEventArgs^ /* eventArgs */)
{
if (watcherStarted)
{
watcherSuspended = true;
StopDeviceWatcher();
}
else
{
watcherSuspended = false;
}
CloseCurrentlyConnectedDevice();
}
/// <summary>
/// When resume into the application, we should reopen a handle to the Usb device again. This will automatically
/// happen when we start the device watcher again; the device will be re-enumerated and we will attempt to reopen it
/// if IsEnabledAutoReconnect property is enabled.
///
/// See OnAppSuspension for why we are starting the device watcher again
/// </summary>
/// <param name="sender"></param>
/// <param name="args"></param>
void EventHandlerForDevice::OnAppResume(Object^ /* sender */, Object^ /* args */)
{
if (watcherSuspended)
{
watcherSuspended = false;
StartDeviceWatcher();
}
}
/// <summary>
/// Close the device that is opened so that all pending operations are canceled properly.
/// </summary>
/// <param name="sender"></param>
/// <param name="deviceInformationUpdate"></param>
void EventHandlerForDevice::OnDeviceRemoved(DeviceWatcher^ /* sender */, DeviceInformationUpdate^ deviceInformationUpdate)
{
if (IsDeviceConnected && (deviceInformationUpdate->Id == deviceInformation->Id))
{
// The main reasons to close the device explicitly is to clean up resources, to properly handle errors,
// and stop talking to the disconnected device.
CloseCurrentlyConnectedDevice();
}
}
/// <summary>
/// Open the device that the user wanted to open if it hasn't been opened yet and auto reconnect is enabled.
/// </summary>
/// <param name="sender"></param>
/// <param name="deviceInfo"></param>
void EventHandlerForDevice::OnDeviceAdded(DeviceWatcher^ /* sender */, Enumeration::DeviceInformation^ deviceInfo)
{
if ((deviceInformation != nullptr) && (deviceInfo->Id == deviceInformation->Id) && !IsDeviceConnected && isEnabledAutoReconnect)
{
//MainPage::Current->Dispatcher->RunAsync(
// CoreDispatcherPriority::Normal,
// ref new DispatchedHandler([this]()
//{
// If we failed to reconnect to the device, don't try to connect anymore
create_task(OpenDeviceAsync(deviceInformation)).then([this](task<bool> openDeviceSuccess)
{
isEnabledAutoReconnect = openDeviceSuccess.get();
});
// Any app specific device intialization should be done here because we don't know the state of the device when it is re-enumerated.
//}));
}
}
/// <summary>
/// Close the device if the device access was denied by anyone (system or the user) and reopen it if permissions are allowed again
/// </summary>
/// <param name="sender"></param>
/// <param name="eventArgs"></param>
void EventHandlerForDevice::OnDeviceAccessChanged(Enumeration::DeviceAccessInformation^ sender, DeviceAccessChangedEventArgs^ eventArgs)
{
if ((eventArgs->Status == DeviceAccessStatus::DeniedBySystem)
|| (eventArgs->Status == DeviceAccessStatus::DeniedByUser))
{
CloseCurrentlyConnectedDevice();
}
else if ((eventArgs->Status == DeviceAccessStatus::Allowed) && (deviceInformation != nullptr) && isEnabledAutoReconnect)
{
//MainPage::Current->Dispatcher->RunAsync(
// CoreDispatcherPriority::Normal,
// ref new DispatchedHandler([this]()
//{
// If we failed to reconnect to the device, don't try to connect anymore
create_task(OpenDeviceAsync(deviceInformation)).then([this](task<bool> openDeviceSuccess)
{
isEnabledAutoReconnect = openDeviceSuccess.get();
});
// Any app specific device intialization should be done here because we don't know the state of the device when it is re-enumerated.
//}));
}
}