diff options
author | Joel Fischer <joeljfischer@gmail.com> | 2018-04-24 11:54:00 -0400 |
---|---|---|
committer | Joel Fischer <joeljfischer@gmail.com> | 2018-04-24 11:54:00 -0400 |
commit | ebd6c03927f4115edfe54aa1c24149a008a17b03 (patch) | |
tree | 8b84c2641ba1b46f6c3cd1e9755d3e155eaa1c72 | |
parent | 109ab29d87178e2c5d620a46380157d7222c25bc (diff) | |
download | sdl_ios-ebd6c03927f4115edfe54aa1c24149a008a17b03.tar.gz |
Add Voice Command Manager, extract stubs from menu manager
* Fix perform interaction not working in example app
* Add voice command to example app
* Fix log module map
* Add comments
-rw-r--r-- | SmartDeviceLink-iOS.xcodeproj/project.pbxproj | 8 | ||||
-rw-r--r-- | SmartDeviceLink/SDLLogFileModuleMap.m | 5 | ||||
-rw-r--r-- | SmartDeviceLink/SDLMenuCell.h | 19 | ||||
-rw-r--r-- | SmartDeviceLink/SDLMenuManager.h | 1 | ||||
-rw-r--r-- | SmartDeviceLink/SDLMenuManager.m | 65 | ||||
-rw-r--r-- | SmartDeviceLink/SDLScreenManager.m | 7 | ||||
-rw-r--r-- | SmartDeviceLink/SDLVoiceCommand.h | 7 | ||||
-rw-r--r-- | SmartDeviceLink/SDLVoiceCommandManager.h | 33 | ||||
-rw-r--r-- | SmartDeviceLink/SDLVoiceCommandManager.m | 251 | ||||
-rw-r--r-- | SmartDeviceLink_Example/Classes/ProxyManager.m | 8 |
10 files changed, 343 insertions, 61 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj index 8ee659289..4a28c01ce 100644 --- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj +++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj @@ -1057,6 +1057,8 @@ 5DEF695D1FD6FA01004B8C2F /* testAudio.mp3 in Resources */ = {isa = PBXBuildFile; fileRef = 5DEF695C1FD6FA01004B8C2F /* testAudio.mp3 */; }; 5DEF69611FD6FB75004B8C2F /* SDLAudioStreamManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DEF69601FD6FB75004B8C2F /* SDLAudioStreamManagerSpec.m */; }; 5DEF69661FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DEF69651FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m */; }; + 5DF40B22208E761A00DD6FDA /* SDLVoiceCommandManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */; }; + 5DF40B23208E761A00DD6FDA /* SDLVoiceCommandManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */; }; 5DFFB9151BD7C89700DB3F04 /* SDLConnectionManagerType.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DFFB9141BD7C89700DB3F04 /* SDLConnectionManagerType.h */; }; 880E8C2920697FEE00CF86C2 /* SDLSystemCapabilityManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 880E8C2720697FEE00CF86C2 /* SDLSystemCapabilityManager.h */; settings = {ATTRIBUTES = (Public, ); }; }; 880E8C2A20697FEE00CF86C2 /* SDLSystemCapabilityManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 880E8C2820697FEE00CF86C2 /* SDLSystemCapabilityManager.m */; }; @@ -2332,6 +2334,8 @@ 5DEF69641FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLStreamingAudioManagerMock.h; sourceTree = "<group>"; }; 5DEF69651FD6FEF7004B8C2F /* SDLStreamingAudioManagerMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLStreamingAudioManagerMock.m; sourceTree = "<group>"; }; 5DF2BB9C1B94E38A00CE5994 /* SDLURLSessionSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLURLSessionSpec.m; path = "UtilitiesSpecs/HTTP Connection/SDLURLSessionSpec.m"; sourceTree = "<group>"; }; + 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLVoiceCommandManager.h; sourceTree = "<group>"; }; + 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLVoiceCommandManager.m; sourceTree = "<group>"; }; 5DFFB9141BD7C89700DB3F04 /* SDLConnectionManagerType.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = SDLConnectionManagerType.h; sourceTree = "<group>"; }; 880E8C2720697FEE00CF86C2 /* SDLSystemCapabilityManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSystemCapabilityManager.h; sourceTree = "<group>"; }; 880E8C2820697FEE00CF86C2 /* SDLSystemCapabilityManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSystemCapabilityManager.m; sourceTree = "<group>"; }; @@ -3054,6 +3058,8 @@ 5D339CEC207C08AB000CC364 /* Cells */, 5D339CF1207C0ACE000CC364 /* SDLMenuManager.h */, 5D339CF2207C0ACE000CC364 /* SDLMenuManager.m */, + 5DF40B20208E761A00DD6FDA /* SDLVoiceCommandManager.h */, + 5DF40B21208E761A00DD6FDA /* SDLVoiceCommandManager.m */, ); name = Menu; sourceTree = "<group>"; @@ -5231,6 +5237,7 @@ 5D1665CB1CF8CA6700CC4CA1 /* NSNumber+NumberType.h in Headers */, 5D9FDA941F2A7D3400A495C8 /* bson_util.h in Headers */, 5D61FD771A84238C00846EE7 /* SDLSamplingRate.h in Headers */, + 5DF40B22208E761A00DD6FDA /* SDLVoiceCommandManager.h in Headers */, 5D61FCBB1A84238C00846EE7 /* SDLGPSData.h in Headers */, 5D61FDA31A84238C00846EE7 /* SDLSoftButtonType.h in Headers */, 5D61FC431A84238C00846EE7 /* SDLAppInterfaceUnregisteredReason.h in Headers */, @@ -5768,6 +5775,7 @@ 5D61FD581A84238C00846EE7 /* SDLPutFileResponse.m in Sources */, 5D61FCB21A84238C00846EE7 /* SDLGetDTCs.m in Sources */, 5D61FD441A84238C00846EE7 /* SDLProtocol.m in Sources */, + 5DF40B23208E761A00DD6FDA /* SDLVoiceCommandManager.m in Sources */, 5D61FC341A84238C00846EE7 /* SDLAddSubMenuResponse.m in Sources */, 5DA240011F325621009C0313 /* SDLStreamingMediaConfiguration.m in Sources */, 5D6F7A2F1BC5650B0070BF37 /* SDLLifecycleConfiguration.m in Sources */, diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m index cdbeccc71..560c262ed 100644 --- a/SmartDeviceLink/SDLLogFileModuleMap.m +++ b/SmartDeviceLink/SDLLogFileModuleMap.m @@ -21,7 +21,8 @@ [self sdl_fileManagerModule], [self sdl_lifecycleManagerModule], [self sdl_lockscreenManagerModule], - [self sdl_streamingMediaManagerModule]]]; + [self sdl_streamingMediaManagerModule], + [self sdl_screenManagerModule]]]; } + (SDLLogFileModule *)sdl_transportModule { @@ -64,7 +65,7 @@ } + (SDLLogFileModule *)sdl_screenManagerModule { - return [SDLLogFileModule moduleWithName:@"Screen" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager", @"SDLSoftButtonManager", @"SDLScreenManager", @"SDLSoftButtonObject", @"SDLSoftButtonState"]]]; + return [SDLLogFileModule moduleWithName:@"Screen" files:[NSSet setWithArray:@[@"SDLTextAndGraphicManager", @"SDLSoftButtonManager", @"SDLScreenManager", @"SDLSoftButtonObject", @"SDLSoftButtonState", @"SDLMenuManager", @"SDLVoiceCommandManager"]]]; } diff --git a/SmartDeviceLink/SDLMenuCell.h b/SmartDeviceLink/SDLMenuCell.h index 1d68fc9fd..fd78e1ad1 100644 --- a/SmartDeviceLink/SDLMenuCell.h +++ b/SmartDeviceLink/SDLMenuCell.h @@ -16,12 +16,29 @@ typedef void(^SDLMenuCellSelectionHandler)(void); @interface SDLMenuCell : NSObject +/** + The cell's text to be displayed + */ @property (copy, nonatomic, readonly) NSString *title; + +/** + The cell's icon to be displayed + */ @property (strong, nonatomic, readonly, nullable) SDLArtwork *icon; + +/** + The strings the user can say to activate this voice command + */ @property (copy, nonatomic, readonly, nullable) NSArray<NSString *> *voiceCommands; + +/** + The handler that will be called when the command is activated + */ @property (copy, nonatomic, readonly, nullable) SDLMenuCellSelectionHandler handler; -// Note that if this is non-nil, the icon and handler will be ignored. +/** + If this is non-nil, this cell will be a sub-menu button, displaying the subcells in a menu when pressed. + */ @property (copy, nonatomic, readonly, nullable) NSArray<SDLMenuCell *> *subCells; - (instancetype)initWithTitle:(NSString *)title icon:(nullable SDLArtwork *)icon voiceCommands:(nullable NSArray<NSString *> *)voiceCommands handler:(SDLMenuCellSelectionHandler)handler; diff --git a/SmartDeviceLink/SDLMenuManager.h b/SmartDeviceLink/SDLMenuManager.h index 07c57511b..acf1aff99 100644 --- a/SmartDeviceLink/SDLMenuManager.h +++ b/SmartDeviceLink/SDLMenuManager.h @@ -28,7 +28,6 @@ typedef void(^SDLMenuUpdateCompletionHandler)(NSError *__nullable error); - (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager; @property (copy, nonatomic) NSArray<SDLMenuCell *> *menuCells; -@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *voiceCommands; @end diff --git a/SmartDeviceLink/SDLMenuManager.m b/SmartDeviceLink/SDLMenuManager.m index 6343867cb..8ad3e1ca1 100644 --- a/SmartDeviceLink/SDLMenuManager.m +++ b/SmartDeviceLink/SDLMenuManager.m @@ -40,12 +40,6 @@ NS_ASSUME_NONNULL_BEGIN @end -@interface SDLVoiceCommand() - -@property (assign, nonatomic) UInt32 commandId; - -@end - @interface SDLMenuManager() @property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; @@ -64,6 +58,7 @@ NS_ASSUME_NONNULL_BEGIN @end UInt32 const ParentIdNotFound = UINT32_MAX; +UInt32 const MenuCellIdMin = 0; @implementation SDLMenuManager @@ -71,9 +66,8 @@ UInt32 const ParentIdNotFound = UINT32_MAX; self = [super init]; if (!self) { return nil; } - _lastMenuId = 0; + _lastMenuId = MenuCellIdMin; _menuCells = @[]; - _voiceCommands = @[]; _oldMenuCells = @[]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_registerResponse:) name:SDLDidReceiveRegisterAppInterfaceResponse object:nil]; @@ -124,6 +118,8 @@ UInt32 const ParentIdNotFound = UINT32_MAX; }]; } +#pragma mark Delete Old Menu Items + - (void)sdl_sendDeleteCurrentMenu:(SDLMenuUpdateCompletionHandler)completionHandler { if (self.oldMenuCells.count == 0) { completionHandler(nil); @@ -145,6 +141,8 @@ UInt32 const ParentIdNotFound = UINT32_MAX; }]; } +#pragma mark Send New Menu Items + - (void)sdl_sendCurrentMenu:(SDLMenuUpdateCompletionHandler)completionHandler { if (self.menuCells.count == 0) { SDLLogD(@"No main menu to send"); @@ -242,19 +240,6 @@ UInt32 const ParentIdNotFound = UINT32_MAX; [self sdl_updateWithCompletionHandler:nil]; } -- (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { - if (self.currentLevel == nil || [self.currentLevel isEqualToString:SDLHMILevelNone]) { - _waitingOnHMILevelUpdate = YES; - _voiceCommands = voiceCommands; - return; - } - - // Set the ids - _voiceCommands = voiceCommands; - - [self sdl_updateWithCompletionHandler:nil]; -} - #pragma mark - Helpers #pragma mark Artworks @@ -307,16 +292,6 @@ UInt32 const ParentIdNotFound = UINT32_MAX; return [mutableDeletes copy]; } -- (NSArray<SDLDeleteCommand *> *)sdl_deleteCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { - NSMutableArray<SDLDeleteCommand *> *mutableDeletes = [NSMutableArray array]; - for (SDLVoiceCommand *command in self.voiceCommands) { - SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:command.commandId]; - [mutableDeletes addObject:delete]; - } - - return [mutableDeletes copy]; -} - #pragma mark Commands / SubMenu RPCs - (NSArray<SDLRPCRequest *> *)sdl_mainMenuCommandsForCells:(NSArray<SDLMenuCell *> *)cells withArtwork:(BOOL)shouldHaveArtwork { @@ -380,36 +355,17 @@ UInt32 const ParentIdNotFound = UINT32_MAX; return submenu; } -- (SDLAddCommand *)sdl_commandForVoiceCommand:(SDLVoiceCommand *)voiceCommand { - SDLAddCommand *command = [[SDLAddCommand alloc] init]; - command.vrCommands = voiceCommand.voiceCommands; - command.cmdID = @(voiceCommand.commandId); - - return command; -} - #pragma mark - Observers - (void)sdl_commandNotification:(SDLRPCNotificationNotification *)notification { SDLOnCommand *onCommand = (SDLOnCommand *)notification.notification; - NSArray<id> *allCommands = [self.menuCells arrayByAddingObjectsFromArray:self.voiceCommands]; - for (id object in allCommands) { - if ([object isKindOfClass:[SDLMenuCell class]]) { - SDLMenuCell *cell = (SDLMenuCell *)object; - if (onCommand.cmdID.unsignedIntegerValue != cell.cellId) { continue; } + for (SDLMenuCell *cell in self.menuCells) { + if (onCommand.cmdID.unsignedIntegerValue != cell.cellId) { continue; } - cell.handler(); - break; - } else if ([object isKindOfClass:[SDLVoiceCommand class]]) { - SDLVoiceCommand *voiceCommand = (SDLVoiceCommand *)object; - if (onCommand.cmdID.unsignedIntegerValue != voiceCommand.commandId) { continue; } - - voiceCommand.handler(); - break; - } + cell.handler(); + break; } - } - (void)sdl_registerResponse:(SDLRPCResponseNotification *)notification { @@ -432,7 +388,6 @@ UInt32 const ParentIdNotFound = UINT32_MAX; if ([oldHMILevel isEqualToString:SDLHMILevelNone] && ![self.currentLevel isEqualToString:SDLHMILevelNone]) { if (self.waitingOnHMILevelUpdate) { [self setMenuCells:_menuCells]; - [self setVoiceCommands:_voiceCommands]; } else { [self sdl_updateWithCompletionHandler:nil]; } diff --git a/SmartDeviceLink/SDLScreenManager.m b/SmartDeviceLink/SDLScreenManager.m index ec811efa6..ee43dda61 100644 --- a/SmartDeviceLink/SDLScreenManager.m +++ b/SmartDeviceLink/SDLScreenManager.m @@ -12,6 +12,7 @@ #import "SDLMenuManager.h" #import "SDLSoftButtonManager.h" #import "SDLTextAndGraphicManager.h" +#import "SDLVoiceCommandManager.h" NS_ASSUME_NONNULL_BEGIN @@ -20,6 +21,7 @@ NS_ASSUME_NONNULL_BEGIN @property (strong, nonatomic) SDLTextAndGraphicManager *textAndGraphicManager; @property (strong, nonatomic) SDLSoftButtonManager *softButtonManager; @property (strong, nonatomic) SDLMenuManager *menuManager; +@property (strong, nonatomic) SDLVoiceCommandManager *voiceCommandMenuManager; @property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; @property (weak, nonatomic) SDLFileManager *fileManager; @@ -38,6 +40,7 @@ NS_ASSUME_NONNULL_BEGIN _textAndGraphicManager = [[SDLTextAndGraphicManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager]; _softButtonManager = [[SDLSoftButtonManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager]; _menuManager = [[SDLMenuManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager]; + _voiceCommandMenuManager = [[SDLVoiceCommandManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager]; return self; } @@ -116,7 +119,7 @@ NS_ASSUME_NONNULL_BEGIN } - (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { - self.menuManager.voiceCommands = voiceCommands; + self.voiceCommandMenuManager.voiceCommands = voiceCommands; } #pragma mark - Getters @@ -186,7 +189,7 @@ NS_ASSUME_NONNULL_BEGIN } - (NSArray<SDLVoiceCommand *> *)voiceCommands { - return _menuManager.voiceCommands; + return _voiceCommandMenuManager.voiceCommands; } #pragma mark - Begin / End Updates diff --git a/SmartDeviceLink/SDLVoiceCommand.h b/SmartDeviceLink/SDLVoiceCommand.h index 8f4442b4e..252fbaeae 100644 --- a/SmartDeviceLink/SDLVoiceCommand.h +++ b/SmartDeviceLink/SDLVoiceCommand.h @@ -14,7 +14,14 @@ typedef void(^SDLVoiceCommandSelectionHandler)(void); @interface SDLVoiceCommand : NSObject +/** + The strings the user can say to activate this voice command + */ @property (copy, nonatomic, readonly) NSArray<NSString *> *voiceCommands; + +/** + The handler that will be called when the command is activated + */ @property (copy, nonatomic, readonly, nullable) SDLVoiceCommandSelectionHandler handler; - (instancetype)initWithVoiceCommands:(NSArray<NSString *> *)voiceCommands handler:(SDLVoiceCommandSelectionHandler)handler; diff --git a/SmartDeviceLink/SDLVoiceCommandManager.h b/SmartDeviceLink/SDLVoiceCommandManager.h new file mode 100644 index 000000000..e248a4dd1 --- /dev/null +++ b/SmartDeviceLink/SDLVoiceCommandManager.h @@ -0,0 +1,33 @@ +// +// SDLVoiceCommandManager.h +// SmartDeviceLink +// +// Created by Joel Fischer on 4/23/18. +// Copyright © 2018 smartdevicelink. All rights reserved. +// + +#import <Foundation/Foundation.h> + +@class SDLFileManager; +@class SDLVoiceCommand; + +@protocol SDLConnectionManagerType; + +NS_ASSUME_NONNULL_BEGIN + +/** + The handler run when the update has completed + + @param error An error if the update failed and an error occurred + */ +typedef void(^SDLMenuUpdateCompletionHandler)(NSError *__nullable error); + +@interface SDLVoiceCommandManager : NSObject + +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager; + +@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *voiceCommands; + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink/SDLVoiceCommandManager.m b/SmartDeviceLink/SDLVoiceCommandManager.m new file mode 100644 index 000000000..161f7787b --- /dev/null +++ b/SmartDeviceLink/SDLVoiceCommandManager.m @@ -0,0 +1,251 @@ +// +// SDLVoiceCommandManager.m +// SmartDeviceLink +// +// Created by Joel Fischer on 4/23/18. +// Copyright © 2018 smartdevicelink. All rights reserved. +// + +#import "SDLVoiceCommandManager.h" + +#import "SDLAddCommand.h" +#import "SDLConnectionManagerType.h" +#import "SDLDeleteCommand.h" +#import "SDLError.h" +#import "SDLHMILevel.h" +#import "SDLLogMacros.h" +#import "SDLNotificationConstants.h" +#import "SDLOnCommand.h" +#import "SDLOnHMIStatus.h" +#import "SDLRPCNotificationNotification.h" +#import "SDLRPCRequest.h" +#import "SDLVoiceCommand.h" + +NS_ASSUME_NONNULL_BEGIN + +@interface SDLVoiceCommand() + +@property (assign, nonatomic) UInt32 commandId; + +@end + +@interface SDLVoiceCommandManager() + +@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager; +@property (weak, nonatomic) SDLFileManager *fileManager; + +@property (copy, nonatomic, nullable) SDLHMILevel currentLevel; + +@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate; +@property (assign, nonatomic) BOOL hasQueuedUpdate; +@property (assign, nonatomic) BOOL waitingOnHMILevelUpdate; + +@property (assign, nonatomic) UInt32 lastVoiceCommandId; +@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *oldVoiceCommands; + +@end + +UInt32 const VoiceCommandIdMin = 1900000000; + +@implementation SDLVoiceCommandManager + +- (instancetype)init { + self = [super init]; + if (!self) { return nil; } + + _lastVoiceCommandId = VoiceCommandIdMin; + _voiceCommands = @[]; + _oldVoiceCommands = @[]; + + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusNotification:) name:SDLDidChangeHMIStatusNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_commandNotification:) name:SDLDidReceiveCommandNotification object:nil]; + + return self; +} + +- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager { + self = [self init]; + if (!self) { return nil; } + + _connectionManager = connectionManager; + _fileManager = fileManager; + + return self; +} + +#pragma mark - Setters + +- (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { + if (self.currentLevel == nil || [self.currentLevel isEqualToString:SDLHMILevelNone]) { + _waitingOnHMILevelUpdate = YES; + _voiceCommands = voiceCommands; + return; + } + + // Set the ids + self.lastVoiceCommandId = VoiceCommandIdMin; + [self sdl_updateIdsOnVoiceCommands:voiceCommands]; + + _oldVoiceCommands = _voiceCommands; + _voiceCommands = voiceCommands; + + [self sdl_updateWithCompletionHandler:nil]; +} + +#pragma mark - Updating System + +- (void)sdl_updateWithCompletionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler { + if (self.currentLevel == nil || [self.currentLevel isEqualToString:SDLHMILevelNone]) { + return; + } + + if (self.inProgressUpdate != nil) { + // There's an in progress update, we need to put this on hold + self.hasQueuedUpdate = YES; + return; + } + + __weak typeof(self) weakself = self; + [self sdl_sendDeleteCurrentVoiceCommands:^(NSError * _Nullable error) { + [weakself sdl_sendCurrentVoiceCommands:^(NSError * _Nullable error) { + weakself.inProgressUpdate = nil; + + if (completionHandler != nil) { + completionHandler(error); + } + + if (weakself.hasQueuedUpdate) { + [weakself sdl_updateWithCompletionHandler:nil]; + weakself.hasQueuedUpdate = NO; + } + }]; + }]; +} + +#pragma mark Delete Old Menu Items + +- (void)sdl_sendDeleteCurrentVoiceCommands:(SDLMenuUpdateCompletionHandler)completionHandler { + if (self.oldVoiceCommands.count == 0) { + completionHandler(nil); + + return; + } + + NSArray<SDLRPCRequest *> *deleteVoiceCommands = [self sdl_deleteCommandsForVoiceCommands:self.oldVoiceCommands]; + + [self.connectionManager sendRequests:deleteVoiceCommands progressHandler:nil completionHandler:^(BOOL success) { + if (!success) { + SDLLogE(@"Error deleting old voice commands"); + } else { + SDLLogD(@"Finished deleting old voice commands"); + } + + completionHandler(nil); + }]; +} + +#pragma mark Send New Menu Items + +- (void)sdl_sendCurrentVoiceCommands:(SDLMenuUpdateCompletionHandler)completionHandler { + if (self.voiceCommands.count == 0) { + SDLLogD(@"No voice commands to send"); + completionHandler(nil); + + return; + } + + self.inProgressUpdate = [self sdl_addCommandsForVoiceCommands:self.voiceCommands]; + + __block NSMutableDictionary<SDLRPCRequest *, NSError *> *errors = [NSMutableDictionary dictionary]; + __weak typeof(self) weakSelf = self; + [self.connectionManager sendRequests:self.inProgressUpdate progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) { + if (error != nil) { + errors[request] = error; + } + } completionHandler:^(BOOL success) { + if (!success) { + SDLLogE(@"Failed to send main menu commands: %@", errors); + completionHandler([NSError sdl_menuManager_failedToUpdateWithDictionary:errors]); + + return; + } + + SDLLogD(@"Finished updating voice commands"); + weakSelf.oldVoiceCommands = weakSelf.voiceCommands; + completionHandler(nil); + }]; +} + +#pragma mark - Helpers + +#pragma mark IDs + +- (void)sdl_updateIdsOnVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { + for (SDLVoiceCommand *voiceCommand in voiceCommands) { + voiceCommand.commandId = self.lastVoiceCommandId++; + } +} + +#pragma mark Deletes + +- (NSArray<SDLDeleteCommand *> *)sdl_deleteCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { + NSMutableArray<SDLDeleteCommand *> *mutableDeletes = [NSMutableArray array]; + for (SDLVoiceCommand *command in self.voiceCommands) { + SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:command.commandId]; + [mutableDeletes addObject:delete]; + } + + return [mutableDeletes copy]; +} + +#pragma mark Commands + +- (NSArray<SDLAddCommand *> *)sdl_addCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands { + NSMutableArray<SDLAddCommand *> *mutableCommands = [NSMutableArray array]; + for (SDLVoiceCommand *command in voiceCommands) { + [mutableCommands addObject:[self sdl_commandForVoiceCommand:command]]; + } + + return [mutableCommands copy]; +} + +- (SDLAddCommand *)sdl_commandForVoiceCommand:(SDLVoiceCommand *)voiceCommand { + SDLAddCommand *command = [[SDLAddCommand alloc] init]; + command.vrCommands = voiceCommand.voiceCommands; + command.cmdID = @(voiceCommand.commandId); + + return command; +} + +#pragma mark - Observers + +- (void)sdl_commandNotification:(SDLRPCNotificationNotification *)notification { + SDLOnCommand *onCommand = (SDLOnCommand *)notification.notification; + + for (SDLVoiceCommand *voiceCommand in self.voiceCommands) { + if (onCommand.cmdID.unsignedIntegerValue != voiceCommand.commandId) { continue; } + + voiceCommand.handler(); + break; + } +} + +- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification { + SDLOnHMIStatus *hmiStatus = (SDLOnHMIStatus *)notification.notification; + + SDLHMILevel oldHMILevel = self.currentLevel; + self.currentLevel = hmiStatus.hmiLevel; + + // Auto-send an updated show if we were in NONE and now we are not + if ([oldHMILevel isEqualToString:SDLHMILevelNone] && ![self.currentLevel isEqualToString:SDLHMILevelNone]) { + if (self.waitingOnHMILevelUpdate) { + [self setVoiceCommands:_voiceCommands]; + } else { + [self sdl_updateWithCompletionHandler:nil]; + } + } +} + +@end + +NS_ASSUME_NONNULL_END diff --git a/SmartDeviceLink_Example/Classes/ProxyManager.m b/SmartDeviceLink_Example/Classes/ProxyManager.m index 9d36dbea7..08542ba60 100644 --- a/SmartDeviceLink_Example/Classes/ProxyManager.m +++ b/SmartDeviceLink_Example/Classes/ProxyManager.m @@ -446,6 +446,9 @@ NS_ASSUME_NONNULL_BEGIN } - (void)sdlex_prepareRemoteSystem { + SDLCreateInteractionChoiceSet *choiceSet = [self.class sdlex_createOnlyChoiceInteractionSet]; + [self.sdlManager sendRequest:choiceSet]; + __weak typeof(self) weakself = self; SDLMenuCell *speakCell = [[SDLMenuCell alloc] initWithTitle:@"Speak" icon:[SDLArtwork artworkWithImage:[UIImage imageNamed:@"speak"] asImageFormat:SDLArtworkImageFormatPNG] voiceCommands:@[@"Speak"] handler:^{ [weakself.sdlManager sendRequest:[ProxyManager sdlex_appNameSpeak]]; @@ -467,7 +470,12 @@ NS_ASSUME_NONNULL_BEGIN SDLMenuCell *submenuCell = [[SDLMenuCell alloc] initWithTitle:@"Submenu" subCells:[menuArray copy]]; + SDLVoiceCommand *voiceCommand = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test"] handler:^{ + [ProxyManager sdlex_sendPerformOnlyChoiceInteractionWithManager:weakself.sdlManager]; + }]; + self.sdlManager.screenManager.menu = @[speakCell, interactionSetCell, getVehicleDataCell, submenuCell]; + self.sdlManager.screenManager.voiceCommands = @[voiceCommand]; } |