summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2018-04-24 11:54:00 -0400
committerJoel Fischer <joeljfischer@gmail.com>2018-04-24 11:54:00 -0400
commitebd6c03927f4115edfe54aa1c24149a008a17b03 (patch)
tree8b84c2641ba1b46f6c3cd1e9755d3e155eaa1c72
parent109ab29d87178e2c5d620a46380157d7222c25bc (diff)
downloadsdl_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.pbxproj8
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m5
-rw-r--r--SmartDeviceLink/SDLMenuCell.h19
-rw-r--r--SmartDeviceLink/SDLMenuManager.h1
-rw-r--r--SmartDeviceLink/SDLMenuManager.m65
-rw-r--r--SmartDeviceLink/SDLScreenManager.m7
-rw-r--r--SmartDeviceLink/SDLVoiceCommand.h7
-rw-r--r--SmartDeviceLink/SDLVoiceCommandManager.h33
-rw-r--r--SmartDeviceLink/SDLVoiceCommandManager.m251
-rw-r--r--SmartDeviceLink_Example/Classes/ProxyManager.m8
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];
}