summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShinichi Watanabe <swatanabe@xevo.com>2021-01-18 19:11:49 +0900
committerShinichi Watanabe <swatanabe@xevo.com>2021-01-18 19:11:49 +0900
commit760f674634c93273e68680dbc8baae152c709714 (patch)
tree9d796a78466ab76760ebd6e3af8f790a3a3743fd
parentf4938b994ac0a8e4857d838e572696e23c5039ad (diff)
parent18dfd8ae8d239eef2f6984a23cd4f137e1465c55 (diff)
downloadsdl_ios-760f674634c93273e68680dbc8baae152c709714.tar.gz
Merge remote-tracking branch 'upstream/develop' into feature/issue-1553
-rw-r--r--.github/PULL_REQUEST_TEMPLATE.md2
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj84
-rw-r--r--SmartDeviceLink/private/SDLChoiceSetManager.m1
-rw-r--r--SmartDeviceLink/private/SDLError.h1
-rw-r--r--SmartDeviceLink/private/SDLError.m7
-rw-r--r--SmartDeviceLink/private/SDLGlobals.m4
-rw-r--r--SmartDeviceLink/private/SDLLifecycleManager.m3
-rw-r--r--SmartDeviceLink/private/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/private/SDLMenuManager.m17
-rw-r--r--SmartDeviceLink/private/SDLPreloadChoicesOperation.m8
-rw-r--r--SmartDeviceLink/private/SDLSoftButtonManager.m4
-rw-r--r--SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m13
-rw-r--r--SmartDeviceLink/private/SDLSoftButtonTransitionOperation.m3
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicManager.m6
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m24
-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/SDLFileManager.h12
-rw-r--r--SmartDeviceLink/public/SDLFileManager.m19
-rw-r--r--SmartDeviceLink/public/SDLMsgVersion.m2
-rw-r--r--SmartDeviceLink/public/SDLScreenManager.h2
-rw-r--r--SmartDeviceLink/public/SDLShowConstantTBT.h8
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m60
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleMobileHMIStateHandlerSpec.m2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m27
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m22
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m29
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m127
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandUpdateOperationSpec.m249
-rw-r--r--SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m30
-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
-rw-r--r--generator/transformers/common_producer.py37
38 files changed, 1078 insertions, 291 deletions
diff --git a/.github/PULL_REQUEST_TEMPLATE.md b/.github/PULL_REQUEST_TEMPLATE.md
index a980c7629..3a169cd81 100644
--- a/.github/PULL_REQUEST_TEMPLATE.md
+++ b/.github/PULL_REQUEST_TEMPLATE.md
@@ -1,6 +1,6 @@
[Things to note: Pull Requests **must** fix an issue. Discussion about the feature / bug takes place in the issue, discussion of the implementation takes place in the PR. Please also see the [Contributing Guide](https://github.com/smartdevicelink/sdl_ios/blob/master/.github/CONTRIBUTING.md) for information on branch naming and the CLA, and the [SmartDeviceLink GitHub Best Practices](https://d83tozu1c8tt6.cloudfront.net/media/resources/SDL_GitHub_BestPractices.pdf) document for more information on how to enter a pull request. Please create the PR as a draft until it is ready for review. Once this PR is ready for review, please request one from @smartdevicelink/ios and mark the PR as ready for review.
-Also, remember that all new public file headers should be added as public in the `File Inspector / Target Membership` pane, added to `SmartDeviceLink.h` and to *both* `.podspec` files.
+Also, remember that all new public file headers should be added as public in the `File Inspector / Target Membership` pane, placed in the `SmartDeviceLink/public` folder on the file system, and added to `SmartDeviceLink.h`. Private files should be placed in the `SmartDeviceLink/private` folder.
Delete the above section when you've read it.]
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/SDLChoiceSetManager.m b/SmartDeviceLink/private/SDLChoiceSetManager.m
index a8a0de0e7..51dd32b9e 100644
--- a/SmartDeviceLink/private/SDLChoiceSetManager.m
+++ b/SmartDeviceLink/private/SDLChoiceSetManager.m
@@ -147,6 +147,7 @@ UInt16 const ChoiceCellCancelIdMin = 1;
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"com.sdl.screenManager.choiceSetManager.transactionQueue";
queue.maxConcurrentOperationCount = 1;
+ queue.qualityOfService = NSQualityOfServiceUserInteractive;
queue.underlyingQueue = [SDLGlobals sharedGlobals].sdlConcurrentQueue;
queue.suspended = YES;
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/SDLGlobals.m b/SmartDeviceLink/private/SDLGlobals.m
index d99f6230e..9bca04e37 100644
--- a/SmartDeviceLink/private/SDLGlobals.m
+++ b/SmartDeviceLink/private/SDLGlobals.m
@@ -60,8 +60,8 @@ typedef NSNumber *MTUBox;
_rpcVersion = [[SDLVersion alloc] initWithString:@"1.0.0"];
_dynamicMTUDict = [NSMutableDictionary dictionary];
- dispatch_queue_attr_t qosSerial = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
- dispatch_queue_attr_t qosConcurrent = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INITIATED, 0);
+ dispatch_queue_attr_t qosSerial = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INTERACTIVE, 0);
+ dispatch_queue_attr_t qosConcurrent = dispatch_queue_attr_make_with_qos_class(DISPATCH_QUEUE_CONCURRENT, QOS_CLASS_USER_INTERACTIVE, 0);
_sdlProcessingQueue = dispatch_queue_create(SDLProcessingQueueName, qosSerial);
dispatch_queue_set_specific(_sdlProcessingQueue, SDLProcessingQueueName, SDLProcessingQueueName, NULL);
diff --git a/SmartDeviceLink/private/SDLLifecycleManager.m b/SmartDeviceLink/private/SDLLifecycleManager.m
index 8d25b79f7..c81dc6dd0 100644
--- a/SmartDeviceLink/private/SDLLifecycleManager.m
+++ b/SmartDeviceLink/private/SDLLifecycleManager.m
@@ -157,6 +157,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
_rpcOperationQueue = [[NSOperationQueue alloc] init];
_rpcOperationQueue.name = @"com.sdl.lifecycle.rpcOperation.concurrent";
+ _rpcOperationQueue.qualityOfService = NSQualityOfServiceUserInteractive;
_rpcOperationQueue.underlyingQueue = [SDLGlobals sharedGlobals].sdlConcurrentQueue;
_lifecycleQueue = dispatch_queue_create_with_target("com.sdl.lifecycle", DISPATCH_QUEUE_SERIAL, [SDLGlobals sharedGlobals].sdlProcessingQueue);
@@ -395,6 +396,8 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
weakSelf.registerResponse = (SDLRegisterAppInterfaceResponse *)response;
[SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithSDLMsgVersion:weakSelf.registerResponse.sdlMsgVersion];
+ SDLLogD(@"Did register app; RPC version: %@, SDL version: %@, starting languages: (VR) %@, (HMI) %@, vehicle type: %@", weakSelf.registerResponse.sdlMsgVersion, (weakSelf.registerResponse.sdlVersion ?: @"unavailable"), weakSelf.registerResponse.language, weakSelf.registerResponse.hmiDisplayLanguage, weakSelf.registerResponse.vehicleType);
+
[weakSelf sdl_transitionToState:SDLLifecycleStateRegistered];
}];
}
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/SDLMenuManager.m b/SmartDeviceLink/private/SDLMenuManager.m
index af91dc3a1..b78f8ee99 100644
--- a/SmartDeviceLink/private/SDLMenuManager.m
+++ b/SmartDeviceLink/private/SDLMenuManager.m
@@ -472,7 +472,7 @@ UInt32 const MenuCellIdMin = 1;
NSArray<SDLRPCRequest *> *mainMenuCommands = nil;
NSArray<SDLRPCRequest *> *subMenuCommands = nil;
- if ([self sdl_findAllArtworksToBeUploadedFromCells:self.menuCells].count > 0 || ![self.windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
+ if (![self sdl_shouldRPCsIncludeImages:self.menuCells] || ![self.windowCapability hasImageFieldOfName:SDLImageFieldNameCommandIcon]) {
// Send artwork-less menu
mainMenuCommands = [self sdl_mainMenuCommandsForCells:updatedMenu withArtwork:NO usingIndexesFrom:menu];
subMenuCommands = [self sdl_subMenuCommandsForCells:updatedMenu withArtwork:NO];
@@ -539,7 +539,7 @@ UInt32 const MenuCellIdMin = 1;
NSMutableSet<SDLArtwork *> *mutableArtworks = [NSMutableSet set];
for (SDLMenuCell *cell in cells) {
- if ([self sdl_artworkNeedsUpload:cell.icon]) {
+ if ([self.fileManager fileNeedsUpload:cell.icon]) {
[mutableArtworks addObject:cell.icon];
}
@@ -551,8 +551,17 @@ UInt32 const MenuCellIdMin = 1;
return [mutableArtworks allObjects];
}
-- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
- return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon);
+- (BOOL)sdl_shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells {
+ for (SDLMenuCell *cell in cells) {
+ SDLArtwork *artwork = cell.icon;
+ if (artwork != nil && !artwork.isStaticIcon && ![self.fileManager hasUploadedFile:artwork]) {
+ return NO;
+ } else if (cell.subCells.count > 0) {
+ return [self sdl_shouldRPCsIncludeImages:cell.subCells];
+ }
+ }
+
+ return YES;
}
#pragma mark IDs
diff --git a/SmartDeviceLink/private/SDLPreloadChoicesOperation.m b/SmartDeviceLink/private/SDLPreloadChoicesOperation.m
index 82f7435ff..b52a82f70 100644
--- a/SmartDeviceLink/private/SDLPreloadChoicesOperation.m
+++ b/SmartDeviceLink/private/SDLPreloadChoicesOperation.m
@@ -87,10 +87,10 @@ NS_ASSUME_NONNULL_BEGIN
NSMutableArray<SDLArtwork *> *artworksToUpload = [NSMutableArray arrayWithCapacity:self.cellsToUpload.count];
for (SDLChoiceCell *cell in self.cellsToUpload) {
- if ([self sdl_shouldSendChoicePrimaryImage] && [self sdl_artworkNeedsUpload:cell.artwork]) {
+ if ([self sdl_shouldSendChoicePrimaryImage] && [self.fileManager fileNeedsUpload:cell.artwork]) {
[artworksToUpload addObject:cell.artwork];
}
- if ([self sdl_shouldSendChoiceSecondaryImage] && [self sdl_artworkNeedsUpload:cell.secondaryArtwork]) {
+ if ([self sdl_shouldSendChoiceSecondaryImage] && [self.fileManager fileNeedsUpload:cell.secondaryArtwork]) {
[artworksToUpload addObject:cell.secondaryArtwork];
}
}
@@ -113,10 +113,6 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
-- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
- return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon);
-}
-
- (void)sdl_preloadCells {
_currentState = SDLPreloadChoicesOperationStatePreloadingChoices;
diff --git a/SmartDeviceLink/private/SDLSoftButtonManager.m b/SmartDeviceLink/private/SDLSoftButtonManager.m
index 97b233aa6..7a7605d22 100644
--- a/SmartDeviceLink/private/SDLSoftButtonManager.m
+++ b/SmartDeviceLink/private/SDLSoftButtonManager.m
@@ -11,6 +11,7 @@
#import "SDLConnectionManagerType.h"
#import "SDLError.h"
#import "SDLFileManager.h"
+#import "SDLGlobals.h"
#import "SDLLogMacros.h"
#import "SDLOnHMIStatus.h"
#import "SDLPredefinedWindows.h"
@@ -94,7 +95,8 @@ NS_ASSUME_NONNULL_BEGIN
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"SDLSoftButtonManager Transaction Queue";
queue.maxConcurrentOperationCount = 1;
- queue.qualityOfService = NSQualityOfServiceUserInitiated;
+ queue.qualityOfService = NSQualityOfServiceUserInteractive;
+ queue.underlyingQueue = [SDLGlobals sharedGlobals].sdlConcurrentQueue;
queue.suspended = YES;
return queue;
diff --git a/SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m b/SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m
index d3a68735c..8897f4b10 100644
--- a/SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m
+++ b/SmartDeviceLink/private/SDLSoftButtonReplaceOperation.m
@@ -103,7 +103,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)sdl_uploadInitialStateImagesWithCompletionHandler:(void (^)(void))handler {
NSMutableArray<SDLArtwork *> *initialStatesToBeUploaded = [NSMutableArray array];
for (SDLSoftButtonObject *object in self.softButtonObjects) {
- if ([self sdl_artworkNeedsUpload:object.currentState.artwork]) {
+ if ([self.fileManager fileNeedsUpload:object.currentState.artwork]) {
[initialStatesToBeUploaded addObject:object.currentState.artwork];
}
}
@@ -118,7 +118,7 @@ NS_ASSUME_NONNULL_BEGIN
for (SDLSoftButtonObject *object in self.softButtonObjects) {
for (SDLSoftButtonState *state in object.states) {
if ([state.name isEqualToString:object.currentState.name]) { continue; }
- if ([self sdl_artworkNeedsUpload:state.artwork]) {
+ if ([self.fileManager fileNeedsUpload:state.artwork]) {
[otherStatesToBeUploaded addObject:state.artwork];
}
}
@@ -174,8 +174,9 @@ NS_ASSUME_NONNULL_BEGIN
[softButtons addObject:buttonObject.currentStateSoftButton];
}
+ // HAX: Work around a bug in Sync where not sending a main field when sending soft buttons will lock up the head unit for 10-15 seconds.
SDLShow *show = [[SDLShow alloc] init];
- show.mainField1 = self.mainField1;
+ show.mainField1 = self.mainField1 ?: @"";
show.softButtons = [softButtons copy];
[self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
@@ -227,17 +228,13 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Images
-- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
- return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && self.softButtonCapabilities.imageSupported.boolValue && !artwork.isStaticIcon);
-}
-
/// Checks all the button states for images that need to be uploaded.
/// @return True if all images have been uploaded; false at least one image needs to be uploaded
- (BOOL)sdl_allStateImagesAreUploaded {
for (SDLSoftButtonObject *button in self.softButtonObjects) {
for (SDLSoftButtonState *state in button.states) {
SDLArtwork *artwork = state.artwork;
- if (![self sdl_artworkNeedsUpload:artwork]) { continue; }
+ if (![self.fileManager fileNeedsUpload:artwork]) { continue; }
return NO;
}
}
diff --git a/SmartDeviceLink/private/SDLSoftButtonTransitionOperation.m b/SmartDeviceLink/private/SDLSoftButtonTransitionOperation.m
index add03489c..23265319f 100644
--- a/SmartDeviceLink/private/SDLSoftButtonTransitionOperation.m
+++ b/SmartDeviceLink/private/SDLSoftButtonTransitionOperation.m
@@ -50,8 +50,9 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)sdl_sendNewSoftButtons {
+ // HAX: Work around a bug in Sync where not sending a main field when sending soft buttons will lock up the head unit for 10-15 seconds.
SDLShow *newShow = [[SDLShow alloc] init];
- newShow.mainField1 = self.mainField1;
+ newShow.mainField1 = self.mainField1 ?: @"";
newShow.softButtons = [self sdl_currentStateSoftButtonsForObjects:self.softButtons];
[self.connectionManager sendConnectionRequest:newShow withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicManager.m b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
index cd6ad0894..6f416b2f1 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicManager.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
@@ -13,6 +13,7 @@
#import "SDLDisplayCapability.h"
#import "SDLError.h"
#import "SDLFileManager.h"
+#import "SDLGlobals.h"
#import "SDLImage.h"
#import "SDLLogMacros.h"
#import "SDLMetadataTags.h"
@@ -127,7 +128,8 @@ NS_ASSUME_NONNULL_BEGIN
NSOperationQueue *queue = [[NSOperationQueue alloc] init];
queue.name = @"SDLTextAndGraphicManager Transaction Queue";
queue.maxConcurrentOperationCount = 1;
- queue.qualityOfService = NSQualityOfServiceUserInitiated;
+ queue.qualityOfService = NSQualityOfServiceUserInteractive;
+ queue.underlyingQueue = [SDLGlobals sharedGlobals].sdlConcurrentQueue;
queue.suspended = YES;
return queue;
@@ -366,7 +368,7 @@ NS_ASSUME_NONNULL_BEGIN
UIImage *blankImage = UIGraphicsGetImageFromCurrentImageContext();
UIGraphicsEndImageContext();
- _blankArtwork = [SDLArtwork artworkWithImage:blankImage name:@"sdl_BlankArt" asImageFormat:SDLArtworkImageFormatPNG];
+ _blankArtwork = [SDLArtwork persistentArtworkWithImage:blankImage name:@"sdl_BlankArt" asImageFormat:SDLArtworkImageFormatPNG];
return _blankArtwork;
}
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
index 990df13f2..cd47531e2 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
@@ -111,7 +111,7 @@ NS_ASSUME_NONNULL_BEGIN
}
[strongSelf finishOperation];
}];
- } else if (![self sdl_artworkNeedsUpload:self.updatedState.primaryGraphic] && ![self sdl_artworkNeedsUpload:self.updatedState.secondaryGraphic]) {
+ } else if (![self.fileManager fileNeedsUpload:self.updatedState.primaryGraphic] && ![self.fileManager fileNeedsUpload:self.updatedState.secondaryGraphic]) {
SDLLogV(@"Images already uploaded, sending full update");
// The files to be updated are already uploaded, send the full show immediately
[self sdl_sendShow:show withHandler:^(NSError * _Nullable error) {
@@ -263,8 +263,8 @@ NS_ASSUME_NONNULL_BEGIN
- (nullable SDLShow *)sdl_createImageOnlyShowWithPrimaryArtwork:(nullable SDLArtwork *)primaryArtwork secondaryArtwork:(nullable SDLArtwork *)secondaryArtwork {
SDLShow *newShow = [[SDLShow alloc] init];
- newShow.graphic = ![self sdl_artworkNeedsUpload:primaryArtwork] ? primaryArtwork.imageRPC : nil;
- newShow.secondaryGraphic = ![self sdl_artworkNeedsUpload:secondaryArtwork] ? secondaryArtwork.imageRPC : nil;
+ newShow.graphic = [self sdl_shouldRPCIncludeImage:primaryArtwork] ? primaryArtwork.imageRPC : nil;
+ newShow.secondaryGraphic = [self sdl_shouldRPCIncludeImage:secondaryArtwork] ? secondaryArtwork.imageRPC : nil;
if (newShow.graphic == nil && newShow.secondaryGraphic == nil) {
SDLLogV(@"No graphics to upload");
@@ -515,30 +515,34 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Should Update
-- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
- return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon);
+- (BOOL)sdl_shouldRPCIncludeImage:(nullable SDLArtwork *)artwork {
+ if (artwork == nil) { return NO; }
+
+ return (artwork.isStaticIcon || [self.fileManager hasUploadedFile:artwork]);
}
- (BOOL)sdl_shouldUpdatePrimaryImage {
// If the template is updating, we don't yet know it's capabilities. Just assume the template supports the primary image.
BOOL templateSupportsPrimaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic] || [self sdl_shouldUpdateTemplateConfig];
- BOOL graphicMatchesExisting = [self.currentScreenData.primaryGraphic.name isEqualToString:self.updatedState.primaryGraphic.name];
+ BOOL graphicNameMatchesExisting = [self.currentScreenData.primaryGraphic.name isEqualToString:self.updatedState.primaryGraphic.name];
+ BOOL shouldOverwriteGraphic = self.updatedState.primaryGraphic.overwrite;
BOOL graphicExists = (self.updatedState.primaryGraphic != nil);
- return (templateSupportsPrimaryArtwork && !graphicMatchesExisting && graphicExists);
+ return (templateSupportsPrimaryArtwork && (shouldOverwriteGraphic || !graphicNameMatchesExisting) && graphicExists);
}
- (BOOL)sdl_shouldUpdateSecondaryImage {
// If the template is updating, we don't yet know it's capabilities. Just assume the template supports the secondary image.
BOOL templateSupportsSecondaryArtwork = [self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameSecondaryGraphic] || [self sdl_shouldUpdateTemplateConfig];
- BOOL graphicMatchesExisting = [self.currentScreenData.secondaryGraphic.name isEqualToString:self.updatedState.secondaryGraphic.name];
+ BOOL graphicNameMatchesExisting = [self.currentScreenData.secondaryGraphic.name isEqualToString:self.updatedState.secondaryGraphic.name];
+ BOOL shouldOverwriteGraphic = self.updatedState.secondaryGraphic != nil && self.updatedState.secondaryGraphic.overwrite;
BOOL graphicExists = (self.updatedState.secondaryGraphic != nil);
// Cannot detect if there is a secondary image below v5.0, so we'll just try to detect if the primary image is allowed and allow the secondary image if it is.
if ([[SDLGlobals sharedGlobals].rpcVersion isGreaterThanOrEqualToVersion:[SDLVersion versionWithMajor:5 minor:0 patch:0]]) {
- return (templateSupportsSecondaryArtwork && !graphicMatchesExisting && graphicExists);
+ return (templateSupportsSecondaryArtwork && (shouldOverwriteGraphic || !graphicNameMatchesExisting) && graphicExists);
} else {
- return ([self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic] && !graphicMatchesExisting && graphicExists);
+ return ([self.currentCapabilities hasImageFieldOfName:SDLImageFieldNameGraphic] && (shouldOverwriteGraphic || !graphicNameMatchesExisting) && graphicExists);
}
}
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/SDLFileManager.h b/SmartDeviceLink/public/SDLFileManager.h
index 24b589a2a..5010b89e4 100644
--- a/SmartDeviceLink/public/SDLFileManager.h
+++ b/SmartDeviceLink/public/SDLFileManager.h
@@ -137,6 +137,18 @@ typedef void (^SDLFileManagerStartupCompletionHandler)(BOOL success, NSError *__
- (void)uploadFiles:(NSArray<SDLFile *> *)files completionHandler:(nullable SDLFileManagerMultiUploadCompletionHandler)completionHandler NS_SWIFT_NAME(upload(files:completionHandler:));
/**
+ * Check if an SDLFile needs to be uploaded to Core or not. This method differs from hasUploadedFile() because it takes the `isStaticIcon` and `overwrite` properties into consideration.
+ *
+ * For example, if the file is static icon, the method always returns false.
+ *
+ * If the file is dynamic, it returns true in one of these situations: 1) the file has the overwrite property set to true, 2) the file hasn't been uploaded to Core before.
+ *
+ * @param file the SDLFile that needs to be checked
+ * @return BOOL that tells whether file needs to be uploaded to Core or not
+ */
+- (BOOL)fileNeedsUpload:(SDLFile *)file;
+
+/**
* Uploads an artwork file to the remote file system and returns the name of the uploaded artwork once completed. If an artwork with the same name is already on the remote system, the artwork is not uploaded and the artwork name is simply returned.
*
* @param artwork A SDLArwork containing an image to be sent
diff --git a/SmartDeviceLink/public/SDLFileManager.m b/SmartDeviceLink/public/SDLFileManager.m
index 2acafe62b..58ed6a547 100644
--- a/SmartDeviceLink/public/SDLFileManager.m
+++ b/SmartDeviceLink/public/SDLFileManager.m
@@ -22,6 +22,7 @@
#import "SDLPutFile.h"
#import "SDLStateMachine.h"
#import "SDLUploadFileOperation.h"
+#import "SDLVersion.h"
NS_ASSUME_NONNULL_BEGIN
@@ -284,13 +285,13 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
// HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly.
if (file.persistent && [self.remoteFileNames containsObject:file.name]) {
// If it's a persistant file, the bug won't present itself; just check if it's on the remote system
- return true;
+ return YES;
} else if (!file.persistent && [self.remoteFileNames containsObject:file.name] && [self.uploadedEphemeralFileNames containsObject:file.name]) {
// If it's an ephemeral file, the bug will present itself; check that it's a remote file AND that we've uploaded it this session
- return true;
+ return YES;
}
- return false;
+ return NO;
}
- (void)uploadFiles:(NSArray<SDLFile *> *)files completionHandler:(nullable SDLFileManagerMultiUploadCompletionHandler)completionHandler {
@@ -377,9 +378,9 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
return;
}
- // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly. This led to attempted uploads failing due to the system thinking they were already there when they were not.
- if (!file.persistent && ![self hasUploadedFile:file]) {
- file.overwrite = true;
+ // HAX: [#827](https://github.com/smartdevicelink/sdl_ios/issues/827) Older versions of Core had a bug where list files would cache incorrectly. This led to attempted uploads failing due to the system thinking they were already there when they were not. This is only needed if connecting to Core v4.3.1 or less which corresponds to RPC v4.3.1 or less
+ if (!file.persistent && ![self hasUploadedFile:file] && [[SDLGlobals sharedGlobals].rpcVersion isLessThanVersion:[SDLVersion versionWithMajor:4 minor:4 patch:0]]) {
+ file.overwrite = YES;
}
// Check our overwrite settings and error out if it would overwrite
@@ -426,6 +427,12 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
#pragma mark Artworks
+- (BOOL)fileNeedsUpload:(SDLFile *)file {
+ if (file == nil || file.isStaticIcon) { return NO; }
+
+ return (file.overwrite || ![self hasUploadedFile:file]);
+}
+
- (void)uploadArtwork:(SDLArtwork *)artwork completionHandler:(nullable SDLFileManagerUploadArtworkCompletionHandler)completion {
__weak typeof(self) weakself = self;
[self uploadFile:artwork completionHandler:^(BOOL success, NSUInteger bytesAvailable, NSError * _Nullable error) {
diff --git a/SmartDeviceLink/public/SDLMsgVersion.m b/SmartDeviceLink/public/SDLMsgVersion.m
index dce291b1b..ee0dc04fa 100644
--- a/SmartDeviceLink/public/SDLMsgVersion.m
+++ b/SmartDeviceLink/public/SDLMsgVersion.m
@@ -52,7 +52,7 @@
}
- (NSString *)description {
- return [NSString stringWithFormat:@"%@.%@.%@", self.majorVersion, self.minorVersion, self.patchVersion];
+ return [NSString stringWithFormat:@"%@.%@.%@", self.majorVersion, self.minorVersion, (self.patchVersion ?: @"0")];
}
@end
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/SmartDeviceLink/public/SDLShowConstantTBT.h b/SmartDeviceLink/public/SDLShowConstantTBT.h
index 3ac01780d..b45774838 100644
--- a/SmartDeviceLink/public/SDLShowConstantTBT.h
+++ b/SmartDeviceLink/public/SDLShowConstantTBT.h
@@ -25,8 +25,8 @@ NS_ASSUME_NONNULL_BEGIN
/// @param totalDistance The distance to the final destination
/// @param turnIcon An icon to show with the turn description
/// @param nextTurnIcon An icon to show with the next turn description
-/// @param distanceToManeuver Fraction of distance till next maneuver
-/// @param distanceToManeuverScale Distance till next maneuver
+/// @param distanceToManeuver Distance (in meters) until next maneuver.
+/// @param distanceToManeuverScale Distance (in meters) from previous maneuver to next maneuver.
/// @param maneuverComplete If and when a maneuver has completed while an AlertManeuver is active, the app must send this value set to TRUE in order to clear the AlertManeuver overlay. If omitted the value will be assumed as FALSE
/// @param softButtons Three dynamic SoftButtons available (first SoftButton is fixed to "Turns")
/// @return An SDLShowConstantTBT object
@@ -86,14 +86,14 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic, nullable) SDLImage *nextTurnIcon;
/**
- * Fraction of distance till next maneuver (starting from when AlertManeuver is triggered). Used to calculate progress bar.
+ * Distance (in meters) until next maneuver. May be used to calculate progress bar.
*
* Optional, Float, 0 - 1,000,000,000
*/
@property (strong, nonatomic, nullable) NSNumber<SDLFloat> *distanceToManeuver;
/**
- * Distance till next maneuver (starting from) from previous maneuver. Used to calculate progress bar.
+ * Distance (in meters) from previous maneuver to next maneuver. May be used to calculate progress bar.
*
* Optional, Float, 0 - 1,000,000,000
*/
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
index af5f443a7..f7ea93fc8 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLFileManagerSpec.m
@@ -466,6 +466,66 @@ describe(@"uploading / deleting single files with the file manager", ^{
expect(testFileManager.pendingTransactions.count).to(equal(1));
});
});
+
+ describe(@"checking if files and artworks needs upload", ^{
+ __block UIImage *testUIImage = nil;
+ __block NSString *expectedArtworkName = nil;
+ __block SDLArtwork *artwork = nil;
+
+ context(@"when artwork is nil", ^{
+ it(@"should not allow file to be uploaded", ^{
+ expect(artwork).to(beNil());
+ BOOL testFileNeedsUpload = [testFileManager fileNeedsUpload:artwork];
+ expect(testFileNeedsUpload).to(beFalse());
+ });
+ });
+
+ context(@"when artwork is static", ^{
+ it(@"should not allow file to be uploaded", ^{
+ artwork = [[SDLArtwork alloc] initWithStaticIcon:SDLStaticIconNameKey];
+
+ BOOL testFileNeedsUpload = [testFileManager fileNeedsUpload:artwork];
+ expect(testFileNeedsUpload).to(beFalse());
+ });
+ });
+
+ context(@"when artwork is dynamic", ^{
+ beforeEach(^{
+ testUIImage = [FileManagerSpecHelper imagesForCount:1].firstObject;
+ expectedArtworkName = testInitialFileNames.firstObject;
+ artwork = [SDLArtwork artworkWithImage:testUIImage name:expectedArtworkName asImageFormat:SDLArtworkImageFormatPNG];
+ });
+
+ context(@"when uploading artwork for the first time", ^{
+ it(@"should allow file to be uploaded", ^{
+ BOOL testFileNeedsUpload = [testFileManager fileNeedsUpload:artwork];
+ expect(testFileNeedsUpload).to(beTrue());
+ });
+ });
+
+ context(@"when artwork is previously uploaded", ^{
+ beforeEach(^{
+ testFileManager.uploadedEphemeralFileNames = [NSMutableSet setWithArray:testInitialFileNames];
+ testFileManager.mutableRemoteFileNames = [NSMutableSet setWithArray:testInitialFileNames];
+ [testFileManager.stateMachine setToState:SDLFileManagerStateReady fromOldState:SDLFileManagerStateShutdown callEnterTransition:NO];
+ });
+
+ it(@"should not allow file to be uploaded when overwrite is set to false", ^{
+ artwork.overwrite = NO;
+
+ BOOL testFileNeedsUpload = [testFileManager fileNeedsUpload:artwork];
+ expect(testFileNeedsUpload).to(beFalse());
+ });
+
+ it(@"should allow file to be uploaded when overwrite is set to true", ^{
+ artwork.overwrite = YES;
+
+ BOOL testFileNeedsUpload = [testFileManager fileNeedsUpload:artwork];
+ expect(testFileNeedsUpload).to(beTrue());
+ });
+ });
+ });
+ });
});
describe(@"uploading/deleting multiple files in the file manager", ^{
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/SDLMenuManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
index ff9c4e7bb..9bde38ac7 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
@@ -35,6 +35,8 @@
@property (assign, nonatomic) UInt32 lastMenuId;
@property (copy, nonatomic) NSArray<SDLMenuCell *> *oldMenuCells;
+- (BOOL)sdl_shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells;
+
@end
QuickSpecBegin(SDLMenuManagerSpec)
@@ -46,6 +48,7 @@ describe(@"menu manager", ^{
__block SDLSystemCapabilityManager *mockSystemCapabilityManager = nil;
__block SDLArtwork *testArtwork = nil;
__block SDLArtwork *testArtwork2 = nil;
+ __block SDLArtwork *testArtwork3 = nil;
__block SDLMenuCell *textOnlyCell = nil;
__block SDLMenuCell *textOnlyCell2 = nil;
@@ -58,6 +61,8 @@ describe(@"menu manager", ^{
beforeEach(^{
testArtwork = [[SDLArtwork alloc] initWithData:[@"Test data" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO];
testArtwork2 = [[SDLArtwork alloc] initWithData:[@"Test data 2" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name 2" fileExtension:@"png" persistent:NO];
+ testArtwork3 = [[SDLArtwork alloc] initWithData:[@"Test data 3" dataUsingEncoding:NSUTF8StringEncoding] name:@"some artwork name" fileExtension:@"png" persistent:NO];
+ testArtwork3.overwrite = YES;
textOnlyCell = [[SDLMenuCell alloc] initWithTitle:@"Test 1" icon:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" icon:testArtwork voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
@@ -180,6 +185,13 @@ describe(@"menu manager", ^{
});
});
+ it(@"should check if all artworks are uploaded and return NO", ^{
+ textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" icon:testArtwork3 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ testManager.menuCells = @[textAndImageCell, textOnlyCell];
+ OCMVerify([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]);
+ expect([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]).to(beFalse());
+ });
+
it(@"should properly update a text cell", ^{
testManager.menuCells = @[textOnlyCell];
@@ -213,6 +225,13 @@ describe(@"menu manager", ^{
OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
});
+ it(@"should check if all artworks are uploaded", ^{
+ textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" icon:testArtwork3 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ testManager.menuCells = @[textAndImageCell, textOnlyCell];
+ OCMVerify([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]);
+ expect([testManager sdl_shouldRPCsIncludeImages:testManager.menuCells]).to(beTrue());
+ });
+
it(@"should properly update an image cell", ^{
testManager.menuCells = @[textAndImageCell, submenuImageCell];
@@ -228,6 +247,14 @@ describe(@"menu manager", ^{
expect(submenu).to(haveCount(1));
expect(sentCommand.cmdIcon.value).to(equal(testArtwork.name));
expect(sentSubmenu.menuIcon.value).to(equal(testArtwork2.name));
+ OCMReject([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+ });
+
+ it(@"should properly overwrite an image cell", ^{
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
+ textAndImageCell = [[SDLMenuCell alloc] initWithTitle:@"Test 2" icon:testArtwork3 voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {}];
+ testManager.menuCells = @[textAndImageCell, submenuImageCell];
+ OCMVerify([mockFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m
index c36e574fe..144f54e3d 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLPreloadChoicesOperationSpec.m
@@ -25,6 +25,7 @@ describe(@"a preload choices operation", ^{
__block NSString *testDisplayName = @"SDL_GENERIC";
__block NSData *cellArtData = [@"testart" dataUsingEncoding:NSUTF8StringEncoding];
+ __block NSData *cellArtData2 = [@"testart2" dataUsingEncoding:NSUTF8StringEncoding];
__block BOOL hasCalledOperationCompletionHandler = NO;
__block NSError *resultError = nil;
@@ -53,6 +54,7 @@ describe(@"a preload choices operation", ^{
windowCapability.textFields = @[primaryTextField];
OCMStub([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]);
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
});
context(@"with artworks", ^{
@@ -60,6 +62,7 @@ describe(@"a preload choices operation", ^{
__block NSSet<SDLChoiceCell *> *cellsWithStaticIcon = nil;
__block NSString *art1Name = @"Art1Name";
__block NSString *art2Name = @"Art2Name";
+ __block SDLArtwork *cell1Art2 = [[SDLArtwork alloc] initWithData:cellArtData2 name:art1Name fileExtension:@"png" persistent:NO];
beforeEach(^{
SDLArtwork *cell1Art = [[SDLArtwork alloc] initWithData:cellArtData name:art1Name fileExtension:@"png" persistent:NO];
@@ -145,6 +148,25 @@ describe(@"a preload choices operation", ^{
}] completionHandler:[OCMArg any]]);
expect(@(testOp.currentState)).to(equal(SDLPreloadChoicesOperationStatePreloadingChoices));
});
+
+ it(@"should properly overwrite artwork", ^{
+ cell1Art2.overwrite = YES;
+ SDLChoiceCell *cell1WithArt = [[SDLChoiceCell alloc] initWithText:@"Cell1" artwork:cell1Art2 voiceCommands:nil];
+
+ SDLArtwork *cell2Art = [[SDLArtwork alloc] initWithData:cellArtData name:art2Name fileExtension:@"png" persistent:NO];
+ SDLChoiceCell *cell2WithArtAndSecondary = [[SDLChoiceCell alloc] initWithText:@"Cell2" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:cell2Art secondaryArtwork:cell2Art];
+
+ SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate];
+ SDLChoiceCell *cellWithStaticIcon = [[SDLChoiceCell alloc] initWithText:@"Static Icon" secondaryText:nil tertiaryText:nil voiceCommands:nil artwork:staticIconArt secondaryArtwork:nil];
+
+ cellsWithArtwork = [NSSet setWithArray:@[cell1WithArt, cell2WithArtAndSecondary]];
+ cellsWithStaticIcon = [NSSet setWithArray:@[cellWithStaticIcon]];
+ testOp = [[SDLPreloadChoicesOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager displayName:testDisplayName windowCapability:windowCapability isVROptional:NO cellsToPreload:cellsWithArtwork];
+ [testOp start];
+
+ OCMExpect([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+ OCMVerify([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+ });
});
context(@"when artworks are static icons", ^{
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
index 77760bb07..b03252c25 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
@@ -820,18 +820,37 @@ describe(@"the text and graphic operation", ^{
updatedState.textField1 = field1String;
updatedState.primaryGraphic = testArtwork;
updatedState.secondaryGraphic = testArtwork2;
+ });
+ it(@"should send a show and not upload any artworks", ^{
testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentData newState:updatedState currentScreenDataUpdatedHandler:^(SDLTextAndGraphicState * _Nullable newScreenData, NSError * _Nullable error) {} updateCompletionHandler:nil];
[testOp start];
- });
- it(@"should send a show and not upload any artworks", ^{
expect(testConnectionManager.receivedRequests).to(haveCount(1));
SDLShow *firstSentRequest = testConnectionManager.receivedRequests[0];
expect(firstSentRequest.mainField1).to(equal(field1String));
expect(firstSentRequest.mainField2).to(beEmpty());
expect(firstSentRequest.graphic).toNot(beNil());
expect(firstSentRequest.secondaryGraphic).to(beNil());
+ OCMReject([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+ });
+
+ it(@"should properly overwrite artwork", ^{
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
+ SDLArtwork *testArtwork3 = [[SDLArtwork alloc] initWithData:[@"Test data 3" dataUsingEncoding:NSUTF8StringEncoding] name:testArtworkName fileExtension:@"png" persistent:NO];
+ testArtwork3.overwrite = YES;
+
+ SDLTextAndGraphicState *updatedState2 = [[SDLTextAndGraphicState alloc] init];
+ updatedState2.textField1 = field1String;
+ updatedState2.primaryGraphic = testArtwork3;
+ updatedState2.secondaryGraphic = testArtwork2;
+
+ SDLTextAndGraphicUpdateOperation *testOp2 = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:updatedState newState:updatedState2 currentScreenDataUpdatedHandler:^(SDLTextAndGraphicState * _Nullable newScreenData, NSError * _Nullable error) {} updateCompletionHandler:nil];
+ [testOp2 start];
+
+ [testConnectionManager respondToLastRequestWithResponse:successShowResponse];
+
+ OCMVerify([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
});
});
@@ -868,6 +887,7 @@ describe(@"the text and graphic operation", ^{
// when there is text to update as well
context(@"when there is text to update as well", ^{
beforeEach(^{
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
updatedState = [[SDLTextAndGraphicState alloc] init];
updatedState.textField1 = field1String;
updatedState.primaryGraphic = testArtwork;
@@ -943,6 +963,7 @@ describe(@"the text and graphic operation", ^{
// when there is no text to update
context(@"when there is no text to update", ^{
beforeEach(^{
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
updatedState = [[SDLTextAndGraphicState alloc] init];
updatedState.primaryGraphic = testArtwork;
@@ -972,6 +993,7 @@ describe(@"the text and graphic operation", ^{
// when the image is a static icon
context(@"when the image is a static icon", ^{
beforeEach(^{
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(NO);
updatedState = [[SDLTextAndGraphicState alloc] init];
updatedState.primaryGraphic = testStaticIcon;
@@ -998,6 +1020,7 @@ describe(@"the text and graphic operation", ^{
context(@"if the images for the primary and secondary graphics fail the upload process", ^{
beforeEach(^{
OCMStub([mockFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
NSArray<NSString *> *testSuccessfulArtworks = @[];
NSError *testError = [NSError errorWithDomain:@"errorDomain"
code:9
@@ -1030,6 +1053,7 @@ describe(@"the text and graphic operation", ^{
NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork.name];
NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork2.name:@"error 2"}];
OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] progressHandler:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
updatedState = [[SDLTextAndGraphicState alloc] init];
updatedState.textField1 = field1String;
updatedState.primaryGraphic = testArtwork;
@@ -1058,6 +1082,7 @@ describe(@"the text and graphic operation", ^{
NSArray<NSString *> *testSuccessfulArtworks = @[testArtwork2.name];
NSError *testError = [NSError errorWithDomain:@"errorDomain" code:9 userInfo:@{testArtwork.name:@"error 2"}];
OCMStub([mockFileManager uploadArtworks:[OCMArg isNotNil] progressHandler:[OCMArg isNotNil] completionHandler:([OCMArg invokeBlockWithArgs:testSuccessfulArtworks, testError, nil])]);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
updatedState = [[SDLTextAndGraphicState alloc] init];
updatedState.textField1 = field1String;
updatedState.primaryGraphic = testArtwork;
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLVoiceCommandManagerSpec.m
index c5e275843..58d5ff591 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).toEventually(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/SDLSoftButtonReplaceOperationSpec.m b/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m
index 623ec427c..3b4669d78 100644
--- a/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m
+++ b/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m
@@ -44,6 +44,7 @@ describe(@"a soft button replace operation", ^{
__block NSString *object2State2ArtworkName = @"O2S2 Artwork";
__block SDLArtwork *object2State1Art = nil;
__block SDLArtwork *object2State2Art = nil;
+ __block SDLArtwork *object2State11Art = nil;
__block SDLSoftButtonState *object2State1 = nil;
__block SDLSoftButtonState *object2State2 = nil;
__block SDLSoftButtonObject *buttonWithTextAndImage = nil;
@@ -97,6 +98,8 @@ describe(@"a soft button replace operation", ^{
object2State1Art = [[SDLArtwork alloc] initWithData:[@"TestData" dataUsingEncoding:NSUTF8StringEncoding] name:object2State1ArtworkName fileExtension:@"png" persistent:YES];
object2State2Art = [[SDLArtwork alloc] initWithData:[@"TestData2" dataUsingEncoding:NSUTF8StringEncoding] name:object2State2ArtworkName fileExtension:@"png" persistent:YES];
+ object2State11Art = [[SDLArtwork alloc] initWithData:[@"TestData11" dataUsingEncoding:NSUTF8StringEncoding] name:object2State1ArtworkName fileExtension:@"png" persistent:YES];
+ object2State11Art.overwrite = YES;
object2State1 = [[SDLSoftButtonState alloc] initWithStateName:object2State1Name text:object2State1Text artwork:object2State1Art];
object2State2 = [[SDLSoftButtonState alloc] initWithStateName:object2State2Name text:object2State2Text artwork:object2State2Art];
buttonWithTextAndImage = [[SDLSoftButtonObject alloc] initWithName:object2Name states:@[object2State1, object2State2] initialStateName:object2State1.name handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
@@ -162,6 +165,7 @@ describe(@"a soft button replace operation", ^{
context(@"When a response is received to the upload", ^{
beforeEach(^{
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg any]]);
[testOp start];
});
@@ -278,7 +282,7 @@ describe(@"a soft button replace operation", ^{
it(@"should not upload artworks", ^{
OCMReject([testFileManager uploadArtworks:[OCMArg any] progressHandler:nil completionHandler:nil]);
-
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO);
[testOp start];
OCMVerifyAllWithDelay(testFileManager, 0.5);
@@ -296,8 +300,22 @@ describe(@"a soft button replace operation", ^{
expect(sentRequests.firstObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeBoth));
});
+ it(@"should properly overwrite artwork", ^{
+ OCMExpect([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg any]]);
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
+ object2State1 = [[SDLSoftButtonState alloc] initWithStateName:object2State1Name text:object2State1Text artwork:object2State11Art];
+ buttonWithTextAndImage = [[SDLSoftButtonObject alloc] initWithName:object2Name states:@[object2State1, object2State2] initialStateName:object2State1.name handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testSoftButtonObjects = @[buttonWithText, buttonWithTextAndImage];
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg any]]);
+ [testOp start];
+ OCMVerify([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+ });
+
context(@"When a response is received to the upload", ^{
beforeEach(^{
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO);
[testOp start];
});
@@ -324,6 +342,7 @@ describe(@"a soft button replace operation", ^{
context(@"when artworks are static icons", ^{
beforeEach(^{
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(NO);
testSoftButtonObjects = @[buttonWithTextAndStaticImage];
testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
@@ -371,8 +390,11 @@ describe(@"a soft button replace operation", ^{
it(@"should upload all artworks", ^{
// Check that the artworks in the initial button states are uploaded
OCMExpect([testFileManager uploadArtworks:@[buttonWithTextAndImage.states[0].artwork] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg any]]);
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
testSoftButtonObjects = @[buttonWithText, buttonWithTextAndImage];
testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg any]]);
[testOp start];
OCMVerifyAllWithDelay(testFileManager, 0.5);
@@ -407,14 +429,19 @@ describe(@"a soft button replace operation", ^{
it(@"should upload all artworks even if the initial state does not have artworks", ^{
OCMReject([testFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(NO);
+ OCMExpect([testFileManager fileNeedsUpload:[OCMArg isNil]]).andReturn(NO);
// buttonWithTextAndImage2 has text in the first state and an text and image in the second & third states
testSoftButtonObjects = @[buttonWithTextAndStaticImage, buttonWithTextAndImage2];
testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+
[testOp start];
OCMVerifyAllWithDelay(testFileManager, 0.5);
NSArray<SDLArtwork *> *testArtworkUploads = @[buttonWithTextAndImage2.states[1].artwork, buttonWithTextAndImage2.states[2].artwork];
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
OCMExpect([testFileManager uploadArtworks:testArtworkUploads progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
[testConnectionManager respondToLastRequestWithResponse:successResponse];
OCMVerifyAllWithDelay(testFileManager, 0.5);
@@ -445,6 +472,7 @@ describe(@"a soft button replace operation", ^{
context(@"When a response is received to the upload", ^{
beforeEach(^{
+ OCMStub([testFileManager fileNeedsUpload:[OCMArg isNotNil]]).andReturn(YES);
OCMExpect([testFileManager uploadArtworks:[OCMArg isNotNil] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
testSoftButtonObjects = @[buttonWithTextAndImage];
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];
}
diff --git a/generator/transformers/common_producer.py b/generator/transformers/common_producer.py
index 977cc2232..3a591a05d 100644
--- a/generator/transformers/common_producer.py
+++ b/generator/transformers/common_producer.py
@@ -365,19 +365,27 @@ class InterfaceProducerCommon(ABC):
'deprecated': json.loads(param.deprecated.lower()) if param.deprecated else False,
'modifier': 'strong',
'history': param.history}
+
+ parameterItems = OrderedDict()
if isinstance(param.param_type, (Integer, Float, String, Array)):
- data['description'].append(self.create_param_descriptor(param.param_type, OrderedDict()))
+ self.create_param_type_descriptor(param.param_type, parameterItems)
+
+ if isinstance(param.param_type, (Boolean, Enum)):
+ self.create_param_default_value_descriptor(param, parameterItems)
+
+ if len(parameterItems) > 0:
+ data['description'].append(json.dumps(parameterItems, sort_keys=False))
data.update(self.extract_type(param))
data.update(self.param_origin_change(param.name))
return self.param_named(**data)
- def create_param_descriptor(self, param_type, parameterItems):
+ def create_param_type_descriptor(self, param_type, parameterItems):
"""
- Recursively creates a documentation string of all the descriptors for a parameter (e.g. {"string_min_length": 1, string_max_length": 500}). The parameters should be returned in the same order they were added to the parameterItems dictionary
+ Gets all the descriptors for a parameter to be used in parameter's documentation (e.g. {"string_min_length": 1, string_max_length": 500}). The parameters should be returned in the same order they were added to the parameterItems dictionary
:param param_type: param_type from the initial Model
:param parameterItems: Ordered dictionary that stores each of the parameter's descriptors
- :return: All the descriptor params from param_type concatenated into one string
+ :return: All the descriptor params
"""
# The key is a descriptor (i.e. max_value) and value is the associated value (i.e. 100). Some values will be dictionaries that have to be parsed to get additional descriptors (e.g. the value for an array of strings' data type will be sub-dictionary describing the min_length, max_length, and default value for the strings used in the array)
for key, value in param_type.__dict__.items():
@@ -387,7 +395,7 @@ class InterfaceProducerCommon(ABC):
# Skip adding documentation for the data type if it is a struct or enum. This is unnecessary as each enum or struct has its own documentation
continue
else:
- self.create_param_descriptor(value, parameterItems)
+ self.create_param_type_descriptor(value, parameterItems)
else:
if key == 'default_value' and value is None:
# Do not add the default_value key/value pair unless it has been explicitly set in the RPC Spec
@@ -396,7 +404,24 @@ class InterfaceProducerCommon(ABC):
parameterDescriptor = self.update_param_descriptor(key)
parameterItems[parameterDescriptor] = value
- return json.dumps(parameterItems, sort_keys=False)
+ return parameterItems
+
+ def create_param_default_value_descriptor(self, param, parameterItems):
+ """
+ Extracts the default value for a parameter to be used in parameter's documentation (HAX: The default_value for Ints and Floats is save to both the param and param_type but is only saved to the param_type for Enums and Bools for some reason)
+ :param param: param from the initial Model
+ :param parameterItems: Ordered dictionary that stores each of the parameter's descriptors
+ :return: All the descriptor params
+ """
+ if param.default_value is None:
+ return parameterItems
+
+ if isinstance(param.param_type, Enum):
+ parameterItems['default_value'] = param.default_value.name
+ else:
+ parameterItems['default_value'] = param.default_value
+
+ return parameterItems
def update_param_descriptor(self, parameterName):
"""