@@ -39,6 +39,13 @@ import { WindowType } from '../rpc/enums/WindowType.js';
39
39
import { WindowTypeCapabilities } from '../rpc/structs/WindowTypeCapabilities.js' ;
40
40
import { DisplayCapability } from '../rpc/structs/DisplayCapability.js' ;
41
41
import { DisplayCapabilities } from '../rpc/structs/DisplayCapabilities.js' ;
42
+ import { TextField } from '../rpc/structs/TextField.js' ;
43
+ import { ImageField } from '../rpc/structs/ImageField.js' ;
44
+ import { SoftButtonCapabilities } from '../rpc/structs/SoftButtonCapabilities.js' ;
45
+ import { ButtonCapabilities } from '../rpc/structs/ButtonCapabilities.js' ;
46
+ import { DynamicUpdateCapabilities } from '../rpc/structs/DynamicUpdateCapabilities.js' ;
47
+ import { KeyboardCapabilities } from '../rpc/structs/KeyboardCapabilities.js' ;
48
+ import { ImageResolution } from '../rpc/structs/ImageResolution.js' ;
42
49
import { ImageType } from '../rpc/enums/ImageType.js' ;
43
50
import { MessageType } from '../rpc/enums/MessageType.js' ;
44
51
import { DisplayType } from '../rpc/enums/DisplayType.js' ;
@@ -68,6 +75,8 @@ class SystemCapabilityManager extends _SubManagerBase {
68
75
this . _seatLocationCapability = null ;
69
76
this . _displays = null ;
70
77
this . _driverDistractionCapability = null ;
78
+ this . _systemCapabilitiesSubscriptionStatus = new Set ( ) ; // Set<SystemCapabilityType>
79
+ this . _systemCapabilitiesSubscriptionStatus . add ( SystemCapabilityType . DISPLAYS ) ; // subscribe to displays by default
71
80
72
81
// capabilities not in the spec
73
82
this . _hmiCapabilities = null ;
@@ -196,6 +205,37 @@ class SystemCapabilityManager extends _SubManagerBase {
196
205
if ( currentWindowId === windowId ) {
197
206
// Clone WindowCapability to prevent modification of stored WindowCapability in SystemCapabilityManager
198
207
const windowCapabilityCopy = new WindowCapability ( JSON . parse ( JSON . stringify ( windowCapability . getParameters ( ) ) ) ) ;
208
+ if ( windowCapability . getTextFields ( ) !== null && windowCapability . getTextFields ( ) !== undefined ) {
209
+ windowCapabilityCopy . setTextFields ( windowCapability . getTextFields ( ) . map ( textField => {
210
+ return new TextField ( JSON . parse ( JSON . stringify ( textField . getParameters ( ) ) ) ) ;
211
+ } ) ) ;
212
+ }
213
+ if ( windowCapability . getImageFields ( ) !== null && windowCapability . getImageFields ( ) !== undefined ) {
214
+ windowCapabilityCopy . setImageFields ( windowCapability . getImageFields ( ) . map ( imageField => {
215
+ return new ImageField ( JSON . parse ( JSON . stringify ( imageField . getParameters ( ) ) ) )
216
+ . setImageResolution ( new ImageResolution ( JSON . parse ( JSON . stringify ( imageField . getImageResolution ( ) . getParameters ( ) ) ) ) ) ;
217
+ } ) ) ;
218
+ }
219
+ if ( windowCapability . getSoftButtonCapabilities ( ) !== null && windowCapability . getSoftButtonCapabilities ( ) !== undefined ) {
220
+ windowCapabilityCopy . setSoftButtonCapabilities ( windowCapability . getSoftButtonCapabilities ( ) . map ( capability => {
221
+ return new SoftButtonCapabilities ( JSON . parse ( JSON . stringify ( capability . getParameters ( ) ) ) ) ;
222
+ } ) ) ;
223
+ }
224
+ if ( windowCapability . getButtonCapabilities ( ) !== null && windowCapability . getButtonCapabilities ( ) !== undefined ) {
225
+ windowCapabilityCopy . setButtonCapabilities ( windowCapability . getButtonCapabilities ( ) . map ( capability => {
226
+ return new ButtonCapabilities ( JSON . parse ( JSON . stringify ( capability . getParameters ( ) ) ) ) ;
227
+ } ) ) ;
228
+ }
229
+ if ( windowCapability . getDynamicUpdateCapabilities ( ) !== null && windowCapability . getDynamicUpdateCapabilities ( ) !== undefined ) {
230
+ windowCapabilityCopy . setDynamicUpdateCapabilities ( new DynamicUpdateCapabilities (
231
+ JSON . parse ( JSON . stringify ( windowCapability . getDynamicUpdateCapabilities ( ) . getParameters ( ) ) )
232
+ ) ) ;
233
+ }
234
+ if ( windowCapability . getKeyboardCapabilities ( ) !== null && windowCapability . getKeyboardCapabilities ( ) !== undefined ) {
235
+ windowCapabilityCopy . setKeyboardCapabilities ( new KeyboardCapabilities (
236
+ JSON . parse ( JSON . stringify ( windowCapability . getKeyboardCapabilities ( ) . getParameters ( ) ) )
237
+ ) ) ;
238
+ }
199
239
// A null windowID is assumed to be the DefaultWindow according to the spec, but that can be hard for developers to check, so set it explicitly.
200
240
windowCapabilityCopy . setWindowID ( windowId ) ;
201
241
return windowCapabilityCopy ;
@@ -258,6 +298,15 @@ class SystemCapabilityManager extends _SubManagerBase {
258
298
return [ displayCapability ] ;
259
299
}
260
300
301
+ // Ford Sync bug returning incorrect template name for "NON-MEDIA" https://github.com/smartdevicelink/sdl_javascript_suite/issues/450
302
+ const templatesAvailable = displayCapabilities . getTemplatesAvailable ( ) !== null ? displayCapabilities . getTemplatesAvailable ( ) : [ ] ;
303
+ for ( let index = 0 ; index < templatesAvailable . length ; index ++ ) {
304
+ if ( templatesAvailable [ index ] === 'NON_MEDIA' ) {
305
+ templatesAvailable [ index ] = 'NON-MEDIA' ;
306
+ break ;
307
+ }
308
+ }
309
+
261
310
// copy all available display capabilities
262
311
defaultWindowCapability . setTemplatesAvailable ( displayCapabilities . getTemplatesAvailable ( ) )
263
312
. setNumCustomPresetsAvailable ( displayCapabilities . getNumCustomPresetsAvailable ( ) )
@@ -498,15 +547,66 @@ class SystemCapabilityManager extends _SubManagerBase {
498
547
* @returns {Promise } - Promise returning either the capability Object or null if not found
499
548
*/
500
549
async updateCapability ( systemCapabilityType ) {
550
+ return await this . _updateCapabilityPrivate ( systemCapabilityType , null , true ) ;
551
+ }
552
+
553
+ /**
554
+ * Checks if the supplied capability type is currently subscribed for or not
555
+ * @param {SystemCapabilityType } systemCapabilityType - type of capability desired
556
+ * @returns {Boolean } - true if subscribed and false if not
557
+ */
558
+ _isSubscribedToSystemCapability ( systemCapabilityType ) {
559
+ return this . _systemCapabilitiesSubscriptionStatus . has ( systemCapabilityType ) ;
560
+ }
561
+
562
+ /**
563
+ * Sends a request to core for the capability, instead of checking cached capabilities
564
+ * @param {SystemCapabilityType } systemCapabilityType - A SystemCapabilityType enum value.
565
+ * @param {Boolean } subscribe - Whether to attempt to subscribe to a capability. Null means no change to the subscription status. Defaults to null
566
+ * @param {Boolean } forceUpdate - Whether to force a query to the head unit to retrieve a capability anyway
567
+ * @param {Function } listener - A newly subscribed listener that may want to receive the updated capability object. Defaults to null
568
+ * @returns {Promise } - Promise returning either the capability Object or null if not found
569
+ */
570
+ async _updateCapabilityPrivate ( systemCapabilityType , subscribe = null , forceUpdate , listener = null ) {
571
+ const cachedCapability = this . getCapability ( systemCapabilityType ) ;
572
+
573
+ // NOTE: subscription has always been supported within the lifespan of this library (5.2.0+)
574
+ const shouldForceUpdate = forceUpdate && ! this . _isSubscribedToSystemCapability ( systemCapabilityType ) ;
575
+ const shouldUpdateSystemCapabilitySubscription = ( subscribe !== null ) && ( subscribe !== this . _isSubscribedToSystemCapability ( systemCapabilityType ) ) ;
576
+ const shouldSendGetCapabilityRequest = shouldForceUpdate || ( cachedCapability === null ) || shouldUpdateSystemCapabilitySubscription ;
577
+ const shouldCallListenerWithCachedValue = ( cachedCapability !== null ) && ( listener !== null ) && ! shouldSendGetCapabilityRequest ;
578
+
579
+ if ( shouldCallListenerWithCachedValue ) {
580
+ listener ( cachedCapability ) ;
581
+ }
582
+
583
+ if ( ! shouldSendGetCapabilityRequest ) {
584
+ return cachedCapability ;
585
+ }
586
+
587
+ // non-cached capability get
588
+
501
589
// don't bother getting a capability if it isn't queryable
502
590
const getCapabilityMethodName = this . _getCapabilityMethodForType ( systemCapabilityType ) ;
503
591
if ( getCapabilityMethodName === null ) {
504
592
console . error ( `The systemCapabilityType ${ systemCapabilityType } cannot be queried for` ) ;
505
593
return null ;
506
594
}
507
595
596
+ /*
597
+ The subscription flag in the request should be set based on multiple variables:
598
+ - if subscribe is null (no change), willSubscribe = current subscription status, or false if the HU does not support subscriptions
599
+ - if subscribe is false, then willSubscribe = false
600
+ - if subscribe is true and the HU supports subscriptions, then willSubscribe = true
601
+ NOTE: subscription has always been supported within the lifespan of this library (5.2.0+)
602
+ */
603
+ const shouldSubscribe = ( subscribe !== null ) ? subscribe : this . _isSubscribedToSystemCapability ( systemCapabilityType ) ;
604
+
508
605
const request = new GetSystemCapability ( )
509
606
. setSystemCapabilityType ( systemCapabilityType ) ;
607
+ if ( subscribe !== null ) {
608
+ request . setSubscribe ( shouldSubscribe ) ;
609
+ }
510
610
511
611
if ( this . _lifecycleManager === null ) {
512
612
return null ;
@@ -526,11 +626,18 @@ class SystemCapabilityManager extends _SubManagerBase {
526
626
return null ;
527
627
}
528
628
629
+ // RPC get succeeded
630
+ if ( shouldSubscribe ) {
631
+ this . _systemCapabilitiesSubscriptionStatus . add ( systemCapabilityType ) ;
632
+ } else {
633
+ this . _systemCapabilitiesSubscriptionStatus . delete ( systemCapabilityType ) ;
634
+ }
635
+
529
636
// invoke the correct get capability method
530
637
const retrievedCapability = response . getSystemCapability ( ) [ getCapabilityMethodName ] ( ) ;
531
638
this . _setCapability ( systemCapabilityType , retrievedCapability ) ;
532
639
// get the capability back through this method, because it may have changed the output
533
- return this . getCapability ( systemCapabilityType , retrievedCapability ) ;
640
+ return this . getCapability ( systemCapabilityType ) ;
534
641
}
535
642
536
643
/**
@@ -547,7 +654,7 @@ class SystemCapabilityManager extends _SubManagerBase {
547
654
this . _onSystemCapabilityListeners [ systemCapabilityType ] = [ ] ;
548
655
}
549
656
this . _onSystemCapabilityListeners [ systemCapabilityType ] . push ( listener ) ;
550
- this . updateCapability ( systemCapabilityType ) ; // send out a query, since the caller wants an update for this capability
657
+ this . _updateCapabilityPrivate ( systemCapabilityType , true , false , listener ) ; // send out a query, since the caller wants an update for this capability
551
658
}
552
659
553
660
/**
@@ -573,6 +680,11 @@ class SystemCapabilityManager extends _SubManagerBase {
573
680
}
574
681
return listener !== listenerCheck ;
575
682
} ) ;
683
+ // If the last listener for the supplied capability type is removed, unsubscribe from the capability type
684
+ // always stay subscribied to DISPLAYS
685
+ if ( removed && this . _onSystemCapabilityListeners [ systemCapabilityType ] . length === 0 && this . _isSubscribedToSystemCapability ( systemCapabilityType ) && systemCapabilityType !== SystemCapabilityType . DISPLAYS ) {
686
+ this . _updateCapabilityPrivate ( systemCapabilityType , false , false ) ;
687
+ }
576
688
return removed ;
577
689
}
578
690
@@ -618,7 +730,18 @@ class SystemCapabilityManager extends _SubManagerBase {
618
730
if ( this . getCapability ( systemCapabilityType ) !== null ) { // The capability already exists
619
731
switch ( systemCapabilityType ) {
620
732
case SystemCapabilityType . APP_SERVICES : {
621
- // setCapability handles updating the local app service state
733
+ // App services only updates what was changed so we need
734
+ // to update the capability rather than override it
735
+ const appServicesCapabilities = capability ;
736
+ if ( capability !== null ) {
737
+ const appServicesCapabilitiesList = appServicesCapabilities . getAppServices ( ) ;
738
+ const cachedAppServicesCapabilities = this . _appServicesCapabilities ;
739
+ // Update the cached app services
740
+ if ( cachedAppServicesCapabilities !== null ) {
741
+ capability . setAppServices ( this . _updateAppServices ( cachedAppServicesCapabilities , appServicesCapabilitiesList ) ) ;
742
+ }
743
+ // Set the new capability object to the updated cached capabilities
744
+ }
622
745
break ;
623
746
}
624
747
case SystemCapabilityType . DISPLAYS : {
@@ -739,15 +862,7 @@ class SystemCapabilityManager extends _SubManagerBase {
739
862
convertedCapabilities . setImageFields ( defaultMainWindow . getImageFields ( ) ) ;
740
863
}
741
864
742
- // Ford Sync bug returning incorrect template name for "NON-MEDIA" https://github.com/smartdevicelink/sdl_javascript_suite/issues/450
743
- const templatesAvailable = defaultMainWindow . getTemplatesAvailable ( ) !== null ? defaultMainWindow . getTemplatesAvailable ( ) : [ ] ;
744
- for ( let index = 0 ; index < templatesAvailable . length ; index ++ ) {
745
- if ( templatesAvailable [ index ] === 'NON_MEDIA' ) {
746
- templatesAvailable [ index ] = 'NON-MEDIA' ;
747
- break ;
748
- }
749
- }
750
- convertedCapabilities . setTemplatesAvailable ( templatesAvailable ) ;
865
+ convertedCapabilities . setTemplatesAvailable ( defaultMainWindow . getTemplatesAvailable ( ) ) ;
751
866
convertedCapabilities . setNumCustomPresetsAvailable ( defaultMainWindow . getNumCustomPresetsAvailable ( ) ) ;
752
867
convertedCapabilities . setMediaClockFormats ( [ ] ) ; // mandatory field but allows empty array
753
868
// if there are imageTypes in the response, we must assume graphics are supported
@@ -767,6 +882,79 @@ class SystemCapabilityManager extends _SubManagerBase {
767
882
768
883
return convertedCapabilities ;
769
884
}
885
+
886
+ /**
887
+ * This method will update the current List<AppServiceCapability> with the updated items. If the
888
+ * items don't exist in the original ist they will be added. If the original list is null or
889
+ * empty, the new list will simply be set as the list.
890
+ *
891
+ * @param {AppServicesCapabilities } appServicesCapabilities - the AppServicesCapabilities instance
892
+ * @param {AppServiceCapability[] } updatedAppServiceCapabilities - the array that have been updated
893
+ * @returns {AppServiceCapability[] } the array of capabilities with old duplicate services removed
894
+ */
895
+ _updateAppServices ( appServicesCapabilities , updatedAppServiceCapabilities ) {
896
+ if ( updatedAppServiceCapabilities === null ) {
897
+ return false ;
898
+ }
899
+
900
+ let appServiceCapabilities = appServicesCapabilities ;
901
+
902
+ if ( appServiceCapabilities === null ) {
903
+ // If there are currently no app services, create one to iterate over with no entries
904
+ appServiceCapabilities = new Map ( ) ;
905
+ }
906
+
907
+ // Create a shallow copy for us to alter while iterating through the original list
908
+ const tempList = new Map ( appServiceCapabilities ) ;
909
+
910
+ for ( const updatedAppServiceCapability of updatedAppServiceCapabilities ) {
911
+ if ( updatedAppServiceCapability !== null ) {
912
+ // First search if the record exists in the current list and remove it if so
913
+ for ( const appServiceCapability of appServiceCapabilities . values ( ) ) {
914
+ if ( this . _matchesAppService ( updatedAppServiceCapability , appServiceCapability ) ) {
915
+ tempList . delete ( appServiceCapability . getUpdatedAppServiceRecord ( ) . getServiceID ( ) ) ; // Remove the old entry
916
+ break ;
917
+ }
918
+ }
919
+
920
+ // Add the app service regardless of whether it was removed or not, the setCapability method will handle removals
921
+ tempList . set ( updatedAppServiceCapability . getUpdatedAppServiceRecord ( ) . getServiceID ( ) , updatedAppServiceCapability ) ;
922
+ }
923
+ }
924
+
925
+ return Array . from ( tempList . values ( ) ) ;
926
+ }
927
+
928
+ /**
929
+ * Helper method to compare AppServiceCapability instances
930
+ * @param {AppServiceCapability } capability - an AppServiceCapability
931
+ * @param {AppServiceCapability } capability2 - the AppServiceCapability to compare to the first
932
+ * @returns {Boolean } if both AppServiceCapability objects refer to the same service
933
+ */
934
+ _matchesAppService ( capability = null , capability2 = null ) {
935
+ if ( capability !== null && capability2 !== null ) {
936
+ const appServiceRecord = capability . getUpdatedAppServiceRecord ( ) ;
937
+ const otherASR = capability2 . getUpdatedAppServiceRecord ( ) ;
938
+
939
+ if ( appServiceRecord !== null && otherASR !== null ) {
940
+ // If both service IDs exists we can compare them. If either is null we can't use
941
+ // only this check.
942
+ if ( appServiceRecord . getServiceID ( ) !== null && otherASR . getServiceID ( ) !== null ) {
943
+ // return whether the app service IDs are equal or not
944
+ return appServiceRecord . getServiceID ( ) . toLowerCase ( ) === otherASR . getServiceID ( ) . toLowerCase ( ) ;
945
+ } else {
946
+ const manifest = appServiceRecord . getServiceManifest ( ) ;
947
+ const otherManifest = otherASR . getServiceManifest ( ) ;
948
+ if ( manifest !== null && otherManifest !== null ) {
949
+ // Check the service names, if they are the same it can be assumed they are the same service
950
+ return ( manifest . getServiceName ( ) !== null && otherManifest . getServiceName ( ) !== null
951
+ && manifest . getServiceName ( ) . toLowerCase ( ) === otherManifest . getServiceName ( ) . toLowerCase ( ) ) ;
952
+ }
953
+ }
954
+ }
955
+ }
956
+ return false ;
957
+ }
770
958
}
771
959
772
960
export { SystemCapabilityManager } ;
0 commit comments