summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2020-12-09 11:55:56 -0500
committerGitHub <noreply@github.com>2020-12-09 11:55:56 -0500
commit039cf18f1632684117be59cba312630112bd9bd6 (patch)
tree2232a0765a27b9c7ddd3961efa5b87529d19d132
parent361db38c0df94d69269358d55c34cc77a0102dac (diff)
parentb6e606a94ff2e0042d9b5b4865799cfe6c7cde52 (diff)
downloadsdl_ios-039cf18f1632684117be59cba312630112bd9bd6.tar.gz
Merge pull request #1843 from smartdevicelink/feature/issue-1841-voice-command-manager
Refactor Voice Command Manager
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj84
-rw-r--r--SmartDeviceLink/private/SDLError.h1
-rw-r--r--SmartDeviceLink/private/SDLError.m7
-rw-r--r--SmartDeviceLink/private/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/private/SDLVoiceCommandManager.h7
-rw-r--r--SmartDeviceLink/private/SDLVoiceCommandManager.m192
-rw-r--r--SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.h38
-rw-r--r--SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.m227
-rw-r--r--SmartDeviceLink/public/SDLErrorConstants.h3
-rw-r--r--SmartDeviceLink/public/SDLScreenManager.h2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m127
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m249
-rw-r--r--SmartDeviceLinkTests/TestConnectionRequestObject.h23
-rw-r--r--SmartDeviceLinkTests/TestConnectionRequestObject.m23
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestConnectionManager.h8
-rw-r--r--SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m43
17 files changed, 802 insertions, 236 deletions
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 948f32bfb..2773665f2 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -294,6 +294,7 @@
4A402561250134CB0080E159 /* SDLStabilityControlsStatus.h in Headers */ = {isa = PBXBuildFile; fileRef = 4A40255F250134CA0080E159 /* SDLStabilityControlsStatus.h */; settings = {ATTRIBUTES = (Public, ); }; };
4A404C66250BBE11003AB65D /* SDLTextAndGraphicUpdateOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A404C65250BBE11003AB65D /* SDLTextAndGraphicUpdateOperationSpec.m */; };
4A404C68250BBE2B003AB65D /* SDLTextAndGraphicStateSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A404C67250BBE2B003AB65D /* SDLTextAndGraphicStateSpec.m */; };
+ 4A41430D255F0A090039C267 /* TestConnectionRequestObject.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A41430C255F0A090039C267 /* TestConnectionRequestObject.m */; };
4A457DC324A2933E00386CBA /* SDLLifecycleRPCAdapterSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A457DC224A2933E00386CBA /* SDLLifecycleRPCAdapterSpec.m */; };
4A457DD324A3886700386CBA /* SDLLifecycleSyncPDataHandlerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A457DD224A3886700386CBA /* SDLLifecycleSyncPDataHandlerSpec.m */; };
4A457DD524A3C16E00386CBA /* SDLLifecycleMobileHMIStateHandlerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4A457DD424A3C16E00386CBA /* SDLLifecycleMobileHMIStateHandlerSpec.m */; };
@@ -1383,6 +1384,9 @@
4ABB2BA724F850AE0061BF55 /* SDLImage.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB2B9924F850AD0061BF55 /* SDLImage.h */; settings = {ATTRIBUTES = (Public, ); }; };
4ABB2BA824F850AE0061BF55 /* SDLLightState.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABB2B9A24F850AD0061BF55 /* SDLLightState.m */; };
4ABB2BA924F850AE0061BF55 /* SDLImageResolution.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABB2B9B24F850AD0061BF55 /* SDLImageResolution.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 4ABED25B257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 4ABED259257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m */; };
+ 4ABED25C257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 4ABED25A257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h */; };
+ 4AD1F1742559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 4AD1F1732559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m */; };
4AE8A7022537796E000666C0 /* SmartDeviceLink.h in Headers */ = {isa = PBXBuildFile; fileRef = 4AE8A7012537796E000666C0 /* SmartDeviceLink.h */; settings = {ATTRIBUTES = (Public, ); }; };
5D0A9F911F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D0A9F901F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m */; };
5D0A9F931F15560B00CC80DD /* SDLNavigationCapabilitySpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5D0A9F921F15560B00CC80DD /* SDLNavigationCapabilitySpec.m */; };
@@ -2071,6 +2075,8 @@
4A40255F250134CA0080E159 /* SDLStabilityControlsStatus.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLStabilityControlsStatus.h; path = public/SDLStabilityControlsStatus.h; sourceTree = "<group>"; };
4A404C65250BBE11003AB65D /* SDLTextAndGraphicUpdateOperationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLTextAndGraphicUpdateOperationSpec.m; path = DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m; sourceTree = "<group>"; };
4A404C67250BBE2B003AB65D /* SDLTextAndGraphicStateSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLTextAndGraphicStateSpec.m; path = DevAPISpecs/SDLTextAndGraphicStateSpec.m; sourceTree = "<group>"; };
+ 4A41430B255F0A090039C267 /* TestConnectionRequestObject.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TestConnectionRequestObject.h; sourceTree = "<group>"; };
+ 4A41430C255F0A090039C267 /* TestConnectionRequestObject.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TestConnectionRequestObject.m; sourceTree = "<group>"; };
4A457DC224A2933E00386CBA /* SDLLifecycleRPCAdapterSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLLifecycleRPCAdapterSpec.m; path = DevAPISpecs/SDLLifecycleRPCAdapterSpec.m; sourceTree = "<group>"; };
4A457DD224A3886700386CBA /* SDLLifecycleSyncPDataHandlerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLLifecycleSyncPDataHandlerSpec.m; path = DevAPISpecs/SDLLifecycleSyncPDataHandlerSpec.m; sourceTree = "<group>"; };
4A457DD424A3C16E00386CBA /* SDLLifecycleMobileHMIStateHandlerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLLifecycleMobileHMIStateHandlerSpec.m; path = DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m; sourceTree = "<group>"; };
@@ -3166,6 +3172,9 @@
4ABB2B9924F850AD0061BF55 /* SDLImage.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLImage.h; path = public/SDLImage.h; sourceTree = "<group>"; };
4ABB2B9A24F850AD0061BF55 /* SDLLightState.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLLightState.m; path = public/SDLLightState.m; sourceTree = "<group>"; };
4ABB2B9B24F850AD0061BF55 /* SDLImageResolution.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLImageResolution.h; path = public/SDLImageResolution.h; sourceTree = "<group>"; };
+ 4ABED259257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandUpdateOperation.m; path = private/SDLVoiceCommandUpdateOperation.m; sourceTree = "<group>"; };
+ 4ABED25A257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLVoiceCommandUpdateOperation.h; path = private/SDLVoiceCommandUpdateOperation.h; sourceTree = "<group>"; };
+ 4AD1F1732559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLVoiceCommandUpdateOperationSpec.m; path = DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m; sourceTree = "<group>"; };
4AE8A7012537796E000666C0 /* SmartDeviceLink.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SmartDeviceLink.h; path = public/SmartDeviceLink.h; sourceTree = "<group>"; };
4AE8A707253779F9000666C0 /* EAAccessory+OCMock.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "EAAccessory+OCMock.h"; sourceTree = "<group>"; };
5D0A9F901F15550400CC80DD /* SDLSystemCapabilityTypeSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLSystemCapabilityTypeSpec.m; sourceTree = "<group>"; };
@@ -4118,6 +4127,47 @@
path = MessageSpecs;
sourceTree = "<group>";
};
+ 4A32B3E425559D93001FFA26 /* Voice Cammands */ = {
+ isa = PBXGroup;
+ children = (
+ 4A32B3E525559DA4001FFA26 /* Cells */,
+ 4A32B3E625559DAC001FFA26 /* Operations */,
+ 4ABB25A824F7E6E10061BF55 /* SDLVoiceCommandManager.h */,
+ 4ABB25A724F7E6E10061BF55 /* SDLVoiceCommandManager.m */,
+ );
+ name = "Voice Cammands";
+ sourceTree = "<group>";
+ };
+ 4A32B3E525559DA4001FFA26 /* Cells */ = {
+ isa = PBXGroup;
+ children = (
+ 4ABB259124F7E6820061BF55 /* SDLVoiceCommand.h */,
+ 4ABB259024F7E6820061BF55 /* SDLVoiceCommand.m */,
+ );
+ name = Cells;
+ sourceTree = "<group>";
+ };
+ 4A32B3E625559DAC001FFA26 /* Operations */ = {
+ isa = PBXGroup;
+ children = (
+ 4ABED25A257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h */,
+ 4ABED259257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m */,
+ );
+ name = Operations;
+ sourceTree = "<group>";
+ };
+ 4A32B3F325559F37001FFA26 /* Menu */ = {
+ isa = PBXGroup;
+ children = (
+ 5D76751022D907F500E8D71A /* Configuration */,
+ 755F175E229F14F70041B9CB /* Dynamic Menu Update Utilities */,
+ 5D339CEC207C08AB000CC364 /* Cells */,
+ 4ABB25A924F7E6E10061BF55 /* SDLMenuManager.h */,
+ 4ABB25A624F7E6E10061BF55 /* SDLMenuManager.m */,
+ );
+ name = Menu;
+ sourceTree = "<group>";
+ };
4A3BA4D9248E8EBB003E56B8 /* SystemRequest Handler */ = {
isa = PBXGroup;
children = (
@@ -4211,6 +4261,16 @@
name = "Status Manager";
sourceTree = "<group>";
};
+ 4AD1F16A2559952D00637FE1 /* Voice Command */ = {
+ isa = PBXGroup;
+ children = (
+ 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */,
+ 5DAB5F5220989A8300A020C8 /* SDLVoiceCommandSpec.m */,
+ 4AD1F1732559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m */,
+ );
+ name = "Voice Command";
+ sourceTree = "<group>";
+ };
5D0218EB1A8E795700D1BF62 /* UI */ = {
isa = PBXGroup;
children = (
@@ -4382,13 +4442,8 @@
5D339CE5207C0651000CC364 /* Menu */ = {
isa = PBXGroup;
children = (
- 5D76751022D907F500E8D71A /* Configuration */,
- 755F175E229F14F70041B9CB /* Dynamic Menu Update Utilities */,
- 5D339CEC207C08AB000CC364 /* Cells */,
- 4ABB25A924F7E6E10061BF55 /* SDLMenuManager.h */,
- 4ABB25A624F7E6E10061BF55 /* SDLMenuManager.m */,
- 4ABB25A824F7E6E10061BF55 /* SDLVoiceCommandManager.h */,
- 4ABB25A724F7E6E10061BF55 /* SDLVoiceCommandManager.m */,
+ 4A32B3F325559F37001FFA26 /* Menu */,
+ 4A32B3E425559D93001FFA26 /* Voice Cammands */,
);
name = Menu;
sourceTree = "<group>";
@@ -4398,8 +4453,6 @@
children = (
4ABB259424F7E6880061BF55 /* SDLMenuCell.h */,
4ABB259524F7E6880061BF55 /* SDLMenuCell.m */,
- 4ABB259124F7E6820061BF55 /* SDLVoiceCommand.h */,
- 4ABB259024F7E6820061BF55 /* SDLVoiceCommand.m */,
);
name = Cells;
sourceTree = "<group>";
@@ -4440,8 +4493,8 @@
children = (
5D4019B11A76EC350006B0C2 /* Examples */,
5D61FA1D1A84237100846EE7 /* SmartDeviceLink */,
- 5D61FA2C1A84237100846EE7 /* SmartDeviceLinkTests */,
5D4346621E6F38E600B639C6 /* SmartDeviceLinkSwift */,
+ 5D61FA2C1A84237100846EE7 /* SmartDeviceLinkTests */,
5D4019B01A76EC350006B0C2 /* Products */,
);
sourceTree = "<group>";
@@ -6083,6 +6136,7 @@
5DAD5F8220507DED0025624C /* Soft Button */,
88D0E5D42478656B009469AB /* Subscribe Button */,
5DAD5F8320507DF30025624C /* Text and Graphic */,
+ 4AD1F16A2559952D00637FE1 /* Voice Command */,
5DAD5F8420507E1F0025624C /* SDLScreenManagerSpec.m */,
);
name = Screen;
@@ -6153,6 +6207,8 @@
8850DB5F1F4475D30053A48D /* TestMultipleFilesConnectionManager.m */,
5D6035D3202CE4A500A429C9 /* TestMultipleRequestsConnectionManager.h */,
5D6035D4202CE4A500A429C9 /* TestMultipleRequestsConnectionManager.m */,
+ 4A41430B255F0A090039C267 /* TestConnectionRequestObject.h */,
+ 4A41430C255F0A090039C267 /* TestConnectionRequestObject.m */,
);
name = "Connection Manager";
sourceTree = "<group>";
@@ -6443,9 +6499,7 @@
isa = PBXGroup;
children = (
5DF40B25208FA7DE00DD6FDA /* SDLMenuManagerSpec.m */,
- 5DF40B27208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m */,
5DAB5F502098994C00A020C8 /* SDLMenuCellSpec.m */,
- 5DAB5F5220989A8300A020C8 /* SDLVoiceCommandSpec.m */,
752ECDB8228C42E100D945F4 /* SDLMenuRunScoreSpec.m */,
752ECDBA228C532600D945F4 /* SDLMenuUpdateAlgorithmSpec.m */,
5D76751522D920FD00E8D71A /* SDLMenuConfigurationSpec.m */,
@@ -7338,6 +7392,7 @@
4ABB26BA24F7FA1C0061BF55 /* SDLLogConstants.h in Headers */,
4ABB28DB24F82A6A0061BF55 /* SDLOnSystemCapabilityUpdated.h in Headers */,
4ABB269324F7F9060061BF55 /* SDLTimer.h in Headers */,
+ 4ABED25C257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.h in Headers */,
4A8BD2F924F93872000945E3 /* SDLVehicleDataType.h in Headers */,
4ABB279924F7FF0B0061BF55 /* SDLLanguage.h in Headers */,
4ABB285F24F828E00061BF55 /* SDLVehicleDataActiveStatus.h in Headers */,
@@ -7543,7 +7598,7 @@
};
5D61FA1B1A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
- LastSwiftMigration = 1170;
+ LastSwiftMigration = 1210;
};
5D61FA251A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
@@ -7700,6 +7755,7 @@
4ABB264524F7F5340061BF55 /* SDLSystemCapabilityManager.m in Sources */,
4A8BD2B124F935BC000945E3 /* SDLSoftButtonCapabilities.m in Sources */,
4ABB2A5524F847B10061BF55 /* SDLGetInteriorVehicleDataConsentResponse.m in Sources */,
+ 4ABED25B257681ED005BDF61 /* SDLVoiceCommandUpdateOperation.m in Sources */,
4ABB2AA924F847F40061BF55 /* SDLSetAppIconResponse.m in Sources */,
4A8BD2D224F93803000945E3 /* SDLTemplateColorScheme.m in Sources */,
4A8BD3C124F994D5000945E3 /* SDLFileManager.m in Sources */,
@@ -8302,6 +8358,7 @@
162E83181A9BDE8B00906325 /* SDLOnKeyboardInputSpec.m in Sources */,
1EE8C4441F34A1B900FDC2CF /* SDLClimateControlDataSpec.m in Sources */,
162E83701A9BDE8B00906325 /* SDLUpdateTurnListResponseSpec.m in Sources */,
+ 4A41430D255F0A090039C267 /* TestConnectionRequestObject.m in Sources */,
88C23E8822297C6000EA171F /* SDLRPCResponseNotificationSpec.m in Sources */,
162E833B1A9BDE8B00906325 /* SDLSetGlobalPropertiesSpec.m in Sources */,
884AF94C220B3FCC00E22928 /* SDLGetSystemCapabilitySpec.m in Sources */,
@@ -8696,6 +8753,7 @@
162E831E1A9BDE8B00906325 /* SDLOnTBTClientStateSpec.m in Sources */,
162E83351A9BDE8B00906325 /* SDLReadDIDSpec.m in Sources */,
5DF40B28208FDA9700DD6FDA /* SDLVoiceCommandManagerSpec.m in Sources */,
+ 4AD1F1742559957100637FE1 /* SDLVoiceCommandUpdateOperationSpec.m in Sources */,
88B3BFA020DA8FD000943565 /* SDLFuelTypeSpec.m in Sources */,
162E836F1A9BDE8B00906325 /* SDLUnsubscribeVehicleDataResponseSpec.m in Sources */,
162E82DB1A9BDE8B00906325 /* SDLECallConfirmationStatusSpec.m in Sources */,
diff --git a/SmartDeviceLink/private/SDLError.h b/SmartDeviceLink/private/SDLError.h
index b7fb5997c..fae799fe9 100644
--- a/SmartDeviceLink/private/SDLError.h
+++ b/SmartDeviceLink/private/SDLError.h
@@ -55,6 +55,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Menu Manager
+ (NSError *)sdl_menuManager_failedToUpdateWithDictionary:(NSDictionary *)userInfo;
++ (NSError *)sdl_voiceCommandManager_pendingUpdateSuperseded;
#pragma mark Choice Set Manager
diff --git a/SmartDeviceLink/private/SDLError.m b/SmartDeviceLink/private/SDLError.m
index 7000d49b8..a54cee83e 100644
--- a/SmartDeviceLink/private/SDLError.m
+++ b/SmartDeviceLink/private/SDLError.m
@@ -262,6 +262,13 @@ NS_ASSUME_NONNULL_BEGIN
return [NSError errorWithDomain:SDLErrorDomainMenuManager code:SDLMenuManagerErrorRPCsFailed userInfo:userInfo];
}
++ (NSError *)sdl_voiceCommandManager_pendingUpdateSuperseded {
+ return [NSError errorWithDomain:SDLErrorDomainMenuManager code:SDLMenuManagerErrorPendingUpdateSuperseded userInfo:@{
+ NSLocalizedDescriptionKey: @"Voice Command Manager error",
+ NSLocalizedFailureReasonErrorKey: @"Voice command operation was cancelled because it was superseded by another update"
+ }];
+}
+
#pragma mark Choice Set Manager
+ (NSError *)sdl_choiceSetManager_choicesDeletedBeforePresentation:(NSDictionary *)userInfo {
diff --git a/SmartDeviceLink/private/SDLLogFileModuleMap.m b/SmartDeviceLink/private/SDLLogFileModuleMap.m
index 403a82258..29834a1d0 100644
--- a/SmartDeviceLink/private/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/private/SDLLogFileModuleMap.m
@@ -130,7 +130,7 @@
}
+ (SDLLogFileModule *)sdl_screenManagerMenuModule {
- return [SDLLogFileModule moduleWithName:@"Screen/Menu" files:[NSSet setWithArray:@[@"SDLMenuManager", @"SDLVoiceCommandManager"]]];
+ return [SDLLogFileModule moduleWithName:@"Screen/Menu" files:[NSSet setWithArray:@[@"SDLMenuManager", @"SDLVoiceCommandManager", @"SDLVoiceCommandUpdateOperation"]]];
}
+ (SDLLogFileModule *)sdl_screenManagerChoiceSetModule {
diff --git a/SmartDeviceLink/private/SDLVoiceCommandManager.h b/SmartDeviceLink/private/SDLVoiceCommandManager.h
index 8c3f3f78f..1bd9a6567 100644
--- a/SmartDeviceLink/private/SDLVoiceCommandManager.h
+++ b/SmartDeviceLink/private/SDLVoiceCommandManager.h
@@ -15,13 +15,6 @@
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;
diff --git a/SmartDeviceLink/private/SDLVoiceCommandManager.m b/SmartDeviceLink/private/SDLVoiceCommandManager.m
index e9bd3d5e6..fc0048736 100644
--- a/SmartDeviceLink/private/SDLVoiceCommandManager.m
+++ b/SmartDeviceLink/private/SDLVoiceCommandManager.m
@@ -21,6 +21,7 @@
#import "SDLRPCNotificationNotification.h"
#import "SDLRPCRequest.h"
#import "SDLVoiceCommand.h"
+#import "SDLVoiceCommandUpdateOperation.h"
NS_ASSUME_NONNULL_BEGIN
@@ -33,15 +34,12 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLVoiceCommandManager()
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
-@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
-@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
-
-@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
-@property (assign, nonatomic) BOOL hasQueuedUpdate;
+@property (copy, nonatomic, nullable) SDLHMILevel currentLevel;
@property (assign, nonatomic) UInt32 lastVoiceCommandId;
-@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *oldVoiceCommands;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *currentVoiceCommands;
@end
@@ -54,8 +52,9 @@ UInt32 const VoiceCommandIdMin = 1900000000;
if (!self) { return nil; }
_lastVoiceCommandId = VoiceCommandIdMin;
+ _transactionQueue = [self sdl_newTransactionQueue];
_voiceCommands = @[];
- _oldVoiceCommands = @[];
+ _currentVoiceCommands = @[];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusNotification:) name:SDLDidChangeHMIStatusNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_commandNotification:) name:SDLDidReceiveCommandNotification object:nil];
@@ -75,120 +74,70 @@ UInt32 const VoiceCommandIdMin = 1900000000;
- (void)stop {
_lastVoiceCommandId = VoiceCommandIdMin;
_voiceCommands = @[];
- _oldVoiceCommands = @[];
+ _currentVoiceCommands = @[];
+ _transactionQueue = [self sdl_newTransactionQueue];
+
+ _currentLevel = nil;
+}
+
+- (NSOperationQueue *)sdl_newTransactionQueue {
+ NSOperationQueue *queue = [[NSOperationQueue alloc] init];
+ queue.name = @"SDLVoiceCommandManager Transaction Queue";
+ queue.maxConcurrentOperationCount = 1;
+ queue.qualityOfService = NSQualityOfServiceUserInitiated;
+ queue.suspended = YES;
- _waitingOnHMIUpdate = NO;
- _currentHMILevel = nil;
- _inProgressUpdate = nil;
- _hasQueuedUpdate = NO;
+ return queue;
+}
+
+/// Suspend the queue if the HMI level is NONE since we want to delay sending RPCs until we're in non-NONE
+- (void)sdl_updateTransactionQueueSuspended {
+ if ([self.currentLevel isEqualToEnum:SDLHMILevelNone]) {
+ SDLLogD(@"Suspending the transaction queue. Current HMI level is NONE: %@", ([self.currentLevel isEqualToEnum:SDLHMILevelNone] ? @"YES" : @"NO"));
+ self.transactionQueue.suspended = YES;
+ } else {
+ SDLLogD(@"Starting the transaction queue");
+ self.transactionQueue.suspended = NO;
+ }
}
#pragma mark - Setters
- (void)setVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
- if (self.currentHMILevel == nil || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
- self.waitingOnHMIUpdate = YES;
+ if (voiceCommands == self.voiceCommands) {
+ SDLLogD(@"New voice commands are equal to the existing voice commands, skipping...");
return;
}
- self.waitingOnHMIUpdate = NO;
-
// Set the ids
- self.lastVoiceCommandId = VoiceCommandIdMin;
[self sdl_updateIdsOnVoiceCommands:voiceCommands];
- _oldVoiceCommands = _voiceCommands;
+ // Set the new voice commands internally
_voiceCommands = voiceCommands;
- [self sdl_updateWithCompletionHandler:nil];
-}
-
-#pragma mark - Updating System
-
-- (void)sdl_updateWithCompletionHandler:(nullable SDLMenuUpdateCompletionHandler)completionHandler {
- if (self.currentHMILevel == nil || [self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
- self.waitingOnHMIUpdate = YES;
- 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;
- }
- }];
+ // Create the operation, cancel previous ones and set this one
+ __weak typeof(self) weakSelf = self;
+ SDLVoiceCommandUpdateOperation *updateOperation = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:self.connectionManager pendingVoiceCommands:voiceCommands oldVoiceCommands:_currentVoiceCommands updateCompletionHandler:^(NSArray<SDLVoiceCommand *> *newCurrentVoiceCommands, NSError * _Nullable error) {
+ weakSelf.currentVoiceCommands = newCurrentVoiceCommands;
+ [weakSelf sdl_updatePendingOperationsWithNewCurrentVoiceCommands:newCurrentVoiceCommands];
}];
-}
-
-#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.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);
- }];
+ [self.transactionQueue cancelAllOperations];
+ [self.transactionQueue addOperation:updateOperation];
}
-#pragma mark Send New Menu Items
-
-- (void)sdl_sendCurrentVoiceCommands:(SDLMenuUpdateCompletionHandler)completionHandler {
- if (self.voiceCommands.count == 0) {
- SDLLogD(@"No voice commands to send");
- completionHandler(nil);
+/// Update currently pending operations with a new set of "current" voice commands (the current state of the head unit) based on a previous completed operation
+/// @param currentVoiceCommands The new current voice commands
+- (void)sdl_updatePendingOperationsWithNewCurrentVoiceCommands:(NSArray<SDLVoiceCommand *> *)currentVoiceCommands {
+ for (NSOperation *operation in self.transactionQueue.operations) {
+ if (operation.isExecuting) { continue; }
- return;
+ SDLVoiceCommandUpdateOperation *updateOp = (SDLVoiceCommandUpdateOperation *)operation;
+ updateOp.oldVoiceCommands = currentVoiceCommands;
}
-
- 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 {
@@ -197,37 +146,6 @@ UInt32 const VoiceCommandIdMin = 1900000000;
}
}
-#pragma mark Deletes
-
-- (NSArray<SDLDeleteCommand *> *)sdl_deleteCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
- NSMutableArray<SDLDeleteCommand *> *mutableDeletes = [NSMutableArray array];
- for (SDLVoiceCommand *command in 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 {
@@ -243,22 +161,12 @@ UInt32 const VoiceCommandIdMin = 1900000000;
- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification {
SDLOnHMIStatus *hmiStatus = (SDLOnHMIStatus *)notification.notification;
-
if (hmiStatus.windowID != nil && hmiStatus.windowID.integerValue != SDLPredefinedWindowsDefaultWindow) {
return;
}
-
- SDLHMILevel oldHMILevel = self.currentHMILevel;
- self.currentHMILevel = hmiStatus.hmiLevel;
-
- // Auto-send an updated show if we were in NONE and now we are not
- if ([oldHMILevel isEqualToEnum:SDLHMILevelNone] && ![self.currentHMILevel isEqualToEnum:SDLHMILevelNone]) {
- if (self.waitingOnHMIUpdate) {
- [self setVoiceCommands:_voiceCommands];
- } else {
- [self sdl_updateWithCompletionHandler:nil];
- }
- }
+
+ self.currentLevel = hmiStatus.hmiLevel;
+ [self sdl_updateTransactionQueueSuspended];
}
@end
diff --git a/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.h b/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.h
new file mode 100644
index 000000000..ec0cf86ec
--- /dev/null
+++ b/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.h
@@ -0,0 +1,38 @@
+//
+// SDLVoiceCommandUpdateOperation.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 11/6/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAsynchronousOperation.h"
+
+@class SDLVoiceCommand;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// A handler run when the operation completes, containing the new voice commands that are available on the head unit
+///
+/// @param newCurrentVoiceCommands The voice commands currently present on the head unit
+/// @param error The error if one occurred
+typedef void(^SDLVoiceCommandUpdateCompletionHandler)(NSArray<SDLVoiceCommand *> *newCurrentVoiceCommands, NSError *__nullable error);
+
+/// An operation that handles changing voice commands on the head unit
+@interface SDLVoiceCommandUpdateOperation : SDLAsynchronousOperation
+
+/// The voice commands currently on the head unit
+@property (strong, nonatomic, nullable) NSArray<SDLVoiceCommand *> *oldVoiceCommands;
+
+/// Initialize a voice command update operation
+/// @param connectionManager The connection manager for sending RPCs
+/// @param pendingVoiceCommands The voice commands that should be on the head unit when this operation completes
+/// @param oldVoiceCommands The voice commands currently on the head unit
+/// @param completionHandler A handler called right before the update operation finishes
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager pendingVoiceCommands:(NSArray<SDLVoiceCommand *> *)pendingVoiceCommands oldVoiceCommands:(NSArray<SDLVoiceCommand *> *)oldVoiceCommands updateCompletionHandler:(SDLVoiceCommandUpdateCompletionHandler)completionHandler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.m b/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.m
new file mode 100644
index 000000000..184615d63
--- /dev/null
+++ b/SmartDeviceLink/private/SDLVoiceCommandUpdateOperation.m
@@ -0,0 +1,227 @@
+//
+// SDLVoiceCommandUpdateOperation.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 11/6/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLVoiceCommandUpdateOperation.h"
+
+#import "SDLAddCommand.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDeleteCommand.h"
+#import "SDLError.h"
+#import "SDLLogMacros.h"
+#import "SDLVoiceCommand.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLVoiceCommand()
+
+@property (assign, nonatomic) UInt32 commandId;
+
+@end
+
+@interface SDLVoiceCommandUpdateOperation ()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *pendingVoiceCommands;
+@property (strong, nonatomic) NSMutableArray<SDLVoiceCommand *> *currentVoiceCommands;
+@property (copy, nonatomic) SDLVoiceCommandUpdateCompletionHandler completionHandler;
+
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+@end
+
+@implementation SDLVoiceCommandUpdateOperation
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager pendingVoiceCommands:(NSArray<SDLVoiceCommand *> *)pendingVoiceCommands oldVoiceCommands:(NSArray<SDLVoiceCommand *> *)oldVoiceCommands updateCompletionHandler:(SDLVoiceCommandUpdateCompletionHandler)completionHandler {
+ self = [self init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _pendingVoiceCommands = pendingVoiceCommands;
+ _oldVoiceCommands = oldVoiceCommands;
+ _currentVoiceCommands = oldVoiceCommands.mutableCopy;
+ _completionHandler = completionHandler;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+ if (self.isCancelled) {
+ [self finishOperation];
+ return;
+ }
+
+ __weak typeof(self) weakSelf = self;
+ [self sdl_sendDeleteCurrentVoiceCommands:^{
+ // If the operation has been canceled, then don't send the new commands and finish the operation
+ if (self.isCancelled) {
+ [weakSelf finishOperation];
+ return;
+ }
+
+ // Send the new commands
+ [weakSelf sdl_sendCurrentVoiceCommands:^{
+ [weakSelf finishOperation];
+ }];
+ }];
+}
+
+#pragma mark - Sending RPCs
+
+/// Send DeleteCommand RPCs for voice commands that should be deleted
+/// @param completionHandler A handler called when all DeleteCommands have completed
+- (void)sdl_sendDeleteCurrentVoiceCommands:(void(^)(void))completionHandler {
+ if (self.oldVoiceCommands.count == 0) {
+ SDLLogD(@"No voice commands to delete");
+ return completionHandler();
+ }
+
+ NSArray<SDLDeleteCommand *> *deleteVoiceCommands = [self sdl_deleteCommandsForVoiceCommands:self.oldVoiceCommands];
+ __block NSMutableDictionary<SDLDeleteCommand *, NSError *> *errors = [NSMutableDictionary dictionary];
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendRequests:deleteVoiceCommands progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ // Add the request to the error dict or remove it from the current voice commands array
+ if (error != nil) {
+ errors[request] = error;
+ } else {
+ [weakSelf sdl_removeCurrentVoiceCommandWithCommandId:((SDLDeleteCommand *)request).cmdID.unsignedIntValue];
+ }
+
+ SDLLogV(@"Deleting voice commands progress: %f", percentComplete);
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send voice commands: %@", errors.allKeys);
+ SDLLogE(@"Failure reasons: %@", errors.allValues);
+ weakSelf.internalError = [NSError sdl_menuManager_failedToUpdateWithDictionary:errors];
+ return completionHandler();
+ }
+
+ SDLLogD(@"Finished deleting old voice commands");
+ return completionHandler();
+ }];
+}
+
+/// Send AddCommand RPCs for voice commands that should be added
+/// @param completionHandler A handler called when all AddCommands have completed
+- (void)sdl_sendCurrentVoiceCommands:(void(^)(void))completionHandler {
+ if (self.pendingVoiceCommands.count == 0) {
+ SDLLogD(@"No voice commands to send");
+ return completionHandler();
+ }
+
+ NSArray<SDLAddCommand *> *addCommandsToSend = [self sdl_addCommandsForVoiceCommands:self.pendingVoiceCommands];
+ __block NSMutableDictionary<SDLAddCommand *, NSError *> *errors = [NSMutableDictionary dictionary];
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendRequests:addCommandsToSend progressHandler:^(__kindof SDLRPCRequest * _Nonnull request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error, float percentComplete) {
+ // Add the request to the error dict or add it to the current voice commands array
+ if (error != nil) {
+ errors[request] = error;
+ } else {
+ [weakSelf.currentVoiceCommands addObject:[weakSelf sdl_pendingVoiceCommandWithCommandId:((SDLAddCommand *)request).cmdID.unsignedIntValue]];
+ }
+
+ SDLLogV(@"Sending voice commands progress: %f", percentComplete);
+ } completionHandler:^(BOOL success) {
+ if (!success) {
+ SDLLogE(@"Failed to send voice commands: %@", errors.allKeys);
+ SDLLogE(@"Failure reasons: %@", errors.allValues);
+ weakSelf.internalError = [NSError sdl_menuManager_failedToUpdateWithDictionary:errors];
+ return completionHandler();
+ }
+
+ SDLLogD(@"Finished updating voice commands");
+ return completionHandler();
+ }];
+}
+
+#pragma mark - Helpers
+#pragma mark Deletes
+
+/// Create DeleteCommand RPCs for passed voice commands
+/// @param voiceCommands The voice commands that should be deleted
+/// @return The DeleteCommand RPCs for the passed voice commands
+- (NSArray<SDLDeleteCommand *> *)sdl_deleteCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ NSMutableArray<SDLDeleteCommand *> *mutableDeletes = [NSMutableArray array];
+ for (SDLVoiceCommand *command in voiceCommands) {
+ SDLDeleteCommand *delete = [[SDLDeleteCommand alloc] initWithId:command.commandId];
+ [mutableDeletes addObject:delete];
+ }
+
+ return [mutableDeletes copy];
+}
+
+#pragma mark Commands
+
+/// Create AddCommand RPCs for passed voice commands
+/// @param voiceCommands The voice commands that should be added
+/// @return The AddCommand RPCs for the passed voice commands
+- (NSArray<SDLAddCommand *> *)sdl_addCommandsForVoiceCommands:(NSArray<SDLVoiceCommand *> *)voiceCommands {
+ NSMutableArray<SDLAddCommand *> *mutableCommands = [NSMutableArray array];
+ for (SDLVoiceCommand *voiceCommand in voiceCommands) {
+ SDLAddCommand *command = [[SDLAddCommand alloc] init];
+ command.vrCommands = voiceCommand.voiceCommands;
+ command.cmdID = @(voiceCommand.commandId);
+
+ [mutableCommands addObject:command];
+ }
+
+ return [mutableCommands copy];
+}
+
+#pragma mark - Managing list of commands on head unit
+
+/// Remove a voice command from the array of voice commands on the head unit based on a command id
+/// @param commandId The command id to use to remove a voice command
+- (void)sdl_removeCurrentVoiceCommandWithCommandId:(UInt32)commandId {
+ for (SDLVoiceCommand *voiceCommand in self.currentVoiceCommands) {
+ if (voiceCommand.commandId == commandId) {
+ [self.currentVoiceCommands removeObject:voiceCommand];
+ break;
+ }
+ }
+}
+
+/// Find a voice command in the pending voice command array based on a command id
+/// @param commandId The command id to use to find a voice command
+- (nullable SDLVoiceCommand *)sdl_pendingVoiceCommandWithCommandId:(UInt32)commandId {
+ for (SDLVoiceCommand *voiceCommand in self.pendingVoiceCommands) {
+ if (voiceCommand.commandId == commandId) {
+ return voiceCommand;
+ }
+ }
+
+ return nil;
+}
+
+#pragma mark - Operation Overrides
+
+- (void)finishOperation {
+ SDLLogV(@"Finishing voice command update operation");
+ if (self.isCancelled) {
+ self.internalError = [NSError sdl_voiceCommandManager_pendingUpdateSuperseded];
+ }
+
+ self.completionHandler(self.currentVoiceCommands.copy, self.error);
+ [super finishOperation];
+}
+
+- (nullable NSString *)name {
+ return @"com.sdl.voicecommand.update";
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLErrorConstants.h b/SmartDeviceLink/public/SDLErrorConstants.h
index ea594ceff..12bb1c235 100644
--- a/SmartDeviceLink/public/SDLErrorConstants.h
+++ b/SmartDeviceLink/public/SDLErrorConstants.h
@@ -186,7 +186,8 @@ typedef NS_ENUM(NSInteger, SDLSubscribeButtonManagerError) {
*/
typedef NS_ENUM(NSInteger, SDLMenuManagerError) {
/// Sending menu-related RPCs returned an error from the remote system
- SDLMenuManagerErrorRPCsFailed = -1
+ SDLMenuManagerErrorRPCsFailed = -1,
+ SDLMenuManagerErrorPendingUpdateSuperseded = -2
};
/// Errors associated with Choice Set class
diff --git a/SmartDeviceLink/public/SDLScreenManager.h b/SmartDeviceLink/public/SDLScreenManager.h
index bd1c9597f..5da5a1cbe 100644
--- a/SmartDeviceLink/public/SDLScreenManager.h
+++ b/SmartDeviceLink/public/SDLScreenManager.h
@@ -197,6 +197,8 @@ If set to `SDLDynamicMenuUpdatesModeForceOff`, menu updates will work the legacy
/**
The current list of voice commands available for the user to speak and be recognized by the IVI's voice recognition engine.
+
+ @warning May not be an empty string
*/
@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *voiceCommands;
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m
index f2ff80c99..505edd4d5 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m
@@ -71,7 +71,7 @@ describe(@"SDLLifecycleMobileHMIStateHandler tests", ^{
describe(@"after the manager is stopped", ^{
beforeEach(^{
- [mockConnectionManager.receivedRequests removeAllObjects];
+ [mockConnectionManager reset];
[testManager stop];
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
index c5e275843..8b808273e 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
@@ -7,10 +7,20 @@
#import "SDLDeleteCommand.h"
#import "SDLFileManager.h"
#import "SDLHMILevel.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLPredefinedWindows.h"
+#import "SDLRPCNotificationNotification.h"
#import "SDLVoiceCommand.h"
#import "SDLVoiceCommandManager.h"
+#import "SDLVoiceCommandUpdateOperation.h"
#import "TestConnectionManager.h"
+@interface SDLVoiceCommandUpdateOperation ()
+
+@property (strong, nonatomic) NSMutableArray<SDLVoiceCommand *> *currentVoiceCommands;
+
+@end
+
@interface SDLVoiceCommand()
@property (assign, nonatomic) UInt32 commandId;
@@ -21,14 +31,11 @@
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
-@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
-@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
-
-@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
-@property (assign, nonatomic) BOOL hasQueuedUpdate;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+@property (copy, nonatomic, nullable) SDLHMILevel currentLevel;
@property (assign, nonatomic) UInt32 lastVoiceCommandId;
-@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *oldVoiceCommands;
+@property (copy, nonatomic) NSArray<SDLVoiceCommand *> *currentVoiceCommands;
@end
@@ -42,104 +49,126 @@ describe(@"voice command manager", ^{
__block SDLVoiceCommand *testVoiceCommand = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test 1"] handler:^{}];
__block SDLVoiceCommand *testVoiceCommand2 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"Test 2"] handler:^{}];
+ __block SDLOnHMIStatus *newHMIStatus = [[SDLOnHMIStatus alloc] init];
+ __block NSArray<SDLVoiceCommand *> *testVCArray = nil;
beforeEach(^{
+ testVCArray = @[testVoiceCommand];
mockConnectionManager = [[TestConnectionManager alloc] init];
testManager = [[SDLVoiceCommandManager alloc] initWithConnectionManager:mockConnectionManager];
});
+ // should instantiate correctly
it(@"should instantiate correctly", ^{
expect(testManager.connectionManager).to(equal(mockConnectionManager));
-
+ expect(testManager.currentLevel).to(beNil());
expect(testManager.voiceCommands).to(beEmpty());
- expect(testManager.connectionManager).to(equal(mockConnectionManager));
- expect(testManager.currentHMILevel).to(beNil());
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.waitingOnHMIUpdate).to(beFalse());
expect(testManager.lastVoiceCommandId).to(equal(VoiceCommandIdMin));
- expect(testManager.oldVoiceCommands).to(beEmpty());
+ expect(testManager.currentVoiceCommands).to(beEmpty());
+ expect(testManager.transactionQueue).toNot(beNil());
});
+ // updating voice commands before HMI is ready
describe(@"updating voice commands before HMI is ready", ^{
+
+ // when in HMI NONE
context(@"when in HMI NONE", ^{
beforeEach(^{
- testManager.currentHMILevel = SDLHMILevelNone;
+ newHMIStatus.hmiLevel = SDLHMILevelNone;
+ newHMIStatus.windowID = @(SDLPredefinedWindowsDefaultWindow);
+
+ SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:newHMIStatus];
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
});
it(@"should not update", ^{
testManager.voiceCommands = @[testVoiceCommand];
- expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.transactionQueue.isSuspended).to(beTrue());
+ expect(testManager.transactionQueue.operationCount).to(equal(1));
});
});
+ // when no HMI level has been received
context(@"when no HMI level has been received", ^{
- beforeEach(^{
- testManager.currentHMILevel = nil;
- });
-
it(@"should not update", ^{
testManager.voiceCommands = @[testVoiceCommand];
- expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.transactionQueue.isSuspended).to(beTrue());
+ expect(testManager.transactionQueue.operationCount).to(equal(1));
});
});
});
- describe(@"updating voice commands", ^{
+ // updating voice commands
+ describe(@"when voice commands are set", ^{
beforeEach(^{
- testManager.currentHMILevel = SDLHMILevelFull;
+ newHMIStatus.hmiLevel = SDLHMILevelFull;
+ newHMIStatus.windowID = @(SDLPredefinedWindowsDefaultWindow);
+
+ SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:newHMIStatus];
+ [[NSNotificationCenter defaultCenter] postNotification:notification];
+
+ testManager.voiceCommands = testVCArray;
});
+ // should properly update a command
it(@"should properly update a command", ^{
- testManager.voiceCommands = @[testVoiceCommand];
+ expect(testManager.voiceCommands.firstObject.commandId).to(equal(VoiceCommandIdMin));
+ expect(testManager.transactionQueue.isSuspended).to(beFalse());
+ expect(testManager.transactionQueue.operations).to(haveCount(1));
+ });
- NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLDeleteCommand class]];
- NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
- expect(deletes).to(beEmpty());
+ // when new voice commands is identical to the existing ones
+ describe(@"when new voice commands is identical to the existing ones", ^{
+ beforeEach(^{
+ testManager.voiceCommands = testVCArray;
+ });
- NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass: %@", [SDLAddCommand class]];
- NSArray *add = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
- expect(add).toNot(beEmpty());
+ // should only have one operation
+ it(@"should only have one operation", ^{
+ expect(testManager.transactionQueue.operations).to(haveCount(1));
+ });
});
- context(@"when a menu already exists", ^{
+ // when new voice commands are set
+ describe(@"when new voice commands are set", ^{
beforeEach(^{
- testManager.voiceCommands = @[testVoiceCommand];
- [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; // Add
-
testManager.voiceCommands = @[testVoiceCommand2];
- [mockConnectionManager respondToLastMultipleRequestsWithSuccess:YES]; // Delete
});
- it(@"should send deletes first", ^{
- NSPredicate *deleteCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLDeleteCommand class]];
- NSArray *deletes = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:deleteCommandPredicate];
+ // should queue another operation
+ it(@"should queue another operation", ^{
+ expect(testManager.transactionQueue.operations).to(haveCount(2));
+ });
+
+ // when the first operation finishes and updates the current voice commands
+ describe(@"when the first operation finishes and updates the current voice commands", ^{
+ beforeEach(^{
+ SDLVoiceCommandUpdateOperation *firstOp = testManager.transactionQueue.operations[0];
+ firstOp.currentVoiceCommands = [@[testVoiceCommand2] mutableCopy];
+ [firstOp finishOperation];
+ });
- NSPredicate *addCommandPredicate = [NSPredicate predicateWithFormat:@"self isMemberOfClass:%@", [SDLAddCommand class]];
- NSArray *adds = [[mockConnectionManager.receivedRequests copy] filteredArrayUsingPredicate:addCommandPredicate];
+ it(@"should update the second operation", ^{
+ SDLVoiceCommandUpdateOperation *secondOp = testManager.transactionQueue.operations[0];
- expect(deletes).to(haveCount(1));
- expect(adds).to(haveCount(2));
+ expect(secondOp.oldVoiceCommands.firstObject).to(equal(testVoiceCommand2));
+ });
});
});
});
- context(@"On disconnects", ^{
+ // on disconnect
+ context(@"on disconnect", ^{
beforeEach(^{
[testManager stop];
});
it(@"should reset correctly", ^{
expect(testManager.connectionManager).to(equal(mockConnectionManager));
-
expect(testManager.voiceCommands).to(beEmpty());
- expect(testManager.connectionManager).to(equal(mockConnectionManager));
- expect(testManager.currentHMILevel).to(beNil());
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.waitingOnHMIUpdate).to(beFalse());
+ expect(testManager.currentLevel).to(beNil());
expect(testManager.lastVoiceCommandId).to(equal(VoiceCommandIdMin));
- expect(testManager.oldVoiceCommands).to(beEmpty());
+ expect(testManager.currentVoiceCommands).to(beEmpty());
});
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m
new file mode 100644
index 000000000..ca8c9cfbd
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m
@@ -0,0 +1,249 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLAddCommand.h"
+#import "SDLAddCommandResponse.h"
+#import "SDLDeleteCommand.h"
+#import "SDLDeleteCommandResponse.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLHMILevel.h"
+#import "SDLVoiceCommand.h"
+#import "SDLVoiceCommandManager.h"
+#import "SDLVoiceCommandUpdateOperation.h"
+#import "TestConnectionManager.h"
+
+@interface SDLVoiceCommand()
+
+@property (assign, nonatomic) UInt32 commandId;
+
+@end
+
+QuickSpecBegin(SDLVoiceCommandUpdateOperationSpec)
+
+__block SDLDeleteCommandResponse *successDelete = nil;
+__block SDLDeleteCommandResponse *failDelete = nil;
+__block SDLAddCommandResponse *successAdd = nil;
+__block SDLAddCommandResponse *failAdd = nil;
+
+__block SDLVoiceCommand *newVoiceCommand1 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"NewVC1"] handler:^{}];
+__block SDLVoiceCommand *newVoiceCommand2 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"NewVC2"] handler:^{}];
+__block SDLVoiceCommand *oldVoiceCommand1 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"OldVC1"] handler:^{}];
+__block SDLVoiceCommand *oldVoiceCommand2 = [[SDLVoiceCommand alloc] initWithVoiceCommands:@[@"OldVC2"] handler:^{}];
+
+
+describe(@"a voice command operation", ^{
+ __block TestConnectionManager *testConnectionManager = nil;
+ __block SDLVoiceCommandUpdateOperation *testOp = nil;
+
+ __block NSError *callbackError = nil;
+ __block NSArray<SDLVoiceCommand *> *callbackCurrentVoiceCommands = nil;
+
+ beforeEach(^{
+ successDelete = [[SDLDeleteCommandResponse alloc] init];
+ successDelete.success = @YES;
+ successDelete.resultCode = SDLResultSuccess;
+
+ failDelete = [[SDLDeleteCommandResponse alloc] init];
+ failDelete.success = @NO;
+ failDelete.resultCode = SDLResultDisallowed;
+
+ successAdd = [[SDLAddCommandResponse alloc] init];
+ successAdd.success = @YES;
+ successAdd.resultCode = SDLResultSuccess;
+
+ failAdd = [[SDLAddCommandResponse alloc] init];
+ failAdd.success = @NO;
+ failAdd.resultCode = SDLResultDisallowed;
+
+ newVoiceCommand1.commandId = 1;
+ newVoiceCommand2.commandId = 2;
+ oldVoiceCommand1.commandId = 3;
+ oldVoiceCommand2.commandId = 4;
+
+ testConnectionManager = [[TestConnectionManager alloc] init];
+ testOp = nil;
+
+ callbackError = nil;
+ callbackCurrentVoiceCommands = nil;
+ });
+
+ // should have a priority of 'normal'
+ it(@"should have a priority of 'normal'", ^{
+ testOp = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:testConnectionManager pendingVoiceCommands:@[] oldVoiceCommands:@[] updateCompletionHandler:^(NSArray<SDLVoiceCommand *> * _Nonnull newCurrentVoiceCommands, NSError * _Nullable error) {
+ }];
+
+ expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
+ });
+
+ // initializing the operation
+ describe(@"initializing the operation", ^{
+ testOp = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:testConnectionManager pendingVoiceCommands:@[newVoiceCommand1, newVoiceCommand2] oldVoiceCommands:@[oldVoiceCommand1, oldVoiceCommand2] updateCompletionHandler:^(NSArray<SDLVoiceCommand *> * _Nonnull newCurrentVoiceCommands, NSError * _Nullable error) {}];
+
+ expect(testOp.oldVoiceCommands).to(equal(@[oldVoiceCommand1, oldVoiceCommand2]));
+ });
+
+ // starting the operation
+ describe(@"starting the operation", ^{
+
+ // if it starts cancelled
+ context(@"if it starts cancelled", ^{
+ beforeEach(^{
+ testOp = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:testConnectionManager pendingVoiceCommands:@[newVoiceCommand1, newVoiceCommand2] oldVoiceCommands:@[oldVoiceCommand1, oldVoiceCommand2] updateCompletionHandler:^(NSArray<SDLVoiceCommand *> * _Nonnull newCurrentVoiceCommands, NSError * _Nullable error) {
+ callbackCurrentVoiceCommands = newCurrentVoiceCommands;
+ callbackError = error;
+ }];
+ [testOp cancel];
+ [testOp start];
+ });
+
+ it(@"should return immediately with an error", ^{
+ expect(callbackError).toEventuallyNot(beNil());
+ });
+ });
+
+ // if it has voice commands to delete
+ context(@"if it has voice commands to delete", ^{
+ beforeEach(^{
+ testOp = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:testConnectionManager pendingVoiceCommands:@[] oldVoiceCommands:@[oldVoiceCommand1, oldVoiceCommand2] updateCompletionHandler:^(NSArray<SDLVoiceCommand *> * _Nonnull newCurrentVoiceCommands, NSError * _Nullable error) {
+ callbackCurrentVoiceCommands = newCurrentVoiceCommands;
+ callbackError = error;
+ }];
+ [testOp start];
+ });
+
+ // and the delete succeeds
+ context(@"and the delete succeeds", ^{
+ beforeEach(^{
+ SDLDeleteCommandResponse *deleteOld1 = [[SDLDeleteCommandResponse alloc] init];
+ deleteOld1.success = @YES;
+ deleteOld1.resultCode = SDLResultSuccess;
+
+ SDLDeleteCommandResponse *deleteOld2 = [[SDLDeleteCommandResponse alloc] init];
+ deleteOld2.success = @YES;
+ deleteOld2.resultCode = SDLResultSuccess;
+
+ [testConnectionManager respondToRequestWithResponse:deleteOld1 requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:deleteOld2 requestNumber:1 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+ });
+
+ it(@"should update the current voice commands", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(0));
+ expect(callbackError).to(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ // and the delete fails
+ context(@"and the delete fails", ^{
+ beforeEach(^{
+ [testConnectionManager respondToRequestWithResponse:failDelete requestNumber:0 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToRequestWithResponse:failDelete requestNumber:1 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
+ });
+
+ it(@"should update the current voice commands and attempt to send the adds", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(2));
+ expect(callbackError).toNot(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ // and the delete partially fails
+ context(@"and the delete partially fails", ^{
+ beforeEach(^{
+ [testConnectionManager respondToRequestWithResponse:failDelete requestNumber:0 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToRequestWithResponse:successDelete requestNumber:1 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
+ });
+
+ it(@"should update the current voice commands and attempt to send the adds", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(1));
+ expect(callbackError).toNot(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+ });
+
+ context(@"if it doesn't have any voice commands to delete", ^{
+ beforeEach(^{
+ testOp = [[SDLVoiceCommandUpdateOperation alloc] initWithConnectionManager:testConnectionManager pendingVoiceCommands:@[newVoiceCommand1, newVoiceCommand2] oldVoiceCommands:@[] updateCompletionHandler:^(NSArray<SDLVoiceCommand *> * _Nonnull newCurrentVoiceCommands, NSError * _Nullable error) {
+ callbackCurrentVoiceCommands = newCurrentVoiceCommands;
+ callbackError = error;
+ }];
+ [testOp start];
+ });
+
+ context(@"adding voice commands", ^{
+ context(@"and the add succeeds", ^{
+ beforeEach(^{
+ SDLAddCommandResponse *addNew1 = [[SDLAddCommandResponse alloc] init];
+ addNew1.success = @YES;
+ addNew1.resultCode = SDLResultSuccess;
+
+ SDLAddCommandResponse *addNew2 = [[SDLAddCommandResponse alloc] init];
+ addNew2.success = @YES;
+ addNew2.resultCode = SDLResultSuccess;
+
+ [testConnectionManager respondToRequestWithResponse:addNew1 requestNumber:0 error:nil];
+ [testConnectionManager respondToRequestWithResponse:addNew2 requestNumber:1 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:YES];
+ });
+
+ it(@"should update the current voice commands", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(2));
+ expect(callbackError).to(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ context(@"and the add fails", ^{
+ beforeEach(^{
+ SDLAddCommandResponse *addNew1 = [[SDLAddCommandResponse alloc] init];
+ addNew1.success = @NO;
+ addNew1.resultCode = SDLResultDisallowed;
+
+ SDLAddCommandResponse *addNew2 = [[SDLAddCommandResponse alloc] init];
+ addNew2.success = @NO;
+ addNew2.resultCode = SDLResultDisallowed;
+
+ [testConnectionManager respondToRequestWithResponse:addNew1 requestNumber:0 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToRequestWithResponse:addNew2 requestNumber:1 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
+ });
+
+ it(@"should update the current voice commands", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(0));
+ expect(callbackError).toNot(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+
+ context(@"and the add partially fails", ^{
+ beforeEach(^{
+ SDLAddCommandResponse *addNew1 = [[SDLAddCommandResponse alloc] init];
+ addNew1.success = @NO;
+ addNew1.resultCode = SDLResultDisallowed;
+
+ SDLAddCommandResponse *addNew2 = [[SDLAddCommandResponse alloc] init];
+ addNew2.success = @YES;
+ addNew2.resultCode = SDLResultSuccess;
+
+ [testConnectionManager respondToRequestWithResponse:addNew1 requestNumber:0 error:[NSError sdl_lifecycle_failedWithBadResult:SDLResultDisallowed info:nil]];
+ [testConnectionManager respondToRequestWithResponse:addNew2 requestNumber:1 error:nil];
+ [testConnectionManager respondToLastMultipleRequestsWithSuccess:NO];
+ });
+
+ it(@"should update the current voice commands", ^{
+ expect(callbackCurrentVoiceCommands).to(haveCount(1));
+ expect(callbackError).toNot(beNil());
+ expect(testConnectionManager.receivedRequests).to(haveCount(2));
+ });
+ });
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/TestConnectionRequestObject.h b/SmartDeviceLinkTests/TestConnectionRequestObject.h
new file mode 100644
index 000000000..4bc6b5098
--- /dev/null
+++ b/SmartDeviceLinkTests/TestConnectionRequestObject.h
@@ -0,0 +1,23 @@
+//
+// TestConnectionRequestObject.h
+// SmartDeviceLinkTests
+//
+// Created by Joel Fischer on 11/13/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+#import <SmartDeviceLink/SmartDeviceLink.h>
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface TestConnectionRequestObject : NSObject
+
+@property (strong, nonatomic) __kindof SDLRPCMessage *message;
+@property (copy, nonatomic, nullable) SDLResponseHandler responseHandler;
+
+- (instancetype)initWithMessage:(__kindof SDLRPCMessage *)message responseHandler:(nullable SDLResponseHandler)handler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLinkTests/TestConnectionRequestObject.m b/SmartDeviceLinkTests/TestConnectionRequestObject.m
new file mode 100644
index 000000000..a1aa57bc4
--- /dev/null
+++ b/SmartDeviceLinkTests/TestConnectionRequestObject.m
@@ -0,0 +1,23 @@
+//
+// TestConnectionRequestObject.m
+// SmartDeviceLinkTests
+//
+// Created by Joel Fischer on 11/13/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "TestConnectionRequestObject.h"
+
+@implementation TestConnectionRequestObject
+
+- (instancetype)initWithMessage:(__kindof SDLRPCMessage *)message responseHandler:(SDLResponseHandler)handler {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _message = message;
+ _responseHandler = handler;
+
+ return self;
+}
+
+@end
diff --git a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.h b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.h
index 5b07db771..d3dd50a19 100644
--- a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.h
+++ b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.h
@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "SDLConnectionManagerType.h"
#import "SDLNotificationConstants.h"
+#import "TestConnectionRequestObject.h"
NS_ASSUME_NONNULL_BEGIN
@@ -20,12 +21,9 @@ NS_ASSUME_NONNULL_BEGIN
/**
* All received requests. Chronological order. The 0th element will be the first request received; the nth request will be the n+1th request received.
*/
-@property (copy, nonatomic, readonly) NSMutableArray<__kindof SDLRPCMessage *> *receivedRequests;
+@property (copy, nonatomic, readonly) NSMutableArray<__kindof TestConnectionRequestObject *> *receivedRequestObjects;
-/**
- * The block passed for the last request send with sendRequest:withCompletionHandler:
- */
-@property (copy, nonatomic, nullable) SDLResponseHandler lastRequestBlock;
+@property (copy, nonatomic, readonly) NSArray<__kindof SDLRPCMessage *> *receivedRequests;
@property (copy, nonatomic, nullable) NSMutableArray<SDLMultipleRequestCompletionHandler> *multipleCompletionBlocks;
diff --git a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
index ded122d0a..4774f8d09 100644
--- a/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
+++ b/SmartDeviceLinkTests/TestUtilities/TestConnectionManager.m
@@ -21,21 +21,28 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
- _receivedRequests = [NSMutableArray<__kindof SDLRPCMessage *> array];
+ _receivedRequestObjects = [NSMutableArray<TestConnectionRequestObject *> array];
_multipleCompletionBlocks = [NSMutableArray array];
return self;
}
+- (NSArray<__kindof SDLRPCMessage *> *)receivedRequests {
+ NSMutableArray<__kindof SDLRPCMessage *> *requests = [NSMutableArray array];
+ [_receivedRequestObjects enumerateObjectsUsingBlock:^(__kindof TestConnectionRequestObject * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
+ [requests addObject:obj.message];
+ }];
+
+ return [requests copy];
+}
+
- (void)sendConnectionRPC:(__kindof SDLRPCMessage *)rpc {
- [self.receivedRequests addObject:rpc];
+ [self.receivedRequestObjects addObject:[[TestConnectionRequestObject alloc] initWithMessage:rpc responseHandler:nil]];
}
- (void)sendConnectionRequest:(__kindof SDLRPCRequest *)request withResponseHandler:(nullable SDLResponseHandler)handler {
- self.lastRequestBlock = handler;
- SDLRPCRequest *requestRPC = (SDLRPCRequest *)request;
- requestRPC.correlationID = [self test_nextCorrelationID];
- [self.receivedRequests addObject:requestRPC];
+ request.correlationID = [self test_nextCorrelationID];
+ [self.receivedRequestObjects addObject:[[TestConnectionRequestObject alloc] initWithMessage:request responseHandler:handler]];
}
- (void)sendConnectionManagerRequest:(__kindof SDLRPCRequest *)request withResponseHandler:(nullable SDLResponseHandler)handler {
@@ -48,11 +55,11 @@ NS_ASSUME_NONNULL_BEGIN
- (void)sendRequests:(nonnull NSArray<SDLRPCRequest *> *)requests progressHandler:(nullable SDLMultipleAsyncRequestProgressHandler)progressHandler completionHandler:(nullable SDLMultipleRequestCompletionHandler)completionHandler {
[requests enumerateObjectsUsingBlock:^(SDLRPCRequest * _Nonnull request, NSUInteger idx, BOOL * _Nonnull stop) {
- [self sendConnectionRequest:request withResponseHandler:nil];
-
- if (progressHandler != nil) {
- progressHandler(request, nil, nil, (double)idx / (double)requests.count);
- }
+ [self sendConnectionRequest:request withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (progressHandler != nil) {
+ progressHandler(request, response, error, (double)idx / (double)requests.count);
+ }
+ }];
}];
[_multipleCompletionBlocks addObject:completionHandler];
@@ -79,8 +86,9 @@ NS_ASSUME_NONNULL_BEGIN
thisError = error;
}
- if (self.lastRequestBlock != nil) {
- self.lastRequestBlock(self.receivedRequests.lastObject, response, thisError);
+ TestConnectionRequestObject *lastObject = self.receivedRequestObjects.lastObject;
+ if (lastObject.responseHandler != nil) {
+ lastObject.responseHandler((SDLRPCRequest *)lastObject.message, response, thisError);
} else {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Attempted to respond to last request, but there was no last request block" userInfo:nil];
}
@@ -94,8 +102,9 @@ NS_ASSUME_NONNULL_BEGIN
thisError = error;
}
- if (self.lastRequestBlock != nil) {
- self.lastRequestBlock(self.receivedRequests[requestNumber], response, thisError);
+ TestConnectionRequestObject *requestObj = self.receivedRequestObjects[requestNumber];
+ if (requestObj.responseHandler != nil) {
+ requestObj.responseHandler((SDLRPCRequest *)requestObj.message, response, thisError);
} else {
@throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"Attempted to respond to last request, but there was no last request block" userInfo:nil];
}
@@ -110,8 +119,8 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)reset {
- _receivedRequests = [NSMutableArray<__kindof SDLRPCMessage *> array];
- _lastRequestBlock = nil;
+ _receivedRequestObjects = [NSMutableArray<__kindof TestConnectionRequestObject *> array];
+ _multipleCompletionBlocks = [NSMutableArray array];
}