summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoel Fischer <joeljfischer@gmail.com>2019-05-23 09:36:13 -0400
committerGitHub <noreply@github.com>2019-05-23 09:36:13 -0400
commitc080f3e0084ea502930c22749e18eb1e0f41f524 (patch)
tree9c083c7d9ce28163435886007fc6f482333a88e5
parent038888038756dbd4e22e36370f78c9588dc42ec5 (diff)
parent44fe2ec9ca5a8e5f2e29ebd12012404c169618aa (diff)
downloadsdl_ios-c080f3e0084ea502930c22749e18eb1e0f41f524.tar.gz
Merge pull request #1251 from smartdevicelink/bugfix/issue_1234_softbutton_multiple_states
Fix SoftButtonManager Race Conditions
-rw-r--r--Example Apps/Example ObjC/ButtonManager.m3
-rw-r--r--SmartDeviceLink-iOS.xcodeproj/project.pbxproj58
-rw-r--r--SmartDeviceLink/SDLAsynchronousOperation.h1
-rw-r--r--SmartDeviceLink/SDLChoiceSetManager.m2
-rw-r--r--SmartDeviceLink/SDLLogFileModuleMap.m2
-rw-r--r--SmartDeviceLink/SDLScreenManager.m1
-rw-r--r--SmartDeviceLink/SDLSoftButtonManager.h10
-rw-r--r--SmartDeviceLink/SDLSoftButtonManager.m302
-rw-r--r--SmartDeviceLink/SDLSoftButtonReplaceOperation.h45
-rw-r--r--SmartDeviceLink/SDLSoftButtonReplaceOperation.m294
-rw-r--r--SmartDeviceLink/SDLSoftButtonTransitionOperation.h43
-rw-r--r--SmartDeviceLink/SDLSoftButtonTransitionOperation.m91
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m236
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonTransitionOperationSpec.m107
-rw-r--r--SmartDeviceLinkTests/SDLScreenManagerSpec.m4
-rw-r--r--SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m249
16 files changed, 1016 insertions, 432 deletions
diff --git a/Example Apps/Example ObjC/ButtonManager.m b/Example Apps/Example ObjC/ButtonManager.m
index 786d5027b..4befdf726 100644
--- a/Example Apps/Example ObjC/ButtonManager.m
+++ b/Example Apps/Example ObjC/ButtonManager.m
@@ -135,6 +135,9 @@ NS_ASSUME_NONNULL_BEGIN
SDLSoftButtonObject *object = [weakself.sdlManager.screenManager softButtonObjectNamed:ImagesVisibleSoftButton];
[object transitionToNextState];
+ SDLSoftButtonObject *textButton = [weakself.sdlManager.screenManager softButtonObjectNamed:TextVisibleSoftButton];
+ [textButton transitionToNextState];
+
SDLLogD(@"Image visibility soft button press fired %d", weakself.imagesEnabled);
}];
diff --git a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
index 183bfcf37..0b49b125a 100644
--- a/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
+++ b/SmartDeviceLink-iOS.xcodeproj/project.pbxproj
@@ -1088,6 +1088,12 @@
5DA026901AD44EE700019F86 /* SDLDialNumberResponseSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA0268F1AD44EE700019F86 /* SDLDialNumberResponseSpec.m */; };
5DA102A41D4122C700C15826 /* NSMutableDictionary+SafeRemove.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA102A21D4122C700C15826 /* NSMutableDictionary+SafeRemove.h */; };
5DA102A51D4122C700C15826 /* NSMutableDictionary+SafeRemove.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA102A31D4122C700C15826 /* NSMutableDictionary+SafeRemove.m */; };
+ 5DA150C72271FDC20032928D /* SDLSoftButtonTransitionOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA150C52271FDC20032928D /* SDLSoftButtonTransitionOperation.h */; };
+ 5DA150C82271FDC20032928D /* SDLSoftButtonTransitionOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA150C62271FDC20032928D /* SDLSoftButtonTransitionOperation.m */; };
+ 5DA150CD2271FE180032928D /* SDLSoftButtonReplaceOperation.h in Headers */ = {isa = PBXBuildFile; fileRef = 5DA150CB2271FE180032928D /* SDLSoftButtonReplaceOperation.h */; };
+ 5DA150CE2271FE180032928D /* SDLSoftButtonReplaceOperation.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA150CC2271FE180032928D /* SDLSoftButtonReplaceOperation.m */; };
+ 5DA150D1227367580032928D /* SDLSoftButtonTransitionOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA150D0227367580032928D /* SDLSoftButtonTransitionOperationSpec.m */; };
+ 5DA150D32273676A0032928D /* SDLSoftButtonReplaceOperationSpec.m in Sources */ = {isa = PBXBuildFile; fileRef = 5DA150D22273676A0032928D /* SDLSoftButtonReplaceOperationSpec.m */; };
5DA22CB71D075CF200245F5F /* Nimble.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DA22CB31D075CF200245F5F /* Nimble.framework */; };
5DA22CB81D075CF200245F5F /* OCMock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DA22CB41D075CF200245F5F /* OCMock.framework */; };
5DA22CBA1D075CF200245F5F /* Quick.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5DA22CB61D075CF200245F5F /* Quick.framework */; };
@@ -2717,6 +2723,12 @@
5DA0268F1AD44EE700019F86 /* SDLDialNumberResponseSpec.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = SDLDialNumberResponseSpec.m; sourceTree = "<group>"; };
5DA102A21D4122C700C15826 /* NSMutableDictionary+SafeRemove.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = "NSMutableDictionary+SafeRemove.h"; sourceTree = "<group>"; };
5DA102A31D4122C700C15826 /* NSMutableDictionary+SafeRemove.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = "NSMutableDictionary+SafeRemove.m"; sourceTree = "<group>"; };
+ 5DA150C52271FDC20032928D /* SDLSoftButtonTransitionOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSoftButtonTransitionOperation.h; sourceTree = "<group>"; };
+ 5DA150C62271FDC20032928D /* SDLSoftButtonTransitionOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSoftButtonTransitionOperation.m; sourceTree = "<group>"; };
+ 5DA150CB2271FE180032928D /* SDLSoftButtonReplaceOperation.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SDLSoftButtonReplaceOperation.h; sourceTree = "<group>"; };
+ 5DA150CC2271FE180032928D /* SDLSoftButtonReplaceOperation.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSoftButtonReplaceOperation.m; sourceTree = "<group>"; };
+ 5DA150D0227367580032928D /* SDLSoftButtonTransitionOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; name = SDLSoftButtonTransitionOperationSpec.m; path = DevAPISpecs/SDLSoftButtonTransitionOperationSpec.m; sourceTree = "<group>"; };
+ 5DA150D22273676A0032928D /* SDLSoftButtonReplaceOperationSpec.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = SDLSoftButtonReplaceOperationSpec.m; sourceTree = "<group>"; };
5DA22CB31D075CF200245F5F /* Nimble.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Nimble.framework; path = sdl_ios/Carthage/Build/iOS/Nimble.framework; sourceTree = "<group>"; };
5DA22CB41D075CF200245F5F /* OCMock.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = OCMock.framework; path = sdl_ios/Carthage/Build/iOS/OCMock.framework; sourceTree = "<group>"; };
5DA22CB61D075CF200245F5F /* Quick.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Quick.framework; path = sdl_ios/Carthage/Build/iOS/Quick.framework; sourceTree = "<group>"; };
@@ -3726,10 +3738,8 @@
children = (
5D0A7380203F23F30001595D /* SDLSoftButtonManager.h */,
5D0A7381203F23F30001595D /* SDLSoftButtonManager.m */,
- 5D0A7384203F24060001595D /* SDLSoftButtonState.h */,
- 5D0A7385203F24060001595D /* SDLSoftButtonState.m */,
- 5D0A7388203F24320001595D /* SDLSoftButtonObject.h */,
- 5D0A7389203F24320001595D /* SDLSoftButtonObject.m */,
+ 5DA150CA2271FE030032928D /* Buttons */,
+ 5DA150C92271FDF90032928D /* Operations */,
);
name = "Soft Button";
sourceTree = "<group>";
@@ -5214,6 +5224,37 @@
name = Permissions;
sourceTree = "<group>";
};
+ 5DA150C92271FDF90032928D /* Operations */ = {
+ isa = PBXGroup;
+ children = (
+ 5DA150C52271FDC20032928D /* SDLSoftButtonTransitionOperation.h */,
+ 5DA150C62271FDC20032928D /* SDLSoftButtonTransitionOperation.m */,
+ 5DA150CB2271FE180032928D /* SDLSoftButtonReplaceOperation.h */,
+ 5DA150CC2271FE180032928D /* SDLSoftButtonReplaceOperation.m */,
+ );
+ name = Operations;
+ sourceTree = "<group>";
+ };
+ 5DA150CA2271FE030032928D /* Buttons */ = {
+ isa = PBXGroup;
+ children = (
+ 5D0A7384203F24060001595D /* SDLSoftButtonState.h */,
+ 5D0A7385203F24060001595D /* SDLSoftButtonState.m */,
+ 5D0A7388203F24320001595D /* SDLSoftButtonObject.h */,
+ 5D0A7389203F24320001595D /* SDLSoftButtonObject.m */,
+ );
+ name = Buttons;
+ sourceTree = "<group>";
+ };
+ 5DA150CF227366900032928D /* Operations */ = {
+ isa = PBXGroup;
+ children = (
+ 5DA150D0227367580032928D /* SDLSoftButtonTransitionOperationSpec.m */,
+ 5DA150D22273676A0032928D /* SDLSoftButtonReplaceOperationSpec.m */,
+ );
+ name = Operations;
+ sourceTree = "<group>";
+ };
5DA23FF11F2FA32A009C0313 /* Audio Service */ = {
isa = PBXGroup;
children = (
@@ -5377,6 +5418,7 @@
5DAD5F8220507DED0025624C /* Soft Button */ = {
isa = PBXGroup;
children = (
+ 5DA150CF227366900032928D /* Operations */,
5DAD5F8820508F090025624C /* SDLSoftButtonStateSpec.m */,
5DAD5F8A20508F140025624C /* SDLSoftButtonObjectSpec.m */,
5DAD5F8C20508F220025624C /* SDLSoftButtonManagerSpec.m */,
@@ -6212,6 +6254,7 @@
5D61FC451A84238C00846EE7 /* SDLAudioPassThruCapabilities.h in Headers */,
5D61FDC71A84238C00846EE7 /* SDLTextAlignment.h in Headers */,
5D61FD051A84238C00846EE7 /* SDLOnButtonPress.h in Headers */,
+ 5DA150C72271FDC20032928D /* SDLSoftButtonTransitionOperation.h in Headers */,
5D61FCC51A84238C00846EE7 /* SDLHMIZoneCapabilities.h in Headers */,
884AF94F220B488900E22928 /* SDLOnSystemCapabilityUpdated.h in Headers */,
880D267D220DE5DF00B3F496 /* SDLWeatherServiceManifest.h in Headers */,
@@ -6400,6 +6443,7 @@
5D61FDE31A84238C00846EE7 /* SDLUnregisterAppInterface.h in Headers */,
5D61FD331A84238C00846EE7 /* SDLPowerModeQualificationStatus.h in Headers */,
5D92937020B5E0E500FCC775 /* SDLDeleteChoicesOperation.h in Headers */,
+ 5DA150CD2271FE180032928D /* SDLSoftButtonReplaceOperation.h in Headers */,
5D61FE011A84238C00846EE7 /* SDLVehicleDataNotificationStatus.h in Headers */,
5D61FDC91A84238C00846EE7 /* SDLTextField.h in Headers */,
5D6F7A351BC5B9B60070BF37 /* SDLLockScreenViewController.h in Headers */,
@@ -6683,7 +6727,7 @@
};
5D61FA1B1A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
- LastSwiftMigration = 0940;
+ LastSwiftMigration = 1020;
};
5D61FA251A84237100846EE7 = {
CreatedOnToolsVersion = 6.1.1;
@@ -7035,6 +7079,7 @@
5D61FD461A84238C00846EE7 /* SDLProtocolHeader.m in Sources */,
5DD8406320FCD6C10082CE04 /* SDLElectronicParkBrakeStatus.m in Sources */,
8BBEA6071F324165003EEA26 /* SDLMetadataType.m in Sources */,
+ 5DA150C82271FDC20032928D /* SDLSoftButtonTransitionOperation.m in Sources */,
5D61FDBC1A84238C00846EE7 /* SDLSystemAction.m in Sources */,
5D61FC381A84238C00846EE7 /* SDLAlert.m in Sources */,
88AAD4BD2211B76800F1E6D7 /* SDLMediaServiceManifest.m in Sources */,
@@ -7097,6 +7142,7 @@
1E5AD0651F207DD50029B8AF /* SDLTemperature.m in Sources */,
DA9F7E901DCC04C000ACAE48 /* SDLUnsubscribeWayPointsResponse.m in Sources */,
88F65137220C74FD00CAF321 /* SDLWeatherData.m in Sources */,
+ 5DA150CE2271FE180032928D /* SDLSoftButtonReplaceOperation.m in Sources */,
5DE372A21ACB2ED300849FAA /* SDLHMICapabilities.m in Sources */,
5D61FDD41A84238C00846EE7 /* SDLTouchEvent.m in Sources */,
5D61FD881A84238C00846EE7 /* SDLSetGlobalProperties.m in Sources */,
@@ -7456,6 +7502,7 @@
162E83171A9BDE8B00906325 /* SDLOnHMIStatusSpec.m in Sources */,
8831FA392201E3D100B8FFB7 /* SDLAppServiceManifestSpec.m in Sources */,
5DE372A41ACB336600849FAA /* SDLHMICapabilitiesSpec.m in Sources */,
+ 5DA150D32273676A0032928D /* SDLSoftButtonReplaceOperationSpec.m in Sources */,
162E82F71A9BDE8B00906325 /* SDLResultSpec.m in Sources */,
1680B1141A9CD7AD00DBD79E /* SDLV1ProtocolHeaderSpec.m in Sources */,
880D2680220E038800B3F496 /* SDLWeatherServiceManifestSpec.m in Sources */,
@@ -7676,6 +7723,7 @@
5D0A9F931F15560B00CC80DD /* SDLNavigationCapabilitySpec.m in Sources */,
5D64FE7120DA9E6700792F9F /* SDLStreamingAudioLifecycleManagerSpec.m in Sources */,
162E83891A9BDE8B00906325 /* SDLScreenParamsSpec.m in Sources */,
+ 5DA150D1227367580032928D /* SDLSoftButtonTransitionOperationSpec.m in Sources */,
162E83441A9BDE8B00906325 /* SDLSystemRequestSpec.m in Sources */,
162E83001A9BDE8B00906325 /* SDLTextFieldNameSpec.m in Sources */,
DA4353EA1D2721680099B8C4 /* SDLTouchManagerSpec.m in Sources */,
diff --git a/SmartDeviceLink/SDLAsynchronousOperation.h b/SmartDeviceLink/SDLAsynchronousOperation.h
index ad0697b2a..b7e0e0614 100644
--- a/SmartDeviceLink/SDLAsynchronousOperation.h
+++ b/SmartDeviceLink/SDLAsynchronousOperation.h
@@ -12,6 +12,7 @@
@property (copy, nonatomic, readonly, nullable) NSError *error;
+- (void)start;
- (void)finishOperation;
@end
diff --git a/SmartDeviceLink/SDLChoiceSetManager.m b/SmartDeviceLink/SDLChoiceSetManager.m
index 21dff1cc5..36914d553 100644
--- a/SmartDeviceLink/SDLChoiceSetManager.m
+++ b/SmartDeviceLink/SDLChoiceSetManager.m
@@ -372,7 +372,7 @@ UInt16 const ChoiceCellIdMin = 1;
}
}
-#pragma mark - Setters
+#pragma mark - Keyboard Configuration
- (void)setKeyboardConfiguration:(nullable SDLKeyboardProperties *)keyboardConfiguration {
if (keyboardConfiguration == nil) {
diff --git a/SmartDeviceLink/SDLLogFileModuleMap.m b/SmartDeviceLink/SDLLogFileModuleMap.m
index 9af182d88..2c4dfd731 100644
--- a/SmartDeviceLink/SDLLogFileModuleMap.m
+++ b/SmartDeviceLink/SDLLogFileModuleMap.m
@@ -87,7 +87,7 @@
}
+ (SDLLogFileModule *)sdl_screenManagerSoftButtonModule {
- return [SDLLogFileModule moduleWithName:@"Screen/SoftButton" files:[NSSet setWithArray:@[@"SDLSoftButtonManager", @"SDLSoftButtonObject", @"SDLSoftButtonState"]]];
+ return [SDLLogFileModule moduleWithName:@"Screen/SoftButton" files:[NSSet setWithArray:@[@"SDLSoftButtonManager", @"SDLSoftButtonObject", @"SDLSoftButtonState", @"SDLSoftButtonTransitionOperation", @"SDLSoftButtonReplaceOperation"]]];
}
+ (SDLLogFileModule *)sdl_screenManagerMenuModule {
diff --git a/SmartDeviceLink/SDLScreenManager.m b/SmartDeviceLink/SDLScreenManager.m
index bfd8c9b71..6365a1efc 100644
--- a/SmartDeviceLink/SDLScreenManager.m
+++ b/SmartDeviceLink/SDLScreenManager.m
@@ -237,7 +237,6 @@ NS_ASSUME_NONNULL_BEGIN
self.textAndGraphicManager.batchUpdates = NO;
[self.textAndGraphicManager updateWithCompletionHandler:handler];
- [self.softButtonManager updateWithCompletionHandler:handler];
}
#pragma mark - Choice Sets
diff --git a/SmartDeviceLink/SDLSoftButtonManager.h b/SmartDeviceLink/SDLSoftButtonManager.h
index b163fe599..dfd3d7104 100644
--- a/SmartDeviceLink/SDLSoftButtonManager.h
+++ b/SmartDeviceLink/SDLSoftButtonManager.h
@@ -35,6 +35,9 @@ typedef void(^SDLSoftButtonUpdateCompletionHandler)(NSError *__nullable error);
*/
@property (copy, nonatomic) NSArray<SDLSoftButtonObject *> *softButtonObjects;
+/**
+ All transitions made in-between beginUpdates and this method will occur as one RPC update.
+ */
@property (assign, nonatomic, getter=isBatchingUpdates) BOOL batchUpdates;
- (instancetype)init NS_UNAVAILABLE;
@@ -54,13 +57,6 @@ typedef void(^SDLSoftButtonUpdateCompletionHandler)(NSError *__nullable error);
- (void)stop;
/**
- Cause all transitions in between `beginUpdates` and this method call to occur in one RPC update.
-
- @param handler The handler called once the update is completed.
- */
-- (void)updateWithCompletionHandler:(nullable SDLSoftButtonUpdateCompletionHandler)handler;
-
-/**
Returns a soft button object associated with the manager that is named the specified name or nil if nothing corresponds.
@param name The name to find a soft button for
diff --git a/SmartDeviceLink/SDLSoftButtonManager.m b/SmartDeviceLink/SDLSoftButtonManager.m
index 3f19ff1e1..56deb83b0 100644
--- a/SmartDeviceLink/SDLSoftButtonManager.m
+++ b/SmartDeviceLink/SDLSoftButtonManager.m
@@ -22,7 +22,9 @@
#import "SDLSoftButton.h"
#import "SDLSoftButtonCapabilities.h"
#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonReplaceOperation.h"
#import "SDLSoftButtonState.h"
+#import "SDLSoftButtonTransitionOperation.h"
NS_ASSUME_NONNULL_BEGIN
@@ -35,23 +37,16 @@ NS_ASSUME_NONNULL_BEGIN
@interface SDLSoftButtonManager()
-@property (strong, nonatomic) NSArray<SDLSoftButton *> *currentSoftButtons;
-
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (weak, nonatomic) SDLFileManager *fileManager;
-@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate;
-@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler inProgressHandler;
-
-@property (assign, nonatomic) BOOL hasQueuedUpdate;
-@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler queuedUpdateHandler;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
@property (copy, nonatomic, nullable) SDLHMILevel currentLevel;
@property (strong, nonatomic, nullable) SDLDisplayCapabilities *displayCapabilities;
@property (strong, nonatomic, nullable) SDLSoftButtonCapabilities *softButtonCapabilities;
-@property (assign, nonatomic) BOOL waitingOnHMILevelUpdateToUpdate;
-@property (assign, nonatomic) BOOL isDirty;
+@property (strong, nonatomic) NSMutableArray<SDLAsynchronousOperation *> *batchQueue;
@end
@@ -66,8 +61,8 @@ NS_ASSUME_NONNULL_BEGIN
_softButtonObjects = @[];
_currentLevel = nil;
- _waitingOnHMILevelUpdateToUpdate = NO;
- _isDirty = NO;
+ _transactionQueue = [self sdl_newTransactionQueue];
+ _batchQueue = [NSMutableArray array];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_registerResponse:) name:SDLDidReceiveRegisterAppInterfaceResponse object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_displayLayoutResponse:) name:SDLDidReceiveSetDisplayLayoutResponse object:nil];
@@ -79,35 +74,31 @@ NS_ASSUME_NONNULL_BEGIN
- (void)stop {
_softButtonObjects = @[];
_currentMainField1 = nil;
-
- _inProgressUpdate = nil;
- _inProgressHandler = nil;
- _hasQueuedUpdate = NO;
- _queuedUpdateHandler = nil;
_currentLevel = nil;
_displayCapabilities = nil;
_softButtonCapabilities = nil;
- _waitingOnHMILevelUpdateToUpdate = NO;
- _isDirty = NO;
+
+ [_transactionQueue cancelAllOperations];
+ self.transactionQueue = [self sdl_newTransactionQueue];
+}
+
+- (NSOperationQueue *)sdl_newTransactionQueue {
+ NSOperationQueue *queue = [[NSOperationQueue alloc] init];
+ queue.name = @"SDLSoftButtonManager Transaction Queue";
+ queue.maxConcurrentOperationCount = 1;
+ queue.qualityOfService = NSQualityOfServiceUserInitiated;
+ queue.suspended = YES;
+
+ return queue;
}
+
+#pragma mark - Sending Soft Buttons
+
- (void)setSoftButtonObjects:(NSArray<SDLSoftButtonObject *> *)softButtonObjects {
// Only update if something changed. This prevents, for example, an empty array being reset
if (_softButtonObjects == softButtonObjects) {
return;
- } else {
- self.isDirty = YES;
- }
-
- self.inProgressUpdate = nil;
- if (self.inProgressHandler != nil) {
- self.inProgressHandler([NSError sdl_softButtonManager_pendingUpdateSuperseded]);
- self.inProgressHandler = nil;
- }
- self.hasQueuedUpdate = NO;
- if (self.queuedUpdateHandler != nil) {
- self.queuedUpdateHandler([NSError sdl_softButtonManager_pendingUpdateSuperseded]);
- self.queuedUpdateHandler = nil;
}
// Set the soft button ids. Check to make sure no two soft buttons have the same name, there aren't many soft buttons, so n^2 isn't going to be bad
@@ -129,231 +120,72 @@ NS_ASSUME_NONNULL_BEGIN
_softButtonObjects = softButtonObjects;
- [self updateWithCompletionHandler:nil];
-}
-
-- (nullable SDLSoftButtonObject *)softButtonObjectNamed:(NSString *)name {
- for (SDLSoftButtonObject *object in self.softButtonObjects) {
- if ([object.name isEqualToString:name]) {
- return object;
- }
- }
-
- return nil;
-}
-
-- (void)sdl_transitionSoftButton:(SDLSoftButtonObject *)softButton {
- self.isDirty = YES;
- [self updateWithCompletionHandler:nil];
-}
-
-- (void)updateWithCompletionHandler:(nullable SDLSoftButtonUpdateCompletionHandler)handler {
- // Don't send if we're batching
- if (self.isBatchingUpdates || !self.isDirty) { return; }
+ SDLSoftButtonReplaceOperation *op = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager capabilities:self.softButtonCapabilities softButtonObjects:_softButtonObjects mainField1:self.currentMainField1];
- // Don't send if we're in HMI NONE
- if (self.currentLevel == nil || [self.currentLevel isEqualToString:SDLHMILevelNone]) {
- self.waitingOnHMILevelUpdateToUpdate = YES;
- return;
+ if (self.isBatchingUpdates) {
+ [self.batchQueue removeAllObjects];
+ [self.batchQueue addObject:op];
} else {
- self.waitingOnHMILevelUpdateToUpdate = NO;
+ [self.transactionQueue cancelAllOperations];
+ [self.transactionQueue addOperation:op];
}
-
- [self sdl_updateWithCompletionHandler:handler];
}
-- (void)sdl_updateWithCompletionHandler:(nullable SDLSoftButtonUpdateCompletionHandler)handler {
- SDLLogD(@"Updating soft buttons");
- self.isDirty = NO;
-
- if (self.inProgressUpdate != nil) {
- SDLLogV(@"In progress update exists, queueing update");
- // If we already have a pending update, we're going to tell the old handler that it was superseded by a new update and then return
- if (self.queuedUpdateHandler != nil) {
- SDLLogV(@"Queued update already exists, superseding previous queued update");
- self.queuedUpdateHandler([NSError sdl_softButtonManager_pendingUpdateSuperseded]);
- self.queuedUpdateHandler = nil;
- }
+- (void)sdl_transitionSoftButton:(SDLSoftButtonObject *)softButton {
+ SDLSoftButtonTransitionOperation *op = [[SDLSoftButtonTransitionOperation alloc] initWithConnectionManager:self.connectionManager capabilities:self.softButtonCapabilities softButtons:self.softButtonObjects mainField1:self.currentMainField1];
- if (handler != nil) {
- self.queuedUpdateHandler = handler;
- } else {
- self.hasQueuedUpdate = YES;
+ if (self.isBatchingUpdates) {
+ for (SDLAsynchronousOperation *sbOperation in self.batchQueue) {
+ if ([sbOperation isMemberOfClass:[SDLSoftButtonTransitionOperation class]]) {
+ [self.batchQueue removeObject:sbOperation];
+ }
}
- return;
- }
-
- self.inProgressHandler = [handler copy];
- self.inProgressUpdate = [[SDLShow alloc] init];
- self.inProgressUpdate.mainField1 = self.currentMainField1 ?: @"";
-
- if ([self sdl_supportsSoftButtonImages]) {
- [self sdl_uploadInitialStateImages];
- [self sdl_uploadOtherStateImages];
- }
-
- if (self.softButtonObjects == nil) {
- SDLLogV(@"Soft button objects are nil, sending an empty array");
- self.inProgressUpdate.softButtons = @[];
- } else if (([self sdl_currentStateHasImages] && ![self sdl_allCurrentStateImagesAreUploaded])
- || ![self sdl_supportsSoftButtonImages]) {
- // The images don't yet exist on the head unit, or we cannot use images, send a text update, if possible. Otherwise, don't send anything yet.
- NSArray<SDLSoftButton *> *textOnlyButtons = [self sdl_textButtonsForCurrentState];
- if (textOnlyButtons != nil) {
- SDLLogV(@"Soft button images unavailable, sending text buttons");
- self.inProgressUpdate.softButtons = textOnlyButtons;
- } else {
- SDLLogV(@"Soft button images unavailable, text buttons unavailable");
- self.inProgressUpdate = nil;
- return;
- }
+ [self.batchQueue addObject:op];
} else {
- SDLLogV(@"Sending soft buttons with images");
- self.inProgressUpdate.softButtons = [self sdl_softButtonsForCurrentState];
+ [self.transactionQueue addOperation:op];
}
-
- __weak typeof(self) weakSelf = self;
- [self.connectionManager sendConnectionRequest:self.inProgressUpdate withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
- __strong typeof(weakSelf) strongSelf = weakSelf;
-
- SDLLogD(@"Soft button update completed");
-
- strongSelf.inProgressUpdate = nil;
- if (strongSelf.inProgressHandler != nil) {
- strongSelf.inProgressHandler(error);
- strongSelf.inProgressHandler = nil;
- }
-
- if (strongSelf.hasQueuedUpdate) {
- SDLLogV(@"Queued update exists, sending another update");
- [strongSelf updateWithCompletionHandler:[strongSelf.queuedUpdateHandler copy]];
- strongSelf.queuedUpdateHandler = nil;
- strongSelf.hasQueuedUpdate = NO;
- }
- }];
-}
-
-#pragma mark - Images
-
-- (BOOL)sdl_currentStateHasImages {
- for (SDLSoftButtonObject *object in self.softButtonObjects) {
- if (object.currentState.artwork != nil) {
- return YES;
- }
- }
-
- return NO;
}
-- (BOOL)sdl_allCurrentStateImagesAreUploaded {
- for (SDLSoftButtonObject *button in self.softButtonObjects) {
- SDLArtwork *artwork = button.currentState.artwork;
- if (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon) {
- return NO;
- }
- }
- return YES;
-}
+#pragma mark - Getting Soft Buttons
-- (BOOL)sdl_supportsSoftButtonImages {
- BOOL supportsGraphics = self.displayCapabilities ? self.displayCapabilities.graphicSupported.boolValue : YES;
- BOOL supportsSoftButtonImages = self.softButtonCapabilities ? self.softButtonCapabilities.imageSupported.boolValue : NO;
-
- return (supportsGraphics && supportsSoftButtonImages);
-}
-
-- (void)sdl_uploadInitialStateImages {
- NSMutableArray<SDLArtwork *> *initialStatesToBeUploaded = [NSMutableArray array];
- // Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready.
+- (nullable SDLSoftButtonObject *)softButtonObjectNamed:(NSString *)name {
for (SDLSoftButtonObject *object in self.softButtonObjects) {
- if ([self sdl_artworkNeedsUpload:object.currentState.artwork]) {
- [initialStatesToBeUploaded addObject:object.currentState.artwork];
+ if ([object.name isEqualToString:name]) {
+ return object;
}
}
- // Upload initial images, then other state images
- if (initialStatesToBeUploaded.count > 0) {
- SDLLogD(@"Uploading soft button initial artworks");
- [self.fileManager uploadArtworks:[initialStatesToBeUploaded copy] completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error uploading soft button artworks: %@", error);
- }
-
- SDLLogD(@"Soft button initial artworks uploaded");
- [self sdl_updateWithCompletionHandler:nil];
- }];
- }
+ return nil;
}
-- (void)sdl_uploadOtherStateImages {
- NSMutableArray<SDLArtwork *> *otherStatesToBeUploaded = [NSMutableArray array];
- // Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready.
- for (SDLSoftButtonObject *object in self.softButtonObjects) {
- for (SDLSoftButtonState *state in object.states) {
- if ([state.name isEqualToString:object.currentState.name]) { continue; }
- if ([self sdl_artworkNeedsUpload:state.artwork]) {
- [otherStatesToBeUploaded addObject:state.artwork];
- }
- }
- }
- if (otherStatesToBeUploaded.count > 0) {
- SDLLogD(@"Uploading soft button other state artworks");
- [self.fileManager uploadArtworks:[otherStatesToBeUploaded copy] completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
- if (error != nil) {
- SDLLogE(@"Error uploading soft button artworks: %@", error);
- }
+#pragma mark - Getters / Setters
- SDLLogD(@"Soft button other state artworks uploaded");
- // In case our soft button states have changed in the meantime
- [self sdl_updateWithCompletionHandler:nil];
- }];
- }
-}
+- (void)setBatchUpdates:(BOOL)batchUpdates {
+ _batchUpdates = batchUpdates;
-- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
- return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon);
+ if (!_batchUpdates) {
+ [self.transactionQueue addOperations:[self.batchQueue copy] waitUntilFinished:NO];
+ [self.batchQueue removeAllObjects];
+ }
}
-#pragma mark - Creating Soft Buttons
-
-/**
- Returns text soft buttons representing the initial states of the button objects, or nil if _any_ of the buttons' current states are image only buttons.
+- (void)setCurrentMainField1:(nullable NSString *)currentMainField1 {
+ _currentMainField1 = currentMainField1;
- @return The text soft buttons
- */
-- (nullable NSArray<SDLSoftButton *> *)sdl_textButtonsForCurrentState {
- NSMutableArray<SDLSoftButton *> *textButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
- for (SDLSoftButtonObject *buttonObject in self.softButtonObjects) {
- SDLSoftButton *button = buttonObject.currentStateSoftButton;
- if (button.text == nil) {
- return nil;
+ for (NSUInteger i = 0; i < self.transactionQueue.operations.count; i++) {
+ if ([self.transactionQueue.operations[i] isMemberOfClass:[SDLSoftButtonReplaceOperation class]]) {
+ SDLSoftButtonReplaceOperation *op = self.transactionQueue.operations[i];
+ op.mainField1 = currentMainField1;
+ } else if ([self.transactionQueue.operations[i] isMemberOfClass:[SDLSoftButtonTransitionOperation class]]) {
+ SDLSoftButtonTransitionOperation *op = self.transactionQueue.operations[i];
+ op.mainField1 = currentMainField1;
}
-
- button.image = nil;
- button.type = SDLSoftButtonTypeText;
- [textButtons addObject:button];
- }
-
- return [textButtons copy];
-}
-
-- (NSArray<SDLSoftButton *> *)sdl_softButtonsForCurrentState {
- NSMutableArray<SDLSoftButton *> *softButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
- for (SDLSoftButtonObject *button in self.softButtonObjects) {
- [softButtons addObject:button.currentStateSoftButton];
}
-
- return [softButtons copy];
}
-#pragma mark - Getters
-
-- (BOOL)hasQueuedUpdate {
- return (_queuedUpdateHandler != nil ?: _hasQueuedUpdate);
-}
#pragma mark - RPC Responses
@@ -382,9 +214,10 @@ NS_ASSUME_NONNULL_BEGIN
self.softButtonCapabilities = response.softButtonCapabilities ? response.softButtonCapabilities.firstObject : nil;
self.displayCapabilities = response.displayCapabilities;
- // Auto-send an updated Show
+ // Auto-send an updated Show to account for changes to the capabilities
if (self.softButtonObjects.count > 0) {
- [self updateWithCompletionHandler:nil];
+ SDLSoftButtonReplaceOperation *op = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:self.connectionManager fileManager:self.fileManager capabilities:self.softButtonCapabilities softButtonObjects:self.softButtonObjects mainField1:self.currentMainField1];
+ [self.transactionQueue addOperation:op];
}
}
@@ -392,12 +225,15 @@ NS_ASSUME_NONNULL_BEGIN
SDLOnHMIStatus *hmiStatus = (SDLOnHMIStatus *)notification.notification;
SDLHMILevel oldHMILevel = self.currentLevel;
- self.currentLevel = hmiStatus.hmiLevel;
-
- // Auto-send an updated show if we were in NONE and now we are not
- if ([oldHMILevel isEqualToString:SDLHMILevelNone] && ![self.currentLevel isEqualToString:SDLHMILevelNone] && self.waitingOnHMILevelUpdateToUpdate) {
- [self updateWithCompletionHandler:nil];
+ if (![oldHMILevel isEqualToEnum:hmiStatus.hmiLevel]) {
+ if ([hmiStatus.hmiLevel isEqualToEnum:SDLHMILevelNone]) {
+ self.transactionQueue.suspended = YES;
+ } else {
+ self.transactionQueue.suspended = NO;
+ }
}
+
+ self.currentLevel = hmiStatus.hmiLevel;
}
@end
diff --git a/SmartDeviceLink/SDLSoftButtonReplaceOperation.h b/SmartDeviceLink/SDLSoftButtonReplaceOperation.h
new file mode 100644
index 000000000..9adb5afb4
--- /dev/null
+++ b/SmartDeviceLink/SDLSoftButtonReplaceOperation.h
@@ -0,0 +1,45 @@
+//
+// SDLSoftButtonReplaceOperation.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/25/19.
+// Copyright © 2019 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLAsynchronousOperation.h"
+
+@class SDLSoftButtonCapabilities;
+@class SDLFileManager;
+@class SDLSoftButtonObject;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ This class is an operation that takes a set of soft buttons and replaces the old set of soft buttons with the new set based on the capabilities available on the system. This operation will handle sending placeholder soft buttons with only text (if possible), uploading the initial state images, sending the initial state soft buttons with those images, and then uploading the other state images.
+ */
+@interface SDLSoftButtonReplaceOperation : SDLAsynchronousOperation
+
+/**
+ The primary text field on the system template. This is necessary to HAX a workaround for Sync 3.
+ */
+@property (strong, nonatomic) NSString *mainField1;
+
+/**
+ Initialize the replace operation
+
+ @param connectionManager The manager that will send the resultant RPCs
+ @param fileManager The file manager that will handle uploading any images
+ @param capabilities The capabilites of the soft buttons on the current template
+ @param softButtonObjects The soft buttons that should be sent
+ @param mainField1 The primary text field of the system template
+ @return The operation
+ */
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager capabilities:(SDLSoftButtonCapabilities *)capabilities softButtonObjects:(NSArray<SDLSoftButtonObject *> *)softButtonObjects mainField1:(NSString *)mainField1;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSoftButtonReplaceOperation.m b/SmartDeviceLink/SDLSoftButtonReplaceOperation.m
new file mode 100644
index 000000000..82f5b4ffe
--- /dev/null
+++ b/SmartDeviceLink/SDLSoftButtonReplaceOperation.m
@@ -0,0 +1,294 @@
+//
+// SDLSoftButtonReplaceOperation.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/25/19.
+// Copyright © 2019 smartdevicelink. All rights reserved.
+//
+
+#import "SDLSoftButtonReplaceOperation.h"
+
+#import "SDLArtwork.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDisplayCapabilities+ShowManagerExtensions.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLShow.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLSoftButtonReplaceOperation ()
+
+@property (strong, nonatomic) SDLSoftButtonCapabilities *softButtonCapabilities;
+@property (strong, nonatomic) NSArray<SDLSoftButtonObject *> *softButtonObjects;
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (weak, nonatomic) SDLFileManager *fileManager;
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+@end
+
+@implementation SDLSoftButtonReplaceOperation
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager fileManager:(SDLFileManager *)fileManager capabilities:(SDLSoftButtonCapabilities *)capabilities softButtonObjects:(NSArray<SDLSoftButtonObject *> *)softButtonObjects mainField1:(NSString *)mainField1 {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _fileManager = fileManager;
+ _softButtonCapabilities = capabilities;
+ _softButtonObjects = softButtonObjects;
+ _mainField1 = mainField1;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+
+ // Check the state of our images
+ if (![self sdl_supportsSoftButtonImages]) {
+ // We don't support images at all
+ SDLLogW(@"Soft button images are not supported. Attempting to send text-only soft buttons. If any button does not contain text, no buttons will be sent.");
+
+ // Send text buttons if all the soft buttons have text
+ [self sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:^{}];
+ } else if ([self sdl_currentStateHasImages] && ![self sdl_allCurrentStateImagesAreUploaded]) {
+ // If there are images that aren't uploaded
+ // Send text buttons if all the soft buttons have text
+ [self sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:^{}];
+
+ // Upload initial images
+ __weak typeof(self) weakself = self;
+ [self sdl_uploadInitialStateImagesWithCompletionHandler:^{
+ // Send initial soft buttons w/ images
+ [weakself sdl_sendCurrentStateSoftButtonsWithCompletionHandler:^{
+ // Upload other images
+ [weakself sdl_uploadOtherStateImagesWithCompletionHandler:^{
+ __strong typeof(weakself) strongself = weakself;
+ SDLLogV(@"Finished sending other images for soft buttons");
+ [strongself finishOperation];
+ }];
+ }];
+ }];
+ } else {
+ // All the images are already uploaded. Send initial soft buttons w/ images.
+ __weak typeof(self) weakself = self;
+ [self sdl_sendCurrentStateSoftButtonsWithCompletionHandler:^{
+ __strong typeof(weakself) strongself = weakself;
+ SDLLogV(@"Finished sending soft buttons with images");
+ [strongself finishOperation];
+ }];
+ }
+}
+
+
+#pragma mark - Uploading Images
+
+- (void)sdl_uploadInitialStateImagesWithCompletionHandler:(void (^)(void))handler {
+ // Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready.
+ NSMutableArray<SDLArtwork *> *initialStatesToBeUploaded = [NSMutableArray array];
+ for (SDLSoftButtonObject *object in self.softButtonObjects) {
+ if ([self sdl_artworkNeedsUpload:object.currentState.artwork]) {
+ [initialStatesToBeUploaded addObject:object.currentState.artwork];
+ }
+ }
+
+ if (initialStatesToBeUploaded.count == 0) {
+ SDLLogV(@"No initial state artworks to upload");
+ handler();
+ return;
+ }
+
+ SDLLogD(@"Uploading soft button initial artworks");
+ NSMutableArray<NSString *> *failedArtworkNames = [NSMutableArray array];
+ __weak typeof(self) weakself = self;
+ [self.fileManager uploadArtworks:[initialStatesToBeUploaded copy] progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) {
+ SDLLogV(@"Uploaded initial state artwork: %@, error: %@, percent complete: %f.2%%", artworkName, error, uploadPercentage * 100);
+ if (error != nil) {
+ [failedArtworkNames addObject:artworkName];
+ }
+
+ if (weakself.isCancelled) {
+ [weakself finishOperation];
+ return NO;
+ }
+
+ return YES;
+ } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading soft button artworks: %@", error);
+ } else {
+ SDLLogD(@"Soft button initial state artworks uploaded");
+ }
+
+ handler();
+ }];
+}
+
+- (void)sdl_uploadOtherStateImagesWithCompletionHandler:(void (^)(void))handler {
+ NSMutableArray<SDLArtwork *> *otherStatesToBeUploaded = [NSMutableArray array];
+ // Upload all soft button images, the initial state images first, then the other states. We need to send updates when the initial state is ready.
+ for (SDLSoftButtonObject *object in self.softButtonObjects) {
+ for (SDLSoftButtonState *state in object.states) {
+ if ([state.name isEqualToString:object.currentState.name]) { continue; }
+ if ([self sdl_artworkNeedsUpload:state.artwork]) {
+ [otherStatesToBeUploaded addObject:state.artwork];
+ }
+ }
+ }
+
+ if (otherStatesToBeUploaded.count == 0) {
+ SDLLogV(@"No other state artworks to upload");
+ handler();
+ return;
+ }
+
+ SDLLogD(@"Uploading soft button other state artworks");
+ NSMutableArray<NSString *> *failedArtworkNames = [NSMutableArray array];
+ __weak typeof(self) weakself = self;
+ [self.fileManager uploadArtworks:[otherStatesToBeUploaded copy] progressHandler:^BOOL(NSString * _Nonnull artworkName, float uploadPercentage, NSError * _Nullable error) {
+ SDLLogV(@"Uploaded other state artwork: %@, error: %@, percent complete: %f.2%%", artworkName, error, uploadPercentage * 100);
+ if (error != nil) {
+ [failedArtworkNames addObject:artworkName];
+ }
+
+ if (weakself.isCancelled) {
+ [weakself finishOperation];
+ return NO;
+ }
+
+ return YES;
+ } completionHandler:^(NSArray<NSString *> * _Nonnull artworkNames, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogE(@"Error uploading soft button artworks: %@", error);
+ } else {
+ SDLLogD(@"Soft button other state artworks uploaded");
+ }
+
+ handler();
+ }];
+}
+
+
+#pragma mark - Sending the Soft Buttons
+
+- (void)sdl_sendCurrentStateSoftButtonsWithCompletionHandler:(void (^)(void))handler {
+ if (self.isCancelled) {
+ [self finishOperation];
+ }
+
+ SDLLogV(@"Preparing to send full soft buttons");
+ NSMutableArray<SDLSoftButton *> *softButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
+ for (SDLSoftButtonObject *buttonObject in self.softButtonObjects) {
+ [softButtons addObject:buttonObject.currentStateSoftButton];
+ }
+
+ SDLShow *show = [[SDLShow alloc] init];
+ show.mainField1 = self.mainField1;
+ show.softButtons = [softButtons copy];
+
+ [self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogW(@"Failed to update soft buttons with text buttons: %@", error);
+ }
+
+ SDLLogD(@"Finished sending text only soft buttons");
+ handler();
+ }];
+}
+
+/**
+ Returns text soft buttons representing the current states of the button objects, or returns if _any_ of the buttons' current states are image only buttons.
+*/
+- (void)sdl_sendCurrentStateTextOnlySoftButtonsWithCompletionHandler:(void (^)(void))handler {
+ if (self.isCancelled) {
+ [self finishOperation];
+ }
+
+ SDLLogV(@"Preparing to send text-only soft buttons");
+ NSMutableArray<SDLSoftButton *> *textButtons = [NSMutableArray arrayWithCapacity:self.softButtonObjects.count];
+ for (SDLSoftButtonObject *buttonObject in self.softButtonObjects) {
+ SDLSoftButton *button = buttonObject.currentStateSoftButton;
+ if (button.text == nil) {
+ SDLLogW(@"Attempted to create text buttons, but some buttons don't support text, so no text-only soft buttons will be sent");
+ handler();
+ return;
+ }
+
+ button.image = nil;
+ button.type = SDLSoftButtonTypeText;
+ [textButtons addObject:button];
+ }
+
+ SDLShow *show = [[SDLShow alloc] init];
+ show.mainField1 = self.mainField1;
+ show.softButtons = [textButtons copy];
+
+ [self.connectionManager sendConnectionRequest:show withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogW(@"Failed to update soft buttons with text buttons: %@", error);
+ }
+
+ SDLLogD(@"Finished sending text only soft buttons");
+ handler();
+ }];
+}
+
+#pragma mark - Images
+
+- (BOOL)sdl_artworkNeedsUpload:(SDLArtwork *)artwork {
+ return (artwork != nil && ![self.fileManager hasUploadedFile:artwork] && !artwork.isStaticIcon);
+}
+
+- (BOOL)sdl_currentStateHasImages {
+ for (SDLSoftButtonObject *object in self.softButtonObjects) {
+ if (object.currentState.artwork != nil) {
+ return YES;
+ }
+ }
+
+ return NO;
+}
+
+- (BOOL)sdl_allCurrentStateImagesAreUploaded {
+ for (SDLSoftButtonObject *button in self.softButtonObjects) {
+ SDLArtwork *artwork = button.currentState.artwork;
+ if ([self sdl_artworkNeedsUpload:artwork]) {
+ return NO;
+ }
+ }
+
+ return YES;
+}
+
+- (BOOL)sdl_supportsSoftButtonImages {
+ return self.softButtonCapabilities ? self.softButtonCapabilities.imageSupported.boolValue : NO;
+}
+
+#pragma mark - Property Overrides
+
+- (void)finishOperation {
+ SDLLogV(@"Finishing soft button replace operation");
+ [super finishOperation];
+}
+
+- (nullable NSString *)name {
+ return @"com.sdl.softbuttonmanager.replace";
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSoftButtonTransitionOperation.h b/SmartDeviceLink/SDLSoftButtonTransitionOperation.h
new file mode 100644
index 000000000..2679c9895
--- /dev/null
+++ b/SmartDeviceLink/SDLSoftButtonTransitionOperation.h
@@ -0,0 +1,43 @@
+//
+// SDLSoftButtonTransitionOperation.h
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/25/19.
+// Copyright © 2019 smartdevicelink. All rights reserved.
+//
+
+#import <Foundation/Foundation.h>
+
+#import "SDLAsynchronousOperation.h"
+
+@class SDLSoftButtonCapabilities;
+@class SDLSoftButtonObject;
+
+@protocol SDLConnectionManagerType;
+
+NS_ASSUME_NONNULL_BEGIN
+
+/**
+ This operation handles changing a set of soft button objects when some of those objects have changed state.
+ */
+@interface SDLSoftButtonTransitionOperation : SDLAsynchronousOperation
+
+/**
+ The primary text field on the system template. This is necessary to HAX a workaround for Sync 3.
+ */
+@property (strong, nonatomic) NSString *mainField1;
+
+/**
+ Initialize the transition operation
+
+ @param connectionManager The manager that will send the resultant RPCs
+ @param capabilities The capabilites of the soft buttons on the current template
+ @param softButtons The soft buttons that should be sent
+ @param mainField1 The primary text field of the system template
+ @return The transition operation
+ */
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager capabilities:(SDLSoftButtonCapabilities *)capabilities softButtons:(NSArray<SDLSoftButtonObject *> *)softButtons mainField1:(NSString *)mainField1;
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLSoftButtonTransitionOperation.m b/SmartDeviceLink/SDLSoftButtonTransitionOperation.m
new file mode 100644
index 000000000..862df6c5e
--- /dev/null
+++ b/SmartDeviceLink/SDLSoftButtonTransitionOperation.m
@@ -0,0 +1,91 @@
+//
+// SDLSoftButtonTransitionOperation.m
+// SmartDeviceLink
+//
+// Created by Joel Fischer on 4/25/19.
+// Copyright © 2019 smartdevicelink. All rights reserved.
+//
+
+#import "SDLSoftButtonTransitionOperation.h"
+
+#import "SDLConnectionManagerType.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLShow.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonObject.h"
+
+NS_ASSUME_NONNULL_BEGIN
+
+@interface SDLSoftButtonTransitionOperation ()
+
+@property (strong, nonatomic) SDLSoftButtonCapabilities *softButtonCapabilities;
+@property (strong, nonatomic) NSArray<SDLSoftButtonObject *> *softButtons;
+
+@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
+@property (copy, nonatomic, nullable) NSError *internalError;
+
+@end
+
+@implementation SDLSoftButtonTransitionOperation
+
+- (instancetype)initWithConnectionManager:(id<SDLConnectionManagerType>)connectionManager capabilities:(SDLSoftButtonCapabilities *)capabilities softButtons:(NSArray<SDLSoftButtonObject *> *)softButtons mainField1:(NSString *)mainField1 {
+ self = [super init];
+ if (!self) { return nil; }
+
+ _connectionManager = connectionManager;
+ _softButtonCapabilities = capabilities;
+ _softButtons = softButtons;
+ _mainField1 = mainField1;
+
+ return self;
+}
+
+- (void)start {
+ [super start];
+
+ [self sdl_sendNewSoftButtons];
+}
+
+- (void)sdl_sendNewSoftButtons {
+ SDLShow *newShow = [[SDLShow alloc] init];
+ newShow.mainField1 = self.mainField1;
+ newShow.softButtons = [self sdl_currentStateSoftButtonsForObjects:self.softButtons];
+
+ [self.connectionManager sendConnectionRequest:newShow withResponseHandler:^(__kindof SDLRPCRequest * _Nullable request, __kindof SDLRPCResponse * _Nullable response, NSError * _Nullable error) {
+ if (error != nil) {
+ SDLLogW(@"Failed to transition soft button to new state. Error: %@, Response: %@", error, response);
+ self.internalError = error;
+ }
+
+ [self finishOperation];
+ }];
+}
+
+- (NSArray<SDLSoftButton *> *)sdl_currentStateSoftButtonsForObjects:(NSArray<SDLSoftButtonObject *> *)objects {
+ NSMutableArray<SDLSoftButton *> *softButtons = [NSMutableArray arrayWithCapacity:objects.count];
+ for (SDLSoftButtonObject *button in objects) {
+ [softButtons addObject:button.currentStateSoftButton];
+ }
+
+ return [softButtons copy];
+}
+
+#pragma mark - Property Overrides
+
+- (nullable NSString *)name {
+ return @"com.sdl.softbuttonmanager.transition";
+}
+
+- (NSOperationQueuePriority)queuePriority {
+ return NSOperationQueuePriorityNormal;
+}
+
+- (nullable NSError *)error {
+ return self.internalError;
+}
+
+@end
+
+NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
index 094da3fc8..3a7ac4a65 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonManagerSpec.m
@@ -12,7 +12,9 @@
#import "SDLSoftButtonCapabilities.h"
#import "SDLSoftButtonManager.h"
#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonReplaceOperation.h"
#import "SDLSoftButtonState.h"
+#import "SDLSoftButtonTransitionOperation.h"
#import "TestConnectionManager.h"
@interface SDLSoftButtonObject()
@@ -29,17 +31,13 @@
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (weak, nonatomic) SDLFileManager *fileManager;
-@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate;
-@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler inProgressHandler;
-
-@property (assign, nonatomic) BOOL hasQueuedUpdate;
-@property (copy, nonatomic, nullable) SDLSoftButtonUpdateCompletionHandler queuedUpdateHandler;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
@property (copy, nonatomic, nullable) SDLHMILevel currentLevel;
@property (strong, nonatomic, nullable) SDLDisplayCapabilities *displayCapabilities;
@property (strong, nonatomic, nullable) SDLSoftButtonCapabilities *softButtonCapabilities;
-@property (assign, nonatomic) BOOL waitingOnHMILevelUpdateToUpdate;
+@property (strong, nonatomic) NSMutableArray<SDLAsynchronousOperation *> *batchQueue;
@end
@@ -63,16 +61,10 @@ describe(@"a soft button manager", ^{
__block SDLSoftButtonObject *testObject2 = nil;
__block NSString *object2Name = @"O2 Name";
__block NSString *object2State1Name = @"O2S1 Name";
- __block NSString *object2State2Name = @"O2S2 Name";
__block NSString *object2State1Text = @"O2S1 Text";
- __block NSString *object2State2Text = @"O2S2 Text";
__block NSString *object2State1ArtworkName = @"O2S1 Artwork";
__block SDLArtwork *object2State1Art = [[SDLArtwork alloc] initWithData:[@"TestData" dataUsingEncoding:NSUTF8StringEncoding] name:object2State1ArtworkName fileExtension:@"png" persistent:YES];
__block SDLSoftButtonState *object2State1 = [[SDLSoftButtonState alloc] initWithStateName:object2State1Name text:object2State1Text artwork:object2State1Art];
- __block SDLSoftButtonState *object2State2 = [[SDLSoftButtonState alloc] initWithStateName:object2State2Name text:object2State2Text image:nil];
-
- __block SDLArtwork *staticIconArt = [SDLArtwork artworkWithStaticIcon:SDLStaticIconNameDate];
- __block SDLSoftButtonState *staticIconState = [[SDLSoftButtonState alloc] initWithStateName:@"Static State" text:nil artwork:staticIconArt];
beforeEach(^{
testFileManager = OCMClassMock([SDLFileManager class]);
@@ -90,11 +82,9 @@ describe(@"a soft button manager", ^{
expect(testManager.softButtonObjects).to(beEmpty());
expect(testManager.currentMainField1).to(beNil());
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.hasQueuedUpdate).to(beFalse());
expect(testManager.displayCapabilities).to(beNil());
expect(testManager.softButtonCapabilities).to(beNil());
- expect(testManager.waitingOnHMILevelUpdateToUpdate).to(beFalse());
+ expect(testManager.transactionQueue).toNot(beNil());
});
context(@"when in HMI NONE", ^{
@@ -109,8 +99,7 @@ describe(@"a soft button manager", ^{
it(@"should set the soft buttons, but not update", ^{
expect(testManager.softButtonObjects).toNot(beEmpty());
- expect(testManager.waitingOnHMILevelUpdateToUpdate).to(beTrue());
- expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.transactionQueue.suspended).to(beTrue());
});
});
@@ -126,8 +115,7 @@ describe(@"a soft button manager", ^{
it(@"should set the soft buttons, but not update", ^{
expect(testManager.softButtonObjects).toNot(beEmpty());
- expect(testManager.waitingOnHMILevelUpdateToUpdate).to(beTrue());
- expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.transactionQueue.suspended).to(beTrue());
});
});
@@ -153,12 +141,38 @@ describe(@"a soft button manager", ^{
testManager.softButtonObjects = @[testObject1, testObject2];
});
+ describe(@"while batching", ^{
+ beforeEach(^{
+ testManager.batchUpdates = YES;
+
+ [testObject1 transitionToNextState];
+ [testObject2 transitionToNextState];
+ testManager.softButtonObjects = @[testObject2, testObject1];
+ });
+
+ it(@"should properly queue the batching updates", ^{
+ expect(testManager.transactionQueue.operationCount).to(equal(1));
+ expect(testManager.batchQueue).to(haveCount(1));
+ });
+ });
+
it(@"should set soft buttons correctly", ^{
expect(testManager.softButtonObjects).toNot(beNil());
expect(testObject1.buttonId).to(equal(0));
expect(testObject2.buttonId).to(equal(100));
expect(testObject1.manager).to(equal(testManager));
expect(testObject2.manager).to(equal(testManager));
+
+ expect(testManager.transactionQueue.operationCount).to(equal(1));
+ });
+
+ it(@"should replace earlier operations when a replace operation is entered", ^{
+ [testObject1 transitionToNextState];
+ testManager.softButtonObjects = @[testObject1];
+ expect(testManager.transactionQueue.operationCount).to(equal(3));
+ expect(testManager.transactionQueue.operations[0].isCancelled).to(beTrue());
+ expect(testManager.transactionQueue.operations[1].isCancelled).to(beTrue());
+ expect(testManager.transactionQueue.operations[2].isCancelled).to(beFalse());
});
it(@"should retrieve soft buttons correctly", ^{
@@ -168,192 +182,52 @@ describe(@"a soft button manager", ^{
context(@"when the HMI level is now NONE", ^{
beforeEach(^{
testManager.currentLevel = SDLHMILevelNone;
- testManager.inProgressUpdate = nil;
});
it(@"should not transition buttons", ^{
[testObject1 transitionToNextState];
- expect(testManager.inProgressUpdate).to(beNil());
+ expect(testManager.transactionQueue.suspended).to(beTrue());
+ expect(testManager.transactionQueue.operationCount).to(equal(2)); // Replace and transition
});
});
});
- describe(@"uploading soft buttons to a head unit that supports images", ^{
+ describe(@"transitioning soft button states", ^{
beforeEach(^{
- SDLSoftButtonCapabilities *softButtonImagesSupported = [[SDLSoftButtonCapabilities alloc] init];
- softButtonImagesSupported.imageSupported = @YES;
- testManager.softButtonCapabilities = softButtonImagesSupported;
- });
-
- context(@"when button artworks are static icons", ^{
- beforeEach(^{
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name state:staticIconState handler:nil];
- testManager.softButtonObjects = @[testObject1];
- });
-
- it(@"should not have attempted to upload any artworks", ^{
- OCMReject([testFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]);
- });
- });
-
- context(@"when button artworks are already on the file system", ^{
- beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
-
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
-
- testManager.softButtonObjects = @[testObject1, testObject2];
- });
-
- it(@"should not have attempted to upload any artworks", ^{
- OCMReject([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- });
-
- it(@"should set the in progress update", ^{
- NSArray<SDLSoftButton *> *inProgressSoftButtons = testManager.inProgressUpdate.softButtons;
-
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.inProgressUpdate.mainField1).to(equal(@""));
- expect(inProgressSoftButtons).to(haveCount(2));
- expect(inProgressSoftButtons[0].text).to(equal(object1State1Text));
- expect(inProgressSoftButtons[1].text).to(equal(object2State1Text));
- expect(inProgressSoftButtons[0].image).to(beNil());
- expect(inProgressSoftButtons[1].image.value).to(equal(object2State1ArtworkName));
- });
- });
-
- context(@"when button artworks are not already on the file system, before upload finishes", ^{
- beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
-
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
-
- testManager.softButtonObjects = @[testObject1, testObject2];
- });
-
- it(@"should attempt to upload an artwork", ^{
- OCMVerify([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- });
-
- it(@"should set the in progress update for text only buttons", ^{
- NSArray<SDLSoftButton *> *inProgressSoftButtons = testManager.inProgressUpdate.softButtons;
+ testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
+ testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.inProgressUpdate.mainField1).to(equal(@""));
- expect(inProgressSoftButtons).to(haveCount(2));
- expect(inProgressSoftButtons[0].text).to(equal(object1State1Text));
- expect(inProgressSoftButtons[1].text).to(equal(object2State1Text));
- expect(inProgressSoftButtons[0].image).to(beNil());
- expect(inProgressSoftButtons[1].image).to(beNil());
- });
+ testManager.softButtonObjects = @[testObject1, testObject2];
});
- context(@"when button artworks are not already on the file system, after upload finishes", ^{
+ context(@"when batching", ^{
beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
- OCMStub([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg invokeBlock]]);
+ testManager.batchUpdates = YES;
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
+ SDLSoftButtonReplaceOperation *replaceOp = [[SDLSoftButtonReplaceOperation alloc] init];
+ SDLSoftButtonTransitionOperation *transitionOp = [[SDLSoftButtonTransitionOperation alloc] init];
+ testManager.batchQueue = [NSMutableArray arrayWithArray:@[replaceOp, transitionOp]];
- testManager.softButtonObjects = @[testObject1, testObject2];
+ [testObject1 transitionToStateNamed:object1State2Name];
});
- it(@"should attempt to upload an artwork", ^{
- OCMVerify([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- });
-
- it(@"should set the in progress update for text only buttons and have a queued update", ^{
- NSArray<SDLSoftButton *> *inProgressSoftButtons = testManager.inProgressUpdate.softButtons;
-
- expect(testManager.hasQueuedUpdate).to(beTrue());
- expect(testManager.inProgressUpdate.mainField1).to(equal(@""));
- expect(inProgressSoftButtons).to(haveCount(2));
- expect(inProgressSoftButtons[0].text).to(equal(object1State1Text));
- expect(inProgressSoftButtons[1].text).to(equal(object2State1Text));
- expect(inProgressSoftButtons[0].image).to(beNil());
- expect(inProgressSoftButtons[1].image).to(beNil());
+ it(@"should batch queue the update and remove the old transition operation", ^{
+ expect(testManager.transactionQueue.operationCount).to(equal(1));
+ expect(testManager.batchQueue.count).to(equal(2));
});
});
- });
- describe(@"uploading soft buttons to a head unit that does not support images", ^{
- beforeEach(^{
- SDLSoftButtonCapabilities *softButtonImagesSupported = [[SDLSoftButtonCapabilities alloc] init];
- softButtonImagesSupported.imageSupported = @NO;
- testManager.softButtonCapabilities = softButtonImagesSupported;
- });
-
- context(@"when the button contains images", ^{
+ context(@"when not batching", ^{
beforeEach(^{
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State2 handler:nil];
- testManager.softButtonObjects = @[testObject1, testObject2];
- });
-
- it(@"should not have attempted to upload any artworks", ^{
- OCMReject([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+ testManager.batchUpdates = NO;
});
- it(@"should set the in progress update to be text buttons", ^{
- NSArray<SDLSoftButton *> *inProgressSoftButtons = testManager.inProgressUpdate.softButtons;
+ it(@"should queue an update", ^{
+ [testObject1 transitionToStateNamed:object1State2Name];
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.inProgressUpdate.mainField1).to(equal(@""));
- expect(inProgressSoftButtons).to(haveCount(2));
- expect(inProgressSoftButtons[0].text).to(equal(object1State1Text));
- expect(inProgressSoftButtons[1].text).to(equal(object2State2Text));
- expect(inProgressSoftButtons[0].image).to(beNil());
- expect(inProgressSoftButtons[1].image.value).to(beNil());
- });
- });
-
- context(@"when the button does not contain images", ^{
- beforeEach(^{
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State2 handler:nil];
- testManager.softButtonObjects = @[testObject1, testObject2];
+ expect(testManager.transactionQueue.operationCount).to(equal(2)); // Replace and transition
});
-
- it(@"should not have attempted to upload any artworks", ^{
- OCMReject([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
- });
-
- it(@"should set the in progress update to be text buttons", ^{
- NSArray<SDLSoftButton *> *inProgressSoftButtons = testManager.inProgressUpdate.softButtons;
-
- expect(testManager.hasQueuedUpdate).to(beFalse());
- expect(testManager.inProgressUpdate.mainField1).to(equal(@""));
- expect(inProgressSoftButtons).to(haveCount(2));
- expect(inProgressSoftButtons[0].text).to(equal(object1State1Text));
- expect(inProgressSoftButtons[1].text).to(equal(object2State2Text));
- expect(inProgressSoftButtons[0].image).to(beNil());
- expect(inProgressSoftButtons[1].image.value).to(beNil());
- });
- });
- });
-
- describe(@"transitioning soft button states", ^{
- beforeEach(^{
- OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
-
- testObject1 = [[SDLSoftButtonObject alloc] initWithName:object1Name states:@[object1State1, object1State2] initialStateName:object1State1Name handler:nil];
- testObject2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:nil];
-
- testManager.softButtonObjects = @[testObject1, testObject2];
- });
-
- it(@"should queue an update", ^{
- testManager.inProgressUpdate = nil; // Reset due to setting the soft button objects
- [testObject1 transitionToStateNamed:object1State2Name];
-
- expect(testManager.inProgressUpdate).toNot(beNil());
- expect(testManager.inProgressUpdate.mainField1).to(beEmpty());
- expect(testManager.inProgressUpdate.softButtons[0].text).to(equal(object1State2Text));
- expect(testManager.inProgressUpdate.softButtons[1].text).to(equal(object2State1Text));
});
});
@@ -368,12 +242,10 @@ describe(@"a soft button manager", ^{
expect(testManager.softButtonObjects).to(beEmpty());
expect(testManager.currentMainField1).to(beNil());
- expect(testManager.inProgressUpdate).to(beNil());
- expect(testManager.hasQueuedUpdate).to(beFalse());
+ expect(testManager.transactionQueue.operationCount).to(equal(0));
expect(testManager.currentLevel).to(beNil());
expect(testManager.displayCapabilities).to(beNil());
expect(testManager.softButtonCapabilities).to(beNil());
- expect(testManager.waitingOnHMILevelUpdateToUpdate).to(beFalse());
});
});
});
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonTransitionOperationSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonTransitionOperationSpec.m
new file mode 100644
index 000000000..7f18dd5a4
--- /dev/null
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLSoftButtonTransitionOperationSpec.m
@@ -0,0 +1,107 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLSoftButtonTransitionOperation.h"
+
+#import "SDLConnectionManagerType.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLShow.h"
+#import "SDLShowResponse.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+#import "TestConnectionManager.h"
+
+QuickSpecBegin(SDLSoftButtonTransitionOperationSpec)
+
+describe(@"a soft button transition operation", ^{
+ __block SDLSoftButtonTransitionOperation *testOp = nil;
+
+ __block TestConnectionManager *testConnectionManager = nil;
+
+ __block BOOL hasCalledOperationCompletionHandler = NO;
+ __block NSError *resultError = nil;
+
+ __block NSArray<SDLSoftButtonObject *> *testSoftButtonObjects = nil;
+ __block NSString *button1Text = @"Text text 1";
+ __block NSString *button2Text = @"Text text 2";
+ __block NSString *testMainField1 = @"Test 1";
+
+ beforeEach(^{
+ resultError = nil;
+ hasCalledOperationCompletionHandler = NO;
+
+ testConnectionManager = [[TestConnectionManager alloc] init];
+
+ SDLSoftButtonState *object1State1 = [[SDLSoftButtonState alloc] initWithStateName:@"State 1" text:button1Text artwork:nil];
+ SDLSoftButtonObject *button1 = [[SDLSoftButtonObject alloc] initWithName:@"Button 1" state:object1State1 handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ SDLSoftButtonState *object2State1 = [[SDLSoftButtonState alloc] initWithStateName:@"State 2" text:button2Text image:nil];
+ SDLSoftButtonObject *button2 = [[SDLSoftButtonObject alloc] initWithName:@"Button 2" state:object2State1 handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ testSoftButtonObjects = @[button1, button2];
+ });
+
+ it(@"should have a priority of 'normal'", ^{
+ testOp = [[SDLSoftButtonTransitionOperation alloc] init];
+
+ expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
+ });
+
+ describe(@"running the operation", ^{
+ beforeEach(^{
+ SDLSoftButtonCapabilities *capabilities = [[SDLSoftButtonCapabilities alloc] init];
+ capabilities.imageSupported = @YES;
+
+ testOp = [[SDLSoftButtonTransitionOperation alloc] initWithConnectionManager:testConnectionManager capabilities:capabilities softButtons:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should send the correct RPCs", ^{
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(1));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(2));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(button1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ expect(sentRequests.firstObject.softButtons.lastObject.text).to(equal(button2Text));
+ expect(sentRequests.firstObject.softButtons.lastObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeText));
+ });
+
+ context(@"if it returns correctly", ^{
+ beforeEach(^{
+ SDLShowResponse *response = [[SDLShowResponse alloc] init];
+ response.success = @YES;
+
+ [testConnectionManager respondToLastRequestWithResponse:response];
+ });
+
+ it(@"should finish the operation", ^{
+ expect(testOp.isFinished).to(beTrue());
+ expect(testOp.error).to(beNil());
+ });
+ });
+
+ context(@"if it returns an error", ^{
+ beforeEach(^{
+ SDLShowResponse *response = [[SDLShowResponse alloc] init];
+ response.success = @NO;
+
+ [testConnectionManager respondToLastRequestWithResponse:response];
+ });
+
+ it(@"should finish the operation", ^{
+ expect(testOp.isFinished).to(beTrue());
+ expect(testOp.error).toNot(beNil());
+ });
+ });
+ });
+});
+
+QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLScreenManagerSpec.m b/SmartDeviceLinkTests/SDLScreenManagerSpec.m
index 481295539..5bb3943c5 100644
--- a/SmartDeviceLinkTests/SDLScreenManagerSpec.m
+++ b/SmartDeviceLinkTests/SDLScreenManagerSpec.m
@@ -17,7 +17,7 @@
@property (weak, nonatomic) id<SDLConnectionManagerType> connectionManager;
@property (weak, nonatomic) SDLFileManager *fileManager;
-@property (strong, nonatomic, nullable) SDLShow *inProgressUpdate;
+@property (strong, nonatomic) NSOperationQueue *transactionQueue;
@property (copy, nonatomic, nullable) SDLHMILevel currentLevel;
@end
@@ -100,7 +100,7 @@ describe(@"screen manager", ^{
it(@"should have in progress updates", ^{
expect(testScreenManager.textAndGraphicManager.inProgressUpdate).toNot(beNil());
- expect(testScreenManager.softButtonManager.inProgressUpdate).toNot(beNil());
+ expect(testScreenManager.softButtonManager.transactionQueue.operationCount).to(equal(1));
expect(testScreenManager.textAndGraphicManager.batchUpdates).to(beFalse());
expect(testScreenManager.softButtonManager.batchUpdates).to(beFalse());
diff --git a/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m b/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m
new file mode 100644
index 000000000..2c8de460f
--- /dev/null
+++ b/SmartDeviceLinkTests/SDLSoftButtonReplaceOperationSpec.m
@@ -0,0 +1,249 @@
+#import <Quick/Quick.h>
+#import <Nimble/Nimble.h>
+#import <OCMock/OCMock.h>
+
+#import "SDLSoftButtonReplaceOperation.h"
+
+#import "SDLArtwork.h"
+#import "SDLConnectionManagerType.h"
+#import "SDLDisplayCapabilities+ShowManagerExtensions.h"
+#import "SDLFileManager.h"
+#import "SDLLogMacros.h"
+#import "SDLShow.h"
+#import "SDLSoftButton.h"
+#import "SDLSoftButtonCapabilities.h"
+#import "SDLSoftButtonObject.h"
+#import "SDLSoftButtonState.h"
+#import "TestConnectionManager.h"
+
+QuickSpecBegin(SDLSoftButtonReplaceOperationSpec)
+
+describe(@"a soft button replace operation", ^{
+ __block SDLSoftButtonReplaceOperation *testOp = nil;
+
+ __block TestConnectionManager *testConnectionManager = nil;
+ __block SDLFileManager *testFileManager = nil;
+
+ __block BOOL hasCalledOperationCompletionHandler = NO;
+ __block NSError *resultError = nil;
+
+ __block NSString *object1Name = @"O1 Name";
+ __block NSString *object1State1Name = @"O1S1 Name";
+ __block NSString *object1State2Name = @"O1S2 Name";
+ __block NSString *object1State1Text = @"O1S1 Text";
+ __block NSString *object1State2Text = @"O1S2 Text";
+ __block SDLSoftButtonState *object1State1 = nil;
+ __block SDLSoftButtonState *object1State2 = nil;
+ __block SDLSoftButtonObject *button1 = nil;
+
+ __block NSString *object2Name = @"O2 Name";
+ __block NSString *object2State1Name = @"O2S1 Name";
+ __block NSString *object2State1Text = @"O2S1 Text";
+ __block NSString *object2State1ArtworkName = @"O2S1 Artwork";
+ __block SDLArtwork *object2State1Art = nil;
+ __block SDLSoftButtonState *object2State1 = nil;
+ __block SDLSoftButtonObject *button2 = nil;
+
+ __block NSString *object3Name = @"O3 Name";
+ __block NSString *object3State1Name = @"O3S1 Name";
+ __block NSString *object3State1Text = @"O3S1 Text";
+ __block NSString *object3State1IconName = SDLStaticIconNameRSS;
+ __block SDLArtwork *object3State1Art = nil;
+ __block SDLSoftButtonState *object3State1 = nil;
+ __block SDLSoftButtonObject *button3 = nil;
+
+ __block NSString *testMainField1 = @"Test main field 1";
+
+ beforeEach(^{
+ resultError = nil;
+ hasCalledOperationCompletionHandler = NO;
+
+ testConnectionManager = [[TestConnectionManager alloc] init];
+ testFileManager = OCMClassMock([SDLFileManager class]);
+
+ object1State1 = [[SDLSoftButtonState alloc] initWithStateName:object1State1Name text:object1State1Text artwork:nil];
+ object1State2 = [[SDLSoftButtonState alloc] initWithStateName:object1State2Name text:object1State2Text artwork:nil];
+ button1 = [[SDLSoftButtonObject alloc] initWithName:object1Name state:object1State1 handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ object2State1Art = [[SDLArtwork alloc] initWithData:[@"TestData" dataUsingEncoding:NSUTF8StringEncoding] name:object2State1ArtworkName fileExtension:@"png" persistent:YES];
+ object2State1 = [[SDLSoftButtonState alloc] initWithStateName:object2State1Name text:object2State1Text artwork:object2State1Art];
+ button2 = [[SDLSoftButtonObject alloc] initWithName:object2Name state:object2State1 handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ object3State1Art = [[SDLArtwork alloc] initWithStaticIcon:object3State1IconName];
+ object3State1 = [[SDLSoftButtonState alloc] initWithStateName:object3State1Name text:object3State1Text artwork:object3State1Art];
+ button3 = [[SDLSoftButtonObject alloc] initWithName:object3Name state:object3State1 handler:^(SDLOnButtonPress * _Nullable buttonPress, SDLOnButtonEvent * _Nullable buttonEvent) {}];
+
+ OCMStub([testFileManager uploadArtworks:[OCMArg any] progressHandler:[OCMArg invokeBlock] completionHandler:[OCMArg invokeBlock]]);
+ });
+
+ it(@"should have a priority of 'normal'", ^{
+ testOp = [[SDLSoftButtonReplaceOperation alloc] init];
+
+ expect(@(testOp.queuePriority)).to(equal(@(NSOperationQueuePriorityNormal)));
+ });
+
+ describe(@"running the operation", ^{
+ context(@"without artworks", ^{
+ beforeEach(^{
+ SDLSoftButtonCapabilities *capabilities = [[SDLSoftButtonCapabilities alloc] init];
+ capabilities.imageSupported = @YES;
+
+ NSArray<SDLSoftButtonObject *> *testSoftButtonObjects = @[button1];
+
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should send the correct RPCs", ^{
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(1));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(1));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(object1State1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ });
+ });
+
+ context(@"with artworks", ^{
+ __block NSArray<SDLSoftButtonObject *> *testSoftButtonObjects = nil;
+
+ beforeEach(^{
+ testSoftButtonObjects = @[button1, button2];
+ });
+
+ context(@"but we don't support artworks", ^{
+ beforeEach(^{
+ SDLSoftButtonCapabilities *capabilities = [[SDLSoftButtonCapabilities alloc] init];
+ capabilities.imageSupported = @NO;
+
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:capabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should not send artworks", ^{
+ OCMReject([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(1));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(2));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(object1State1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ expect(sentRequests.firstObject.softButtons.lastObject.text).to(equal(object2State1Text));
+ expect(sentRequests.firstObject.softButtons.lastObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeText));
+ });
+ });
+
+ context(@"and we support artworks", ^{
+ __block SDLSoftButtonCapabilities *buttonCapabilities = nil;
+
+ beforeEach(^{
+ buttonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ buttonCapabilities.imageSupported = @YES;
+ });
+
+ context(@"when artworks are already on the system", ^{
+ beforeEach(^{
+ OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(YES);
+
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:buttonCapabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should not upload artworks", ^{
+ OCMReject([testFileManager uploadArtworks:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(1));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(2));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(object1State1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ expect(sentRequests.firstObject.softButtons.lastObject.text).to(equal(object2State1Text));
+ expect(sentRequests.firstObject.softButtons.lastObject.image).toNot(beNil());
+ expect(sentRequests.firstObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeBoth));
+ });
+ });
+
+ context(@"when the artworks need uploading", ^{
+ __block SDLSoftButtonCapabilities *buttonCapabilities = nil;
+
+ beforeEach(^{
+ buttonCapabilities = [[SDLSoftButtonCapabilities alloc] init];
+ buttonCapabilities.imageSupported = @YES;
+ });
+
+ context(@"when artworks are static icons", ^{
+ beforeEach(^{
+ testSoftButtonObjects = @[button3];
+
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:buttonCapabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should skip uploading artwork", ^{
+ OCMReject([testFileManager uploadArtwork:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(1));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(1));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(object3State1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).toNot(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeBoth));
+ });
+ });
+
+ context(@"when artwork are not already on the system", ^{
+ beforeEach(^{
+ OCMStub([testFileManager hasUploadedFile:[OCMArg isNotNil]]).andReturn(NO);
+
+ testOp = [[SDLSoftButtonReplaceOperation alloc] initWithConnectionManager:testConnectionManager fileManager:testFileManager capabilities:buttonCapabilities softButtonObjects:testSoftButtonObjects mainField1:testMainField1];
+ [testOp start];
+ });
+
+ it(@"should upload artworks", ^{
+ OCMVerify([testFileManager uploadArtworks:[OCMArg checkWithBlock:^BOOL(id obj) {
+ NSArray<SDLArtwork *> *artworks = (NSArray<SDLArtwork *> *)obj;
+ return (artworks.count == 1);
+ }] progressHandler:[OCMArg any] completionHandler:[OCMArg any]]);
+
+ // Both the text only buttons and the image buttons should be sent
+ NSArray<SDLShow *> *sentRequests = testConnectionManager.receivedRequests;
+ expect(sentRequests).to(haveCount(2));
+ expect(sentRequests.firstObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.firstObject.mainField2).to(beNil());
+ expect(sentRequests.firstObject.softButtons).to(haveCount(2));
+ expect(sentRequests.firstObject.softButtons.firstObject.text).to(equal(object1State1Text));
+ expect(sentRequests.firstObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ expect(sentRequests.firstObject.softButtons.lastObject.text).to(equal(object2State1Text));
+ expect(sentRequests.firstObject.softButtons.lastObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeText));
+
+ expect(sentRequests.lastObject.mainField1).to(equal(testMainField1));
+ expect(sentRequests.lastObject.mainField2).to(beNil());
+ expect(sentRequests.lastObject.softButtons).to(haveCount(2));
+ expect(sentRequests.lastObject.softButtons.firstObject.text).to(equal(object1State1Text));
+ expect(sentRequests.lastObject.softButtons.firstObject.image).to(beNil());
+ expect(sentRequests.firstObject.softButtons.firstObject.type).to(equal(SDLSoftButtonTypeText));
+ expect(sentRequests.lastObject.softButtons.lastObject.text).to(equal(object2State1Text));
+ expect(sentRequests.lastObject.softButtons.lastObject.image).toNot(beNil());
+ expect(sentRequests.lastObject.softButtons.lastObject.type).to(equal(SDLSoftButtonTypeBoth));
+ });
+ });
+ });
+ });
+ });
+ });
+});
+
+QuickSpecEnd