diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2019-05-29 09:19:36 -0400 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-05-29 09:19:36 -0400 |
commit | 70deeceb07e60d9f5c911ea30bc335eb130aaa71 (patch) | |
tree | 8966f2b138176b3b2b7ee73fe6457c38c958c7b8 | |
parent | 7f0f28f94b023f7107ee896cf0c64836cc532010 (diff) | |
parent | 3b237aba6cbce79006b8e6d9d156fd5ed2dbcc3c (diff) | |
download | sdl_ios-70deeceb07e60d9f5c911ea30bc335eb130aaa71.tar.gz |
Merge pull request #1279 from smartdevicelink/feature/issue_1222_align_systemcapabilitymanager_callbacks
Support SDLSystemCapabilityManager Subscriptions
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 30 | ||||
-rw-r--r-- | SmartDeviceLink/SDLError.h | 1 | ||||
-rw-r--r-- | SmartDeviceLink/SDLError.m | 6 | ||||
-rw-r--r-- | SmartDeviceLink/SDLLifecycleManager.m | 1 | ||||
-rw-r--r-- | SmartDeviceLink/SDLSystemCapabilityManager.h | 110 | ||||
-rw-r--r-- | SmartDeviceLink/SDLSystemCapabilityManager.m | 137 | ||||
-rw-r--r-- | SmartDeviceLink/SDLSystemCapabilityObserver.h | 57 | ||||
-rw-r--r-- | SmartDeviceLink/SDLSystemCapabilityObserver.m | 37 | ||||
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.h | 24 | ||||
-rw-r--r-- | SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.m | 30 | ||||
-rw-r--r-- | SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m | 131 |
11 files changed, 514 insertions, 50 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index 91cebebb7..46abf3e31 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -1004,6 +1004,9 @@ 5D6F7A351BC5B9B60070BF37 /* SDLLockScreenViewController.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D6F7A331BC5B9B60070BF37 /* SDLLockScreenViewController.h */; settings = {ATTRIBUTES = (Public, ); }; }; 5D6F7A361BC5B9B60070BF37 /* SDLLockScreenViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D6F7A341BC5B9B60070BF37 /* SDLLockScreenViewController.m */; }; 5D6F7A3E1BC811FC0070BF37 /* SDLAssets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5D6F7A3D1BC811FC0070BF37 /* SDLAssets.xcassets */; }; + 5D75960D22972F830013207C /* TestSystemCapabilityObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D75960C22972F830013207C /* TestSystemCapabilityObserver.m */; }; + 5D75961122972FCA0013207C /* SDLSystemCapabilityObserver.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D75960F22972FCA0013207C /* SDLSystemCapabilityObserver.h */; }; + 5D75961222972FCA0013207C /* SDLSystemCapabilityObserver.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D75961022972FCA0013207C /* SDLSystemCapabilityObserver.m */; }; 5D76E31C1D3805FF00647CFA /* SDLLockScreenManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D76E31B1D3805FF00647CFA /* SDLLockScreenManagerSpec.m */; }; 5D76E3211D39742300647CFA /* SDLViewControllerPresentable.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D76E3201D39742300647CFA /* SDLViewControllerPresentable.h */; }; 5D76E3241D39767000647CFA /* SDLLockScreenPresenter.h in Headers */ = {isa = PBXBuildFile; fileRef = 5D76E3221D39767000647CFA /* SDLLockScreenPresenter.h */; }; @@ -2638,6 +2641,10 @@ 5D6F7A331BC5B9B60070BF37 /* SDLLockScreenViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLLockScreenViewController.h; sourceTree = "<group>"; }; 5D6F7A341BC5B9B60070BF37 /* SDLLockScreenViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLLockScreenViewController.m; sourceTree = "<group>"; }; 5D6F7A3D1BC811FC0070BF37 /* SDLAssets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; name = SDLAssets.xcassets; path = Assets/SDLAssets.xcassets; sourceTree = "<group>"; }; + 5D75960B22972F830013207C /* TestSystemCapabilityObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = TestSystemCapabilityObserver.h; path = DevAPISpecs/TestSystemCapabilityObserver.h; sourceTree = "<group>"; }; + 5D75960C22972F830013207C /* TestSystemCapabilityObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = TestSystemCapabilityObserver.m; path = DevAPISpecs/TestSystemCapabilityObserver.m; sourceTree = "<group>"; }; + 5D75960F22972FCA0013207C /* SDLSystemCapabilityObserver.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSystemCapabilityObserver.h; sourceTree = "<group>"; }; + 5D75961022972FCA0013207C /* SDLSystemCapabilityObserver.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSystemCapabilityObserver.m; sourceTree = "<group>"; }; 5D76E31B1D3805FF00647CFA /* SDLLockScreenManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLLockScreenManagerSpec.m; path = DevAPISpecs/SDLLockScreenManagerSpec.m; sourceTree = "<group>"; }; 5D76E3201D39742300647CFA /* SDLViewControllerPresentable.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLViewControllerPresentable.h; sourceTree = "<group>"; }; 5D76E3221D39767000647CFA /* SDLLockScreenPresenter.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLLockScreenPresenter.h; sourceTree = "<group>"; }; @@ -5060,6 +5067,24 @@ name = "Lock Screen UI"; sourceTree = "<group>"; }; + 5D75960A22972F620013207C /* Utilities */ = { + isa = PBXGroup; + children = ( + 5D75960B22972F830013207C /* TestSystemCapabilityObserver.h */, + 5D75960C22972F830013207C /* TestSystemCapabilityObserver.m */, + ); + name = Utilities; + sourceTree = "<group>"; + }; + 5D75960E22972FB90013207C /* Utilities */ = { + isa = PBXGroup; + children = ( + 5D75960F22972FCA0013207C /* SDLSystemCapabilityObserver.h */, + 5D75961022972FCA0013207C /* SDLSystemCapabilityObserver.m */, + ); + name = Utilities; + sourceTree = "<group>"; + }; 5D76E31A1D3805E600647CFA /* LockScreen */ = { isa = PBXGroup; children = ( @@ -5782,6 +5807,7 @@ 880E35B12088F73400181259 /* System Capabilities */ = { isa = PBXGroup; children = ( + 5D75960E22972FB90013207C /* Utilities */, 880E35B32088F75A00181259 /* SDLSystemCapabilityManager.h */, 880E35B22088F75A00181259 /* SDLSystemCapabilityManager.m */, ); @@ -5791,6 +5817,7 @@ 880E35B62088F77C00181259 /* System Capabilities */ = { isa = PBXGroup; children = ( + 5D75960A22972F620013207C /* Utilities */, 880E35B72088F78E00181259 /* SDLSystemCapabilityManagerSpec.m */, ); name = "System Capabilities"; @@ -6315,6 +6342,7 @@ 5D61FDB11A84238C00846EE7 /* SDLSubscribeVehicleData.h in Headers */, 5DB996601F28C6ED002D8795 /* SDLControlFramePayloadVideoStartServiceAck.h in Headers */, 5DCF76F51ACDBAD300BB647B /* SDLSendLocation.h in Headers */, + 5D75961122972FCA0013207C /* SDLSystemCapabilityObserver.h in Headers */, 5D61FC9E1A84238C00846EE7 /* SDLEncodedSyncPData.h in Headers */, 1FF7DABA1F75B2A800B46C30 /* SDLFocusableItemLocator.h in Headers */, 5D019276214994AC003500F6 /* NSMutableArray+Safe.h in Headers */, @@ -7069,6 +7097,7 @@ 88EEC5BC220A327B005AA2F9 /* SDLPublishAppServiceResponse.m in Sources */, 5D61FC951A84238C00846EE7 /* SDLDriverDistractionState.m in Sources */, 5DB996611F28C6ED002D8795 /* SDLControlFramePayloadVideoStartServiceAck.m in Sources */, + 5D75961222972FCA0013207C /* SDLSystemCapabilityObserver.m in Sources */, 884E702021FB983F008D53BA /* SDLAppServiceManifest.m in Sources */, 5D61FD961A84238C00846EE7 /* SDLShowResponse.m in Sources */, 5D61FD981A84238C00846EE7 /* SDLSingleTireStatus.m in Sources */, @@ -7415,6 +7444,7 @@ 162E837E1A9BDE8B00906325 /* SDLGPSDataSpec.m in Sources */, 5DAE06731BDEC6C000F9B498 /* SDLFileSpec.m in Sources */, 162E82E11A9BDE8B00906325 /* SDLHMIZoneCapabilitiesSpec.m in Sources */, + 5D75960D22972F830013207C /* TestSystemCapabilityObserver.m in Sources */, 88B58DBD222042500011B063 /* SDLDirectionSpec.m in Sources */, 162E83721A9BDE8B00906325 /* SDLAudioPassThruCapabilitiesSpec.m in Sources */, 162E83681A9BDE8B00906325 /* SDLSpeakResponseSpec.m in Sources */, diff --git a/SmartDeviceLink/SDLError.h b/SmartDeviceLink/SDLError.h index 9c6209c5b..280c20309 100644 --- a/SmartDeviceLink/SDLError.h +++ b/SmartDeviceLink/SDLError.h @@ -90,6 +90,7 @@ extern SDLErrorDomain *const SDLErrorDomainRPCStore; + (NSException *)sdl_invalidSoftButtonStateException; + (NSException *)sdl_carWindowOrientationException; + (NSException *)sdl_invalidLockscreenSetupException; ++ (NSException *)sdl_invalidSelectorExceptionWithSelector:(SEL)selector; @end diff --git a/SmartDeviceLink/SDLError.m b/SmartDeviceLink/SDLError.m index 7e5a0e95b..f72608a31 100644 --- a/SmartDeviceLink/SDLError.m +++ b/SmartDeviceLink/SDLError.m @@ -328,6 +328,12 @@ SDLErrorDomain *const SDLErrorDomainRPCStore = @"com.sdl.rpcStore.error"; userInfo:nil]; } ++ (NSException *)sdl_invalidSelectorExceptionWithSelector:(SEL)selector { + return [NSException exceptionWithName:@"com.sdl.systemCapabilityManager.selectorException" + reason:[NSString stringWithFormat:@"Capability observation selector: %@ does not match possible selectors, which must have either 0 or 1 parameters", NSStringFromSelector(selector)] + userInfo:nil]; +} + @end NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLLifecycleManager.m b/SmartDeviceLink/SDLLifecycleManager.m index 0c3b257ab..7ba94a6f7 100644 --- a/SmartDeviceLink/SDLLifecycleManager.m +++ b/SmartDeviceLink/SDLLifecycleManager.m @@ -385,6 +385,7 @@ SDLLifecycleState *const SDLLifecycleStateReady = @"Ready"; dispatch_group_enter(managerGroup); SDLLogD(@"Setting up assistant managers"); [self.lockScreenManager start]; + [self.systemCapabilityManager start]; dispatch_group_enter(managerGroup); [self.fileManager startWithCompletionHandler:^(BOOL success, NSError *_Nullable error) { diff --git a/SmartDeviceLink/SDLSystemCapabilityManager.h b/SmartDeviceLink/SDLSystemCapabilityManager.h index 11e31a1c2..38030365a 100644 --- a/SmartDeviceLink/SDLSystemCapabilityManager.h +++ b/SmartDeviceLink/SDLSystemCapabilityManager.h @@ -24,6 +24,7 @@ @class SDLPresetBankCapabilities; @class SDLRemoteControlCapabilities; @class SDLSoftButtonCapabilities; +@class SDLSystemCapability; @class SDLSystemCapabilityManager; @class SDLVideoStreamingCapability; @@ -31,7 +32,6 @@ NS_ASSUME_NONNULL_BEGIN - /** * A completion handler called after a request for the capability type is returned from the remote system. * @@ -40,10 +40,24 @@ NS_ASSUME_NONNULL_BEGIN */ typedef void (^SDLUpdateCapabilityHandler)(NSError * _Nullable error, SDLSystemCapabilityManager *systemCapabilityManager); +/** + An observer block for whenever a subscription is called. + + @param capability The capability that was updated. + */ +typedef void (^SDLCapabilityUpdateHandler)(SDLSystemCapability *capability); +/** + A manager that handles updating and subscribing to SDL capabilities. + */ @interface SDLSystemCapabilityManager : NSObject /** + YES if subscriptions are available on the connected head unit. If NO, calls to `subscribeToCapabilityType:withBlock` and `subscribeToCapabilityType:withObserver:selector` will fail. + */ +@property (assign, nonatomic, readonly) BOOL supportsSubscriptions; + +/** * @see SDLDisplayCapabilities * * Optional @@ -134,59 +148,64 @@ typedef void (^SDLUpdateCapabilityHandler)(NSError * _Nullable error, SDLSystemC @property (nullable, strong, nonatomic, readonly) SDLAppServicesCapabilities *appServicesCapabilities; /** - * If returned, the platform supports navigation - * - * @see SDLNavigationCapability - * - * Optional + If returned, the platform supports navigation + + @see SDLNavigationCapability + + Optional */ @property (nullable, strong, nonatomic, readonly) SDLNavigationCapability *navigationCapability; /** - * If returned, the platform supports making phone calls - * - * @see SDLPhoneCapability - * - * Optional + If returned, the platform supports making phone calls + + @see SDLPhoneCapability + + Optional */ @property (nullable, strong, nonatomic, readonly) SDLPhoneCapability *phoneCapability; /** - * If returned, the platform supports video streaming - * - * @see SDLVideoStreamingCapability - * - * Optional + If returned, the platform supports video streaming + + @see SDLVideoStreamingCapability + + Optional */ @property (nullable, strong, nonatomic, readonly) SDLVideoStreamingCapability *videoStreamingCapability; /** - * If returned, the platform supports remote control capabilities - * - * @see SDLRemoteControlCapabilities - * - * Optional + If returned, the platform supports remote control capabilities + + @see SDLRemoteControlCapabilities + + Optional */ @property (nullable, strong, nonatomic, readonly) SDLRemoteControlCapabilities *remoteControlCapability; /** - * Init is unavailable. Dependencies must be injected using initWithConnectionManager: - * - * @return nil + Init is unavailable. Dependencies must be injected using initWithConnectionManager: + + @return nil */ - (instancetype)init NS_UNAVAILABLE; /** - * Creates a new system capability manager with a specified connection manager - * - * @param manager A connection manager to use to forward on RPCs - * - * @return An instance of SDLSystemCapabilityManager + Creates a new system capability manager with a specified connection manager + + @param manager A connection manager to use to forward on RPCs + + @return An instance of SDLSystemCapabilityManager */ - (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)manager NS_DESIGNATED_INITIALIZER; /** - * Stops the manager. This method is used internally. + Starts the manager. This method is used internally. + */ +- (void)start; + +/** + Stops the manager. This method is used internally. */ - (void)stop; @@ -198,6 +217,37 @@ typedef void (^SDLUpdateCapabilityHandler)(NSError * _Nullable error, SDLSystemC */ - (void)updateCapabilityType:(SDLSystemCapabilityType)type completionHandler:(SDLUpdateCapabilityHandler)handler; +/** + Subscribe to a particular capability type using a block callback + + @param type The type of capability to subscribe to + @param block The block to be called when the capability is updated + @return An object that can be used to unsubscribe the block using unsubscribeFromCapabilityType:withObserver: by passing it in the observer callback, or nil if subscriptions aren't available on this head unit + */ +- (nullable id<NSObject>)subscribeToCapabilityType:(SDLSystemCapabilityType)type usingBlock:(SDLCapabilityUpdateHandler)block; + +/** + * Subscribe to a particular capability type with a selector callback. The selector supports the following parameters: + * + * 1. No parameters e.g. `- (void)phoneCapabilityUpdated;` + * 2. One `SDLSystemCapability *` parameter e.g. `- (void)phoneCapabilityUpdated:(SDLSystemCapability *)capability` + * + * This method will be called immediately with the current value and called every time the value is updated on RPC v5.1.0+ systems (`supportsSubscriptions == YES`). If this method is called on a sub-v5.1.0 system (`supportsSubscriptions == NO`), the method will return `NO` and the selector will never be called. + * + * @param type The type of the system capability to subscribe to + * @param observer The object that will have `selector` called whenever the capability is updated + * @param selector The selector on `observer` that will be called whenever the capability is updated + * @return Whether or not the subscription succeeded. `NO` if the connected system doesn't support capability subscriptions, or if the `selector` doesn't support the correct parameters (see above) + */ +- (BOOL)subscribeToCapabilityType:(SDLSystemCapabilityType)type withObserver:(id)observer selector:(SEL)selector; + +/** + * Unsubscribe from a particular capability type. If it was subscribed with a block, the return value should be passed to the `observer` to unsubscribe the block. If it was subscribed with a selector, the `observer` object should be passed to unsubscribe the object selector. + * + * @param type The type of the system capability to unsubscribe from + * @param observer The object that will be unsubscribed. If a block was subscribed, the return value should be passed. If a selector was subscribed, the observer object should be passed. + */ +- (void)unsubscribeFromCapabilityType:(SDLSystemCapabilityType)type withObserver:(id)observer; @end diff --git a/SmartDeviceLink/SDLSystemCapabilityManager.m b/SmartDeviceLink/SDLSystemCapabilityManager.m index 0a48ce71f..82d7c8c82 100644 --- a/SmartDeviceLink/SDLSystemCapabilityManager.m +++ b/SmartDeviceLink/SDLSystemCapabilityManager.m @@ -12,19 +12,24 @@ #import "SDLAppServiceRecord.h" #import "SDLAppServicesCapabilities.h" #import "SDLConnectionManagerType.h" +#import "SDLError.h" #import "SDLGenericResponse.h" #import "SDLGetSystemCapability.h" #import "SDLGetSystemCapabilityResponse.h" #import "SDLGlobals.h" #import "SDLLogMacros.h" +#import "SDLNavigationCapability.h" #import "SDLNotificationConstants.h" #import "SDLOnHMIStatus.h" #import "SDLOnSystemCapabilityUpdated.h" +#import "SDLPhoneCapability.h" #import "SDLRegisterAppInterfaceResponse.h" +#import "SDLRemoteControlCapabilities.h" #import "SDLRPCNotificationNotification.h" #import "SDLRPCResponseNotification.h" #import "SDLSetDisplayLayoutResponse.h" #import "SDLSystemCapability.h" +#import "SDLSystemCapabilityObserver.h" #import "SDLVersion.h" #import "SDLVideoStreamingCapability.h" @@ -55,6 +60,11 @@ typedef NSString * SDLServiceID; @property (nullable, strong, nonatomic) NSMutableDictionary<SDLServiceID, SDLAppServiceCapability *> *appServicesCapabilitiesDictionary; +@property (assign, nonatomic, readwrite) BOOL supportsSubscriptions; +@property (strong, nonatomic) NSMutableDictionary<SDLSystemCapabilityType, NSMutableArray<SDLSystemCapabilityObserver *> *> *capabilityObservers; + +@property (nullable, strong, nonatomic) SDLSystemCapability *lastReceivedCapability; + @property (assign, nonatomic) BOOL isFirstHMILevelFull; @end @@ -73,11 +83,24 @@ typedef NSString * SDLServiceID; _isFirstHMILevelFull = NO; _appServicesCapabilitiesDictionary = [NSMutableDictionary dictionary]; + _capabilityObservers = [NSMutableDictionary dictionary]; + for (SDLSystemCapabilityType capabilityType in [self.class sdl_systemCapabilityTypes]) { + _capabilityObservers[capabilityType] = [NSMutableArray array]; + } + [self sdl_registerForNotifications]; return self; } +- (void)start { + SDLVersion *onSystemCapabilityNotificationRPCVersion = [SDLVersion versionWithString:@"5.1.0"]; + SDLVersion *headUnitRPCVersion = SDLGlobals.sharedGlobals.rpcVersion; + if ([headUnitRPCVersion isGreaterThanOrEqualToVersion:onSystemCapabilityNotificationRPCVersion]) { + _supportsSubscriptions = YES; + } +} + /** * Resets the capabilities when a transport session is closed. */ @@ -100,6 +123,11 @@ typedef NSString * SDLServiceID; _remoteControlCapability = nil; _appServicesCapabilitiesDictionary = [NSMutableDictionary dictionary]; + _supportsSubscriptions = NO; + for (SDLSystemCapabilityType capabilityType in [self.class sdl_systemCapabilityTypes]) { + _capabilityObservers[capabilityType] = [NSMutableArray array]; + } + _isFirstHMILevelFull = NO; } @@ -120,6 +148,7 @@ typedef NSString * SDLServiceID; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_registerResponse:) name:SDLDidReceiveRegisterAppInterfaceResponse object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_displayLayoutResponse:) name:SDLDidReceiveSetDisplayLayoutResponse object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_systemCapabilityUpdatedNotification:) name:SDLDidReceiveSystemCapabilityUpdatedNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_systemCapabilityResponseNotification:) name:SDLDidReceiveGetSystemCapabilitiesResponse object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusNotification:) name:SDLDidChangeHMIStatusNotification object:nil]; } @@ -171,6 +200,16 @@ typedef NSString * SDLServiceID; } /** + Called with a `GetSystemCapabilityResponse` notification is received from core. The updated system capability is saved. + + @param notification The `GetSystemCapabilityResponse` notification received from Core + */ +- (void)sdl_systemCapabilityResponseNotification:(SDLRPCResponseNotification *)notification { + SDLGetSystemCapabilityResponse *systemCapabilityResponse = (SDLGetSystemCapabilityResponse *)notification.response; + [self sdl_saveSystemCapability:systemCapabilityResponse.systemCapability completionHandler:nil]; +} + +/** * Called when an `OnHMIStatus` notification is received from Core. The first time the `hmiLevel` is `FULL` attempt to subscribe to system capabilty updates. * * @param notification The `OnHMIStatus` notification received from Core @@ -188,9 +227,7 @@ typedef NSString * SDLServiceID; #pragma mark - System Capabilities - (void)updateCapabilityType:(SDLSystemCapabilityType)type completionHandler:(SDLUpdateCapabilityHandler)handler { - SDLVersion *onSystemCapabilityNotificationRPCVersion = [SDLVersion versionWithString:@"5.1.0"]; - SDLVersion *headUnitRPCVersion = SDLGlobals.sharedGlobals.rpcVersion; - if ([headUnitRPCVersion isGreaterThanOrEqualToVersion:onSystemCapabilityNotificationRPCVersion]) { + if (self.supportsSubscriptions) { // Just return the cached data because we get `onSystemCapability` callbacks handler(nil, self); } else { @@ -215,9 +252,7 @@ typedef NSString * SDLServiceID; - (void)sdl_subscribeToSystemCapabilityUpdates { for (SDLSystemCapabilityType type in [self.class sdl_systemCapabilityTypes]) { SDLGetSystemCapability *getSystemCapability = [[SDLGetSystemCapability alloc] initWithType:type]; - SDLVersion *onSystemCapabilityNotificationRPCVersion = [SDLVersion versionWithString:@"5.1.0"]; - SDLVersion *headUnitRPCVersion = SDLGlobals.sharedGlobals.rpcVersion; - if ([headUnitRPCVersion isGreaterThanOrEqualToVersion:onSystemCapabilityNotificationRPCVersion]) { + if (self.supportsSubscriptions) { getSystemCapability.subscribe = @YES; } @@ -231,47 +266,87 @@ typedef NSString * SDLServiceID; * @param getSystemCapability The `GetSystemCapability` request to send */ - (void)sdl_sendGetSystemCapability:(SDLGetSystemCapability *)getSystemCapability completionHandler:(nullable SDLUpdateCapabilityHandler)handler { + __weak typeof(self) weakSelf = self; [self.connectionManager sendConnectionRequest:getSystemCapability withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) { if (error != nil) { // An error is returned if the request was unsuccessful or if a Generic Response was returned if (handler == nil) { return; } - handler(error, self); + handler(error, weakSelf); return; } SDLGetSystemCapabilityResponse *getSystemCapabilityResponse = (SDLGetSystemCapabilityResponse *)response; - if (!getSystemCapabilityResponse.success.boolValue) { return; } - [self sdl_saveSystemCapability:getSystemCapabilityResponse.systemCapability completionHandler:handler]; + [weakSelf sdl_saveSystemCapability:getSystemCapabilityResponse.systemCapability completionHandler:handler]; }]; } /** - * Saves a system capability. All system capabilities will update with the full object except for app services. For app services only the updated app service capabilities will be included in the `SystemCapability` sent from Core. The cached `appServicesCapabilities` will be updated with the new `appService` data. - * - * @param systemCapability The system capability + Saves a system capability. All system capabilities will update with the full object except for app services. For app services only the updated app service capabilities will be included in the `SystemCapability` sent from Core. The cached `appServicesCapabilities` will be updated with the new `appService` data. + + @param systemCapability The system capability to be saved + @param handler The handler to be called when the save completes + @return Whether or not the save occurred. This can be `NO` if the new system capability is equivalent to the old capability. */ -- (void)sdl_saveSystemCapability:(SDLSystemCapability *)systemCapability completionHandler:(nullable SDLUpdateCapabilityHandler)handler { +- (BOOL)sdl_saveSystemCapability:(SDLSystemCapability *)systemCapability completionHandler:(nullable SDLUpdateCapabilityHandler)handler { + if ([self.lastReceivedCapability isEqual:systemCapability]) { + return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:NO handler:handler]; + } + self.lastReceivedCapability = systemCapability; + SDLSystemCapabilityType systemCapabilityType = systemCapability.systemCapabilityType; if ([systemCapabilityType isEqualToEnum:SDLSystemCapabilityTypePhoneCall]) { + if ([self.phoneCapability isEqual:systemCapability.phoneCapability]) { return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:NO handler:handler]; } self.phoneCapability = systemCapability.phoneCapability; } else if ([systemCapabilityType isEqualToEnum:SDLSystemCapabilityTypeNavigation]) { + if ([self.navigationCapability isEqual:systemCapability.navigationCapability]) { return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:NO handler:handler]; } self.navigationCapability = systemCapability.navigationCapability; } else if ([systemCapabilityType isEqualToEnum:SDLSystemCapabilityTypeRemoteControl]) { + if ([self.remoteControlCapability isEqual:systemCapability.remoteControlCapability]) { return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:NO handler:handler]; } self.remoteControlCapability = systemCapability.remoteControlCapability; } else if ([systemCapabilityType isEqualToEnum:SDLSystemCapabilityTypeVideoStreaming]) { + if ([self.videoStreamingCapability isEqual:systemCapability.videoStreamingCapability]) { return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:NO handler:handler]; } self.videoStreamingCapability = systemCapability.videoStreamingCapability; } else if ([systemCapabilityType isEqualToEnum:SDLSystemCapabilityTypeAppServices]) { [self sdl_saveAppServicesCapabilitiesUpdate:systemCapability.appServicesCapabilities]; + systemCapability = [[SDLSystemCapability alloc] initWithAppServicesCapabilities:self.appServicesCapabilities]; } else { SDLLogW(@"Received response for unknown System Capability Type: %@", systemCapabilityType); - return; + return NO; } SDLLogD(@"Updated system capability manager with new data: %@", systemCapability); - if (handler == nil) { return; } + return [self sdl_callSaveHandlerForCapability:systemCapability andReturnWithValue:YES handler:handler]; +} + +- (BOOL)sdl_callSaveHandlerForCapability:(SDLSystemCapability *)capability andReturnWithValue:(BOOL)value handler:(nullable SDLUpdateCapabilityHandler)handler { + for (SDLSystemCapabilityObserver *observer in self.capabilityObservers[capability.systemCapabilityType]) { + if (observer.block != nil) { + observer.block(capability); + } else { + NSUInteger numberOfParametersInSelector = [NSStringFromSelector(observer.selector) componentsSeparatedByString:@":"].count - 1; +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Warc-performSelector-leaks" + if (numberOfParametersInSelector == 0) { + if ([observer.observer respondsToSelector:observer.selector]) { + [observer.observer performSelector:observer.selector]; + } + } else if (numberOfParametersInSelector == 1) { + if ([observer.observer respondsToSelector:observer.selector]) { + [observer.observer performSelector:observer.selector withObject:capability]; + } + } else { + @throw [NSException sdl_invalidSelectorExceptionWithSelector:observer.selector]; + } +#pragma clang diagnostic pop + } + } + + if (handler == nil) { return value; } handler(nil, self); + + return value; } - (void)sdl_saveAppServicesCapabilitiesUpdate:(SDLAppServicesCapabilities *)newCapabilities { @@ -288,6 +363,38 @@ typedef NSString * SDLServiceID; } } +#pragma mark - Subscriptions + +- (nullable id<NSObject>)subscribeToCapabilityType:(SDLSystemCapabilityType)type usingBlock:(SDLCapabilityUpdateHandler)block { + if (!self.supportsSubscriptions) { return nil; } + + SDLSystemCapabilityObserver *observerObject = [[SDLSystemCapabilityObserver alloc] initWithObserver:[[NSObject alloc] init] block:block]; + [self.capabilityObservers[type] addObject:observerObject]; + + return observerObject.observer; +} + +- (BOOL)subscribeToCapabilityType:(SDLSystemCapabilityType)type withObserver:(id<NSObject>)observer selector:(SEL)selector { + if (!self.supportsSubscriptions) { return NO; } + + NSUInteger numberOfParametersInSelector = [NSStringFromSelector(selector) componentsSeparatedByString:@":"].count - 1; + if (numberOfParametersInSelector > 1) { return NO; } + + SDLSystemCapabilityObserver *observerObject = [[SDLSystemCapabilityObserver alloc] initWithObserver:observer selector:selector]; + [self.capabilityObservers[type] addObject:observerObject]; + + return observerObject.observer; +} + +- (void)unsubscribeFromCapabilityType:(SDLSystemCapabilityType)type withObserver:(id)observer { + for (SDLSystemCapabilityObserver *capabilityObserver in self.capabilityObservers[type]) { + if ([observer isEqual:capabilityObserver.observer]) { + [self.capabilityObservers[type] removeObject:capabilityObserver]; + break; + } + } +} + @end NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLSystemCapabilityObserver.h b/SmartDeviceLink/SDLSystemCapabilityObserver.h new file mode 100644 index 000000000..f5450d60e --- /dev/null +++ b/SmartDeviceLink/SDLSystemCapabilityObserver.h @@ -0,0 +1,57 @@ +// +// SDLSystemCapabilityObserver.h +// SmartDeviceLink +// +// Created by Joel Fischer on 5/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class SDLSystemCapability; + +NS_ASSUME_NONNULL_BEGIN + +typedef void (^SDLCapabilityUpdateHandler)(SDLSystemCapability *capability); + +/** + An observer object for SDLSystemCapabilityManager + */ +@interface SDLSystemCapabilityObserver : NSObject + +/** + The object that will be used to call the selector if available, and to unsubscribe this observer + */ +@property (strong, nonatomic) id<NSObject> observer; + +/** + A selector called when the observer is triggered + */ +@property (assign, nonatomic) SEL selector; + +/** + A block called when the observer is triggered + */ +@property (copy, nonatomic) SDLCapabilityUpdateHandler block; + +/** + Create an observer using an object and a selector on that object + + @param observer The object to be called when the subscription triggers + @param selector The selector to be called when the subscription triggers + @return The observer + */ +- (instancetype)initWithObserver:(id<NSObject>)observer selector:(SEL)selector; + +/** + Create an observer using an object and a callback block + + @param observer The object that can be used to unsubscribe the block + @param block The block that will be called when the subscription triggers + @return The observer + */ +- (instancetype)initWithObserver:(id<NSObject>)observer block:(SDLCapabilityUpdateHandler)block; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLSystemCapabilityObserver.m b/SmartDeviceLink/SDLSystemCapabilityObserver.m new file mode 100644 index 000000000..64832f1a7 --- /dev/null +++ b/SmartDeviceLink/SDLSystemCapabilityObserver.m @@ -0,0 +1,37 @@ +// +// SDLSystemCapabilityObserver.m +// SmartDeviceLink +// +// Created by Joel Fischer on 5/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import "SDLSystemCapabilityObserver.h" + +NS_ASSUME_NONNULL_BEGIN + +@implementation SDLSystemCapabilityObserver + +- (instancetype)initWithObserver:(id<NSObject>)observer selector:(SEL)selector { + self = [super init]; + if (!self) { return nil; } + + _observer = observer; + _selector = selector; + + return self; +} + +- (instancetype)initWithObserver:(id<NSObject>)observer block:(SDLCapabilityUpdateHandler)block { + self = [super init]; + if (!self) { return nil; } + + _observer = observer; + _block = block; + + return self; +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.h b/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.h new file mode 100644 index 000000000..0e6c8bcbb --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.h @@ -0,0 +1,24 @@ +// +// TestSystemCapabilityObserver.h +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 5/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class SDLSystemCapabilityManager; + +NS_ASSUME_NONNULL_BEGIN + +@interface TestSystemCapabilityObserver : NSObject + +@property (assign, nonatomic) NSUInteger selectorCalledCount; + +- (void)capabilityUpdated; +- (void)capabilityUpdatedWithNotification:(SDLSystemCapabilityManager *)capabilityManager; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.m b/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.m new file mode 100644 index 000000000..5366b0884 --- /dev/null +++ b/SmartDeviceLinkTests/DevAPISpecs/TestSystemCapabilityObserver.m @@ -0,0 +1,30 @@ +// +// TestSystemCapabilityObserver.m +// SmartDeviceLinkTests +// +// Created by Joel Fischer on 5/23/19. +// Copyright © 2019 smartdevicelink. All rights reserved. +// + +#import "TestSystemCapabilityObserver.h" + +@implementation TestSystemCapabilityObserver + +- (instancetype)init { + self = [super init]; + if (!self) { return nil; } + + _selectorCalledCount = 0; + + return self; +} + +- (void)capabilityUpdated { + self.selectorCalledCount++; +} + +- (void)capabilityUpdatedWithNotification:(SDLSystemCapabilityManager *)capabilityManager { + self.selectorCalledCount++; +} + +@end diff --git a/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m b/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m index 109b8ea2d..4fc9a6547 100644 --- a/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m +++ b/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m @@ -29,6 +29,14 @@ #import "SDLSystemCapabilityManager.h" #import "SDLVideoStreamingCapability.h" #import "TestConnectionManager.h" +#import "TestSystemCapabilityObserver.h" + + +@interface SDLSystemCapabilityManager () + +@property (assign, nonatomic, readwrite) BOOL supportsSubscriptions; + +@end QuickSpecBegin(SDLSystemCapabilityManagerSpec) @@ -181,7 +189,7 @@ describe(@"System capability manager", ^{ }); }); - context(@"When notified of a Set Display Layout Response", ^ { + context(@"When notified of a SetDisplayLayout Response", ^ { __block SDLSetDisplayLayoutResponse *testSetDisplayLayoutResponse = nil; __block SDLDisplayCapabilities *testDisplayCapabilities = nil; __block NSArray<SDLSoftButtonCapabilities *> *testSoftButtonCapabilities = nil; @@ -216,7 +224,7 @@ describe(@"System capability manager", ^{ testSetDisplayLayoutResponse.presetBankCapabilities = testPresetBankCapabilities; }); - describe(@"If the Set Display Layout request fails", ^{ + describe(@"If the SetDisplayLayout request fails", ^{ beforeEach(^{ testSetDisplayLayoutResponse.success = @NO; SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveSetDisplayLayoutResponse object:self rpcResponse:testSetDisplayLayoutResponse]; @@ -231,7 +239,7 @@ describe(@"System capability manager", ^{ }); }); - describe(@"If the Set Display Layout request succeeds", ^{ + describe(@"If the SetDisplayLayout request succeeds", ^{ beforeEach(^{ testSetDisplayLayoutResponse.success = @YES; SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveSetDisplayLayoutResponse object:self rpcResponse:testSetDisplayLayoutResponse]; @@ -263,8 +271,8 @@ describe(@"System capability manager", ^{ }); }); - context(@"When sending a Get System Capability request", ^{ - __block SDLGetSystemCapabilityResponse *testGetSystemCapabilityResponse; + context(@"When sending a GetSystemCapability request", ^{ + __block SDLGetSystemCapabilityResponse *testGetSystemCapabilityResponse = nil; __block SDLPhoneCapability *testPhoneCapability = nil; beforeEach(^{ @@ -347,6 +355,7 @@ describe(@"System capability manager", ^{ SDLSystemCapability *newCapability = [[SDLSystemCapability alloc] initWithPhoneCapability:phoneCapability]; SDLOnSystemCapabilityUpdated *update = [[SDLOnSystemCapabilityUpdated alloc] initWithSystemCapability:newCapability]; SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveSystemCapabilityUpdatedNotification object:nil rpcNotification:update]; + [[NSNotificationCenter defaultCenter] postNotification:notification]; }); @@ -355,6 +364,118 @@ describe(@"System capability manager", ^{ }); }); + describe(@"subscribing to capability types", ^{ + __block TestSystemCapabilityObserver *phoneObserver = nil; + __block TestSystemCapabilityObserver *navigationObserver = nil; + + __block NSUInteger blockObserverTriggeredCount = 0; + + beforeEach(^{ + blockObserverTriggeredCount = 0; + testSystemCapabilityManager.supportsSubscriptions = YES; + + phoneObserver = [[TestSystemCapabilityObserver alloc] init]; + [testSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:phoneObserver selector:@selector(capabilityUpdatedWithNotification:)]; + navigationObserver = [[TestSystemCapabilityObserver alloc] init]; + [testSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeNavigation withObserver:navigationObserver selector:@selector(capabilityUpdatedWithNotification:)]; + }); + + describe(@"when observers aren't supported", ^{ + __block BOOL observationSuccess = NO; + + beforeEach(^{ + testSystemCapabilityManager.supportsSubscriptions = NO; + + observationSuccess = [testSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:phoneObserver selector:@selector(capabilityUpdatedWithNotification:)]; + }); + + it(@"should fail to subscribe", ^{ + expect(observationSuccess).to(beFalse()); + }); + }); + + context(@"from a GetSystemCapabilitiesResponse", ^{ + __block id blockObserver = nil; + + beforeEach(^{ + blockObserver = [testSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypePhoneCall usingBlock:^(SDLSystemCapability * _Nonnull systemCapability) { + blockObserverTriggeredCount++; + }]; + + SDLGetSystemCapabilityResponse *testResponse = [[SDLGetSystemCapabilityResponse alloc] init]; + testResponse.systemCapability = [[SDLSystemCapability alloc] initWithPhoneCapability:[[SDLPhoneCapability alloc] initWithDialNumber:YES]]; + SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveGetSystemCapabilitiesResponse object:nil rpcResponse:testResponse]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + }); + + it(@"should notify subscribers of the new data", ^{ + expect(phoneObserver.selectorCalledCount).toEventually(equal(1)); + expect(blockObserverTriggeredCount).toEventually(equal(1)); + expect(navigationObserver.selectorCalledCount).toEventually(equal(0)); + }); + + describe(@"unsubscribing", ^{ + beforeEach(^{ + [testSystemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:phoneObserver]; + [testSystemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:blockObserver]; + + SDLGetSystemCapabilityResponse *testResponse = [[SDLGetSystemCapabilityResponse alloc] init]; + testResponse.systemCapability = [[SDLSystemCapability alloc] initWithPhoneCapability:[[SDLPhoneCapability alloc] initWithDialNumber:YES]]; + SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveGetSystemCapabilitiesResponse object:nil rpcResponse:testResponse]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + }); + + it(@"should not notify the subscriber of the new data", ^{ + expect(phoneObserver.selectorCalledCount).toEventually(equal(1)); // No change from above + expect(blockObserverTriggeredCount).toEventually(equal(1)); + expect(navigationObserver.selectorCalledCount).toEventually(equal(0)); + }); + }); + }); + + context(@"from an OnSystemCapabilities notification", ^{ + __block id blockObserver = nil; + + beforeEach(^{ + blockObserver = [testSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypePhoneCall usingBlock:^(SDLSystemCapability * _Nonnull systemCapability) { + blockObserverTriggeredCount++; + }]; + + SDLOnSystemCapabilityUpdated *testNotification = [[SDLOnSystemCapabilityUpdated alloc] initWithSystemCapability:[[SDLSystemCapability alloc] initWithPhoneCapability:[[SDLPhoneCapability alloc] initWithDialNumber:YES]]]; + SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveSystemCapabilityUpdatedNotification object:nil rpcNotification:testNotification]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + }); + + it(@"should notify subscribers of the new data", ^{ + expect(phoneObserver.selectorCalledCount).toEventually(equal(1)); + expect(blockObserverTriggeredCount).toEventually(equal(1)); + expect(navigationObserver.selectorCalledCount).toEventually(equal(0)); + }); + + describe(@"unsubscribing", ^{ + beforeEach(^{ + [testSystemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:phoneObserver]; + [testSystemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypePhoneCall withObserver:blockObserver]; + + SDLGetSystemCapabilityResponse *testResponse = [[SDLGetSystemCapabilityResponse alloc] init]; + testResponse.systemCapability = [[SDLSystemCapability alloc] initWithPhoneCapability:[[SDLPhoneCapability alloc] initWithDialNumber:YES]]; + SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveGetSystemCapabilitiesResponse object:nil rpcResponse:testResponse]; + + [[NSNotificationCenter defaultCenter] postNotification:notification]; + }); + + it(@"should not notify the subscriber of the new data", ^{ + expect(phoneObserver.selectorCalledCount).toEventually(equal(1)); // No change from above + expect(blockObserverTriggeredCount).toEventually(equal(1)); + expect(navigationObserver.selectorCalledCount).toEventually(equal(0)); + }); + }); + }); + }); + describe(@"merging app services capability changes", ^{ __block SDLAppServicesCapabilities *baseAppServices = nil; __block SDLAppServiceCapability *deleteCapability = nil; |