53
53
import androidx .lifecycle .LifecycleObserver ;
54
54
import androidx .lifecycle .LifecycleOwner ;
55
55
import androidx .lifecycle .OnLifecycleEvent ;
56
+ import de .cotech .hw .internal .HwSentry ;
56
57
import de .cotech .hw .internal .dispatch .UsbIntentDispatchActivity ;
57
58
import de .cotech .hw .internal .transport .Transport ;
58
59
import de .cotech .hw .internal .transport .nfc .NfcConnectionDispatcher ;
73
74
* Once initialized, this class will dispatch newly connected security keys to all currently registered listeners.
74
75
* Listeners can be registered with {@link #registerCallback}.
75
76
* <p>
76
- * <pre>{@code
77
+ * <pre>
77
78
* public void onCreate() {
78
79
* super.onCreate();
79
80
* SecurityKeyManager securityKeyManager = SecurityKeyManager.getInstance();
80
81
* securityKeyManager.init(this);
81
82
* }
82
- * } </pre>
83
+ * </pre>
83
84
* <p>
84
85
* A callback is registered together with a {@link SecurityKeyConnectionMode}, which establishes a
85
- * connection to a particular type of Security Token, such as FIDO, PIV, or OpenPGP. Implementations
86
- * for different SecurityKeyConnectionModes are shipped as modules, such as :de.cotech:hwsecurity-fido: ,
87
- * :de.cotech: hwsecurity-piv: , and :de.cotech: hwsecurity-openpgp:. Apps will typically use only a
88
- * single type of Security Key.
86
+ * connection to a particular type of Security Token, such as FIDO2, FIDO, PIV, or OpenPGP.
87
+ * Implementations for different SecurityKeyConnectionModes are shipped as artifacts ,
88
+ * such as hwsecurity-fido2, hwsecurity-fido, hwsecurity- piv, and hwsecurity-openpgp.
89
+ * Apps will typically use only a single type of Security Key.
89
90
* <p>
90
91
* To receive callbacks in an Activity, register for a callback bound to the Activity's lifecycle:
91
92
* <p>
92
- * <pre>{@code
93
+ * <pre>
93
94
* public void onCreate() {
94
95
* super.onResume();
95
96
* FidoSecurityKeyConnectionMode connectionMode = new FidoSecurityKeyConnectionMode();
98
99
* public void onSecurityKeyDiscovered(FidoSecurityKey securityKey) {
99
100
* // perform operations on FidoSecurityKey
100
101
* }
101
- * } </pre>
102
+ * </pre>
102
103
* <p>
103
104
* Advanced applications that want to work with different applets on the same connected Security Key
104
105
* can do so using {@link de.cotech.hw.raw.RawSecurityKeyConnectionMode}.
@@ -178,6 +179,8 @@ public void init(@NonNull Application application, @NonNull SecurityKeyManagerCo
178
179
HwTimber .plant (loggingTree );
179
180
}
180
181
182
+ HwSentry .initializeIfAvailable (config );
183
+
181
184
if (config .isEnableDebugLogging () && HwTimber .treeCount () == 0 ) {
182
185
HwTimber .plant (new DebugTree () {
183
186
@ Override
@@ -238,6 +241,8 @@ private class DispatcherActivityLifecycleCallbacks implements ActivityLifecycleC
238
241
private Activity boundActivity ;
239
242
private UsbConnectionDispatcher activeUsbDispatcher ;
240
243
private NfcConnectionDispatcher activeNfcDispatcher ;
244
+ private boolean isResumed ;
245
+ private boolean isActive ;
241
246
242
247
private void bindToActivity (Activity activity ) {
243
248
if (isUsbDispatchActivity (activity )) {
@@ -274,31 +279,60 @@ private void unbindFromActivity(Activity activity) {
274
279
boundActivity = null ;
275
280
}
276
281
277
- @ Override
278
- public void onActivityResumed (Activity activity ) {
279
- bindToActivity (activity );
282
+ private void refreshActiveState () {
283
+ if (boundActivity == null ) {
284
+ return ;
285
+ }
286
+ boolean isActive = isResumed && (!config .isDisableWhileInactive () || !registeredCallbacks .isEmpty ());
287
+ if (isActive ) {
288
+ ensureStateActive ();
289
+ } else {
290
+ ensureStateInactive ();
291
+ }
292
+ }
293
+
294
+ private void ensureStateActive () {
295
+ if (isActive ) {
296
+ return ;
297
+ }
298
+ isActive = true ;
299
+ HwTimber .d ("Switching hwsecurity state to active" );
280
300
if (activeUsbDispatcher != null ) {
281
- activeUsbDispatcher .onResume ();
301
+ activeUsbDispatcher .onActive ();
282
302
}
283
303
if (activeNfcDispatcher != null ) {
284
- activeNfcDispatcher .onResume ();
304
+ activeNfcDispatcher .onActive ();
285
305
}
286
306
postTriggerCallbacksActively ();
287
307
}
288
308
289
- @ Override
290
- public void onActivityPaused (Activity activity ) {
291
- if (boundActivity != activity ) {
309
+ private void ensureStateInactive () {
310
+ if (!isActive ) {
292
311
return ;
293
312
}
313
+ isActive = false ;
314
+ HwTimber .d ("Switching hwsecurity state to inactive" );
294
315
if (activeUsbDispatcher != null ) {
295
- activeUsbDispatcher .onPause ();
316
+ activeUsbDispatcher .onInactive ();
296
317
}
297
318
if (activeNfcDispatcher != null ) {
298
- activeNfcDispatcher .onPause ();
319
+ activeNfcDispatcher .onInactive ();
299
320
}
300
321
}
301
322
323
+ @ Override
324
+ public void onActivityResumed (Activity activity ) {
325
+ bindToActivity (activity );
326
+ isResumed = true ;
327
+ refreshActiveState ();
328
+ }
329
+
330
+ @ Override
331
+ public void onActivityPaused (Activity activity ) {
332
+ isResumed = false ;
333
+ refreshActiveState ();
334
+ }
335
+
302
336
@ Override
303
337
public void onActivityDestroyed (Activity activity ) {
304
338
unbindFromActivity (activity );
@@ -424,6 +458,7 @@ public <T extends SecurityKey> void registerCallback(SecurityKeyConnectionMode<T
424
458
RegisteredConnectionMode <T > registeredConnectionMode = new RegisteredConnectionMode <>(mode , callback , false );
425
459
lifecycleOwner .getLifecycle ().addObserver (registeredConnectionMode );
426
460
registeredCallbacks .add (0 , registeredConnectionMode );
461
+ activityLifecycleCallbacks .refreshActiveState ();
427
462
428
463
postTriggerCallbacksActively ();
429
464
}
@@ -439,6 +474,7 @@ public <T extends SecurityKey> void registerCallbackForever(SecurityKeyConnectio
439
474
throw new IllegalStateException ("SecurityKeyManager must be initialized in your Application class!" );
440
475
}
441
476
registeredCallbacks .add (0 , new RegisteredConnectionMode <>(mode , callback , true ));
477
+ activityLifecycleCallbacks .refreshActiveState ();
442
478
}
443
479
444
480
@ SuppressWarnings ("WeakerAccess" ) // public API
@@ -463,6 +499,24 @@ public void rediscoverConnectedSecurityKeys() {
463
499
postTriggerCallbacksActively ();
464
500
}
465
501
502
+ /**
503
+ * This method clears and releases all connected security keys.
504
+ *
505
+ * Calling this method will clear the managed state of all persistently connected Security Keys.
506
+ * This operation should not be called during regular operation, since all managed devices (NFC
507
+ * and USB) are automatically cleaned up when they are disconnected. However, it may still be
508
+ * useful to clear managed state in order to rediscover a connected Security Key with a different
509
+ * SecurityKeyConnectionMode.
510
+ *
511
+ * This method is not part of the public API.
512
+ */
513
+ @ AnyThread
514
+ @ RestrictTo (Scope .LIBRARY_GROUP )
515
+ public void clearConnectedSecurityKeys () {
516
+ nfcTagManager .clearManagedNfcTags ();
517
+ usbDeviceManager .clearManagedUsbDevices ();
518
+ }
519
+
466
520
/**
467
521
* Returns true if USB host mode is available.
468
522
*
@@ -549,11 +603,18 @@ private void maybeDeliverPostponedTransport() {
549
603
550
604
@ UiThread
551
605
boolean maybeRedeliverSecurityKey (SecurityKey securityKeyCandidate ) {
552
- if (!isBoundForever && isActive && postponedTransport == null &&
553
- connectionMode .isRelevantSecurityKey (securityKeyCandidate )) {
554
- // noinspection unchecked, this is checked with isRelevantSecurityKey
555
- deliverDiscover ((T ) securityKeyCandidate );
556
- return true ;
606
+ if (!isBoundForever && isActive && postponedTransport == null ) {
607
+ if (connectionMode .isRelevantSecurityKey (securityKeyCandidate )) {
608
+ // noinspection unchecked, this is checked with isRelevantSecurityKey
609
+ deliverDiscover ((T ) securityKeyCandidate );
610
+ return true ;
611
+ } else {
612
+ HwTimber .d (
613
+ "Connected Security Key of type %s doesn't match SecurityKeyConnectionMode %s" ,
614
+ securityKeyCandidate .getClass ().getSimpleName (),
615
+ connectionMode .getClass ().getSimpleName ());
616
+ return false ;
617
+ }
557
618
}
558
619
return false ;
559
620
}
@@ -566,6 +627,9 @@ private boolean attemptConnectWithRegisteredSecurityMode(Transport transport) {
566
627
if (securityKey == null ) {
567
628
return false ;
568
629
}
630
+ HwSentry .addBreadcrumb ("Connected security key of type %s on transport %s" ,
631
+ securityKey .getClass ().getSimpleName (),
632
+ securityKey .transport .getTransportType ());
569
633
} catch (IOException e ) {
570
634
callbackHandlerMain .post (() -> {
571
635
if (!isActive ) {
@@ -602,6 +666,12 @@ private void deliverDiscover(T securityKey) {
602
666
603
667
@ AnyThread
604
668
private void handleTransportRelease (T securityKey ) {
669
+ HwSentry .addBreadcrumb ("Released security key of type %s on transport %s" ,
670
+ securityKey .getClass ().getSimpleName (),
671
+ securityKey .transport .getTransportType ());
672
+ // Clear tags that may have been added by us while this Security Key was active
673
+ connectionMode .clearSentryTags ();
674
+
605
675
persistentSecurityKeys .remove (securityKey );
606
676
607
677
boolean isNfcTransport = securityKey .transport instanceof NfcTransport ;
@@ -646,6 +716,10 @@ void onDestroy() {
646
716
connectionMode .getClass ().getSimpleName (), callback .getClass ().getSimpleName ());
647
717
registeredCallbacks .remove (this );
648
718
postponedTransport = null ;
719
+ if (persistentSecurityKeys .isEmpty ()) {
720
+ connectionMode .clearSentryTags ();
721
+ }
722
+ activityLifecycleCallbacks .refreshActiveState ();
649
723
}
650
724
}
651
725
}
0 commit comments