summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorleonid lokhmatov, Luxoft <zaqqqqqqqq@gmail.com>2021-03-04 11:22:51 +0200
committerleonid lokhmatov, Luxoft <zaqqqqqqqq@gmail.com>2021-03-04 11:22:51 +0200
commita348d3c038271cadd92e11a4592e7e598aca9a33 (patch)
treeade9e9c828345ad79bd1d97f93ae974c0f80a22a
parent1ff1f1fd2883be1c5a2961edfb673b50f8907ee0 (diff)
parentab95191870654e556caa5448dbccfb1d72cef612 (diff)
downloadsdl_ios-a348d3c038271cadd92e11a4592e7e598aca9a33.tar.gz
[0296] 'upd video stream cap (7): Merge branch 'develop' into this. Conflicts in project.pbxproj resolved.
-rw-r--r--.github/workflows/test.yml4
-rw-r--r--Example Apps/Example ObjC/AlertManager.m26
-rw-r--r--Example Apps/Example Swift/AlertManager.swift23
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj100
-rw-r--r--SmartDeviceLink/private/SDLAlertManager.h52
-rw-r--r--SmartDeviceLink/private/SDLAlertManager.m171
-rw-r--r--SmartDeviceLink/private/SDLChoiceSetManager.m30
-rw-r--r--SmartDeviceLink/private/SDLError.h7
-rw-r--r--SmartDeviceLink/private/SDLError.m39
-rw-r--r--SmartDeviceLink/private/SDLLifecycleManager.m2
-rw-r--r--SmartDeviceLink/private/SDLLogFileModuleMap.m5
-rw-r--r--SmartDeviceLink/private/SDLMenuManager.m15
-rw-r--r--SmartDeviceLink/private/SDLPresentAlertOperation.h38
-rw-r--r--SmartDeviceLink/private/SDLPresentAlertOperation.m451
-rw-r--r--SmartDeviceLink/private/SDLSoftButtonManager.m33
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicManager.m25
-rw-r--r--SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.h6
-rw-r--r--SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.m21
-rw-r--r--SmartDeviceLink/public/SDLAlertAudioData.h22
-rw-r--r--SmartDeviceLink/public/SDLAlertAudioData.m25
-rw-r--r--SmartDeviceLink/public/SDLAlertView.h76
-rw-r--r--SmartDeviceLink/public/SDLAlertView.m146
-rw-r--r--SmartDeviceLink/public/SDLAudioData.h55
-rw-r--r--SmartDeviceLink/public/SDLAudioData.m135
-rw-r--r--SmartDeviceLink/public/SDLErrorConstants.h16
-rw-r--r--SmartDeviceLink/public/SDLErrorConstants.m1
-rw-r--r--SmartDeviceLink/public/SDLFileManager.h2
-rw-r--r--SmartDeviceLink/public/SDLFileManager.m2
-rw-r--r--SmartDeviceLink/public/SDLScreenManager.h29
-rw-r--r--SmartDeviceLink/public/SDLScreenManager.m25
-rw-r--r--SmartDeviceLink/public/SDLSystemCapabilityManager.m9
-rw-r--r--SmartDeviceLink/public/SmartDeviceLink.h4
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m56
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m20
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m128
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m12
-rw-r--r--SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLWindowCapabilitySpec.m108
-rw-r--r--SmartDeviceLinkTests/SDLAlertAudioDataSpec.m98
-rw-r--r--SmartDeviceLinkTests/SDLAlertManagerSpec.m316
-rw-r--r--SmartDeviceLinkTests/SDLAlertViewSpec.m307
-rw-r--r--SmartDeviceLinkTests/SDLAudioDataSpec.m301
-rw-r--r--SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m1147
-rw-r--r--SmartDeviceLinkTests/SDLScreenManagerSpec.m32
43 files changed, 3969 insertions, 151 deletions
diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml
index f675f0b41..45d857d1b 100644
--- a/.github/workflows/test.yml
+++ b/.github/workflows/test.yml
@@ -75,12 +75,10 @@ jobs:
# Upload coverage reports to Codecov
- name: Upload coverage to Codecov
uses: codecov/codecov-action@v1.0.10
- with:
- yml: ./codecov.yml
rpcTest:
name: RPC Generator Tests
- runs-on: ubuntu-latest
+ runs-on: ubuntu-20.04
strategy:
fail-fast: false
steps:
diff --git a/Example Apps/Example ObjC/AlertManager.m b/Example Apps/Example ObjC/AlertManager.m
index df902dc0a..a352696ff 100644
--- a/Example Apps/Example ObjC/AlertManager.m
+++ b/Example Apps/Example ObjC/AlertManager.m
@@ -15,19 +15,23 @@ NS_ASSUME_NONNULL_BEGIN
@implementation AlertManager
+ (void)sendAlertWithManager:(SDLManager *)sdlManager image:(nullable NSString *)imageName textField1:(NSString *)textField1 textField2:(nullable NSString *)textField2 {
- SDLSoftButton *okSoftButton = [[SDLSoftButton alloc] initWithType:SDLSoftButtonTypeText text:AlertOKButtonText image:nil highlighted:YES buttonId:10000 systemAction:nil handler:nil];
- SDLAlert *alert = [[SDLAlert alloc] initWithAlertText1:textField1 alertText2:textField2 alertText3:nil softButtons:@[okSoftButton] playTone:YES ttsChunks:nil duration:5000 progressIndicator:NO alertIcon:nil cancelID:0];
+ SDLSoftButtonObject *okSoftButton = [[SDLSoftButtonObject alloc] initWithName:AlertOKButtonText text:AlertOKButtonText artwork:nil handler:nil];
+ SDLAlertView *alert = [[SDLAlertView alloc] initWithText:textField1 buttons:@[okSoftButton]];
+ alert.secondaryText = textField2;
- if (imageName == nil) {
- [sdlManager sendRequest:alert];
- } else {
- [self sdlex_sendImageWithName:imageName sdlManager:sdlManager completionHandler:^(BOOL success, NSString * _Nullable artworkName) {
- if (success) {
- alert.alertIcon = [[SDLImage alloc] initWithName:artworkName isTemplate:YES];
- }
- [sdlManager sendRequest:alert];
- }];
+ SDLAlertAudioData *alertAudioData = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"alert"];
+ alertAudioData.playTone = YES;
+ alert.audio = alertAudioData;
+
+ if (imageName != nil) {
+ alert.icon = [SDLArtwork artworkWithImage:[[UIImage imageNamed:imageName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] asImageFormat:SDLArtworkImageFormatPNG];
}
+
+ [sdlManager.screenManager presentAlert:alert withCompletionHandler:^(NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogD(@"There was an error presenting the alert: %@", error);
+ }
+ }];
}
+ (void)sendSubtleAlertWithManager:(SDLManager *)sdlManager image:(nullable NSString *)imageName textField1:(NSString *)textField1 textField2:(nullable NSString *)textField2 {
diff --git a/Example Apps/Example Swift/AlertManager.swift b/Example Apps/Example Swift/AlertManager.swift
index bf0f1af96..71769c51d 100644
--- a/Example Apps/Example Swift/AlertManager.swift
+++ b/Example Apps/Example Swift/AlertManager.swift
@@ -8,6 +8,7 @@
import Foundation
import SmartDeviceLink
+import SmartDeviceLinkSwift
class AlertManager {
/// Sends an alert with up to two lines of text, an image, and a close button that will dismiss the alert when tapped.
@@ -17,18 +18,22 @@ class AlertManager {
/// - textField2: The second line of text in the alert
/// - sdlManager: The SDLManager
class func sendAlert(imageName: String? = nil, textField1: String, textField2: String? = nil, sdlManager: SDLManager) {
- let okSoftButton = SDLSoftButton(type: .text, text: AlertOKButtonText, image: nil, highlighted: true, buttonId: 10000, systemAction: nil, handler: nil)
- let alert = SDLAlert(alertText1: textField1, alertText2: textField2, alertText3: nil, softButtons: [okSoftButton], playTone: true, ttsChunks: nil, duration: 5000, progressIndicator: false, alertIcon: nil, cancelID: 0)
+ let okSoftButton = SDLSoftButtonObject(name: AlertOKButtonText, text: AlertOKButtonText, artwork: nil, handler: nil)
+ let alert = SDLAlertView(text: textField1, buttons: [okSoftButton])
+ alert.secondaryText = textField2
+
+ let alertAudioData = SDLAlertAudioData(speechSynthesizerString: "alert")
+ alertAudioData.playTone = true
+ alert.audio = alertAudioData
if let imageName = imageName {
- sendImage(imageName, sdlManager: sdlManager) { (success, artworkName) in
- if success {
- alert.alertIcon = SDLImage(name: artworkName, isTemplate: true)
- }
- sdlManager.send(alert)
+ alert.icon = SDLArtwork(image: UIImage(named: imageName)!.withRenderingMode(.alwaysTemplate), persistent: false, as: .PNG)
+ }
+
+ sdlManager.screenManager.presentAlert(alert) { error in
+ if let error = error {
+ SDLLog.e("There was an error presenting the alert: \(error.localizedDescription)")
}
- } else {
- sdlManager.send(alert)
}
}
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 44ed2f90d..0101a9e4f 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -1563,6 +1563,11 @@
8815D0F022330765000F24E6 /* SDLRPCRequestNotificationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88C23E8522297BD500EA171F /* SDLRPCRequestNotificationSpec.m */; };
8816772922208B82001FACFF /* SDLNavigationInstructionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8816772822208B82001FACFF /* SDLNavigationInstructionSpec.m */; };
8818ADDD2100FE0C007D6F19 /* SDLTurnSignalSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8818ADDC2100FE0C007D6F19 /* SDLTurnSignalSpec.m */; };
+ 881BBF50255AC27000761B7E /* SDLAlertView.h in Headers */ = {isa = PBXBuildFile; fileRef = 881BBF4E255AC27000761B7E /* SDLAlertView.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 881BBF51255AC27000761B7E /* SDLAlertView.m in Sources */ = {isa = PBXBuildFile; fileRef = 881BBF4F255AC27000761B7E /* SDLAlertView.m */; };
+ 881BBF5B255ADB8300761B7E /* SDLAlertViewSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 881BBF5A255ADB8300761B7E /* SDLAlertViewSpec.m */; };
+ 881BBF60255B1C1E00761B7E /* SDLAlertManager.h in Headers */ = {isa = PBXBuildFile; fileRef = 881BBF5E255B1C1E00761B7E /* SDLAlertManager.h */; };
+ 881BBF61255B1C1E00761B7E /* SDLAlertManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 881BBF5F255B1C1E00761B7E /* SDLAlertManager.m */; };
881F388D22D904BE00DF6DCE /* SDLCancelInteractionResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 881F388C22D904BE00DF6DCE /* SDLCancelInteractionResponseSpec.m */; };
8829568B207CF68800EF056C /* SmartDeviceLink.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5D61FA1C1A84237100846EE7 /* SmartDeviceLink.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
88295693207CF68800EF056C /* SmartDeviceLink.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 5D61FA1C1A84237100846EE7 /* SmartDeviceLink.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -1585,6 +1590,8 @@
8855F9EC220CBFB700A5C897 /* SDLGetFileSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8855F9EB220CBFB700A5C897 /* SDLGetFileSpec.m */; };
8863747E22D650DE00D2671F /* SDLCloseApplicationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8863747D22D650DE00D2671F /* SDLCloseApplicationSpec.m */; };
88665B6C220B796A00D9DA77 /* SDLPerformAppServiceInteractionResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88665B6B220B796A00D9DA77 /* SDLPerformAppServiceInteractionResponseSpec.m */; };
+ 886E413D2565D0D200F073B8 /* SDLPresentAlertOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 886E413C2565D0D200F073B8 /* SDLPresentAlertOperationSpec.m */; };
+ 886E41412565D11200F073B8 /* SDLAlertManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 886E41402565D11200F073B8 /* SDLAlertManagerSpec.m */; };
8877F5EB1F34A3BE00DC128A /* SDLSendHapticDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8877F5EA1F34A3BE00DC128A /* SDLSendHapticDataSpec.m */; };
8877F5F11F34AA2D00DC128A /* SDLSendHapticDataResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8877F5F01F34AA2D00DC128A /* SDLSendHapticDataResponseSpec.m */; };
887BE4D422272B2200B397C2 /* SDLControlFramePayloadConstantsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 887BE4D322272B2200B397C2 /* SDLControlFramePayloadConstantsSpec.m */; };
@@ -1600,6 +1607,12 @@
8881AFBB2225E7FA00EA870B /* SDLGetCloudAppPropertiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8881AFBA2225E7FA00EA870B /* SDLGetCloudAppPropertiesSpec.m */; };
8881AFC12225EB9300EA870B /* SDLGetCloudAppPropertiesResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8881AFC02225EB9300EA870B /* SDLGetCloudAppPropertiesResponseSpec.m */; };
8886EB982111F4FA008294A5 /* SDLFileManagerConfigurationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8886EB972111F4FA008294A5 /* SDLFileManagerConfigurationSpec.m */; };
+ 8889C2ED2559C7E2004F5966 /* SDLAudioDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8889C2EC2559C7E2004F5966 /* SDLAudioDataSpec.m */; };
+ 8889C2F42559CFAF004F5966 /* SDLAlertAudioData.h in Headers */ = {isa = PBXBuildFile; fileRef = 8889C2F22559CFAF004F5966 /* SDLAlertAudioData.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8889C2F52559CFAF004F5966 /* SDLAlertAudioData.m in Sources */ = {isa = PBXBuildFile; fileRef = 8889C2F32559CFAF004F5966 /* SDLAlertAudioData.m */; };
+ 8889C2FC2559CFE3004F5966 /* SDLAudioData.m in Sources */ = {isa = PBXBuildFile; fileRef = 8889C2FA2559CFE2004F5966 /* SDLAudioData.m */; };
+ 8889C2FD2559CFE3004F5966 /* SDLAudioData.h in Headers */ = {isa = PBXBuildFile; fileRef = 8889C2FB2559CFE3004F5966 /* SDLAudioData.h */; settings = {ATTRIBUTES = (Public, ); }; };
+ 8889C3052559E109004F5966 /* SDLAlertAudioDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 8889C3042559E109004F5966 /* SDLAlertAudioDataSpec.m */; };
888F8700221DF4880052FE4C /* SDLAsynchronousRPCOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 888F86FF221DF4880052FE4C /* SDLAsynchronousRPCOperationSpec.m */; };
889D0B9624D065EE008AD494 /* SDLSubtleAlertResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 889D0B9524D065EE008AD494 /* SDLSubtleAlertResponseSpec.m */; };
889D0B9824D06E52008AD494 /* SDLSubtleAlertSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 889D0B9724D06E52008AD494 /* SDLSubtleAlertSpec.m */; };
@@ -1629,6 +1642,8 @@
88D0E5D624786580009469AB /* SDLSubscribeButtonManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D0E5D524786580009469AB /* SDLSubscribeButtonManagerSpec.m */; };
88D0E5D824786770009469AB /* SDLSubscribeButtonObserverSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D0E5D724786770009469AB /* SDLSubscribeButtonObserverSpec.m */; };
88D2AAE41F682BB20078D5B2 /* SDLLogConstantsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D2AAE31F682BB20078D5B2 /* SDLLogConstantsSpec.m */; };
+ 88D79EED255D8D5B005FACB1 /* SDLPresentAlertOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 88D79EEB255D8D5B005FACB1 /* SDLPresentAlertOperation.h */; };
+ 88D79EEE255D8D5B005FACB1 /* SDLPresentAlertOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 88D79EEC255D8D5B005FACB1 /* SDLPresentAlertOperation.m */; };
88DDD0F9229ECA57002F9623 /* SDLIAPConstantsSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DDD0F8229ECA57002F9623 /* SDLIAPConstantsSpec.m */; };
88DF998D22035CC600477AC1 /* EAAccessory+OCMock.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DF998C22035CC600477AC1 /* EAAccessory+OCMock.m */; };
88DF998F22035D1700477AC1 /* SDLIAPSessionSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 88DF998E22035D1700477AC1 /* SDLIAPSessionSpec.m */; };
@@ -3450,6 +3465,11 @@
880E35B72088F78E00181259 /* SDLSystemCapabilityManagerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLSystemCapabilityManagerSpec.m; sourceTree = "<group>"; };
8816772822208B82001FACFF /* SDLNavigationInstructionSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLNavigationInstructionSpec.m; sourceTree = "<group>"; };
8818ADDC2100FE0C007D6F19 /* SDLTurnSignalSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLTurnSignalSpec.m; sourceTree = "<group>"; };
+ 881BBF4E255AC27000761B7E /* SDLAlertView.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLAlertView.h; path = public/SDLAlertView.h; sourceTree = "<group>"; };
+ 881BBF4F255AC27000761B7E /* SDLAlertView.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLAlertView.m; path = public/SDLAlertView.m; sourceTree = "<group>"; };
+ 881BBF5A255ADB8300761B7E /* SDLAlertViewSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAlertViewSpec.m; sourceTree = "<group>"; };
+ 881BBF5E255B1C1E00761B7E /* SDLAlertManager.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLAlertManager.h; path = private/SDLAlertManager.h; sourceTree = "<group>"; };
+ 881BBF5F255B1C1E00761B7E /* SDLAlertManager.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLAlertManager.m; path = private/SDLAlertManager.m; sourceTree = "<group>"; };
881F388C22D904BE00DF6DCE /* SDLCancelInteractionResponseSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLCancelInteractionResponseSpec.m; sourceTree = "<group>"; };
88295697207CF68800EF056C /* SDL Example Swift.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = "SDL Example Swift.app"; sourceTree = BUILT_PRODUCTS_DIR; };
882FAC4C2209D7EF0062385D /* SDLAppServiceDataSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAppServiceDataSpec.m; sourceTree = "<group>"; };
@@ -3472,6 +3492,8 @@
8855F9EB220CBFB700A5C897 /* SDLGetFileSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLGetFileSpec.m; sourceTree = "<group>"; };
8863747D22D650DE00D2671F /* SDLCloseApplicationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLCloseApplicationSpec.m; sourceTree = "<group>"; };
88665B6B220B796A00D9DA77 /* SDLPerformAppServiceInteractionResponseSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLPerformAppServiceInteractionResponseSpec.m; sourceTree = "<group>"; };
+ 886E413C2565D0D200F073B8 /* SDLPresentAlertOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLPresentAlertOperationSpec.m; sourceTree = "<group>"; };
+ 886E41402565D11200F073B8 /* SDLAlertManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAlertManagerSpec.m; sourceTree = "<group>"; };
8877F5EA1F34A3BE00DC128A /* SDLSendHapticDataSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLSendHapticDataSpec.m; sourceTree = "<group>"; };
8877F5F01F34AA2D00DC128A /* SDLSendHapticDataResponseSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLSendHapticDataResponseSpec.m; sourceTree = "<group>"; };
887BE4D322272B2200B397C2 /* SDLControlFramePayloadConstantsSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLControlFramePayloadConstantsSpec.m; sourceTree = "<group>"; };
@@ -3481,6 +3503,12 @@
8881AFBA2225E7FA00EA870B /* SDLGetCloudAppPropertiesSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLGetCloudAppPropertiesSpec.m; sourceTree = "<group>"; };
8881AFC02225EB9300EA870B /* SDLGetCloudAppPropertiesResponseSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLGetCloudAppPropertiesResponseSpec.m; sourceTree = "<group>"; };
8886EB972111F4FA008294A5 /* SDLFileManagerConfigurationSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLFileManagerConfigurationSpec.m; sourceTree = "<group>"; };
+ 8889C2EC2559C7E2004F5966 /* SDLAudioDataSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAudioDataSpec.m; sourceTree = "<group>"; };
+ 8889C2F22559CFAF004F5966 /* SDLAlertAudioData.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLAlertAudioData.h; path = public/SDLAlertAudioData.h; sourceTree = "<group>"; };
+ 8889C2F32559CFAF004F5966 /* SDLAlertAudioData.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLAlertAudioData.m; path = public/SDLAlertAudioData.m; sourceTree = "<group>"; };
+ 8889C2FA2559CFE2004F5966 /* SDLAudioData.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLAudioData.m; path = public/SDLAudioData.m; sourceTree = "<group>"; };
+ 8889C2FB2559CFE3004F5966 /* SDLAudioData.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = SDLAudioData.h; path = public/SDLAudioData.h; sourceTree = "<group>"; };
+ 8889C3042559E109004F5966 /* SDLAlertAudioDataSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAlertAudioDataSpec.m; sourceTree = "<group>"; };
888F86FF221DF4880052FE4C /* SDLAsynchronousRPCOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLAsynchronousRPCOperationSpec.m; sourceTree = "<group>"; };
889D0B9524D065EE008AD494 /* SDLSubtleAlertResponseSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSubtleAlertResponseSpec.m; sourceTree = "<group>"; };
889D0B9724D06E52008AD494 /* SDLSubtleAlertSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSubtleAlertSpec.m; sourceTree = "<group>"; };
@@ -3512,6 +3540,8 @@
88D0E5D524786580009469AB /* SDLSubscribeButtonManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSubscribeButtonManagerSpec.m; sourceTree = "<group>"; };
88D0E5D724786770009469AB /* SDLSubscribeButtonObserverSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSubscribeButtonObserverSpec.m; sourceTree = "<group>"; };
88D2AAE31F682BB20078D5B2 /* SDLLogConstantsSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLLogConstantsSpec.m; sourceTree = "<group>"; };
+ 88D79EEB255D8D5B005FACB1 /* SDLPresentAlertOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLPresentAlertOperation.h; path = private/SDLPresentAlertOperation.h; sourceTree = "<group>"; };
+ 88D79EEC255D8D5B005FACB1 /* SDLPresentAlertOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLPresentAlertOperation.m; path = private/SDLPresentAlertOperation.m; sourceTree = "<group>"; };
88DDD0F8229ECA57002F9623 /* SDLIAPConstantsSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLIAPConstantsSpec.m; sourceTree = "<group>"; };
88DF998C22035CC600477AC1 /* EAAccessory+OCMock.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = "EAAccessory+OCMock.m"; sourceTree = "<group>"; };
88DF998E22035D1700477AC1 /* SDLIAPSessionSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLIAPSessionSpec.m; sourceTree = "<group>"; };
@@ -4447,6 +4477,7 @@
5D0A736F203F0C450001595D /* Screen */ = {
isa = PBXGroup;
children = (
+ 8889C2E12559AE6C004F5966 /* Alert */,
5D92935720B33D3C00FCC775 /* Choice Set */,
5D339CE5207C0651000CC364 /* Menu */,
5D0A737F203F23D10001595D /* Soft Button */,
@@ -4545,6 +4576,7 @@
5D1BF6AA2047429C00D36881 /* Utilities */ = {
isa = PBXGroup;
children = (
+ 8889C2E22559AE82004F5966 /* Audio */,
B3A9DB1425D4AD1800CDFD21 /* SDLImageResolution + StreamingVideoExtensions.h */,
B3A9DB1525D4AD1800CDFD21 /* SDLImageResolution + StreamingVideoExtensions.m */,
4ABB25D024F7E7630061BF55 /* SDLImageField+ScreenManagerExtensions.h */,
@@ -6305,11 +6337,13 @@
5DAD5F8120507DE40025624C /* Screen */ = {
isa = PBXGroup;
children = (
+ 8889C2EB2559C7AA004F5966 /* Alert */,
5DE35E4320CAFBEA0034BE5A /* Choice Set */,
5DF40B24208FA7C500DD6FDA /* Menu */,
5DAD5F8220507DED0025624C /* Soft Button */,
88D0E5D42478656B009469AB /* Subscribe Button */,
5DAD5F8320507DF30025624C /* Text and Graphic */,
+ 8889C3082559E11F004F5966 /* Utilities */,
4AD1F16A2559952D00637FE1 /* Voice Command */,
5DAD5F8420507E1F0025624C /* SDLScreenManagerSpec.m */,
B3A0BA212593FE7400CC3BDF /* SDLCarWindowSpec.m */,
@@ -6827,6 +6861,48 @@
name = "Data Session";
sourceTree = "<group>";
};
+ 8889C2E12559AE6C004F5966 /* Alert */ = {
+ isa = PBXGroup;
+ children = (
+ 88D79EEA255D87F8005FACB1 /* Operations */,
+ 8889C2F22559CFAF004F5966 /* SDLAlertAudioData.h */,
+ 8889C2F32559CFAF004F5966 /* SDLAlertAudioData.m */,
+ 881BBF4E255AC27000761B7E /* SDLAlertView.h */,
+ 881BBF4F255AC27000761B7E /* SDLAlertView.m */,
+ 881BBF5E255B1C1E00761B7E /* SDLAlertManager.h */,
+ 881BBF5F255B1C1E00761B7E /* SDLAlertManager.m */,
+ );
+ name = Alert;
+ sourceTree = "<group>";
+ };
+ 8889C2E22559AE82004F5966 /* Audio */ = {
+ isa = PBXGroup;
+ children = (
+ 8889C2FB2559CFE3004F5966 /* SDLAudioData.h */,
+ 8889C2FA2559CFE2004F5966 /* SDLAudioData.m */,
+ );
+ name = Audio;
+ sourceTree = "<group>";
+ };
+ 8889C2EB2559C7AA004F5966 /* Alert */ = {
+ isa = PBXGroup;
+ children = (
+ 8889C3042559E109004F5966 /* SDLAlertAudioDataSpec.m */,
+ 881BBF5A255ADB8300761B7E /* SDLAlertViewSpec.m */,
+ 886E413C2565D0D200F073B8 /* SDLPresentAlertOperationSpec.m */,
+ 886E41402565D11200F073B8 /* SDLAlertManagerSpec.m */,
+ );
+ name = Alert;
+ sourceTree = "<group>";
+ };
+ 8889C3082559E11F004F5966 /* Utilities */ = {
+ isa = PBXGroup;
+ children = (
+ 8889C2EC2559C7E2004F5966 /* SDLAudioDataSpec.m */,
+ );
+ name = Utilities;
+ sourceTree = "<group>";
+ };
88A098AB2476F08F00A50005 /* Subscribe Button */ = {
isa = PBXGroup;
children = (
@@ -6900,6 +6976,15 @@
name = "Subscribe Button";
sourceTree = "<group>";
};
+ 88D79EEA255D87F8005FACB1 /* Operations */ = {
+ isa = PBXGroup;
+ children = (
+ 88D79EEB255D8D5B005FACB1 /* SDLPresentAlertOperation.h */,
+ 88D79EEC255D8D5B005FACB1 /* SDLPresentAlertOperation.m */,
+ );
+ name = Operations;
+ sourceTree = "<group>";
+ };
88DF998A22035CA400477AC1 /* TCP */ = {
isa = PBXGroup;
children = (
@@ -7167,6 +7252,7 @@
4ABB28FE24F82BE90061BF55 /* SDLAddSubMenu.h in Headers */,
4ABB299624F845440061BF55 /* SDLRegisterAppInterface.h in Headers */,
4ABB27DE24F800CA0061BF55 /* SDLPredefinedLayout.h in Headers */,
+ 88D79EED255D8D5B005FACB1 /* SDLPresentAlertOperation.h in Headers */,
4ABB27E424F800CA0061BF55 /* SDLPrerecordedSpeech.h in Headers */,
4ABB2A2E24F847980061BF55 /* SDLDeleteCommandResponse.h in Headers */,
4ABB277524F7FE910061BF55 /* SDLIgnitionStatus.h in Headers */,
@@ -7216,6 +7302,7 @@
5D9FDA991F2A7D3F00A495C8 /* emhashmap.h in Headers */,
4ABB255F24F7E59E0061BF55 /* SDLPermissionConstants.h in Headers */,
4ABB270324F7FB8F0061BF55 /* SDLButtonName.h in Headers */,
+ 881BBF50255AC27000761B7E /* SDLAlertView.h in Headers */,
4ABB25B324F7E6F60061BF55 /* SDLSoftButtonTransitionOperation.h in Headers */,
4ABB24C624F592900061BF55 /* SDLError.h in Headers */,
5D9FDA901F2A7D3400A495C8 /* bson_array.h in Headers */,
@@ -7304,6 +7391,7 @@
4A8BD33824F945B4000945E3 /* SDLControlFramePayloadConstants.h in Headers */,
4ABB256724F7E5B80061BF55 /* SDLRPCPermissionStatus.h in Headers */,
4ABB286724F828E00061BF55 /* SDLVehicleDataStatus.h in Headers */,
+ 8889C2F42559CFAF004F5966 /* SDLAlertAudioData.h in Headers */,
4A8BD25024F93135000945E3 /* SDLMetadataTags.h in Headers */,
4ABB2A3424F847980061BF55 /* SDLCancelInteractionResponse.h in Headers */,
4ABB2AF424F849CF0061BF55 /* SDLGenericResponse.h in Headers */,
@@ -7415,6 +7503,7 @@
4ABB284224F828630061BF55 /* SDLTextFieldName.h in Headers */,
4ABB2A3D24F847980061BF55 /* SDLEncodedSyncPDataResponse.h in Headers */,
4ABB24C924F593090061BF55 /* SDLStreamingProtocolDelegate.h in Headers */,
+ 8889C2FD2559CFE3004F5966 /* SDLAudioData.h in Headers */,
4ABB275F24F7FE1F0061BF55 /* SDLFuelType.h in Headers */,
4A8BD3A024F9474B000945E3 /* SDLIAPDataSessionDelegate.h in Headers */,
B3838A28257C5CE600420C11 /* SDLDoorStatusType.h in Headers */,
@@ -7670,6 +7759,7 @@
4ABB25BC24F7E70E0061BF55 /* SDLSoftButtonObject.h in Headers */,
4ABB256324F7E5AA0061BF55 /* SDLPermissionElement.h in Headers */,
4ABB2AE624F848270061BF55 /* SDLUnsubscribeVehicleDataResponse.h in Headers */,
+ 881BBF60255B1C1E00761B7E /* SDLAlertManager.h in Headers */,
4ABB258524F7E62D0061BF55 /* SDLChoiceSetDelegate.h in Headers */,
4ABB2B8D24F8504A0061BF55 /* SDLHMISettingsControlData.h in Headers */,
4ABB25F824F7E7EF0061BF55 /* SDLTouchManager.h in Headers */,
@@ -7958,6 +8048,7 @@
4ABB291624F842160061BF55 /* SDLCloseApplication.m in Sources */,
4ABB2B4F24F84EF50061BF55 /* SDLClimateControlData.m in Sources */,
4ABB2A2824F847980061BF55 /* SDLDeleteCommandResponse.m in Sources */,
+ 881BBF51255AC27000761B7E /* SDLAlertView.m in Sources */,
4ABB2AD224F848130061BF55 /* SDLSubscribeWayPointsResponse.m in Sources */,
4ABB286224F828E00061BF55 /* SDLVehicleDataResultCode.m in Sources */,
4ABB2B8A24F8504A0061BF55 /* SDLHMISettingsControlCapabilities.m in Sources */,
@@ -7994,6 +8085,7 @@
4ABB2AB724F847F40061BF55 /* SDLSetGlobalPropertiesResponse.m in Sources */,
C9DFFE79257ACE0000F7D57A /* SDLSeekStreamingIndicator.m in Sources */,
4ABB2B0B24F84D950061BF55 /* SDLAppServiceData.m in Sources */,
+ 88D79EEE255D8D5B005FACB1 /* SDLPresentAlertOperation.m in Sources */,
4ABB282C24F824E70061BF55 /* SDLTBTState.m in Sources */,
4ABB27DF24F800CA0061BF55 /* SDLPowerModeQualificationStatus.m in Sources */,
4ABB269D24F7F9710061BF55 /* SDLRPCParameterNames.m in Sources */,
@@ -8002,6 +8094,7 @@
4ABB28E224F82A6A0061BF55 /* SDLOnLanguageChange.m in Sources */,
4ABB29DD24F846880061BF55 /* SDLSubscribeButton.m in Sources */,
4ABB25F324F7E7EF0061BF55 /* SDLPinchGesture.m in Sources */,
+ 8889C2F52559CFAF004F5966 /* SDLAlertAudioData.m in Sources */,
4A8BD39524F94731000945E3 /* SDLIAPConstants.m in Sources */,
4ABB2B8C24F8504A0061BF55 /* SDLGrid.m in Sources */,
4ABB299024F845440061BF55 /* SDLRegisterAppInterface.m in Sources */,
@@ -8262,6 +8355,7 @@
4ABB254824F7E49D0061BF55 /* SDLLockScreenStatusManager.m in Sources */,
4A8BD2B724F935BC000945E3 /* SDLSoftButton.m in Sources */,
4ABB2AD824F8481D0061BF55 /* SDLSystemRequestResponse.m in Sources */,
+ 8889C2FC2559CFE3004F5966 /* SDLAudioData.m in Sources */,
4ABB28E924F82A6A0061BF55 /* SDLOnWayPointChange.m in Sources */,
4A8BD24324F93135000945E3 /* SDLMetadataTags.m in Sources */,
4ABB283124F824E70061BF55 /* SDLTemperatureUnit.m in Sources */,
@@ -8402,6 +8496,7 @@
4A8BD27A24F9343F000945E3 /* SDLRemoteControlCapabilities.m in Sources */,
4ABB291824F842160061BF55 /* SDLChangeRegistration.m in Sources */,
4A8BD26224F933C7000945E3 /* SDLNavigationInstruction.m in Sources */,
+ 881BBF61255B1C1E00761B7E /* SDLAlertManager.m in Sources */,
4ABB29EB24F847360061BF55 /* SDLUnsubscribeWayPoints.m in Sources */,
4ABB25E124F7E7980061BF55 /* SDLStreamingMediaConfiguration.m in Sources */,
4ABB299424F845440061BF55 /* SDLSendLocation.m in Sources */,
@@ -8765,6 +8860,7 @@
162E83761A9BDE8B00906325 /* SDLChoiceSpec.m in Sources */,
162E83571A9BDE8B00906325 /* SDLGetDTCsResponseSpec.m in Sources */,
5D4346471E6F0BDA00B639C6 /* SDLLogFileModuleSpec.m in Sources */,
+ 8889C3052559E109004F5966 /* SDLAlertAudioDataSpec.m in Sources */,
889D0B9824D06E52008AD494 /* SDLSubtleAlertSpec.m in Sources */,
88A1CF1E21669AC7001ACC75 /* SDLLifecycleConfigurationUpdateSpec.m in Sources */,
1EE8C4581F387ABD00FDC2CF /* SDLButtonPressResponseSpec.m in Sources */,
@@ -8805,6 +8901,7 @@
8863747E22D650DE00D2671F /* SDLCloseApplicationSpec.m in Sources */,
162E834B1A9BDE8B00906325 /* SDLAlertManeuverResponseSpec.m in Sources */,
162E833E1A9BDE8B00906325 /* SDLShowSpec.m in Sources */,
+ 886E41412565D11200F073B8 /* SDLAlertManagerSpec.m in Sources */,
5D6035D8202CF5C900A429C9 /* TestRequestProgressResponse.m in Sources */,
162E83241A9BDE8B00906325 /* SDLAlertManeuverSpec.m in Sources */,
5D43466F1E6F55BD00B639C6 /* SDLLogManagerSpec.m in Sources */,
@@ -8880,10 +8977,12 @@
162E83821A9BDE8B00906325 /* SDLImageSpec.m in Sources */,
162E834A1A9BDE8B00906325 /* SDLAddSubMenuResponseSpec.m in Sources */,
162E830C1A9BDE8B00906325 /* SDLWarningLightStatusSpec.m in Sources */,
+ 881BBF5B255ADB8300761B7E /* SDLAlertViewSpec.m in Sources */,
1EE8C45F1F3884FF00FDC2CF /* SDLSetInteriorVehicleDataSpec.m in Sources */,
162E82E71A9BDE8B00906325 /* SDLKeyboardEventSpec.m in Sources */,
162E834E1A9BDE8B00906325 /* SDLCreateInteractionChoiceSetResponseSpec.m in Sources */,
DA9F7EB61DCC086A00ACAE48 /* SDLOasisAddressSpec.m in Sources */,
+ 8889C2ED2559C7E2004F5966 /* SDLAudioDataSpec.m in Sources */,
162E833F1A9BDE8B00906325 /* SDLSliderSpec.m in Sources */,
162E838C1A9BDE8B00906325 /* SDLSoftButtonSpec.m in Sources */,
5DA23FF81F2FAF2D009C0313 /* SDLControlFramePayloadRPCStartServiceAckSpec.m in Sources */,
@@ -8974,6 +9073,7 @@
1680B1171A9CD7AD00DBD79E /* SDLProtocolSpec.m in Sources */,
162E836D1A9BDE8B00906325 /* SDLUnregisterAppInterfaceResponseSpec.m in Sources */,
162E837C1A9BDE8B00906325 /* SDLECallInfoSpec.m in Sources */,
+ 886E413D2565D0D200F073B8 /* SDLPresentAlertOperationSpec.m in Sources */,
1EAA47782036BA74000FE74B /* SDLAudioControlCapabilitiesSpec.m in Sources */,
5DB1BCD51D243A8E002FFC37 /* SDLUploadFileOperationSpec.m in Sources */,
162E83401A9BDE8B00906325 /* SDLSpeakSpec.m in Sources */,
diff --git a/SmartDeviceLink/private/SDLAlertManager.h b/SmartDeviceLink/private/SDLAlertManager.h
new file mode 100644
index 000000000..c80645b20
--- /dev/null
+++ b/SmartDeviceLink/private/SDLAlertManager.h
@@ -0,0 +1,52 @@
+//
+// SDLAlertManager.h
+// SmartDeviceLink
+//
+// Created by Nicole on 11/10/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLAlertView;
+@class SDLFileManager;
+@class SDLPermissionManager;
+@class SDLSystemCapabilityManager;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Handler called when the alert either dismisses from the screen or it has failed to present
+typedef void(^SDLAlertCompletionHandler)(NSError *__nullable error);
+
+/// An alert manager that handles uploading images and audio needed by an alert, sending an alert and cancelling an alert.
+@interface SDLAlertManager : NSObject
+
+/// Initialize the manager with required dependencies
+/// @param connectionManager The connection manager object for sending RPCs
+/// @param fileManager The file manager object for uploading files
+/// @param systemCapabilityManager The system capability manager object for reading window capabilities
+/// @param permissionManager The permission manager object for checking permissions
+/// @return The alert manager
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager permissionManager:(SDLPermissionManager *)permissionManager;
+
+/// Starts the manager.
+- (void)start;
+
+/// Stops the manager.
+- (void)stop;
+
+/// Present the alert on the screen.
+///
+/// If the alert contains an audio indication with a file that needs to be uploaded, it will be uploaded before presenting the alert. If the alert contains soft buttons with images, they will be uploaded before presenting the alert. If the alert contains an icon, that will be uploaded before presenting the alert.
+///
+/// The handler will be called when the alert either dismisses from the screen or it has failed to present. If the error value in the handler is present, then the alert failed to appear or was aborted, if not, then the alert dismissed without error. The error will contain `userInfo` with information on how long to wait before retrying.
+///
+/// @param alert Alert to be presented
+/// @param handler The handler to be called when the alert either dismisses from the screen or it has failed to present
+- (void)presentAlert:(SDLAlertView *)alert withCompletionHandler:(nullable SDLAlertCompletionHandler)handler;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLAlertManager.m b/SmartDeviceLink/private/SDLAlertManager.m
new file mode 100644
index 000000000..2dc774869
--- /dev/null
+++ b/SmartDeviceLink/private/SDLAlertManager.m
@@ -0,0 +1,171 @@
+//
+// SDLAlertManager.m
+// SmartDeviceLink
+//
+// Created by Nicole on 11/10/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAlertManager.h"
+
+#import "SDLAlertView.h"
+#import "SDLDisplayCapability.h"
+#import "SDLGlobals.h"
+#import "SDLLogMacros.h"
+#import "SDLNotificationConstants.h"
+#import "SDLOnHMIStatus.h"
+#import "SDLPermissionManager.h"
+#import "SDLPredefinedWindows.h"
+#import "SDLPresentAlertOperation.h"
+#import "SDLRPCNotificationNotification.h"
+#import "SDLSystemCapability.h"
+#import "SDLSystemCapabilityManager.h"
+#import "SDLWindowCapability.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+// Assigns a set range of unique cancel ids in order to prevent overlap with other sub-screen managers that use cancel ids. If the max cancel id is reached, generation starts over from the cancel id min value.
+UInt16 const AlertCancelIdMin = 1;
+UInt16 const AlertCancelIdMax = 100;
+
+@interface SDLAlertManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager;
+@property (weak, nonatomic) SDLPermissionManager *permissionManager;
+
+@property (copy, nonatomic, nullable) SDLWindowCapability *currentWindowCapability;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+@property (copy, nonatomic) dispatch_queue_t readWriteQueue;
+
+@property (assign, nonatomic) UInt16 nextCancelId;
+@property (assign, nonatomic) BOOL isAlertRPCAllowed;
+
+@end
+
+@implementation SDLAlertManager
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager permissionManager:(SDLPermissionManager *)permissionManager {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _systemCapabilityManager = systemCapabilityManager;
+ _permissionManager = permissionManager;
+ _transactionQueue = [self sdl_newTransactionQueue];
+
+ _readWriteQueue = dispatch_queue_create_with_target("com.sdl.screenManager.alertManager.readWriteQueue", DISPATCH_QUEUE_SERIAL, [SDLGlobals sharedGlobals].sdlProcessingQueue);
+ _nextCancelId = AlertCancelIdMin;
+
+ _currentWindowCapability = self.systemCapabilityManager.defaultMainWindowCapability;
+
+ return self;
+}
+
+- (void)start {
+ SDLLogD(@"Starting manager");
+
+ __weak typeof(self) weakself = self;
+ [self.permissionManager subscribeToRPCPermissions:@[[[SDLPermissionElement alloc] initWithRPCName:SDLRPCFunctionNameAlert parameterPermissions:nil]] groupType:SDLPermissionGroupTypeAny withHandler:^(NSDictionary<SDLRPCFunctionName,SDLRPCPermissionStatus *> * _Nonnull updatedPermissionStatuses, SDLPermissionGroupStatus status) {
+ weakself.isAlertRPCAllowed = (status == SDLPermissionGroupStatusAllowed);
+
+ [weakself sdl_updateTransactionQueueSuspended];
+ }];
+ [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate)];
+}
+
+- (void)stop {
+ SDLLogD(@"Stopping manager");
+
+ _currentWindowCapability = nil;
+ _nextCancelId = AlertCancelIdMin;
+
+ [_transactionQueue cancelAllOperations];
+ self.transactionQueue = [self sdl_newTransactionQueue];
+
+ [self.permissionManager removeAllObservers];
+ [self.systemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self];
+}
+
+- (void)presentAlert:(SDLAlertView *)alert withCompletionHandler:(nullable SDLAlertCompletionHandler)handler {
+ SDLPresentAlertOperation *op = [[SDLPresentAlertOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager systemCapabilityManager:self.systemCapabilityManager currentWindowCapability:self.currentWindowCapability alertView:alert cancelID:self.nextCancelId];
+
+ __weak typeof(op) weakPreloadOp = op;
+ op.completionBlock = ^{
+ SDLLogD(@"Alert finished presenting: %@", alert);
+
+ if (handler != nil) {
+ handler(weakPreloadOp.error);
+ }
+ };
+
+ [self.transactionQueue addOperation:op];
+}
+
+/// Creates a new serial queue. If an alert is already being presented when a new alert is added to the queue, the newest alert will not be sent until module dismisses the previous alert.
+/// The queue is initially suspended until the manager knows it can send the `Alert` RPCS without getting a disallowed response.
+/// @return A concurrent operation queue
+- (NSOperationQueue *)sdl_newTransactionQueue {
+ NSOperationQueue *queue = [[NSOperationQueue alloc] init];
+ queue.name = @"com.sdl.screenManager.alertManager.transactionQueue";
+ queue.maxConcurrentOperationCount = 1;
+ queue.qualityOfService = NSQualityOfServiceUserInteractive;
+ queue.underlyingQueue = [SDLGlobals sharedGlobals].sdlConcurrentQueue;
+ queue.suspended = YES;
+
+ return queue;
+}
+
+/// Suspend the queue if the window capabilities are nil (we assume that text and graphics are not supported yet)
+- (void)sdl_updateTransactionQueueSuspended {
+ if (self.currentWindowCapability == nil || !self.isAlertRPCAllowed) {
+ SDLLogD(@"Suspending the transaction queue. Window capabilities is nil: %@, alert has permission be sent at the current HMI level: %@", (self.currentWindowCapability == nil ? @"YES" : @"NO"), self.isAlertRPCAllowed ? @"YES" : @"NO");
+ self.transactionQueue.suspended = YES;
+ } else {
+ SDLLogD(@"Starting the transaction queue");
+ self.transactionQueue.suspended = NO;
+ }
+}
+
+/// Updates pending operations in the queue with the new window capability (i.e. the window capability will change when the template changes)
+- (void)sdl_updatePendingOperationsWithNewWindowCapability {
+ for (NSOperation *operation in self.transactionQueue.operations) {
+ if (operation.isExecuting) { continue; }
+
+ ((SDLPresentAlertOperation *)operation).currentWindowCapability = self.currentWindowCapability;
+ }
+}
+
+#pragma mark - Observers
+
+/// Called when the current window capabilities have updated.
+- (void)sdl_displayCapabilityDidUpdate {
+ self.currentWindowCapability = [self.systemCapabilityManager defaultMainWindowCapability];
+ [self sdl_updateTransactionQueueSuspended];
+ [self sdl_updatePendingOperationsWithNewWindowCapability];
+}
+
+#pragma mark - Getters
+
+/// Generates a `cancelID` for an Alert `CancelInteraction` request. `cancelID`s do not need to be unique for different RPC functions, however, we will set a max value for `cancelID`s so if a developer, for some reason, is using both the alert manager and the `Alert` RPC they can use any value above the max `cancelID` without worrying about conflicts. Once an alert with the associated `cancelID` has been dismissed, the `cancelID` can be reused so it is very unlikely there will be conflicts with an already existing generated `cancelID`.
+- (UInt16)nextCancelId {
+ __block UInt16 cancelId = 0;
+ [SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
+ cancelId = self->_nextCancelId;
+ if (cancelId >= AlertCancelIdMax) {
+ self->_nextCancelId = AlertCancelIdMin;
+ } else {
+ self->_nextCancelId = cancelId + 1;
+ }
+ }];
+
+ return cancelId;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLChoiceSetManager.m b/SmartDeviceLink/private/SDLChoiceSetManager.m
index 0f2cb67ad..926b7043d 100644
--- a/SmartDeviceLink/private/SDLChoiceSetManager.m
+++ b/SmartDeviceLink/private/SDLChoiceSetManager.m
@@ -82,7 +82,10 @@ typedef NSNumber * SDLChoiceId;
@end
UInt16 const ChoiceCellIdMin = 1;
-UInt16 const ChoiceCellCancelIdMin = 1;
+
+// Assigns a set range of unique cancel ids in order to prevent overlap with other sub-screen managers that use cancel ids. If the max cancel id is reached, generation starts over from the cancel id min value.
+UInt16 const ChoiceCellCancelIdMin = 101;
+UInt16 const ChoiceCellCancelIdMax = 200;
@implementation SDLChoiceSetManager
@@ -117,7 +120,7 @@ UInt16 const ChoiceCellCancelIdMin = 1;
- (void)start {
SDLLogD(@"Starting manager");
- [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate:)];
+ [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate)];
if ([self.currentState isEqualToString:SDLChoiceManagerStateShutdown]) {
[self.stateMachine transitionToState:SDLChoiceManagerStateCheckingVoiceOptional];
@@ -552,7 +555,11 @@ UInt16 const ChoiceCellCancelIdMin = 1;
__block UInt16 cancelId = 0;
[SDLGlobals runSyncOnSerialSubQueue:self.readWriteQueue block:^{
cancelId = self->_nextCancelId;
- self->_nextCancelId = cancelId + 1;
+ if (cancelId >= ChoiceCellCancelIdMax) {
+ self->_nextCancelId = ChoiceCellCancelIdMin;
+ } else {
+ self->_nextCancelId = cancelId + 1;
+ }
}];
return cancelId;
@@ -564,21 +571,8 @@ UInt16 const ChoiceCellCancelIdMin = 1;
#pragma mark - RPC Responses / Notifications
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability {
- NSArray<SDLDisplayCapability *> *capabilities = systemCapability.displayCapabilities;
- if (capabilities == nil || capabilities.count == 0) {
- self.currentWindowCapability = nil;
- } else {
- SDLDisplayCapability *mainDisplay = capabilities[0];
- for (SDLWindowCapability *windowCapability in mainDisplay.windowCapabilities) {
- NSUInteger currentWindowID = windowCapability.windowID != nil ? windowCapability.windowID.unsignedIntegerValue : SDLPredefinedWindowsDefaultWindow;
- if (currentWindowID != SDLPredefinedWindowsDefaultWindow) { continue; }
-
- self.currentWindowCapability = windowCapability;
- break;
- }
- }
-
+- (void)sdl_displayCapabilityDidUpdate {
+ self.currentWindowCapability = self.systemCapabilityManager.defaultMainWindowCapability;
[self sdl_updateTransactionQueueSuspended];
}
diff --git a/SmartDeviceLink/private/SDLError.h b/SmartDeviceLink/private/SDLError.h
index 4b3099a84..1621d8f1b 100644
--- a/SmartDeviceLink/private/SDLError.h
+++ b/SmartDeviceLink/private/SDLError.h
@@ -65,6 +65,11 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSError *)sdl_choiceSetManager_failedToCreateMenuItems;
+ (NSError *)sdl_choiceSetManager_incorrectState:(NSString *)state;
+#pragma mark Alert Manager
+
++ (NSError *)sdl_alertManager_presentationFailedWithError:(NSError *)error tryAgainTime:(int)tryAgainTime;
++ (NSError *)sdl_alertManager_alertDataInvalid;
++ (NSError *)sdl_alertManager_alertAudioFileNotSupported;
#pragma mark System Capability Manager
@@ -99,6 +104,8 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSException *)sdl_missingHandlerException;
+ (NSException *)sdl_missingIdException;
+ (NSException *)sdl_missingFilesException;
++ (NSException *)sdl_invalidTTSSpeechCapabilitiesException;
++ (NSException *)sdl_invalidAlertSoftButtonStatesException;
+ (NSException *)sdl_invalidSoftButtonStateException;
+ (NSException *)sdl_carWindowOrientationException;
+ (NSException *)sdl_invalidLockscreenSetupException;
diff --git a/SmartDeviceLink/private/SDLError.m b/SmartDeviceLink/private/SDLError.m
index 0874e0674..35818d361 100644
--- a/SmartDeviceLink/private/SDLError.m
+++ b/SmartDeviceLink/private/SDLError.m
@@ -302,6 +302,37 @@ NS_ASSUME_NONNULL_BEGIN
return [NSError errorWithDomain:SDLErrorDomainChoiceSetManager code:SDLChoiceSetManagerErrorInvalidState userInfo:userInfo];
}
+#pragma mark Alert Manager
+
++ (NSError *)sdl_alertManager_presentationFailedWithError:(NSError *)error tryAgainTime:(int)tryAgainTime {
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: @"The alert presentation failed",
+ NSLocalizedFailureReasonErrorKey: @"Either the alert failed to present on the module or it was dismissed early after being shown",
+ NSLocalizedRecoverySuggestionErrorKey: @"Please check the \"error\" key and the \"tryAgainTime\" keys for more information",
+ @"tryAgainTime": @(tryAgainTime),
+ @"error": error
+ };
+ return [NSError errorWithDomain:SDLErrorDomainAlertManager code:SDLAlertManagerPresentationError userInfo:userInfo];
+}
+
++ (NSError *)sdl_alertManager_alertDataInvalid {
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: @"The alert data is invalid",
+ NSLocalizedFailureReasonErrorKey: @"At least either text, secondaryText or audio needs to be provided",
+ NSLocalizedRecoverySuggestionErrorKey: @"Make sure to set at least the text, secondaryText or audio properties on the SDLAlertView"
+ };
+ return [NSError errorWithDomain:SDLErrorDomainAlertManager code:SDLAlertManagerInvalidDataError userInfo:userInfo];
+}
+
++ (NSError *)sdl_alertManager_alertAudioFileNotSupported {
+ NSDictionary *userInfo = @{
+ NSLocalizedDescriptionKey: @"The module does not support the use of only audio file data in an alert",
+ NSLocalizedFailureReasonErrorKey: @"The alert has no data and can not be sent to the module",
+ NSLocalizedRecoverySuggestionErrorKey: @"The use of audio file data in an alert is only supported on modules supporting RPC Spec v5.0 or newer"
+ };
+ return [NSError errorWithDomain:SDLErrorDomainAlertManager code:SDLAlertManagerInvalidDataError userInfo:userInfo];
+}
+
#pragma mark System Capability Manager
+ (NSError *)sdl_systemCapabilityManager_moduleDoesNotSupportSystemCapabilities {
@@ -432,6 +463,14 @@ NS_ASSUME_NONNULL_BEGIN
userInfo:nil];
}
++ (NSException *)sdl_invalidTTSSpeechCapabilitiesException {
+ return [NSException exceptionWithName:@"InvalidTTSSpeechCapabilities" reason:@"Attempting to create a text-to-speech string with an invalid phonetic type. The phoneticType must be of type `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED`." userInfo:nil];
+}
+
++ (NSException *)sdl_invalidAlertSoftButtonStatesException {
+ return [NSException exceptionWithName:@"InvalidSoftButtonStates" reason:@"Attempting to create a soft button for an Alert with more than one state. Alerts only support soft buttons with one state" userInfo:nil];
+}
+
+ (NSException *)sdl_invalidSoftButtonStateException {
return [NSException exceptionWithName:@"InvalidSoftButtonState" reason:@"Attempting to transition to a state that does not exist" userInfo:nil];
}
diff --git a/SmartDeviceLink/private/SDLLifecycleManager.m b/SmartDeviceLink/private/SDLLifecycleManager.m
index f71223bc9..45e84e531 100644
--- a/SmartDeviceLink/private/SDLLifecycleManager.m
+++ b/SmartDeviceLink/private/SDLLifecycleManager.m
@@ -169,7 +169,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
_permissionManager = [[SDLPermissionManager alloc] init];
_lockScreenManager = [[SDLLockScreenManager alloc] initWithConfiguration:_configuration.lockScreenConfig notificationDispatcher:_notificationDispatcher presenter:[[SDLLockScreenPresenter alloc] init]];
_systemCapabilityManager = [[SDLSystemCapabilityManager alloc] initWithConnectionManager:self];
- _screenManager = [[SDLScreenManager alloc] initWithConnectionManager:self fileManager:_fileManager systemCapabilityManager:_systemCapabilityManager];
+ _screenManager = [[SDLScreenManager alloc] initWithConnectionManager:self fileManager:_fileManager systemCapabilityManager:_systemCapabilityManager permissionManager:_permissionManager];
if ([self.class sdl_isStreamingConfiguration:self.configuration]) {
_streamManager = [[SDLStreamingMediaManager alloc] initWithConnectionManager:self configuration:configuration systemCapabilityManager:self.systemCapabilityManager];
diff --git a/SmartDeviceLink/private/SDLLogFileModuleMap.m b/SmartDeviceLink/private/SDLLogFileModuleMap.m
index 29834a1d0..44b6b47dd 100644
--- a/SmartDeviceLink/private/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/private/SDLLogFileModuleMap.m
@@ -36,6 +36,7 @@
[self sdl_screenManagerSubscribeButtonModule],
[self sdl_screenManagerMenuModule],
[self sdl_screenManagerChoiceSetModule],
+ [self sdl_screenManagerAlertModule],
[self sdl_utilitiesModule]]];
}
@@ -129,6 +130,10 @@
return [SDLLogFileModule moduleWithName:@"Screen/SubscribeButton" files:[NSSet setWithArray:@[@"SDLSubscribeButtonManager", @"SDLSubscribeButtonObserver"]]];
}
++ (SDLLogFileModule *)sdl_screenManagerAlertModule {
+ return [SDLLogFileModule moduleWithName:@"Screen/Alert" files:[NSSet setWithArray:@[@"SDLAlertManager", @"SDLAlertView", @"SDLAlertAudioData", @"SDLPresentAlertOperation"]]];
+}
+
+ (SDLLogFileModule *)sdl_screenManagerMenuModule {
return [SDLLogFileModule moduleWithName:@"Screen/Menu" files:[NSSet setWithArray:@[@"SDLMenuManager", @"SDLVoiceCommandManager", @"SDLVoiceCommandUpdateOperation"]]];
}
diff --git a/SmartDeviceLink/private/SDLMenuManager.m b/SmartDeviceLink/private/SDLMenuManager.m
index a01861d6f..a45d171f2 100644
--- a/SmartDeviceLink/private/SDLMenuManager.m
+++ b/SmartDeviceLink/private/SDLMenuManager.m
@@ -105,7 +105,7 @@ UInt32 const MenuCellIdMin = 1;
}
- (void)start {
- [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate:)];
+ [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate)];
}
- (void)stop {
@@ -673,8 +673,8 @@ UInt32 const MenuCellIdMin = 1;
params.menuName = cell.title;
params.parentID = cell.parentCellId != UINT32_MAX ? @(cell.parentCellId) : nil;
params.position = @(position);
- params.tertiaryText = cell.tertiaryText;
- params.secondaryText = cell.secondaryText;
+ params.secondaryText = (cell.secondaryText.length == 0) ? nil : cell.secondaryText;
+ params.tertiaryText = (cell.tertiaryText.length == 0) ? nil : cell.tertiaryText;
command.menuParams = params;
command.vrCommands = (cell.voiceCommands.count == 0) ? nil : cell.voiceCommands;
@@ -695,7 +695,11 @@ UInt32 const MenuCellIdMin = 1;
} else {
submenuLayout = self.menuConfiguration.defaultSubmenuLayout;
}
- return [[SDLAddSubMenu alloc] initWithMenuID:cell.cellId menuName:cell.title position:@(position) menuIcon:icon menuLayout:submenuLayout parentID:nil secondaryText:cell.secondaryText tertiaryText:cell.tertiaryText secondaryImage:secondaryImage];
+
+ NSString *secondaryText = (cell.secondaryText.length == 0) ? nil : cell.secondaryText;
+ NSString *tertiaryText = (cell.tertiaryText.length == 0) ? nil : cell.tertiaryText;
+
+ return [[SDLAddSubMenu alloc] initWithMenuID:cell.cellId menuName:cell.title position:@(position) menuIcon:icon menuLayout:submenuLayout parentID:nil secondaryText:secondaryText tertiaryText:tertiaryText secondaryImage:secondaryImage];
}
#pragma mark - Calling handlers
@@ -724,8 +728,7 @@ UInt32 const MenuCellIdMin = 1;
[self sdl_callHandlerForCells:self.menuCells command:onCommand];
}
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability {
- // We won't use the object in the parameter but the convenience method of the system capability manager
+- (void)sdl_displayCapabilityDidUpdate {
self.windowCapability = self.systemCapabilityManager.defaultMainWindowCapability;
}
diff --git a/SmartDeviceLink/private/SDLPresentAlertOperation.h b/SmartDeviceLink/private/SDLPresentAlertOperation.h
new file mode 100644
index 000000000..22cd56fb3
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPresentAlertOperation.h
@@ -0,0 +1,38 @@
+//
+// SDLPresentAlertOperation.h
+// SmartDeviceLink
+//
+// Created by Nicole on 11/12/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAsynchronousOperation.h"
+
+@class SDLAlertView;
+@class SDLFileManager;
+@class SDLSystemCapabilityManager;
+@class SDLWindowCapability;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Operation that handles uploading images and audio data needed by the alert and, once the data uploads are complete, sending the alert.
+@interface SDLPresentAlertOperation : SDLAsynchronousOperation
+
+/// The current window capabilities
+@property (copy, nonatomic, nullable) SDLWindowCapability *currentWindowCapability;
+
+/// An operation to present an alert.
+/// @param connectionManager The connection manager
+/// @param fileManager The file manager
+/// @param systemCapabilityManager The system capability manager
+/// @param currentWindowCapability The current window capability
+/// @param alertView The alert to be displayed
+/// @param cancelID A unique ID for this specific choice set that allows cancellation through the `CancelInteraction` RPC
+/// @return A SDLPresentAlertOperation object
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager currentWindowCapability:(nullable SDLWindowCapability *)currentWindowCapability alertView:(SDLAlertView *)alertView cancelID:(UInt16)cancelID;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLPresentAlertOperation.m b/SmartDeviceLink/private/SDLPresentAlertOperation.m
new file mode 100644
index 000000000..6999bf55d
--- /dev/null
+++ b/SmartDeviceLink/private/SDLPresentAlertOperation.m
@@ -0,0 +1,451 @@
+//
+// SDLPresentAlertOperation.m
+// SmartDeviceLink
+//
+// Created by Nicole on 11/12/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLPresentAlertOperation.h"
+
+#import "SDLAlert.h"
+#import "SDLAlertAudioData.h"
+#import "SDLAlertResponse.h"
+#import "SDLAlertView.h"
+#import "SDLArtwork.h"
+#import "SDLCancelInteraction.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLError.h"
+#import "SDLFile.h"
+#import "SDLFileManager.h"
+#import "SDLGlobals.h"
+#import "SDLLogMacros.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+#import "SDLSystemCapabilityManager.h"
+#import "SDLTextField.h"
+#import "SDLTTSChunk.h"
+#import "SDLVersion.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+static const int SDLAlertSoftButtonIDMin = 10;
+static const int SDLAlertSoftButtonCount = 4;
+
+@interface SDLAlertAudioData()
+
+@property (nullable, copy, nonatomic, readonly) NSDictionary<SDLFileName *, SDLFile *> *audioFileData;
+
+@end
+
+@interface SDLAlertView()
+
+/// Handler called when the alert should be dismissed.
+@property (copy, nonatomic) SDLAlertCanceledHandler canceledHandler;
+
+@end
+
+@interface SDLSoftButtonObject()
+
+/// Unique id assigned to the soft button.
+@property (assign, nonatomic) NSUInteger buttonId;
+
+@end
+
+@interface SDLPresentAlertOperation()
+
+@property (strong, nonatomic) NSUUID *operationId;
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager;
+@property (strong, nonatomic, readwrite) SDLAlertView *alertView;
+@property (assign, nonatomic) UInt16 cancelId;
+@property (copy, nonatomic, nullable) NSError *internalError;
+@property (assign, atomic) BOOL isAlertPresented;
+
+@end
+
+@implementation SDLPresentAlertOperation
+
+#pragma mark - Lifecycle
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager currentWindowCapability:(nullable SDLWindowCapability *)currentWindowCapability alertView:(SDLAlertView *)alertView cancelID:(UInt16)cancelID {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _systemCapabilityManager = systemCapabilityManager;
+
+ __weak typeof(self) weakSelf = self;
+ alertView.canceledHandler = ^{
+ [weakSelf sdl_cancelInteraction];
+ };
+ _alertView = [alertView copy];
+
+ _cancelId = cancelID;
+ _operationId = [NSUUID UUID];
+ _currentWindowCapability = currentWindowCapability;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+ if (self.isCancelled) { return; }
+
+ NSError *alertViewValidatedError = [self sdl_isValidAlertViewData:self.alertView];
+ if (alertViewValidatedError != nil) {
+ [self finishOperation];
+ self.internalError = alertViewValidatedError;
+ return;
+ }
+
+ dispatch_group_t uploadFilesTask = dispatch_group_create();
+ dispatch_group_enter(uploadFilesTask);
+
+ dispatch_group_enter(uploadFilesTask);
+ [self sdl_uploadImagesWithCompletionHandler:^{
+ dispatch_group_leave(uploadFilesTask);
+ }];
+
+ dispatch_group_enter(uploadFilesTask);
+ [self sdl_uploadAudioFilesWithCompletionHandler:^{
+ dispatch_group_leave(uploadFilesTask);
+ }];
+
+ dispatch_group_leave(uploadFilesTask);
+ // This will always run after all `leave`s
+ __weak typeof(self) weakSelf = self;
+ dispatch_group_notify(uploadFilesTask, [SDLGlobals sharedGlobals].sdlConcurrentQueue, ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf sdl_presentAlert];
+ });
+}
+
+/// Checks the `AlertView` data to make sure it conforms to the RPC Spec, which says that at least either `alertText1`, `alertText2` or `TTSChunks` need to be provided.
+/// @return The error if the alert view does not have valid data; nil if the alert view data is valid
+- (nullable NSError *)sdl_isValidAlertViewData:(SDLAlertView *)alertView {
+ BOOL isValidData = NO;
+ if ((alertView.text.length > 0)
+ || (alertView.secondaryText.length > 0)
+ || ([self sdl_ttsChunksForAlertView:alertView].count > 0)) {
+ isValidData = YES;
+ }
+
+ if (isValidData) {
+ return nil;
+ } else if (alertView.audio.audioData.count > 0) {
+ return [NSError sdl_alertManager_alertAudioFileNotSupported];
+ } else {
+ return [NSError sdl_alertManager_alertDataInvalid];
+ }
+}
+
+#pragma mark Uploads
+
+/// Upload the alert audio files.
+/// @param handler Called when all audio files have been uploaded
+- (void)sdl_uploadAudioFilesWithCompletionHandler:(void (^)(void))handler {
+ if (![self sdl_supportsAlertAudioFile]) {
+ SDLLogD(@"Module does not support audio files for alerts, skipping upload of audio files");
+ return handler();
+ }
+
+ NSMutableArray<SDLFile *> *filesToBeUploaded = [NSMutableArray array];
+ for (SDLTTSChunk *ttsChunk in self.alertView.audio.audioData) {
+ if (ttsChunk.type != SDLSpeechCapabilitiesFile) { continue; }
+
+ SDLFile *audioFile = self.alertView.audio.audioFileData[ttsChunk.text];
+ if (![self.fileManager fileNeedsUpload:audioFile]) { continue; }
+ [filesToBeUploaded addObject:audioFile];
+ }
+
+ if (filesToBeUploaded.count == 0) {
+ SDLLogV(@"No audio files need to be uploaded for alert");
+ return handler();
+ }
+
+ SDLLogD(@"Uploading audio files for alert");
+ __weak typeof(self) weakself = self;
+ [self.fileManager uploadFiles:filesToBeUploaded progressHandler:^BOOL(SDLFileName * _Nonnull fileName, float uploadPercentage, NSError * _Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
+ SDLLogD(@"Uploaded alert audio file: %@, error: %@, percent complete: %f.2%%", fileName, error, uploadPercentage * 100);
+ if (strongself.isCancelled) { return NO; }
+
+ return YES;
+ } completionHandler:^(NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading alert audio files: %@", error);
+ } else {
+ SDLLogD(@"All alert audio files uploaded");
+ }
+
+ handler();
+ }];
+}
+
+/// Upload the alert icon and soft button images.
+/// @param handler Called when all images have been uploaded.
+- (void)sdl_uploadImagesWithCompletionHandler:(void (^)(void))handler {
+ NSMutableArray<SDLArtwork *> *artworksToBeUploaded = [NSMutableArray array];
+ if ([self sdl_supportsAlertIcon] && [self.fileManager fileNeedsUpload:self.alertView.icon]) {
+ [artworksToBeUploaded addObject:self.alertView.icon];
+ }
+
+ // Don't upload artworks for buttons that will not be shown.
+ for (NSUInteger i = 0; i < [self sdl_softButtonCount]; i++) {
+ SDLSoftButtonObject *object = self.alertView.softButtons[i];
+ if ([self sdl_supportsSoftButtonImages] && [self.fileManager fileNeedsUpload:object.currentState.artwork]) {
+ [artworksToBeUploaded addObject:object.currentState.artwork];
+ }
+ }
+
+ if (artworksToBeUploaded.count == 0) {
+ SDLLogV(@"No images to upload for alert");
+ return handler();
+ }
+
+ SDLLogD(@"Uploading images for alert");
+ __weak typeof(self) weakself = self;
+ [self.fileManager uploadArtworks:[artworksToBeUploaded copy] progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
+ SDLLogD(@"Uploaded alert images: %@, error: %@, percent complete: %f.2%%", artworkName, error, uploadPercentage * 100);
+ if (strongself.isCancelled) { return NO; }
+
+ return YES;
+ } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading alert images: %@", error);
+ } else {
+ SDLLogD(@"All alert images uploaded");
+ }
+
+ return handler();
+ }];
+}
+
+/// Sends the alert RPC to the module. The operation is finished once a response has been received from the module.
+- (void)sdl_presentAlert {
+ if (self.isCancelled) { return [self finishOperation]; }
+
+ self.isAlertPresented = YES;
+
+ __weak typeof(self) weakSelf = self;
+ [self.connectionManager sendConnectionRequest:self.alertRPC withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (error != nil) {
+ SDLAlertResponse *alertResponse = (SDLAlertResponse *)response;
+ strongSelf.internalError = [NSError sdl_alertManager_presentationFailedWithError:error tryAgainTime:alertResponse.tryAgainTime.intValue];
+ }
+
+ [strongSelf finishOperation];
+ }];
+}
+
+#pragma mark - Cancel
+
+/// Cancels the alert. If the alert has not yet been sent to the module, it will not be sent. If the alert is already presented on the module, the alert will be immediately dismissed. Canceling an already presented alert will only work if connected to modules supporting RPC spec versions 6.0+. On older versions alert will not be dismissed.
+- (void)sdl_cancelInteraction {
+ if (self.isFinished) {
+ SDLLogW(@"This operation has already finished so it can not be canceled.");
+ return;
+ } else if (self.isCancelled) {
+ SDLLogW(@"This operation has already been canceled. It will be finished at some point during the operation.");
+ return;
+ } else if (self.isExecuting) {
+ if ([SDLGlobals.sharedGlobals.rpcVersion isLessThanVersion:[[SDLVersion alloc] initWithMajor:6 minor:0 patch:0]]) {
+ SDLLogD(@"Attempting to cancel this operation in-progress; if the alert is already presented on the module, it cannot be dismissed.");
+ [self cancel];
+ return;
+ } else if (self.isAlertPresented == NO) {
+ SDLLogD(@"Alert has not yet been sent to the module. Alert will not be shown.");
+ [self cancel];
+ return;
+ }
+
+ SDLLogD(@"Canceling the presented alert");
+ __weak typeof(self) weakSelf = self;
+ SDLCancelInteraction *cancelInteraction = [[SDLCancelInteraction alloc] initWithAlertCancelID:self.cancelId];
+ [self.connectionManager sendConnectionRequest:cancelInteraction withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ weakSelf.internalError = error;
+ SDLLogE(@"Error canceling the presented alert: %@, with error: %@", request, error);
+ return;
+ }
+ SDLLogD(@"The presented alert was canceled successfully");
+ }];
+ } else {
+ SDLLogD(@"Canceling an alert that has not started: %@", self);
+ [self cancel];
+ }
+}
+
+#pragma mark - Private Getters / Setters
+
+/// Assembles an `Alert` RPC from the `SDLAlertView` information.
+/// @return The `Alert` RPC to be sent to the module.
+- (SDLAlert *)alertRPC {
+ SDLAlert *alert = [[SDLAlert alloc] init];
+ [self sdl_assembleAlertText:alert];
+ alert.duration = @((NSUInteger)(self.alertView.timeout * 1000));
+ alert.alertIcon = ([self sdl_supportsAlertIcon] && ![self.fileManager fileNeedsUpload:self.alertView.icon]) ? self.alertView.icon.imageRPC : nil;
+ alert.progressIndicator = @(self.alertView.showWaitIndicator);
+ alert.cancelID = @(self.cancelId);
+
+ // The number of alert soft buttons sent must be capped so there are no clashes with soft button ids assigned by other managers (And thus leading to clashes saving/retreiving the button handlers in the `SDLResponseDispatcher` class)
+ NSMutableArray<SDLSoftButton *> *softButtons = [NSMutableArray arrayWithCapacity:[self sdl_softButtonCount]];
+ for (NSUInteger i = 0; i < [self sdl_softButtonCount]; i += 1) {
+ SDLSoftButtonObject *button = self.alertView.softButtons[i];
+ button.buttonId = SDLAlertSoftButtonIDMin + i;
+ [softButtons addObject:button.currentStateSoftButton.copy];
+ }
+ alert.softButtons = softButtons;
+
+ alert.playTone = @(self.alertView.audio.playTone);
+ alert.ttsChunks = [self sdl_ttsChunksForAlertView:self.alertView];
+
+ return alert;
+}
+
+/// Checks the number of soft buttons added to the alert view against the max number of soft buttons allowed by the RPC Spec and returns the smaller of the two values.
+/// @return The maximum number of soft buttons that can be sent to the module
+- (unsigned long)sdl_softButtonCount {
+ return MIN(self.alertView.softButtons.count, SDLAlertSoftButtonCount);
+}
+
+/// Creates an array of text-to-speech chunks for the `Alert` RPC from the text strings and the audio data files.
+/// @param alertView The alert view
+/// @return An array of TTS chunks or nil if there are no TTS chunks
+- (nullable NSArray<SDLTTSChunk *> *)sdl_ttsChunksForAlertView:(SDLAlertView *)alertView {
+ SDLAlertAudioData *alertAudio = alertView.audio;
+ NSMutableArray<SDLTTSChunk *> *ttsChunks = [NSMutableArray array];
+
+ for (SDLTTSChunk *audioData in alertAudio.audioData) {
+ // If the audio data is a file and the connected system doesn't support files, skip that audio data
+ if (audioData.type == SDLSpeechCapabilitiesFile && ![self sdl_supportsAlertAudioFile]) { continue; }
+ [ttsChunks addObject:audioData];
+ }
+
+ return ttsChunks.count > 0 ? [ttsChunks copy] : nil;
+}
+
+/// Checks if the connected module or current template supports soft button images.
+/// @return True if soft button images are currently supported; false if not.
+- (BOOL)sdl_supportsSoftButtonImages {
+ return self.currentWindowCapability.softButtonCapabilities.firstObject.imageSupported.boolValue;
+}
+
+/// Checks if the connected module supports audio files. Using an audio file in an alert will only work if connected to modules supporting RPC spec versions 5.0+. If the module does not return a speechCapabilities, assume that the module supports playing an audio file.
+/// @return True if the module supports playing audio files in an alert; false if not.
+- (BOOL)sdl_supportsAlertAudioFile {
+ NSUInteger majorVersion = [SDLGlobals sharedGlobals].rpcVersion.major;
+ BOOL supportSpeechCapabilities = (self.systemCapabilityManager.speechCapabilities != nil) ? [self.systemCapabilityManager.speechCapabilities containsObject:SDLSpeechCapabilitiesFile] : YES;
+ return (majorVersion >= 5 && supportSpeechCapabilities);
+}
+
+/// Checks if the connected module or current template supports alert icons.
+/// @return True if alert icons are currently supported; false if not.
+- (BOOL)sdl_supportsAlertIcon {
+ return [self.currentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon];
+}
+
+#pragma mark - Text Helpers
+
+/// Populates the alert RPC text-fields based on the number of text-fields the current template supports. If more text-fields are set in the SDLAlertView than the template supports, the text is concancated so all text fits in the currently available text-fields.
+/// @param alert The alert RPC with no text-fields set
+/// @return An alert RPC with the text-fields set
+- (SDLAlert *)sdl_assembleAlertText:(SDLAlert *)alert {
+ NSArray *nonNilFields = [self sdl_findNonNilTextFields];
+ if (nonNilFields.count == 0) { return alert; }
+
+ NSUInteger maxNumberOfLines = (self.currentWindowCapability != nil) ? self.currentWindowCapability.maxNumberOfAlertFieldLines : MaxAlertTextFieldLineCount;
+ if (maxNumberOfLines == 1) {
+ alert = [self sdl_assembleOneLineAlertText:alert withAlertFields:nonNilFields];
+ } else if (maxNumberOfLines == 2) {
+ alert = [self sdl_assembleTwoLineAlertText:alert withAlertFields:nonNilFields];
+ } else if (maxNumberOfLines == 3) {
+ alert = [self sdl_assembleThreeLineAlertText:alert withAlertFields:nonNilFields];
+ }
+
+ return alert;
+}
+
+/// Generates a list of all non-empty text-fields set in the SDLAlertView in order from first, second to third.
+/// @return An array of all the text-fields set in the SDLAlertView
+- (NSArray<NSString *> *)sdl_findNonNilTextFields {
+ NSMutableArray *array = [NSMutableArray array];
+ (self.alertView.text.length > 0) ? [array addObject:self.alertView.text] : nil;
+ (self.alertView.secondaryText.length > 0) ? [array addObject:self.alertView.secondaryText] : nil;
+ (self.alertView.tertiaryText.length > 0) ? [array addObject:self.alertView.tertiaryText] : nil;
+
+ return [array copy];
+}
+
+/// Called if the alert template only supports one line of text. A single string is created from all the text and is used to set the first text-field in the alert RPC.
+/// @param alert The alert RPC
+/// @param fields A list all the text set in the SDLAlertView
+/// @return An alert RPC with the text-fields set
+- (SDLAlert *)sdl_assembleOneLineAlertText:(SDLAlert *)alert withAlertFields:(NSArray<NSString *> *)fields {
+ NSMutableString *alertString = [NSMutableString stringWithString:[fields objectAtIndex:0]];
+ for (NSUInteger i = 1; i < fields.count; i+= 1) {
+ [alertString appendFormat:@" - %@", fields[i]];
+ }
+ alert.alertText1 = alertString.copy;
+
+ return alert;
+}
+
+/// Called if the alert template only supports two lines of text. The first text-field in the alert RPC is set with the first available text and the second text-field is set with a single string created from all remaining text.
+/// @param alert The alert RPC
+/// @param fields A list all the text set in the SDLAlertView
+/// @return An alert RPC with the text-fields set
+- (SDLAlert *)sdl_assembleTwoLineAlertText:(SDLAlert *)alert withAlertFields:(NSArray<NSString *> *)fields {
+ if (fields.count <= 2) {
+ alert.alertText1 = fields.count > 0 ? fields[0] : nil;
+ alert.alertText2 = fields.count > 1 ? [fields objectAtIndex:1] : nil;
+ } else {
+ alert.alertText1 = fields.count > 0 ? [fields objectAtIndex:0] : nil;
+ alert.alertText2 = [NSString stringWithFormat:@"%@ - %@", [fields objectAtIndex:1], [fields objectAtIndex:2]];
+ }
+
+ return alert;
+}
+
+/// Called if the alert template supports all three lines of text. Each text-field in the alert RPC is set with its corresponding text.
+/// @param alert The alert RPC
+/// @param fields A list all the text set in the SDLAlertView
+/// @return An alert RPC with the text-fields set
+- (SDLAlert *)sdl_assembleThreeLineAlertText:(SDLAlert *)alert withAlertFields:(NSArray<NSString *> *)fields {
+ alert.alertText1 = fields.count > 0 ? fields[0] : nil;
+ alert.alertText2 = fields.count > 1 ? [fields objectAtIndex:1] : nil;
+ alert.alertText3 = fields.count > 2 ? [fields objectAtIndex:2] : nil;
+ return alert;
+}
+
+#pragma mark - Property Overrides
+
+- (void)finishOperation {
+ SDLLogV(@"Finishing present alert operation");
+ [super finishOperation];
+}
+
+- (nullable NSString *)name {
+ return @"com.sdl.alertManager.present";
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLSoftButtonManager.m b/SmartDeviceLink/private/SDLSoftButtonManager.m
index 2a637f831..7a2725c7c 100644
--- a/SmartDeviceLink/private/SDLSoftButtonManager.m
+++ b/SmartDeviceLink/private/SDLSoftButtonManager.m
@@ -33,6 +33,9 @@
NS_ASSUME_NONNULL_BEGIN
+static const int SDLShowSoftButtonIDMin = 1;
+static const int SDLShowSoftButtonIDCount = 8;
+
@interface SDLSoftButtonObject()
@property (assign, nonatomic) NSUInteger buttonId;
@@ -76,7 +79,7 @@ NS_ASSUME_NONNULL_BEGIN
}
- (void)start {
- [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate:)];
+ [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate)];
}
- (void)stop {
@@ -124,12 +127,15 @@ NS_ASSUME_NONNULL_BEGIN
return;
}
- // Set the soft button ids. Check to make sure no two soft buttons have the same name, there aren't many soft buttons, so n^2 isn't going to be bad
- for (NSUInteger i = 0; i < softButtonObjects.count; i++) {
+ // Set the soft button ids. The number of soft buttons is maxed at 8 according to the RPC spec. We will only send the first 8 soft buttons if more are set into the array.
+ // Check to make sure no two soft buttons have the same name, there aren't many soft buttons, so n^2 isn't going to be bad
+ NSUInteger softButtonCount = MIN(softButtonObjects.count, SDLShowSoftButtonIDCount);
+ for (NSUInteger i = 0; i < softButtonCount; i++) {
NSString *buttonName = softButtonObjects[i].name;
// HAX: Due to a SYNC 3.0 bug (https://github.com/smartdevicelink/sdl_ios/issues/1793#issue-708356008), a `buttonId` can not be zero. As a workaround we will start the `buttonId`s from 1.
- softButtonObjects[i].buttonId = i + 1;
- for (NSUInteger j = (i + 1); j < softButtonObjects.count; j++) {
+ // Offset the soft buttons based on the minimum ID number to prevent clashes with other managers.
+ softButtonObjects[i].buttonId = i + SDLShowSoftButtonIDMin;
+ for (NSUInteger j = (i + 1); j < softButtonCount; j++) {
if ([softButtonObjects[j].name isEqualToString:buttonName]) {
_softButtonObjects = @[];
SDLLogE(@"Attempted to set soft button objects, but two buttons had the same name: %@", softButtonObjects);
@@ -213,23 +219,12 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Observers
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability {
+- (void)sdl_displayCapabilityDidUpdate {
SDLSoftButtonCapabilities *oldCapabilities = self.softButtonCapabilities;
// Extract and update the capabilities
- NSArray<SDLDisplayCapability *> *capabilities = systemCapability.displayCapabilities;
- if (capabilities == nil || capabilities.count == 0) {
- self.softButtonCapabilities = nil;
- } else {
- SDLDisplayCapability *mainDisplay = capabilities[0];
- for (SDLWindowCapability *windowCapability in mainDisplay.windowCapabilities) {
- NSUInteger currentWindowID = windowCapability.windowID != nil ? windowCapability.windowID.unsignedIntegerValue : SDLPredefinedWindowsDefaultWindow;
- if (currentWindowID != SDLPredefinedWindowsDefaultWindow) { continue; }
-
- self.softButtonCapabilities = windowCapability.softButtonCapabilities.firstObject;
- break;
- }
- }
+ SDLWindowCapability *currentWindowCapability = self.systemCapabilityManager.defaultMainWindowCapability;
+ self.softButtonCapabilities = currentWindowCapability.softButtonCapabilities.firstObject;
// Update the queue's suspend state
[self sdl_updateTransactionQueueSuspended];
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicManager.m b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
index 6f416b2f1..37b9f95fa 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicManager.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
@@ -90,7 +90,7 @@ NS_ASSUME_NONNULL_BEGIN
// Make sure none of the properties were set after the manager was shut down
[self sdl_reset];
- [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate:)];
+ [self.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:self selector:@selector(sdl_displayCapabilityDidUpdate)];
}
- (void)stop {
@@ -375,23 +375,12 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Subscribed notifications
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability {
- // Extract and update the capabilities
- NSArray<SDLDisplayCapability *> *capabilities = systemCapability.displayCapabilities;
- if (capabilities == nil || capabilities.count == 0) {
- self.windowCapability = nil;
- } else {
- SDLDisplayCapability *mainDisplay = capabilities[0];
- for (SDLWindowCapability *windowCapability in mainDisplay.windowCapabilities) {
- NSUInteger currentWindowID = windowCapability.windowID != nil ? windowCapability.windowID.unsignedIntegerValue : SDLPredefinedWindowsDefaultWindow;
- if (currentWindowID != SDLPredefinedWindowsDefaultWindow) { continue; }
-
- // Check if the window capability is equal to the one we already have. If it is, abort.
- if ([windowCapability isEqual:self.windowCapability]) { return; }
- self.windowCapability = windowCapability;
- break;
- }
- }
+- (void)sdl_displayCapabilityDidUpdate {
+ SDLWindowCapability *currentWindowCapability = self.systemCapabilityManager.defaultMainWindowCapability;
+
+ // Check if the window capability is equal to the one we already have. If it is, abort.
+ if ([currentWindowCapability isEqual:self.windowCapability]) { return; }
+ self.windowCapability = currentWindowCapability;
[self sdl_updateTransactionQueueSuspended];
diff --git a/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.h b/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.h
index ca095da24..71e541666 100644
--- a/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.h
+++ b/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.h
@@ -1,5 +1,5 @@
//
-// SDLWindowCapability+ShowManagerExtensions.h
+// SDLWindowCapability+ScreenManagerExtensions.h
// SmartDeviceLink
//
// Created by Joel Fischer on 2/28/18.
@@ -16,9 +16,13 @@
NS_ASSUME_NONNULL_BEGIN
+static const int MaxMainFieldLineCount = 4;
+static const int MaxAlertTextFieldLineCount = 3;
+
@interface SDLWindowCapability (ScreenManagerExtensions)
@property (assign, nonatomic, readonly) NSUInteger maxNumberOfMainFieldLines;
+@property (assign, nonatomic, readonly) NSUInteger maxNumberOfAlertFieldLines;
- (BOOL)hasTextFieldOfName:(SDLTextFieldName)name;
- (BOOL)hasImageFieldOfName:(SDLImageFieldName)name;
diff --git a/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.m b/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.m
index 9aa00fa62..e83063545 100644
--- a/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.m
+++ b/SmartDeviceLink/private/SDLWindowCapability+ScreenManagerExtensions.m
@@ -1,5 +1,5 @@
//
-// SDLWindowCapability+ShowManagerExtensions.m
+// SDLWindowCapability+ScreenManagerExtensions.m
// SmartDeviceLink
//
// Created by Joel Fischer on 2/28/18.
@@ -39,7 +39,24 @@
NSInteger fieldNumber = [[textField.name substringFromIndex:(textField.name.length - 1)] integerValue];
highestFound = (highestFound < fieldNumber) ? fieldNumber : highestFound;
- if (highestFound == 4) { break; }
+ if (highestFound == MaxMainFieldLineCount) { break; }
+ }
+ }
+
+ return (NSUInteger)highestFound;
+}
+
+- (NSUInteger)maxNumberOfAlertFieldLines {
+ NSInteger highestFound = 0;
+ for (SDLTextField *textField in self.textFields) {
+ if (![textField.name isKindOfClass:[NSString class]]) { continue; }
+ if ([textField.name isEqualToString:SDLTextFieldNameAlertText1]
+ || [textField.name isEqualToString:SDLTextFieldNameAlertText2]
+ || [textField.name isEqualToString:SDLTextFieldNameAlertText3]) {
+ NSInteger fieldNumber = [[textField.name substringFromIndex:(textField.name.length - 1)] integerValue];
+ highestFound = (highestFound < fieldNumber) ? fieldNumber : highestFound;
+
+ if (highestFound == MaxAlertTextFieldLineCount) { break; }
}
}
diff --git a/SmartDeviceLink/public/SDLAlertAudioData.h b/SmartDeviceLink/public/SDLAlertAudioData.h
new file mode 100644
index 000000000..93b5462aa
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAlertAudioData.h
@@ -0,0 +1,22 @@
+//
+// SDLAlertAudioData.h
+// SmartDeviceLink
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAudioData.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLAlertAudioData : SDLAudioData
+
+/// Whether the alert tone should be played before the prompt (if any) is spoken. Defaults to NO.
+@property (assign, nonatomic) BOOL playTone;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLAlertAudioData.m b/SmartDeviceLink/public/SDLAlertAudioData.m
new file mode 100644
index 000000000..3e6f2a8bd
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAlertAudioData.m
@@ -0,0 +1,25 @@
+//
+// SDLAlertAudioData.m
+// SmartDeviceLink
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAlertAudioData.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@implementation SDLAlertAudioData
+
+#pragma mark - NSCopying
+
+- (id)copyWithZone:(nullable NSZone *)zone {
+ SDLAlertAudioData *new = [super copyWithZone:zone];
+ new->_playTone = _playTone;
+ return new;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLAlertView.h b/SmartDeviceLink/public/SDLAlertView.h
new file mode 100644
index 000000000..c2524b17a
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAlertView.h
@@ -0,0 +1,76 @@
+//
+// SDLAlertView.h
+// SmartDeviceLink
+//
+// Created by Nicole on 11/10/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "NSNumber+NumberType.h"
+
+@class SDLAlertAudioData;
+@class SDLArtwork;
+@class SDLSoftButtonObject;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/// Notifies the subscriber that the alert should be cancelled.
+typedef void (^SDLAlertCanceledHandler)(void);
+
+@interface SDLAlertView : NSObject <NSCopying>
+
+/// Set this to change the default timeout for all alerts. If a timeout is not set on an individual alert object (or if it is set to 0.0), then it will use this timeout instead. See `timeout` for more details. If this is not set by you, it will default to 5 seconds. The minimum is 3 seconds, the maximum is 10 seconds. If this is set below the minimum, it will be capped at 3 seconds. If this is set above the maximum, it will be capped at 10 seconds.
+/// Please note that if a button is added to the alert, the defaultTimeout and timeout values will be ignored.
+@property (class, assign, nonatomic) NSTimeInterval defaultTimeout;
+
+/// The primary line of text for display on the alert. If fewer than three alert lines are available on the head unit, the screen manager will automatically concatenate some of the lines together.
+@property (nullable, strong, nonatomic) NSString *text;
+
+/// The secondary line of text for display on the alert. If fewer than three alert lines are available on the head unit, the screen manager will automatically concatenate some of the lines together.
+@property (nullable, strong, nonatomic) NSString *secondaryText;
+
+/// The tertiary line of text for display on the alert. If fewer than three alert lines are available on the head unit, the screen manager will automatically concatenate some of the lines together.
+@property (nullable, strong, nonatomic) NSString *tertiaryText;
+
+/// Timeout in seconds. Defaults to 0, which will use `defaultTimeout`. If this is set below the minimum, it will be capped at 3 seconds. Minimum 3 seconds, maximum 10 seconds. If this is set above the maximum, it will be capped at 10 seconds. Defaults to 0.
+/// Please note that if a button is added to the alert, the defaultTimeout and timeout values will be ignored.
+@property (assign, nonatomic) NSTimeInterval timeout;
+
+/// Text spoken, file(s) played, and/or tone played when the alert appears
+@property (nullable, copy, nonatomic) SDLAlertAudioData *audio;
+
+/// If supported, the alert GUI will display some sort of indefinite waiting / refresh / loading indicator animation. Defaults to NO.
+@property (assign, nonatomic) BOOL showWaitIndicator;
+
+/// Soft buttons the user may select to perform actions. Only one `SDLSoftButtonState` per object is supported; if any soft button object contains multiple states, an exception will be thrown.
+@property (nullable, copy, nonatomic) NSArray<SDLSoftButtonObject *> *softButtons;
+
+/// An artwork that will be displayed when the icon appears. This will be uploaded prior to the appearance of the alert if necessary. This will not be uploaded if the head unit does not declare support for alertIcon.
+@property (nullable, copy, nonatomic) SDLArtwork *icon;
+
+- (instancetype)init NS_DESIGNATED_INITIALIZER;
+
+/// Initialize a basic alert with a message and buttons
+/// @param text The primary line of text for display on the alert
+/// @param softButtons Soft buttons the user may select to perform actions
+- (instancetype)initWithText:(NSString *)text buttons:(NSArray<SDLSoftButtonObject *> *)softButtons;
+
+/// Initialize a alert with a text, image, buttons and sound
+/// @param text The primary line of text for display on the alert
+/// @param secondaryText The secondary line of text for display on the alert
+/// @param tertiaryText The tertiary line of text for display on the alert
+/// @param timeout Timeout in seconds
+/// @param showWaitIndicator If supported, the alert GUI will display some sort of indefinite waiting / refresh / loading indicator animation
+/// @param audio Text spoken and/or tone played when the alert appears
+/// @param softButtons Soft buttons the user may select to perform actions
+/// @param icon An artwork that will be displayed when the icon appears
+- (instancetype)initWithText:(nullable NSString *)text secondaryText:(nullable NSString *)secondaryText tertiaryText:(nullable NSString *)tertiaryText timeout:(nullable NSNumber<SDLFloat> *)timeout showWaitIndicator:(nullable NSNumber<SDLBool> *)showWaitIndicator audioIndication:(nullable SDLAlertAudioData *)audio buttons:(nullable NSArray<SDLSoftButtonObject *> *)softButtons icon:(nullable SDLArtwork *)icon;
+
+/// Cancels the alert. If the alert has not yet been sent to the module, it will not be sent. If the alert is already presented on the module, the alert will be immediately dismissed. Canceling an already presented alert will only work if connected to modules supporting RPC Spec v.6.0+. On older versions the alert will not be dismissed.
+- (void)cancel;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLAlertView.m b/SmartDeviceLink/public/SDLAlertView.m
new file mode 100644
index 000000000..f8f824f1b
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAlertView.m
@@ -0,0 +1,146 @@
+//
+// SDLAlertView.m
+// SmartDeviceLink
+//
+// Created by Nicole on 11/10/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAlertView.h"
+
+#import "SDLAlertAudioData.h"
+#import "SDLArtwork.h"
+#import "SDLError.h"
+#import "SDLSoftButtonObject.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLAlertView()
+
+@property (copy, nonatomic, nullable) SDLAlertCanceledHandler canceledHandler;
+
+@end
+
+@implementation SDLAlertView
+
+static const float TimeoutDefault = 0.0;
+static const float TimeoutMinCap = 3.0;
+static const float TimeoutMaxCap = 10.0;
+static NSTimeInterval _defaultTimeout = 5.0;
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _timeout = TimeoutDefault;
+
+ return self;
+}
+
+- (instancetype)initWithText:(NSString *)text buttons:(NSArray<SDLSoftButtonObject *> *)softButtons {
+ self = [self init];
+ if (!self) { return nil; }
+
+ _text = text;
+ self.softButtons = softButtons;
+
+ return self;
+}
+
+- (instancetype)initWithText:(nullable NSString *)text secondaryText:(nullable NSString *)secondaryText tertiaryText:(nullable NSString *)tertiaryText timeout:(nullable NSNumber<SDLFloat> *)timeout showWaitIndicator:(nullable NSNumber<SDLBool> *)showWaitIndicator audioIndication:(nullable SDLAlertAudioData *)audio buttons:(nullable NSArray<SDLSoftButtonObject *> *)softButtons icon:(nullable SDLArtwork *)icon {
+ self = [self initWithText:text buttons:softButtons];
+ if (!self) { return nil; }
+
+ _secondaryText = secondaryText;
+ _tertiaryText = tertiaryText;
+ self.timeout = timeout.doubleValue;
+ _showWaitIndicator = showWaitIndicator.boolValue;
+ self.audio = audio;
+ self.icon = icon;
+
+ return self;
+}
+
+#pragma mark - Cancel
+
+- (void)cancel {
+ if (self.canceledHandler == nil) { return; }
+ self.canceledHandler();
+}
+
+#pragma mark - Getters / Setters
+
+- (void)setSoftButtons:(nullable NSArray<SDLSoftButtonObject *> *)softButtons {
+ for (SDLSoftButtonObject *softButton in softButtons) {
+ if (softButton.states.count == 1) { continue; }
+ @throw [NSException sdl_invalidAlertSoftButtonStatesException];
+ }
+
+ _softButtons = softButtons;
+}
+
++ (void)setDefaultTimeout:(NSTimeInterval)defaultTimeout {
+ _defaultTimeout = defaultTimeout;
+}
+
++ (NSTimeInterval)defaultTimeout {
+ if (_defaultTimeout < TimeoutMinCap) {
+ return TimeoutMinCap;
+ } else if (_defaultTimeout > TimeoutMaxCap) {
+ return TimeoutMaxCap;
+ }
+
+ return _defaultTimeout;
+}
+
+- (NSTimeInterval)timeout {
+ if (_timeout == TimeoutDefault) {
+ return SDLAlertView.defaultTimeout;
+ } else if (_timeout < TimeoutMinCap) {
+ return TimeoutMinCap;
+ } else if (_timeout > TimeoutMaxCap) {
+ return TimeoutMaxCap;
+ }
+
+ return _timeout;
+}
+
+#pragma mark - NSCopying
+
+- (id)copyWithZone:(nullable NSZone *)zone {
+ SDLAlertView *newAlertView = [[SDLAlertView allocWithZone:zone] initWithText:[_text copyWithZone:zone] secondaryText:[_secondaryText copyWithZone:zone] tertiaryText:[_tertiaryText copyWithZone:zone] timeout:@(_timeout) showWaitIndicator:@(_showWaitIndicator) audioIndication:[_audio copyWithZone:zone] buttons:[_softButtons copyWithZone:zone] icon:[_icon copyWithZone:zone]];
+ newAlertView->_canceledHandler = [_canceledHandler copyWithZone:zone];
+ return newAlertView;
+}
+
+#pragma mark - Debug Description
+
+- (NSString *)description {
+ return [NSString stringWithFormat:@"SDLAlertView: \"%@\", text: \"%@\"", [self sdl_alertType], _text];
+}
+
+- (NSString *)debugDescription {
+ return [NSString stringWithFormat:@"SDLAlertView: \"%@\", text: \"%@\", secondaryText: \"%@\", tertiaryText: \"%@\", timeout: %.1f, showWaitIndicator: %d, audio: \"%@\", softButtons: \"%@\", icon: \"%@\"", [self sdl_alertType], _text, _secondaryText, _tertiaryText, _timeout, _showWaitIndicator, _audio, _softButtons, _icon];
+}
+
+- (NSString *)sdl_alertType {
+ BOOL alertHasText = (_text || _secondaryText || _tertiaryText);
+ BOOL alertHasAudio = _audio.audioData.count > 0;
+
+ NSString *alertType;
+ if (alertHasText && alertHasAudio) {
+ alertType = @"Text-and-audio";
+ } else if (alertHasText) {
+ alertType = @"Text-only";
+ } else if (alertHasAudio) {
+ alertType = @"Audio-only";
+ } else {
+ alertType = @"Invalid";
+ }
+
+ return alertType;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLAudioData.h b/SmartDeviceLink/public/SDLAudioData.h
new file mode 100644
index 000000000..a4c0a3667
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAudioData.h
@@ -0,0 +1,55 @@
+//
+// SDLAudioData.h
+// SmartDeviceLink
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLSpeechCapabilities.h"
+
+@class SDLFile;
+@class SDLTTSChunk;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLAudioData : NSObject <NSCopying>
+
+/// The text-to-speech prompts that will used and/or audio files that will be played. The audio prompts and files will be played in the same order they are added.
+@property (nullable, copy, nonatomic, readonly) NSArray<SDLTTSChunk *> *audioData;
+
+/// Initialize with an SDLFile holding data or pointing to a file on the file system. When this object is passed to an `Alert` or `Speak`, the file will be uploaded if it is not already, then played if the system supports that feature.
+/// @discussion Only available on systems supporting RPC Spec v5.0+
+///
+/// @param audioFile The audio file to be played by the system
+- (instancetype)initWithAudioFile:(SDLFile *)audioFile;
+
+/// Initialize with a string to be spoken by the system speech synthesizer.
+/// @param spokenString The string to be spoken by the system speech synthesizer
+- (instancetype)initWithSpeechSynthesizerString:(NSString *)spokenString;
+
+/// Initialize with a string to be spoken by the system speech synthesizer using a phonetic string.
+/// @param phoneticString The string to be spoken by the system speech synthesizer
+/// @param phoneticType Must be one of `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED` or no object will be created
+- (instancetype)initWithPhoneticSpeechSynthesizerString:(NSString *)phoneticString phoneticType:(SDLSpeechCapabilities)phoneticType;
+
+- (instancetype)init NS_UNAVAILABLE;
+
+/// Add additional SDLFiles holding data or pointing to a file on the file system. When this object is passed to an `Alert` or `Speak`, the file will be uploaded if it is not already, then played if the system supports that feature.
+/// @param audioFiles An array of audio file to be played by the system
+- (void)addAudioFiles:(NSArray<SDLFile *> *)audioFiles;
+
+/// Create additional strings to be spoken by the system speech synthesizer.
+/// @param spokenStrings The strings to be spoken by the system speech synthesizer
+- (void)addSpeechSynthesizerStrings:(NSArray<NSString *> *)spokenStrings;
+
+/// Create additional strings to be spoken by the system speech synthesizer using a phonetic string.
+/// @param phoneticStrings The strings to be spoken by the system speech synthesizer
+/// @param phoneticType Must be one of `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED` or no object will be created
+- (void)addPhoneticSpeechSynthesizerStrings:(NSArray<NSString *> *)phoneticStrings phoneticType:(SDLSpeechCapabilities)phoneticType;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLAudioData.m b/SmartDeviceLink/public/SDLAudioData.m
new file mode 100644
index 000000000..e0ddb3501
--- /dev/null
+++ b/SmartDeviceLink/public/SDLAudioData.m
@@ -0,0 +1,135 @@
+//
+// SDLAudioData.m
+// SmartDeviceLink
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import "SDLAudioData.h"
+
+#import "SDLError.h"
+#import "SDLFile.h"
+#import "SDLSpeak.h"
+#import "SDLTTSChunk.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLAudioData()
+
+/// The audio file data that will be uploaded.
+@property (nullable, copy, nonatomic, readonly) NSMutableDictionary<NSString *, SDLFile *> *audioFileData;
+@property (nullable, copy, nonatomic, readonly) NSMutableArray<SDLTTSChunk *> *mutableAudioData;
+
+@end
+
+@implementation SDLAudioData
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _mutableAudioData = [NSMutableArray array];
+ _audioFileData = [NSMutableDictionary dictionary];
+
+ return self;
+}
+
+- (instancetype)initWithAudioFile:(SDLFile *)audioFile {
+ self = [self init];
+ if (!self) { return nil; }
+
+ [_mutableAudioData addObjectsFromArray:[SDLTTSChunk fileChunksWithName:audioFile.name]];
+ _audioFileData[audioFile.name] = audioFile;
+
+ return self;
+}
+
+- (instancetype)initWithSpeechSynthesizerString:(NSString *)spokenString {
+ self = [self init];
+ if (!self) { return nil; }
+
+ [_mutableAudioData addObjectsFromArray:[SDLTTSChunk textChunksFromString:spokenString]];
+
+ return self;
+}
+
+- (instancetype)initWithPhoneticSpeechSynthesizerString:(NSString *)phoneticString phoneticType:(SDLSpeechCapabilities)phoneticType {
+ self = [self init];
+ if (!self) { return nil; }
+
+ if (![self.class sdl_isValidPhoneticType:phoneticType]) {
+ @throw [NSException sdl_invalidTTSSpeechCapabilitiesException];
+ }
+
+ [_mutableAudioData addObjectsFromArray:@[[[SDLTTSChunk alloc] initWithText:phoneticString type:phoneticType]]];
+
+ return self;
+}
+
+#pragma mark - Adding additional audio data
+- (void)addAudioFiles:(NSArray<SDLFile *> *)audioFiles {
+ if (audioFiles.count == 0) { return; }
+
+ for (SDLFile *audioFile in audioFiles) {
+ self.audioFileData[audioFile.name] = audioFile;
+ [self.mutableAudioData addObjectsFromArray:[SDLTTSChunk fileChunksWithName:audioFile.name]];
+ }
+}
+
+- (void)addSpeechSynthesizerStrings:(NSArray<NSString *> *)spokenStrings {
+ if (spokenStrings.count == 0) { return; }
+
+ for (NSString *spokenString in spokenStrings) {
+ if (spokenString.length == 0) { continue; }
+ [self.mutableAudioData addObjectsFromArray:[SDLTTSChunk textChunksFromString:spokenString]];
+ }
+}
+
+- (void)addPhoneticSpeechSynthesizerStrings:(NSArray<NSString *> *)phoneticStrings phoneticType:(SDLSpeechCapabilities)phoneticType {
+ if (![self.class sdl_isValidPhoneticType:phoneticType]) {
+ @throw [NSException sdl_invalidTTSSpeechCapabilitiesException];
+ } else if (phoneticStrings.count == 0) {
+ return;
+ }
+
+ for (NSString *phoneticString in phoneticStrings) {
+ if (phoneticString.length == 0) { continue; }
+ [self.mutableAudioData addObject:[[SDLTTSChunk alloc] initWithText:phoneticString type:phoneticType]];
+ }
+}
+
+#pragma mark - Private Utilities
+
+/// Checks if the phonetic type can be used to create a text-to-speech string.
+/// @param phoneticType The phonetic type of the text-to-speech string
+/// @return True if the phoneticType is of type `SAPI_PHONEMES`, `LHPLUS_PHONEMES`, `TEXT`, or `PRE_RECORDED`; false if not.
++ (BOOL)sdl_isValidPhoneticType:(SDLSpeechCapabilities)phoneticType {
+ if (!([phoneticType isEqualToEnum:SDLSpeechCapabilitiesSAPIPhonemes]
+ || [phoneticType isEqualToEnum:SDLSpeechCapabilitiesLHPlusPhonemes]
+ || [phoneticType isEqualToEnum:SDLSpeechCapabilitiesText]
+ || [phoneticType isEqualToEnum:SDLSpeechCapabilitiesPrerecorded])) {
+ return NO;
+ }
+
+ return YES;
+}
+
+#pragma mark - Getters
+
+- (nullable NSArray<SDLTTSChunk *> *)audioData {
+ return [_mutableAudioData copy];
+}
+
+#pragma mark - NSCopying
+
+- (id)copyWithZone:(nullable NSZone *)zone {
+ SDLAudioData *newAudioData = [[self class] allocWithZone:zone];
+ newAudioData->_mutableAudioData = [_mutableAudioData copyWithZone:zone];
+ newAudioData->_audioFileData = [_audioFileData copyWithZone:zone];
+ return newAudioData;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLErrorConstants.h b/SmartDeviceLink/public/SDLErrorConstants.h
index 12bb1c235..14f92a3ba 100644
--- a/SmartDeviceLink/public/SDLErrorConstants.h
+++ b/SmartDeviceLink/public/SDLErrorConstants.h
@@ -22,6 +22,9 @@ extern SDLErrorDomain *const SDLErrorDomainCacheFileManager;
/// An error in the SDLChoiceSetManager subset of SDLScreenManager
extern SDLErrorDomain *const SDLErrorDomainChoiceSetManager;
+/// An error in the SDLAlertManager subset of SDLScreenManager
+extern SDLErrorDomain *const SDLErrorDomainAlertManager;
+
/// An error in the SDLEncryptionLifecycleManager private class
extern SDLErrorDomain *const SDLErrorDomainEncryptionLifecycleManager;
@@ -182,7 +185,7 @@ typedef NS_ENUM(NSInteger, SDLSubscribeButtonManagerError) {
};
/**
- Errors associated with the ScreenManager class
+ Errors associated with the Menu Manager class
*/
typedef NS_ENUM(NSInteger, SDLMenuManagerError) {
/// Sending menu-related RPCs returned an error from the remote system
@@ -190,7 +193,7 @@ typedef NS_ENUM(NSInteger, SDLMenuManagerError) {
SDLMenuManagerErrorPendingUpdateSuperseded = -2
};
-/// Errors associated with Choice Set class
+/// Errors associated with Choice Set Manager class
typedef NS_ENUM(NSInteger, SDLChoiceSetManagerError) {
/// The choice set has been deleted before it was presented
SDLChoiceSetManagerErrorPendingPresentationDeleted = -1,
@@ -208,6 +211,15 @@ typedef NS_ENUM(NSInteger, SDLChoiceSetManagerError) {
SDLChoiceSetManagerErrorInvalidState = -5
};
+/// Errors associated with Alert Manager class
+typedef NS_ENUM(NSInteger, SDLAlertManagerError) {
+ /// There was an error presenting the alert
+ SDLAlertManagerPresentationError = -1,
+
+ /// The alert data is invalid
+ SDLAlertManagerInvalidDataError = -2,
+};
+
/// Errors associated with the system capability manager
typedef NS_ENUM(NSInteger, SDLSystemCapabilityManagerError) {
/// The connected head unit does not support any system capabilities
diff --git a/SmartDeviceLink/public/SDLErrorConstants.m b/SmartDeviceLink/public/SDLErrorConstants.m
index 75ae0a7a3..ccfc62472 100644
--- a/SmartDeviceLink/public/SDLErrorConstants.m
+++ b/SmartDeviceLink/public/SDLErrorConstants.m
@@ -13,6 +13,7 @@
SDLErrorDomain *const SDLErrorDomainAudioStreamManager = @"com.sdl.extension.pcmAudioStreamManager";
SDLErrorDomain *const SDLErrorDomainCacheFileManager = @"com.sdl.cachefilemanager.error";
SDLErrorDomain *const SDLErrorDomainChoiceSetManager = @"com.sdl.choicesetmanager.error";
+SDLErrorDomain *const SDLErrorDomainAlertManager = @"com.sdl.alertmanager.error";
SDLErrorDomain *const SDLErrorDomainEncryptionLifecycleManager = @"com.sdl.encryptionlifecyclemanager.error";
SDLErrorDomain *const SDLErrorDomainFileManager = @"com.sdl.filemanager.error";
SDLErrorDomain *const SDLErrorDomainLifecycleManager = @"com.sdl.lifecyclemanager.error";
diff --git a/SmartDeviceLink/public/SDLFileManager.h b/SmartDeviceLink/public/SDLFileManager.h
index 5010b89e4..d5a053e3b 100644
--- a/SmartDeviceLink/public/SDLFileManager.h
+++ b/SmartDeviceLink/public/SDLFileManager.h
@@ -146,7 +146,7 @@ typedef void (^SDLFileManagerStartupCompletionHandler)(BOOL success, NSError *__
* @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;
+- (BOOL)fileNeedsUpload:(nullable 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.
diff --git a/SmartDeviceLink/public/SDLFileManager.m b/SmartDeviceLink/public/SDLFileManager.m
index 58ed6a547..2bd0bde31 100644
--- a/SmartDeviceLink/public/SDLFileManager.m
+++ b/SmartDeviceLink/public/SDLFileManager.m
@@ -427,7 +427,7 @@ SDLFileManagerState *const SDLFileManagerStateStartupError = @"StartupError";
#pragma mark Artworks
-- (BOOL)fileNeedsUpload:(SDLFile *)file {
+- (BOOL)fileNeedsUpload:(nullable SDLFile *)file {
if (file == nil || file.isStaticIcon) { return NO; }
return (file.overwrite || ![self hasUploadedFile:file]);
diff --git a/SmartDeviceLink/public/SDLScreenManager.h b/SmartDeviceLink/public/SDLScreenManager.h
index 5da5a1cbe..c2da90750 100644
--- a/SmartDeviceLink/public/SDLScreenManager.h
+++ b/SmartDeviceLink/public/SDLScreenManager.h
@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
#import "NSNumber+NumberType.h"
+#import "SDLAlertView.h"
#import "SDLButtonName.h"
#import "SDLInteractionMode.h"
#import "SDLMenuManagerConstants.h"
@@ -25,6 +26,7 @@
@class SDLMenuConfiguration;
@class SDLOnButtonEvent;
@class SDLOnButtonPress;
+@class SDLPermissionManager;
@class SDLSoftButtonObject;
@class SDLSystemCapabilityManager;
@class SDLTemplateConfiguration;
@@ -222,14 +224,27 @@ If set to `SDLDynamicMenuUpdatesModeForceOff`, menu updates will work the legacy
/**
Initialize a screen manager
+ @warning For internal use. An exception will be thrown if used.
+
+ @param connectionManager The connection manager used to send RPCs
+ @param fileManager The file manager used to upload files
+ @param systemCapabilityManager The system capability manager object for reading window capabilities
+ @return The screen manager
+ */
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager __deprecated_msg("Use initWithConnectionManager:fileManager:systemCapabilityManager:permissionManager: instead");
+
+/**
+ Initialize a screen manager
+
@warning For internal use
@param connectionManager The connection manager used to send RPCs
@param fileManager The file manager used to upload files
@param systemCapabilityManager The system capability manager object for reading window capabilities
+ @param permissionManager The permission manager object for checking RPC permissions
@return The screen manager
*/
-- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager;
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager permissionManager:(SDLPermissionManager *)permissionManager;
/**
Starts the manager and all sub-managers
@@ -404,6 +419,18 @@ If set to `SDLDynamicMenuUpdatesModeForceOff`, menu updates will work the legacy
*/
- (BOOL)openSubmenu:(SDLMenuCell *)cell;
+#pragma mark - Alert
+
+/// Present the alert on the screen. To replace a currently presenting alert with a new alert, you must first call `cancel` on the currently presenting alert before sending the new alert. Otherwise the newest alert will only be presented when the module dismisses the currently presented alert (either due to the timeout or the user selecting a button on the alert). Please note that cancelling a currently presented alert will only work on modules supporting RPC Spec v.5.0+.
+///
+/// If the alert contains an audio indication with a file that needs to be uploaded, it will be uploaded before presenting the alert. If the alert contains soft buttons with images, they will be uploaded before presenting the alert. If the alert contains an icon, that will be uploaded before presenting the alert.
+///
+/// The handler will be called when the alert either dismisses from the screen or it has failed to present. If the error value in the handler is present, then the alert failed to appear or was aborted, if not, then the alert dismissed without error. The `userInfo` object on the error contais an `error` key with more information about the error. If the alert failed to present, the `userInfo` object will contain a `tryAgainTime` key with information on how long to wait before trying to send another alert. The value for `tryAgainTime` may be `nil` if the module did not return a value in its response.
+///
+/// @param alert Alert to be presented
+/// @param handler The handler to be called when the alert either dismisses from the screen or it has failed to present.
+- (void)presentAlert:(SDLAlertView *)alert withCompletionHandler:(nullable SDLScreenManagerUpdateCompletionHandler)handler;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLScreenManager.m b/SmartDeviceLink/public/SDLScreenManager.m
index a8792e2fa..ea60f9584 100644
--- a/SmartDeviceLink/public/SDLScreenManager.m
+++ b/SmartDeviceLink/public/SDLScreenManager.m
@@ -7,9 +7,12 @@
//
#import "SDLScreenManager.h"
+
+#import "SDLAlertManager.h"
#import "SDLArtwork.h"
#import "SDLChoiceSetManager.h"
#import "SDLMenuManager.h"
+#import "SDLPermissionManager.h"
#import "SDLSoftButtonManager.h"
#import "SDLSubscribeButtonManager.h"
#import "SDLTextAndGraphicManager.h"
@@ -25,22 +28,29 @@ NS_ASSUME_NONNULL_BEGIN
@property (strong, nonatomic) SDLVoiceCommandManager *voiceCommandMenuManager;
@property (strong, nonatomic) SDLChoiceSetManager *choiceSetManager;
@property (strong, nonatomic) SDLSubscribeButtonManager *subscribeButtonManager;
+@property (strong, nonatomic) SDLAlertManager *alertManager;
-@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
-@property (weak, nonatomic) SDLFileManager *fileManager;
-@property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager;
+@property (weak, nonatomic, nullable) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic, nullable) SDLFileManager *fileManager;
+@property (weak, nonatomic, nullable) SDLSystemCapabilityManager *systemCapabilityManager;
+@property (weak, nonatomic, nullable) SDLPermissionManager *permissionManager;
@end
@implementation SDLScreenManager
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager {
+ @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:@"This convenience init is for internal use only and is no longer used. The initWithConnectionManager:fileManager:systemCapabilityManager:permissionManager: convenience init is now used." userInfo:nil];
+}
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager systemCapabilityManager:(SDLSystemCapabilityManager *)systemCapabilityManager permissionManager:(SDLPermissionManager *)permissionManager {
self = [super init];
if (!self) { return nil; }
_connectionManager = connectionManager;
_fileManager = fileManager;
_systemCapabilityManager = systemCapabilityManager;
+ _permissionManager = permissionManager;
_textAndGraphicManager = [[SDLTextAndGraphicManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager systemCapabilityManager:systemCapabilityManager];
_softButtonManager = [[SDLSoftButtonManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager systemCapabilityManager:systemCapabilityManager];
@@ -48,6 +58,7 @@ NS_ASSUME_NONNULL_BEGIN
_menuManager = [[SDLMenuManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager systemCapabilityManager:systemCapabilityManager];
_voiceCommandMenuManager = [[SDLVoiceCommandManager alloc] initWithConnectionManager:connectionManager];
_choiceSetManager = [[SDLChoiceSetManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager systemCapabilityManager:systemCapabilityManager];
+ _alertManager = [[SDLAlertManager alloc] initWithConnectionManager:connectionManager fileManager:fileManager systemCapabilityManager:systemCapabilityManager permissionManager:permissionManager];
return self;
}
@@ -58,6 +69,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.menuManager start];
[self.choiceSetManager start];
[self.subscribeButtonManager start];
+ [self.alertManager start];
handler(nil);
}
@@ -69,6 +81,7 @@ NS_ASSUME_NONNULL_BEGIN
[self.voiceCommandMenuManager stop];
[self.choiceSetManager stop];
[self.subscribeButtonManager stop];
+ [self.alertManager stop];
}
#pragma mark - Setters
@@ -326,6 +339,12 @@ NS_ASSUME_NONNULL_BEGIN
return [self.menuManager openSubmenu:cell];
}
+#pragma mark - Alert
+
+- (void)presentAlert:(SDLAlertView *)alert withCompletionHandler:(nullable SDLScreenManagerUpdateCompletionHandler)handler {
+ [self.alertManager presentAlert:alert withCompletionHandler:handler];
+}
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/public/SDLSystemCapabilityManager.m b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
index ff63f7dde..4d9160f92 100644
--- a/SmartDeviceLink/public/SDLSystemCapabilityManager.m
+++ b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
@@ -170,13 +170,14 @@ typedef NSString * SDLServiceID;
return nil;
}
- SDLDisplayCapability *mainDisplay = capabilities.firstObject;
+ SDLDisplayCapability *mainDisplay = capabilities[0];
for (SDLWindowCapability *windowCapability in mainDisplay.windowCapabilities) {
NSUInteger currentWindowID = windowCapability.windowID != nil ? windowCapability.windowID.unsignedIntegerValue : SDLPredefinedWindowsDefaultWindow;
- if (currentWindowID == windowID) {
- return windowCapability;
- }
+ if (currentWindowID != windowID) { continue; }
+
+ return windowCapability;
}
+
return nil;
}
diff --git a/SmartDeviceLink/public/SmartDeviceLink.h b/SmartDeviceLink/public/SmartDeviceLink.h
index 4f309d669..8882ae147 100644
--- a/SmartDeviceLink/public/SmartDeviceLink.h
+++ b/SmartDeviceLink/public/SmartDeviceLink.h
@@ -481,6 +481,10 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[];
#import "SDLChoiceSetDelegate.h"
#import "SDLKeyboardDelegate.h"
+#import "SDLAlertAudioData.h"
+#import "SDLAlertView.h"
+#import "SDLAudioData.h"
+
#pragma mark Touches
#import "SDLPinchGesture.h"
#import "SDLTouch.h"
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
index 2d3fa0707..f7379b6f0 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLChoiceSetManagerSpec.m
@@ -35,6 +35,7 @@
@interface SDLPresentChoiceSetOperation()
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (assign, nonatomic) UInt16 cancelId;
@end
@@ -48,6 +49,7 @@
@property (strong, nonatomic, readonly) SDLStateMachine *stateMachine;
@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+@property (assign, nonatomic) UInt16 nextCancelId;
@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext;
@@ -62,7 +64,7 @@
@property (assign, nonatomic, getter=isVROptional) BOOL vrOptional;
- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification;
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability;
+- (void)sdl_displayCapabilityDidUpdate;
@end
@@ -134,9 +136,8 @@ describe(@"choice set manager tests", ^{
it(@"should enable the queue when receiving a good window capability", ^{
testManager.currentWindowCapability = disabledWindowCapability;
-
- SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"TEST" windowCapabilities:@[enabledWindowCapability] windowTypeSupported:nil];
- [testManager sdl_displayCapabilityDidUpdate:[[SDLSystemCapability alloc] initWithDisplayCapabilities:@[displayCapability]]];
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(enabledWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
expect(testManager.transactionQueue.isSuspended).to(beFalse());
});
@@ -158,15 +159,15 @@ describe(@"choice set manager tests", ^{
});
it(@"should suspend the queue when receiving a bad display capability", ^{
- SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"TEST" windowCapabilities:@[disabledWindowCapability] windowTypeSupported:nil];
- [testManager sdl_displayCapabilityDidUpdate:[[SDLSystemCapability alloc] initWithDisplayCapabilities:@[displayCapability]]];
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(disabledWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
expect(testManager.transactionQueue.isSuspended).to(beTrue());
});
it(@"should not suspend the queue when receiving an empty display capability", ^{
- SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"TEST" windowCapabilities:@[blankWindowCapability] windowTypeSupported:nil];
- [testManager sdl_displayCapabilityDidUpdate:[[SDLSystemCapability alloc] initWithDisplayCapabilities:@[displayCapability]]];
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(blankWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
expect(testManager.transactionQueue.isSuspended).to(beTrue());
});
@@ -526,6 +527,45 @@ describe(@"choice set manager tests", ^{
});
});
+ describe(@"generating a cancel id", ^{
+ __block SDLChoiceSet *testChoiceSet = nil;
+ __block id<SDLChoiceSetDelegate> testChoiceDelegate = nil;
+
+ beforeEach(^{
+ testChoiceDelegate = OCMProtocolMock(@protocol(SDLChoiceSetDelegate));
+ testChoiceSet = [[SDLChoiceSet alloc] initWithTitle:@"tests" delegate:testChoiceDelegate choices:@[testCell1]];
+ testManager = [[SDLChoiceSetManager alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager systemCapabilityManager:testSystemCapabilityManager];
+ [testManager.stateMachine setToState:SDLChoiceManagerStateReady fromOldState:SDLChoiceManagerStateCheckingVoiceOptional callEnterTransition:NO];
+ });
+
+ it(@"should set the first cancelID correctly", ^{
+ [testManager presentChoiceSet:testChoiceSet mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
+
+ expect(testManager.transactionQueue.operations.count).to(equal(2));
+ expect(testManager.transactionQueue.operations[0]).to(beAKindOf([SDLPreloadChoicesOperation class]));
+ SDLPresentChoiceSetOperation *testPresentOp = (SDLPresentChoiceSetOperation *)testManager.transactionQueue.operations[1];
+ expect(@(testPresentOp.cancelId)).to(equal(101));
+ });
+
+ it(@"should reset the cancelID correctly once the max has been reached", ^{
+ testManager.nextCancelId = 200;
+ [testManager presentChoiceSet:testChoiceSet mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
+
+ expect(testManager.transactionQueue.operations.count).to(equal(2));
+
+ expect(testManager.transactionQueue.operations[0]).to(beAKindOf([SDLPreloadChoicesOperation class]));
+ SDLPresentChoiceSetOperation *testPresentOp = (SDLPresentChoiceSetOperation *)testManager.transactionQueue.operations[1];
+ expect(@(testPresentOp.cancelId)).to(equal(200));
+
+ [testManager presentChoiceSet:testChoiceSet mode:SDLInteractionModeBoth withKeyboardDelegate:nil];
+
+ expect(testManager.transactionQueue.operations.count).to(equal(3));
+
+ SDLPresentChoiceSetOperation *testPresentOp2 = (SDLPresentChoiceSetOperation *)testManager.transactionQueue.operations[2];
+ expect(@(testPresentOp2.cancelId)).to(equal(101));
+ });
+ });
+
describe(@"dismissing the keyboard", ^{
__block SDLPresentKeyboardOperation *mockKeyboardOp1 = nil;
__block SDLPresentKeyboardOperation *mockKeyboardOp2 = nil;
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
index 0a4045f5a..7e5c89925 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
@@ -24,18 +24,18 @@
@property (copy, nonatomic, nullable) SDLHMILevel currentHMILevel;
@property (copy, nonatomic, nullable) SDLSystemContext currentSystemContext;
-
+@property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability;
@property (strong, nonatomic, nullable) NSArray<SDLRPCRequest *> *inProgressUpdate;
@property (assign, nonatomic) BOOL hasQueuedUpdate;
@property (assign, nonatomic) BOOL waitingOnHMIUpdate;
@property (copy, nonatomic) NSArray<SDLMenuCell *> *waitingUpdateMenuCells;
-@property (strong, nonatomic, nullable) SDLWindowCapability *windowCapability;
@property (assign, nonatomic) UInt32 lastMenuId;
@property (copy, nonatomic) NSArray<SDLMenuCell *> *oldMenuCells;
- (BOOL)sdl_shouldRPCsIncludeImages:(NSArray<SDLMenuCell *> *)cells;
+- (void)sdl_displayCapabilityDidUpdate;
@end
@@ -166,6 +166,22 @@ describe(@"menu manager", ^{
});
});
+ describe(@"display capability updates", ^{
+ beforeEach(^{
+ testManager.currentHMILevel = SDLHMILevelFull;
+ testManager.currentSystemContext = SDLSystemContextMain;
+ });
+
+ it(@"should save the new window capability", ^{
+ SDLWindowCapability *testWindowCapability = [[SDLWindowCapability alloc] init];
+ testWindowCapability.textFields = @[[[SDLTextField alloc] initWithName:SDLTextFieldNameMenuName characterSet:SDLCharacterSetUtf8 width:500 rows:1]];
+ OCMStub([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+
+ expect(testManager.windowCapability).to(equal(testWindowCapability));
+ });
+ });
+
describe(@"updating menu cells", ^{
beforeEach(^{
testManager.currentHMILevel = SDLHMILevelFull;
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
index a24c432a7..8ddfd6aec 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
@@ -46,7 +46,7 @@
@property (strong, nonatomic) NSMutableArray<SDLAsynchronousOperation *> *batchQueue;
- (void)sdl_hmiStatusNotification:(SDLRPCNotificationNotification *)notification;
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability;
+- (void)sdl_displayCapabilityDidUpdate;
@end
@@ -76,6 +76,8 @@ describe(@"a soft button manager", ^{
__block SDLArtwork *object2State1Art = [[SDLArtwork alloc] initWithData:[@"TestData" dataUsingEncoding:NSUTF8StringEncoding] name:object2State1ArtworkName fileExtension:@"png" persistent:YES];
__block SDLSoftButtonState *object2State1 = [[SDLSoftButtonState alloc] initWithStateName:object2State1Name text:object2State1Text artwork:object2State1Art];
+ __block SDLWindowCapability *testWindowCapability = nil;
+
beforeEach(^{
testFileManager = OCMClassMock([SDLFileManager class]);
testSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]);
@@ -84,71 +86,102 @@ describe(@"a soft button manager", ^{
testManager = [[SDLSoftButtonManager alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager systemCapabilityManager:testSystemCapabilityManager];
[testManager start];
- SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
- status.hmiLevel = SDLHMILevelFull;
- [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
-
SDLSoftButtonCapabilities *softButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
softButtonCapabilities.imageSupported = @YES;
softButtonCapabilities.textSupported = @YES;
softButtonCapabilities.longPressAvailable = @YES;
softButtonCapabilities.shortPressAvailable = @YES;
- SDLWindowCapability *windowCapability = [[SDLWindowCapability alloc] init];
- windowCapability.softButtonCapabilities = @[softButtonCapabilities];
- SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"TEST" windowCapabilities:@[windowCapability] windowTypeSupported:nil];
- [testManager sdl_displayCapabilityDidUpdate:[[SDLSystemCapability alloc] initWithDisplayCapabilities:@[displayCapability]]];
+ testWindowCapability = [[SDLWindowCapability alloc] init];
+ testWindowCapability.softButtonCapabilities = @[softButtonCapabilities];
});
it(@"should instantiate correctly", ^{
expect(testManager.connectionManager).to(equal(testConnectionManager));
expect(testManager.fileManager).to(equal(testFileManager));
-
expect(testManager.softButtonObjects).to(beEmpty());
expect(testManager.currentMainField1).to(beNil());
expect(testManager.transactionQueue).toNot(beNil());
- expect(testManager.transactionQueue.isSuspended).to(beFalse());
- expect(testManager.softButtonCapabilities).toNot(beNil());
- expect(testManager.currentLevel).to(equal(SDLHMILevelFull));
-
- // These are set up earlier for future tests and therefore won't be nil
-// expect(testManager.windowCapability).to(beNil());
-// expect(testManager.currentLevel).to(beNil());
+ expect(testManager.transactionQueue.isSuspended).to(beTrue());
+ expect(testManager.softButtonCapabilities).to(beNil());
+ expect(testManager.currentLevel).to(beNil());
});
- context(@"when in HMI NONE", ^{
+ describe(@"the SDL app has not been opened", ^{
beforeEach(^{
- SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
- status.hmiLevel = SDLHMILevelNone;
- [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
-
testObject1 = [[SDLSoftButtonObject alloc] initWithName:@"name1" states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
testObject2 = [[SDLSoftButtonObject alloc] initWithName:@"name2" state:object2State1 handler:nil];
-
testManager.softButtonObjects = @[testObject1, testObject2];
});
- it(@"should set the soft buttons, but not update", ^{
- expect(testManager.softButtonObjects).toNot(beEmpty());
- expect(testManager.transactionQueue.suspended).to(beTrue());
+ context(@"when the HMI level notification has not been received", ^{
+ it(@"should set the soft buttons, but not update", ^{
+ expect(testManager.currentLevel).to(beNil());
+ expect(testManager.softButtonObjects).toNot(beEmpty());
+ expect(testManager.transactionQueue.suspended).to(beTrue());
+ });
+ });
+
+ context(@"when the HMI level is NONE", ^{
+ beforeEach(^{
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelNone;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
+ });
+
+ it(@"should set the soft buttons, but not update", ^{
+ expect(testManager.currentLevel).to(equal(SDLHMILevelNone));
+ expect(testManager.softButtonObjects).toNot(beEmpty());
+ expect(testManager.transactionQueue.suspended).to(beTrue());
+ });
});
});
- context(@"when there are no soft button capabilities", ^{
+ describe(@"the SDL app has been opened", ^{
beforeEach(^{
- SDLWindowCapability *windowCapability = [[SDLWindowCapability alloc] init];
- SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"TEST" windowCapabilities:@[windowCapability] windowTypeSupported:nil];
- [testManager sdl_displayCapabilityDidUpdate:[[SDLSystemCapability alloc] initWithDisplayCapabilities:@[displayCapability]]];
+ testObject1 = [[SDLSoftButtonObject alloc] initWithName:@"name1" states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
+ testObject2 = [[SDLSoftButtonObject alloc] initWithName:@"name2" state:object2State1 handler:nil];
+ testManager.softButtonObjects = @[testObject1, testObject2];
+
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelFull;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
});
- it(@"should set the buttons but have the queue suspended", ^{
- expect(testManager.softButtonObjects).toNot(beNil());
- expect(testManager.transactionQueue.isSuspended).to(beTrue());
+ context(@"when the soft button capabilities notification has not been received", ^{
+ beforeEach(^{
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(nil);
+ [testManager sdl_displayCapabilityDidUpdate];
+ });
+
+ it(@"should set the buttons but have the queue suspended", ^{
+ expect(testManager.softButtonObjects).toNot(beNil());
+ expect(testManager.transactionQueue.isSuspended).to(beTrue());
+ });
+ });
+
+ context(@"when the soft button capabilities notification has been received", ^{
+ beforeEach(^{
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+ });
+
+ it(@"should set the buttons and unsuspend the queue", ^{
+ expect(testManager.softButtonObjects).toNot(beNil());
+ expect(testManager.transactionQueue.isSuspended).to(beFalse());
+ });
});
});
- context(@"when button objects have the same name", ^{
+ describe(@"invalid button objects (button objects have same names)", ^{
beforeEach(^{
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelFull;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
+
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+
NSString *sameName = @"Same name";
testObject1 = [[SDLSoftButtonObject alloc] initWithName:sameName states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
testObject2 = [[SDLSoftButtonObject alloc] initWithName:sameName state:object2State1 handler:nil];
@@ -162,8 +195,15 @@ describe(@"a soft button manager", ^{
});
});
- context(@"when button objects have different names", ^{
+ describe(@"valid button objects (button objects have different names)", ^{
beforeEach(^{
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelFull;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
+
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+
testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
@@ -228,6 +268,13 @@ describe(@"a soft button manager", ^{
describe(@"transitioning soft button states", ^{
beforeEach(^{
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelFull;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
+
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+
testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
@@ -266,6 +313,17 @@ describe(@"a soft button manager", ^{
context(@"On disconnects", ^{
beforeEach(^{
+ SDLOnHMIStatus *status = [[SDLOnHMIStatus alloc] init];
+ status.hmiLevel = SDLHMILevelFull;
+ [testManager sdl_hmiStatusNotification:[[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:nil rpcNotification:status]];
+
+ OCMStub([testSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
+
+ testObject1 = [[SDLSoftButtonObject alloc] initWithName:@"name1" states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
+ testObject2 = [[SDLSoftButtonObject alloc] initWithName:@"name2" state:object2State1 handler:nil];
+ testManager.softButtonObjects = @[testObject1, testObject2];
+
[testManager stop];
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
index c3e95144a..ea230248c 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
@@ -42,7 +42,7 @@
@property (assign, nonatomic) BOOL isDirty;
-- (void)sdl_displayCapabilityDidUpdate:(SDLSystemCapability *)systemCapability;
+- (void)sdl_displayCapabilityDidUpdate;
@end
@@ -491,9 +491,9 @@ describe(@"text and graphic manager", ^{
testWindowCapability = [[SDLWindowCapability alloc] initWithWindowID:@(SDLPredefinedWindowsDefaultWindow) textFields:nil imageFields:nil imageTypeSupported:nil templatesAvailable:nil numCustomPresetsAvailable:nil buttonCapabilities:nil softButtonCapabilities:nil menuLayoutsAvailable:nil dynamicUpdateCapabilities:nil keyboardCapabilities:nil];
testDisplayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:@"Test display" windowCapabilities:@[testWindowCapability] windowTypeSupported:nil];
- testSystemCapability = [[SDLSystemCapability alloc] initWithDisplayCapabilities:@[testDisplayCapability]];
- [testManager sdl_displayCapabilityDidUpdate:testSystemCapability];
+ OCMStub([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
});
// with a non-default window
@@ -560,7 +560,8 @@ describe(@"text and graphic manager", ^{
});
it(@"should start the transaction queue and not send a transaction", ^{
- [testManager sdl_displayCapabilityDidUpdate:testSystemCapability];
+ OCMStub([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
expect(testManager.transactionQueue.isSuspended).to(beFalse());
expect(testManager.transactionQueue.operationCount).to(equal(0));
@@ -569,7 +570,8 @@ describe(@"text and graphic manager", ^{
context(@"if there's data", ^{
beforeEach(^{
testManager.textField1 = @"test";
- [testManager sdl_displayCapabilityDidUpdate:testSystemCapability];
+ OCMStub([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testManager sdl_displayCapabilityDidUpdate];
});
it(@"should send an update and not supersede the previous update", ^{
diff --git a/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLWindowCapabilitySpec.m b/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLWindowCapabilitySpec.m
index ee5d34ef5..7543826eb 100644
--- a/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLWindowCapabilitySpec.m
+++ b/SmartDeviceLinkTests/RPCSpecs/StructSpecs/SDLWindowCapabilitySpec.m
@@ -18,6 +18,7 @@
#import "SDLTextFieldName.h"
#import "SDLWindowCapability+ScreenManagerExtensions.h"
+
QuickSpecBegin(SDLWindowCapabilitySpec)
__block SDLWindowCapability *testStruct = nil;
@@ -282,4 +283,111 @@ describe(@"creating a valid keyboard configuration based on keyboard capabilitie
});
});
+describe(@"extensions", ^{
+ describe(@"getting the max number of main field lines", ^{
+ __block SDLTextField *testTextField1 = nil;
+ __block SDLTextField *testTextField2 = nil;
+ __block SDLTextField *testTextField3 = nil;
+ __block SDLTextField *testTextField4 = nil;
+
+ beforeEach(^{
+ testTextField1 = [[SDLTextField alloc] init];
+ testTextField1.name = SDLTextFieldNameMainField1;
+
+ testTextField2 = [[SDLTextField alloc] init];
+ testTextField2.name = SDLTextFieldNameMainField2;
+
+ testTextField3 = [[SDLTextField alloc] init];
+ testTextField3.name = SDLTextFieldNameMainField3;
+
+ testTextField4 = [[SDLTextField alloc] init];
+ testTextField4.name = SDLTextFieldNameMainField4;
+
+ testStruct = [[SDLWindowCapability alloc] init];
+ });
+
+ it(@"should return 0 if none of the text fields are supported", ^ {
+ testStruct.textFields = @[];
+ NSUInteger maxNumberOfMainFieldLines = [testStruct maxNumberOfMainFieldLines];
+
+ expect(maxNumberOfMainFieldLines).to(equal(0));
+ });
+
+ it(@"should return 1 if only one text field is supported", ^ {
+ testStruct.textFields = @[testTextField1];
+ NSUInteger maxNumberOfMainFieldLines = [testStruct maxNumberOfMainFieldLines];
+
+ expect(maxNumberOfMainFieldLines).to(equal(1));
+ });
+
+ it(@"should return 2 if two text fields are supported", ^ {
+ testStruct.textFields = @[testTextField2, testTextField1];
+ NSUInteger maxNumberOfMainFieldLines = [testStruct maxNumberOfMainFieldLines];
+
+ expect(maxNumberOfMainFieldLines).to(equal(2));
+ });
+
+ it(@"should return 3 if all the text fields are supported", ^ {
+ testStruct.textFields = @[testTextField2, testTextField1, testTextField3];
+ NSUInteger maxNumberOfMainFieldLines = [testStruct maxNumberOfMainFieldLines];
+
+ expect(maxNumberOfMainFieldLines).to(equal(3));
+ });
+
+ it(@"should return 4 if all the text fields are supported", ^ {
+ testStruct.textFields = @[testTextField1, testTextField2, testTextField3, testTextField4];
+ NSUInteger maxNumberOfMainFieldLines = [testStruct maxNumberOfMainFieldLines];
+
+ expect(maxNumberOfMainFieldLines).to(equal(4));
+ });
+ });
+
+ describe(@"getting the max number of alert text field lines", ^{
+ __block SDLTextField *testAlertTextField1 = nil;
+ __block SDLTextField *testAlertTextField2 = nil;
+ __block SDLTextField *testAlertTextField3 = nil;
+
+ beforeEach(^{
+ testAlertTextField1 = [[SDLTextField alloc] init];
+ testAlertTextField1.name = SDLTextFieldNameAlertText1;
+
+ testAlertTextField2 = [[SDLTextField alloc] init];
+ testAlertTextField2.name = SDLTextFieldNameAlertText2;
+
+ testAlertTextField3 = [[SDLTextField alloc] init];
+ testAlertTextField3.name = SDLTextFieldNameAlertText3;
+
+ testStruct = [[SDLWindowCapability alloc] init];
+ });
+
+ it(@"should return 0 if none of the text fields are supported", ^ {
+ testStruct.textFields = @[];
+ NSUInteger maxNumberOfAlertMainFieldLines = [testStruct maxNumberOfAlertFieldLines];
+
+ expect(maxNumberOfAlertMainFieldLines).to(equal(0));
+ });
+
+ it(@"should return 1 if only one text field is supported", ^ {
+ testStruct.textFields = @[testAlertTextField1];
+ NSUInteger maxNumberOfAlertMainFieldLines = [testStruct maxNumberOfAlertFieldLines];
+
+ expect(maxNumberOfAlertMainFieldLines).to(equal(1));
+ });
+
+ it(@"should return 2 if two text fields are supported", ^ {
+ testStruct.textFields = @[testAlertTextField1, testAlertTextField2];
+ NSUInteger maxNumberOfAlertMainFieldLines = [testStruct maxNumberOfAlertFieldLines];
+
+ expect(maxNumberOfAlertMainFieldLines).to(equal(2));
+ });
+
+ it(@"should return 3 if all the text fields are supported", ^ {
+ testStruct.textFields = @[testAlertTextField1, testAlertTextField2, testAlertTextField3];
+ NSUInteger maxNumberOfAlertMainFieldLines = [testStruct maxNumberOfAlertFieldLines];
+
+ expect(maxNumberOfAlertMainFieldLines).to(equal(3));
+ });
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLAlertAudioDataSpec.m b/SmartDeviceLinkTests/SDLAlertAudioDataSpec.m
new file mode 100644
index 000000000..33aab3d0d
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLAlertAudioDataSpec.m
@@ -0,0 +1,98 @@
+//
+// SDLAlertAudioDataSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLAlertAudioData.h"
+#import "SDLFile.h"
+#import "SDLTTSChunk.h"
+
+
+@interface SDLAlertAudioData()
+
+@property (nullable, copy, nonatomic, readonly) NSDictionary<NSString *, SDLFile *> *audioFileData;
+
+@end
+
+QuickSpecBegin(SDLAlertAudioDataSpec)
+
+describe(@"SDLAlertAudioData", ^{
+ __block NSString *testSpeechSynthesizerString = nil;
+ __block SDLFile *testAudioFile = nil;
+ __block SDLFile *testAudioFile2 = nil;
+
+ beforeEach(^{
+ testSpeechSynthesizerString = @"testSpeechSynthesizerString";
+
+ NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
+ testAudioFile = [[SDLFile alloc] initWithFileURL:[testBundle URLForResource:@"testAudio" withExtension:@"mp3"] name:@"testAudioFile" persistent:YES];
+ testAudioFile2 = [[SDLFile alloc] initWithFileURL:[testBundle URLForResource:@"testAudio" withExtension:@"mp3"] name:@"testAudioFile2" persistent:YES];
+ });
+
+ describe(@"Initialization", ^{
+ it(@"Should get and set playTone correctly", ^{
+ SDLAlertAudioData *testAlertAudioData = [[SDLAlertAudioData alloc] initWithAudioFile:testAudioFile];
+ testAlertAudioData.playTone = YES;
+
+ expect(testAlertAudioData.playTone).to(beTrue());
+ expect(testAlertAudioData.audioData).to(haveCount(1));
+ expect(testAlertAudioData.audioFileData).to(haveCount(1));
+ });
+ });
+
+ describe(@"Copying alert audio data", ^{
+ __block SDLAlertAudioData *testAlertAudioData = nil;
+ __block SDLAlertAudioData *copiedTestAlertAudioData = nil;
+ __block NSString *testSpeechSynthesizerString1 = @"testSpeechSynthesizerString1";
+ __block NSString *testSpeechSynthesizerString2 = @"testSpeechSynthesizerString2";
+
+ beforeEach(^{
+ testAlertAudioData = [[SDLAlertAudioData alloc] initWithAudioFile:testAudioFile];
+ [testAlertAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString1]];
+ testAlertAudioData.playTone = YES;
+
+ copiedTestAlertAudioData = [testAlertAudioData copy];
+ });
+
+ it(@"Should copy correctly", ^{
+ expect(testAlertAudioData == copiedTestAlertAudioData).to(beFalse());
+ expect(testAlertAudioData.audioData).to(equal(copiedTestAlertAudioData.audioData));
+ expect(testAlertAudioData.audioFileData).to(equal(copiedTestAlertAudioData.audioFileData));
+ expect(testAlertAudioData.playTone).to(equal(copiedTestAlertAudioData.playTone));
+ });
+
+ it(@"Should not update the copy if changes are made to the original", ^{
+ [testAlertAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString2]];
+ [testAlertAudioData addAudioFiles:@[testAudioFile2]];
+ testAlertAudioData.playTone = NO;
+
+ expect(testAlertAudioData.audioData).to(haveCount(4));
+ expect(testAlertAudioData.audioData[0].text).to(equal(testAudioFile.name));
+ expect(testAlertAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString1));
+ expect(testAlertAudioData.audioData[2].text).to(equal(testSpeechSynthesizerString2));
+ expect(testAlertAudioData.audioData[3].text).to(equal(testAudioFile2.name));
+
+ expect(copiedTestAlertAudioData.audioData).to(haveCount(2));
+ expect(copiedTestAlertAudioData.audioData[0].text).to(equal(testAudioFile.name));
+ expect(copiedTestAlertAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString1));
+
+ expect(testAlertAudioData.audioFileData).to(haveCount(2));
+ expect(testAlertAudioData.audioFileData[testAudioFile.name]).to(equal(testAudioFile));
+ expect(testAlertAudioData.audioFileData[testAudioFile2.name]).to(equal(testAudioFile2));
+
+ expect(copiedTestAlertAudioData.audioFileData).to(haveCount(1));
+ expect(testAlertAudioData.audioFileData[testAudioFile.name]).to(equal(testAudioFile));
+
+ expect(testAlertAudioData.playTone).to(beFalse());
+ expect(copiedTestAlertAudioData.playTone).to(beTrue());
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLAlertManagerSpec.m b/SmartDeviceLinkTests/SDLAlertManagerSpec.m
new file mode 100644
index 000000000..e39d770cc
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLAlertManagerSpec.m
@@ -0,0 +1,316 @@
+//
+// SDLAlertManagerSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Nicole on 11/18/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLAlertManager.h"
+#import "SDLAlertView.h"
+#import "SDLFileManager.h"
+#import "SDLPermissionElement.h"
+#import "SDLPermissionManager.h"
+#import "SDLPresentAlertOperation.h"
+#import "SDLSystemCapabilityManager.h"
+#import "SDLWindowCapability.h"
+#import "TestConnectionManager.h"
+
+@interface SDLAlertManager()
+
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+@property (assign, nonatomic) UInt16 nextCancelId;
+@property (copy, nonatomic, nullable) SDLWindowCapability *currentWindowCapability;
+
+- (void)sdl_displayCapabilityDidUpdate;
+
+@end
+
+@interface SDLPresentAlertOperation()
+
+@property (copy, nonatomic, nullable) NSError *internalError;
+@property (assign, nonatomic) UInt16 cancelId;
+
+@end
+
+QuickSpecBegin(SDLAlertManagerSpec)
+
+describe(@"alert manager tests", ^{
+ __block SDLAlertManager *testAlertManager = nil;
+ __block id mockConnectionManager = nil;
+ __block id mockFileManager = nil;
+ __block id mockSystemCapabilityManager = nil;
+ __block id mockCurrentWindowCapability = nil;
+ __block id mockPermissionManager = nil;
+ __block SDLWindowCapability *testWindowCapability = nil;
+
+ beforeEach(^{
+ mockConnectionManager = OCMProtocolMock(@protocol(SDLConnectionManagerType));
+ mockFileManager = OCMClassMock([SDLFileManager class]);
+ mockSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]);
+ mockCurrentWindowCapability = OCMClassMock([SDLWindowCapability class]);
+ mockPermissionManager = OCMClassMock([SDLPermissionManager class]);
+
+ testWindowCapability = [[SDLWindowCapability alloc] init];
+ testWindowCapability.numCustomPresetsAvailable = @10;
+ testWindowCapability.windowID = @(SDLPredefinedWindowsDefaultWindow);
+ });
+
+ describe(@"when initialized", ^{
+ it(@"the transaction queue should be suspended", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+
+ expect(testAlertManager.transactionQueue.suspended).toEventually(beTrue());
+ });
+ });
+
+ describe(@"when started", ^{
+ it(@"should subscribe to capability and permission updates", ^{
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wundeclared-selector"
+ SEL testSelector = @selector(sdl_displayCapabilityDidUpdate);
+#pragma clang diagnostic pop
+
+ OCMExpect([mockSystemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:[OCMArg any] selector:testSelector]);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLPermissionElement *> *permissionElements = (NSArray<SDLPermissionElement *> *)value;
+ SDLPermissionElement *permissionElement = permissionElements[0];
+ expect(permissionElement.rpcName).to(equal(SDLRPCFunctionNameAlert));
+ return [permissionElement isKindOfClass:[SDLPermissionElement class]];
+ }] groupType:SDLPermissionGroupTypeAny withHandler:[OCMArg any]]);
+
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ OCMVerifyAll(mockSystemCapabilityManager);
+ OCMVerifyAll(mockPermissionManager);
+ });
+
+ describe(@"transaction queue state", ^{
+ it(@"should not start the transaction queue until the alert rpc has the correct permissions to be sent", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg any] groupType:SDLPermissionGroupTypeAny withHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], @(SDLPermissionGroupStatusDisallowed), nil])]);
+
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ expect(testAlertManager.transactionQueue.suspended).to(beTrue());
+ });
+
+ it(@"should start the transaction queue if the alert rpc has the correct permissions and the currentWindowCapability has been set", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg any] groupType:SDLPermissionGroupTypeAny withHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], @(SDLPermissionGroupStatusAllowed), nil])]);
+
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ expect(testAlertManager.transactionQueue.suspended).toEventually(beFalse());
+ });
+
+ it(@"should not start the transaction queue until the currentWindowCapability has been set", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(nil);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg any] groupType:SDLPermissionGroupTypeAny withHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], @(SDLPermissionGroupStatusAllowed), nil])]);
+
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ expect(testAlertManager.transactionQueue.suspended).toEventually(beTrue());
+ });
+ });
+ });
+
+ describe(@"When the display capability updates", ^{
+ __block SDLAlertView *testAlertView = nil;
+ __block SDLAlertView *testAlertView2 = nil;
+
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"alert text" secondaryText:nil tertiaryText:nil timeout:@(5.0) showWaitIndicator:@(NO) audioIndication:nil buttons:nil icon:nil];
+ testAlertView2 = [[SDLAlertView alloc] initWithText:@"alert 2 text" secondaryText:nil tertiaryText:nil timeout:@(5.0) showWaitIndicator:@(NO) audioIndication:nil buttons:nil icon:nil];
+ });
+
+ it(@"should suspend the queue if the new capability is nil and it should not update operations that are currently excecuting with the new capability", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg any] groupType:SDLPermissionGroupTypeAny withHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], @(SDLPermissionGroupStatusAllowed), nil])]);
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ expect(testAlertManager.transactionQueue.suspended).to(beFalse());
+
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+ [testAlertManager presentAlert:testAlertView2 withCompletionHandler:nil];
+ expect(testAlertManager.transactionQueue.operationCount).to(equal(2));
+
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(nil);
+ [testAlertManager sdl_displayCapabilityDidUpdate];
+
+ OCMVerifyAllWithDelay(mockSystemCapabilityManager, 0.5);
+
+ expect(testAlertManager.transactionQueue.suspended).to(beTrue());
+ expect(testAlertManager.transactionQueue.operationCount).to(equal(2));
+
+ SDLPresentAlertOperation *presentAlertOp1 = testAlertManager.transactionQueue.operations[0];
+ SDLPresentAlertOperation *presentAlertOp2 = testAlertManager.transactionQueue.operations[1];
+ expect(presentAlertOp1.isExecuting).to(beTrue());
+ expect(presentAlertOp2.isExecuting).to(beFalse());
+ expect(presentAlertOp1.currentWindowCapability).to(equal(testWindowCapability));
+ expect(presentAlertOp2.currentWindowCapability).to(beNil());
+ });
+
+ it(@"should start the queue if the new capability is not nil and update the pending operations with the new capability", ^{
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(nil);
+ OCMExpect([mockPermissionManager subscribeToRPCPermissions:[OCMArg any] groupType:SDLPermissionGroupTypeAny withHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], @(SDLPermissionGroupStatusAllowed), nil])]);
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+
+ expect(testAlertManager.transactionQueue.suspended).to(beTrue());
+
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+ [testAlertManager presentAlert:testAlertView2 withCompletionHandler:nil];
+ expect(testAlertManager.transactionQueue.operationCount).to(equal(2));
+
+ OCMExpect([mockSystemCapabilityManager defaultMainWindowCapability]).andReturn(testWindowCapability);
+ [testAlertManager sdl_displayCapabilityDidUpdate];
+
+ OCMVerifyAllWithDelay(mockSystemCapabilityManager, 0.5);
+
+ expect(testAlertManager.transactionQueue.suspended).toEventually(beFalse());
+ expect(testAlertManager.transactionQueue.operationCount).to(equal(2));
+
+ SDLPresentAlertOperation *presentAlertOp1 = testAlertManager.transactionQueue.operations[0];
+ SDLPresentAlertOperation *presentAlertOp2 = testAlertManager.transactionQueue.operations[1];
+ expect(presentAlertOp1.isExecuting).to(beTrue());
+ expect(presentAlertOp2.isExecuting).to(beFalse());
+ expect(presentAlertOp2.currentWindowCapability).to(equal(testWindowCapability));
+ expect(presentAlertOp1.currentWindowCapability).to(equal(testWindowCapability));
+ });
+ });
+
+ describe(@"generating a cancel id", ^{
+ __block SDLAlertView *testAlertView = nil;
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"alert text" secondaryText:nil tertiaryText:nil timeout:@(5.0) showWaitIndicator:@(NO) audioIndication:nil buttons:nil icon:nil];
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+ });
+
+ it(@"should set the first cancelID correctly", ^{
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+
+ expect(testAlertManager.transactionQueue.operations.count).to(equal(1));
+
+ SDLPresentAlertOperation *testPresentOp = (SDLPresentAlertOperation *)testAlertManager.transactionQueue.operations.firstObject;
+ expect(@(testPresentOp.cancelId)).to(equal(1));
+ });
+
+ it(@"should reset the cancelID correctly once the max has been reached", ^{
+ testAlertManager.nextCancelId = 100;
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+
+ expect(testAlertManager.transactionQueue.operations.count).to(equal(1));
+
+ SDLPresentAlertOperation *testPresentOp = (SDLPresentAlertOperation *)testAlertManager.transactionQueue.operations[0];
+ expect(@(testPresentOp.cancelId)).to(equal(100));
+
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+
+ expect(testAlertManager.transactionQueue.operations.count).to(equal(2));
+
+ SDLPresentAlertOperation *testPresentOp2 = (SDLPresentAlertOperation *)testAlertManager.transactionQueue.operations[1];
+ expect(@(testPresentOp2.cancelId)).to(equal(1));
+ });
+ });
+
+ describe(@"presenting an alert", ^{
+ __block SDLAlertView *testAlertView = nil;
+ __block SDLAlertView *testAlertView2 = nil;
+
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"alert text" secondaryText:nil tertiaryText:nil timeout:@(5.0) showWaitIndicator:@(NO) audioIndication:nil buttons:nil icon:nil];
+ testAlertView2 = [[SDLAlertView alloc] initWithText:@"alert 2 text" secondaryText:nil tertiaryText:nil timeout:@(5.0) showWaitIndicator:@(NO) audioIndication:nil buttons:nil icon:nil];
+ testAlertManager = [[SDLAlertManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
+ [testAlertManager start];
+ });
+
+ it(@"should add the alert operation to the queue", ^{
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+
+ expect(testAlertManager.transactionQueue.operations.count).to(equal(1));
+ expect(testAlertManager.transactionQueue.operations.firstObject).to(beAKindOf([SDLPresentAlertOperation class]));
+ });
+
+ describe(@"when the completion handler is called", ^{
+ __block BOOL completionHandlerCalled = NO;
+ __block NSError *completionHandlerError = nil;
+
+ beforeEach(^{
+ completionHandlerCalled = NO;
+ completionHandlerError = nil;
+
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:^(NSError * _Nullable error) {
+ completionHandlerCalled = YES;
+ completionHandlerError = error;
+ }];
+ });
+
+ context(@"without an error", ^{
+ it(@"should call the handler", ^{
+ SDLPresentAlertOperation *op = testAlertManager.transactionQueue.operations.lastObject;
+ op.internalError = nil;
+ op.completionBlock();
+
+ expect(completionHandlerCalled).to(beTrue());
+ expect(completionHandlerError).to(beNil());
+ });
+ });
+
+ context(@"with an error", ^{
+ __block NSError *testError;
+
+ beforeEach(^{
+ testError = [NSError errorWithDomain:@"com.sdl.testConnectionManager" code:-1 userInfo:nil];
+ });
+
+ it(@"should call the handler with the error", ^{
+ SDLPresentAlertOperation *op = testAlertManager.transactionQueue.operations.lastObject;
+ op.internalError = testError;
+ op.completionBlock();
+
+ expect(completionHandlerCalled).to(beTrue());
+ expect(completionHandlerError).to(equal(testError));
+ });
+ });
+ });
+
+ describe(@"when the manager shuts down during presentation", ^{
+ beforeEach(^{
+ [testAlertManager presentAlert:testAlertView withCompletionHandler:nil];
+ [testAlertManager presentAlert:testAlertView2 withCompletionHandler:nil];
+ });
+
+ it(@"should cancel any pending operations and unsubscribe to capability and permission updates", ^{
+ SDLPresentAlertOperation *presentAlertOp1 = testAlertManager.transactionQueue.operations[0];
+ SDLPresentAlertOperation *presentAlertOp2 = testAlertManager.transactionQueue.operations[1];
+
+ OCMExpect([mockSystemCapabilityManager unsubscribeFromCapabilityType:SDLSystemCapabilityTypeDisplays withObserver:[OCMArg any]]);
+ OCMExpect([mockPermissionManager removeAllObservers]);
+
+ [testAlertManager stop];
+
+ expect(presentAlertOp1.isCancelled).to(beTrue());
+ expect(presentAlertOp2.isCancelled).to(beTrue());
+ expect(testAlertManager.transactionQueue.operationCount).to(equal(0));
+
+ OCMVerifyAll(mockSystemCapabilityManager);
+ OCMVerifyAll(mockPermissionManager);
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLAlertViewSpec.m b/SmartDeviceLinkTests/SDLAlertViewSpec.m
new file mode 100644
index 000000000..2e5e0a381
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLAlertViewSpec.m
@@ -0,0 +1,307 @@
+//
+// SDLAlertViewSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Nicole on 11/10/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLAlertView.h"
+#import "SDLAlertAudioData.h"
+#import "SDLArtwork.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+#import "SDLStaticIconName.h"
+#import "SDLTTSChunk.h"
+
+@interface SDLAlertView()
+
+@property (nullable, copy, nonatomic) SDLAlertCanceledHandler canceledHandler;
+
+@end
+
+QuickSpecBegin(SDLAlertViewSpec)
+
+describe(@"An SDLAlertView", ^{
+ __block NSString *testTextField1 = nil;
+ __block NSString *testTextField2 = nil;
+ __block NSString *testTextField3 = nil;
+ __block NSTimeInterval testTimeout = 0;
+ __block SDLAlertAudioData *testAudio = nil;
+ __block BOOL testShowWaitIndicator = NO;
+ __block NSArray<SDLSoftButtonObject *> *testSoftButtons = nil;
+ __block SDLArtwork *testIcon = nil;
+ __block SDLArtwork *testIcon2 = nil;
+
+ beforeEach(^{
+ testTextField1 = @"testTextField1";
+ testTextField2 = @"testTextField2";
+ testTextField3 = @"testTextField3";
+ testAudio = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"test speech synthesizer string"];
+ testSoftButtons = @[[[SDLSoftButtonObject alloc] initWithName:@"test soft button" text:@"test soft button text" artwork:nil handler:nil]];
+
+ NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
+ UIImage *testImage = [[UIImage alloc] initWithContentsOfFile:[testBundle pathForResource:@"testImageJPEG" ofType:@"jpeg"]];
+ testIcon = [SDLArtwork artworkWithImage:testImage asImageFormat:SDLArtworkImageFormatJPG];
+ UIImage *testImage2 = [[UIImage alloc] initWithContentsOfFile:[testBundle pathForResource:@"testImagePNG" ofType:@"png"]];
+ testIcon2 = [SDLArtwork artworkWithImage:testImage2 asImageFormat:SDLArtworkImageFormatPNG];
+ });
+
+ it(@"should get and set correctly", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.text = testTextField1;
+ testAlertView.secondaryText = testTextField2;
+ testAlertView.tertiaryText = testTextField3;
+ testAlertView.timeout = testTimeout;
+ testAlertView.audio = testAudio;
+ testAlertView.showWaitIndicator = testShowWaitIndicator;
+ testAlertView.softButtons = testSoftButtons;
+ testAlertView.icon = testIcon;
+
+ expect(testAlertView.text).to(equal(testTextField1));
+ expect(testAlertView.secondaryText).to(equal(testTextField2));
+ expect(testAlertView.tertiaryText).to(equal(testTextField3));
+ expect(testAlertView.timeout).to(equal(5.0));
+
+ expect(testAlertView.audio == testAudio).to(beFalse());
+ expect(testAlertView.audio.audioData[0].text).to(equal(testAudio.audioData[0].text));
+ expect(testAlertView.audio.playTone).to(equal(testAudio.playTone));
+
+ expect(testAlertView.showWaitIndicator).to(beFalse());
+ expect(testAlertView.softButtons).to(equal(testSoftButtons));
+
+ expect(testAlertView.icon == testIcon).to(beFalse());
+ expect(testAlertView.icon.name).to(equal(testIcon.name));
+ });
+
+ describe(@"initializing", ^{
+ it(@"should initialize correctly with initWithText:buttons:", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] initWithText:testTextField1 buttons:testSoftButtons];
+
+ expect(testAlertView.text).to(equal(testTextField1));
+ expect(testAlertView.secondaryText).to(beNil());
+ expect(testAlertView.tertiaryText).to(beNil());
+ expect(testAlertView.timeout).to(equal(5.0));
+ expect(testAlertView.audio).to(beNil());
+ expect(testAlertView.showWaitIndicator).to(beFalse());
+ expect(testAlertView.softButtons).to(equal(testSoftButtons));
+ expect(testAlertView.icon).to(beNil());
+ });
+
+ it(@"should initialize correctly with initWithText:secondaryText:tertiaryText:timeout: showWaitIndicator:audioIndication:buttons:icon:", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] initWithText:testTextField1 secondaryText:testTextField2 tertiaryText:testTextField3 timeout:@(testTimeout) showWaitIndicator:@(testShowWaitIndicator) audioIndication:testAudio buttons:testSoftButtons icon:testIcon];
+
+ expect(testAlertView.text).to(equal(testTextField1));
+ expect(testAlertView.secondaryText).to(equal(testTextField2));
+ expect(testAlertView.tertiaryText).to(equal(testTextField3));
+ expect(testAlertView.timeout).to(equal(5.0));
+ expect(testAlertView.audio).toNot(beNil());
+
+ expect(testAlertView.audio == testAudio).to(beFalse());
+ expect(testAlertView.audio.audioData[0].text).to(equal(testAudio.audioData[0].text));
+ expect(testAlertView.audio.playTone).to(equal(testAudio.playTone));
+
+ expect(testAlertView.showWaitIndicator).to(beFalse());
+ expect(testAlertView.softButtons).to(equal(testSoftButtons));
+
+ expect(testAlertView.icon == testIcon).to(beFalse());
+ expect(testAlertView.icon.name).to(equal(testIcon.name));
+ });
+
+ it(@"should set a default value for timeout and set nil for all other properties", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] init];
+
+ expect(testAlertView.text).to(beNil());
+ expect(testAlertView.secondaryText).to(beNil());
+ expect(testAlertView.tertiaryText).to(beNil());
+ expect(testAlertView.timeout).to(equal(5.0));
+ expect(testAlertView.audio).to(beNil());
+ expect(testAlertView.showWaitIndicator).to(beFalse());
+ expect(testAlertView.softButtons).to(beNil());
+ expect(testAlertView.icon).to(beNil());
+ });
+ });
+
+ describe(@"setting invalid data", ^{
+ __block NSArray<SDLSoftButtonObject *> *testInvalidSoftButtons = nil;
+
+ beforeEach(^{
+ SDLSoftButtonState *state1 = [[SDLSoftButtonState alloc] initWithStateName:@"state1" text:@"state 1" image:nil];
+ SDLSoftButtonState *state2 = [[SDLSoftButtonState alloc] initWithStateName:@"state2" text:@"state 2" image:nil];
+ SDLSoftButtonObject *testInvalidSoftButton = [[SDLSoftButtonObject alloc] initWithName:@"invalid soft button" states:@[state1, state2] initialStateName:state1.name handler:nil];
+ SDLSoftButtonObject *testValidSoftButton = [[SDLSoftButtonObject alloc] initWithName:@"valid soft button" text:@"state 3" artwork:nil handler:nil];
+ testInvalidSoftButtons = @[testValidSoftButton, testInvalidSoftButton];
+ });
+
+ it(@"should throw an exception if any button has multiple states", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] init];
+
+ expectAction(^{
+ [testAlertView setSoftButtons:testInvalidSoftButtons];
+ }).to(raiseException().named(@"InvalidSoftButtonStates"));
+ });
+
+ it(@"should throw an exception if any button has multiple states when the property is set via a convenience init", ^{
+ expectAction(^{
+ (void)[[SDLAlertView alloc] initWithText:@"test" buttons:testInvalidSoftButtons];
+ }).to(raiseException().named(@"InvalidSoftButtonStates"));
+ });
+ });
+
+ describe(@"setting the default timeout", ^{
+ __block SDLAlertView *testAlertView = nil;
+
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] init];
+ });
+
+ it(@"should return 10 if a value greater than 10 has been set", ^{
+ SDLAlertView.defaultTimeout = 15.0;
+ expect(SDLAlertView.defaultTimeout).to(equal(10.0));
+ expect(testAlertView.timeout).to(equal(10.0));
+ });
+
+ it(@"should return 3 if a value less than 3 has been set", ^{
+ SDLAlertView.defaultTimeout = -2.0;
+ expect(SDLAlertView.defaultTimeout).to(equal(3.0));
+ expect(testAlertView.timeout).to(equal(3.0));
+ });
+
+ it(@"should return the set value if a value between 3 and 10 has been set", ^{
+ SDLAlertView.defaultTimeout = 4.5;
+ expect(SDLAlertView.defaultTimeout).to(equal(4.5));
+ expect(testAlertView.timeout).to(equal(4.5));
+ });
+
+ it(@"should return the set value if it is between 3 and 10", ^{
+ SDLAlertView.defaultTimeout = 3.0;
+ expect(SDLAlertView.defaultTimeout).to(equal(3.0));
+ expect(testAlertView.timeout).to(equal(3.0));
+ });
+ });
+
+ describe(@"setting the timeout", ^{
+ __block SDLAlertView *testAlertView = nil;
+ __block NSTimeInterval testDefaultTimeout = 5.0;
+
+ beforeEach(^{
+ SDLAlertView.defaultTimeout = testDefaultTimeout;
+ testAlertView = [[SDLAlertView alloc] init];
+ });
+
+ it(@"should return the default timeout if it has not been set", ^{
+ expect(testAlertView.timeout).to(equal(SDLAlertView.defaultTimeout));
+ });
+
+ it(@"should return the default timeout if 0 has been set", ^{
+ testAlertView.timeout = 0.0;
+ expect(testAlertView.timeout).to(equal(testDefaultTimeout));
+ });
+
+ it(@"should return 10 if a value greater than 10 has been set", ^{
+ testAlertView.timeout = 15.0;
+ expect(testAlertView.timeout).to(equal(10.0));
+ });
+
+ it(@"should return 3 if a value less than 3 has been set", ^{
+ testAlertView.timeout = 2.25;
+ expect(testAlertView.timeout).to(equal(3.0));
+ });
+
+ it(@"should return the set value if a value between 3 and 10 has been set", ^{
+ testAlertView.timeout = 9.5;
+ expect(testAlertView.timeout).to(equal(9.5));
+ });
+ });
+
+ describe(@"canceling the alert", ^{
+ __block SDLAlertView *testAlertView = nil;
+ __block BOOL canceledHandlerCalled = NO;
+
+ context(@"the cancel handler is set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.canceledHandler = ^{
+ canceledHandlerCalled = YES;
+ };
+ });
+
+ it(@"should call the cancelled handler", ^{
+ [testAlertView cancel];
+ expect(canceledHandlerCalled).to(beTrue());
+ });
+ });
+
+ context(@"the cancel handler is not set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.canceledHandler = nil;
+ });
+
+ it(@"should not crash", ^{
+ [testAlertView cancel];
+ });
+ });
+ });
+
+ describe(@"copying the alert", ^{
+ __block SDLAlertView *testAlertView = nil;
+ __block SDLAlertView *copiedTestAlertView = nil;
+
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:testTextField1 secondaryText:testTextField2 tertiaryText:testTextField3 timeout:@(testTimeout) showWaitIndicator:@(testShowWaitIndicator) audioIndication:testAudio buttons:testSoftButtons icon:testIcon];
+ testAlertView.canceledHandler = ^{};
+ copiedTestAlertView = [testAlertView copy];
+ });
+
+ it(@"should correctly copy the alert view", ^{
+ expect(testAlertView == copiedTestAlertView).to(beFalse());
+
+ expect(copiedTestAlertView.text).to(equal(testAlertView.text));
+ expect(copiedTestAlertView.secondaryText).to(equal(testAlertView.secondaryText));
+ expect(copiedTestAlertView.tertiaryText).to(equal(testAlertView.tertiaryText));
+ expect(copiedTestAlertView.timeout).to(equal(testAlertView.timeout));
+
+ expect(copiedTestAlertView.audio == testAlertView.audio).to(beFalse());
+ expect(copiedTestAlertView.audio.audioData).to(haveCount(1));
+ expect(copiedTestAlertView.audio.audioData[0]).to(equal(testAlertView.audio.audioData[0]));
+ expect(copiedTestAlertView.audio.playTone).to(equal(testAlertView.audio.playTone));
+
+ expect(copiedTestAlertView.showWaitIndicator).to(equal(testAlertView.showWaitIndicator));
+ expect(copiedTestAlertView.softButtons).to(equal(testAlertView.softButtons));
+
+ expect(copiedTestAlertView.icon == testAlertView.icon).to(beFalse());
+ expect(copiedTestAlertView.icon.name).to(equal(testAlertView.icon.name));
+
+ expect((id)copiedTestAlertView.canceledHandler).to(equal((id)testAlertView.canceledHandler));
+ });
+
+ it(@"Should not update the copy if changes are made to the original", ^{
+ testAlertView.text = @"changedText";
+ testAlertView.secondaryText = @"changedSecondaryText";
+ testAlertView.tertiaryText = @"changedTertiaryText";
+ testAlertView.timeout = 45;
+ testAlertView.audio = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"changedAudio"];
+ testAlertView.showWaitIndicator = YES;
+ testAlertView.softButtons = @[];
+ testAlertView.icon = testIcon2;
+ testAlertView.canceledHandler = ^{};
+
+ expect(copiedTestAlertView.text).toNot(equal(testAlertView.text));
+ expect(copiedTestAlertView.secondaryText).toNot(equal(testAlertView.secondaryText));
+ expect(copiedTestAlertView.tertiaryText).toNot(equal(testAlertView.tertiaryText));
+ expect(copiedTestAlertView.timeout).toNot(equal(testAlertView.timeout));
+ expect(copiedTestAlertView.audio).toNot(equal(testAlertView.audio));
+ expect(copiedTestAlertView.showWaitIndicator).toNot(equal(testAlertView.showWaitIndicator));
+ expect(copiedTestAlertView.softButtons).to(haveCount(1));
+ expect(copiedTestAlertView.softButtons).toNot(equal(testAlertView.softButtons));
+ expect(copiedTestAlertView.icon).toNot(equal(testAlertView.icon));
+ expect((id)copiedTestAlertView.canceledHandler).toNot(equal((id)testAlertView.canceledHandler));
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLAudioDataSpec.m b/SmartDeviceLinkTests/SDLAudioDataSpec.m
new file mode 100644
index 000000000..74d46a063
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLAudioDataSpec.m
@@ -0,0 +1,301 @@
+//
+// SDLAudioDataSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Nicole on 11/9/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLAudioData.h"
+#import "SDLFile.h"
+#import "SDLFileManagerConstants.h"
+#import "SDLTTSChunk.h"
+
+@interface SDLAudioData()
+
+@property (nullable, copy, nonatomic, readonly) NSDictionary<SDLFileName *, SDLFile *> *audioFileData;
+
+@end
+
+QuickSpecBegin(SDLAudioDataSpec)
+
+describe(@"SDLAudioData", ^{
+ __block NSString *testSpeechSynthesizerString = nil;
+ __block SDLFile *testAudioFile1 = nil;
+ __block SDLFile *testAudioFile2 = nil;
+ __block SDLFile *testAudioFile3 = nil;
+
+ beforeEach(^{
+ testSpeechSynthesizerString = @"testSpeechSynthesizerString";
+
+ NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
+ testAudioFile1 = [[SDLFile alloc] initWithFileURL:[testBundle URLForResource:@"testAudio" withExtension:@"mp3"] name:@"testAudioFile1" persistent:YES];
+ testAudioFile2 = [[SDLFile alloc] initWithFileURL:[testBundle URLForResource:@"testAudio" withExtension:@"mp3"] name:@"testAudioFile2" persistent:YES];
+ testAudioFile3 = [[SDLFile alloc] initWithFileURL:[testBundle URLForResource:@"testAudio" withExtension:@"mp3"] name:@"testAudioFile3" persistent:YES];
+ });
+
+ describe(@"Initialization", ^{
+ it(@"Should get correctly when initialized with initWithAudioFile:", ^{
+ SDLAudioData *testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ });
+
+ it(@"Should get correctly when initialized with initWithSpeechSynthesizerString:", ^{
+ SDLAudioData *testAudioData = [[SDLAudioData alloc] initWithSpeechSynthesizerString:testSpeechSynthesizerString];
+
+ expect(testAudioData.audioFileData).to(beEmpty());
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testSpeechSynthesizerString));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesText));
+ });
+
+ it(@"Should get correctly when initialized with initWithPhoneticSpeechSynthesizerString:phoneticType:", ^{
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesLHPlusPhonemes;
+ SDLAudioData *testAudioData = [[SDLAudioData alloc] initWithPhoneticSpeechSynthesizerString:testSpeechSynthesizerString phoneticType:testSpeechCapabilities];
+
+ expect(testAudioData.audioFileData).to(beEmpty());
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testSpeechSynthesizerString));
+ expect(testAudioData.audioData[0].type).to(equal(testSpeechCapabilities));
+ });
+
+ it(@"Should fail if initialized with an invalid phoneticType in initWithPhoneticSpeechSynthesizerString:phoneticType:", ^{
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesSilence;
+
+#pragma clang diagnostic push
+#pragma clang diagnostic ignored "-Wunused-value"
+ expectAction(^{ [[SDLAudioData alloc] initWithPhoneticSpeechSynthesizerString:testSpeechSynthesizerString phoneticType:testSpeechCapabilities]; }).to(raiseException().named(@"InvalidTTSSpeechCapabilities"));
+#pragma clang diagnostic pop
+ });
+ });
+
+ describe(@"Adding additional audio data", ^{
+ __block SDLAudioData *testAudioData = nil;
+ __block NSString *testSpeechSynthesizerString1 = @"testSpeechSynthesizerString1";
+ __block NSString *testSpeechSynthesizerString2 = @"testSpeechSynthesizerString2";
+ __block NSString *testSpeechSynthesizerString3 = @"testSpeechSynthesizerString3";
+ __block NSString *testEmptySpeechSynthesizerString = @"";
+
+ context(@"If adding audio files", ^{
+ it(@"Should append the audio file data to the current existing lists if the first added item was an audio file", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addAudioFiles:@[testAudioFile2, testAudioFile3]];
+
+ expect(testAudioData.audioData).to(haveCount(3));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[1].text).to(equal(testAudioFile2.name));
+ expect(testAudioData.audioData[2].text).to(equal(testAudioFile3.name));
+
+ expect(testAudioData.audioFileData).to(haveCount(3));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+ expect(testAudioData.audioFileData[testAudioFile2.name]).to(equal(testAudioFile2));
+ expect(testAudioData.audioFileData[testAudioFile3.name]).to(equal(testAudioFile3));
+ });
+
+ it(@"Should append the audio file data to the current existing lists if the first added item was a prompt", ^{
+ testAudioData = [[SDLAudioData alloc] initWithSpeechSynthesizerString:testSpeechSynthesizerString];
+ [testAudioData addAudioFiles:@[testAudioFile1]];
+
+ expect(testAudioData.audioData).to(haveCount(2));
+ expect(testAudioData.audioData[0].text).to(equal(testSpeechSynthesizerString));
+ expect(testAudioData.audioData[1].text).to(equal(testAudioFile1.name));
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+ });
+
+ it(@"Should replace audio file data with duplicate file names", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addAudioFiles:@[testAudioFile1, testAudioFile2, testAudioFile2]];
+
+ expect(testAudioData.audioData).to(haveCount(4));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[1].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[2].text).to(equal(testAudioFile2.name));
+ expect(testAudioData.audioData[3].text).to(equal(testAudioFile2.name));
+
+ expect(testAudioData.audioFileData).to(haveCount(2));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+ expect(testAudioData.audioFileData[testAudioFile2.name]).to(equal(testAudioFile2));
+ });
+ });
+
+ context(@"If adding speech synthesizer strings", ^{
+ it(@"Should append the additional speech synthesizer strings to the existing audio data", ^{
+ testAudioData = [[SDLAudioData alloc] initWithSpeechSynthesizerString:testSpeechSynthesizerString1];
+ [testAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString2, testSpeechSynthesizerString3]];
+
+ expect(testAudioData.audioFileData).to(beEmpty());
+
+ expect(testAudioData.audioData).to(haveCount(3));
+ expect(testAudioData.audioData[0].text).to(equal(testSpeechSynthesizerString1));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesText));
+ expect(testAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString2));
+ expect(testAudioData.audioData[1].type).to(equal(SDLSpeechCapabilitiesText));
+ expect(testAudioData.audioData[2].text).to(equal(testSpeechSynthesizerString3));
+ expect(testAudioData.audioData[2].type).to(equal(SDLSpeechCapabilitiesText));
+ });
+
+ it(@"Should not append any additional speech synthesizer strings that are empty", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString1, testEmptySpeechSynthesizerString, testSpeechSynthesizerString2]];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(3));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ expect(testAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString1));
+ expect(testAudioData.audioData[1].type).to(equal(SDLSpeechCapabilitiesText));
+ expect(testAudioData.audioData[2].text).to(equal(testSpeechSynthesizerString2));
+ expect(testAudioData.audioData[2].type).to(equal(SDLSpeechCapabilitiesText));
+ });
+
+ it(@"Should not append an array with only empty additional speech synthesizer strings", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addSpeechSynthesizerStrings:@[testEmptySpeechSynthesizerString]];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ });
+
+ it(@"Should not append an empty array of speech synthesizer strings", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addSpeechSynthesizerStrings:@[]];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ });
+ });
+
+ context(@"If adding phonetic speech synthesizer strings", ^{
+ it(@"Should append the additional phonetic speech synthesizer strings to the existing audio data", ^{
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesSAPIPhonemes;
+ testAudioData = [[SDLAudioData alloc] initWithPhoneticSpeechSynthesizerString:testSpeechSynthesizerString1 phoneticType:testSpeechCapabilities];
+ [testAudioData addPhoneticSpeechSynthesizerStrings:@[testSpeechSynthesizerString2] phoneticType:testSpeechCapabilities];
+
+ expect(testAudioData.audioFileData).to(beEmpty());
+
+ expect(testAudioData.audioData).to(haveCount(2));
+ expect(testAudioData.audioData[0].text).to(equal(testSpeechSynthesizerString1));
+ expect(testAudioData.audioData[0].type).to(equal(testSpeechCapabilities));
+ expect(testAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString2));
+ expect(testAudioData.audioData[1].type).to(equal(testSpeechCapabilities));
+ });
+
+ it(@"Should not append any additional phonetic speech synthesizer strings that are empty", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesText;
+ [testAudioData addPhoneticSpeechSynthesizerStrings:@[testSpeechSynthesizerString1, testSpeechSynthesizerString2, testEmptySpeechSynthesizerString] phoneticType:testSpeechCapabilities];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(3));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ expect(testAudioData.audioData[1].text).to(equal(testSpeechSynthesizerString1));
+ expect(testAudioData.audioData[1].type).to(equal(testSpeechCapabilities));
+ expect(testAudioData.audioData[2].text).to(equal(testSpeechSynthesizerString2));
+ expect(testAudioData.audioData[2].type).to(equal(testSpeechCapabilities));
+ });
+
+ it(@"Should not append an array with only empty additional phonetic speech synthesizer strings", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesText;
+ [testAudioData addPhoneticSpeechSynthesizerStrings:@[testEmptySpeechSynthesizerString] phoneticType:testSpeechCapabilities];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ });
+
+ it(@"Should not append an empty array of phonetic speech synthesizer strings", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesText;
+ [testAudioData addPhoneticSpeechSynthesizerStrings:@[] phoneticType:testSpeechCapabilities];
+
+ expect(testAudioData.audioFileData).to(haveCount(1));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+
+ expect(testAudioData.audioData).to(haveCount(1));
+ expect(testAudioData.audioData[0].text).to(equal(testAudioFile1.name));
+ expect(testAudioData.audioData[0].type).to(equal(SDLSpeechCapabilitiesFile));
+ });
+
+ it(@"Should not append additional phonetic speech synthesizer strings with an invalid phonetic type", ^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ SDLSpeechCapabilities testSpeechCapabilities = SDLSpeechCapabilitiesFile;
+
+ expectAction(^{ [testAudioData addPhoneticSpeechSynthesizerStrings:@[testSpeechSynthesizerString1] phoneticType:testSpeechCapabilities]; }).to(raiseException().named(@"InvalidTTSSpeechCapabilities"));
+ });
+ });
+ });
+
+ describe(@"Copying audio data", ^{
+ __block SDLAudioData *testAudioData = nil;
+ __block SDLAudioData *copiedTestAudioData = nil;
+ __block NSString *testSpeechSynthesizerString1 = @"testSpeechSynthesizerString1";
+ __block NSString *testSpeechSynthesizerString2 = @"testSpeechSynthesizerString2";
+ __block NSString *testSpeechSynthesizerString3 = @"testSpeechSynthesizerString3";
+
+ beforeEach(^{
+ testAudioData = [[SDLAudioData alloc] initWithAudioFile:testAudioFile1];
+ [testAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString1, testSpeechSynthesizerString2]];
+
+ copiedTestAudioData = [testAudioData copy];
+ });
+
+ it(@"Should copy correctly", ^{
+ expect(testAudioData == copiedTestAudioData).to(beFalse());
+ expect(testAudioData.audioData).to(equal(copiedTestAudioData.audioData));
+ expect(testAudioData.audioFileData).to(equal(copiedTestAudioData.audioFileData));
+ });
+
+ it(@"Should not update the copy if changes are made to the original", ^{
+ [testAudioData addSpeechSynthesizerStrings:@[testSpeechSynthesizerString3]];
+ [testAudioData addAudioFiles:@[testAudioFile2]];
+
+ expect(testAudioData.audioData).to(haveCount(5));
+ expect(testAudioData.audioData[0].text).to(contain(testAudioFile1.name));
+ expect(testAudioData.audioData[1].text).to(contain(testSpeechSynthesizerString1));
+ expect(testAudioData.audioData[2].text).to(contain(testSpeechSynthesizerString2));
+ expect(testAudioData.audioData[3].text).to(contain(testSpeechSynthesizerString3));
+ expect(testAudioData.audioData[4].text).to(contain(testAudioFile2.name));
+
+ expect(testAudioData.audioFileData).to(haveCount(2));
+ expect(testAudioData.audioFileData[testAudioFile1.name]).to(equal(testAudioFile1));
+ expect(testAudioData.audioFileData[testAudioFile2.name]).to(equal(testAudioFile2));
+
+ expect(copiedTestAudioData.audioData).to(haveCount(3));
+ expect(copiedTestAudioData.audioFileData).to(haveCount(1));
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m b/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m
new file mode 100644
index 000000000..e9395e86c
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m
@@ -0,0 +1,1147 @@
+//
+// SDLPresentAlertOperationSpec.m
+// SmartDeviceLinkTests
+//
+// Created by Nicole on 11/18/20.
+// Copyright © 2020 smartdevicelink. All rights reserved.
+//
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLAlert.h"
+#import "SDLAlertResponse.h"
+#import "SDLAlertView.h"
+#import "SDLAlertAudioData.h"
+#import "SDLCancelInteraction.h"
+#import "SDLCancelInteractionResponse.h"
+#import "SDLError.h"
+#import "SDLFileManager.h"
+#import "SDLFunctionID.h"
+#import "SDLGlobals.h"
+#import "SDLImage.h"
+#import "SDLPresentAlertOperation.h"
+#import "SDLPutFile.h"
+#import "SDLWindowCapability.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+#import "SDLSystemCapabilityManager.h"
+#import "SDLTTSChunk.h"
+#import "SDLVersion.h"
+#import "SDLWindowCapability.h"
+#import "SDLWindowCapability+ScreenManagerExtensions.h"
+#import "TestConnectionManager.h"
+
+@interface SDLPresentAlertOperation()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (strong, nonatomic, readwrite) SDLAlertView *alertView;
+@property (assign, nonatomic) UInt16 cancelId;
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+- (nullable NSError *)sdl_isValidAlertViewData:(SDLAlertView *)alertView;
+- (SDLAlert *)alertRPC;
+
+@end
+
+QuickSpecBegin(SDLPresentAlertOperationSpec)
+
+describe(@"SDLPresentAlertOperation", ^{
+ __block SDLPresentAlertOperation *testPresentAlertOperation = nil;
+ __block id mockConnectionManager = nil;
+ __block id mockFileManager = nil;
+ __block id mockSystemCapabilityManager = nil;
+ __block id mockCurrentWindowCapability = nil;
+ __block SDLAlertView *testAlertView = nil;
+ __block UInt16 testCancelID = 45;
+ __block BOOL hasCalledOperationCompletionHandler = NO;
+
+ __block SDLAlertAudioData *testAlertAudioData = nil;
+ __block SDLFile *testAudioFile = nil;
+ __block SDLAlertAudioData *testAlertAudioFileData = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton1 = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton2 = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton3 = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton4 = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton5 = nil;
+ __block SDLSoftButtonObject *testAlertSoftButton6 = nil;
+ __block SDLArtwork *testAlertIcon = nil;
+ __block SDLArtwork *testButton1Icon = nil;
+ __block SDLArtwork *testButton2Icon = nil;
+
+ __block SDLVersion *alertAudioFileSupportedSpecVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0];
+ __block SDLVersion *alertAudioFileNotSupportedSpecVersion = [SDLVersion versionWithMajor:4 minor:8 patch:0];
+
+ beforeEach(^{
+ mockConnectionManager = OCMProtocolMock(@protocol(SDLConnectionManagerType));
+ mockFileManager = OCMClassMock([SDLFileManager class]);
+ mockSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]);
+ mockCurrentWindowCapability = OCMClassMock([SDLWindowCapability class]);
+
+ testAlertAudioData = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"test synthesizer string"];
+ NSBundle *testBundle = [NSBundle bundleForClass:[self class]];
+ NSURL *testAudioFileURL = [testBundle URLForResource:@"testAudio" withExtension:@"mp3"];
+ NSString *testAudioFileName = @"testAudioFile";
+ testAudioFile = [[SDLFile alloc] initWithFileURL:testAudioFileURL name:testAudioFileName persistent:YES];
+ testAlertAudioFileData = [[SDLAlertAudioData alloc] initWithAudioFile:testAudioFile];
+
+ UIImage *testButton1Image = [[UIImage alloc] initWithContentsOfFile:[testBundle pathForResource:@"testImageJPEG" ofType:@"jpeg"]];
+ testButton1Icon = [SDLArtwork artworkWithImage:testButton1Image asImageFormat:SDLArtworkImageFormatJPG];
+ UIImage *testButton2Image = [[UIImage alloc] initWithContentsOfFile:[testBundle pathForResource:@"testImagePNG" ofType:@"png"]];
+ testButton2Icon = [SDLArtwork artworkWithImage:testButton2Image asImageFormat:SDLArtworkImageFormatPNG];
+
+ testAlertSoftButton1 = [[SDLSoftButtonObject alloc] initWithName:@"button1" text:@"button1" artwork:testButton1Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testAlertSoftButton2 = [[SDLSoftButtonObject alloc] initWithName:@"button2" text:@"button2" artwork:testButton2Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testAlertSoftButton3 = [[SDLSoftButtonObject alloc] initWithName:@"button3" text:@"button3" artwork:testButton2Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testAlertSoftButton4 = [[SDLSoftButtonObject alloc] initWithName:@"button4" text:@"button4" artwork:testButton2Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testAlertSoftButton5 = [[SDLSoftButtonObject alloc] initWithName:@"button5" text:@"button5" artwork:testButton2Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+ testAlertSoftButton6 = [[SDLSoftButtonObject alloc] initWithName:@"button6" text:@"button6" artwork:testButton2Icon handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ UIImage *testImage = [[UIImage alloc] initWithContentsOfFile:[testBundle pathForResource:@"testImageJPEG" ofType:@"jpeg"]];
+ testAlertIcon = [SDLArtwork artworkWithImage:testImage asImageFormat:SDLArtworkImageFormatPNG];
+
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ });
+
+ it(@"should be initialized correctly", ^{
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ expect(@(testPresentAlertOperation.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
+ expect(testPresentAlertOperation.connectionManager).to(equal(mockConnectionManager));
+ expect(testPresentAlertOperation.fileManager).to(equal(mockFileManager));
+ expect(testPresentAlertOperation.alertView).toNot(equal(testAlertView));
+ expect(@(testPresentAlertOperation.cancelId)).to(equal(@(testCancelID)));
+ expect(testPresentAlertOperation.currentWindowCapability).to(equal(mockCurrentWindowCapability));
+ expect(testPresentAlertOperation.internalError).to(beNil());
+ });
+
+ describe(@"creating the alert", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0];
+ });
+
+ describe(@"setting the text fields", ^{
+ describe(@"with all three text fields set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set all textfields if all textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(equal(testAlertView.secondaryText));
+ expect(testAlert.alertText3).to(equal(testAlertView.tertiaryText));
+ });
+
+ it(@"should set textfields correctly if only two textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(equal([NSString stringWithFormat:@"%@ - %@", testAlertView.secondaryText, testAlertView.tertiaryText]));
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only one textfield is supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(1);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal([NSString stringWithFormat:@"%@ - %@ - %@", testAlertView.text, testAlertView.secondaryText, testAlertView.tertiaryText]));
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+ });
+
+ describe(@"with two text fields set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:nil timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set all textfields if all textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(equal(testAlertView.secondaryText));
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only two textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(equal(testAlertView.secondaryText));
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only one textfield is supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(1);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal([NSString stringWithFormat:@"%@ - %@", testAlertView.text, testAlertView.secondaryText]));
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+ });
+
+ describe(@"with one text field set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:nil tertiaryText:nil timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set all textfields if all textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only two textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only one textfield is supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(1);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+ });
+
+ describe(@"with no text fields set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:nil secondaryText:nil tertiaryText:nil timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set all textfields if all textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(beNil());
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only two textfields are supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(beNil());
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+
+ it(@"should set textfields correctly if only one textfield is supported", ^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(1);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(beNil());
+ expect(testAlert.alertText2).to(beNil());
+ expect(testAlert.alertText3).to(beNil());
+ });
+ });
+
+ describe(@"with a nil currentWindowCapability", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:nil alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should assume all textfields are supported", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertText1).to(equal(testAlertView.text));
+ expect(testAlert.alertText2).to(equal(testAlertView.secondaryText));
+ expect(testAlert.alertText3).to(equal(testAlertView.tertiaryText));
+ });
+ });
+ });
+
+ describe(@"setting the audio data", ^{
+ context(@"only audio prompts set", ^{
+ beforeEach(^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+
+ SDLAlertAudioData *audioData = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"test synthesizer string"];
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:audioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set the tts chunks correctly", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks.count).to(equal(1));
+ expect(testAlert.ttsChunks[0].text).to(equal(testAlertView.audio.audioData.firstObject.text));
+ });
+ });
+
+ context(@"only audio file data set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioFileData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ context(@"the negotiated RPC spec version does not support the audio file feature", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileNotSupportedSpecVersion;
+ });
+
+ it(@"should set the `ttsChunks` to nil (and not an empty array) if only an audio file was set", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks).to(beNil());
+ });
+ });
+
+ context(@"the negotiated RPC spec version supports the audio file feature", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileSupportedSpecVersion;
+ });
+
+ context(@"the module does not support the speech capability of type `file`", ^{
+ beforeEach(^{
+ OCMStub([mockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesSilence]));
+ });
+
+ it(@"should set the `ttsChunks` to nil (and not an empty array) if only an audio file was set", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks).to(beNil());
+ });
+ });
+
+ context(@"the module supports the speech capability of type `file`", ^{
+ beforeEach(^{
+ OCMStub([mockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesFile, SDLSpeechCapabilitiesText]));
+ });
+
+ it(@"should set the tts chunks correctly", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks.count).to(equal(1));
+ expect(testAlert.ttsChunks[0].text).to(equal(testAlertView.audio.audioData.firstObject.text));
+ });
+ });
+ });
+ });
+
+ context(@"both audio prompts and audio file data set", ^{
+ beforeEach(^{
+ SDLAlertAudioData *audioData = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"test synthesizer string"];
+ [audioData addAudioFiles:@[testAudioFile]];
+
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:audioData buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set the tts chunks correctly", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks.count).to(equal(2));
+ expect(testAlert.ttsChunks[0].text).to(equal(testAlertView.audio.audioData[0].text));
+ expect(testAlert.ttsChunks[1].text).to(equal(testAlertView.audio.audioData[1].text));
+ });
+ });
+
+ context(@"no audio data set", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:nil buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set the `ttsChunks` to nil (and not an empty array)", ^{
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.ttsChunks).to(beNil());
+ });
+ });
+ });
+
+ describe(@"setting the icon", ^{
+ beforeEach(^{
+ testAlertView = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:nil buttons:@[testAlertSoftButton1, testAlertSoftButton2] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ });
+
+ it(@"should set the image if icons are supported on the module", ^{
+ OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertIcon.value).to(equal(testAlertView.icon.name));
+ });
+
+ it(@"should not set the image if icons are not supported on the module", ^{
+ OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(NO);
+ SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
+ expect(testAlert.alertIcon).to(beNil());
+ });
+ });
+ });
+
+ describe(@"uploading", ^{
+ __block id strictMockFileManager = nil;
+ __block id strictMockSystemCapabilityManager = nil;
+ __block id strictMockCurrentWindowCapability = nil;
+
+ beforeEach(^{
+ strictMockFileManager = OCMStrictClassMock([SDLFileManager class]);
+ strictMockSystemCapabilityManager = OCMStrictClassMock([SDLSystemCapabilityManager class]);
+ strictMockCurrentWindowCapability = OCMStrictClassMock([SDLWindowCapability class]);
+ });
+
+ describe(@"audio files", ^{
+ beforeEach(^{
+ testAlertView.audio = testAlertAudioFileData;
+ testAlertView.softButtons = @[];
+ testAlertView.icon = nil;
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ OCMStub([strictMockFileManager fileNeedsUpload:nil]).andReturn(NO);
+ OCMStub([strictMockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ });
+
+ context(@"the negotiated RPC spec version does not support the audio file feature", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileNotSupportedSpecVersion;
+ OCMStub([strictMockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesFile, SDLSpeechCapabilitiesText]));
+ });
+
+ it(@"should not attempt to upload audio files", ^{
+ OCMReject([strictMockFileManager fileNeedsUpload:testAudioFile]);
+ OCMReject([strictMockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+ });
+
+ context(@"the negotiated RPC spec version supports the audio file feature", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileSupportedSpecVersion;
+ });
+
+ context(@"the module does not support the speech capability of type `file`", ^{
+ beforeEach(^{
+ OCMStub([strictMockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesText]));
+ });
+
+ it(@"should not attempt to upload audio files", ^{
+ OCMReject([strictMockFileManager fileNeedsUpload:testAudioFile]);
+ OCMReject([strictMockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+ });
+
+ context(@"the module supports the speech capability of type `file`", ^{
+ beforeEach(^{
+ OCMStub([strictMockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesText, SDLSpeechCapabilitiesFile]));
+ });
+
+ it(@"should not upload the audio file if it has already been uploaded", ^{
+ OCMExpect([strictMockFileManager fileNeedsUpload:testAudioFile]).andReturn(NO);
+ OCMReject([strictMockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should upload the audio file if it has not yet been uploaded", ^{
+ OCMExpect([strictMockFileManager fileNeedsUpload:testAudioFile]).andReturn(YES);
+ OCMExpect([strictMockFileManager uploadFiles:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLPutFile *> *files = (NSArray<SDLPutFile *> *)value;
+ expect(files.count).to(equal(1));
+ expect(files.firstObject.name).to(equal(testAlertAudioFileData.audioData.firstObject.text));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should re-upload an audio file if `overwrite` has been set to true", ^{
+ testAudioFile.overwrite = YES;
+ OCMStub([strictMockFileManager fileNeedsUpload:testAudioFile]).andReturn(YES);;
+ OCMExpect([strictMockFileManager uploadFiles:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLPutFile *> *files = (NSArray<SDLPutFile *> *)value;
+ expect(files.count).to(equal(1));
+ expect(files.firstObject.name).to(equal(testAlertAudioFileData.audioData.firstObject.text));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+ });
+ });
+ });
+
+ describe(@"image files", ^{
+ beforeEach(^{
+ OCMStub([strictMockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(2);
+ OCMStub([strictMockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesFile, SDLSpeechCapabilitiesText]));
+ });
+
+ it(@"should upload the alert icons and soft button images if they are supported on the module", ^{
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @YES;
+ OCMStub([strictMockCurrentWindowCapability softButtonCapabilities]).andReturn((@[testSoftButtonCapabilities]));
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+
+ OCMExpect([strictMockFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLArtwork *> *files = (NSArray<SDLArtwork *> *)value;
+ expect(files.count).to(equal(3));
+ expect(files[0].name).to(equal(testAlertView.icon.name));
+ expect(files[1].name).to(equal(testAlertView.softButtons[0].currentState.artwork.name));
+ expect(files[2].name).to(equal(testAlertView.softButtons[1].currentState.artwork.name));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should only upload a max of 4 soft button images if soft button images supported on the module", ^{
+ SDLAlertView *testAlertViewWithExtraSoftButtons = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2, testAlertSoftButton3, testAlertSoftButton4, testAlertSoftButton5, testAlertSoftButton6] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertViewWithExtraSoftButtons cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @YES;
+ OCMStub([strictMockCurrentWindowCapability softButtonCapabilities]).andReturn((@[testSoftButtonCapabilities]));
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+
+ OCMExpect([strictMockFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLArtwork *> *files = (NSArray<SDLArtwork *> *)value;
+ expect(files).to(haveCount(5));
+ expect(files[0].name).to(equal(testAlertViewWithExtraSoftButtons.icon.name));
+ expect(files[1].name).to(equal(testAlertViewWithExtraSoftButtons.softButtons[0].currentState.artwork.name));
+ expect(files[2].name).to(equal(testAlertViewWithExtraSoftButtons.softButtons[1].currentState.artwork.name));
+ expect(files[3].name).to(equal(testAlertViewWithExtraSoftButtons.softButtons[2].currentState.artwork.name));
+ expect(files[4].name).to(equal(testAlertViewWithExtraSoftButtons.softButtons[3].currentState.artwork.name));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should not upload the soft button images if soft button images are not supported on the module", ^{
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @(NO);
+ OCMStub([strictMockCurrentWindowCapability softButtonCapabilities]).andReturn(@[testSoftButtonCapabilities]);
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+
+ OCMExpect([strictMockFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLArtwork *> *files = (NSArray<SDLArtwork *> *)value;
+ expect(files.count).to(equal(1));
+ expect(files[0].name).to(equal(testAlertView.icon.name));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should upload the alert icon if the alert icon is not supported on the module", ^{
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(NO);
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @(YES);
+ OCMStub([strictMockCurrentWindowCapability softButtonCapabilities]).andReturn((@[testSoftButtonCapabilities]));
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+
+ OCMExpect([strictMockFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id value) {
+ NSArray<SDLArtwork *> *files = (NSArray<SDLArtwork *> *)value;
+ expect(files.count).to(equal(2));
+ expect(files[0].name).to(equal(testAlertView.softButtons[0].currentState.artwork.name));
+ expect(files[1].name).to(equal(testAlertView.softButtons[1].currentState.artwork.name));
+ return [value isKindOfClass:[NSArray class]];
+ }] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+
+ it(@"should not upload any images if the alert icon and soft button graphics are not supported on the module", ^{
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(NO);
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @NO;
+ OCMStub([strictMockCurrentWindowCapability softButtonCapabilities]).andReturn(@[testSoftButtonCapabilities]);
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+
+ OCMReject([strictMockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ });
+ });
+ });
+
+ describe(@"presenting the alert", ^{
+ describe(@"checking if alert data is valid", ^{
+ context(@"the module does not support audio data uploads", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileSupportedSpecVersion;
+ });
+
+ it(@"should be valid if at least the first text field was set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.text = @"test text";
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ NSError *testAlertValidError = [testPresentAlertOperation sdl_isValidAlertViewData:testAlertView];
+ expect(testAlertValidError).to(beNil());
+ });
+
+ it(@"should be valid if at least the second text field was set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.secondaryText = @"test text";
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ NSError *testAlertValidError = [testPresentAlertOperation sdl_isValidAlertViewData:testAlertView];
+ expect(testAlertValidError).to(beNil());
+ });
+
+ it(@"should be valid if at least the audio data was set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.audio = [[SDLAlertAudioData alloc] initWithSpeechSynthesizerString:@"test audio"];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ NSError *testAlertValidError = [testPresentAlertOperation sdl_isValidAlertViewData:testAlertView];
+ expect(testAlertValidError).to(beNil());
+ });
+
+ it(@"should be invalid if the first two text fields or the audio data was not set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.tertiaryText = @"test text";
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ NSError *testAlertValidError = [testPresentAlertOperation sdl_isValidAlertViewData:testAlertView];
+ expect(testAlertValidError).to(equal([NSError sdl_alertManager_alertDataInvalid]));
+ });
+ });
+
+ context(@"the module does not support audio data uploads", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileNotSupportedSpecVersion;
+ });
+
+ it(@"should be invalid if only audio data was set but audio data is not supported on the module", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.audio = [[SDLAlertAudioData alloc] initWithAudioFile:testAudioFile];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ NSError *testAlertValidError = [testPresentAlertOperation sdl_isValidAlertViewData:testAlertView];
+ expect(testAlertValidError).to(equal([NSError sdl_alertManager_alertAudioFileNotSupported]));
+ });
+ });
+ });
+
+ context(@"with invalid data", ^{
+ context(@"the module supports audio data uploads", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileSupportedSpecVersion;
+ });
+
+ it(@"should return an error if invalid data was set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.tertiaryText = @"test text";
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError).to(equal([NSError sdl_alertManager_alertDataInvalid]));
+ expect(hasCalledOperationCompletionHandler).to(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+ });
+ });
+
+ context(@"the module does not support audio data uploads", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = alertAudioFileNotSupportedSpecVersion;
+ });
+
+ it(@"should return an error if valid audio data was set but the module does not support audio files", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.audio = [[SDLAlertAudioData alloc] initWithAudioFile:testAudioFile];
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError).to(equal([NSError sdl_alertManager_alertAudioFileNotSupported]));
+ expect(hasCalledOperationCompletionHandler).to(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+ });
+
+ it(@"should return an error if invalid data was set", ^{
+ testAlertView = [[SDLAlertView alloc] init];
+ testAlertView.tertiaryText = @"test text";
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError).to(equal([NSError sdl_alertManager_alertDataInvalid]));
+ expect(hasCalledOperationCompletionHandler).to(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+ });
+ });
+ });
+
+ context(@"with too many soft buttons", ^{
+ __block SDLAlertView *testAlertViewWithExtraSoftButtons = nil;
+
+ beforeEach(^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0];
+ OCMStub([mockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesText, SDLSpeechCapabilitiesFile]));;
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @YES;
+ OCMStub([mockCurrentWindowCapability softButtonCapabilities]).andReturn(@[testSoftButtonCapabilities]);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO);
+ OCMStub([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+ OCMStub([mockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ SDLVersion *supportedVersion = [SDLVersion versionWithMajor:6 minor:3 patch:0];
+ id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
+ OCMStub([globalMock rpcVersion]).andReturn(supportedVersion);
+
+ testAlertViewWithExtraSoftButtons = [[SDLAlertView alloc] initWithText:@"text" secondaryText:@"secondaryText" tertiaryText:@"tertiaryText" timeout:@(4) showWaitIndicator:@(YES) audioIndication:testAlertAudioData buttons:@[testAlertSoftButton1, testAlertSoftButton2, testAlertSoftButton3, testAlertSoftButton4, testAlertSoftButton5, testAlertSoftButton6] icon:testAlertIcon];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertViewWithExtraSoftButtons cancelID:testCancelID];
+
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+ });
+
+ it(@"should send the alert but only allow 4 soft buttons to be sent", ^{
+ OCMExpect([mockConnectionManager sendConnectionRequest:[OCMArg checkWithBlock:^BOOL(id value) {
+ SDLAlert *alertRequest = (SDLAlert *)value;
+ expect(alertRequest.alertText1).to(equal(testAlertView.text));
+ expect(alertRequest.alertText2).to(equal(testAlertView.secondaryText));
+ expect(alertRequest.alertText3).to(equal(testAlertView.tertiaryText));
+ expect(alertRequest.ttsChunks.count).to(equal(1));
+ expect(alertRequest.ttsChunks[0].text).to(equal(testAlertView.audio.audioData.firstObject.text));
+ expect(alertRequest.duration).to(equal(testAlertView.timeout * 1000));
+ expect(alertRequest.playTone).to(equal(testAlertView.audio.playTone));
+ expect(alertRequest.progressIndicator).to(equal(testAlertView.showWaitIndicator));
+ expect(alertRequest.softButtons.count).to(equal(4));
+ expect(alertRequest.softButtons[0].text).to(equal(testAlertViewWithExtraSoftButtons.softButtons[0].currentState.text));
+ expect(alertRequest.softButtons[1].text).to(equal(testAlertViewWithExtraSoftButtons.softButtons[1].currentState.text));
+ expect(alertRequest.softButtons[2].text).to(equal(testAlertViewWithExtraSoftButtons.softButtons[2].currentState.text));
+ expect(alertRequest.softButtons[3].text).to(equal(testAlertViewWithExtraSoftButtons.softButtons[3].currentState.text));
+ expect(alertRequest.softButtons[0].softButtonID).to(equal(10));
+ expect(alertRequest.softButtons[1].softButtonID).to(equal(11));
+ expect(alertRequest.softButtons[2].softButtonID).to(equal(12));
+ expect(alertRequest.softButtons[3].softButtonID).to(equal(13));
+
+ expect(alertRequest.cancelID).to(equal(testCancelID));
+ expect(alertRequest.alertIcon.value).to(equal(testAlertView.icon.name));
+ return [value isKindOfClass:[SDLAlert class]];
+ }] withResponseHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(mockConnectionManager, 1.0);
+ });
+ });
+
+ context(@"with a failed alert icon upload", ^{
+ beforeEach(^{
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0];
+ OCMStub([mockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesText, SDLSpeechCapabilitiesFile]));;
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @YES;
+ OCMStub([mockCurrentWindowCapability softButtonCapabilities]).andReturn(@[testSoftButtonCapabilities]);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(YES);
+ OCMStub([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:([OCMArg invokeBlockWithArgs: @[testAlertView.icon.name], [NSNull null], nil])]);
+ OCMStub([mockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ SDLVersion *supportedVersion = [SDLVersion versionWithMajor:6 minor:3 patch:0];
+ id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
+ OCMStub([globalMock rpcVersion]).andReturn(supportedVersion);
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+ });
+
+ it(@"should send the alert but not set the SDLImage for icon", ^{
+ OCMExpect([mockConnectionManager sendConnectionRequest:[OCMArg checkWithBlock:^BOOL(id value) {
+ SDLAlert *alertRequest = (SDLAlert *)value;
+ expect(alertRequest.alertText1).to(equal(testAlertView.text));
+ expect(alertRequest.alertText2).to(equal(testAlertView.secondaryText));
+ expect(alertRequest.alertText3).to(equal(testAlertView.tertiaryText));
+ expect(alertRequest.ttsChunks.count).to(equal(1));
+ expect(alertRequest.ttsChunks[0].text).to(equal(testAlertView.audio.audioData.firstObject.text));
+ expect(alertRequest.duration).to(equal(testAlertView.timeout * 1000));
+ expect(alertRequest.playTone).to(equal(testAlertView.audio.playTone));
+ expect(alertRequest.progressIndicator).to(equal(testAlertView.showWaitIndicator));
+ expect(alertRequest.softButtons.count).to(equal(2));
+ expect(alertRequest.softButtons[0].text).to(equal(testAlertView.softButtons[0].currentState.text));
+ expect(alertRequest.softButtons[1].text).to(equal(testAlertView.softButtons[1].currentState.text));
+ expect(alertRequest.softButtons[0].softButtonID).to(equal(10));
+ expect(alertRequest.softButtons[1].softButtonID).to(equal(11));
+ expect(alertRequest.cancelID).to(equal(testCancelID));
+ expect(alertRequest.alertIcon).to(beNil());
+ return [value isKindOfClass:[SDLAlert class]];
+ }] withResponseHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(mockConnectionManager, 0.5);
+ });
+ });
+
+ context(@"with valid data", ^{
+ __block id strictMockConnectionManager = nil;
+
+ beforeEach(^{
+ strictMockConnectionManager = OCMStrictProtocolMock(@protocol(SDLConnectionManagerType));
+
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+ OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ [SDLGlobals sharedGlobals].rpcVersion = [SDLVersion versionWithMajor:5 minor:0 patch:0];
+ OCMStub([mockSystemCapabilityManager speechCapabilities]).andReturn((@[SDLSpeechCapabilitiesText, SDLSpeechCapabilitiesFile]));;
+ SDLSoftButtonCapabilities *testSoftButtonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ testSoftButtonCapabilities.imageSupported = @YES;
+ OCMStub([mockCurrentWindowCapability softButtonCapabilities]).andReturn(@[testSoftButtonCapabilities]);
+ OCMStub([mockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO);
+ OCMStub([mockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+ OCMStub([mockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+
+ SDLVersion *supportedVersion = [SDLVersion versionWithMajor:6 minor:3 patch:0];
+ id globalMock = OCMPartialMock([SDLGlobals sharedGlobals]);
+ OCMStub([globalMock rpcVersion]).andReturn(supportedVersion);
+
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:strictMockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testAlertView cancelID:testCancelID];
+
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+ });
+
+ it(@"should send the alert if the operation has not been cancelled", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg checkWithBlock:^BOOL(id value) {
+ SDLAlert *alertRequest = (SDLAlert *)value;
+ expect(alertRequest.alertText1).to(equal(testAlertView.text));
+ expect(alertRequest.alertText2).to(equal(testAlertView.secondaryText));
+ expect(alertRequest.alertText3).to(equal(testAlertView.tertiaryText));
+ expect(alertRequest.ttsChunks.count).to(equal(1));
+ expect(alertRequest.ttsChunks[0].text).to(equal(testAlertView.audio.audioData.firstObject.text));
+ expect(alertRequest.duration).to(equal(testAlertView.timeout * 1000));
+ expect(alertRequest.playTone).to(equal(testAlertView.audio.playTone));
+ expect(alertRequest.progressIndicator).to(equal(testAlertView.showWaitIndicator));
+ expect(alertRequest.softButtons.count).to(equal(2));
+ expect(alertRequest.softButtons[0].text).to(equal(testAlertView.softButtons[0].currentState.text));
+ expect(alertRequest.softButtons[1].text).to(equal(testAlertView.softButtons[1].currentState.text));
+ expect(alertRequest.softButtons[0].softButtonID).to(equal(10));
+ expect(alertRequest.softButtons[1].softButtonID).to(equal(11));
+ expect(alertRequest.cancelID).to(equal(testCancelID));
+ expect(alertRequest.alertIcon.value).to(equal(testAlertView.icon.name));
+ return [value isKindOfClass:[SDLAlert class]];
+ }] withResponseHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ it(@"should not send the alert if the operation has been cancelled", ^{
+ [testPresentAlertOperation cancel];
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError).to(beNil());
+ expect(hasCalledOperationCompletionHandler).to(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ describe(@"Getting a response from the module", ^{
+ __block SDLAlertResponse *response = nil;
+
+ it(@"should call the completion handler and finish the operation after a successful alert response", ^{
+ response = [[SDLAlertResponse alloc] init];
+ response.tryAgainTime = nil;
+ response.success = @YES;
+ response.resultCode = SDLResultSuccess;
+
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], response, [NSNull null], nil])]);
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError).toEventually(beNil());
+ expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ it(@"should save the error, call the completion handler and finish the operation after an unsuccessful alert response", ^{
+ response = [[SDLAlertResponse alloc] init];
+ response.tryAgainTime = @5;
+ response.success = @NO;
+ response.resultCode = SDLResultAborted;
+ NSError *defaultError = [NSError errorWithDomain:@"com.sdl.testConnectionManager" code:-1 userInfo:nil];
+
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], response, defaultError, nil])]);
+
+ [testPresentAlertOperation start];
+
+ expect(testPresentAlertOperation.internalError.userInfo[@"tryAgainTime"]).toEventually(equal(response.tryAgainTime));
+ expect(testPresentAlertOperation.internalError.userInfo[@"error"]).toEventually(equal(defaultError));
+ expect(hasCalledOperationCompletionHandler).toEventually(beTrue());
+ expect(testPresentAlertOperation.isFinished).toEventually(beTrue());
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+ });
+ });
+ });
+
+ describe(@"canceling the alert", ^{
+ __block SDLAlertView *testCancelAlertView = nil;
+ __block SDLVersion *cancelInteractionSupportedSpecVersion = [SDLVersion versionWithMajor:6 minor:0 patch:0];
+ __block SDLVersion *cancelInteractionNotSupportedSpecVersion = [SDLVersion versionWithMajor:5 minor:10 patch:0];
+ __block id strictMockConnectionManager = nil;
+
+ beforeEach(^{
+ testCancelAlertView = [[SDLAlertView alloc] init];
+ testCancelAlertView.text = @"Alert view to be canceled";
+
+ OCMStub([mockCurrentWindowCapability maxNumberOfAlertFieldLines]).andReturn(3);
+
+ strictMockConnectionManager = OCMStrictProtocolMock(@protocol(SDLConnectionManagerType));
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:strictMockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager currentWindowCapability:mockCurrentWindowCapability alertView:testCancelAlertView cancelID:testCancelID];
+ testPresentAlertOperation.completionBlock = ^{
+ hasCalledOperationCompletionHandler = YES;
+ };
+ });
+
+ context(@"Module supports the CancelInteration RPC", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = cancelInteractionSupportedSpecVersion;
+ });
+
+ describe(@"If the operation is executing", ^{
+ it(@"should attempt to send a cancel interaction", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg checkWithBlock:^BOOL(id value) {
+ return [value isKindOfClass:[SDLAlert class]];
+ }] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ expect(testPresentAlertOperation.isExecuting).to(beTrue());
+ expect(testPresentAlertOperation.isFinished).to(beFalse());
+ expect(testPresentAlertOperation.isCancelled).to(beFalse());
+
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg checkWithBlock:^BOOL(id value) {
+ SDLCancelInteraction *cancelRequest = (SDLCancelInteraction *)value;
+ expect(cancelRequest).to(beAnInstanceOf([SDLCancelInteraction class]));
+ expect(cancelRequest.cancelID).to(equal(testCancelID));
+ expect(cancelRequest.functionID).to(equal([SDLFunctionID.sharedInstance functionIdForName:SDLRPCFunctionNameAlert]));
+ return [value isKindOfClass:[SDLCancelInteraction class]];
+ }] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ context(@"If the cancel interaction was successful", ^{
+ it(@"should not save an error", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+
+ SDLCancelInteractionResponse *testResponse = [[SDLCancelInteractionResponse alloc] init];
+ testResponse.success = @YES;
+ testResponse.resultCode = SDLResultSuccess;
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], testResponse, [NSNull null], nil])]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 1.0);
+ expect(testPresentAlertOperation.error).to(beNil());
+ });
+ });
+
+ context(@"If the cancel interaction was not successful", ^{
+ it(@"should save an error", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 1.0);
+
+ SDLCancelInteractionResponse *testResponse = [[SDLCancelInteractionResponse alloc] init];
+ testResponse.success = @NO;
+ testResponse.resultCode = SDLResultAborted;
+ NSError *defaultError = [NSError errorWithDomain:@"com.sdl.testConnectionManager" code:-1 userInfo:nil];
+
+ OCMStub([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:([OCMArg invokeBlockWithArgs:[OCMArg any], testResponse, defaultError, nil])]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 1.0);
+ expect(testPresentAlertOperation.error).to(equal(defaultError));
+ });
+ });
+ });
+
+ describe(@"If the operation has already finished", ^{
+ beforeEach(^{
+ [testPresentAlertOperation finishOperation];
+ });
+
+ it(@"should not attempt to send a cancel interaction", ^{
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+ });
+
+ describe(@"If the started operation has been canceled", ^{
+ it(@"should not attempt to send a cancel interaction", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+
+ [testPresentAlertOperation cancel];
+
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+ });
+
+ context(@"If the operation has not started", ^{
+ it(@"should not attempt to send a cancel interaction", ^{
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ context(@"Once the operation has started after being canceled", ^{
+ it(@"should not attempt to send a cancel interaction", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+
+ [testPresentAlertOperation cancel];
+
+ OCMReject([mockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+ [testCancelAlertView cancel];
+
+ OCMReject([mockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+ });
+ });
+ });
+
+ context(@"Module does not support the CancelInteration RPC", ^{
+ beforeEach(^{
+ [SDLGlobals sharedGlobals].rpcVersion = cancelInteractionNotSupportedSpecVersion;
+ });
+
+ it(@"should not attempt to send a cancel interaction if the operation is executing", ^{
+ OCMExpect([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLAlert.class] withResponseHandler:[OCMArg any]]);
+ [testPresentAlertOperation start];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+
+ it(@"should cancel the operation if it has not yet been run", ^{
+ OCMReject([strictMockConnectionManager sendConnectionRequest:[OCMArg isKindOfClass:SDLCancelInteraction.class] withResponseHandler:[OCMArg any]]);
+
+ [testCancelAlertView cancel];
+
+ expect(testPresentAlertOperation.isCancelled).to(beTrue());
+
+ OCMVerifyAllWithDelay(strictMockConnectionManager, 0.5);
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLScreenManagerSpec.m b/SmartDeviceLinkTests/SDLScreenManagerSpec.m
index 9f137a5b2..5982109ce 100644
--- a/SmartDeviceLinkTests/SDLScreenManagerSpec.m
+++ b/SmartDeviceLinkTests/SDLScreenManagerSpec.m
@@ -2,11 +2,13 @@
#import <Nimble/Nimble.h>
#import <OCMock/OCMock.h>
+#import "SDLAlertManager.h"
#import "SDLFileManager.h"
#import "SDLHMILevel.h"
#import "SDLGlobals.h"
#import "SDLMenuCell.h"
#import "SDLMenuManager.h"
+#import "SDLPermissionManager.h"
#import "SDLScreenManager.h"
#import "SDLShow.h"
#import "SDLSoftButtonManager.h"
@@ -18,6 +20,16 @@
#import "SDLVersion.h"
#import "TestConnectionManager.h"
+@interface SDLAlertManager()
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (weak, nonatomic) SDLSystemCapabilityManager *systemCapabilityManager;
+@property (weak, nonatomic, nullable) SDLPermissionManager *permissionManager;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
+
+@end
+
@interface SDLSoftButtonManager()
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@@ -41,6 +53,7 @@
@property (strong, nonatomic) SDLTextAndGraphicManager *textAndGraphicManager;
@property (strong, nonatomic) SDLSoftButtonManager *softButtonManager;
@property (strong, nonatomic) SDLMenuManager *menuManager;
+@property (strong, nonatomic) SDLAlertManager *alertManager;
@end
@@ -50,6 +63,7 @@ describe(@"screen manager", ^{
__block TestConnectionManager *mockConnectionManager = nil;
__block SDLFileManager *mockFileManager = nil;
__block SDLSystemCapabilityManager *mockSystemCapabilityManager = nil;
+ __block SDLPermissionManager *mockPermissionManager = nil;
__block SDLScreenManager *testScreenManager = nil;
__block NSString *testString1 = @"test1";
@@ -77,8 +91,9 @@ describe(@"screen manager", ^{
mockConnectionManager = [[TestConnectionManager alloc] init];
mockFileManager = OCMClassMock([SDLFileManager class]);
mockSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]);
+ mockPermissionManager = OCMClassMock([SDLPermissionManager class]);
- testScreenManager = [[SDLScreenManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager];
+ testScreenManager = [[SDLScreenManager alloc] initWithConnectionManager:mockConnectionManager fileManager:mockFileManager systemCapabilityManager:mockSystemCapabilityManager permissionManager:mockPermissionManager];
});
// should set up the sub-managers correctly
@@ -87,6 +102,10 @@ describe(@"screen manager", ^{
expect(testScreenManager.textAndGraphicManager.fileManager).to(equal(mockFileManager));
expect(testScreenManager.softButtonManager.connectionManager).to(equal(mockConnectionManager));
expect(testScreenManager.softButtonManager.fileManager).to(equal(mockFileManager));
+ expect(testScreenManager.alertManager.connectionManager).to(equal(mockConnectionManager));
+ expect(testScreenManager.alertManager.fileManager).to(equal(mockFileManager));
+ expect(testScreenManager.alertManager.systemCapabilityManager).to(equal(mockSystemCapabilityManager));
+ expect(testScreenManager.alertManager.permissionManager).to(equal(mockPermissionManager));
});
// batching updates
@@ -170,6 +189,17 @@ describe(@"screen manager", ^{
expect(testScreenManager.textAndGraphicManager.transactionQueue.operationCount).to(equal(1));
});
});
+
+ // presenting an alert
+ describe(@"presenting an alert", ^{
+ it(@"should pass the call to the alert manager", ^{
+ SDLAlertView *testAlertView = [[SDLAlertView alloc] initWithText:@"Test" buttons:@[[[SDLSoftButtonObject alloc] initWithName:@"Test Button" text:@"Test Button" artwork:nil handler:nil]]];
+
+ [testScreenManager presentAlert:testAlertView withCompletionHandler:nil];
+
+ expect(testScreenManager.alertManager.transactionQueue.operationCount).to(equal(1));
+ });
+ });
});
QuickSpecEnd