summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorjacobrau-livio <114027102+jacobrau-livio@users.noreply.github.com>2022-10-26 07:25:36 -0700
committerGitHub <noreply@github.com>2022-10-26 07:25:36 -0700
commita26dbc28de24ca7ad9fc4258394ebcdf4387bccc (patch)
tree5068069ea214ab14e8e805da9ef1e0860a985cab
parent3869a3028830035c4c1529149f88ba56d0b23153 (diff)
parentb234b1b9c0e677740924a11776f9d07cf711fb3f (diff)
downloadsdl_ios-master.tar.gz
Merge pull request #2111 from smartdevicelink/develop7.6.0master
v7.6.0 Release
-rw-r--r--.github/CONTRIBUTING.md10
-rw-r--r--CHANGELOG.md27
-rw-r--r--Example Apps/Example ObjC/MenuManager.h3
-rw-r--r--Example Apps/Example ObjC/MenuManager.m55
-rw-r--r--Example Apps/Example ObjC/ProxyManager.m28
-rw-r--r--Example Apps/Example ObjC/RemoteControlManager.h29
-rw-r--r--Example Apps/Example ObjC/RemoteControlManager.m247
-rw-r--r--Example Apps/Example Swift/MenuManager.swift58
-rw-r--r--Example Apps/Example Swift/ProxyManager.swift25
-rw-r--r--Example Apps/Example Swift/RemoteControlManager.swift226
-rw-r--r--Example Apps/Shared/AppConstants.h5
-rw-r--r--Example Apps/Shared/AppConstants.m5
-rw-r--r--Example Apps/Shared/Images.xcassets/remote_control.imageset/Contents.json21
-rw-r--r--Example Apps/Shared/Images.xcassets/remote_control.imageset/remote_control_icon.pngbin0 -> 2146 bytes
-rw-r--r--SmartDeviceLink-iOS.podspec2
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj40
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/xcshareddata/xcschemes/SmartDeviceLinkTests.xcscheme52
-rw-r--r--SmartDeviceLink.podspec2
-rw-r--r--SmartDeviceLink/private/SDLIAPDataSession.m6
-rw-r--r--SmartDeviceLink/private/SDLMenuManager.m10
-rw-r--r--SmartDeviceLink/private/SDLPresentAlertOperation.m18
-rw-r--r--SmartDeviceLink/private/SDLProtocol.m73
-rw-r--r--SmartDeviceLink/private/SDLProtocolHeader.m13
-rw-r--r--SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h31
-rw-r--r--SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m290
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicManager.m24
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h6
-rw-r--r--SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m27
-rw-r--r--SmartDeviceLink/private/SDLV1ProtocolHeader.m5
-rw-r--r--SmartDeviceLink/private/SDLV2ProtocolHeader.m10
-rw-r--r--SmartDeviceLink/public/SDLSystemCapabilityManager.m5
-rw-r--r--SmartDeviceLink/public/SDLTouchManagerDelegate.h2
-rw-r--r--SmartDeviceLink/public/SmartDeviceLink.h2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m23
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m42
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m114
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m38
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m17
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m17
-rw-r--r--SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m769
-rw-r--r--SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m39
-rw-r--r--SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m27
-rwxr-xr-xscripts/create_framework.sh15
-rwxr-xr-xscripts/project_file_header_fix.sh227
-rwxr-xr-xscripts/release.sh203
45 files changed, 2679 insertions, 209 deletions
diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index a9e617d4f..189e8a039 100644
--- a/.github/CONTRIBUTING.md
+++ b/.github/CONTRIBUTING.md
@@ -12,12 +12,12 @@ We use [Gitflow](http://nvie.com/posts/a-successful-git-branching-model/) as our
### Pull Requests
* Please follow the repository's for all code and documentation.
-* All feature branches should be based on `develop` and have the format `feature/issue-#num-branch_name`.
-* Minor bug fixes, that is bug fixes that do not change, add, or remove any public API, should be based on `develop` and have the format `bugfix/issue-#num-branch_name`, unless they are slated for a hotfix release, in which case they should be based on `master`.
-* All pull requests should implement a single feature or fix a single bug related to an open issue. Pull Requests that involve multiple changes (it is our discretion what precisely this means) will be rejected with a reason.
+* All feature branches should be based on `develop` and have the format `feature/branch_name`.
+* Minor bug fixes, that is bug fixes that do not change, add, or remove any public API, should be based on `master` and have the format `hotfix/branch_name`.
+* All pull requests should implement a single feature or fix a single bug. Pull Requests that involve multiple changes (it is our discretion what precisely this means) will be rejected with a reason.
* All commits should separated into logical units, i.e. unrelated changes should be in different commits within a pull request.
-* Work in progress pull requests should be Draft PRs. When you believe the pull request is ready to merge, mark them as ready for review to make them an open PR and @mention the appropriate SDL team to schedule a review.
-* All new code *must* include unit tests. Bug fixes should have a test that failed previously and now passes. All new features should have test coverage. If your code does not have tests, or regresses old tests, it will be rejected.
+* Work in progress pull requests should have "[WIP]" in front of the Pull Request title. When you believe the pull request is ready to merge, remove this tag and @mention the appropriate SDL team to schedule a review.
+* All new code *must* include unit tests. Bug fixes should have a test that fails previously and now passes. All new features should be covered. If your code does not have tests, or regresses old tests, it will be rejected.
* Make sure you fill out all sections of the PR template. A great example of a [pull request can be found here](https://github.com/smartdevicelink/sdl_ios/pull/1688).
### Contributor's License Agreement (CLA)
diff --git a/CHANGELOG.md b/CHANGELOG.md
index f51c28dfc..d114b5d14 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,4 +1,31 @@
# Changelog
+## 7.6.0
+* Supports [SDL RPC Spec 8.0.0](https://github.com/smartdevicelink/rpc_spec/releases/tag/8.0.0) and [SDL Protocol Spec 5.4.1](https://github.com/smartdevicelink/protocol_spec/releases/tag/5.4.1).
+
+### Testing
+* Xcode 14.0
+* iOS 15.7 / iOS 16.0
+* Core:
+ * Manticore (Core v8.1.0, Generic HMI v0.12.0)
+ * Ford Sync 3.4 (19353_DEVTEST)
+ * Ford Sync 3.0 (17276_DEVTEST)
+ * Ford Sync 4.0 (20016_DEVTEST)
+ * Core v8.2.0-RC with sdl_hmi v5.8.0-RC and generic_hmi v0.13.0-RC
+
+### Bug Fixes
+* SDLProtocol message parsing should use a state machine (https://github.com/smartdevicelink/sdl_ios/issues/17)
+* Default cellID value passed in during openSubMenu method call (https://github.com/smartdevicelink/sdl_ios/issues/1633)
+* SDLTouchManagerDelegate's didReceivePinchInView is never called in the codebase (https://github.com/smartdevicelink/sdl_ios/issues/1637)
+* Setting bad data in one T&G field then good data quickly in another can lead to the good data failing (https://github.com/smartdevicelink/sdl_ios/issues/1781)
+* SystemCapabilityManager screenParams nil, mediaClockFormats empty, and windowID nil when they shouldn't be (https://github.com/smartdevicelink/sdl_ios/issues/2105)
+* Alert images that are flagged for overwrite fail to display (https://github.com/smartdevicelink/sdl_ios/issues/2109)
+* Fix iAP write data range error (https://github.com/smartdevicelink/sdl_ios/issues/2112)
+
+### Other
+* Use a script to include public header files based on project settings (https://github.com/smartdevicelink/sdl_ios/issues/179)
+* Missing a remote control example in example app (https://github.com/smartdevicelink/sdl_ios/issues/2101)
+* Release Script Bugs (https://github.com/smartdevicelink/sdl_ios/issues/2098)
+
## 7.5.0
* Supports [SDL RPC Spec 8.0.0](https://github.com/smartdevicelink/rpc_spec/releases/tag/8.0.0) and [SDL Protocol Spec 5.4.1](https://github.com/smartdevicelink/protocol_spec/releases/tag/5.4.1).
diff --git a/Example Apps/Example ObjC/MenuManager.h b/Example Apps/Example ObjC/MenuManager.h
index a7c1404c1..a78b2d077 100644
--- a/Example Apps/Example ObjC/MenuManager.h
+++ b/Example Apps/Example ObjC/MenuManager.h
@@ -9,6 +9,7 @@
#import <Foundation/Foundation.h>
@class PerformInteractionManager;
+@class RemoteControlManager;
@class SDLManager;
@class SDLMenuCell;
@class SDLVoiceCommand;
@@ -17,7 +18,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface MenuManager : NSObject
-+ (NSArray<SDLMenuCell *> *)allMenuItemsWithManager:(SDLManager *)manager performManager:(PerformInteractionManager *)performManager;
++ (NSArray<SDLMenuCell *> *)allMenuItemsWithManager:(SDLManager *)manager performManager:(PerformInteractionManager *)performManager remoteManager:(RemoteControlManager *)remoteManager;
+ (NSArray<SDLVoiceCommand *> *)allVoiceMenuItemsWithManager:(SDLManager *)manager;
@end
diff --git a/Example Apps/Example ObjC/MenuManager.m b/Example Apps/Example ObjC/MenuManager.m
index d78be2b8f..29d5986fa 100644
--- a/Example Apps/Example ObjC/MenuManager.m
+++ b/Example Apps/Example ObjC/MenuManager.m
@@ -7,6 +7,7 @@
//
#import "MenuManager.h"
+
#import "AlertManager.h"
#import "AudioManager.h"
#import "AppConstants.h"
@@ -14,14 +15,16 @@
#import "RPCPermissionsManager.h"
#import "SmartDeviceLink.h"
#import "VehicleDataManager.h"
+#import "RemoteControlManager.h"
NS_ASSUME_NONNULL_BEGIN
@implementation MenuManager
-+ (NSArray<SDLMenuCell *> *)allMenuItemsWithManager:(SDLManager *)manager performManager:(PerformInteractionManager *)performManager {
++ (NSArray<SDLMenuCell *> *)allMenuItemsWithManager:(SDLManager *)manager performManager:(PerformInteractionManager *)performManager remoteManager:(RemoteControlManager *)remoteControlManager {
return @[[self sdlex_menuCellSpeakNameWithManager:manager],
[self sdlex_menuCellGetAllVehicleDataWithManager:manager],
+ [self sdlex_menuCellRemoteWithManager:manager remoteManager:remoteControlManager],
[self sdlex_menuCellShowPerformInteractionWithManager:manager performManager:performManager],
[self sdlex_sliderMenuCellWithManager:manager],
[self sdlex_scrollableMessageMenuCellWithManager:manager],
@@ -91,7 +94,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (SDLMenuCell *)sdlex_menuCellChangeTemplateWithManager:(SDLManager *)manager {
- /// Lets give an example of 2 templates
+ // Lets give an example of 2 templates
NSMutableArray *submenuItems = [NSMutableArray array];
NSString *errorMessage = @"Changing the template failed";
@@ -134,7 +137,7 @@ NS_ASSUME_NONNULL_BEGIN
return [[SDLMenuCell alloc] initWithTitle:ACSliderMenuName secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[ACSliderMenuName] handler:^(SDLTriggerSource _Nonnull triggerSource) {
SDLSlider *sliderRPC = [[SDLSlider alloc] initWithNumTicks:3 position:1 sliderHeader:@"Select a letter" sliderFooters:@[@"A", @"B", @"C"] timeout:10000];
[manager sendRequest:sliderRPC withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if(![response.resultCode isEqualToEnum:SDLResultSuccess]) {
+ if (![response.resultCode isEqualToEnum:SDLResultSuccess]) {
if ([response.resultCode isEqualToEnum:SDLResultTimedOut]) {
[AlertManager sendAlertWithManager:manager image:nil textField1:AlertSliderTimedOutWarningText textField2:nil];
} else if ([response.resultCode isEqualToEnum:SDLResultAborted]) {
@@ -151,7 +154,7 @@ NS_ASSUME_NONNULL_BEGIN
return [[SDLMenuCell alloc] initWithTitle:ACScrollableMessageMenuName secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:@[ACScrollableMessageMenuName] handler:^(SDLTriggerSource _Nonnull triggerSource) {
SDLScrollableMessage *messageRPC = [[SDLScrollableMessage alloc] initWithMessage:@"This is a scrollable message\nIt can contain many lines"];
[manager sendRequest:messageRPC withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- if(![response.resultCode isEqualToEnum:SDLResultSuccess]) {
+ if (![response.resultCode isEqualToEnum:SDLResultSuccess]) {
if ([response.resultCode isEqualToEnum:SDLResultTimedOut]) {
[AlertManager sendAlertWithManager:manager image:nil textField1:AlertScrollableMessageTimedOutWarningText textField2:nil];
} else if ([response.resultCode isEqualToEnum:SDLResultAborted]) {
@@ -164,6 +167,50 @@ NS_ASSUME_NONNULL_BEGIN
}];
}
++ (SDLMenuCell *)sdlex_menuCellRemoteWithManager:(SDLManager *)manager remoteManager:(RemoteControlManager *)remoteManager {
+ SDLArtwork *remoteControlIcon = [SDLArtwork artworkWithImage:[[UIImage imageNamed:RemoteControlIconName] imageWithRenderingMode:UIImageRenderingModeAlwaysTemplate] asImageFormat:SDLArtworkImageFormatPNG];
+
+ // Clicking on cell shows alert message when remote control permissions are disabled
+ if (!remoteManager.isEnabled) {
+ return [[SDLMenuCell alloc] initWithTitle:ACRemoteMenuName secondaryText:nil tertiaryText:nil icon:remoteControlIcon secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ [AlertManager sendAlertWithManager:manager image:nil textField1:AlertRemoteControlNotEnabledWarningText textField2:nil];
+ }];
+ }
+
+ // Let's give an example of 2 templates
+ NSMutableArray *submenuItems = [NSMutableArray array];
+ NSString *errorMessage = @"Changing the template failed";
+
+ // Climate Control
+ SDLMenuCell *climateControlCell = [[SDLMenuCell alloc] initWithTitle:ACRemoteControlClimateMenuName secondaryText:nil tertiaryText:nil icon: nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ [manager.screenManager changeLayout:[[SDLTemplateConfiguration alloc] initWithPredefinedLayout:SDLPredefinedLayoutTilesOnly] withCompletionHandler:^(NSError * _Nullable error) {
+ if (error != nil) {
+ [AlertManager sendAlertWithManager:manager image:nil textField1:errorMessage textField2:nil];
+ return;
+ }
+ [remoteManager showClimateControl];
+ }];
+ }];
+ [submenuItems addObject:climateControlCell];
+
+ // View Climate
+ SDLMenuCell *viewClimateCell = [[SDLMenuCell alloc] initWithTitle:ACRemoteViewClimateMenuName secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil voiceCommands:nil handler:^(SDLTriggerSource _Nonnull triggerSource) {
+ SDLScrollableMessage *messageRPC = [[SDLScrollableMessage alloc] initWithMessage:remoteManager.climateDataString];
+ [manager sendRequest:messageRPC withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if ([response.resultCode isEqualToEnum:SDLResultTimedOut]) {
+ [AlertManager sendAlertWithManager:manager image:nil textField1:AlertScrollableMessageTimedOutWarningText textField2:nil];
+ } else if ([response.resultCode isEqualToEnum:SDLResultAborted]) {
+ [AlertManager sendAlertWithManager:manager image:nil textField1:AlertScrollableMessageCancelledWarningText textField2:nil];
+ } else if (![response.resultCode isEqualToEnum:SDLResultSuccess]) {
+ [AlertManager sendAlertWithManager:manager image:nil textField1:AlertScrollableMessageGeneralWarningText textField2:nil];
+ }
+ }];
+ }];
+ [submenuItems addObject:viewClimateCell];
+
+ return [[SDLMenuCell alloc] initWithTitle:ACRemoteMenuName secondaryText:nil tertiaryText:nil icon:remoteControlIcon secondaryArtwork:nil submenuLayout:SDLMenuLayoutList subCells:[submenuItems copy]];
+}
+
#pragma mark - Voice Commands
+ (SDLVoiceCommand *)sdlex_voiceCommandStartWithManager:(SDLManager *)manager {
diff --git a/Example Apps/Example ObjC/ProxyManager.m b/Example Apps/Example ObjC/ProxyManager.m
index 35520d2f9..06f1d7334 100644
--- a/Example Apps/Example ObjC/ProxyManager.m
+++ b/Example Apps/Example ObjC/ProxyManager.m
@@ -9,6 +9,7 @@
#import "PerformInteractionManager.h"
#import "Preferences.h"
#import "ProxyManager.h"
+#import "RemoteControlManager.h"
#import "RPCPermissionsManager.h"
#import "SmartDeviceLink.h"
#import "SubscribeButtonManager.h"
@@ -22,6 +23,8 @@ NS_ASSUME_NONNULL_BEGIN
// Describes the first time the HMI state goes non-none and full.
@property (assign, nonatomic) SDLHMILevel firstHMILevel;
+@property (assign, nonatomic, getter=isRemoteControlEnabled) BOOL remoteControlEnabled;
+@property (strong, nonatomic) RemoteControlManager *remoteControlManager;
@property (strong, nonatomic) VehicleDataManager *vehicleDataManager;
@property (strong, nonatomic) PerformInteractionManager *performManager;
@property (strong, nonatomic) ButtonManager *buttonManager;
@@ -50,6 +53,7 @@ NS_ASSUME_NONNULL_BEGIN
return nil;
}
+ _remoteControlEnabled = NO;
_state = ProxyStateStopped;
_firstHMILevel = SDLHMILevelNone;
@@ -69,7 +73,7 @@ NS_ASSUME_NONNULL_BEGIN
self.performManager = [[PerformInteractionManager alloc] initWithManager:self.sdlManager];
self.buttonManager = [[ButtonManager alloc] initWithManager:self.sdlManager refreshUIHandler:self.refreshUIHandler];
self.subscribeButtonManager = [[SubscribeButtonManager alloc] initWithManager:self.sdlManager];
-
+ self.remoteControlManager = [[RemoteControlManager alloc] initWithManager:self.sdlManager isEnabled:self.isRemoteControlEnabled softButtons:[self.buttonManager allScreenSoftButtons]];
[weakSelf sdlex_updateProxyState:ProxyStateConnected];
[RPCPermissionsManager setupPermissionsCallbacksWithManager:weakSelf.sdlManager];
@@ -99,18 +103,19 @@ NS_ASSUME_NONNULL_BEGIN
[self sdlex_updateProxyState:ProxyStateSearchingForConnection];
SDLConfiguration *config = (proxyTransportType == ProxyTransportTypeIAP) ? [self.class sdlex_iapConfiguration] : [self.class sdlex_tcpConfiguration];
+ self.remoteControlEnabled = (proxyTransportType == ProxyTransportTypeTCP);
self.sdlManager = [[SDLManager alloc] initWithConfiguration:config delegate:self];
[self sdlex_startManager];
}
+ (SDLConfiguration *)sdlex_iapConfiguration {
- SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration defaultConfigurationWithAppName:ExampleAppName fullAppId:ExampleFullAppId]];
+ SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration defaultConfigurationWithAppName:ExampleAppName fullAppId:ExampleFullAppId] enableRemote:NO];
return [self sdlex_setupManagerConfigurationWithLifecycleConfiguration:lifecycleConfig];
}
+ (SDLConfiguration *)sdlex_tcpConfiguration {
- SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration debugConfigurationWithAppName:ExampleAppName fullAppId:ExampleFullAppId ipAddress:[Preferences sharedPreferences].ipAddress port:[Preferences sharedPreferences].port]];
+ SDLLifecycleConfiguration *lifecycleConfig = [self.class sdlex_setLifecycleConfigurationPropertiesOnConfiguration:[SDLLifecycleConfiguration debugConfigurationWithAppName:ExampleAppName fullAppId:ExampleFullAppId ipAddress:[Preferences sharedPreferences].ipAddress port:[Preferences sharedPreferences].port] enableRemote:YES];
return [self sdlex_setupManagerConfigurationWithLifecycleConfiguration:lifecycleConfig];
}
@@ -121,7 +126,7 @@ NS_ASSUME_NONNULL_BEGIN
return [[SDLConfiguration alloc] initWithLifecycle:lifecycleConfiguration lockScreen:lockScreenConfiguration logging:[self.class sdlex_logConfiguration] fileManager:[SDLFileManagerConfiguration defaultConfiguration] encryption:[SDLEncryptionConfiguration defaultConfiguration]];
}
-+ (SDLLifecycleConfiguration *)sdlex_setLifecycleConfigurationPropertiesOnConfiguration:(SDLLifecycleConfiguration *)config {
++ (SDLLifecycleConfiguration *)sdlex_setLifecycleConfigurationPropertiesOnConfiguration:(SDLLifecycleConfiguration *)config enableRemote:(BOOL)enableRemote {
UIImage *appLogo = [[UIImage imageNamed:ExampleAppLogoName] imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
SDLArtwork *appIconArt = [SDLArtwork persistentArtworkWithImage:appLogo asImageFormat:SDLArtworkImageFormatPNG];
@@ -133,6 +138,12 @@ NS_ASSUME_NONNULL_BEGIN
config.languagesSupported = @[SDLLanguageEnUs, SDLLanguageFrCa, SDLLanguageEsMx];
config.appType = SDLAppHMITypeDefault;
+ // On actual hardware, the app requires permissions to do remote control which this example app will not have.
+ // Only use the remote control type on the TCP connection.
+ if (enableRemote) {
+ config.additionalAppTypes = @[SDLAppHMITypeRemoteControl];
+ }
+
SDLRGBColor *green = [[SDLRGBColor alloc] initWithRed:126 green:188 blue:121];
SDLRGBColor *white = [[SDLRGBColor alloc] initWithRed:249 green:251 blue:254];
SDLRGBColor *darkGrey = [[SDLRGBColor alloc] initWithRed:57 green:78 blue:96];
@@ -145,7 +156,7 @@ NS_ASSUME_NONNULL_BEGIN
+ (SDLLogConfiguration *)sdlex_logConfiguration {
SDLLogConfiguration *logConfig = [SDLLogConfiguration debugConfiguration];
- SDLLogFileModule *sdlExampleModule = [SDLLogFileModule moduleWithName:@"SDL Obj-C Example App" files:[NSSet setWithArray:@[@"ProxyManager", @"AlertManager", @"AudioManager", @"ButtonManager", @"SubscribeButtonManager", @"MenuManager", @"PerformInteractionManager", @"RPCPermissionsManager", @"VehicleDataManager"]]];
+ SDLLogFileModule *sdlExampleModule = [SDLLogFileModule moduleWithName:@"SDL Obj-C Example App" files:[NSSet setWithArray:@[@"ProxyManager", @"AlertManager", @"AudioManager", @"ButtonManager", @"SubscribeButtonManager", @"MenuManager", @"PerformInteractionManager", @"RPCPermissionsManager", @"VehicleDataManager", @"RemoteControlManager"]]];
logConfig.modules = [logConfig.modules setByAddingObject:sdlExampleModule];
logConfig.targets = [logConfig.targets setByAddingObject:[SDLLogTargetFile logger]];
logConfig.globalLogLevel = SDLLogLevelDebug;
@@ -156,7 +167,7 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Screen UI Helpers
- (void)sdlex_createMenus {
- self.sdlManager.screenManager.menu = [MenuManager allMenuItemsWithManager:self.sdlManager performManager:self.performManager];
+ self.sdlManager.screenManager.menu = [MenuManager allMenuItemsWithManager:self.sdlManager performManager:self.performManager remoteManager:self.remoteControlManager];
self.sdlManager.screenManager.voiceCommands = [MenuManager allVoiceMenuItemsWithManager:self.sdlManager];
}
@@ -242,10 +253,13 @@ NS_ASSUME_NONNULL_BEGIN
if (![newLevel isEqualToEnum:SDLHMILevelNone] && ([self.firstHMILevel isEqualToEnum:SDLHMILevelNone])) {
// This is our first time in a non-NONE state
self.firstHMILevel = newLevel;
-
+
// Subscribe to vehicle data.
[self.vehicleDataManager subscribeToVehicleOdometer];
+ // Start Remote Control Connection
+ [self.remoteControlManager start];
+
//Handle initial launch
[self sdlex_showInitialData];
}
diff --git a/Example Apps/Example ObjC/RemoteControlManager.h b/Example Apps/Example ObjC/RemoteControlManager.h
new file mode 100644
index 000000000..d85c49d5c
--- /dev/null
+++ b/Example Apps/Example ObjC/RemoteControlManager.h
@@ -0,0 +1,29 @@
+//
+// RemoteControlManager.h
+// SmartDeviceLink-Example-ObjC
+//
+// Created by Beharry, Justin (J.S.) on 8/1/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLManager;
+@class SDLSoftButtonObject;
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface RemoteControlManager : NSObject
+
+@property (assign, nonatomic, readonly, getter=isEnabled) BOOL enabled;
+@property (copy, nonatomic, readonly) NSString *climateDataString;
+
+- (instancetype)init NS_UNAVAILABLE;
+- (instancetype)initWithManager:(SDLManager *)manager isEnabled:(BOOL)enabled softButtons:(NSArray<SDLSoftButtonObject *> *)buttons;
+
+- (void)start;
+- (void)showClimateControl;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/Example Apps/Example ObjC/RemoteControlManager.m b/Example Apps/Example ObjC/RemoteControlManager.m
new file mode 100644
index 000000000..05d74540b
--- /dev/null
+++ b/Example Apps/Example ObjC/RemoteControlManager.m
@@ -0,0 +1,247 @@
+//
+// RemoteControlManager.m
+// SmartDeviceLink-Example-ObjC
+//
+// Created by Beharry, Justin (J.S.) on 8/1/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import "RemoteControlManager.h"
+
+#import "AlertManager.h"
+#import "SmartDeviceLink.h"
+
+@interface RemoteControlManager()
+
+@property (strong, nonatomic) SDLManager *sdlManager;
+@property (strong, nonatomic) NSArray<SDLSoftButtonObject *> *homeButtons;
+
+@property (strong, nonatomic) SDLRemoteControlCapabilities *remoteControlCapabilities;
+@property (strong, nonatomic) NSString *climateModuleId;
+@property (strong, nonatomic) NSNumber<SDLBool> *hasConsent;
+@property (strong, nonatomic) SDLClimateControlData *climateData;
+@property (copy, nonatomic, readwrite) NSString *climateDataString;
+@property (strong, nonatomic, readonly) NSArray<SDLSoftButtonObject *> *remoteButtons;
+
+@end
+
+@implementation RemoteControlManager
+
+- (instancetype)initWithManager:(SDLManager *)manager isEnabled:(BOOL)enabled softButtons:(NSArray<SDLSoftButtonObject *> *)buttons {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _sdlManager = manager;
+ _enabled = enabled;
+ _homeButtons = buttons;
+
+ return self;
+}
+
+- (void)start {
+ if (!self.isEnabled) {
+ SDLLogW(@"Missing permissions for Remote Control Manager. Example remote control works only on TCP.");
+ return;
+ }
+
+ [self.sdlManager.systemCapabilityManager subscribeToCapabilityType:SDLSystemCapabilityTypeRemoteControl withUpdateHandler:^(SDLSystemCapability * _Nullable capability, BOOL subscribed, NSError * _Nullable error) {
+ if (!capability.remoteControlCapability) {
+ SDLLogE(@"SDL errored getting remote control module information: %@", error);
+ return;
+ }
+
+ self.remoteControlCapabilities = capability.remoteControlCapability;
+ self.climateModuleId = self.remoteControlCapabilities.climateControlCapabilities.firstObject.moduleInfo.moduleId;
+
+ // Get consent to control modules
+ SDLGetInteriorVehicleDataConsent *getInteriorVehicleDataConsent = [[SDLGetInteriorVehicleDataConsent alloc] initWithModuleType:SDLModuleTypeClimate moduleIds:@[self.climateModuleId]];
+ [self.sdlManager sendRequest:getInteriorVehicleDataConsent withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored getting remote control consent: %@", error);
+ return;
+ }
+ self.hasConsent = response.success;
+
+ // Initialize climate data and setup subscription
+ if (self.hasConsent) {
+ [self sdlex_initializeClimateData];
+ [self sdlex_subscribeClimateControlData];
+ }
+ }];
+ }];
+}
+
+- (void)showClimateControl {
+ if (self.climateModuleId == nil && self.hasConsent) {
+ NSString *errorMessage = @"The climate module id was not set or consent was not given";
+ [AlertManager sendAlertWithManager:self.sdlManager image:nil textField1:errorMessage textField2:nil];
+ return;
+ }
+
+ // Set the soft buttons to change remote control parameters
+ self.sdlManager.screenManager.softButtonObjects = self.remoteButtons;
+}
+
+- (NSString *)climateDataString {
+ return [NSString stringWithFormat:@"AC: %@\n"
+ "AC Max: %@\n"
+ "Auto Mode: %@\n"
+ "Circulate Air: %@\n"
+ "Climate: %@\n"
+ "Current Temperature: %@\n"
+ "Defrost Zone: %@\n"
+ "Desired Temperature: %@\n"
+ "Dual Mode: %@\n"
+ "Fan Speed: %@\n"
+ "Heated Mirrors: %@\n"
+ "Heated Rears Window: %@\n"
+ "Heated Steering: %@\n"
+ "Heated Windshield: %@\n"
+ "Ventilation: %@\n",
+ self.climateData.acEnable.boolValue ? @"On" : @"Off",
+ self.climateData.acMaxEnable.boolValue ? @"On" : @"Off",
+ self.climateData.autoModeEnable.boolValue ? @"On" : @"Off",
+ self.climateData.circulateAirEnable.boolValue ? @"On" : @"Off",
+ self.climateData.climateEnable.boolValue ? @"On" : @"Off",
+ self.climateData.currentTemperature,
+ self.climateData.defrostZone,
+ self.climateData.desiredTemperature,
+ self.climateData.dualModeEnable.boolValue ? @"On" : @"Off",
+ self.climateData.fanSpeed,
+ self.climateData.heatedMirrorsEnable.boolValue ? @"On" : @"Off",
+ self.climateData.heatedRearWindowEnable.boolValue ? @"On" : @"Off",
+ self.climateData.heatedSteeringWheelEnable.boolValue ? @"On" : @"Off",
+ self.climateData.heatedWindshieldEnable.boolValue ? @"On" : @"Off",
+ self.climateData.ventilationMode
+ ];
+}
+
+- (void)sdlex_initializeClimateData {
+ if (self.climateModuleId == nil && !self.hasConsent.boolValue) {
+ NSString *errorMessage = @"The climate module id was not set or consent was not given";
+ [AlertManager sendAlertWithManager:self.sdlManager image:nil textField1:errorMessage textField2:nil];
+ }
+
+ SDLGetInteriorVehicleData *getInteriorVehicleData = [[SDLGetInteriorVehicleData alloc] initWithModuleType:SDLModuleTypeClimate moduleId:self.climateModuleId];
+ [self.sdlManager sendRequest:getInteriorVehicleData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ SDLGetInteriorVehicleDataResponse *dataResponse = (SDLGetInteriorVehicleDataResponse *)response;
+ self.climateData = dataResponse.moduleData.climateControlData;
+ }];
+}
+
+- (void)sdlex_subscribeClimateControlData {
+ // Start the subscription to remote control data
+ [self.sdlManager subscribeToRPC:SDLDidReceiveInteriorVehicleDataNotification withBlock:^(__kindof SDLRPCMessage * _Nonnull message) {
+ SDLOnInteriorVehicleData *onInteriorVehicleData = (SDLOnInteriorVehicleData *)message;
+ self.climateData = onInteriorVehicleData.moduleData.climateControlData;
+ }];
+
+ // Start the subscriptin to climate data
+ SDLGetInteriorVehicleData *getInteriorVehicleData = [[SDLGetInteriorVehicleData alloc] initAndSubscribeToModuleType:SDLModuleTypeClimate moduleId:self.climateModuleId];
+ [self.sdlManager sendRequest:getInteriorVehicleData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ return SDLLogE(@"SDL errored trying to subscribe to climate data: %@", error);
+ }
+ SDLLogD(@"SDL Subscribing to climate control data");
+ }];
+}
+
+- (void)sdlex_turnOnAC {
+ SDLClimateControlData *climateControlData = [[SDLClimateControlData alloc] initWithDictionary:@{ @"acEnable": @YES }];
+ SDLModuleData *moduleData = [[SDLModuleData alloc] initWithClimateControlData:climateControlData];
+ SDLSetInteriorVehicleData *setInteriorVehicleData = [[SDLSetInteriorVehicleData alloc] initWithModuleData:moduleData];
+
+ [self.sdlManager sendRequest:setInteriorVehicleData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored trying to turn on climate AC: %@", error);
+ return;
+ }
+ }];
+}
+
+- (void)sdlex_turnOffAC {
+ SDLClimateControlData *climateControlData = [[SDLClimateControlData alloc] initWithDictionary:@{ @"acEnable": @NO }];
+ SDLModuleData *moduleData = [[SDLModuleData alloc] initWithClimateControlData:climateControlData];
+ SDLSetInteriorVehicleData *setInteriorVehicleData = [[SDLSetInteriorVehicleData alloc] initWithModuleData:moduleData];
+
+ [self.sdlManager sendRequest:setInteriorVehicleData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored trying to turn off climate AC: %@", error);
+ return;
+ }
+ }];
+}
+
+- (void)sdlex_setClimateTemperature {
+ SDLTemperature *temperature = [[SDLTemperature alloc] initWithFahrenheitValue:73];
+ NSDictionary<NSString *, id> *climateDictionary = @{@"acEnable": @YES, @"fanSpeed": @100, @"desiredTemperature": temperature, @"ventilationMode": SDLVentilationModeBoth };
+
+ SDLClimateControlData *climateControlData = [[SDLClimateControlData alloc] initWithDictionary:climateDictionary];
+ SDLModuleData *moduleData = [[SDLModuleData alloc] initWithClimateControlData:climateControlData];
+ SDLSetInteriorVehicleData *setInteriorVehicleData = [[SDLSetInteriorVehicleData alloc] initWithModuleData:moduleData];
+
+ [self.sdlManager sendRequest:setInteriorVehicleData withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored trying to set climate temperature to 73 degrees: %@", error);
+ return;
+ }
+ }];
+}
+
+- (NSArray *)remoteButtons {
+ SDLSoftButtonObject *acOnButton = [[SDLSoftButtonObject alloc] initWithName:@"AC On" text:@"AC On" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+ [self sdlex_turnOnAC];
+ }];
+
+ SDLSoftButtonObject *acOffButton = [[SDLSoftButtonObject alloc] initWithName:@"AC Off" text:@"AC Off" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+ [self sdlex_turnOffAC];
+ }];
+
+ SDLSoftButtonObject *acMaxToggle = [[SDLSoftButtonObject alloc] initWithName:@"AC Max" text:@"AC Max" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+
+ SDLButtonPress *buttonTouch = [[SDLButtonPress alloc] initWithButtonName:SDLButtonNameACMax moduleType:SDLModuleTypeClimate moduleId:self.climateModuleId buttonPressMode:SDLButtonPressModeShort];
+ [self.sdlManager sendRequest:buttonTouch withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored toggle AC Max with remote button press: %@", error);
+ return;
+ }
+ }];
+ }];
+
+ SDLSoftButtonObject *temperatureDecreaseButton = [[SDLSoftButtonObject alloc] initWithName:@"Temperature Decrease" text:@"Temperature -" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+
+ SDLButtonPress *buttonTouch = [[SDLButtonPress alloc] initWithButtonName:SDLButtonNameTempDown moduleType:SDLModuleTypeClimate moduleId:self.climateModuleId buttonPressMode:SDLButtonPressModeShort];
+ [self.sdlManager sendRequest:buttonTouch withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored decreasing target climate temperature with remote button: %@", error);
+ return;
+ }
+ }];
+ }];
+
+ SDLSoftButtonObject *temperatureIncreaseButton = [[SDLSoftButtonObject alloc] initWithName:@"Temperature Increase" text:@"Temperature +" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+
+ SDLButtonPress *buttonTouch = [[SDLButtonPress alloc] initWithButtonName:SDLButtonNameTempUp moduleType:SDLModuleTypeClimate moduleId:self.climateModuleId buttonPressMode:SDLButtonPressModeShort];
+ [self.sdlManager sendRequest:buttonTouch withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (!response.success) {
+ SDLLogE(@"SDL errored increasing target climate temperature with remote button:: %@", error);
+ return;
+ }
+ }];
+ }];
+
+ SDLSoftButtonObject *backToHomeButton = [[SDLSoftButtonObject alloc] initWithName:@"Home" text:@"Back to Home" artwork:nil handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {
+ if (buttonPress == nil) { return; }
+ self.sdlManager.screenManager.softButtonObjects = self.homeButtons;
+ [self.sdlManager.screenManager changeLayout:[[SDLTemplateConfiguration alloc] initWithPredefinedLayout:SDLPredefinedLayoutNonMedia] withCompletionHandler:nil];
+ }];
+
+ return @[acOnButton, acOffButton, acMaxToggle, temperatureDecreaseButton, temperatureIncreaseButton, backToHomeButton];
+}
+
+@end
diff --git a/Example Apps/Example Swift/MenuManager.swift b/Example Apps/Example Swift/MenuManager.swift
index 092670559..b35af48aa 100644
--- a/Example Apps/Example Swift/MenuManager.swift
+++ b/Example Apps/Example Swift/MenuManager.swift
@@ -15,9 +15,10 @@ class MenuManager: NSObject {
///
/// - Parameter manager: The SDL Manager
/// - Returns: An array of SDLAddCommand objects
- class func allMenuItems(with manager: SDLManager, choiceSetManager: PerformInteractionManager) -> [SDLMenuCell] {
+ class func allMenuItems(with manager: SDLManager, choiceSetManager: PerformInteractionManager, remoteManager: RemoteControlManager) -> [SDLMenuCell] {
return [menuCellSpeakName(with: manager),
menuCellGetAllVehicleData(with: manager),
+ menuCellRemoteControl(with: manager, remoteManager: remoteManager),
menuCellShowPerformInteraction(with: manager, choiceSetManager: choiceSetManager),
sliderMenuCell(with: manager),
scrollableMessageMenuCell(with: manager),
@@ -117,11 +118,11 @@ private extension MenuManager {
/// - Returns: A SDLMenuCell object
class func menuCellChangeTemplate(with manager: SDLManager) -> SDLMenuCell {
- /// Lets give an example of 2 templates
+ // Lets give an example of 2 templates
var submenuItems = [SDLMenuCell]()
let errorMessage = "Changing the template failed"
- /// Non-Media
+ // Non-Media
let submenuTitleNonMedia = "Non - Media (Default)"
submenuItems.append(SDLMenuCell(title: submenuTitleNonMedia, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in
manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .nonMedia)) { err in
@@ -132,7 +133,7 @@ private extension MenuManager {
}
}))
- /// Graphic with Text
+ // Graphic with Text
let submenuTitleGraphicText = "Graphic With Text"
submenuItems.append(SDLMenuCell(title: submenuTitleGraphicText, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in
manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .graphicWithText)) { err in
@@ -206,6 +207,55 @@ private extension MenuManager {
})
})
}
+
+ /// Menu item that shows remote control example
+ ///
+ /// - Parameters:
+ /// - manager: The SDL Manager
+ /// - remoteManager: The manager for controling and viewing remote control data
+ /// - Returns: A SDLMenuCell object
+ class func menuCellRemoteControl(with manager: SDLManager, remoteManager: RemoteControlManager) -> SDLMenuCell {
+ let remoteControlIcon = SDLArtwork(image: UIImage(named: RemoteControlIconName)!.withRenderingMode(.alwaysTemplate), persistent: true, as: .PNG)
+
+ // Clicking on cell shows alert message when remote control permissions are disabled
+ guard remoteManager.isEnabled else {
+ return SDLMenuCell(title: ACRemoteMenuName, secondaryText: nil, tertiaryText: nil, icon: remoteControlIcon, secondaryArtwork: nil, voiceCommands: nil, handler: { _ in
+ AlertManager.sendAlert(textField1: AlertRemoteControlNotEnabledWarningText, sdlManager: manager)
+ })
+ }
+
+ var submenuItems = [SDLMenuCell]()
+ // Climate Control Menu
+ submenuItems.append(SDLMenuCell(title: ACRemoteControlClimateMenuName, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { (triggerSource) in
+ manager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .tilesOnly)) { err in
+ if let error = err {
+ AlertManager.sendAlert(textField1: error.localizedDescription, sdlManager: manager)
+ return
+ }
+ remoteManager.showClimateControl()
+ }
+ }))
+
+ // View Climate Data
+ submenuItems.append(SDLMenuCell(title: ACRemoteViewClimateMenuName, secondaryText: nil, tertiaryText: nil, icon: nil, secondaryArtwork: nil, voiceCommands: nil, handler: { _ in
+ let climateDataMessage = SDLScrollableMessage(message: remoteManager.climateDataString)
+ manager.send(request: climateDataMessage, responseHandler: { (request, response, error) in
+ guard let response = response else { return }
+ guard response.resultCode == .success else {
+ if response.resultCode == .timedOut {
+ AlertManager.sendAlert(textField1: AlertScrollableMessageTimedOutWarningText, sdlManager: manager)
+ } else if response.resultCode == .aborted {
+ AlertManager.sendAlert(textField1: AlertScrollableMessageCancelledWarningText, sdlManager: manager)
+ } else {
+ AlertManager.sendAlert(textField1: AlertScrollableMessageGeneralWarningText, sdlManager: manager)
+ }
+ return
+ }
+ })
+ }))
+
+ return SDLMenuCell(title: ACRemoteMenuName, secondaryText: nil, tertiaryText: nil, icon: remoteControlIcon, secondaryArtwork: nil, submenuLayout: .list, subCells: submenuItems)
+ }
}
// MARK: - Menu Voice Commands
diff --git a/Example Apps/Example Swift/ProxyManager.swift b/Example Apps/Example Swift/ProxyManager.swift
index efd364784..9a9d10140 100644
--- a/Example Apps/Example Swift/ProxyManager.swift
+++ b/Example Apps/Example Swift/ProxyManager.swift
@@ -26,7 +26,9 @@ class ProxyManager: NSObject {
private var subscribeButtonManager: SubscribeButtonManager!
private var vehicleDataManager: VehicleDataManager!
private var performInteractionManager: PerformInteractionManager!
+ private var remoteControlManager: RemoteControlManager!
private var firstHMILevelState: SDLHMILevel
+ private var isRemoteControlEnabled: Bool
weak var delegate: ProxyManagerDelegate?
@@ -34,6 +36,7 @@ class ProxyManager: NSObject {
static let sharedManager = ProxyManager()
private override init() {
firstHMILevelState = .none
+ isRemoteControlEnabled = false;
super.init()
}
}
@@ -48,6 +51,7 @@ extension ProxyManager {
delegate?.didChangeProxyState(ProxyState.searching)
sdlManager = SDLManager(configuration: (proxyTransportType == .iap) ? ProxyManager.iapConfiguration : ProxyManager.tcpConfiguration, delegate: self)
+ self.isRemoteControlEnabled = (proxyTransportType == .tcp)
startManager()
}
@@ -74,7 +78,7 @@ private extension ProxyManager {
/// - Returns: A SDLConfiguration object
class var iapConfiguration: SDLConfiguration {
let lifecycleConfiguration = SDLLifecycleConfiguration(appName: ExampleAppName, fullAppId: ExampleFullAppId)
- return setupManagerConfiguration(with: lifecycleConfiguration)
+ return setupManagerConfiguration(with: lifecycleConfiguration, enableRemote: false)
}
/// Configures a TCP transport layer with the IP address and port of the remote SDL Core instance.
@@ -82,18 +86,25 @@ private extension ProxyManager {
/// - Returns: A SDLConfiguration object
class var tcpConfiguration: SDLConfiguration {
let lifecycleConfiguration = SDLLifecycleConfiguration(appName: ExampleAppName, fullAppId: ExampleFullAppId, ipAddress: AppUserDefaults.shared.ipAddress!, port: UInt16(AppUserDefaults.shared.port!)!)
- return setupManagerConfiguration(with: lifecycleConfiguration)
+ return setupManagerConfiguration(with: lifecycleConfiguration, enableRemote: true)
}
/// Helper method for setting additional configuration parameters for both TCP and iAP transport layers.
///
/// - Parameter lifecycleConfiguration: The transport layer configuration
/// - Returns: A SDLConfiguration object
- class func setupManagerConfiguration(with lifecycleConfiguration: SDLLifecycleConfiguration) -> SDLConfiguration {
+ class func setupManagerConfiguration(with lifecycleConfiguration: SDLLifecycleConfiguration, enableRemote: Bool) -> SDLConfiguration {
lifecycleConfiguration.shortAppName = ExampleAppNameShort
let appIcon = UIImage(named: ExampleAppLogoName)?.withRenderingMode(.alwaysOriginal)
lifecycleConfiguration.appIcon = appIcon != nil ? SDLArtwork(image: appIcon!, persistent: true, as: .PNG) : nil
lifecycleConfiguration.appType = .default
+
+ // On actual hardware, the app requires permissions to do remote control which this example app will not have.
+ // Only use the remote control type on the TCP connection.
+ if enableRemote {
+ lifecycleConfiguration.additionalAppTypes = [.remoteControl]
+ }
+
lifecycleConfiguration.language = .enUs
lifecycleConfiguration.languagesSupported = [.enUs, .esMx, .frCa]
lifecycleConfiguration.ttsName = [SDLTTSChunk(text: "S D L", type: .text)]
@@ -114,7 +125,7 @@ private extension ProxyManager {
/// - Returns: A SDLLogConfiguration object
class func logConfiguration() -> SDLLogConfiguration {
let logConfig = SDLLogConfiguration.default()
- let exampleLogFileModule = SDLLogFileModule(name: "SDL Swift Example App", files: ["ProxyManager", "AlertManager", "AudioManager", "ButtonManager", "SubscribeButtonManager", "MenuManager", "PerformInteractionManager", "RPCPermissionsManager", "VehicleDataManager"])
+ let exampleLogFileModule = SDLLogFileModule(name: "SDL Swift Example App", files: ["ProxyManager", "AlertManager", "AudioManager", "ButtonManager", "SubscribeButtonManager", "MenuManager", "PerformInteractionManager", "RPCPermissionsManager", "VehicleDataManager", "RemoteControlManager"])
logConfig.modules.insert(exampleLogFileModule)
_ = logConfig.targets.insert(SDLLogTargetFile()) // Logs to file
logConfig.globalLogLevel = .debug // Filters the logs
@@ -136,6 +147,7 @@ private extension ProxyManager {
self.subscribeButtonManager = SubscribeButtonManager(sdlManager: self.sdlManager)
self.vehicleDataManager = VehicleDataManager(sdlManager: self.sdlManager, refreshUIHandler: self.refreshUIHandler)
self.performInteractionManager = PerformInteractionManager(sdlManager: self.sdlManager)
+ self.remoteControlManager = RemoteControlManager(sdlManager: self.sdlManager, enabled: self.isRemoteControlEnabled, homeButtons: self.buttonManager.allScreenSoftButtons())
RPCPermissionsManager.setupPermissionsCallbacks(with: self.sdlManager)
@@ -169,6 +181,9 @@ extension ProxyManager: SDLManagerDelegate {
// Subscribe to vehicle data.
vehicleDataManager.subscribeToVehicleOdometer()
+ // Start Remote Control Connection
+ remoteControlManager.start()
+
//Handle initial launch
showInitialData()
}
@@ -306,7 +321,7 @@ private extension ProxyManager {
func createMenuAndGlobalVoiceCommands() {
// Send the root menu items
let screenManager = sdlManager.screenManager
- let menuItems = MenuManager.allMenuItems(with: sdlManager, choiceSetManager: performInteractionManager)
+ let menuItems = MenuManager.allMenuItems(with: sdlManager, choiceSetManager: performInteractionManager, remoteManager: remoteControlManager)
let voiceMenuItems = MenuManager.allVoiceMenuItems(with: sdlManager)
if !menuItems.isEmpty { screenManager.menu = menuItems }
diff --git a/Example Apps/Example Swift/RemoteControlManager.swift b/Example Apps/Example Swift/RemoteControlManager.swift
new file mode 100644
index 000000000..fc73434b4
--- /dev/null
+++ b/Example Apps/Example Swift/RemoteControlManager.swift
@@ -0,0 +1,226 @@
+//
+// RemoteControlManager.swift
+// SmartDeviceLink-Example-Swift
+//
+// Created by Beharry, Justin (J.S.) on 7/28/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+import Foundation
+import SmartDeviceLink
+import SmartDeviceLinkSwift
+
+class RemoteControlManager {
+ private let sdlManager: SDLManager
+ private let homeButtons: [SDLSoftButtonObject]
+ private var remoteControlCapabilities: SDLRemoteControlCapabilities?
+ private var climateModuleId: String?
+ private var hasConsent: Bool?
+ private var climateData: SDLClimateControlData?
+
+ let isEnabled: Bool
+ var climateDataString: String {
+ """
+ AC: \(optionalNumberBoolToString(climateData?.acEnable))
+ AC Max: \(optionalNumberBoolToString(climateData?.acMaxEnable))
+ Auto Mode: \(optionalNumberBoolToString(climateData?.autoModeEnable))
+ Circulate Air: \(optionalNumberBoolToString(climateData?.circulateAirEnable))
+ Climate: \(optionalNumberBoolToString(climateData?.climateEnable))
+ Current Temperature: \(optionalTemperatureToString(climateData?.currentTemperature))
+ Defrost Zone: \(optionalSDLEnumToString(climateData?.defrostZone?.rawValue))
+ Desired Temperature: \(optionalTemperatureToString(climateData?.desiredTemperature))
+ Dual Mode: \(optionalNumberBoolToString(climateData?.dualModeEnable))
+ Fan Speed: \(optionalNumberToString(climateData?.fanSpeed))
+ Heated Mirrors: \(optionalNumberBoolToString(climateData?.heatedMirrorsEnable))
+ Heated Rears Window: \(optionalNumberBoolToString(climateData?.heatedRearWindowEnable))
+ Heated Steering: \(optionalNumberBoolToString(climateData?.heatedSteeringWheelEnable))
+ Heated Windshield: \(optionalNumberBoolToString(climateData?.heatedWindshieldEnable))
+ Ventilation: \(optionalSDLEnumToString(climateData?.ventilationMode?.rawValue))
+ """
+ }
+
+ /// Initialize the RemoteControlManager Object
+ ///
+ /// - Parameters:
+ /// - sdlManager: The SDL Manager.
+ /// - enabled: Permission from the proxy manager to access remote control data
+ /// - homeButton: An array of SDLSoftButtonObjects that remote control manager can reset to.
+ init(sdlManager: SDLManager, enabled: Bool, homeButtons: [SDLSoftButtonObject]) {
+ self.sdlManager = sdlManager
+ self.isEnabled = enabled
+ self.homeButtons = homeButtons
+ }
+
+ func start() {
+ if !self.isEnabled {
+ return SDLLog.w("Missing permissions for Remote Control Manager. Example remote control works only on TCP.")
+ }
+
+ // Retrieve remote control information and store module ids
+ self.sdlManager.systemCapabilityManager.subscribe(capabilityType: .remoteControl) { (capability, subscribed, error) in
+ guard capability?.remoteControlCapability != nil else {
+ return SDLLog.e("SDL errored getting remote control module information: \(String(describing: error))")
+ }
+ self.remoteControlCapabilities = capability?.remoteControlCapability
+
+ let firstClimateModule = self.remoteControlCapabilities?.climateControlCapabilities?.first
+ let moduleId = firstClimateModule?.moduleInfo?.moduleId
+ self.climateModuleId = moduleId
+
+ // Get Consent to control module
+ let getInteriorVehicleDataConsent = SDLGetInteriorVehicleDataConsent(moduleType: .climate, moduleIds: [self.climateModuleId!])
+ self.sdlManager.send(request: getInteriorVehicleDataConsent, responseHandler: { (request, response, error) in
+ guard let response = response as? SDLGetInteriorVehicleDataConsentResponse else {
+ return SDLLog.e("SDL errored getting remote control consent: \(String(describing: error))");
+ }
+ guard let allowed = response.allowed?.first?.boolValue else { return }
+
+ self.hasConsent = allowed
+
+ // initialize climate data and setup subscription
+ if self.hasConsent == true {
+ self.initializeClimateData()
+ self.subscribeClimateControlData()
+ }
+ })
+ }
+ }
+
+ /// Displays Buttons for the user to control the climate
+ func showClimateControl() {
+ // Check that the climate module id has been set and consent has been given
+ guard climateModuleId != nil && hasConsent == true else {
+ return AlertManager.sendAlert(textField1: "The climate module id was not set or consent was not given", sdlManager: self.sdlManager)
+ }
+ self.sdlManager.screenManager.softButtonObjects = climateButtons
+ }
+
+ private func optionalNumberBoolToString(_ number: NSNumber?) -> String {
+ guard let number = number else { return "Unknown" }
+ return number.boolValue ? "On" : "Off"
+ }
+
+ private func optionalNumberToString(_ number: NSNumber?) -> String {
+ guard let number = number else { return "Unknown" }
+ return number.stringValue
+ }
+
+ private func optionalTemperatureToString(_ temperature: SDLTemperature?) -> String {
+ guard let temperature = temperature else { return "Unknown" }
+ return temperature.description
+ }
+
+ private func optionalSDLEnumToString(_ sdlEnum: SDLEnum?) -> String {
+ guard let sdlEnum = sdlEnum else { return "Unknown" }
+ return sdlEnum.rawValue
+ }
+
+ private func initializeClimateData() {
+ // Check that the climate module id has been set and consent has been given
+ guard climateModuleId != nil && hasConsent == true else {
+ return AlertManager.sendAlert(textField1: "The climate module id was not set or consent was not given", sdlManager: self.sdlManager)
+ }
+
+ let getInteriorVehicleData = SDLGetInteriorVehicleData(moduleType: .climate, moduleId: self.climateModuleId!)
+ self.sdlManager.send(request: getInteriorVehicleData) { (req, res, err) in
+ guard let response = res as? SDLGetInteriorVehicleDataResponse else { return }
+ self.climateData = response.moduleData?.climateControlData
+ }
+ }
+
+ private func subscribeClimateControlData() {
+ // Start the subscription to remote control data
+ sdlManager.subscribe(to: .SDLDidReceiveInteriorVehicleData) { (message) in
+ guard let onInteriorVehicleData = message as? SDLOnInteriorVehicleData else { return }
+ self.climateData = onInteriorVehicleData.moduleData.climateControlData
+ }
+
+ // Start the subscription to climate data
+ let getInteriorVehicleData = SDLGetInteriorVehicleData(andSubscribeToModuleType: .climate, moduleId: self.climateModuleId!)
+ sdlManager.send(request: getInteriorVehicleData) { (req, res, err) in
+ guard let response = res as? SDLGetInteriorVehicleDataResponse, response.success.boolValue == true else {
+ return SDLLog.e("SDL errored trying to subscribe to climate data: \(String(describing: err))")
+ }
+ SDLLog.d("Subscribed to climate control data");
+ }
+ }
+
+ private func turnOnAC() {
+ let climateControlData = SDLClimateControlData(dictionary: [
+ "acEnable": true
+ ])
+ let moduleData = SDLModuleData(climateControlData: climateControlData)
+ let setInteriorVehicleData = SDLSetInteriorVehicleData(moduleData: moduleData)
+
+ self.sdlManager.send(request: setInteriorVehicleData) { (request, response, error) in
+ guard response?.success.boolValue == true else {
+ return SDLLog.e("SDL errored trying to turn on climate AC: \(String(describing: error))")
+ }
+ }
+ }
+
+ private func turnOffAC() {
+ let climateControlData = SDLClimateControlData(dictionary: [
+ "acEnable": false
+ ])
+ let moduleData = SDLModuleData(climateControlData: climateControlData)
+ let setInteriorVehicleData = SDLSetInteriorVehicleData(moduleData: moduleData)
+
+ self.sdlManager.send(request: setInteriorVehicleData) { (request, response, error) in
+ guard response?.success.boolValue == true else {
+ return SDLLog.e("SDL errored trying to turn off climate AC: \(String(describing: error))")
+ }
+ }
+ }
+
+ // An array of button objects to control the climate
+ private var climateButtons : [SDLSoftButtonObject] {
+ let acOnButton = SDLSoftButtonObject(name: "AC On", text: "AC On", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ self.turnOnAC()
+ }
+
+ let acOffButton = SDLSoftButtonObject(name: "AC Off", text: "AC Off", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ self.turnOffAC()
+ }
+
+ let acMaxToggle = SDLSoftButtonObject(name: "AC Max", text: "AC Max", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ let remoteButtonPress = SDLButtonPress(buttonName: .acMax, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short)
+ self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in
+ guard response?.success.boolValue == true else {
+ return SDLLog.e("SDL errored toggling AC Max with remote button press: \(String(describing: error))")
+ }
+ }
+ }
+
+ let temperatureDecreaseButton = SDLSoftButtonObject(name: "Temperature Decrease", text: "Temperature -", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ let remoteButtonPress = SDLButtonPress(buttonName: .tempDown, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short)
+ self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in
+ guard response?.success.boolValue == true else {
+ return SDLLog.e("SDL errored decreasing target climate temperature with remote button: \(String(describing: error))")
+ }
+ }
+ }
+
+ let temperatureIncreaseButton = SDLSoftButtonObject(name: "Temperature Increase", text: "Temperature +", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ let remoteButtonPress = SDLButtonPress(buttonName: .tempUp, moduleType: .climate, moduleId: self.climateModuleId, buttonPressMode: .short)
+ self.sdlManager.send(request: remoteButtonPress) { (request, response, error) in
+ guard response?.success.boolValue == true else {
+ return SDLLog.e("SDL errored increasing target climate temperature with remote button:: \(String(describing: error))")
+ }
+ }
+ }
+
+ let backToHomeButton = SDLSoftButtonObject(name: "Home", text: "Back to Home", artwork: nil) { (buttonPress, buttonEvent) in
+ guard buttonPress != nil else { return }
+ self.sdlManager.screenManager.softButtonObjects = self.homeButtons
+ self.sdlManager.screenManager.changeLayout(SDLTemplateConfiguration(predefinedLayout: .nonMedia))
+ }
+
+ return [acOnButton, acOffButton, acMaxToggle, temperatureDecreaseButton, temperatureIncreaseButton, backToHomeButton]
+ }
+}
diff --git a/Example Apps/Shared/AppConstants.h b/Example Apps/Shared/AppConstants.h
index 36f25b4b7..94d698e0c 100644
--- a/Example Apps/Shared/AppConstants.h
+++ b/Example Apps/Shared/AppConstants.h
@@ -59,6 +59,7 @@ extern NSString * const AlertScrollableMessageCancelledWarningText;
extern NSString * const AlertScrollableMessageGeneralWarningText;
extern NSString * const AlertVehicleDataPermissionsWarningText;
extern NSString * const AlertVehicleDataGeneralWarningText;
+extern NSString * const AlertRemoteControlNotEnabledWarningText;
extern NSString * const AlertSpeechPermissionsWarningText;
#pragma mark - SDL Text-To-Speech
@@ -119,6 +120,9 @@ extern NSString * const ACMyKeyMenuName;
extern NSString * const ACOdometerMenuName;
extern NSString * const ACPRNDLMenuName;
extern NSString * const ACRPMMenuName;
+extern NSString * const ACRemoteMenuName;
+extern NSString * const ACRemoteControlClimateMenuName;
+extern NSString * const ACRemoteViewClimateMenuName;
extern NSString * const ACSpeedMenuName;
extern NSString * const ACSteeringWheelAngleMenuName;
extern NSString * const ACTirePressureMenuName;
@@ -136,6 +140,7 @@ extern NSString * const PhoneBWIconImageName;
extern NSString * const SpeakBWIconImageName;
extern NSString * const BatteryEmptyBWIconName;
extern NSString * const BatteryFullBWIconName;
+extern NSString * const RemoteControlIconName;
#pragma mark - SDL App Name in Different Languages
extern NSString * const ExampleAppNameSpanish;
diff --git a/Example Apps/Shared/AppConstants.m b/Example Apps/Shared/AppConstants.m
index 20afa0b6c..1524547bf 100644
--- a/Example Apps/Shared/AppConstants.m
+++ b/Example Apps/Shared/AppConstants.m
@@ -56,6 +56,7 @@ NSString * const AlertScrollableMessageCancelledWarningText = @"Scrollable Messa
NSString * const AlertScrollableMessageGeneralWarningText = @"Scrollable Message could not be displayed";
NSString * const AlertVehicleDataPermissionsWarningText = @"This app does not have the required permissions to access vehicle data";
NSString * const AlertVehicleDataGeneralWarningText = @"Something went wrong while getting vehicle data";
+NSString * const AlertRemoteControlNotEnabledWarningText = @"Remote Control is disabled on hardware. Connect this app to Manticore to test Remote Control.";
NSString * const AlertSpeechPermissionsWarningText = @"You must give this app permission to access Speech Recognition";
#pragma mark - SDL Text-To-Speech
@@ -116,6 +117,9 @@ NSString * const ACMyKeyMenuName = @"MyKey";
NSString * const ACOdometerMenuName = @"Odometer";
NSString * const ACPRNDLMenuName = @"PRNDL";
NSString * const ACRPMMenuName = @"RPM";
+NSString * const ACRemoteMenuName = @"Remote Control";
+NSString * const ACRemoteControlClimateMenuName = @"Climate Control";
+NSString * const ACRemoteViewClimateMenuName = @"View Climate";
NSString * const ACSpeedMenuName = @"Speed";
NSString * const ACSteeringWheelAngleMenuName = @"Steering Wheel Angle";
NSString * const ACTirePressureMenuName = @"Tire Pressure";
@@ -133,6 +137,7 @@ NSString * const PhoneBWIconImageName = @"phone";
NSString * const SpeakBWIconImageName = @"speak";
NSString * const BatteryEmptyBWIconName = @"toggle_off";
NSString * const BatteryFullBWIconName = @"toggle_on";
+NSString * const RemoteControlIconName = @"remote_control";
#pragma mark - SDL App Name in Different Languages
NSString * const ExampleAppNameSpanish = @"SDL Aplicación de ejemplo";
diff --git a/Example Apps/Shared/Images.xcassets/remote_control.imageset/Contents.json b/Example Apps/Shared/Images.xcassets/remote_control.imageset/Contents.json
new file mode 100644
index 000000000..bbc03879b
--- /dev/null
+++ b/Example Apps/Shared/Images.xcassets/remote_control.imageset/Contents.json
@@ -0,0 +1,21 @@
+{
+ "images" : [
+ {
+ "filename" : "remote_control_icon.png",
+ "idiom" : "universal",
+ "scale" : "1x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "2x"
+ },
+ {
+ "idiom" : "universal",
+ "scale" : "3x"
+ }
+ ],
+ "info" : {
+ "author" : "xcode",
+ "version" : 1
+ }
+}
diff --git a/Example Apps/Shared/Images.xcassets/remote_control.imageset/remote_control_icon.png b/Example Apps/Shared/Images.xcassets/remote_control.imageset/remote_control_icon.png
new file mode 100644
index 000000000..95b5dcec5
--- /dev/null
+++ b/Example Apps/Shared/Images.xcassets/remote_control.imageset/remote_control_icon.png
Binary files differ
diff --git a/SmartDeviceLink-iOS.podspec b/SmartDeviceLink-iOS.podspec
index 9276c881f..c18092c69 100644
--- a/SmartDeviceLink-iOS.podspec
+++ b/SmartDeviceLink-iOS.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SmartDeviceLink-iOS"
-s.version = "7.5.0"
+s.version = "7.6.0"
s.summary = "Connect your app with cars!"
s.homepage = "https://github.com/smartdevicelink/SmartDeviceLink-iOS"
s.license = { :type => "New BSD", :file => "LICENSE" }
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 4476e9e0c..98a18cdf5 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -234,6 +234,8 @@
1680B11C1A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */; };
1680B11D1A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */; };
1680B11E1A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */; };
+ 1C87BFB228981328006E79F1 /* RemoteControlManager.m in Sources */ = {isa = PBXBuildFile; fileRef = 1C87BFB128981328006E79F1 /* RemoteControlManager.m */; };
+ 1CFDAAA72893181200332B84 /* RemoteControlManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1CFDAAA62893181200332B84 /* RemoteControlManager.swift */; };
1E89B0DE2031636000A47992 /* SDLSeatControlDataSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E89B0DD2031636000A47992 /* SDLSeatControlDataSpec.m */; };
1E89B0E2203196B800A47992 /* SDLSeatControlCapabilitiesSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1E89B0E1203196B800A47992 /* SDLSeatControlCapabilitiesSpec.m */; };
1EAA470E2032BF1D000FE74B /* SDLOnRCStatusSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 1EAA470D2032BF1D000FE74B /* SDLOnRCStatusSpec.m */; };
@@ -1787,6 +1789,9 @@
EEB2537E2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */; };
EED5CA041F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA031F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m */; };
EED5CA0A1F4D206800F04000 /* SDLRTPH264PacketizerSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EED5CA091F4D206800F04000 /* SDLRTPH264PacketizerSpec.m */; };
+ EF44EC2328A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */; };
+ EFEBDC9E287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m in Sources */ = {isa = PBXBuildFile; fileRef = EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */; };
+ EFEBDCA0287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h in Headers */ = {isa = PBXBuildFile; fileRef = EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */; };
/* End PBXBuildFile section */
/* Begin PBXContainerItemProxy section */
@@ -2100,6 +2105,9 @@
1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolMessageAssemblerSpec.m; sourceTree = "<group>"; };
1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolMessageDisassemblerSpec.m; sourceTree = "<group>"; };
1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolReceivedMessageRouterSpec.m; sourceTree = "<group>"; };
+ 1C87BFB028981328006E79F1 /* RemoteControlManager.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; name = RemoteControlManager.h; path = "Example Apps/Example ObjC/RemoteControlManager.h"; sourceTree = SOURCE_ROOT; };
+ 1C87BFB128981328006E79F1 /* RemoteControlManager.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = RemoteControlManager.m; path = "Example Apps/Example ObjC/RemoteControlManager.m"; sourceTree = SOURCE_ROOT; };
+ 1CFDAAA62893181200332B84 /* RemoteControlManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = RemoteControlManager.swift; path = "Example Apps/Example Swift/RemoteControlManager.swift"; sourceTree = SOURCE_ROOT; };
1E89B0DD2031636000A47992 /* SDLSeatControlDataSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSeatControlDataSpec.m; sourceTree = "<group>"; };
1E89B0E1203196B800A47992 /* SDLSeatControlCapabilitiesSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSeatControlCapabilitiesSpec.m; sourceTree = "<group>"; };
1EAA470D2032BF1D000FE74B /* SDLOnRCStatusSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLOnRCStatusSpec.m; sourceTree = "<group>"; };
@@ -3710,6 +3718,9 @@
EEB2537D2067D3E80069584E /* SDLSecondaryTransportManagerSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSecondaryTransportManagerSpec.m; path = ProxySpecs/SDLSecondaryTransportManagerSpec.m; sourceTree = "<group>"; };
EED5CA031F4D1D5E00F04000 /* SDLRAWH264PacketizerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLRAWH264PacketizerSpec.m; path = DevAPISpecs/SDLRAWH264PacketizerSpec.m; sourceTree = "<group>"; };
EED5CA091F4D206800F04000 /* SDLRTPH264PacketizerSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; name = SDLRTPH264PacketizerSpec.m; path = DevAPISpecs/SDLRTPH264PacketizerSpec.m; sourceTree = "<group>"; };
+ EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLProtocolReceivedMessageProcessorSpec.m; sourceTree = "<group>"; };
+ EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLProtocolReceivedMessageProcessor.m; path = private/SDLProtocolReceivedMessageProcessor.m; sourceTree = "<group>"; };
+ EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; name = SDLProtocolReceivedMessageProcessor.h; path = private/SDLProtocolReceivedMessageProcessor.h; sourceTree = "<group>"; };
/* End PBXFileReference section */
/* Begin PBXFrameworksBuildPhase section */
@@ -4298,6 +4309,7 @@
1680B10B1A9CD7AD00DBD79E /* SDLProtocolSpec.m */,
1680B1101A9CD7AD00DBD79E /* SDLProtocolMessageAssemblerSpec.m */,
1680B1111A9CD7AD00DBD79E /* SDLProtocolMessageDisassemblerSpec.m */,
+ EF44EC2228A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m */,
1680B1121A9CD7AD00DBD79E /* SDLProtocolReceivedMessageRouterSpec.m */,
);
path = ProtocolSpecs;
@@ -4789,7 +4801,7 @@
88295677207CF46C00EF056C /* Objective-C */,
);
name = Examples;
- path = SmartDeviceLink_Example;
+ path = "Example Apps";
sourceTree = "<group>";
};
5D4019B21A76EC350006B0C2 /* Supporting Files */ = {
@@ -4902,6 +4914,8 @@
4A40255B250026620080E159 /* SubscribeButtonManager.m */,
5D1FF29021304513000EB9B4 /* VehicleDataManager.h */,
5D1FF29921304514000EB9B4 /* VehicleDataManager.m */,
+ 1C87BFB028981328006E79F1 /* RemoteControlManager.h */,
+ 1C87BFB128981328006E79F1 /* RemoteControlManager.m */,
);
name = SDL;
sourceTree = "<group>";
@@ -4923,6 +4937,8 @@
4A8BD36924F94636000945E3 /* SDLProtocolMessageAssembler.m */,
4A8BD36724F94636000945E3 /* SDLProtocolMessageDisassembler.h */,
4A8BD36824F94636000945E3 /* SDLProtocolMessageDisassembler.m */,
+ EFEBDC9F287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h */,
+ EFEBDC9D287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m */,
4A8BD37124F9465E000945E3 /* SDLProtocolReceivedMessageRouter.h */,
4A8BD37024F9465E000945E3 /* SDLProtocolReceivedMessageRouter.m */,
);
@@ -6866,6 +6882,7 @@
5D1FF2D521304746000EB9B4 /* RPCPermissionsManager.swift */,
4A402558250026430080E159 /* SubscribeButtonManager.swift */,
5D1FF2D921304746000EB9B4 /* VehicleDataManager.swift */,
+ 1CFDAAA62893181200332B84 /* RemoteControlManager.swift */,
);
name = SDL;
sourceTree = "<group>";
@@ -7541,6 +7558,7 @@
B3A9DB2025D4BBB800CDFD21 /* SDLVideoStreamingCapability+StreamingVideoExtensions.h in Headers */,
4ABB257724F7E5E80061BF55 /* SDLDeleteChoicesOperation.h in Headers */,
4ABB24C124F592620061BF55 /* NSMapTable+Subscripting.h in Headers */,
+ EFEBDCA0287F1DD500E035C2 /* SDLProtocolReceivedMessageProcessor.h in Headers */,
4ABB2A2C24F847980061BF55 /* SDLChangeRegistrationResponse.h in Headers */,
4ABB264024F7F45B0061BF55 /* SDLSystemCapabilityObserver.h in Headers */,
4ABB27E524F800CA0061BF55 /* SDLPrimaryAudioSource.h in Headers */,
@@ -8100,6 +8118,7 @@
5D1FF2A221304515000EB9B4 /* ButtonManager.m in Sources */,
5D1FF2A021304515000EB9B4 /* AudioManager.m in Sources */,
5D1FF29E21304515000EB9B4 /* MenuManager.m in Sources */,
+ 1C87BFB228981328006E79F1 /* RemoteControlManager.m in Sources */,
4ADBD1FB26FCEDFC00ABB045 /* ConnectionTabBarController.m in Sources */,
5D1FF2C3213045EB000EB9B4 /* AppConstants.m in Sources */,
5D1FF2B821304581000EB9B4 /* ConnectionIAPTableViewController.m in Sources */,
@@ -8632,6 +8651,7 @@
5D9FDA911F2A7D3400A495C8 /* bson_object.c in Sources */,
4A8BD26024F933C7000945E3 /* SDLNavigationCapability.m in Sources */,
4ABB29E024F846880061BF55 /* SDLSubscribeWayPoints.m in Sources */,
+ EFEBDC9E287F103C00E035C2 /* SDLProtocolReceivedMessageProcessor.m in Sources */,
4ABB2B5824F84EF50061BF55 /* SDLButtonCapabilities.m in Sources */,
4A8BD32C24F9431B000945E3 /* SDLV2ProtocolHeader.m in Sources */,
4ABB2B4724F84EF50061BF55 /* SDLBeltStatus.m in Sources */,
@@ -8893,6 +8913,7 @@
1680B1141A9CD7AD00DBD79E /* SDLV1ProtocolHeaderSpec.m in Sources */,
B3DF19F3251225AA0090D7BA /* TestSmartConnection.m in Sources */,
B3838A15257C4EFD00420C11 /* SDLRoofStatusSpec.m in Sources */,
+ EF44EC2328A2F37E007BAA2B /* SDLProtocolReceivedMessageProcessorSpec.m in Sources */,
880D2680220E038800B3F496 /* SDLWeatherServiceManifestSpec.m in Sources */,
88EEC5BE220A3B8B005AA2F9 /* SDLPublishAppServiceResponseSpec.m in Sources */,
1680B1161A9CD7AD00DBD79E /* SDLProtocolMessageSpec.m in Sources */,
@@ -9249,6 +9270,7 @@
5D1FF2DF21304746000EB9B4 /* PerformInteractionManager.swift in Sources */,
5D1FF2ED2130479C000EB9B4 /* ConnectionIAPTableViewController.swift in Sources */,
5D1FF2DD21304746000EB9B4 /* RPCPermissionsManager.swift in Sources */,
+ 1CFDAAA72893181200332B84 /* RemoteControlManager.swift in Sources */,
5D1FF2E721304761000EB9B4 /* TextValidator.swift in Sources */,
4A402559250026430080E159 /* SubscribeButtonManager.swift in Sources */,
5D1FF2F8213047C1000EB9B4 /* AppDelegate.swift in Sources */,
@@ -9453,7 +9475,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9475,7 +9497,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9528,7 +9550,7 @@
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.smartdevicelink;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9585,7 +9607,7 @@
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.smartdevicelink;
PRODUCT_NAME = "$(TARGET_NAME)";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9695,7 +9717,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example Swift";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9721,7 +9743,7 @@
"$(inherited)",
"@executable_path/Frameworks",
);
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SDLTestApp;
PRODUCT_NAME = "SDL Example Swift";
PROVISIONING_PROFILE_SPECIFIER = "";
@@ -9772,7 +9794,7 @@
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SmartDeviceLinkSwift;
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
@@ -9827,7 +9849,7 @@
"@loader_path/Frameworks",
);
LIBRARY_SEARCH_PATHS = "$(inherited)";
- MARKETING_VERSION = 7.5.0;
+ MARKETING_VERSION = 7.6.0;
PRODUCT_BUNDLE_IDENTIFIER = com.smartdevicelink.SmartDeviceLinkSwift;
PRODUCT_NAME = "$(TARGET_NAME)";
RUN_CLANG_STATIC_ANALYZER = YES;
diff --git a/SmartDeviceLink-iOS.xcodeproj/xcshareddata/xcschemes/SmartDeviceLinkTests.xcscheme b/SmartDeviceLink-iOS.xcodeproj/xcshareddata/xcschemes/SmartDeviceLinkTests.xcscheme
new file mode 100644
index 000000000..2a19af5b0
--- /dev/null
+++ b/SmartDeviceLink-iOS.xcodeproj/xcshareddata/xcschemes/SmartDeviceLinkTests.xcscheme
@@ -0,0 +1,52 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<Scheme
+ LastUpgradeVersion = "1340"
+ version = "1.3">
+ <BuildAction
+ parallelizeBuildables = "YES"
+ buildImplicitDependencies = "YES">
+ </BuildAction>
+ <TestAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ shouldUseLaunchSchemeArgsEnv = "YES">
+ <Testables>
+ <TestableReference
+ skipped = "NO">
+ <BuildableReference
+ BuildableIdentifier = "primary"
+ BlueprintIdentifier = "5D61FA251A84237100846EE7"
+ BuildableName = "SmartDeviceLinkTests.xctest"
+ BlueprintName = "SmartDeviceLinkTests"
+ ReferencedContainer = "container:SmartDeviceLink-iOS.xcodeproj">
+ </BuildableReference>
+ </TestableReference>
+ </Testables>
+ </TestAction>
+ <LaunchAction
+ buildConfiguration = "Debug"
+ selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
+ selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
+ launchStyle = "0"
+ useCustomWorkingDirectory = "NO"
+ ignoresPersistentStateOnLaunch = "NO"
+ debugDocumentVersioning = "YES"
+ debugServiceExtension = "internal"
+ allowLocationSimulation = "YES">
+ </LaunchAction>
+ <ProfileAction
+ buildConfiguration = "Release"
+ shouldUseLaunchSchemeArgsEnv = "YES"
+ savedToolIdentifier = ""
+ useCustomWorkingDirectory = "NO"
+ debugDocumentVersioning = "YES">
+ </ProfileAction>
+ <AnalyzeAction
+ buildConfiguration = "Debug">
+ </AnalyzeAction>
+ <ArchiveAction
+ buildConfiguration = "Release"
+ revealArchiveInOrganizer = "YES">
+ </ArchiveAction>
+</Scheme>
diff --git a/SmartDeviceLink.podspec b/SmartDeviceLink.podspec
index b691129f4..98ee685a4 100644
--- a/SmartDeviceLink.podspec
+++ b/SmartDeviceLink.podspec
@@ -1,7 +1,7 @@
Pod::Spec.new do |s|
s.name = "SmartDeviceLink"
-s.version = "7.5.0"
+s.version = "7.6.0"
s.summary = "Connect your app with cars!"
s.homepage = "https://github.com/smartdevicelink/SmartDeviceLink-iOS"
s.license = { :type => "New BSD", :file => "LICENSE" }
diff --git a/SmartDeviceLink/private/SDLIAPDataSession.m b/SmartDeviceLink/private/SDLIAPDataSession.m
index 06d2e1f1d..be8b8cfbb 100644
--- a/SmartDeviceLink/private/SDLIAPDataSession.m
+++ b/SmartDeviceLink/private/SDLIAPDataSession.m
@@ -58,10 +58,14 @@ NS_ASSUME_NONNULL_BEGIN
if (bytesWritten >= 0) {
if (bytesWritten == bytesRemaining) {
[self.sendDataQueue popBuffer];
- } else {
+ } else if (bytesRemaining > bytesWritten) {
// Cleave the sent bytes from the data, the remainder will sit at the head of the queue
SDLLogV(@"SDLIAPDataSession writeDataToSessionStream bytes written %ld", (long)bytesWritten);
[remainder replaceBytesInRange:NSMakeRange(0, (NSUInteger)bytesWritten) withBytes:NULL length:0];
+ } else {
+ // Error processing current data. Remove corrupted buffer
+ SDLLogE(@"Unable to remove sent bytes. Bytes remaining is less than bytes written %lu < %lu. Clearing buffer", bytesRemaining, bytesWritten);
+ [self.sendDataQueue popBuffer];
}
} else {
// The write operation failed but there is no further information about the error. This can occur when disconnecting from an external accessory.
diff --git a/SmartDeviceLink/private/SDLMenuManager.m b/SmartDeviceLink/private/SDLMenuManager.m
index ae422345f..264bf94c1 100644
--- a/SmartDeviceLink/private/SDLMenuManager.m
+++ b/SmartDeviceLink/private/SDLMenuManager.m
@@ -227,6 +227,16 @@ NS_ASSUME_NONNULL_BEGIN
return NO;
}
+ // Check if a passed cell is a "re-created" cell without a cellID. If it is, then try to find the equivalent cell and use it instead
+ if (cell != nil && cell.cellId == UINT32_MAX) {
+ for (SDLMenuCell *headUnitCell in self.menuCells) {
+ if ([cell isEqual:headUnitCell]) {
+ cell = headUnitCell;
+ break;
+ }
+ }
+ }
+
// Create the operation
SDLMenuShowOperation *showMenuOp = [[SDLMenuShowOperation alloc] initWithConnectionManager:self.connectionManager toMenuCell:cell completionHandler:^(NSError * _Nullable error) {
if (error != nil) {
diff --git a/SmartDeviceLink/private/SDLPresentAlertOperation.m b/SmartDeviceLink/private/SDLPresentAlertOperation.m
index 9359106f5..28ecec877 100644
--- a/SmartDeviceLink/private/SDLPresentAlertOperation.m
+++ b/SmartDeviceLink/private/SDLPresentAlertOperation.m
@@ -66,6 +66,7 @@ static const int SDLAlertSoftButtonCount = 4;
@property (assign, nonatomic) UInt16 cancelId;
@property (copy, nonatomic, nullable) NSError *internalError;
@property (assign, atomic) BOOL isAlertPresented;
+@property (assign, nonatomic) BOOL alertIconUploaded;
@end
@@ -90,6 +91,7 @@ static const int SDLAlertSoftButtonCount = 4;
_cancelId = cancelID;
_operationId = [NSUUID UUID];
_currentWindowCapability = currentWindowCapability;
+ _alertIconUploaded = NO;
return self;
}
@@ -193,8 +195,14 @@ static const int SDLAlertSoftButtonCount = 4;
/// @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];
+ if ([self sdl_supportsAlertIcon] && (self.alertView.icon != nil)) {
+ if ([self.fileManager fileNeedsUpload:self.alertView.icon]) {
+ // If the file is not uploaded, attempt to upload it
+ [artworksToBeUploaded addObject:self.alertView.icon];
+ } else if ([self.fileManager hasUploadedFile:self.alertView.icon] || self.alertView.icon.isStaticIcon) {
+ // If the file is already uploaded, add it to the uploaded set so we can show it
+ self.alertIconUploaded = YES;
+ }
}
// Don't upload artworks for buttons that will not be shown.
@@ -219,12 +227,16 @@ static const int SDLAlertSoftButtonCount = 4;
return YES;
} completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ __strong typeof(weakself) strongself = weakself;
if (error != nil) {
SDLLogE(@"Error uploading alert images: %@", error);
} else {
SDLLogD(@"All alert images uploaded");
}
+ if ([artworkNames containsObject:strongself.alertView.icon.name]) {
+ strongself.alertIconUploaded = YES;
+ }
return handler();
}];
}
@@ -293,7 +305,7 @@ static const int SDLAlertSoftButtonCount = 4;
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.alertIcon = self.alertIconUploaded ? self.alertView.icon.imageRPC : nil;
alert.progressIndicator = @(self.alertView.showWaitIndicator);
alert.cancelID = @(self.cancelId);
diff --git a/SmartDeviceLink/private/SDLProtocol.m b/SmartDeviceLink/private/SDLProtocol.m
index 7ea872f18..676243add 100644
--- a/SmartDeviceLink/private/SDLProtocol.m
+++ b/SmartDeviceLink/private/SDLProtocol.m
@@ -22,6 +22,7 @@
#import "SDLProtocolHeader.h"
#import "SDLProtocolMessage.h"
#import "SDLProtocolMessageDisassembler.h"
+#import "SDLProtocolReceivedMessageProcessor.h"
#import "SDLProtocolReceivedMessageRouter.h"
#import "SDLRPCNotification.h"
#import "SDLRPCPayload.h"
@@ -50,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN
SDLPrioritizedObjectCollection *_prioritizedCollection;
}
+@property (nullable, strong, nonatomic) SDLProtocolReceivedMessageProcessor *receiveProcessor;
@property (strong, nonatomic) NSMutableData *receiveBuffer;
@property (nullable, strong, nonatomic) SDLProtocolReceivedMessageRouter *messageRouter;
@property (strong, nonatomic) NSMutableDictionary<SDLServiceTypeBox *, SDLProtocolHeader *> *serviceHeaders;
@@ -78,12 +80,11 @@ NS_ASSUME_NONNULL_BEGIN
_prioritizedCollection = [[SDLPrioritizedObjectCollection alloc] init];
_protocolDelegateTable = [NSHashTable weakObjectsHashTable];
_serviceHeaders = [[NSMutableDictionary alloc] init];
+ _receiveProcessor = [[SDLProtocolReceivedMessageProcessor alloc] init];
_messageRouter = [[SDLProtocolReceivedMessageRouter alloc] init];
_messageRouter.delegate = self;
-
_transport = transport;
_transport.delegate = self;
-
_encryptionLifecycleManager = encryptionManager;
return self;
@@ -497,66 +498,22 @@ NS_ASSUME_NONNULL_BEGIN
// Turn received bytes into message objects.
- (void)sdl_handleBytesFromTransport:(NSData *)receivedData {
- // Initialize the receive buffer which will contain bytes while messages are constructed.
- if (self.receiveBuffer == nil) {
- self.receiveBuffer = [NSMutableData dataWithCapacity:(4 * [[SDLGlobals sharedGlobals] mtuSizeForServiceType:SDLServiceTypeRPC])];
- }
-
- // Save the data
- [self.receiveBuffer appendData:receivedData];
-
- [self sdl_processMessages];
-}
-
-- (void)sdl_processMessages {
- UInt8 incomingVersion = [SDLProtocolHeader determineVersion:self.receiveBuffer];
-
- // If we have enough bytes, create the header.
- SDLProtocolHeader *header = [SDLProtocolHeader headerForVersion:incomingVersion];
- NSUInteger headerSize = header.size;
- if (self.receiveBuffer.length >= headerSize) {
- [header parse:self.receiveBuffer];
- } else {
- return;
- }
-
- // If we have enough bytes, finish building the message.
- SDLProtocolMessage *message = nil;
- NSUInteger payloadSize = header.bytesInPayload;
- NSUInteger messageSize = headerSize + payloadSize;
- if (self.receiveBuffer.length >= messageSize) {
- NSUInteger payloadOffset = headerSize;
- NSUInteger payloadLength = payloadSize;
- NSData *payload = [self.receiveBuffer subdataWithRange:NSMakeRange(payloadOffset, payloadLength)];
-
+ // Call the state machine, and pass it the bytes to be processed
+ [_receiveProcessor processReceiveBuffer:receivedData withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
// If the message in encrypted and there is payload, try to decrypt it
- if (header.encrypted && payload.length) {
- NSError *decryptError = nil;
+ NSError *decryptError = nil;
+ if (header.encrypted && (payload.length > 0)) {
payload = [self.securityManager decryptData:payload withError:&decryptError];
-
- if (decryptError != nil) {
- SDLLogE(@"Error attempting to decrypt a payload with error: %@", decryptError);
- return;
- }
}
- message = [SDLProtocolMessage messageWithHeader:header andPayload:payload];
- } else {
- // Need to wait for more bytes.
- SDLLogV(@"Protocol header complete, message incomplete, waiting for %ld more bytes. Header: %@", (long)(messageSize - self.receiveBuffer.length), header);
- return;
- }
-
- // Need to maintain the receiveBuffer, remove the bytes from it which we just processed.
- self.receiveBuffer = [[self.receiveBuffer subdataWithRange:NSMakeRange(messageSize, self.receiveBuffer.length - messageSize)] mutableCopy];
-
- // Pass on the message to the message router.
- [self.messageRouter handleReceivedMessage:message protocol:self];
-
- // Call recursively until the buffer is empty or incomplete message is encountered
- if (self.receiveBuffer.length > 0) {
- [self sdl_processMessages];
- }
+ if (decryptError != nil) {
+ return SDLLogE(@"Error attempting to decrypt a payload with error: %@", decryptError);
+ }
+
+ // Build the message and send it on to the router
+ SDLProtocolMessage *message = [SDLProtocolMessage messageWithHeader:header andPayload:payload];
+ [self.messageRouter handleReceivedMessage:message protocol:self];
+ }];
}
diff --git a/SmartDeviceLink/private/SDLProtocolHeader.m b/SmartDeviceLink/private/SDLProtocolHeader.m
index afe93724d..2eb4f16d2 100644
--- a/SmartDeviceLink/private/SDLProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLProtocolHeader.m
@@ -3,8 +3,10 @@
#import "SDLProtocolHeader.h"
+
#import "SDLV1ProtocolHeader.h"
#import "SDLV2ProtocolHeader.h"
+#import "SDLMacros.h"
NS_ASSUME_NONNULL_BEGIN
@@ -41,6 +43,17 @@ NS_ASSUME_NONNULL_BEGIN
return description;
}
+- (NSUInteger)hash {
+ return NSUIntRotateCell(self.version, NSUIntBitCell / 2)
+ ^ NSUIntRotateCell(self.size, NSUIntBitCell / 3)
+ ^ NSUIntRotateCell(self.encrypted, NSUIntBitCell / 4)
+ ^ NSUIntRotateCell(self.frameType, NSUIntBitCell / 5)
+ ^ NSUIntRotateCell(self.serviceType, NSUIntBitCell / 6)
+ ^ NSUIntRotateCell(self.frameData, NSUIntBitCell / 7)
+ ^ NSUIntRotateCell(self.sessionID, NSUIntBitCell / 8)
+ ^ NSUIntRotateCell(self.bytesInPayload, NSUIntBitCell / 9);
+}
+
+ (__kindof SDLProtocolHeader *)headerForVersion:(UInt8)version {
// VERSION DEPENDENT CODE
switch (version) {
diff --git a/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h
new file mode 100644
index 000000000..ac98ecac9
--- /dev/null
+++ b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.h
@@ -0,0 +1,31 @@
+//
+// SDLProtocolReceivedMessageProcessor.h
+// SmartDeviceLink
+//
+// Created by George Miller on 7/13/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+@class SDLProtocolHeader;
+
+/**
+ * Handles decryption and creation of the message from header and payload. Decryption needed to be handled outside of the MessageProcessor because of access to the securitymanager.
+ * @param header Pointer to the header for the message
+ * @param payload Pointer to the payload of the message
+ */
+typedef void (^StateMachineMessageReadyBlock)(SDLProtocolHeader *header, NSData *payload);
+
+/// Class for processing received byte data into protocol messages
+@interface SDLProtocolReceivedMessageProcessor : NSObject
+
+/**
+ * Processes a data buffer into the state machine.
+ * Loop through the given bytes and call the state machine to process each byte.
+ * @param receiveBuffer The data to process
+ * @param messageReadyBlock Passes back a completed protocol message when one has been assembled
+ */
+- (void)processReceiveBuffer:(NSData *)receiveBuffer withMessageReadyBlock:(StateMachineMessageReadyBlock)messageReadyBlock;
+
+@end
diff --git a/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m
new file mode 100644
index 000000000..10f27a789
--- /dev/null
+++ b/SmartDeviceLink/private/SDLProtocolReceivedMessageProcessor.m
@@ -0,0 +1,290 @@
+//
+// SDLProtocolProcessMessageByte.m
+// SmartDeviceLink
+//
+// Created by George Miller on 7/13/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import "SDLProtocolReceivedMessageProcessor.h"
+
+#import "SDLGlobals.h"
+#import "SDLLogMacros.h"
+#import "SDLProtocolHeader.h"
+
+
+typedef NS_ENUM(NSUInteger, ProcessorState) {
+ START_STATE = 0x0,
+ SERVICE_TYPE_STATE = 0x01,
+ CONTROL_FRAME_INFO_STATE = 0x02,
+ SESSION_ID_STATE = 0x03,
+ DATA_SIZE_1_STATE = 0x04,
+ DATA_SIZE_2_STATE = 0x05,
+ DATA_SIZE_3_STATE = 0x06,
+ DATA_SIZE_4_STATE = 0x07,
+ MESSAGE_1_STATE = 0x08,
+ MESSAGE_2_STATE = 0x09,
+ MESSAGE_3_STATE = 0x0A,
+ MESSAGE_4_STATE = 0x0B,
+ DATA_PUMP_STATE = 0x0C,
+ ERROR_STATE = -1,
+};
+
+@interface SDLProtocolReceivedMessageProcessor ()
+// State management
+@property (assign, nonatomic) ProcessorState state;
+
+// Message assembly state
+@property (strong, nonatomic) SDLProtocolHeader *header;
+@property (strong, nonatomic) NSMutableData *headerBuffer;
+@property (strong, nonatomic) NSMutableData *payloadBuffer;
+
+@property (assign, nonatomic) UInt8 version;
+@property (assign, nonatomic) BOOL encrypted;
+@property (assign, nonatomic) SDLFrameType frameType;
+@property (assign, nonatomic) UInt32 dataLength;
+@property (assign, nonatomic) UInt32 dataBytesRemaining;
+@property (assign, nonatomic) SDLServiceType serviceType;
+@end
+
+@implementation SDLProtocolReceivedMessageProcessor
+
+- (instancetype)init {
+ self = [super init];
+ if (!self) { return nil; }
+
+ [self resetState];
+
+ return self;
+}
+
+- (void)resetState {
+ // Flush Buffers
+ _headerBuffer = [NSMutableData data];
+ _payloadBuffer = [NSMutableData data];
+ _version = 0;
+ _encrypted = NO;
+ _frameType = 0x00;
+ _dataLength = 0;
+ _dataBytesRemaining = 0;
+ _serviceType = 0x00;
+
+ // Reset state
+ _state = START_STATE;
+}
+
+
+- (void)processReceiveBuffer:(NSData *)receiveBuffer withMessageReadyBlock:(StateMachineMessageReadyBlock)messageReadyBlock {
+ const BytePtr bytes = (BytePtr)receiveBuffer.bytes;
+ BOOL messageIsComplete = NO;
+ for (int i = 0; i < receiveBuffer.length; i++) {
+ // If we have reached the end of a message, we need to immediately call the message ready block with the completed data, then reset the buffers and keep pumping data into the state machine
+ messageIsComplete = [self sdl_processByte:(Byte)bytes[i]];
+ if (messageIsComplete) {
+ messageReadyBlock(self.header, [self.payloadBuffer copy]);
+ [self resetState];
+ }
+
+ // If we end up in error state, trigger the reset so we can properly handle the next byte
+ if (_state == ERROR_STATE) {
+ [self resetState];
+ }
+ }
+}
+
+/// This is the state machine. It processes a single byte of a message, checks for errors, and builds up a header buffer and a payload buffer.
+/// When the header and payload are complete, this returns true to notify the calling funciton.
+/// For reference: https://smartdevicelink.com/en/guides/sdl-overview-guides/protocol-spec/
+/// If a byte comes in that does not conform to spec, the buffers are flushed and state is reset.
+/// @param currentByte The byte currently being processed
+/// @return YES if the byte processed is the last byte of a message, else NO
+- (BOOL)sdl_processByte:(Byte)currentByte {
+ BOOL messageHasEnded = NO;
+
+ switch (self.state) {
+ case START_STATE:
+ [self resetState];
+
+ // bits 0-3 for version. (b1111 0000)
+ self.version = (currentByte & 0xF0 ) >> 4;
+
+ // bit 4 for either encryption or compression, depending on version. (b0000 1000)
+ self.encrypted = ((currentByte & 0x08 ) >> 3 == 1);
+
+ // bits 5-7 for frameType. (b0000 0111)
+ self.frameType = (currentByte & 0x07) >> 0;
+
+ self.state = SERVICE_TYPE_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ if ((self.version < 1) || (self.version > 5)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message version is out of spec: %hhu, skipping message", self.version);
+ break;
+ }
+
+ if ((self.frameType < SDLFrameTypeControl) || (self.frameType > SDLFrameTypeConsecutive)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message frameType is out of spec: %hhu, skipping message", self.frameType);
+ }
+ break;
+
+ case SERVICE_TYPE_STATE: {
+ _serviceType = 0x00;
+ // 8 bits for service type
+ _serviceType = currentByte;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // ServiceType must be one of the defined types, else error.
+ switch (_serviceType) {
+ case SDLServiceTypeControl:
+ case SDLServiceTypeRPC:
+ case SDLServiceTypeAudio:
+ case SDLServiceTypeVideo:
+ case SDLServiceTypeBulkData:
+ self.state = CONTROL_FRAME_INFO_STATE;
+ break;
+ default:
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message serviceType is out of spec: %u, skipping message", _serviceType);
+ break;
+ }
+ }
+ break;
+
+ case CONTROL_FRAME_INFO_STATE: {
+ SDLFrameInfo controlFrameInfo = 0x00;
+ // 8 bits for frame information
+ controlFrameInfo = currentByte;
+ self.state = SESSION_ID_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // Check for errors. For these two frame types, the frame info should be 0x00
+ if (((self.frameType == SDLFrameTypeFirst) || (self.frameType == SDLFrameTypeSingle)) && (controlFrameInfo != 0x00)){
+ self.state = ERROR_STATE;
+ SDLLogE(@"Message frameType is out of spec. FrameType: %hhu, frameInfo: %hhu, skipping message", self.frameType, controlFrameInfo);
+ }
+ }
+ break;
+
+ case SESSION_ID_STATE:
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ self.state = DATA_SIZE_1_STATE;
+ break;
+
+ // 32 bits for data size
+ case DATA_SIZE_1_STATE:
+ self.dataLength = 0;
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 24;
+ self.state = DATA_SIZE_2_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_2_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 16;
+ self.state = DATA_SIZE_3_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_3_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 8;
+ self.state = DATA_SIZE_4_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case DATA_SIZE_4_STATE:
+ self.dataLength += (UInt32)(currentByte & 0xFF) << 0;
+ self.state = MESSAGE_1_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+
+ // Set the counter for the data pump.
+ self.dataBytesRemaining = self.dataLength;
+
+ // Version 1 does not have a message ID so we skip to the data pump or the end.
+ if (self.version == 1) {
+ if (self.dataLength == 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ } else {
+ self.state = DATA_PUMP_STATE;
+ }
+ }
+
+ Byte headerSize = 0;
+ if (self.version == 1) {
+ headerSize = 8;
+ } else {
+ headerSize = 12;
+ }
+
+ NSUInteger maxMtuSize = [[SDLGlobals sharedGlobals] mtuSizeForServiceType:_serviceType];
+
+ // Error if the data length is greater than the MTU size for this version
+ if (self.dataLength >= (maxMtuSize - headerSize)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Data length %u exceeds maximum MTU size %lu, skipping message", self.dataLength, (unsigned long)maxMtuSize);
+ break;
+ }
+
+ // If this is the first frame, it is not encrypted, and the length is not 8 then error.
+ if ((self.frameType == SDLFrameTypeFirst) && (self.dataLength != 8) && (self.encrypted == NO)) {
+ self.state = ERROR_STATE;
+ SDLLogE(@"Data length may not exceed 8 (%u) for non encrypted first frame, skipping message", self.dataLength);
+ break;
+ }
+ break;
+
+ // 32 bits for data size (version 2+)
+ case MESSAGE_1_STATE:
+ self.state = MESSAGE_2_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_2_STATE:
+ self.state = MESSAGE_3_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_3_STATE:
+ self.state = MESSAGE_4_STATE;
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ break;
+
+ case MESSAGE_4_STATE:
+ [self.headerBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ // If there is no payload, we are done.
+ if (self.dataLength == 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ break;
+ }
+
+ self.state = DATA_PUMP_STATE;
+ break;
+
+ case DATA_PUMP_STATE:
+ // The pump state takes bytes in and adds them to the payload array
+ // Note that we do not set state here. If we are pumping, state will not change. If we are done pumping, we return the messageHasEnded and state will be reset externally.
+ [self.payloadBuffer appendBytes:&currentByte length:sizeof(currentByte)];
+ self.dataBytesRemaining--;
+
+ // If all the bytes have been read, then parse the header into an object and return the end of message
+ if (self.dataBytesRemaining <= 0) {
+ self.header = [SDLProtocolHeader headerForVersion:self.version];
+ [self.header parse:self.headerBuffer];
+ messageHasEnded = YES;
+ }
+ break;
+
+ case ERROR_STATE:
+ default:
+ [self resetState];
+ break;
+ }
+
+ return messageHasEnded;
+}
+
+@end
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicManager.m b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
index 34c4dcf54..23a7a3df9 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicManager.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicManager.m
@@ -157,7 +157,7 @@ NS_ASSUME_NONNULL_BEGIN
}
} else if (self.isDirty) {
self.isDirty = NO;
- [self sdl_updateAndCancelPreviousOperations:YES completionHandler:handler];
+ [self sdl_createOperationWithCompletionHandler:handler];
} else {
if (handler != nil) {
handler([NSError sdl_textAndGraphicManager_nothingToUpdate]);
@@ -165,12 +165,8 @@ NS_ASSUME_NONNULL_BEGIN
}
}
-- (void)sdl_updateAndCancelPreviousOperations:(BOOL)supersedePreviousOperations completionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler {
+- (void)sdl_createOperationWithCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)handler {
SDLLogD(@"Updating text and graphics");
- if (self.transactionQueue.operationCount > 0 && supersedePreviousOperations) {
- SDLLogV(@"Transactions already exist, cancelling them");
- [self.transactionQueue cancelAllOperations];
- }
__weak typeof(self) weakSelf = self;
SDLTextAndGraphicUpdateOperation *updateOperation = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager currentCapabilities:self.windowCapability currentScreenData:self.currentScreenData newState:[self currentState] currentScreenDataUpdatedHandler:^(SDLTextAndGraphicState *_Nullable newScreenData, NSError *_Nullable error) {
@@ -182,7 +178,10 @@ NS_ASSUME_NONNULL_BEGIN
} else if (error != nil) {
// Invalidate data that's different from our current screen data if a Show or SetDisplayLayout fails. This will prevent subsequent `Show`s from failing if the request failed due to the developer setting invalid data or subsequent `SetDisplayLayout`s from failing if the template is not supported on the module.
[strongSelf sdl_resetFieldsToCurrentScreenData];
- [strongSelf sdl_updatePendingOperationsWithNewScreenData:strongSelf.currentScreenData];
+ SDLTextAndGraphicState *errorState = error.userInfo[SDLTextAndGraphicFailedScreenStateErrorKey];
+ if (errorState != nil) {
+ [strongSelf sdl_updatePendingOperationsWithFailedScreenState:errorState];
+ }
}
} updateCompletionHandler:handler];
@@ -204,6 +203,15 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+- (void)sdl_updatePendingOperationsWithFailedScreenState:(SDLTextAndGraphicState *)errorState {
+ for (NSOperation *operation in self.transactionQueue.operations) {
+ if (operation.isExecuting) { continue; }
+ SDLTextAndGraphicUpdateOperation *updateOp = (SDLTextAndGraphicUpdateOperation *)operation;
+
+ [updateOp updateTargetStateWithErrorState:errorState];
+ }
+}
+
- (void)sdl_resetFieldsToCurrentScreenData {
_textField1 = _currentScreenData.textField1;
_textField2 = _currentScreenData.textField2;
@@ -394,7 +402,7 @@ NS_ASSUME_NONNULL_BEGIN
// Auto-send an updated show
if ([self sdl_hasData]) {
// TODO: HAX: Capability updates cannot supersede earlier updates because of the case where a developer batched a `changeLayout` call w/ T&G changes on <6.0 systems could cause this to come in before the operation completes. That would cause the operation to report a "failure" (because it was superseded by this call) when in fact the operation didn't fail at all and is just being adjusted. Even though iOS is able to tell the developer that it was superseded, Java Suite cannot, and therefore we are matching functionality with their library.
- [self sdl_updateAndCancelPreviousOperations:NO completionHandler:nil];
+ [self sdl_createOperationWithCompletionHandler:nil];
}
}
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
index d3f88b667..e06306937 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
+++ b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.h
@@ -21,6 +21,8 @@
NS_ASSUME_NONNULL_BEGIN
+extern NSString *const SDLTextAndGraphicFailedScreenStateErrorKey;
+
typedef void(^SDLTextAndGraphicUpdateCompletionHandler)(NSError *__nullable error);
typedef void(^CurrentDataUpdatedHandler)(SDLTextAndGraphicState *__nullable newScreenData, NSError *__nullable error);
@@ -38,6 +40,10 @@ typedef void(^CurrentDataUpdatedHandler)(SDLTextAndGraphicState *__nullable newS
/// @param updateCompletionHandler The handler potentially passed by the developer to be called when the update finishes
- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager currentCapabilities:(SDLWindowCapability *)currentCapabilities currentScreenData:(SDLTextAndGraphicState *)currentData newState:(SDLTextAndGraphicState *)newState currentScreenDataUpdatedHandler:(CurrentDataUpdatedHandler)currentDataUpdatedHandler updateCompletionHandler:(nullable SDLTextAndGraphicUpdateCompletionHandler)updateCompletionHandler;
+/// Changes updated state to remove failed updates from previous update operations. This will find and revert those failed updates back to current screen data so that we don't duplicate the failure.
+/// @param errorState A updated state that failed in a previous operation that will be used to filter out erroneous data
+- (void)updateTargetStateWithErrorState:(SDLTextAndGraphicState *)errorState;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
index cd47531e2..246e1a014 100644
--- a/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
+++ b/SmartDeviceLink/private/SDLTextAndGraphicUpdateOperation.m
@@ -26,6 +26,8 @@
NS_ASSUME_NONNULL_BEGIN
+NSString *const SDLTextAndGraphicFailedScreenStateErrorKey = @"failedScreenState";
+
@interface SDLTextAndGraphicUpdateOperation ()
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@@ -97,6 +99,23 @@ NS_ASSUME_NONNULL_BEGIN
}
}
+- (void)updateTargetStateWithErrorState:(SDLTextAndGraphicState *)errorState {
+ self.updatedState.textField1 = [errorState.textField1 isEqualToString:self.updatedState.textField1] ? self.currentScreenData.textField1 : self.updatedState.textField1;
+ self.updatedState.textField2 = [errorState.textField2 isEqualToString:self.updatedState.textField2] ? self.currentScreenData.textField2 : self.updatedState.textField2;
+ self.updatedState.textField3 = [errorState.textField3 isEqualToString:self.updatedState.textField3] ? self.currentScreenData.textField3 : self.updatedState.textField3;
+ self.updatedState.textField4 = [errorState.textField4 isEqualToString:self.updatedState.textField4] ? self.currentScreenData.textField4 : self.updatedState.textField4;
+ self.updatedState.mediaTrackTextField = [errorState.mediaTrackTextField isEqualToString:self.updatedState.mediaTrackTextField] ? self.currentScreenData.mediaTrackTextField : self.updatedState.mediaTrackTextField;
+ self.updatedState.title = [errorState.title isEqualToString:self.updatedState.title] ? self.currentScreenData.title : self.updatedState.title;
+ self.updatedState.primaryGraphic = [errorState.primaryGraphic isEqual:self.updatedState.primaryGraphic] ? self.currentScreenData.primaryGraphic : self.updatedState.primaryGraphic;
+ self.updatedState.secondaryGraphic = [errorState.secondaryGraphic isEqual:self.updatedState.secondaryGraphic] ? self.currentScreenData.secondaryGraphic : self.updatedState.secondaryGraphic;
+ self.updatedState.alignment = [errorState.alignment isEqualToEnum:self.updatedState.alignment] ? self.currentScreenData.alignment : self.updatedState.alignment;
+ self.updatedState.textField1Type = [errorState.textField1Type isEqualToEnum:self.updatedState.textField1Type] ? self.currentScreenData.textField1Type : self.updatedState.textField1Type;
+ self.updatedState.textField2Type = [errorState.textField2Type isEqualToEnum:self.updatedState.textField2Type] ? self.currentScreenData.textField2Type : self.updatedState.textField2Type;
+ self.updatedState.textField3Type = [errorState.textField3Type isEqualToEnum:self.updatedState.textField3Type] ? self.currentScreenData.textField3Type : self.updatedState.textField3Type;
+ self.updatedState.textField4Type = [errorState.textField4Type isEqualToEnum:self.updatedState.textField4Type] ? self.currentScreenData.textField4Type : self.updatedState.textField4Type;
+ self.updatedState.templateConfig = [errorState.templateConfig isEqual:self.updatedState.templateConfig] ? self.currentScreenData.templateConfig : self.updatedState.templateConfig;
+}
+
#pragma mark - Send Show / Set Display Layout
- (void)sdl_updateGraphicsAndShow:(SDLShow *)show {
@@ -158,8 +177,12 @@ NS_ASSUME_NONNULL_BEGIN
SDLLogD(@"Text and Graphic Show completed successfully");
[strongSelf sdl_updateCurrentScreenDataFromShow:request];
} else {
- SDLLogD(@"Text and Graphic Show failed");
- self.currentDataUpdatedHandler(nil, error);
+ SDLLogE(@"Text and Graphic Show failed: %@", error);
+ NSError *updateError = [NSError errorWithDomain:error.domain code:error.code userInfo:@{
+ NSUnderlyingErrorKey: error.userInfo,
+ SDLTextAndGraphicFailedScreenStateErrorKey: self.updatedState
+ }];
+ self.currentDataUpdatedHandler(nil, updateError);
}
handler(error);
diff --git a/SmartDeviceLink/private/SDLV1ProtocolHeader.m b/SmartDeviceLink/private/SDLV1ProtocolHeader.m
index ba6474481..cfbb6b1fc 100644
--- a/SmartDeviceLink/private/SDLV1ProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLV1ProtocolHeader.m
@@ -4,6 +4,8 @@
#import "SDLV1ProtocolHeader.h"
+#import "SDLMacros.h"
+
const int ProtocolV1HeaderByteSize = 8;
NS_ASSUME_NONNULL_BEGIN
@@ -82,6 +84,9 @@ NS_ASSUME_NONNULL_BEGIN
return description;
}
+- (BOOL)isEqual:(SDLV1ProtocolHeader *)object {
+ return (self.hash == object.hash);
+}
@end
diff --git a/SmartDeviceLink/private/SDLV2ProtocolHeader.m b/SmartDeviceLink/private/SDLV2ProtocolHeader.m
index 00567180c..d91e74926 100644
--- a/SmartDeviceLink/private/SDLV2ProtocolHeader.m
+++ b/SmartDeviceLink/private/SDLV2ProtocolHeader.m
@@ -4,6 +4,8 @@
#import "SDLV2ProtocolHeader.h"
+#import "SDLMacros.h"
+
const int ProtocolV2HeaderByteSize = 12;
@@ -123,4 +125,12 @@ const int ProtocolV2HeaderByteSize = 12;
return description;
}
+- (NSUInteger)hash {
+ return [super hash] ^ NSUIntRotateCell(self.messageID, NSUIntBitCell / 10);
+}
+
+- (BOOL)isEqual:(SDLV2ProtocolHeader *)object {
+ return (self.hash == object.hash);
+}
+
@end
diff --git a/SmartDeviceLink/public/SDLSystemCapabilityManager.m b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
index 849f8f2e8..4b1cf804b 100644
--- a/SmartDeviceLink/public/SDLSystemCapabilityManager.m
+++ b/SmartDeviceLink/public/SDLSystemCapabilityManager.m
@@ -157,6 +157,8 @@ typedef NSString * SDLServiceID;
NSUInteger currentWindowID = windowCapability.windowID != nil ? windowCapability.windowID.unsignedIntegerValue : SDLPredefinedWindowsDefaultWindow;
if (currentWindowID != windowID) { continue; }
+ // A nil windowID is assumed to be the DefaultWindow according to the spec, but that can be hard for developers to check, so set it explicitly.
+ windowCapability.windowID = @(currentWindowID);
return windowCapability;
}
@@ -255,8 +257,9 @@ typedef NSString * SDLServiceID;
convertedCapabilities.imageFields = [defaultMainWindowCapabilities.imageFields copy];
convertedCapabilities.templatesAvailable = [defaultMainWindowCapabilities.templatesAvailable copy];
convertedCapabilities.numCustomPresetsAvailable = [defaultMainWindowCapabilities.numCustomPresetsAvailable copy];
- convertedCapabilities.mediaClockFormats = @[]; // mandatory field but allows empty array
+ convertedCapabilities.mediaClockFormats = self.displayCapabilities.mediaClockFormats ?: @[];
convertedCapabilities.graphicSupported = @([defaultMainWindowCapabilities.imageTypeSupported containsObject:SDLImageTypeDynamic]);
+ convertedCapabilities.screenParams = self.displayCapabilities.screenParams;
self.displayCapabilities = convertedCapabilities;
self.buttonCapabilities = defaultMainWindowCapabilities.buttonCapabilities;
diff --git a/SmartDeviceLink/public/SDLTouchManagerDelegate.h b/SmartDeviceLink/public/SDLTouchManagerDelegate.h
index f6a85fd36..414557ed2 100644
--- a/SmartDeviceLink/public/SDLTouchManagerDelegate.h
+++ b/SmartDeviceLink/public/SDLTouchManagerDelegate.h
@@ -99,7 +99,7 @@ NS_ASSUME_NONNULL_BEGIN
@param point Center point of the pinch in the head unit's coordinate system
@param scale Scale relative to the distance between touch points
*/
-- (void)touchManager:(SDLTouchManager *)manager didReceivePinchInView:(UIView *_Nullable)view atCenterPoint:(CGPoint)point withScale:(CGFloat)scale;
+- (void)touchManager:(SDLTouchManager *)manager didReceivePinchInView:(UIView *_Nullable)view atCenterPoint:(CGPoint)point withScale:(CGFloat)scale __deprecated_msg("Use touchManager:didReceivePinchAtCenterPoint:withScale: instead");
/**
Pinch did end
diff --git a/SmartDeviceLink/public/SmartDeviceLink.h b/SmartDeviceLink/public/SmartDeviceLink.h
index 8882ae147..514b34df8 100644
--- a/SmartDeviceLink/public/SmartDeviceLink.h
+++ b/SmartDeviceLink/public/SmartDeviceLink.h
@@ -317,6 +317,7 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[];
#import "SDLButtonEventMode.h"
#import "SDLButtonName.h"
#import "SDLButtonPressMode.h"
+#import "SDLCapacityUnit.h"
#import "SDLCarModeStatus.h"
#import "SDLCharacterSet.h"
#import "SDLCompassDirection.h"
@@ -496,6 +497,7 @@ FOUNDATION_EXPORT const unsigned char SmartDeviceLinkVersionString[];
#import "SDLRPCFunctionNames.h"
#import "SDLNotificationConstants.h"
#import "SDLStreamingMediaManagerConstants.h"
+#import "SDLSystemInfo.h"
#import "SDLVersion.h"
#pragma mark Notifications
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
index c663205e2..1ee9d5afd 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLMenuManagerSpec.m
@@ -6,6 +6,7 @@
#import "SDLGlobals.h"
#import "SDLMenuManager.h"
+#import "SDLMenuShowOperation.h"
#import "SDLMenuReplaceOperation.h"
#import "TestConnectionManager.h"
@@ -17,6 +18,12 @@
@end
+@interface SDLMenuShowOperation()
+
+@property (strong, nonatomic, nullable) SDLMenuCell *submenuCell;
+
+@end
+
@interface SDLMenuManager()
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@@ -269,6 +276,22 @@ describe(@"menu manager", ^{
expect(canSendRPC).to(equal(YES));
});
+ // should queue an open menu operation for a copied submenu cell
+ it(@"should queue an open menu operation for a copied submenu cell and match the original cell id", ^ {
+ submenuCell.cellId = 1;
+ testManager.menuCells = @[submenuCell];
+
+ SDLMenuCell *copiedCell = [[SDLMenuCell alloc] initWithTitle:@"Test 3" secondaryText:nil tertiaryText:nil icon:nil secondaryArtwork:nil submenuLayout:nil subCells:@[textOnlyCell]];
+
+ BOOL canSendRPC = [testManager openMenu:copiedCell];
+ SDLMenuShowOperation *showOperation = (SDLMenuShowOperation *)testManager.transactionQueue.operations[1];
+
+ expect(showOperation.submenuCell.cellId).to(equal(submenuCell.cellId));
+ expect(showOperation.submenuCell.cellId).toNot(equal(copiedCell.cellId));
+ expect(testManager.transactionQueue.operationCount).to(equal(2));
+ expect(canSendRPC).to(equal(YES));
+ });
+
it(@"should cancel the first task if a second is queued", ^{
testManager.menuCells = @[submenuCell];
[testManager openMenu:nil];
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
index ea230248c..29e9a775f 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicManagerSpec.m
@@ -140,19 +140,23 @@ describe(@"text and graphic manager", ^{
});
});
- // when previous updates have bene cancelled
- context(@"when previous updates have bene cancelled", ^{
+ // without batching
+ context(@"without batching", ^{
beforeEach(^{
- testManager.textField1 = @"Hello";
-
- // This should cancel the first operation
- testManager.textField2 = @"Goodbye";
+ testManager.batchUpdates = NO;
+ testManager.textField1 = @"test1";
+ testManager.textField2 = @"test2";
+ testManager.textField3 = @"test3";
+ testManager.textField4 = @"test4";
});
- it(@"should properly queue the new update", ^{
+ it(@"should create individual operations and not be cancelled", ^{
expect(testManager.transactionQueue.isSuspended).to(beTrue());
- expect(testManager.transactionQueue.operationCount).to(equal(2));
- expect(testManager.transactionQueue.operations[0].cancelled).to(beTrue());
+ expect(testManager.transactionQueue.operationCount).to(equal(4));
+ expect(testManager.transactionQueue.operations[0].cancelled).to(beFalse());
+ expect(testManager.transactionQueue.operations[1].cancelled).to(beFalse());
+ expect(testManager.transactionQueue.operations[2].cancelled).to(beFalse());
+ expect(testManager.transactionQueue.operations[3].cancelled).to(beFalse());
});
});
@@ -450,6 +454,7 @@ describe(@"text and graphic manager", ^{
describe(@"when the operation updates the current screen data", ^{
__block SDLTextAndGraphicUpdateOperation *testOperation = nil;
__block SDLTextAndGraphicUpdateOperation *testOperation2 = nil;
+ __block SDLTextAndGraphicUpdateOperation *testOperation3 = nil;
beforeEach(^{
testManager.textField1 = @"test";
@@ -475,11 +480,26 @@ describe(@"text and graphic manager", ^{
beforeEach(^{
testManager.currentScreenData = [[SDLTextAndGraphicState alloc] init];
testManager.currentScreenData.textField1 = @"Test1";
- testOperation.currentDataUpdatedHandler(nil, [NSError errorWithDomain:@"any" code:1 userInfo:nil]);
+
+ // Create a "bad data" text field 1, then set it in the manager, which should create an operation (op 2)
+ SDLTextAndGraphicState *errorState = [[SDLTextAndGraphicState alloc] init];
+ errorState.textField1 = @"Bad Data";
+ testManager.textField1 = errorState.textField1;
+
+ // Create a "good data text field 4, which should create a second operation (op 3)
+ testManager.textField4 = @"Good Data";
+ testOperation3 = testManager.transactionQueue.operations[3];
+
+ // Simulate a failure of the first operation
+ NSDictionary *userInfo = @{
+ SDLTextAndGraphicFailedScreenStateErrorKey: errorState
+ };
+ testOperation.currentDataUpdatedHandler(nil, [NSError errorWithDomain:@"any" code:1 userInfo:userInfo]);
});
- it(@"should reset the manager's data", ^{
+ it(@"should reset the manager's data and update other operations updated state", ^{
expect(testManager.textField1).to(equal(testManager.currentScreenData.textField1));
+ expect(testOperation3.updatedState.textField1).to(equal(testManager.currentScreenData.textField1));
});
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
index b03252c25..a3f780ab0 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLTextAndGraphicUpdateOperationSpec.m
@@ -797,6 +797,116 @@ describe(@"the text and graphic operation", ^{
});
});
+ // updating with error state
+ describe(@"updating with error state", ^{
+ beforeEach(^{
+ updatedState = [[SDLTextAndGraphicState alloc] init];
+ updatedState.textField1 = field1String;
+ updatedState.textField2 = field2String;
+ updatedState.textField3 = field3String;
+ updatedState.textField4 = field4String;
+ updatedState.mediaTrackTextField = mediaTrackString;
+ updatedState.title = titleString;
+ updatedState.primaryGraphic = testArtwork;
+ updatedState.secondaryGraphic = testArtwork2;
+ updatedState.alignment = SDLTextAlignmentLeft;
+ updatedState.textField1Type = SDLMetadataTypeMediaTitle;
+ updatedState.textField2Type = SDLMetadataTypeMediaArtist;
+ updatedState.textField3Type = SDLMetadataTypeMediaAlbum;
+ updatedState.textField4Type = SDLMetadataTypeMediaYear;
+
+ emptyCurrentData = [[SDLTextAndGraphicState alloc] init];
+
+ testOp = [[SDLTextAndGraphicUpdateOperation alloc] initWithConnectionManager:testConnectionManager fileManager:mockFileManager currentCapabilities:windowCapability currentScreenData:emptyCurrentData newState:updatedState currentScreenDataUpdatedHandler:^(SDLTextAndGraphicState * _Nullable newScreenData, NSError * _Nullable error) {} updateCompletionHandler:nil];
+ [testOp start];
+ });
+
+ it(@"should reset to current screen data for equivalent properties in updated state and error state", ^{
+ // Create an error state that matches the updated state, which should reset the updated state
+ SDLTextAndGraphicState *errorState = [updatedState copy];
+ errorState.primaryGraphic = testArtwork;
+ errorState.secondaryGraphic = testArtwork2;
+
+ [testOp updateTargetStateWithErrorState:errorState];
+
+ expect(updatedState.textField1).to(beNil());
+ expect(updatedState.textField2).to(beNil());
+ expect(updatedState.textField3).to(beNil());
+ expect(updatedState.textField4).to(beNil());
+ expect(updatedState.mediaTrackTextField).to(beNil());
+ expect(updatedState.title).to(beNil());
+ expect(updatedState.primaryGraphic).to(beNil());
+ expect(updatedState.secondaryGraphic).to(beNil());
+ expect(updatedState.textField1Type).to(beNil());
+ expect(updatedState.textField2Type).to(beNil());
+ expect(updatedState.textField3Type).to(beNil());
+ expect(updatedState.textField4Type).to(beNil());
+ });
+
+ it(@"should not reset to current screen data for non equivalent properties in updated state and error state", ^{
+ // Save an original of the updatedState for confirming no changes later
+ SDLTextAndGraphicState *originalState = [updatedState copy];
+ originalState.primaryGraphic = testArtwork;
+ originalState.secondaryGraphic = testArtwork2;
+
+ // Create an error state that does not match the updated state, which should not reset the updated state
+ SDLTextAndGraphicState *errorState = [[SDLTextAndGraphicState alloc] init];
+ errorState.textField1 = @"Error Text";
+ errorState.textField2 = @"Error Text";
+ errorState.textField3 = @"Error Text";
+ errorState.textField4 = @"Error Text";
+ errorState.mediaTrackTextField = @"Error Text";
+ errorState.title = @"Error Text";
+ errorState.primaryGraphic = testArtwork2;
+ errorState.secondaryGraphic = testArtwork;
+ errorState.alignment = SDLTextAlignmentRight;
+ errorState.textField1Type = SDLMetadataTypeMediaYear;
+ errorState.textField2Type = SDLMetadataTypeMediaAlbum;
+ errorState.textField3Type = SDLMetadataTypeMediaArtist;
+ errorState.textField4Type = SDLMetadataTypeMediaTitle;
+
+ [testOp updateTargetStateWithErrorState:errorState];
+
+ expect(updatedState.textField1).to(equal(originalState.textField1));
+ expect(updatedState.textField2).to(equal(originalState.textField2));
+ expect(updatedState.textField3).to(equal(originalState.textField3));
+ expect(updatedState.textField4).to(equal(originalState.textField4));
+ expect(updatedState.mediaTrackTextField).to(equal(originalState.mediaTrackTextField));
+ expect(updatedState.title).to(equal(originalState.title));
+ expect(updatedState.primaryGraphic).to(equal(originalState.primaryGraphic));
+ expect(updatedState.secondaryGraphic).to(equal(originalState.secondaryGraphic));
+ expect(updatedState.textField1Type).to(equal(originalState.textField1Type));
+ expect(updatedState.textField2Type).to(equal(originalState.textField2Type));
+ expect(updatedState.textField3Type).to(equal(originalState.textField3Type));
+ expect(updatedState.textField4Type).to(equal(originalState.textField4Type));
+ });
+
+ it(@"should not reset to current screen data for nil error state", ^{
+ // Save an original of the updatedState for confirming no changes later
+ SDLTextAndGraphicState *originalState = [updatedState copy];
+ originalState.primaryGraphic = testArtwork;
+ originalState.secondaryGraphic = testArtwork2;
+
+ // Create an empty error state
+ SDLTextAndGraphicState *errorState = [[SDLTextAndGraphicState alloc] init];
+
+ [testOp updateTargetStateWithErrorState:errorState];
+
+ expect(updatedState.textField1).to(equal(originalState.textField1));
+ expect(updatedState.textField2).to(equal(originalState.textField2));
+ expect(updatedState.textField3).to(equal(originalState.textField3));
+ expect(updatedState.textField4).to(equal(originalState.textField4));
+ expect(updatedState.mediaTrackTextField).to(equal(originalState.mediaTrackTextField));
+ expect(updatedState.title).to(equal(originalState.title));
+ expect(updatedState.primaryGraphic).to(equal(originalState.primaryGraphic));
+ expect(updatedState.secondaryGraphic).to(equal(originalState.secondaryGraphic));
+ expect(updatedState.textField1Type).to(equal(originalState.textField1Type));
+ expect(updatedState.textField2Type).to(equal(originalState.textField2Type));
+ expect(updatedState.textField3Type).to(equal(originalState.textField3Type));
+ expect(updatedState.textField4Type).to(equal(originalState.textField4Type));
+ });
+ });
+
// updating image fields
describe(@"updating image fields", ^{
beforeEach(^{
@@ -933,6 +1043,8 @@ describe(@"the text and graphic operation", ^{
// Then it should return a failure and finish
expect(receivedState).to(beNil());
expect(receivedError).toNot(beNil());
+ expect(receivedError.userInfo[NSUnderlyingErrorKey]).toNot(beNil());
+ expect(receivedError.userInfo[SDLTextAndGraphicFailedScreenStateErrorKey]).to(equal(updatedState));
expect(completionError).toNot(beNil());
expect(testOp.isFinished).to(beTrue());
@@ -1295,6 +1407,8 @@ describe(@"the text and graphic operation", ^{
[testConnectionManager respondToLastRequestWithResponse:failShowResponse];
expect(receivedState).to(beNil());
expect(receivedError).toNot(beNil());
+ expect(receivedError.userInfo[NSUnderlyingErrorKey]).toNot(beNil());
+ expect(receivedError.userInfo[SDLTextAndGraphicFailedScreenStateErrorKey]).to(equal(updatedState));
expect(testOp.isFinished).to(beTrue());
});
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
index a578ec561..9dac0203f 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLProtocolHeaderSpec.m
@@ -15,6 +15,18 @@
QuickSpecBegin(SDLProtocolHeaderSpec)
+__block SDLProtocolHeader *testHeader;
+
+beforeSuite(^ {
+ testHeader = [[SDLProtocolHeader alloc] init];
+ testHeader.encrypted = YES;
+ testHeader.frameType = SDLFrameTypeControl;
+ testHeader.serviceType = SDLServiceTypeRPC;
+ testHeader.frameData = SDLFrameInfoStartService;
+ testHeader.sessionID = 0x53;
+ testHeader.bytesInPayload = 0x1234;
+});
+
describe(@"HeaderForVersion Tests", ^ {
it(@"Should return the correct header", ^ {
expect([SDLProtocolHeader headerForVersion:1]).to(beAKindOf(SDLV1ProtocolHeader.class));
@@ -38,4 +50,30 @@ describe(@"DetermineVersion Tests", ^ {
});
});
+describe(@"hashing tests", ^ {
+ it(@"should return equivalent hash values", ^ {
+ SDLProtocolHeader *equalHeader = [[SDLProtocolHeader alloc] init];
+ equalHeader.encrypted = YES;
+ equalHeader.frameType = SDLFrameTypeControl;
+ equalHeader.serviceType = SDLServiceTypeRPC;
+ equalHeader.frameData = SDLFrameInfoStartService;
+ equalHeader.sessionID = 0x53;
+ equalHeader.bytesInPayload = 0x1234;
+
+ expect([testHeader hash]).to(equal([equalHeader hash]));
+ });
+
+ it(@"should return unequivalent hash values", ^ {
+ SDLProtocolHeader *unequalHeader = [[SDLProtocolHeader alloc] init];
+ unequalHeader.encrypted = NO;
+ unequalHeader.frameType = SDLFrameTypeFirst;
+ unequalHeader.serviceType = SDLServiceTypeVideo;
+ unequalHeader.frameData = SDLFrameInfoStartService;
+ unequalHeader.sessionID = 0x54;
+ unequalHeader.bytesInPayload = 0x1234;
+
+ expect([testHeader hash]).toNot(equal([unequalHeader hash]));
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
index a7c6c1660..f2218f7db 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV1ProtocolHeaderSpec.m
@@ -86,4 +86,21 @@ describe(@"RPCPayloadWithData Test", ^ {
});
});
+describe(@"equality tests", ^ {
+ it (@"should be equal to copy of header", ^ {
+ // Create exact copy of test header
+ SDLV1ProtocolHeader *equalHeader = [testHeader copy];
+
+ expect([testHeader isEqual:equalHeader]).to(beTrue());
+ });
+
+ it (@"should not be equal to a different header", ^ {
+ // create a slighty different version of test header
+ SDLV1ProtocolHeader *unequalHeader = [testHeader copy];
+ unequalHeader.encrypted = NO;
+
+ expect(([testHeader isEqual:unequalHeader])).to(beFalse());
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
index 1ade6e426..ec06d2b4d 100644
--- a/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
+++ b/SmartDeviceLinkTests/ProtocolSpecs/HeaderSpecs/SDLV2ProtocolHeaderSpec.m
@@ -90,4 +90,21 @@ describe(@"RPCPayloadWithData Test", ^ {
});
});
+describe(@"equality tests", ^ {
+ it (@"should be equal to copy of header", ^ {
+ // Create exact copy of test header
+ SDLV2ProtocolHeader *equalHeader = [testHeader copy];
+
+ expect([testHeader isEqual:equalHeader]).to(beTrue());
+ });
+
+ it (@"should not be equal to a different header", ^ {
+ // Create a slighty different version of test header
+ SDLV2ProtocolHeader *unequalHeader = [testHeader copy];
+ unequalHeader.messageID = 0x6DAB424E;
+
+ expect(([testHeader isEqual:unequalHeader])).to(beFalse());
+ });
+});
+
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m
new file mode 100644
index 000000000..7065e89a8
--- /dev/null
+++ b/SmartDeviceLinkTests/ProtocolSpecs/SDLProtocolReceivedMessageProcessorSpec.m
@@ -0,0 +1,769 @@
+//
+// SDLProtocolReceivedMessageProcessorSpec.m
+// SmartDeviceLinkTests
+//
+// Created by George Miller on 8/9/22.
+// Copyright © 2022 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+
+#import "SDLProtocol.h"
+#import "SDLProtocolReceivedMessageProcessor.h"
+#import "SDLV2ProtocolHeader.h"
+#import "SDLV2ProtocolMessage.h"
+
+typedef NS_ENUM(NSInteger, ProcessorState) {
+ START_STATE = 0x0,
+ SERVICE_TYPE_STATE = 0x01,
+ CONTROL_FRAME_INFO_STATE = 0x02,
+ SESSION_ID_STATE = 0x03,
+ DATA_SIZE_1_STATE = 0x04,
+ DATA_SIZE_2_STATE = 0x05,
+ DATA_SIZE_3_STATE = 0x06,
+ DATA_SIZE_4_STATE = 0x07,
+ MESSAGE_1_STATE = 0x08,
+ MESSAGE_2_STATE = 0x09,
+ MESSAGE_3_STATE = 0x0A,
+ MESSAGE_4_STATE = 0x0B,
+ DATA_PUMP_STATE = 0x0C,
+ ERROR_STATE = -1,
+};
+
+@interface SDLProtocolReceivedMessageProcessor ()
+// State management
+@property (assign, nonatomic) ProcessorState state;
+
+// Message assembly state
+@property (strong, nonatomic) SDLProtocolHeader *header;
+@property (strong, nonatomic) NSMutableData *headerBuffer;
+@property (strong, nonatomic) NSMutableData *payloadBuffer;
+
+@property (assign, nonatomic) UInt8 version;
+@property (assign, nonatomic) BOOL encrypted;
+@property (assign, nonatomic) SDLFrameType frameType;
+@property (assign, nonatomic) UInt32 dataLength;
+@property (assign, nonatomic) UInt32 dataBytesRemaining;
+@property (assign, nonatomic) SDLServiceType serviceType;
+@end
+
+QuickSpecBegin(SDLProtocolReceivedMessageProcessorSpec)
+
+describe(@"The received message processor", ^{
+ __block SDLProtocolReceivedMessageProcessor *testProcessor = nil;
+ __block NSMutableData *testBuffer;
+ __block NSMutableData *testHeaderBuffer;
+ __block SDLProtocolHeader *messageReadyHeader = nil;
+ __block SDLProtocolHeader *expectedMessageReadyHeader = nil;
+ __block NSData *messageReadyPayload = nil;
+ __block NSData *expectedPayloadBuffer = nil;
+
+ beforeEach(^{
+ testProcessor = [[SDLProtocolReceivedMessageProcessor alloc] init];
+ testBuffer = [NSMutableData data];
+ testHeaderBuffer = [NSMutableData data];
+ messageReadyHeader = nil;
+ expectedMessageReadyHeader = nil;
+ messageReadyPayload = nil;
+ });
+
+ it(@"test processor should be initialized correctly", ^{
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.version)).to(equal(0));
+ expect(@(testProcessor.encrypted)).to(equal(NO));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.dataBytesRemaining)).to(equal(0));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ });
+
+ describe(@"when in START_STATE", ^{
+ it(@"should transition to next state when receiving version 1", ^{
+ Byte testByte = 0x11;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(1));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 2", ^{
+ Byte testByte = 0x21;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(2));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 3", ^{
+ Byte testByte = 0x31;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(3));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 4", ^{
+ Byte testByte = 0x41;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(4));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving version 5", ^{
+ Byte testByte = 0x51;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(testProcessor.version).to(equal(5));
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeControl", ^{
+ Byte testByte = 0x10;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeSingle", ^{
+ Byte testByte = 0x11;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeSingle));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeFirst", ^{
+ Byte testByte = 0x12;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeFirst));
+ });
+
+ it(@"should transition to next state when receiving a byte with a frameType of SDLFrameTypeConsecutive", ^{
+ Byte testByte = 0x13;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SERVICE_TYPE_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeConsecutive));
+ });
+
+ it(@"should reset state when receiving a byte with a bad version 0", ^{
+ Byte testByte = 0x01;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should reset state when receiving a byte with a bad version 6", ^{
+ Byte testByte = 0x61;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should reset state when receiving a byte with an invalid frameType of 6", ^{
+ Byte testByte = 0x46; //0100 0 110
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(0));
+ expect(testProcessor.version).to(equal(0));
+ });
+ });
+
+ // transitions to CONTROL_FRAME_INFO_STATE when in SERVICE_TYPE_STATE
+ describe(@"when in SERVICE_TYPE_STATE", ^{
+
+ beforeEach(^{
+ testProcessor.state = SERVICE_TYPE_STATE;
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeControl byte", ^{
+ Byte testByte = SDLServiceTypeControl;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeRPC byte", ^{
+ Byte testByte = SDLServiceTypeRPC;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeRPC));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeAudio byte", ^{
+ Byte testByte = SDLServiceTypeAudio;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeAudio));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeVideo byte", ^{
+ Byte testByte = SDLServiceTypeVideo;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeVideo));
+ });
+
+ it(@"should transition to next state when receiving a SDLServiceTypeBulkData byte", ^{
+ Byte testByte = SDLServiceTypeBulkData;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(CONTROL_FRAME_INFO_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeBulkData));
+ });
+
+ it(@"should reset state when receiving an invalid byte", ^{
+ Byte testByte = 0xFF;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(SDLServiceTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+ });
+
+ describe(@"when in CONTROL_FRAME_INFO_STATE", ^{
+
+ beforeEach(^{
+ testProcessor.state = CONTROL_FRAME_INFO_STATE;
+ });
+
+ it(@"should transition to next state when receiving a valid byte", ^{
+ Byte testByte = 0x00;
+ testProcessor.frameType = SDLFrameTypeFirst;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(SESSION_ID_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeFirst));
+ });
+
+ it(@"should reset state when receiving a byte where controlFrameInfo is not 0 and frameType is SDLFrameTypeFirst", ^{
+ Byte testByte = 0x01;
+ testProcessor.frameType = SDLFrameTypeFirst;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should resets state when receiving a byte where controlFrameInfo is not 0 and frameType is SDLFrameTypeSingle", ^{
+ Byte testByte = 0x01;
+ testProcessor.frameType = SDLFrameTypeSingle;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.frameType)).to(equal(SDLFrameTypeControl));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+ });
+
+ it(@"should transition to DATA_SIZE_1_STATE when in SESSION_ID_STATE and receiving a byte", ^{
+ testProcessor.state = SESSION_ID_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_1_STATE));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ });
+
+ it(@"should transition to DATA_SIZE_2_STATE when in DATA_SIZE_1_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_1_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_2_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 24));
+ });
+
+ it(@"should transitions to DATA_SIZE_3_STATE when in DATA_SIZE_2_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_2_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_3_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 16));
+ });
+
+ it(@"should transition to DATA_SIZE_4_STATE when in DATA_SIZE_3_STATE and receiving a byte", ^{
+ testProcessor.state = DATA_SIZE_3_STATE;
+ Byte testByte = 0x02;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_SIZE_4_STATE));
+ expect(@(testProcessor.dataLength)).to(equal((UInt32)(testByte & 0xFF) << 8));
+ });
+
+ describe(@"when in DATA_SIZE_4_STATE and the version is 1", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_SIZE_4_STATE;
+ testProcessor.version = 1;
+
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+ });
+
+ it(@"should reset state when receiving a byte and determines the data length is 0", ^{
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+ messageReadyHeader = [SDLProtocolHeader headerForVersion:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(expectedPayloadBuffer));
+ }];
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.version)).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ });
+
+ it(@"should transition to DATA_PUMP_STATE when receiving a byte and determines the data length is greater than 0", ^{
+ Byte testByte = 0x01;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(@(testProcessor.dataLength)).to(equal(1));
+ expect(@(testProcessor.version)).to(equal(1));
+ });
+
+ it(@"should transition to START_STATE when receiving a byte and determines the data length is greater than maxMtuSize", ^{
+ testProcessor.serviceType = SDLServiceTypeControl;
+ testProcessor.dataLength = 200000;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(@(testProcessor.serviceType)).to(equal(0));
+ expect(@(testProcessor.dataLength)).to(equal(0));
+ expect(@(testProcessor.version)).to(equal(0));
+ });
+ });
+
+ describe(@"when in DATA_SIZE_4_STATE and the version is greater than 1", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_SIZE_4_STATE;
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ testProcessor.version = 1;
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+ });
+
+ it(@"should transition to MESSAGE_1_STATE when it receives a byte", ^{
+ testProcessor.version = 2;
+ testProcessor.dataLength = 0;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_1_STATE));
+ expect(testProcessor.version).to(equal(2));
+ });
+ });
+
+ it(@"should transition to MESSAGE_2_STATE when in MESSAGE_1_STATE, it receives a byte", ^{
+ testProcessor.state = MESSAGE_1_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_2_STATE));
+ });
+
+ it(@"should transition to MESSAGE_3_STATE when in MESSAGE_2_STATE and it receives a byte", ^{
+ testProcessor.state = MESSAGE_2_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_3_STATE));
+ });
+
+ it(@"should transition to MESSAGE_4_STATE when in MESSAGE_3_STATE and it receives a byte", ^{
+ testProcessor.state = MESSAGE_3_STATE;
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(MESSAGE_4_STATE));
+ });
+
+ describe(@"when in MESSAGE_4_STATE and version is greater than 1", ^{
+ beforeEach(^{
+ testProcessor.state = MESSAGE_4_STATE;
+ testProcessor.version = 2;
+ //need a valid headerbuffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ expectedPayloadBuffer = [NSData data];
+
+ messageReadyHeader = nil;
+ messageReadyPayload = nil;
+
+ Byte testByte = 0x00;
+ [testBuffer appendBytes:&testByte length:1];
+ });
+
+ it(@"should reset state when data length is 0 and receiving a byte", ^{
+ testProcessor.dataLength = 0;
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(expectedPayloadBuffer));
+ }];
+ expect(testProcessor.version).to(equal(0));
+ expect(testProcessor.headerBuffer).to(equal([NSMutableData data]));
+ expect(testProcessor.payloadBuffer).to(equal([NSMutableData data]));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ });
+
+ it(@"should transition to DATA_PUMP_STATE when datalength is greater than 0 and receiving a byte", ^{
+ testProcessor.dataLength = 1;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(testProcessor.version).to(equal(2));
+ });
+ });
+
+ describe(@"in DATA_PUMP_STATE", ^{
+ beforeEach(^{
+ testProcessor.state = DATA_PUMP_STATE;
+ testProcessor.version = 3;
+ //need a valid header buffer.
+ Byte firstByte = ((testProcessor.version & 0x0f) << 4) + (0 << 3) + (1 & 0x07); //version 2 with no encryption, frametype 1
+ UInt32 dataLength = 3;
+ const Byte testBytes[8] = {firstByte, 0x00, 0x00, 0x00, (dataLength >> 24) & 0xff, (dataLength >> 16) & 0xff, (dataLength >> 8) & 0xff, (dataLength) & 0xff };
+ [testHeaderBuffer appendBytes:&testBytes length:8];
+ UInt32 messageID = 0;
+ Byte messageIDBytes[4] = {(messageID >> 24) & 0xff, (messageID >> 16) & 0xff, (messageID >> 8) & 0xff, (messageID) & 0xff};
+ [testHeaderBuffer appendBytes:&messageIDBytes length:4];
+
+ testProcessor.headerBuffer = testHeaderBuffer;
+ expectedMessageReadyHeader= [SDLProtocolHeader headerForVersion:testProcessor.version];
+ [expectedMessageReadyHeader parse:testHeaderBuffer];
+
+ Byte testByte = 0xBA;
+ [testBuffer appendBytes:&testByte length:1];
+ });
+
+ it(@"should stay in current state when dataBytesRemaining is greater than 1 and receiving a byte", ^{
+ testProcessor.dataBytesRemaining = 2;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(beNil());
+ expect(messageReadyPayload).to(beNil());
+ }];
+ expect(@(testProcessor.state)).to(equal(DATA_PUMP_STATE));
+ expect(testProcessor.dataBytesRemaining).to(equal(1));
+ expect(testProcessor.version).to(equal(3));
+ });
+
+ it(@"should transition to START_STATE when dataBytesRemaining is 1 and receiving a byte", ^{
+ testProcessor.dataBytesRemaining = 1;
+
+ [testProcessor processReceiveBuffer:testBuffer withMessageReadyBlock:^(SDLProtocolHeader *header, NSData *payload) {
+ messageReadyHeader = header;
+ messageReadyPayload = payload;
+
+ expect(messageReadyHeader).to(equal(expectedMessageReadyHeader));
+ expect(messageReadyPayload).to(equal(testBuffer));
+ }];
+ expect(testProcessor.dataBytesRemaining).to(equal(0));
+ expect(@(testProcessor.state)).to(equal(START_STATE));
+ expect(testProcessor.version).to(equal(0));
+ });
+ });
+});
+
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m b/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m
index 78dda1e82..4173b923d 100644
--- a/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m
+++ b/SmartDeviceLinkTests/SDLPresentAlertOperationSpec.m
@@ -42,6 +42,7 @@
@property (strong, nonatomic, readwrite) SDLAlertView *alertView;
@property (assign, nonatomic) UInt16 cancelId;
@property (copy, nonatomic, nullable) NSError *internalError;
+@property (assign, nonatomic) BOOL alertIconUploaded;
- (nullable NSError *)sdl_isValidAlertViewData:(SDLAlertView *)alertView;
- (SDLAlert *)alertRPC;
@@ -365,16 +366,16 @@ describe(@"SDLPresentAlertOperation", ^{
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);
+ it(@"should not set the image if it fails to upload to the module", ^{
SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
- expect(testAlert.alertIcon.value).to(equal(testAlertView.icon.name));
+ expect(testAlert.alertIcon.value).to(beNil());
});
- it(@"should not set the image if icons are not supported on the module", ^{
- OCMStub([mockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(NO);
+ it(@"should set the image if it is uploaded to the module", ^{
+ testPresentAlertOperation.alertIconUploaded = YES;
+
SDLAlert *testAlert = testPresentAlertOperation.alertRPC;
- expect(testAlert.alertIcon).to(beNil());
+ expect(testAlert.alertIcon.value).to(equal(testAlertView.icon.name));
});
});
});
@@ -635,6 +636,25 @@ describe(@"SDLPresentAlertOperation", ^{
OCMVerifyAll(strictMockSystemCapabilityManager);
OCMVerifyAll(strictMockCurrentWindowCapability);
});
+
+ it(@"should not upload the image if the alert icon is a static icon", ^{
+ SDLAlertView *alertView = [[SDLAlertView alloc] initWithText:@"Test" secondaryText:nil tertiaryText:nil timeout:nil showWaitIndicator:nil audioIndication:nil buttons:nil icon:[SDLArtwork artworkWithStaticIcon:SDLStaticIconNameKey]];
+ testPresentAlertOperation = [[SDLPresentAlertOperation alloc] initWithConnectionManager:mockConnectionManager fileManager:strictMockFileManager systemCapabilityManager:strictMockSystemCapabilityManager currentWindowCapability:strictMockCurrentWindowCapability alertView:alertView cancelID:testCancelID];
+
+ OCMStub([strictMockCurrentWindowCapability hasImageFieldOfName:SDLImageFieldNameAlertIcon]).andReturn(YES);
+ OCMStub([strictMockFileManager hasUploadedFile:[OCMArg any]]).andReturn(NO);
+ OCMStub([strictMockFileManager fileNeedsUpload:[OCMArg any]]).andReturn(NO);
+
+ OCMReject([strictMockFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ [testPresentAlertOperation start];
+
+ OCMVerifyAll(strictMockFileManager);
+ OCMVerifyAll(strictMockSystemCapabilityManager);
+ OCMVerifyAll(strictMockCurrentWindowCapability);
+ expect(testPresentAlertOperation.alertIconUploaded).to(beTrue());
+ expect(testPresentAlertOperation.alertRPC.alertIcon).toNot(beNil());
+ });
});
});
@@ -781,6 +801,7 @@ describe(@"SDLPresentAlertOperation", ^{
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.alertIconUploaded = YES;
testPresentAlertOperation.completionBlock = ^{
hasCalledOperationCompletionHandler = YES;
@@ -829,7 +850,7 @@ describe(@"SDLPresentAlertOperation", ^{
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 uploadArtworks:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:([OCMArg invokeBlockWithArgs: @[], [NSNull null], nil])]);
OCMStub([mockFileManager uploadFiles:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
SDLVersion *supportedVersion = [SDLVersion versionWithMajor:6 minor:3 patch:0];
@@ -883,8 +904,8 @@ describe(@"SDLPresentAlertOperation", ^{
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 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];
diff --git a/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m b/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m
index 613030611..5343c4397 100644
--- a/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m
+++ b/SmartDeviceLinkTests/SDLSystemCapabilityManagerSpec.m
@@ -95,9 +95,16 @@ describe(@"a system capability manager", ^{
imageField.imageTypeSupported = @[SDLFileTypePNG];
imageField.imageResolution = [[SDLImageResolution alloc] initWithWidth:42 height:4711];
testDisplayCapabilities.imageFields = @[imageField];
- testDisplayCapabilities.mediaClockFormats = @[];
+ testDisplayCapabilities.mediaClockFormats = @[SDLMediaClockFormatClock1, SDLMediaClockFormatClock2];
testDisplayCapabilities.templatesAvailable = @[@"DEFAULT", @"MEDIA"];
testDisplayCapabilities.numCustomPresetsAvailable = @(8);
+ SDLScreenParams *screenParams = [[SDLScreenParams alloc] init];
+ [screenParams setResolution:[[SDLImageResolution alloc] initWithWidth:675 height:960]];
+ [screenParams setTouchEventAvailable:[[SDLTouchEventCapabilities alloc] init]];
+ [screenParams.touchEventAvailable setPressAvailable:@YES];
+ [screenParams.touchEventAvailable setMultiTouchAvailable:@YES];
+ [screenParams.touchEventAvailable setDoublePressAvailable:@YES];
+ testDisplayCapabilities.screenParams = screenParams;
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
@@ -702,18 +709,28 @@ describe(@"a system capability manager", ^{
expect(testSystemCapabilityManager.defaultMainWindowCapability.textFields).to(haveCount(38));
expect(testSystemCapabilityManager.defaultMainWindowCapability.imageFields).to(haveCount(18));
});
+
+ it(@"should set the default window id if window id is missing", ^{
+ // Set window id to nil. windowID can be potentially nil in real applications
+ testSystemCapabilityManager.displays[0].windowCapabilities[0].windowID = nil;
+
+ expect(testSystemCapabilityManager.defaultMainWindowCapability.windowID).to(equal(SDLPredefinedWindowsDefaultWindow));
+ expect([testSystemCapabilityManager windowCapabilityWithWindowID:0].windowID).to(equal(SDLPredefinedWindowsDefaultWindow));
+ });
});
});
// when updating display capabilities with OnSystemCapabilityUpdated
describe(@"when updating display capabilities with OnSystemCapabilityUpdated", ^{
it(@"should properly update display capability including conversion two times", ^{
+ // Set to display capabilities that have screenParams and mediaClockFormats set
+ testSystemCapabilityManager.displayCapabilities = testDisplayCapabilities;
// two times because capabilities are just saved in first run but merged/updated in subsequent runs
for (int i = 0; i < 2; i++) {
testDisplayCapabilities.displayName = [NSString stringWithFormat:@"Display %i", i];
testDisplayCapabilities.graphicSupported = i == 0 ? @(NO) : @(YES);
testDisplayCapabilities.templatesAvailable = @[[NSString stringWithFormat:@"Template %i", i]];
-
+
SDLWindowTypeCapabilities *windowTypeCapabilities = [[SDLWindowTypeCapabilities alloc] initWithType:SDLWindowTypeMain maximumNumberOfWindows:1];
SDLDisplayCapability *displayCapability = [[SDLDisplayCapability alloc] initWithDisplayName:testDisplayCapabilities.displayName];
displayCapability.windowTypeSupported = @[windowTypeCapabilities];
@@ -728,16 +745,18 @@ describe(@"a system capability manager", ^{
defaultWindowCapability.imageTypeSupported = testDisplayCapabilities.graphicSupported.boolValue ? @[SDLImageTypeStatic, SDLImageTypeDynamic] : @[SDLImageTypeStatic];
displayCapability.windowCapabilities = @[defaultWindowCapability];
NSArray<SDLDisplayCapability *> *newDisplayCapabilityList = testDisplayCapabilityList = @[displayCapability];
-
+
SDLSystemCapability *newCapability = [[SDLSystemCapability alloc] initWithDisplayCapabilities:newDisplayCapabilityList];
SDLOnSystemCapabilityUpdated *testUpdateNotification = [[SDLOnSystemCapabilityUpdated alloc] initWithSystemCapability:newCapability];
SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidReceiveSystemCapabilityUpdatedNotification object:nil rpcNotification:testUpdateNotification];
[[NSNotificationCenter defaultCenter] postNotification:notification];
-
+
expect(testSystemCapabilityManager.displays).to(equal(testDisplayCapabilityList));
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated"
expect(testSystemCapabilityManager.displayCapabilities).to(equal(testDisplayCapabilities));
+ expect(testSystemCapabilityManager.displayCapabilities.screenParams).to(equal(testDisplayCapabilities.screenParams));
+ expect(testSystemCapabilityManager.displayCapabilities.mediaClockFormats).to(equal(testDisplayCapabilities.mediaClockFormats));
expect(testSystemCapabilityManager.buttonCapabilities).to(equal(testButtonCapabilities));
expect(testSystemCapabilityManager.softButtonCapabilities).to(equal(testSoftButtonCapabilities));
expect(testSystemCapabilityManager.presetBankCapabilities).to(beNil());
diff --git a/scripts/create_framework.sh b/scripts/create_framework.sh
index 84d3f0a06..a24b7c160 100755
--- a/scripts/create_framework.sh
+++ b/scripts/create_framework.sh
@@ -57,6 +57,8 @@ if [ -z $1 ]; then
echo "No version number entered. Skipping..."
new_version_number=$current_version_number
fi
+ else
+ new_version_number=$current_version_number
fi
else
new_version_number=$1
@@ -71,15 +73,13 @@ xcodebuild -create-xcframework -framework './SmartDeviceLink-Device.xcarchive/Pr
# TODO - is there a way we can test that the build was successful.
-folder="SmartDeviceLink.xcframework"
+framework_folder="SmartDeviceLink.xcframework"
zip_file_name="SmartDeviceLink-$new_version_number.xcframework.zip"
# Kill the old zip if present. Useful for re-running the script
if [ -f $zip_file_name ]; then rm $zip_file_name; fi
-# Verify folder exists before acting on it.
-if [ -d "$folder" ]; then
- zip $zip_file_name $folder
- # Check to see if the zip exists, and then remove old files.
- if [ -f "$zip_file_name" ]; then rm -r $folder; fi
+# Verify framework_folder exists and create a zip from it.
+if [ -d "$framework_folder" ]; then
+ tar -cf $zip_file_name $framework_folder
fi
# Cleanup artifacts
@@ -89,5 +89,8 @@ if [ -d "$folder" ]; then rm -r $folder; fi
folder="SmartDeviceLink-Simulator.xcarchive"
if [ -d "$folder" ]; then rm -r $folder; fi
+# Check to see if the zip exists, and then remove old files.
+if [ -f "$zip_file_name" ]; then rm -r $framework_folder; fi
+
echo
echo "The xcframework zip file was created at $zip_file_name." \ No newline at end of file
diff --git a/scripts/project_file_header_fix.sh b/scripts/project_file_header_fix.sh
new file mode 100755
index 000000000..03e31b164
--- /dev/null
+++ b/scripts/project_file_header_fix.sh
@@ -0,0 +1,227 @@
+#!/bin/bash
+
+# George Miller
+# 6-10-2022
+# If you do not have permission to run, try: chmod u+x project_file_header_fix.sh
+#
+# The purpose of this script is to update the location of header files in an xcode project by their public attribute.
+# It goes backwards, finding the paths to the header files first, then backtracking to find the attributes for file.
+# This was more reliable because files marked private/project have a path, but do not always have attributes.
+# This script also identifies and moves any associated code files for the headers
+#
+# Also,
+# Any file in public/ needs to be referenced in the project header
+# Any file located in private/ needs to NOT be referenced in the project header
+
+project_file="SmartDeviceLink-iOS.xcodeproj/project.pbxproj"
+target_path="SmartDeviceLink"
+project_header=$target_path"/public/SmartDeviceLink.h"
+
+
+# A utility function for prompting the user Y/N
+# This takes in a string prompt for the input
+# This returns 1 for yes/true or 0 for no/false
+prompt_user() {
+ user_input="g"
+ echo
+ echo $1" (Y/N)?"
+ read user_input
+ while [[ ! $user_input == [YyNn] ]]; do
+ echo $1" (Y/N)?"
+ read user_input
+ done
+ if [[ ! $user_input == [Nn] ]]; then
+ return 1
+ else
+ return 0
+ fi
+}
+
+# Make sure we are in the correct directory.
+# If we are running from the scripts directory, we want to pop back to the project root to do everything.
+if [[ $PWD == *"scripts" ]]; then
+ cd ..
+fi
+# If, for some reason, we are not now in the correct working directory, exit.
+if [[ $PWD != *"sdl_ios" ]]; then
+ echo "Please run this from the sdl_ios project root or the sdl_ios/scripts directory"
+ exit 0
+fi
+
+
+# Find the lines in the project file with "path".
+path_lines=$(sed -n '/path/{s/[[:space:]]*//;s/\/\*.*\*\///g;s/{.*path//;p;}' $project_file)
+# Filter to get only the lines with the header_filepath and fileref.
+header_files=$(sed -n '/\.h/{s/[[:space:]]*//g;s/\"//g;s/\;.*//g;s/==/=/;p;}' <<< "$path_lines")
+
+echo "Checking files for public / private correctness..."
+
+for line in $header_files
+do
+ # Pick out the fileref and the header_filepath.
+ fileref=$(sed -n 's/=.*//;p;' <<< $line)
+ header_filepath=$(sed -n 's/.*=//;p;' <<< $line)
+
+ # Use the fileref to get the attributes.
+ attributes=$(sed -n '/fileRef[[:space:]]*=[[:space:]]*'$fileref'/{s/.*fileRef[[:space:]]*=[[:space:]]*'$fileref'//;s/\/\*.*\*\///g;p;};' $project_file)
+
+ # Determine public or private.
+ # Save off the opposite for the file path change later.
+ if [[ $attributes == *"Public"* ]]; then
+ header_type="public"
+ else
+ header_type="private"
+ fi
+
+
+ # Only look at entries where the attributes line is not empty.
+ if [ ! -z "$attributes" ]; then
+ # Find the real location of the file.
+ file_found_location=$(find "$target_path" -name "$(basename "$header_filepath")" -maxdepth 2)
+
+ # If file is found.
+ if [ ! -z "$file_found_location" ]; then
+
+ # Test to see if the file is where it should be. (Does the path contain the correct folder)
+ if [[ ! $file_found_location == *"/$header_type/"* ]]; then
+ # Add the file to the list of files that are in the wrong location.
+ broken_file_list+=$file_found_location"=="$header_type"=="$fileref" "
+ fi
+ fi
+ fi
+done
+
+# If the broken file list is not empty.
+if [ ! -z "$broken_file_list" ]; then
+ echo
+ # List the files found for the user.
+ for header_file in $broken_file_list
+ do
+ if [ ! -z "$header_file" ]; then
+ header_filepath="${header_file%%==*}"
+ header_type1="${header_file%==*}"
+ header_type="${header_type1##*==}"
+ fileref="${header_file##*==}"
+ code_file=$(sed -n 's/\.h/\.m/p' <<< "$header_filepath")
+ echo $header_filepath" and "$code_file" are marked "$header_type
+ fi
+ done
+
+ # Prompt the user to move the files.
+ prompt_user "These files were found to be out of place. Would you like to move them automatically"
+ if [[ $? == 1 ]]; then
+ for header_file in $broken_file_list
+ do
+ echo
+ header_filepath="${header_file%%==*}"
+ header_type1="${header_file%==*}"
+ header_type="${header_type1##*==}"
+ fileref="${header_file##*==}"
+
+ # Figure out where the file should be located
+ destiny=$target_path"/"$header_type"/"
+
+ # Move the file to the correct destination
+ mv -f $header_filepath $destiny
+ echo "File "$header_filepath" moved to "$destiny
+
+ # Figure out the opposite of the type
+ if [[ $header_type == "public" ]]; then
+ header_opp="private"
+ else
+ header_opp="public"
+ fi
+
+ # Fix path in the project file.
+ # Output to a second file, then overwrite the first with the second.
+ # This is done because sed does not like writing to the file it is currently reading.
+ sed '/'$fileref'/{s/'$header_opp'/'$header_type'/;}' $project_file > $project_file"2"
+ mv -f $project_file"2" $project_file
+
+ # Identify associated implementation file.
+ code_file=$(sed -n 's/\.h/\.m/p' <<< "$header_filepath")
+
+ code_file_basename=$(basename "$code_file")
+ code_file_found_location=$(find "$target_path" -name "$code_file_basename" -maxdepth 2)
+ if [ ! -z "$code_file_found_location" ]; then
+ if [[ ! $code_file_found_location -ef $destiny$code_file_basename ]]; then
+ # Move associated implementation file.
+ mv -f $code_file_found_location $destiny
+ echo "File "$code_file" moved to "$destiny
+
+ # Fix path in the project file.
+ # Output to a second file, then overwrite the first with the second.
+ # This is done because sed does not like writing to the file it is currently reading.
+ sed '/'$code_file_basename'/{s/'$header_opp'/'$header_type'/;}' $project_file > $project_file"2"
+ mv -f $project_file"2" $project_file
+ else
+ echo $code_file_found_location" does not need to be moved"
+ fi
+ fi
+ done
+ fi
+
+else
+ echo "All files are in the correct folder..."
+fi
+
+# Find all header files in public/
+public_file_list=$(find "$target_path"/public -name '*.h')
+
+if [ ! -z "$public_file_list" ]; then
+ for header_file in $public_file_list
+ do
+ file_basename=$(basename "$header_file")
+ # Use sed to check to see if the file is in the project header
+ found=$(sed -n '/'$file_basename'/{p;}' $project_header)
+ if [ -z "$found" ]; then
+ project_header_not_found_list+=$header_file" "
+ fi
+ done
+fi
+
+# List the files found.
+if [ ! -z "$project_header_not_found_list" ]; then
+ echo
+ echo "These files are public and were not found in "$project_header
+ for header_file in $project_header_not_found_list
+ do
+ echo $header_file
+ done
+ echo "Please add them to the project header, then press enter to continue..."
+ read user_input
+else
+ echo "All public header files were found in "$project_header
+fi
+
+# Find all header files in private/
+private_file_list=$(find "$target_path"/private -name '*.h')
+
+if [ ! -z "$private_file_list" ]; then
+ for private_header_file in $private_file_list
+ do
+ file_basename=$(basename "$private_header_file")
+
+ # Use sed to check to see if the file is NOT in the project header
+ found=$(sed -n '/'$file_basename'/{p;}' $project_header)
+ if [ ! -z "$found" ]; then
+ private_file_found_list+=$private_header_file" "
+ fi
+ done
+fi
+
+# List the files found.
+if [ ! -z "$private_file_found_list" ]; then
+ echo
+ echo "These files are private and were found in "$project_header
+ for private_header_file in $private_file_found_list
+ do
+ echo $private_header_file
+ done
+ echo "Please remove them from the project header, then press enter to continue..."
+ read user_input
+else
+ echo "No private header files were found in "$project_header
+fi
+
+echo "Done checking public / private headers for correctness"
diff --git a/scripts/release.sh b/scripts/release.sh
index 443a24aa4..fd66a86ea 100755
--- a/scripts/release.sh
+++ b/scripts/release.sh
@@ -23,8 +23,6 @@ prompt_user() {
fi
}
-# TODO - phase 4 - github cli "gh" needs to be installed before we can use those commands. We could automate this or at least check if gh is installed.
-
# Script start
echo
echo "Starting SDL release script..."
@@ -43,23 +41,68 @@ fi
develop_branch="develop"
main_branch="master"
-# Stash any local changes to avoid errors during checkout
-git status
-prompt_user "Would you like to stash any local changes"
-if [[ $? == 1 ]]; then
- # Stash local changes to prevent issues with checkout
- git stash
- echo "use \"git stash pop\" when this script is complete to restore your changes"
+# Checkout develop - so we can update versions.
+# We need to checkout the branch before we start modifying files.
+current_branch=$(git branch --show-current)
+if [ $current_branch == $develop_branch ]; then
+ echo
+ echo "$develop_branch is already checked out."
else
- # Dump local changes to prevent issues with checkout
- git reset --hard
+ echo
+ echo "Checkout of $develop_branch is required for some steps"
+ prompt_user "Would you like to automatically checkout $develop_branch"
+ if [[ $? == 1 ]]; then
+
+ # Stash any local changes to avoid errors during checkout
+ git_status=$(git status)
+ git_uncommitted_changes=$(sed -n '/Changes not staged for commit:/{p;}' <<< "$git_status")
+ if [ ! -z "$git_uncommitted_changes" ]; then
+ echo "There are uncommitted changes in these files"
+ file_changes=$(git diff-files)
+ file_changes=$(sed -n '/^[[:space:]]*/{s/^.*[[:space:]]//g;p;q;}' <<< "$file_changes")
+ echo $file_changes
+ prompt_user "Would you like to stash these local changes before checkout of $develop_branch"
+ if [[ $? == 1 ]]; then
+ # Stash local changes to prevent issues with checkout
+ git stash
+ echo "Local changes have been stashed."
+ echo "Use \"git stash pop\" when this script is complete to restore your changes"
+ else
+ # Dump local changes to prevent issues with checkout. Reset cleans up any uncommitted changes to the index.
+ echo "Local changes were not stashed."
+ git reset --hard
+ fi
+ fi
+
+ # Do a pull to make sure we are up to date.
+ pull_response=$(git pull --ff)
+
+ # Check that the pull was successful If we did not get a response, then we know the pull failed.
+ if [ -z "$pull_response" ]; then
+ echo "git pull for automatic checkout has failed. Abort."
+ exit 0
+ fi
+
+ # Now do the checkout
+ git checkout $develop_branch
+
+ # check if the checkout was successful
+ current_branch=$(git branch --show-current)
+ if [ $current_branch == $develop_branch ]; then
+ develop_checked_out=1
+ else
+ echo "Automatic checkout has failed. Abort."
+ exit 0
+ fi
+ fi
fi
-# Checkout develop
-# We need to checkout the branch before we start modifying files.
-echo
-echo "Checking out $develop_branch"
-git checkout $develop_branch
+# Fix any header files that are in the wrong location according to the project file
+prompt_user "Would you like to run the project file header fixer"
+if [[ $? == 1 ]]; then
+ chmod u+x ./scripts/project_file_header_fix.sh
+ ./scripts/project_file_header_fix.sh
+fi
# Bump version in projectFile
echo
@@ -107,11 +150,11 @@ fi
echo
echo "Checking SDLGlobals.m for RPC and Protocol versions"
file="SmartDeviceLink/private/SDLGlobals.m"
-current_rpc_version=$(sed -n '/SDLMaxProxyProtocolVersion/{s/^.*@//;s/[\;]//;s/[\"]//g;p;q;}' $file)
-current_protocol_version=$(sed -n '/SDLMaxProxyRPCVersion/{s/^.*@//;s/[\;]//;s/[\"]//g;p;q;}' $file)
+current_rpc_version=$(sed -n '/SDLMaxProxyRPCVersion/{s/^.*@//;s/[\;]//;s/[\"]//g;p;q;}' $file)
+current_protocol_version=$(sed -n '/SDLMaxProxyProtocolVersion/{s/^.*@//;s/[\;]//;s/[\"]//g;p;q;}' $file)
echo "Current RPC Version: "$current_rpc_version
echo "Current Protocol Version: "$current_protocol_version
-echo "If these are not correct, please update protocol versions in /SmartDeviceLink/private/SDLGlobals.m. Then press enter..."
+echo "If these are not correct, please update versions in /SmartDeviceLink/private/SDLGlobals.m. Then press enter..."
read user_input
# Update to the newest BSON submodule. Update Package.swift and CocoaPods dependency files to point to latest if necessary.
@@ -139,12 +182,11 @@ if [ ! -z "$submodule_info" ]; then
fi
# Update changelog
-# TODO - insert a template into the changelog that includes the version the users have selected above.
-#echo "A template for this release has been inserted into the changelog. Please update it."
+# TODO - we could insert a template into the changelog that includes the version the users have selected above.
echo
echo "Please update CHANGELOG.md, then return here and press enter..."
read user_input
-# TODO - check modified info before and after so we can detect if the user failed to update the file.
+# TODO - check modified info before and after so we can detect if the user updated the file.
# Generate documentation
prompt_user "Would you like to automatically generate documentation with Jazzy"
@@ -157,7 +199,15 @@ if [[ $? == 1 ]]; then
# This runs Jazzy to generate the documentation.
echo "Running Jazzy to generate documentation..."
- jazzy --clean --objc --framework-root SmartDeviceLink --sdk iphonesimulator --umbrella-header SmartDeviceLink/public/SmartDeviceLink.h --theme theme --output docs
+ # generate-documentation.sh throws an error if we are not in the correct directory when we run it.
+ cd scripts
+ chmod u+x ./generate-documentation.sh
+ if [ $(uname -m) == "x86_64" ]; then
+ ./generate-documentation.sh
+ else
+ arch -x86_64 /bin/bash ./generate-documentation.sh
+ fi
+ cd ..
fi
# Ensure that the RPC_SPEC has released to the master branch and update the submodule to point to the new release tag (or to the HEAD of master, if no release of the RPC_SPEC is occurring).
@@ -167,63 +217,77 @@ echo "If there is, please update the rpc_spec submodule to point to the newest c
read user_input
# Git commands
-echo
-echo "$develop_branch has already been checked out for you."
-
-prompt_user "Would you like to walk through the git commands for this release"
-if [[ $? == 1 ]]; then
-
- # commit release to develop
- prompt_user "Would you like to commit and push these changes to the develop branch"
- if [[ $? == 1 ]]; then
- # Add, commit, and push changes
- git add -A
- git commit -m "Update for release $new_version_number"
- git push --set-upstream origin $develop_branch
- else
- echo "Aborting script!"
- exit 0
- fi
+# Check to make sure the correct branch is checked out.
+current_branch=$(git branch --show-current)
+if [ $current_branch == $develop_branch ]; then
+ echo
+ echo "$develop_branch has already been checked out for you."
- # Merge release to master
- prompt_user "Would you like to merge this release to master? (This will not push to master)"
+ prompt_user "Would you like to walk through the git commands for this release"
if [[ $? == 1 ]]; then
- # Checkout master
- git checkout $main_branch
+ prompt_user "Would you like to commit and push the changes made so far to the develop branch"
+ if [[ $? == 1 ]]; then
+ # Add, commit, and push changes to develop
+ git add -A
+ git commit -m "Update for release $new_version_number"
+ git push --set-upstream origin $develop_branch
+ else
+ echo "Aborting script!"
+ exit 0
+ fi
- # Merge develop with master
- git merge $main_branch $develop_branch
- echo "Please check that everything is correct. Then, assuming you have permissions, push to master, then press enter..."
- else
- echo "Aborting script!"
- exit 0
- fi
+ # Merge release to master (update master from develop)
+ prompt_user "Would you like to merge this release from develop to master? (This will not push to master)"
+ if [[ $? == 1 ]]; then
+ # Checkout master
+ git checkout $main_branch
+
+ # Merge develop with master.
+ # This updates the checked out master with the contents of develop
+ git merge $develop_branch $main_branch
+
+ echo "Please check that everything is correct."
+
+ # Tag it
+ prompt_user "Would you like to tag this release with $new_version_number? (This will not push the tag)"
+ if [[ $? == 1 ]]; then
+ git tag $new_version_number
+ fi
+
+ echo "If these changes are correct, please commit them manually and then push them to master..."
+ read user_input
+ # TODO - here are the commands if we decide to automate this.
+ # git commit -m "commit message here "
+ # git push --set-upstream origin $main_branch
+ else
+ echo "Aborting script!"
+ exit 0
+ fi
- # Tag it
- prompt_user "Would you like to tag this release with $new_version_number? (This will not push the tag)"
- if [[ $? == 1 ]]; then
- git tag $new_version_number
- # IDEA - else condition that allows the user to enter a different tag
+ # Merge master back to develop
+ prompt_user "Would you like to merge master back into develop (You will need to push manually)"
+ if [[ $? == 1 ]]; then
+ git checkout $develop_branch
+ git merge $main_branch $develop_branch
+ else
+ echo "Aborting script!"
+ exit 0
+ fi
fi
- # Merge master back to develop
- prompt_user "Would you like to merge master back into develop (This will not push the branch)"
- if [[ $? == 1 ]]; then
- git merge $develop_branch $main_branch
- else
- echo "Aborting script!"
- exit 0
- fi
+else
+ echo "You do not have $develop_branch currently checked out."
fi
+# TODO - can we provide templates for the release based on the changelog?
+# TODO - can we open directories for drag and drop to the release?
# Create new release for tag
prompt_user "Would you like to open to the Github releases page to create a release"
if [[ $? == 1 ]]; then
open "https://github.com/smartdevicelink/sdl_ios/releases"
fi
-echo
# Push new release to primary and secondary cocoapod using command line:
prompt_user "Would you like to push the release to CocoaPods"
if [[ $? == 1 ]]; then
@@ -237,7 +301,11 @@ if [[ $? == 1 ]]; then
# We pass in the version so that the framework script does not need to ask
# Give the user permissions to the framework script, then run the script.
chmod u+x ./scripts/create_framework.sh
- ./scripts/create_framework.sh $new_version_number
+ if [ $(uname -m) == "x86_64" ]; then
+ ./scripts/create_framework.sh $new_version_number
+ else
+ arch -x86_64 /bin/bash ./scripts/create_framework.sh $new_version_number
+ fi
echo
zip_file_name="SmartDeviceLink-$new_version_number.xcframework.zip"
@@ -255,7 +323,6 @@ if [[ $? == 1 ]]; then
echo
echo "Please add the docset at $docset_tar_file_name to the Github release, then press enter..."
read user_input
- # TODO - phase 4 - adding the docset to the release should also be automatic
fi
echo
-echo "Release complete." \ No newline at end of file
+echo "Release complete."