summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--SmartDeviceLink/SDLIAPControlSession.m80
-rw-r--r--SmartDeviceLink/SDLIAPDataSession.m140
-rw-r--r--SmartDeviceLink/SDLIAPSession.h7
-rw-r--r--SmartDeviceLink/SDLIAPSession.m2
-rw-r--r--SmartDeviceLink/SDLIAPTransport.m72
-rw-r--r--SmartDeviceLink/SDLLifecycleManager.m35
-rw-r--r--SmartDeviceLink/SDLProtocol.m2
-rw-r--r--SmartDeviceLink/SDLProxy.h3
-rw-r--r--SmartDeviceLink/SDLProxy.m65
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.h8
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.m202
-rw-r--r--SmartDeviceLink/SDLStreamingAudioLifecycleManager.h5
-rw-r--r--SmartDeviceLink/SDLStreamingAudioLifecycleManager.m8
-rw-r--r--SmartDeviceLink/SDLStreamingMediaManager.m51
-rw-r--r--SmartDeviceLink/SDLStreamingProtocolDelegate.h9
-rw-r--r--SmartDeviceLink/SDLStreamingVideoLifecycleManager.h5
-rw-r--r--SmartDeviceLink/SDLStreamingVideoLifecycleManager.m10
-rw-r--r--SmartDeviceLink/SDLTCPTransport.m100
-rw-r--r--SmartDeviceLink/SDLTransportType.h2
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLAudioStreamManagerSpec.m24
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m1
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLStreamingAudioLifecycleManagerSpec.m31
-rw-r--r--SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m46
-rw-r--r--SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m690
-rw-r--r--SmartDeviceLinkTests/SDLStreamingMediaManagerSpec.m203
-rw-r--r--SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m98
-rw-r--r--SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPDataSessionSpec.m2
-rw-r--r--SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m42
28 files changed, 1123 insertions, 820 deletions
diff --git a/SmartDeviceLink/SDLIAPControlSession.m b/SmartDeviceLink/SDLIAPControlSession.m
index a1e4c07ab..50d7a98e4 100644
--- a/SmartDeviceLink/SDLIAPControlSession.m
+++ b/SmartDeviceLink/SDLIAPControlSession.m
@@ -49,19 +49,18 @@ int const ProtocolIndexTimeoutSeconds = 10;
- (void)startSession {
if (self.accessory == nil) {
SDLLogW(@"There is no control session in progress, attempting to create a new control session.");
- if (self.delegate == nil) { return; }
[self.delegate controlSessionShouldRetry];
} else {
SDLLogD(@"Starting a control session with accessory (%@)", self.accessory.name);
-
__weak typeof(self) weakSelf = self;
[self sdl_startStreamsWithCompletionHandler:^(BOOL success) {
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!success) {
SDLLogW(@"Control session failed to setup with accessory: %@. Attempting to create a new control session", strongSelf.accessory);
- [strongSelf destroySession];
- if (strongSelf.delegate == nil) { return; }
- [strongSelf.delegate controlSessionShouldRetry];
+ [strongSelf destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf.delegate controlSessionShouldRetry];
+ }];
} else {
SDLLogD(@"Waiting for the protocol string from Core, setting timeout timer for %d seconds", ProtocolIndexTimeoutSeconds);
strongSelf.protocolIndexTimer = [strongSelf sdl_createControlSessionProtocolIndexStringDataTimeoutTimer];
@@ -91,27 +90,17 @@ int const ProtocolIndexTimeoutSeconds = 10;
#pragma mark Stop
-- (void)destroySession {
+/// Makes sure the session is closed and destroyed on the main thread.
+/// @param disconnectCompletionHandler Handler called when the session has disconnected
+- (void)destroySessionWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
SDLLogD(@"Destroying the control session");
- [self sdl_destroySession];
-}
-
-/**
- * Makes sure the session is closed and destroyed on the main thread.
- */
-- (void)sdl_destroySession {
- if ([NSThread isMainThread]) {
+ dispatch_async(dispatch_get_main_queue(), ^{
[self sdl_stopAndDestroySession];
- } else {
- dispatch_sync(dispatch_get_main_queue(), ^{
- [self sdl_stopAndDestroySession];
- });
- }
+ return disconnectCompletionHandler();
+ });
}
-/**
- * Closes the session streams and then destroys the session.
- */
+/// Closes the session streams and then destroys the session.
- (void)sdl_stopAndDestroySession {
NSAssert(NSThread.isMainThread, @"%@ must only be called on the main thread", NSStringFromSelector(_cmd));
@@ -184,10 +173,12 @@ int const ProtocolIndexTimeoutSeconds = 10;
// End events come in pairs, only perform this once per set.
[self.protocolIndexTimer cancel];
- [self destroySession];
- if (self.delegate == nil) { return; }
- [self.delegate controlSessionShouldRetry];
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf.delegate controlSessionShouldRetry];
+ }];
}
/**
@@ -209,14 +200,14 @@ int const ProtocolIndexTimeoutSeconds = 10;
SDLLogD(@"Control Stream will switch to protocol %@", indexedProtocolString);
// Destroy the control session as it is no longer needed, and then create the data session.
- [self destroySession];
-
- if (self.accessory.isConnected) {
- if (self.delegate != nil) {
- [self.delegate controlSession:self didReceiveProtocolString:indexedProtocolString];
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (strongSelf.accessory.isConnected) {
+ [strongSelf.protocolIndexTimer cancel];
+ [strongSelf.delegate controlSession:strongSelf didReceiveProtocolString:indexedProtocolString];
}
- [self.protocolIndexTimer cancel];
- }
+ }];
}
/**
@@ -226,10 +217,11 @@ int const ProtocolIndexTimeoutSeconds = 10;
SDLLogE(@"Control stream error");
[self.protocolIndexTimer cancel];
- [self destroySession];
-
- if (self.delegate == nil) { return; }
- [self.delegate controlSessionShouldRetry];
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf.delegate controlSessionShouldRetry];
+ }];
}
#pragma mark - Timer
@@ -246,10 +238,10 @@ int const ProtocolIndexTimeoutSeconds = 10;
void (^elapsedBlock)(void) = ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
SDLLogW(@"Control session failed to get the protocol string from Core after %d seconds, retrying.", ProtocolIndexTimeoutSeconds);
- [strongSelf sdl_destroySession];
-
- if (strongSelf.delegate == nil) { return; }
- [strongSelf.delegate controlSessionShouldRetry];
+ [strongSelf destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf.delegate controlSessionShouldRetry];
+ }];
};
protocolIndexTimer.elapsedBlock = elapsedBlock;
@@ -264,14 +256,6 @@ int const ProtocolIndexTimeoutSeconds = 10;
[self.protocolIndexTimer start];
}
-#pragma mark - Lifecycle Destruction
-
-- (void)dealloc {
- SDLLogV(@"SDLIAPControlSession dealloc");
- [self destroySession];
- _protocolIndexTimer = nil;
-}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLIAPDataSession.m b/SmartDeviceLink/SDLIAPDataSession.m
index 5bf61e304..785573fde 100644
--- a/SmartDeviceLink/SDLIAPDataSession.m
+++ b/SmartDeviceLink/SDLIAPDataSession.m
@@ -16,7 +16,7 @@
#import "SDLMutableDataQueue.h"
NSString *const IOStreamThreadName = @"com.smartdevicelink.iostream";
-NSTimeInterval const IOStreamThreadWaitSecs = 1.0;
+NSTimeInterval const IOStreamThreadCanceledSemaphoreWaitSecs = 1.0;
NS_ASSUME_NONNULL_BEGIN
@@ -25,27 +25,29 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, nonatomic, strong) NSThread *ioStreamThread;
@property (nonatomic, strong) SDLMutableDataQueue *sendDataQueue;
@property (weak, nonatomic) id<SDLIAPDataSessionDelegate> delegate;
-@property (nonatomic, strong) dispatch_semaphore_t canceledSemaphore;
+/// A semaphore used to block the current thread until we know that the I/O streams have been shutdown on the ioStreamThread
+@property (nonatomic, strong) dispatch_semaphore_t ioStreamThreadCancelledSemaphore;
@end
@implementation SDLIAPDataSession
-#pragma mark - Session lifecycle
+#pragma mark - Lifecycle
- (instancetype)initWithAccessory:(nullable EAAccessory *)accessory delegate:(id<SDLIAPDataSessionDelegate>)delegate forProtocol:(NSString *)protocol; {
- SDLLogV(@"SDLIAPDataSession init");
+ SDLLogV(@"iAP data session init for accessory: %@", accessory);
self = [super initWithAccessory:accessory forProtocol:protocol];
if (!self) { return nil; }
_delegate = delegate;
_sendDataQueue = [[SDLMutableDataQueue alloc] init];
- _canceledSemaphore = dispatch_semaphore_create(0);
+ _ioStreamThreadCancelledSemaphore = dispatch_semaphore_create(0);
return self;
}
+
#pragma mark Start
- (void)startSession {
@@ -58,9 +60,12 @@ NS_ASSUME_NONNULL_BEGIN
if (![super createSession]) {
SDLLogW(@"Data session failed to setup with accessory: %@. Retrying...", self.accessory);
- [self destroySession];
- if (self.delegate == nil) { return; }
- [self.delegate dataSessionShouldRetry];
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (strongSelf.delegate == nil) { return; }
+ [strongSelf.delegate dataSessionShouldRetry];
+ }];
}
if (self.eaSession != nil) {
@@ -73,66 +78,56 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark Stop
-- (void)destroySession {
+/// Waits for the ioStreamThread to close and destroy the I/O streams.
+/// @param disconnectCompletionHandler Handler called when the session has disconnected
+- (void)destroySessionWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
SDLLogD(@"Destroying the data session");
- [self sdl_destroySession];
- [self.sendDataQueue removeAllObjects];
-}
-
-/**
- * Makes sure the session is closed and destroyed on the main thread.
- */
-- (void)sdl_destroySession {
- if ([NSThread isMainThread]) {
- [self sdl_stopAndDestroySession];
- } else {
- dispatch_sync(dispatch_get_main_queue(), ^{
- [self sdl_stopAndDestroySession];
- });
- }
-}
-
-/**
- * Waits for the session streams to close on the I/O Thread and then destroys the session.
- */
-- (void)sdl_stopAndDestroySession {
- NSAssert(NSThread.isMainThread, @"%@ must only be called on the main thread", NSStringFromSelector(_cmd));
if (self.ioStreamThread == nil) {
- SDLLogV(@"Stopping data session but no thread established.");
+ SDLLogV(@"No data session established");
[super cleanupClosedSession];
- return;
+ return disconnectCompletionHandler();
}
- [self.ioStreamThread cancel];
+ // Tell the ioStreamThread to shutdown the I/O streams. The I/O streams must be opened and closed on the same thread; if they are not, random crashes can occur. Dispatch this task to the main queue to ensure that this task is performed on the Main Thread. We are using the Main Thread for ease since we don't want to create a separate thread just to wait on closing the I/O streams. Using the Main Thread ensures that semaphore wait is not called from ioStreamThread, which would block the ioStreamThread and prevent shutdown.
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+
+ // Attempt to cancel the ioStreamThread. Once the thread realizes it has been cancelled, it will cleanup the I/O streams. Make sure to wake up the run loop in case there is no current I/O event running on the ioThread.
+ [strongSelf.ioStreamThread cancel];
+ [strongSelf performSelector:@selector(sdl_doNothing) onThread:self.ioStreamThread withObject:nil waitUntilDone:NO];
- // Waiting for the I/O streams of the data session to close
- [self sdl_isIOThreadCanceled:self.canceledSemaphore completionHandler:^(BOOL success) {
- if (success == NO) {
- SDLLogE(@"Destroying thread (IOStreamThread) for data session when I/O streams have not yet closed.");
+ // Block the thread until the semaphore has been released by the ioStreamThread (or a timeout has occured).
+ BOOL cancelledSuccessfully = [strongSelf sdl_isIOThreadCancelled];
+ if (!cancelledSuccessfully) {
+ SDLLogE(@"The I/O streams were not shut down successfully. We might not be able to create a new session with an accessory during the same app session. If this happens, only force quitting and restarting the app will allow new sessions.");
}
- self.ioStreamThread = nil;
- [super cleanupClosedSession];
- }];
+
+ [strongSelf.sendDataQueue removeAllObjects];
+
+ disconnectCompletionHandler();
+ });
}
-/**
- * Wait for the data session to detroy its input and output streams. The data EASession can not be destroyed until both streams have closed.
- *
- * @param canceledSemaphore When the canceled semaphore is released, the data session's input and output streams have been destroyed.
- * @param completionHandler Returns whether or not the data session's I/O streams were closed successfully.
- */
-- (void)sdl_isIOThreadCanceled:(dispatch_semaphore_t)canceledSemaphore completionHandler:(void (^)(BOOL success))completionHandler {
- long lWait = dispatch_semaphore_wait(canceledSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(IOStreamThreadWaitSecs * NSEC_PER_SEC)));
+/// Wait for the ioStreamThread to destroy the I/O streams. Make sure this method is not called on the ioStreamThread, as it will block the thread until the timeout occurs.
+/// @return Whether or not the session's I/O streams were closed successfully.
+- (BOOL)sdl_isIOThreadCancelled {
+ NSAssert(![NSThread.currentThread.name isEqualToString:IOStreamThreadName], @"%@ must not be called from the ioStreamThread!", NSStringFromSelector(_cmd));
+
+ long lWait = dispatch_semaphore_wait(self.ioStreamThreadCancelledSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(IOStreamThreadCanceledSemaphoreWaitSecs * NSEC_PER_SEC)));
if (lWait == 0) {
- SDLLogD(@"Stream thread canceled successfully");
- return completionHandler(YES);
- } else {
- SDLLogE(@"Failed to cancel stream thread");
- return completionHandler(NO);
+ SDLLogD(@"ioStreamThread cancelled successfully");
+ return YES;
}
+
+ SDLLogE(@"Failed to cancel ioStreamThread within %.1f seconds", IOStreamThreadCanceledSemaphoreWaitSecs);
+ return NO;
}
+/// Helper method for waking up the ioStreamThread.
+- (void)sdl_doNothing {}
+
#pragma mark - Sending data
- (void)sendData:(NSData *)data {
@@ -256,10 +251,12 @@ NS_ASSUME_NONNULL_BEGIN
// The handler will be called on the I/O thread, but the session stop method must be called on the main thread
dispatch_async(dispatch_get_main_queue(), ^{
- [self destroySession];
-
- if (self.delegate == nil) { return; }
- [self.delegate dataSessionShouldRetry];
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (strongSelf.delegate == nil) { return; }
+ [strongSelf.delegate dataSessionShouldRetry];
+ }];
});
// To prevent deadlocks the handler must return to the runloop and not block the thread
@@ -308,11 +305,14 @@ NS_ASSUME_NONNULL_BEGIN
// To prevent deadlocks the handler must return to the runloop and not block the thread
dispatch_async(dispatch_get_main_queue(), ^{
- [self destroySession];
- if (![self.protocolString isEqualToString:LegacyProtocolString]) {
- if (self.delegate == nil) { return; }
- [self.delegate dataSessionShouldRetry];
- }
+ __weak typeof(self) weakSelf = self;
+ [self destroySessionWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (![strongSelf.protocolString isEqualToString:LegacyProtocolString]) {
+ if (strongSelf.delegate == nil) { return; }
+ [strongSelf.delegate dataSessionShouldRetry];
+ }
+ }];
});
}
@@ -331,17 +331,19 @@ NS_ASSUME_NONNULL_BEGIN
SDLLogD(@"Starting the accessory event loop on thread: %@", NSThread.currentThread.name);
- while (!self.ioStreamThread.cancelled) {
+ while (self.ioStreamThread != nil && !self.ioStreamThread.cancelled) {
// Enqueued data will be written to and read from the streams in the runloop
[[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.25f]];
}
SDLLogD(@"Closing the accessory event loop on thread: %@", NSThread.currentThread.name);
- // Close I/O streams of the data session. When the streams are closed. Notify the thread that it can close
+ // Close I/O streams
[self sdl_closeSession];
+ [super cleanupClosedSession];
- dispatch_semaphore_signal(self.canceledSemaphore);
+ // If a thread is blocked waiting on the I/O streams to shutdown, let the thread know that shutdown has completed.
+ dispatch_semaphore_signal(self.ioStreamThreadCancelledSemaphore);
}
}
@@ -367,12 +369,6 @@ NS_ASSUME_NONNULL_BEGIN
[super stopStream:stream];
}
-#pragma mark - Lifecycle Destruction
-
-- (void)dealloc {
- SDLLogV(@"SDLIAPDataSession dealloc");
- [self destroySession];
-}
@end
diff --git a/SmartDeviceLink/SDLIAPSession.h b/SmartDeviceLink/SDLIAPSession.h
index 75b1f6d55..6bc62ed5f 100644
--- a/SmartDeviceLink/SDLIAPSession.h
+++ b/SmartDeviceLink/SDLIAPSession.h
@@ -65,10 +65,9 @@ NS_ASSUME_NONNULL_BEGIN
*/
- (void)startSession;
-/**
- * Stops the current session.
- */
-- (void)destroySession;
+/// Stops the current session.
+/// @param disconnectCompletionHandler Handler called when the session has been closed
+- (void)destroySessionWithCompletionHandler:(void (^)(void))disconnectCompletionHandler;
/**
* Creates a session with the accessory.
diff --git a/SmartDeviceLink/SDLIAPSession.m b/SmartDeviceLink/SDLIAPSession.m
index c69efce6d..eaf79725f 100644
--- a/SmartDeviceLink/SDLIAPSession.m
+++ b/SmartDeviceLink/SDLIAPSession.m
@@ -39,7 +39,7 @@ NS_ASSUME_NONNULL_BEGIN
- (void)startSession {}
-- (void)destroySession {}
+- (void)destroySessionWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {}
#pragma mark - Private Stream Lifecycle Helpers
diff --git a/SmartDeviceLink/SDLIAPTransport.m b/SmartDeviceLink/SDLIAPTransport.m
index a1be67a65..f47fcca2a 100644
--- a/SmartDeviceLink/SDLIAPTransport.m
+++ b/SmartDeviceLink/SDLIAPTransport.m
@@ -170,8 +170,8 @@ int const CreateSessionRetries = 3;
- (void)sdl_closeSessions {
self.retryCounter = 0;
self.sessionSetupInProgress = NO;
- [self.controlSession destroySession];
- [self.dataSession destroySession];
+
+ [self sdl_closeSessionsWithCompletionHandler:nil];
}
/**
@@ -181,8 +181,11 @@ int const CreateSessionRetries = 3;
self.retryCounter = 0;
self.sessionSetupInProgress = NO;
self.transportDestroyed = YES;
- [self disconnect];
- [self.delegate onTransportDisconnected];
+ __weak typeof(self) weakSelf = self;
+ [self disconnectWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf.delegate onTransportDisconnected];
+ }];
}
#pragma mark - Stream Lifecycle
@@ -212,7 +215,7 @@ int const CreateSessionRetries = 3;
/**
* Cleans up after a disconnected accessory by closing any open I/O streams.
*/
-- (void)disconnect {
+- (void)disconnectWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
// Stop event listening here so that even if the transport is disconnected by `SDLProxy` when there is a start session timeout, the class unregisters for accessory notifications
[self sdl_stopEventListening];
@@ -220,8 +223,7 @@ int const CreateSessionRetries = 3;
self.sessionSetupInProgress = NO;
self.transportDestroyed = YES;
- [self.controlSession destroySession];
- [self.dataSession destroySession];
+ [self sdl_closeSessionsWithCompletionHandler:disconnectCompletionHandler];
}
@@ -285,11 +287,46 @@ int const CreateSessionRetries = 3;
- (void)sdl_retryEstablishSession {
// Current strategy disallows automatic retries.
self.sessionSetupInProgress = NO;
- [self.controlSession destroySession];
- [self.dataSession destroySession];
- // Search connected accessories
- [self sdl_connect:nil];
+ __weak typeof(self) weakSelf = self;
+ [self sdl_closeSessionsWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ // Search connected accessories
+ [strongSelf sdl_connect:nil];
+ }];
+}
+
+/// Helper method for closing both the data and control sessions.
+/// @param disconnectCompletionHandler Handler called when both the data and control sessions have been disconnected successfully
+- (void)sdl_closeSessionsWithCompletionHandler:(nullable void (^)(void))disconnectCompletionHandler {
+ dispatch_group_t endSessionsTask = dispatch_group_create();
+ dispatch_group_enter(endSessionsTask);
+
+ if (self.controlSession != nil) {
+ dispatch_group_enter(endSessionsTask);
+ [self.controlSession destroySessionWithCompletionHandler:^{
+ SDLLogV(@"Control session destroyed");
+ dispatch_group_leave(endSessionsTask);
+ }];
+ }
+
+ if (self.dataSession != nil) {
+ dispatch_group_enter(endSessionsTask);
+ [self.dataSession destroySessionWithCompletionHandler:^{
+ SDLLogV(@"Data session destroyed");
+ dispatch_group_leave(endSessionsTask);
+ }];
+ }
+
+ dispatch_group_leave(endSessionsTask);
+
+ // This will always run after all `leave` calls
+ dispatch_group_notify(endSessionsTask, [SDLGlobals sharedGlobals].sdlProcessingQueue, ^{
+ SDLLogV(@"Both the data and control sessions are closed");
+ if (disconnectCompletionHandler != nil) {
+ disconnectCompletionHandler();
+ }
+ });
}
@@ -519,19 +556,6 @@ int const CreateSessionRetries = 3;
return NO;
}
-
-#pragma mark - Lifecycle Destruction
-
-- (void)dealloc {
- SDLLogV(@"SDLIAPTransport dealloc");
- [self disconnect];
- self.controlSession = nil;
- self.dataSession = nil;
- self.delegate = nil;
- self.sessionSetupInProgress = NO;
- self.accessoryConnectDuringActiveSession = NO;
-}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLLifecycleManager.m b/SmartDeviceLink/SDLLifecycleManager.m
index f4d9daa22..3cb6fb961 100644
--- a/SmartDeviceLink/SDLLifecycleManager.m
+++ b/SmartDeviceLink/SDLLifecycleManager.m
@@ -270,10 +270,40 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
[self sdl_stopManager:YES];
}
+/// Shuts down the all the managers used to manage the lifecycle of the SDL app after the connection between the phone and SDL enabled accessory has closed. If a restart is desired, attempt to start looking for another SDL enabled accessory. If no restart is desired, another connection will not be made with a SDL enabled accessory during the current app session
+/// @param shouldRestart Whether or not to start looking for another SDL enabled accessory.
- (void)sdl_stopManager:(BOOL)shouldRestart {
SDLLogV(@"Stopping manager, %@", (shouldRestart ? @"will restart" : @"will not restart"));
- [self.proxy disconnectSession];
+ dispatch_group_t stopManagersTask = dispatch_group_create();
+ dispatch_group_enter(stopManagersTask);
+
+ if (self.proxy != nil) {
+ dispatch_group_enter(stopManagersTask);
+ [self.proxy disconnectSessionWithCompletionHandler:^{
+ dispatch_group_leave(stopManagersTask);
+ }];
+ }
+ if (self.secondaryTransportManager != nil) {
+ dispatch_group_enter(stopManagersTask);
+ [self.secondaryTransportManager stopWithCompletionHandler:^{
+ dispatch_group_leave(stopManagersTask);
+ }];
+ }
+
+ dispatch_group_leave(stopManagersTask);
+
+ // This will always run after all `leave`s
+ __weak typeof(self) weakSelf = self;
+ dispatch_group_notify(stopManagersTask, [SDLGlobals sharedGlobals].sdlProcessingQueue, ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [strongSelf sdl_stopManagersAndRestart:shouldRestart];
+ });
+}
+
+/// Helper method for shutting down the remaining managers that do not need extra time to shutdown. Once all the managers have been shutdown, attempt to start looking for another SDL enabled accessory.
+/// @param shouldRestart Whether or not to start looking for another SDL enabled accessory.
+- (void)sdl_stopManagersAndRestart:(BOOL)shouldRestart {
self.proxy = nil;
[self.fileManager stop];
@@ -282,7 +312,6 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
[self.screenManager stop];
[self.encryptionLifecycleManager stop];
[self.streamManager stop];
- [self.secondaryTransportManager stop];
[self.systemCapabilityManager stop];
[self.responseDispatcher clear];
@@ -308,7 +337,7 @@ NSString *const BackgroundTaskTransportName = @"com.sdl.transport.backgroundTask
[strongSelf sdl_transitionToState:SDLLifecycleStateStarted];
} else {
// End the background task because a session will not be established
- [self.backgroundTaskManager endBackgroundTask];
+ [strongSelf.backgroundTaskManager endBackgroundTask];
}
});
}
diff --git a/SmartDeviceLink/SDLProtocol.m b/SmartDeviceLink/SDLProtocol.m
index 7361e4295..d2ab8ad8d 100644
--- a/SmartDeviceLink/SDLProtocol.m
+++ b/SmartDeviceLink/SDLProtocol.m
@@ -255,6 +255,8 @@ NS_ASSUME_NONNULL_BEGIN
#pragma mark - Register Secondary Transport
- (void)registerSecondaryTransport {
+ SDLLogV(@"Attempting to register the secondary transport");
+
SDLProtocolHeader *header = [SDLProtocolHeader headerForVersion:(UInt8)[SDLGlobals sharedGlobals].protocolVersion.major];
header.frameType = SDLFrameTypeControl;
header.serviceType = SDLServiceTypeControl;
diff --git a/SmartDeviceLink/SDLProxy.h b/SmartDeviceLink/SDLProxy.h
index 4fd20c4af..f4869c9d6 100644
--- a/SmartDeviceLink/SDLProxy.h
+++ b/SmartDeviceLink/SDLProxy.h
@@ -134,7 +134,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)putFileStream:(NSInputStream *)inputStream withRequest:(SDLPutFile *)putFileRPCRequest;
/// Disconnects the current app session, including the security manager and primary transport.
-- (void)disconnectSession;
+/// @param completionHandler Handler called when the session has been disconnected
+- (void)disconnectSessionWithCompletionHandler:(void (^)(void))completionHandler;
@end
diff --git a/SmartDeviceLink/SDLProxy.m b/SmartDeviceLink/SDLProxy.m
index 8161f7427..914aaeefe 100644
--- a/SmartDeviceLink/SDLProxy.m
+++ b/SmartDeviceLink/SDLProxy.m
@@ -176,19 +176,23 @@ static float DefaultConnectionTimeout = 45.0;
return ret;
}
-- (void)disconnectSession {
+- (void)disconnectSessionWithCompletionHandler:(void (^)(void))completionHandler {
SDLLogD(@"Disconnecting the proxy; stopping security manager and primary transport.");
if (self.protocol.securityManager != nil) {
[self.protocol.securityManager stop];
}
+ __weak typeof(self) weakSelf = self;
if (self.transport != nil) {
- [self.transport disconnect];
+ [self.transport disconnectWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ [[NSNotificationCenter defaultCenter] removeObserver:self];
+ [strongSelf.urlSession invalidateAndCancel];
+ return completionHandler();
+ }];
+ } else {
+ return completionHandler();
}
-
- [[NSNotificationCenter defaultCenter] removeObserver:self];
-
- [_urlSession invalidateAndCancel];
}
- (void)dealloc {
@@ -209,33 +213,30 @@ static float DefaultConnectionTimeout = 45.0;
/// This was originally designed to make sure that the head unit properly knew about the mobile app's ability to run timers in the background, which affected heartbeat.
/// It may still affect some features on the head unit and the ability for the head unit to know which app is in the foreground is useful. It should not be removed due to unknown backward compatibility issues.
- (void)sdl_sendMobileHMIState {
- __block UIApplicationState appState = UIApplicationStateInactive;
- if ([NSThread isMainThread]) {
- appState = [UIApplication sharedApplication].applicationState;
- } else {
- dispatch_sync(dispatch_get_main_queue(), ^{
- appState = [UIApplication sharedApplication].applicationState;
- });
- }
-
- SDLOnHMIStatus *HMIStatusRPC = [[SDLOnHMIStatus alloc] init];
-
- HMIStatusRPC.audioStreamingState = SDLAudioStreamingStateNotAudible;
- HMIStatusRPC.systemContext = SDLSystemContextMain;
-
- switch (appState) {
- case UIApplicationStateActive: {
- HMIStatusRPC.hmiLevel = SDLHMILevelFull;
- } break;
- case UIApplicationStateBackground: // Fallthrough
- case UIApplicationStateInactive: {
- HMIStatusRPC.hmiLevel = SDLHMILevelBackground;
- } break;
- default: break;
- }
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+
+ UIApplicationState appState = [UIApplication sharedApplication].applicationState;
+ SDLOnHMIStatus *hmiStatusRPC = [[SDLOnHMIStatus alloc] init];
+
+ hmiStatusRPC.audioStreamingState = SDLAudioStreamingStateNotAudible;
+ hmiStatusRPC.systemContext = SDLSystemContextMain;
+
+ switch (appState) {
+ case UIApplicationStateActive: {
+ hmiStatusRPC.hmiLevel = SDLHMILevelFull;
+ } break;
+ case UIApplicationStateBackground: // Fallthrough
+ case UIApplicationStateInactive: {
+ hmiStatusRPC.hmiLevel = SDLHMILevelBackground;
+ } break;
+ default: break;
+ }
- SDLLogD(@"Mobile UIApplication state changed, sending to remote system: %@", HMIStatusRPC.hmiLevel);
- [self sendRPC:HMIStatusRPC];
+ SDLLogD(@"Mobile UIApplication state changed, sending to remote system: %@", hmiStatusRPC.hmiLevel);
+ [strongSelf sendRPC:hmiStatusRPC];
+ });
}
#pragma mark - Accessors
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.h b/SmartDeviceLink/SDLSecondaryTransportManager.h
index 85f3cc79e..c42068afc 100644
--- a/SmartDeviceLink/SDLSecondaryTransportManager.h
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.h
@@ -46,11 +46,13 @@ extern SDLSecondaryTransportState *const SDLSecondaryTransportStateReconnecting;
/// @param primaryProtocol The protocol that runs on the main (primary) transport
- (void)startWithPrimaryProtocol:(SDLProtocol *)primaryProtocol;
-/// Stop the manager.
-- (void)stop;
+/// Stop the manager
+/// @param completionHandler Handler called when the manager has shutdown
+- (void)stopWithCompletionHandler:(void (^)(void))completionHandler;
/// Destroys the secondary transport.
-- (BOOL)disconnectSecondaryTransport;
+/// @param completionHandler Handler called when the session has been destroyed
+- (void)disconnectSecondaryTransportWithCompletionHandler:(void (^)(void))completionHandler;
@end
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.m b/SmartDeviceLink/SDLSecondaryTransportManager.m
index a65bd4bb3..64d2b980f 100644
--- a/SmartDeviceLink/SDLSecondaryTransportManager.m
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.m
@@ -110,9 +110,15 @@ struct TransportProtocolUpdated {
/// The current hmi level of the SDL app.
@property (strong, nonatomic, nullable) SDLHMILevel currentHMILevel;
+/// The current application state of the app on the phone. This information is tracked because TCP transport can only reliably work when the app on the phone is in the foreground. If the phone is locked or the app is put in the background for a long time, the OS can reclaim the socket.
+@property (assign, nonatomic) UIApplicationState currentApplicationState;
+
/// A background task used to close the secondary transport before the app is suspended.
@property (strong, nonatomic) SDLBackgroundTaskManager *backgroundTaskManager;
+/// A handler called when the secondary transport has been shutdown
+@property (nonatomic, copy, nullable) void (^disconnectCompletionHandler)(void);
+
@end
@implementation SDLSecondaryTransportManager
@@ -139,7 +145,16 @@ struct TransportProtocolUpdated {
_backgroundTaskManager = [[SDLBackgroundTaskManager alloc] initWithBackgroundTaskName:BackgroundTaskSecondaryTransportName];
+ _currentApplicationState = UIApplicationStateInactive;
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ strongSelf.currentApplicationState = UIApplication.sharedApplication.applicationState;
+ });
+
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_hmiStatusDidChange:) name:SDLDidChangeHMIStatusNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_appStateDidBecomeActive:) name:UIApplicationDidBecomeActiveNotification object:nil];
+ [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_appStateDidBecomeInactive:) name:UIApplicationWillResignActiveNotification object:nil];
return self;
}
@@ -166,7 +181,7 @@ struct TransportProtocolUpdated {
[self.stateMachine transitionToState:SDLSecondaryTransportStateStarted];
}
-- (void)stop {
+- (void)stopWithCompletionHandler:(void (^)(void))completionHandler {
SDLLogD(@"Stopping manager");
// this method must be called in SDLLifecycleManager's state machine queue
@@ -178,32 +193,20 @@ struct TransportProtocolUpdated {
SDLLogD(@"Stopping audio / video services on both transports");
[self sdl_handleTransportUpdateWithPrimaryAvailable:NO secondaryAvailable:NO];
- [self.stateMachine transitionToState:SDLSecondaryTransportStateStopped];
-}
+ if ([self.stateMachine.currentState isEqualToEnum:SDLSecondaryTransportStateStopped]) {
+ return completionHandler();
+ }
-#pragma mark - Manager Lifecycle
+ // In order to stop the secondary transport
+ // 1. The observer must be told to shut down the services running on the transports. This can take a few moments since a request must be sent to the module a response must be awaited.
+ // 2. Once the services have been shutdown, the observer will notifiy this class by calling `disconnectSecondaryTransportWithCompletionHandler`. The secondary transport can now be shutdown. This can take a few memoments since the I/O streams must be closed on the thread they were opened on.
+ // 3. Once the secondary transport has shutdown successfully, the `disconnectCompletionHandler` will be called.
+ self.disconnectCompletionHandler = completionHandler;
-- (void)sdl_startManager {
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationDidBecomeActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(sdl_onAppStateUpdated:) name:UIApplicationWillResignActiveNotification object:nil];
+ [self.stateMachine transitionToState:SDLSecondaryTransportStateStopped];
}
-- (void)sdl_stopManager {
- SDLLogD(@"SDLSecondaryTransportManager stop");
-
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidBecomeActiveNotification object:nil];
- [[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillResignActiveNotification object:nil];
-
- self.streamingServiceTransportMap = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
- @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
- self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
- self.transportsForAudioService = @[];
- self.transportsForVideoService = @[];
-
- self.ipAddress = nil;
- self.tcpPort = TCPPortUnspecified;
- self.currentHMILevel = nil;
-}
+#pragma mark - Manager Lifecycle
- (void)sdl_configureManager:(nullable NSArray<SDLSecondaryTransportTypeBox *> *)availableSecondaryTransports
availableTransportsForAudio:(nullable NSArray<SDLTransportClassBox *> *)availableTransportsForAudio
@@ -264,12 +267,25 @@ struct TransportProtocolUpdated {
- (void)didEnterStateStopped {
SDLLogD(@"Secondary transport manager stopped");
- [self sdl_stopManager];
+
+ self.streamingServiceTransportMap = [@{@(SDLServiceTypeAudio):@(SDLTransportClassInvalid),
+ @(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
+ self.secondaryTransportType = SDLSecondaryTransportTypeDisabled;
+ self.transportsForAudioService = @[];
+ self.transportsForVideoService = @[];
+
+ self.ipAddress = nil;
+ self.tcpPort = TCPPortUnspecified;
+ self.currentHMILevel = nil;
+
+ if (self.disconnectCompletionHandler != nil) {
+ self.disconnectCompletionHandler();
+ self.disconnectCompletionHandler = nil;
+ }
}
- (void)didEnterStateStarted {
SDLLogD(@"Secondary transport manager started");
- [self sdl_startManager];
}
- (void)didEnterStateConfigured {
@@ -280,6 +296,8 @@ struct TransportProtocolUpdated {
|| self.secondaryTransportType == SDLSecondaryTransportTypeIAP)
&& self.sdl_isHMILevelNonNone) {
[self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+ } else {
+ SDLLogD(@"The secondary transport manager is not ready to transition to connecting state. The app's application state needs to be Active in order to create a socket (current app state is %ld) and we need the socket address (current IP address: %@ and port: %d)", (long)self.currentApplicationState, self.ipAddress, self.tcpPort);
}
}
@@ -304,17 +322,12 @@ struct TransportProtocolUpdated {
[self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
}
-- (void)willTransitionFromStateRegisteredToStateReconnecting {
- SDLLogD(@"Manger is closing transport but will try to reconnect if configured correctly. Stopping services on secondary transport");
- [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
-}
-
- (void)didEnterStateReconnecting {
- SDLLogD(@"Secondary transport is reconnecting");
+ SDLLogD(@"The secondary transport manager will try to reconnect in %.01f seconds", RetryConnectionDelay);
__weak typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(RetryConnectionDelay * NSEC_PER_SEC)), _stateMachineQueue, ^{
if ([weakSelf.stateMachine isCurrentState:SDLSecondaryTransportStateReconnecting]) {
- SDLLogD(@"Attempting reconnection");
+ SDLLogD(@"Attempting to reconnect");
[weakSelf.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
}
});
@@ -427,20 +440,24 @@ struct TransportProtocolUpdated {
}
}
-- (BOOL)disconnectSecondaryTransport {
+- (void)disconnectSecondaryTransportWithCompletionHandler:(void (^)(void))completionHandler {
if (self.secondaryTransport == nil) {
SDLLogW(@"Attempted to disconnect secondary transport but it's already stopped.");
- return NO;
+ return completionHandler();
}
- SDLLogD(@"Disconnecting secondary transport");
- [self.secondaryTransport disconnect];
- self.secondaryTransport = nil;
- self.secondaryProtocol = nil;
+ __weak typeof(self) weakSelf = self;
+ [self.secondaryTransport disconnectWithCompletionHandler:^{
+ SDLLogD(@"Disconnecting secondary transport manager");
- [self.backgroundTaskManager endBackgroundTask];
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ strongSelf.secondaryTransport = nil;
+ strongSelf.secondaryProtocol = nil;
+ [strongSelf.streamingServiceTransportMap removeAllObjects];
- return YES;
+ [strongSelf.backgroundTaskManager endBackgroundTask];
+ return completionHandler();
+ }];
}
- (BOOL)sdl_startTCPSecondaryTransport {
@@ -514,7 +531,7 @@ struct TransportProtocolUpdated {
return NO;
}
- if (self.sdl_getAppState != UIApplicationStateActive) {
+ if (self.currentApplicationState != UIApplicationStateActive) {
SDLLogD(@"App state is not Active, TCP transport is not ready");
return NO;
}
@@ -522,19 +539,6 @@ struct TransportProtocolUpdated {
return YES;
}
-- (UIApplicationState)sdl_getAppState {
- if ([NSThread isMainThread]) {
- return [UIApplication sharedApplication].applicationState;
- } else {
- __block UIApplicationState appState;
- dispatch_sync(dispatch_get_main_queue(), ^{
- appState = [UIApplication sharedApplication].applicationState;
- });
- return appState;
- }
-}
-
-
#pragma mark - SDLProtocolListener Implementation
// called on transport's thread, notifying that the transport is established
@@ -551,12 +555,12 @@ struct TransportProtocolUpdated {
return;
}
- // if the state is still Connecting, go back to Configured state and retry immediately
+ // If still in Connecting state, shutdown the transport and try to reconnect
if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateConnecting]) {
- SDLLogD(@"Retry secondary transport connection after registration timeout");
- [strongSelf.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ SDLLogD(@"Shutting down and restarting the secondary transport connection after registration timeout");
+ [strongSelf sdl_transportClosed];
} else {
- SDLLogD(@"Will not retry secondary transport connection because current state is: %@", strongSelf.stateMachine.currentState);
+ SDLLogD(@"Could not register the secondary transport with the module. The services will not be started on the secondary transport.");
}
});
};
@@ -565,19 +569,29 @@ struct TransportProtocolUpdated {
[self.secondaryProtocol registerSecondaryTransport];
}
+/// Called on the transport's thread, notifying that the transport has errored before a connection was established
+/// @param error The error
- (void)onTransportError:(NSError *)error {
- SDLLogE(@"The transport errored. Disconnecting the transport");
- [self disconnectSecondaryTransport];
+ SDLLogE(@"The secondary transport errored.");
+ [self sdl_transportClosed];
}
-// called on transport's thread, notifying that the transport is disconnected
+// Called on transport's thread, notifying that the transport is disconnected
// (Note: when transport's disconnect method is called, this method will not be called)
- (void)onProtocolClosed {
- SDLLogD(@"secondary transport disconnected");
+ SDLLogE(@"The secondary transport disconnected.");
+ [self sdl_transportClosed];
+}
+/// Try to reconnect the secondary transport if the transport errored during the connection attempt or closed unexpectedly.
+- (void)sdl_transportClosed {
dispatch_async(self.stateMachineQueue, ^{
if ([self sdl_isTransportOpened]) {
+ SDLLogV(@"Secondary transport is ready to reconnect. Attempting to reconnect the secondary transport");
+ [self.streamingProtocolDelegate transportClosed];
[self.stateMachine transitionToState:SDLSecondaryTransportStateReconnecting];
+ } else {
+ SDLLogD(@"Secondary transport is not ready to reconnect. Will not attempt to reconnect the secondary transport");
}
});
}
@@ -597,6 +611,9 @@ struct TransportProtocolUpdated {
SDLLogW(@"Received Register Secondary Transport NAK frame");
dispatch_async(self.stateMachineQueue, ^{
+ if ([self.stateMachine.currentState isEqualToEnum:SDLSecondaryTransportStateRegistered]) {
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
+ }
[self.stateMachine transitionToState:SDLSecondaryTransportStateReconnecting];
});
}
@@ -677,34 +694,42 @@ struct TransportProtocolUpdated {
/// Closes and re-opens the the secondary transport when the app is backgrounded and foregrounded on the device respectively. This is done because sockets can be reclaimed by the system at anytime when the app is not in the foreground.
/// @param notification Notification from the OS that the app's life-cycle state has changed
-- (void)sdl_onAppStateUpdated:(NSNotification *)notification {
+- (void)sdl_appStateDidBecomeActive:(NSNotification *)notification {
+ self.currentApplicationState = UIApplicationStateActive;
+
__weak typeof(self) weakSelf = self;
dispatch_async(self.stateMachineQueue, ^{
__strong typeof(self) strongSelf = weakSelf;
- if (notification.name == UIApplicationWillResignActiveNotification) {
- SDLLogD(@"App will enter the background");
- if ([strongSelf sdl_isTransportOpened] && strongSelf.secondaryTransportType == SDLSecondaryTransportTypeTCP) {
- SDLLogD(@"Starting background task to keep TCP transport alive");
- strongSelf.backgroundTaskManager.taskExpiringHandler = [strongSelf sdl_backgroundTaskEndedHandler];
- [strongSelf.backgroundTaskManager startBackgroundTask];
- } else {
- SDLLogD(@"TCP transport already disconnected, will not start a background task.");
- }
- } else if (notification.name == UIApplicationDidBecomeActiveNotification) {
- SDLLogD(@"App entered the foreground");
- if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateRegistered]) {
- SDLLogD(@"In the registered state; TCP transport has not yet been shutdown. Ending the background task.");
- [strongSelf.backgroundTaskManager endBackgroundTask];
- } else if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured]
- && strongSelf.secondaryTransportType == SDLSecondaryTransportTypeTCP
- && [strongSelf sdl_isTCPReady]
- && [strongSelf sdl_isHMILevelNonNone]) {
- SDLLogD(@"In the configured state; restarting the TCP transport. Ending the background task.");
- [strongSelf.backgroundTaskManager endBackgroundTask];
- [strongSelf.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
- } else {
- SDLLogD(@"TCP transport not ready to start, our current state is: %@", strongSelf.stateMachine.currentState);
- }
+ SDLLogD(@"App entered the foreground");
+ if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateRegistered]) {
+ SDLLogD(@"In the registered state; TCP transport has not yet been shutdown. Ending the background task.");
+ [strongSelf.backgroundTaskManager endBackgroundTask];
+ } else if ([strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured]
+ && strongSelf.secondaryTransportType == SDLSecondaryTransportTypeTCP
+ && [strongSelf sdl_isTCPReady]
+ && [strongSelf sdl_isHMILevelNonNone]) {
+ SDLLogD(@"In the configured state; restarting the TCP transport. Ending the background task.");
+ [strongSelf.backgroundTaskManager endBackgroundTask];
+ [strongSelf.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
+ } else {
+ SDLLogD(@"TCP transport not ready to start, secondary transport state must be in state Configured (currently in state: %@), the SDL app hmi level must be non-NONE (currently in state: %@), and the app must be ready to start a TCP socket (current app state: %ld, current IP address: %@, current port: %d)", strongSelf.stateMachine.currentState, strongSelf.currentHMILevel, self.currentApplicationState, self.ipAddress, self.tcpPort);
+ }
+ });
+}
+
+- (void)sdl_appStateDidBecomeInactive:(NSNotification *)notification {
+ self.currentApplicationState = UIApplicationStateInactive;
+
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(self.stateMachineQueue, ^{
+ __strong typeof(self) strongSelf = weakSelf;
+ SDLLogD(@"App will enter the background");
+ if ([strongSelf sdl_isTransportOpened] && strongSelf.secondaryTransportType == SDLSecondaryTransportTypeTCP) {
+ SDLLogD(@"Starting background task to keep TCP transport alive");
+ strongSelf.backgroundTaskManager.taskExpiringHandler = [strongSelf sdl_backgroundTaskEndedHandler];
+ [strongSelf.backgroundTaskManager startBackgroundTask];
+ } else {
+ SDLLogD(@"TCP transport already disconnected, will not start a background task.");
}
});
}
@@ -715,7 +740,7 @@ struct TransportProtocolUpdated {
__weak typeof(self) weakSelf = self;
return ^{
__strong typeof(self) strongSelf = weakSelf;
- if (strongSelf.sdl_getAppState == UIApplicationStateActive || [strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateStopped]) {
+ if (strongSelf.currentApplicationState == UIApplicationStateActive || [strongSelf.stateMachine isCurrentState:SDLSecondaryTransportStateStopped]) {
// Return NO as we do not need to perform any cleanup and can end the background task immediately
SDLLogV(@"No cleanup needed since app has been foregrounded.");
return NO;
@@ -790,6 +815,7 @@ struct TransportProtocolUpdated {
}
#pragma mark - RPC Notifications
+
/// Check and track the HMI status to ensure that the secondary transport only attempts a connection in non-NONE HMI states
///
/// See: https://github.com/smartdevicelink/sdl_evolution/blob/master/proposals/0214-secondary-transport-optimization.md
diff --git a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.h b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.h
index cfa10ca5d..75e972ec5 100644
--- a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.h
+++ b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.h
@@ -79,6 +79,11 @@ NS_ASSUME_NONNULL_BEGIN
/// @param audioEndedCompletionHandler Called when the module ACKs or NAKs to the request to end the audio service.
- (void)endAudioServiceWithCompletionHandler:(void (^)(void))audioEndedCompletionHandler;
+/// This method is used internally to stop audio streaming when the secondary transport has been closed due to an connection error. The primary transport is still open.
+/// 1. Since the transport has been closed, we can not send an end audio service control frame to the module.
+/// 2. Since the primary transport is still open, we will not reset the `hmiLevel`. This lets us resume audio streaming if the secondary transport can be reestablished during the same app session.
+- (void)secondaryTransportDidDisconnect;
+
/**
* This method receives PCM audio data and will attempt to send that data across to the head unit for immediate playback
*
diff --git a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
index 50fb56dac..554eb89e1 100644
--- a/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingAudioLifecycleManager.m
@@ -108,9 +108,13 @@ NS_ASSUME_NONNULL_BEGIN
[self.audioStreamStateMachine transitionToState:SDLAudioStreamManagerStateStopped];
}
-- (void)endAudioServiceWithCompletionHandler:(void (^)(void))completionHandler {
+- (void)secondaryTransportDidDisconnect {
+ [self.audioStreamStateMachine transitionToState:SDLAudioStreamManagerStateStopped];
+}
+
+- (void)endAudioServiceWithCompletionHandler:(void (^)(void))audioEndedCompletionHandler {
SDLLogD(@"Ending audio service");
- self.audioServiceEndedCompletionHandler = completionHandler;
+ self.audioServiceEndedCompletionHandler = audioEndedCompletionHandler;
[self.audioTranscodingManager stop];
[self.protocol endServiceWithType:SDLServiceTypeAudio];
diff --git a/SmartDeviceLink/SDLStreamingMediaManager.m b/SmartDeviceLink/SDLStreamingMediaManager.m
index 01c8e557e..ed9ec3ec6 100644
--- a/SmartDeviceLink/SDLStreamingMediaManager.m
+++ b/SmartDeviceLink/SDLStreamingMediaManager.m
@@ -98,13 +98,13 @@ NS_ASSUME_NONNULL_BEGIN
[self didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:protocol fromOldAudioProtocol:nil toNewAudioProtocol:protocol];
}
-- (void)sdl_disconnectSecondaryTransport {
+- (void)sdl_disconnectSecondaryTransportWithCompletionHandler:(void (^)(void))completionHandler {
if (self.secondaryTransportManager == nil) {
SDLLogV(@"Attempting to disconnect a non-existent secondary transport. Returning.");
- return;
+ return completionHandler();
}
- [self.secondaryTransportManager disconnectSecondaryTransport];
+ [self.secondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:completionHandler];
}
# pragma mark SDLStreamingProtocolDelegate
@@ -149,22 +149,51 @@ NS_ASSUME_NONNULL_BEGIN
// This will always run
dispatch_group_notify(endServiceTask, [SDLGlobals sharedGlobals].sdlProcessingQueue, ^{
if (oldVideoProtocol != nil || oldAudioProtocol != nil) {
- SDLLogV(@"Disconnecting the secondary transport");
- [self sdl_disconnectSecondaryTransport];
-
- SDLLogD(@"Destroying the audio and video protocol and starting new audio and video protocols");
- self.audioProtocol = nil;
- self.videoProtocol = nil;
+ [self sdl_reconnectSecondaryTransportWithNewVideoProtocol:newVideoProtocol newAudioProtocol:newAudioProtocol transportDestroyed:false];
+ } else {
+ SDLLogV(@"No need to disconnect the secondary transport. Starting new audio and video protocols");
+ [self sdl_startNewProtocolForAudio:newAudioProtocol forVideo:newVideoProtocol];
}
-
- [self sdl_startNewProtocolForAudio:newAudioProtocol forVideo:newVideoProtocol];
});
}
+- (void)transportClosed {
+ [self sdl_reconnectSecondaryTransportWithNewVideoProtocol:nil newAudioProtocol:nil transportDestroyed:true];
+}
+
+/// Disconnects the secondary transport. If the transport is still open and a new video or audio protocol have been set, then a new video/audio sessions are attempted. If the transport has been closed, then the audio/video managers are stopped.
+/// @param newVideoProtocol The new video protocol
+/// @param newAudioProtocol The new audio protocol
+/// @param transportDestroyed Whether or not the transport is still open
+- (void)sdl_reconnectSecondaryTransportWithNewVideoProtocol:(nullable SDLProtocol *)newVideoProtocol newAudioProtocol:(nullable SDLProtocol *)newAudioProtocol transportDestroyed:(BOOL)transportDestroyed {
+ SDLLogV(@"Disconnecting the secondary transport");
+ __weak typeof(self) weakSelf = self;
+ [self sdl_disconnectSecondaryTransportWithCompletionHandler:^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ if (transportDestroyed) {
+ SDLLogD(@"Transport destroyed. Shutting down audio and video managers");
+ [strongSelf.audioLifecycleManager secondaryTransportDidDisconnect];
+ [strongSelf.videoLifecycleManager secondaryTransportDidDisconnect];
+ strongSelf.audioProtocol = nil;
+ strongSelf.videoProtocol = nil;
+ } else {
+ SDLLogD(@"Checking if new audio and video sessions need to be started on the transport");
+ strongSelf.audioProtocol = nil;
+ strongSelf.videoProtocol = nil;
+ [strongSelf sdl_startNewProtocolForAudio:newAudioProtocol forVideo:newVideoProtocol];
+ }
+ }];
+}
+
/// Starts the audio and/or video services using the new protocol.
/// @param newAudioProtocol The new audio protocol
/// @param newVideoProtocol The new video protocol
- (void)sdl_startNewProtocolForAudio:(nullable SDLProtocol *)newAudioProtocol forVideo:(nullable SDLProtocol *)newVideoProtocol {
+ if (newAudioProtocol == nil && newVideoProtocol == nil) {
+ SDLLogD(@"No new audio or video session will be started");
+ return;
+ }
+
if (newAudioProtocol != nil) {
self.audioProtocol = newAudioProtocol;
[self.audioLifecycleManager startWithProtocol:newAudioProtocol];
diff --git a/SmartDeviceLink/SDLStreamingProtocolDelegate.h b/SmartDeviceLink/SDLStreamingProtocolDelegate.h
index 259cc502b..1639c3b7d 100644
--- a/SmartDeviceLink/SDLStreamingProtocolDelegate.h
+++ b/SmartDeviceLink/SDLStreamingProtocolDelegate.h
@@ -14,9 +14,9 @@ NS_ASSUME_NONNULL_BEGIN
@protocol SDLStreamingProtocolDelegate <NSObject>
-/// Called when protocol instance for audio and/or video service has been updated.
+/// Called when protocol instance for audio and/or video service has been updated and the transport is established.
///
-/// If `newVideoProtocol` or `newAudioProtocol` is nil it indicates that underlying transport has become unavailable.
+/// If `newVideoProtocol` or `newAudioProtocol` have been set then the transport is established and a current session can be started. If `newVideoProtocol` or `newAudioProtocol` is nil it indicates that underlying transport will become unavailable and any ongoing sessions on that transport should be stopped (i.e. send a request to the module to stop the session and wait for a response).
///
/// @param oldVideoProtocol protocol instance that was being used for video streaming
/// @param newVideoProtocol protocol instance that will be used for video streaming
@@ -24,6 +24,11 @@ NS_ASSUME_NONNULL_BEGIN
/// @param newAudioProtocol protocol instance that will be used for audio streaming
- (void)didUpdateFromOldVideoProtocol:(nullable SDLProtocol *)oldVideoProtocol toNewVideoProtocol:(nullable SDLProtocol *)newVideoProtocol fromOldAudioProtocol:(nullable SDLProtocol *)oldAudioProtocol toNewAudioProtocol:(nullable SDLProtocol *)newAudioProtocol;
+/// Called when the audio and/or video must be stopped because the transport has been destroyed or errored out.
+///
+/// Since the transport does not exist, current sessions can not be properly stopped (i.e. no request can be sent module to stop the session) but the observer still needs to stop creating and sending data.
+- (void)transportClosed;
+
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.h b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.h
index 54021b6ba..f3b5abee7 100644
--- a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.h
+++ b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.h
@@ -157,6 +157,11 @@ NS_ASSUME_NONNULL_BEGIN
/// @param videoEndedCompletionHandler Called when the module ACKs or NAKs to the request to end the video service.
- (void)endVideoServiceWithCompletionHandler:(void (^)(void))videoEndedCompletionHandler;
+/// This method is used internally to stop video streaming when the secondary transport has been closed due to an connection error. The primary transport is still open.
+/// 1. Since the transport has been closed, we can not send an end video service control frame to the module.
+/// 2. Since the primary transport is still open, we will not reset the `hmiLevel`, `videoStreamingState` or the video scale manager. This lets us resume video streaming if the secondary transport can be reestablished during the same app session.
+- (void)secondaryTransportDidDisconnect;
+
/**
* This method receives raw image data and will run iOS8+'s hardware video encoder to turn the data into a video stream, which will then be passed to the connected head unit.
*
diff --git a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
index 168ddf3e3..6e0b8a0c7 100644
--- a/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
+++ b/SmartDeviceLink/SDLStreamingVideoLifecycleManager.m
@@ -212,10 +212,16 @@ typedef void(^SDLVideoCapabilityResponseHandler)(SDLVideoStreamingCapability *_N
[self.videoStreamStateMachine transitionToState:SDLVideoStreamManagerStateStopped];
}
-- (void)endVideoServiceWithCompletionHandler:(void (^)(void))completionHandler {
+- (void)secondaryTransportDidDisconnect {
+ SDLLogD(@"Stopping video manager");
+ [self.focusableItemManager stop];
+ [self.videoStreamStateMachine transitionToState:SDLVideoStreamManagerStateStopped];
+}
+
+- (void)endVideoServiceWithCompletionHandler:(void (^)(void))videoEndedCompletionHandler {
SDLLogD(@"Ending video service");
[self sdl_disposeDisplayLink];
- self.videoServiceEndedCompletionHandler = completionHandler;
+ self.videoServiceEndedCompletionHandler = videoEndedCompletionHandler;
[self.protocol endServiceWithType:SDLServiceTypeVideo];
}
diff --git a/SmartDeviceLink/SDLTCPTransport.m b/SmartDeviceLink/SDLTCPTransport.m
index 0dc347f8a..962c1ffc4 100644
--- a/SmartDeviceLink/SDLTCPTransport.m
+++ b/SmartDeviceLink/SDLTCPTransport.m
@@ -9,6 +9,7 @@
#import "SDLTCPTransport.h"
#import "SDLMutableDataQueue.h"
#import "SDLError.h"
+#import "SDLGlobals.h"
#import "SDLLogMacros.h"
#import "SDLTimer.h"
#import <errno.h>
@@ -16,6 +17,7 @@
NS_ASSUME_NONNULL_BEGIN
NSString *const TCPIOThreadName = @"com.smartdevicelink.tcpiothread";
+NSTimeInterval const IOThreadCanceledSemaphoreWaitSecs = 1.0;
NSUInteger const DefaultReceiveBufferSize = 16 * 1024;
NSTimeInterval ConnectionTimeoutSecs = 30.0;
@@ -30,15 +32,21 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
@property (nullable, nonatomic, strong) SDLTimer *connectionTimer;
@property (nonatomic, assign) BOOL transportConnected;
@property (nonatomic, assign) BOOL transportErrorNotified;
+/// A semaphore used to block the current thread until we know that the I/O streams have been shutdown on the ioThread
+@property (nonatomic, strong) dispatch_semaphore_t ioThreadCancelledSemaphore;
+
@end
@implementation SDLTCPTransport
- (instancetype)init {
- if (self = [super init]) {
- _receiveBufferSize = DefaultReceiveBufferSize;
- _sendDataQueue = [[SDLMutableDataQueue alloc] init];
- }
+ self = [super init];
+ if (!self) { return nil; }
+
+ _receiveBufferSize = DefaultReceiveBufferSize;
+ _sendDataQueue = [[SDLMutableDataQueue alloc] init];
+ _ioThreadCancelledSemaphore = dispatch_semaphore_create(0);
+
return self;
}
@@ -52,10 +60,6 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
return self;
}
-- (void)dealloc {
- SDLLogV(@"dealloc");
-}
-
#pragma mark - Stream Lifecycle
// Note: When a connection is refused (e.g. TCP port number is not correct) or timed out (e.g. invalid IP address), then onError will be notified.
@@ -95,22 +99,55 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
[self.ioThread start];
}
-- (void)disconnect {
- if (self.ioThread == nil) {
- SDLLogV(@"TCP transport thread already disconnected");
- return;
- }
-
+- (void)disconnectWithCompletionHandler:(void (^)(void))disconnectCompletionHandler {
SDLLogD(@"Disconnecting");
[self.sendDataQueue removeAllObjects];
self.transportErrorNotified = NO;
self.transportConnected = NO;
- // Attempt to cancel the `ioThread`. Once the thread realizes it has been cancelled, it will cleanup the input/output streams.
- [self sdl_cancelIOThread];
+ if (self.ioThread == nil) {
+ SDLLogV(@"TCP transport not yet started or has been shutdown");
+ return disconnectCompletionHandler();
+ }
+
+ // Tell the ioThread to shutdown the I/O streams. The I/O streams must be opened and closed on the same thread; if they are not, random crashes can occur. Dispatch this task to the main queue to ensure that this task is performed on the Main Thread. We are using the Main Thread for ease since we don't want to create a separate thread just to wait on closing the I/O streams. Using the Main Thread ensures that semaphore wait is not called from ioThread, which would block the ioThread and prevent shutdown.
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(dispatch_get_main_queue(), ^{
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+
+ // Attempt to cancel the ioThread. Once the thread realizes it has been cancelled, it will cleanup the I/O streams. Make sure to wake up the run loop in case there is no current I/O event running on the ioThread.
+ [strongSelf.ioThread cancel];
+ [strongSelf performSelector:@selector(sdl_doNothing) onThread:self.ioThread withObject:nil waitUntilDone:NO];
+
+ // Block the thread until the semaphore has been released by the ioThread (or a timeout has occured).
+ BOOL cancelledSuccessfully = [strongSelf sdl_isIOThreadCancelled];
+ if (!cancelledSuccessfully) {
+ SDLLogE(@"The I/O streams were not shut down successfully. We might not be able to create a new session with an accessory during the same app session. If this happens, only force quitting and restarting the app will allow new sessions.");
+ }
+
+ disconnectCompletionHandler();
+ });
}
+/// Wait for the ioThread to destroy the I/O streams. To ensure that we are not blocking the ioThread, this method should only be called from the main thread.
+/// @return Whether or not the session's I/O streams were closed successfully.
+- (BOOL)sdl_isIOThreadCancelled {
+ NSAssert(![NSThread.currentThread.name isEqualToString:TCPIOThreadName], @"%@ must not be called from the ioThread!", NSStringFromSelector(_cmd));
+
+ long lWait = dispatch_semaphore_wait(self.ioThreadCancelledSemaphore, dispatch_time(DISPATCH_TIME_NOW, (int64_t)(IOThreadCanceledSemaphoreWaitSecs * NSEC_PER_SEC)));
+ if (lWait == 0) {
+ SDLLogD(@"ioThread cancelled successfully");
+ return YES;
+ }
+
+ SDLLogE(@"Failed to cancel ioThread within %f seconds", IOThreadCanceledSemaphoreWaitSecs);
+ return NO;
+}
+
+/// Helper method for waking up the ioThread.
+- (void)sdl_doNothing {}
+
#pragma mark - Data Transmission
- (void)sendData:(NSData *)msgBytes {
@@ -139,27 +176,36 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
[self.inputStream open];
[self.outputStream open];
- while (self.ioThread != nil && ![self.ioThread isCancelled]) {
+ while (self.ioThread != nil && !self.ioThread.cancelled) {
BOOL ret = [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate distantFuture]];
if (!ret) {
SDLLogW(@"Failed to start TCP transport run loop");
break;
}
}
- SDLLogD(@"TCP transport run loop terminated");
+ // Close the I/O streams
+ SDLLogD(@"TCP transport run loop terminated");
[self sdl_teardownStream:self.inputStream];
[self sdl_teardownStream:self.outputStream];
-
+ self.inputStream = nil;
+ self.outputStream = nil;
[self.connectionTimer cancel];
+
+ // If a thread is blocked waiting on the I/O streams to shutdown, let the thread know that shutdown has completed.
+ dispatch_semaphore_signal(self.ioThreadCancelledSemaphore);
}
}
+/// Configures a stream
+/// @param stream An input or output stream
- (void)sdl_setupStream:(NSStream *)stream {
stream.delegate = self;
[stream scheduleInRunLoop:[NSRunLoop currentRunLoop] forMode:NSDefaultRunLoopMode];
}
+/// Tears down a stream
+/// @param stream An input or output stream
- (void)sdl_teardownStream:(NSStream *)stream {
NSStreamStatus streamStatus = stream.streamStatus;
if (streamStatus != NSStreamStatusNotOpen && streamStatus != NSStreamStatusClosed) {
@@ -169,13 +215,6 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
[stream setDelegate:nil];
}
-- (void)sdl_cancelIOThread {
- [self.ioThread cancel];
-
- // Wake up the run loop in case we don't have any I/O event
- [self performSelector:@selector(sdl_doNothing) onThread:self.ioThread withObject:nil waitUntilDone:NO];
-}
-
#pragma mark - NSStreamDelegate
// this method runs only on the I/O thread (i.e. invoked from the run loop)
@@ -271,8 +310,6 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onConnectionTimedOut is called on a wrong thread!");
SDLLogW(@"TCP connection timed out");
- [self sdl_cancelIOThread];
-
if (!self.transportErrorNotified) {
NSAssert(!self.transportConnected, @"transport should not be connected in this case");
[self.delegate onError:[NSError sdl_transport_connectionTimedOutError]];
@@ -283,9 +320,6 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
- (void)sdl_onStreamError:(NSStream *)stream {
NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onStreamError is called on a wrong thread!");
- // stop I/O thread
- [self sdl_cancelIOThread];
-
// avoid notifying multiple error events
if (self.transportErrorNotified) {
return;
@@ -333,16 +367,12 @@ NSTimeInterval ConnectionTimeoutSecs = 30.0;
SDLLogD(@"Stream ended");
NSAssert([[NSThread currentThread] isEqual:self.ioThread], @"sdl_onStreamEnd is called on a wrong thread!");
- [self sdl_cancelIOThread];
-
if (!self.transportErrorNotified) {
[self.delegate onTransportDisconnected];
self.transportErrorNotified = YES;
}
}
-- (void)sdl_doNothing {}
-
@end
NS_ASSUME_NONNULL_END
diff --git a/SmartDeviceLink/SDLTransportType.h b/SmartDeviceLink/SDLTransportType.h
index 5d516808d..8cfb4ec7e 100644
--- a/SmartDeviceLink/SDLTransportType.h
+++ b/SmartDeviceLink/SDLTransportType.h
@@ -11,7 +11,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nullable, weak, nonatomic) id<SDLTransportDelegate> delegate;
- (void)connect;
-- (void)disconnect;
+- (void)disconnectWithCompletionHandler:(void (^)(void))disconnectCompletionHandler;
- (void)sendData:(NSData *)dataToSend;
@end
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLAudioStreamManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLAudioStreamManagerSpec.m
index 77999e9d9..1341fc916 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLAudioStreamManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLAudioStreamManagerSpec.m
@@ -33,8 +33,6 @@ describe(@"the audio stream manager", ^{
beforeEach(^{
mockAudioManager.audioConnected = NO;
[testManager pushWithFileURL:testAudioFileURL];
-
- [NSThread sleepForTimeInterval:0.5];
});
describe(@"after attempting to play the file", ^{
@@ -44,7 +42,7 @@ describe(@"the audio stream manager", ^{
});
it(@"should fail to send data", ^{
- expect(mockAudioManager.dataSinceClear.length).to(equal(0));
+ expect(mockAudioManager.dataSinceClear.length).toEventually(equal(0));
expect(mockAudioManager.error.code).toEventually(equal(SDLAudioStreamManagerErrorNotConnected));
});
});
@@ -54,8 +52,6 @@ describe(@"the audio stream manager", ^{
beforeEach(^{
mockAudioManager.audioConnected = NO;
[testManager pushWithData:testAudioFileData];
-
- [NSThread sleepForTimeInterval:0.5];
});
describe(@"after attempting to play the file", ^{
@@ -65,7 +61,7 @@ describe(@"the audio stream manager", ^{
});
it(@"should fail to send data", ^{
- expect(mockAudioManager.dataSinceClear.length).to(equal(0));
+ expect(mockAudioManager.dataSinceClear.length).toEventually(equal(0));
expect(mockAudioManager.error.code).toEventually(equal(SDLAudioStreamManagerErrorNotConnected));
});
});
@@ -76,27 +72,27 @@ describe(@"the audio stream manager", ^{
beforeEach(^{
mockAudioManager.audioConnected = YES;
[testManager pushWithFileURL:testAudioFileURL];
-
- [NSThread sleepForTimeInterval:0.5];
});
it(@"should have a file in the queue", ^{
- expect(testManager.queue).toNot(beEmpty());
+ expect(testManager.queue).toEventuallyNot(beEmpty());
});
describe(@"after attempting to play the file", ^{
beforeEach(^{
[mockAudioManager clearData];
[testManager playNextWhenReady];
-
- [NSThread sleepForTimeInterval:1.0];
});
it(@"should be sending data", ^{
expect(testManager.isPlaying).toEventually(beTrue());
expect(mockAudioManager.dataSinceClear.length).toEventually(equal(34380));
- // Fails when it shouldn't, `weakself` goes to nil in `sdl_playNextWhenReady`
+ // wait for the delegate to be called when the audio finishes
+ float waitTime = 1.1 + 0.25; // length of audio in testAudioFileURL + 0.25 buffer
+ NSLog(@"Please wait %f for audio file to finish playing...", waitTime);
+ [NSThread sleepForTimeInterval:waitTime];
+
expect(mockAudioManager.finishedPlaying).toEventually(beTrue());
});
});
@@ -116,12 +112,10 @@ describe(@"the audio stream manager", ^{
beforeEach(^{
mockAudioManager.audioConnected = YES;
[testManager pushWithData:testAudioFileData];
-
- [NSThread sleepForTimeInterval:0.5];
});
it(@"should have a file in the queue", ^{
- expect(testManager.queue).toNot(beEmpty());
+ expect(testManager.queue).toEventuallyNot(beEmpty());
});
describe(@"after attempting to play the audio buffer", ^{
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
index 0c1219cdb..550615de6 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
@@ -117,6 +117,7 @@ describe(@"a lifecycle manager", ^{
beforeEach(^{
OCMStub([proxyMock iapProxyWithListener:[OCMArg any] secondaryTransportManager:[OCMArg any] encryptionLifecycleManager:[OCMArg any]]).andReturn(proxyMock);
OCMStub([(SDLProxy*)proxyMock protocol]).andReturn(protocolMock);
+ OCMStub([proxyMock disconnectSessionWithCompletionHandler:[OCMArg invokeBlock]]);
SDLLifecycleConfiguration *testLifecycleConfig = [SDLLifecycleConfiguration defaultConfigurationWithAppName:@"Test App" appId:@"Test Id"];
testLifecycleConfig.shortAppName = @"Short Name";
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingAudioLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingAudioLifecycleManagerSpec.m
index 4770f44f1..96ea3897d 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingAudioLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingAudioLifecycleManagerSpec.m
@@ -46,8 +46,6 @@ describe(@"the streaming audio manager", ^{
hmiStatus.hmiLevel = hmiLevel;
SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:self rpcNotification:hmiStatus];
[[NSNotificationCenter defaultCenter] postNotification:notification];
-
- [NSThread sleepForTimeInterval:0.3];
};
beforeEach(^{
@@ -113,11 +111,10 @@ describe(@"the streaming audio manager", ^{
SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveRegisterAppInterfaceResponse object:self rpcResponse:someRegisterAppInterfaceResponse];
[[NSNotificationCenter defaultCenter] postNotification:notification];
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should should save the connected vehicle make", ^{
- expect(streamingLifecycleManager.connectedVehicleMake).to(equal(testVehicleType.make));
+ expect(streamingLifecycleManager.connectedVehicleMake).toEventually(equal(testVehicleType.make));
});
});
@@ -149,7 +146,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should close the streams", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateShuttingDown));
});
});
@@ -159,7 +156,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateShuttingDown));
});
});
@@ -169,7 +166,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateReady));
});
});
@@ -179,7 +176,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateReady));
});
});
});
@@ -208,7 +205,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should close the streams", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateShuttingDown));
});
});
@@ -218,7 +215,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateShuttingDown));
});
});
@@ -228,7 +225,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateReady));
});
});
@@ -238,7 +235,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateReady));
});
});
});
@@ -260,7 +257,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not start the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateStopped));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateStopped));
});
});
@@ -270,7 +267,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should not start the stream", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateStopped));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateStopped));
});
});
@@ -280,7 +277,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should start the streams", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateStarting));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateStarting));
});
});
@@ -290,7 +287,7 @@ describe(@"the streaming audio manager", ^{
});
it(@"should start the streams", ^{
- expect(streamingLifecycleManager.currentAudioStreamState).to(equal(SDLAudioStreamManagerStateStarting));
+ expect(streamingLifecycleManager.currentAudioStreamState).toEventually(equal(SDLAudioStreamManagerStateStarting));
});
});
});
@@ -392,7 +389,7 @@ describe(@"the streaming audio manager", ^{
});
describe(@"attempting to stop the manager", ^{
- __block BOOL handlerCalled = nil;
+ __block BOOL handlerCalled = NO;
beforeEach(^{
handlerCalled = NO;
diff --git a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
index 4dfae005b..8fb5caec7 100644
--- a/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
+++ b/SmartDeviceLinkTests/DevAPISpecs/SDLStreamingVideoLifecycleManagerSpec.m
@@ -72,8 +72,6 @@ describe(@"the streaming video manager", ^{
hmiStatus.videoStreamingState = streamState;
SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:self rpcNotification:hmiStatus];
[[NSNotificationCenter defaultCenter] postNotification:notification];
-
- [NSThread sleepForTimeInterval:0.3];
};
beforeEach(^{
@@ -183,12 +181,11 @@ describe(@"the streaming video manager", ^{
SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveRegisterAppInterfaceResponse object:self rpcResponse:someRegisterAppInterfaceResponse];
[[NSNotificationCenter defaultCenter] postNotification:notification];
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should save the connected vehicle make but not the screen size", ^{
- expect(@(CGSizeEqualToSize(streamingLifecycleManager.videoScaleManager.displayViewportResolution, CGSizeZero))).to(equal(@YES));
- expect(streamingLifecycleManager.connectedVehicleMake).to(equal(testVehicleType.make));
+ expect(@(CGSizeEqualToSize(streamingLifecycleManager.videoScaleManager.displayViewportResolution, CGSizeZero))).toEventually(equal(@YES));
+ expect(streamingLifecycleManager.connectedVehicleMake).toEventually(equal(testVehicleType.make));
});
});
@@ -213,11 +210,10 @@ describe(@"the streaming video manager", ^{
SDLRPCResponseNotification *notification = [[SDLRPCResponseNotification alloc] initWithName:SDLDidReceiveRegisterAppInterfaceResponse object:self rpcResponse:someRegisterAppInterfaceResponse];
[[NSNotificationCenter defaultCenter] postNotification:notification];
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should save the connected vehicle make and the screen size", ^{
- expect(@(CGSizeEqualToSize(streamingLifecycleManager.videoScaleManager.displayViewportResolution, CGSizeMake(600, 100)))).to(equal(@YES));
+ expect(@(CGSizeEqualToSize(streamingLifecycleManager.videoScaleManager.displayViewportResolution, CGSizeMake(600, 100)))).toEventually(equal(@YES));
expect(streamingLifecycleManager.connectedVehicleMake).toEventually(equal(testVehicleType.make));
});
});
@@ -250,7 +246,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -260,7 +256,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -270,7 +266,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateReady));
});
});
@@ -280,7 +276,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateReady));
});
});
@@ -290,7 +286,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
});
@@ -319,7 +315,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the streams", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -329,7 +325,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -339,7 +335,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateReady));
});
});
@@ -349,7 +345,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateReady));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateReady));
});
});
@@ -359,7 +355,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -370,7 +366,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
@@ -381,7 +377,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should close the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateShuttingDown));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateShuttingDown));
});
});
});
@@ -403,7 +399,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not start the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateStopped));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateStopped));
});
});
@@ -413,7 +409,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not start the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateStopped));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateStopped));
});
});
@@ -423,7 +419,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should start the streams", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateStarting));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateStarting));
});
});
@@ -433,7 +429,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should start the streams", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateStarting));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateStarting));
});
});
@@ -443,7 +439,7 @@ describe(@"the streaming video manager", ^{
});
it(@"should not start the stream", ^{
- expect(streamingLifecycleManager.currentVideoStreamState).to(equal(SDLVideoStreamManagerStateStopped));
+ expect(streamingLifecycleManager.currentVideoStreamState).toEventually(equal(SDLVideoStreamManagerStateStopped));
});
});
});
@@ -766,7 +762,7 @@ describe(@"the streaming video manager", ^{
});
describe(@"stopping the manager", ^{
- __block BOOL handlerCalled = nil;
+ __block BOOL handlerCalled = NO;
beforeEach(^{
handlerCalled = NO;
diff --git a/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
index b110af0f6..f3e34ee09 100644
--- a/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
+++ b/SmartDeviceLinkTests/ProxySpecs/SDLSecondaryTransportManagerSpec.m
@@ -44,7 +44,8 @@ typedef NS_ENUM(NSInteger, SDLSecondaryTransportType) {
};
// should be in sync with SDLSecondaryTransportManager.m
-static const float RetryConnectionDelay = 5.0;
+static const float RetryConnectionDelay = 5.25;
+static const float RegisterTransportTime = 10.25;
static const int TCPPortUnspecified = -1;
@@ -62,46 +63,13 @@ static const int TCPPortUnspecified = -1;
@property (strong, nonatomic, nullable) NSString *ipAddress;
@property (assign, nonatomic) int tcpPort;
@property (strong, nonatomic, nullable) SDLHMILevel currentHMILevel;
+@property (assign, nonatomic) UIApplicationState currentApplicationState;
@property (strong, nonatomic) SDLBackgroundTaskManager *backgroundTaskManager;
- (nullable BOOL (^)(void))sdl_backgroundTaskEndedHandler;
@end
-@interface SDLSecondaryTransportManager (ForTest)
-// Swap sdl_getAppState method to dummy implementation.
-// Since the test runs on the main thread, dispatch_sync()-ing to the main thread freezes the tests.
-+ (void)swapGetActiveAppStateMethod;
-+ (void)swapGetInactiveAppStateMethod;
-@end
-
-@implementation SDLSecondaryTransportManager (ForTest)
-
-- (UIApplicationState)dummyGetActiveAppState {
- NSLog(@"Testing: app state for secondary transport manager is ACTIVE");
- return UIApplicationStateActive;
-}
-
-+ (void)swapGetActiveAppStateMethod {
- SEL selector = NSSelectorFromString(@"sdl_getAppState");
- Method from = class_getInstanceMethod(self, selector);
- Method to = class_getInstanceMethod(self, @selector(dummyGetActiveAppState));
- method_exchangeImplementations(from, to);
-}
-
-- (UIApplicationState)dummyGetInactiveAppState {
- NSLog(@"Testing: app state for secondary transport manager is INACTIVE");
- return UIApplicationStateBackground;
-}
-
-+ (void)swapGetInactiveAppStateMethod {
- SEL selector = NSSelectorFromString(@"sdl_getAppState");
- Method from = class_getInstanceMethod(self, selector);
- Method to = class_getInstanceMethod(self, @selector(dummyGetInactiveAppState));
- method_exchangeImplementations(from, to);
-}
-
-@end
@interface SDLTCPTransport (ConnectionDisabled)
// Disable connect and disconnect methods
@@ -121,12 +89,13 @@ static const int TCPPortUnspecified = -1;
Method to = class_getInstanceMethod(self, @selector(dummyConnect));
method_exchangeImplementations(from, to);
- from = class_getInstanceMethod(self, @selector(disconnect));
+ from = class_getInstanceMethod(self, @selector(disconnectWithCompletionHandler:));
to = class_getInstanceMethod(self, @selector(dummyDisconnect));
method_exchangeImplementations(from, to);
}
@end
+
@interface SDLIAPTransport (ConnectionDisabled)
// Disable connect and disconnect methods
+ (void)swapConnectionMethods;
@@ -145,7 +114,7 @@ static const int TCPPortUnspecified = -1;
Method to = class_getInstanceMethod(self, @selector(dummyConnect));
method_exchangeImplementations(from, to);
- from = class_getInstanceMethod(self, @selector(disconnect));
+ from = class_getInstanceMethod(self, @selector(disconnectWithCompletionHandler:));
to = class_getInstanceMethod(self, @selector(dummyDisconnect));
method_exchangeImplementations(from, to);
}
@@ -166,12 +135,9 @@ describe(@"the secondary transport manager ", ^{
hmiStatus.hmiLevel = hmiLevel;
SDLRPCNotificationNotification *notification = [[SDLRPCNotificationNotification alloc] initWithName:SDLDidChangeHMIStatusNotification object:self rpcNotification:hmiStatus];
[[NSNotificationCenter defaultCenter] postNotification:notification];
-
- [NSThread sleepForTimeInterval:0.3];
};
beforeEach(^{
- [SDLSecondaryTransportManager swapGetActiveAppStateMethod];
[SDLTCPTransport swapConnectionMethods];
[SDLIAPTransport swapConnectionMethods];
@@ -179,6 +145,8 @@ describe(@"the secondary transport manager ", ^{
testStreamingProtocolDelegate = OCMStrictProtocolMock(@protocol(SDLStreamingProtocolDelegate));
testStateMachineQueue = dispatch_queue_create("com.sdl.testsecondarytransportmanager", DISPATCH_QUEUE_SERIAL);
manager = [[SDLSecondaryTransportManager alloc] initWithStreamingProtocolDelegate:testStreamingProtocolDelegate serialQueue:testStateMachineQueue];
+
+ manager.currentApplicationState = UIApplicationStateActive;
});
afterEach(^{
@@ -186,14 +154,10 @@ describe(@"the secondary transport manager ", ^{
// (Don't put OCMVerifyAll() after calling stop.)
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:OCMOCK_ANY toNewVideoProtocol:nil fromOldAudioProtocol:OCMOCK_ANY toNewAudioProtocol:nil]);
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
- });
manager = nil;
[SDLIAPTransport swapConnectionMethods];
[SDLTCPTransport swapConnectionMethods];
- [SDLSecondaryTransportManager swapGetActiveAppStateMethod];
});
@@ -214,13 +178,24 @@ describe(@"the secondary transport manager ", ^{
testPrimaryProtocol = [[SDLProtocol alloc] init];
});
- it(@"should transition to Started state", ^{
+ it(@"if in the stopped state, it should transition to state started", ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateStopped fromOldState:nil callEnterTransition:YES];
+
dispatch_sync(testStateMachineQueue, ^{
[manager startWithPrimaryProtocol:testPrimaryProtocol];
});
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStarted));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ });
+
+ it(@"if not in the stopped state, it should stay in the same state", ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConfigured fromOldState:nil callEnterTransition:YES];
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
});
});
@@ -273,17 +248,15 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:nil fromOldAudioProtocol:nil toNewAudioProtocol:testPrimaryProtocol]);
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionTCP));
NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary), @(SDLTransportClassPrimary)];
- expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ expect(manager.transportsForAudioService).toEventually(equal(expectedTransportsForAudioService));
NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
- expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
+ expect(manager.transportsForVideoService).toEventually(equal(expectedTransportsForVideoService));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -301,15 +274,13 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:testPrimaryProtocol fromOldAudioProtocol:nil toNewAudioProtocol:testPrimaryProtocol]);
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
- expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
- expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionTCP));
+ expect(manager.transportsForAudioService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -324,21 +295,17 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should transition to Configured state with transport type disabled", ^{
- // Since primary transport is iAP, we cannot use iAP for secondary transport.
- // So both services run on primary transport.
+ // Since primary transport is iAP, we cannot use iAP for secondary transport. This means both services run on primary transport.
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:testPrimaryProtocol fromOldAudioProtocol:nil toNewAudioProtocol:testPrimaryProtocol]);
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- // see the comment above
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
- expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
- expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -353,15 +320,13 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:testPrimaryProtocol fromOldAudioProtocol:nil toNewAudioProtocol:testPrimaryProtocol]);
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
-
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
- expect(manager.transportsForAudioService).to(equal(@[@(SDLTransportClassPrimary)]));
- expect(manager.transportsForVideoService).to(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
+ expect(manager.transportsForVideoService).toEventually(equal(@[@(SDLTransportClassPrimary)]));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
});
@@ -386,18 +351,18 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should configure its properties but stay in Started state", ^{
- [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
+ OCMReject([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:[OCMArg any] fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:[OCMArg any]]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStarted));
+ [testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionDisabled));
- expect(manager.transportsForAudioService).to(equal(@[]));
- expect(manager.transportsForVideoService).to(equal(@[]));
- expect(manager.ipAddress).to(equal(testTcpIpAddress));
- expect(manager.tcpPort).to(equal(testTcpPort));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateStarted));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionDisabled));
+ expect(manager.transportsForAudioService).toEventually(equal(@[]));
+ expect(manager.transportsForVideoService).toEventually(equal(@[]));
+ expect(manager.ipAddress).toEventually(equal(testTcpIpAddress));
+ expect(manager.tcpPort).toEventually(equal(testTcpPort));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -440,30 +405,31 @@ describe(@"the secondary transport manager ", ^{
testStartServiceACKPayload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithHashId:testHashId mtu:testMtu authToken:nil protocolVersion:testProtocolVersion secondaryTransports:testSecondaryTransports audioServiceTransports:testAudioServiceTransports videoServiceTransports:testVideoServiceTransports];
testStartServiceACKMessage = [[SDLV2ProtocolMessage alloc] initWithHeader:testStartServiceACKHeader andPayload:testStartServiceACKPayload.data];
manager.currentHMILevel = SDLHMILevelFull;
+ manager.currentApplicationState = UIApplicationStateActive;
});
it(@"should configure its properties and immediately transition to Connecting state", ^{
+ // audio and video services will not start until secondary transport is established
+ OCMReject([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:[OCMArg any] fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:[OCMArg any]]);
+
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
-
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionTCP));
NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary)];
- expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ expect(manager.transportsForAudioService).toEventually(equal(expectedTransportsForAudioService));
NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
- expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
- expect(manager.ipAddress).to(equal(testTcpIpAddress));
- expect(manager.tcpPort).to(equal(testTcpPort));
+ expect(manager.transportsForVideoService).toEventually(equal(expectedTransportsForVideoService));
+ expect(manager.ipAddress).toEventually(equal(testTcpIpAddress));
+ expect(manager.tcpPort).toEventually(equal(testTcpPort));
SDLTCPTransport *secondaryTransport = (SDLTCPTransport *)manager.secondaryTransport;
- expect(secondaryTransport.hostName).to(equal(testTcpIpAddress));
+ expect(secondaryTransport.hostName).toEventually(equal(testTcpIpAddress));
NSString *portNumberString = [NSString stringWithFormat:@"%d", testTcpPort];
- expect(secondaryTransport.portNumber).to(equal(portNumberString));
+ expect(secondaryTransport.portNumber).toEventually(equal(portNumberString));
- // audio and video services will not start until secondary transport is established
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -480,36 +446,37 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should configure its properties and transition to Configured state", ^{
+ // audio and video services will not start until secondary transport is established
+ OCMReject([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:[OCMArg any] fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:[OCMArg any]]);
+
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
[testPrimaryProtocol handleBytesFromTransport:testStartServiceACKMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- expect((NSInteger)manager.secondaryTransportType).to(equal(SDLTransportSelectionTCP));
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ expect((NSInteger)manager.secondaryTransportType).toEventually(equal(SDLTransportSelectionTCP));
NSArray<SDLTransportClassBox *> *expectedTransportsForAudioService = @[@(SDLTransportClassSecondary)];
- expect(manager.transportsForAudioService).to(equal(expectedTransportsForAudioService));
+ expect(manager.transportsForAudioService).toEventually(equal(expectedTransportsForAudioService));
NSArray<SDLTransportClassBox *> *expectedTransportsForVideoService = @[@(SDLTransportClassSecondary)];
- expect(manager.transportsForVideoService).to(equal(expectedTransportsForVideoService));
- expect(manager.ipAddress).to(equal(testTcpIpAddress));
- expect(manager.tcpPort).to(equal(testTcpPort));
+ expect(manager.transportsForVideoService).toEventually(equal(expectedTransportsForVideoService));
+ expect(manager.ipAddress).toEventually(equal(testTcpIpAddress));
+ expect(manager.tcpPort).toEventually(equal(testTcpPort));
+ expect(manager.secondaryTransport).toEventually(beNil());
- expect(manager.secondaryTransport).to(beNil());
-
- // audio and video services will not start until secondary transport is established
- OCMVerifyAll(testStreamingProtocolDelegate);
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
});
describe(@"when stopped", ^{
- it(@"should transition to Stopped state", ^{
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
+ it(@"should transition to the Stopped state", ^{
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
});
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
@@ -539,8 +506,6 @@ describe(@"the secondary transport manager ", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
expect(manager.currentHMILevel).to(beNil());
expect(manager.secondaryProtocol.securityManager).to(beNil());
-
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
@@ -553,8 +518,6 @@ describe(@"the secondary transport manager ", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
expect(manager.currentHMILevel).to(equal(SDLHMILevelFull));
expect(manager.secondaryProtocol.securityManager).to(equal(testPrimaryProtocol.securityManager));
-
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
@@ -581,8 +544,6 @@ describe(@"the secondary transport manager ", ^{
describe(@"and Transport Event Update is not received", ^{
it(@"should stay in Configured state", ^{
expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
-
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
@@ -606,17 +567,13 @@ describe(@"the secondary transport manager ", ^{
testPrimaryProtocol.securityManager = OCMClassMock([SDLFakeSecurityManager class]);
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
});
context(@"before the app context is HMI FULL", ^{
it(@"should stay in Configured state", ^{
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- expect(manager.currentHMILevel).to(beNil());
- expect(manager.secondaryProtocol.securityManager).to(beNil());
-
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ expect(manager.currentHMILevel).toEventually(beNil());
+ expect(manager.secondaryProtocol.securityManager).toEventually(beNil());
});
});
@@ -626,11 +583,9 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should transition to Connecting", ^{
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- expect(manager.currentHMILevel).to(equal(SDLHMILevelFull));
- expect(manager.secondaryProtocol.securityManager).to(equal(testPrimaryProtocol.securityManager));
-
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
+ expect(manager.currentHMILevel).toEventually(equal(SDLHMILevelFull));
+ expect(manager.secondaryProtocol.securityManager).toEventually(equal(testPrimaryProtocol.securityManager));
});
});
});
@@ -649,12 +604,14 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should transition to Stopped state", ^{
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
});
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
@@ -687,12 +644,8 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testSecondaryProtocolMock registerSecondaryTransport]);
[testSecondaryProtocolMock onProtocolOpened];
- [NSThread sleepForTimeInterval:0.1];
-
- OCMVerifyAll(testSecondaryProtocolMock);
- OCMVerifyAll(testStreamingProtocolDelegate);
- // Note: cannot test the timeout scenario since the timer fires on main thread
+ OCMVerifyAllWithDelay(testSecondaryProtocolMock, 0.5);
});
describe(@"and Register Secondary Transport ACK is received", ^{
@@ -718,10 +671,9 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:secondaryProtocol fromOldAudioProtocol:nil toNewAudioProtocol:secondaryProtocol]);
[testSecondaryProtocolMock handleBytesFromTransport:testRegisterSecondaryTransportAckMessage.data];
- [NSThread sleepForTimeInterval:0.1];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateRegistered));
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -743,21 +695,63 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Reconnecting state", ^{
[testSecondaryProtocolMock handleBytesFromTransport:testRegisterSecondaryTransportNakMessage.data];
- [NSThread sleepForTimeInterval:0.1];
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+ });
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ describe(@"and timeout occurs while waiting for the module to respond to the Register Secondary Transport request", ^{
+ beforeEach(^{
+ // assume audio and video services are allowed only on secondary transport
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(SDLTransportClassSecondary);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(SDLTransportClassSecondary);
+ });
+
+ it(@"if in the Connecting state it should transition to Reconnecting state", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConnecting fromOldState:nil callEnterTransition:NO];
+ });
+
+ [testSecondaryProtocolMock onProtocolOpened];
+
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
+ // Wait for the timer to elapse
+ float waitTime = RegisterTransportTime;
+ NSLog(@"Please wait for register transport timer to elapse... (for %.02f seconds)", waitTime);
+ [NSThread sleepForTimeInterval:waitTime];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+
+ it(@"if not in the Connecting state it should not try to reconnect", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateReconnecting fromOldState:nil callEnterTransition:NO];
+ });
+
+ [testSecondaryProtocolMock onProtocolOpened];
+
+ // Wait for the timer to elapse
+ float waitTime = RegisterTransportTime;
+ NSLog(@"Please wait for register transport timer to elapse... (for %.02f seconds)", waitTime);
+ [NSThread sleepForTimeInterval:waitTime];
+
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
});
});
});
describe(@"when transport is closed", ^{
it(@"should transition to Reconnecting state", ^{
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
[testSecondaryProtocolMock onProtocolClosed];
- [NSThread sleepForTimeInterval:0.1];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -790,10 +784,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should ignore the frame and stay in Connecting state", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
});
});
@@ -809,10 +800,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Configured state, then transition to Connecting state again", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
});
});
@@ -827,10 +815,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Configured state", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
});
});
@@ -838,17 +823,18 @@ describe(@"the secondary transport manager ", ^{
describe(@"when stopped", ^{
it(@"should transition to Stopped state", ^{
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
});
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
-
describe(@"In Registered state", ^{
__block SDLProtocol *secondaryProtocol = nil;
__block id testSecondaryProtocolMock = nil;
@@ -908,10 +894,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should ignore the frame and stay in Registered state", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateRegistered));
});
});
@@ -928,10 +911,8 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:secondaryProtocol toNewVideoProtocol:nil fromOldAudioProtocol:secondaryProtocol toNewAudioProtocol:nil]);
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -948,10 +929,8 @@ describe(@"the secondary transport manager ", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:secondaryProtocol toNewVideoProtocol:nil fromOldAudioProtocol:secondaryProtocol toNewAudioProtocol:nil]);
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
});
@@ -966,13 +945,12 @@ describe(@"the secondary transport manager ", ^{
});
it(@"should transition to Reconnecting state", ^{
- OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:secondaryProtocol toNewVideoProtocol:nil fromOldAudioProtocol:secondaryProtocol toNewAudioProtocol:nil]);
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
[testSecondaryProtocolMock onProtocolClosed];
- [NSThread sleepForTimeInterval:0.1];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
});
});
@@ -988,17 +966,18 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Stopped state", ^{
OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:secondaryProtocol toNewVideoProtocol:nil fromOldAudioProtocol:secondaryProtocol toNewAudioProtocol:nil]);
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
});
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
-
describe(@"In Reconnecting state", ^{
beforeEach(^{
testPrimaryProtocol = [[SDLProtocol alloc] init];
@@ -1024,12 +1003,11 @@ describe(@"the secondary transport manager ", ^{
});
// wait for the timer
- float waitTime = RetryConnectionDelay + 5.0;
- NSLog(@"Please wait for reconnection timeout ... (for %f seconds)", waitTime);
+ float waitTime = RetryConnectionDelay;
+ NSLog(@"Please wait for reconnection timeout... (for %.02f seconds)", waitTime);
[NSThread sleepForTimeInterval:waitTime];
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
});
});
@@ -1062,10 +1040,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should ignore the frame and stay in Reconnecting state", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateReconnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
});
});
@@ -1081,10 +1056,7 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Configured state before timeout, then transition to Connecting state again", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
});
});
@@ -1099,28 +1071,27 @@ describe(@"the secondary transport manager ", ^{
it(@"should transition to Configured state before timeout", ^{
[testPrimaryProtocol handleBytesFromTransport:testTransportEventUpdateMessage.data];
- [NSThread sleepForTimeInterval:0.1];
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
- OCMVerifyAll(testStreamingProtocolDelegate);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
});
});
});
describe(@"when stopped", ^{
it(@"should transition to Stopped state", ^{
- dispatch_sync(testStateMachineQueue, ^{
- [manager stop];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
});
-
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
- OCMVerifyAll(testStreamingProtocolDelegate);
});
});
});
describe(@"app lifecycle state change", ^{
- __block SDLBackgroundTaskManager *mockBackgroundTaskManager = nil;
+ __block id mockBackgroundTaskManager = nil;
beforeEach(^{
// In the tests, we assume primary transport is iAP
@@ -1128,12 +1099,12 @@ describe(@"the secondary transport manager ", ^{
testPrimaryTransport = [[SDLIAPTransport alloc] init];
testPrimaryProtocol.transport = testPrimaryTransport;
+ mockBackgroundTaskManager = OCMClassMock([SDLBackgroundTaskManager class]);
+ manager.backgroundTaskManager = mockBackgroundTaskManager;
+
dispatch_sync(testStateMachineQueue, ^{
[manager startWithPrimaryProtocol:testPrimaryProtocol];
});
-
- mockBackgroundTaskManager = OCMPartialMock([[SDLBackgroundTaskManager alloc] initWithBackgroundTaskName:@"com.test.backgroundTask"]);
- manager.backgroundTaskManager = mockBackgroundTaskManager;
});
context(@"app enters the background", ^{
@@ -1144,32 +1115,32 @@ describe(@"the secondary transport manager ", ^{
describe(@"if the secondary transport is connected", ^{
beforeEach(^{
[manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ });
+
+ it(@"should start a background task and stay connected", ^{
+ OCMExpect([mockBackgroundTaskManager startBackgroundTask]);
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillResignActiveNotification object:nil];
- // Wait for the notification to propagate
- [NSThread sleepForTimeInterval:0.1];
- });
+ OCMVerifyAllWithDelay(mockBackgroundTaskManager, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateRegistered));
- it(@"should start a background task and stay connected", ^{
- OCMVerify([mockBackgroundTaskManager startBackgroundTask]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
});
});
describe(@"if the secondary transport has not yet connected", ^{
beforeEach(^{
[manager.stateMachine setToState:SDLSecondaryTransportStateConfigured fromOldState:nil callEnterTransition:NO];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillResignActiveNotification object:nil];
-
- // Wait for the notification to propagate
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should ignore the state change notification", ^{
OCMReject([mockBackgroundTaskManager startBackgroundTask]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConfigured));
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationWillResignActiveNotification object:nil];
+
+ OCMVerifyAllWithDelay(mockBackgroundTaskManager, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+
});
});
});
@@ -1178,16 +1149,16 @@ describe(@"the secondary transport manager ", ^{
describe(@"if the secondary transport is still connected", ^{
beforeEach(^{
[manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ });
+
+ it(@"should end the background task and stay in the connected state", ^{
+ OCMExpect([mockBackgroundTaskManager endBackgroundTask]);
[[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
- // Wait for the notification to propagate
- [NSThread sleepForTimeInterval:0.1];
- });
+ OCMVerifyAllWithDelay(mockBackgroundTaskManager, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateRegistered));
- it(@"should end the background task and stay in the connected state", ^{
- OCMVerify([mockBackgroundTaskManager endBackgroundTask]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateRegistered));
});
});
@@ -1199,32 +1170,30 @@ describe(@"the secondary transport manager ", ^{
manager.secondaryTransportType = SDLTransportSelectionTCP;
[manager.stateMachine setToState:SDLSecondaryTransportStateConfigured fromOldState:nil callEnterTransition:NO];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
-
- // Wait for the notification to propagate
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should end the background task and try to restart the TCP transport", ^{
- OCMVerify([mockBackgroundTaskManager endBackgroundTask]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+ OCMExpect([mockBackgroundTaskManager endBackgroundTask]);
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
+
+ OCMVerifyAllWithDelay(mockBackgroundTaskManager, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
});
});
describe(@"if the secondary transport not connected and is not configured", ^{
beforeEach(^{
[manager.stateMachine setToState:SDLSecondaryTransportStateConnecting fromOldState:nil callEnterTransition:NO];
-
- [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
-
- // Wait for the notification to propagate
- [NSThread sleepForTimeInterval:0.1];
});
it(@"should ignore the state change notification", ^{
OCMReject([mockBackgroundTaskManager endBackgroundTask]);
- expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateConnecting));
+
+ [[NSNotificationCenter defaultCenter] postNotificationName:UIApplicationDidBecomeActiveNotification object:nil];
+
+ OCMVerifyAllWithDelay(mockBackgroundTaskManager, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConnecting));
});
});
});
@@ -1232,7 +1201,7 @@ describe(@"the secondary transport manager ", ^{
describe(@"When the background task expires", ^{
context(@"If the app is still in the background", ^{
beforeEach(^{
- [SDLSecondaryTransportManager swapGetInactiveAppStateMethod];
+ manager.currentApplicationState = UIApplicationStateBackground;
});
it(@"should stop the TCP transport if the app is still in the background and perform cleanup before ending the background task", ^{
@@ -1254,7 +1223,7 @@ describe(@"the secondary transport manager ", ^{
});
afterEach(^{
- [SDLSecondaryTransportManager swapGetInactiveAppStateMethod];
+ manager.currentApplicationState = UIApplicationStateActive;
});
});
@@ -1270,6 +1239,225 @@ describe(@"the secondary transport manager ", ^{
});
});
});
+
+ describe(@"when the secondary transport is closed", ^{
+ __block SDLProtocol *secondaryProtocol = nil;
+ __block id testSecondaryProtocolMock = nil;
+
+ beforeEach(^{
+ secondaryProtocol = [[SDLProtocol alloc] init];
+ testSecondaryProtocolMock = OCMPartialMock(secondaryProtocol);
+
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ [secondaryProtocol.protocolDelegateTable addObject:manager];
+ manager.secondaryProtocol = secondaryProtocol;
+ });
+
+ it(@"should transition to Reconnecting state if in state connecting", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConnecting fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onProtocolClosed];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+
+ it(@"should transition to Reconnecting state if in state registered", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onProtocolClosed];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+
+ it(@"should stay in the same state if not in the connecting or registered states", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConfigured fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMReject([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onProtocolClosed];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ });
+ });
+
+ describe(@"when the secondary transport errors (wifi turned off or socket breaks)", ^{
+ __block SDLProtocol *secondaryProtocol = nil;
+ __block id testSecondaryProtocolMock = nil;
+
+ beforeEach(^{
+ secondaryProtocol = [[SDLProtocol alloc] init];
+ testSecondaryProtocolMock = OCMPartialMock(secondaryProtocol);
+
+ testPrimaryProtocol = [[SDLProtocol alloc] init];
+ testPrimaryTransport = [[SDLIAPTransport alloc] init];
+ testPrimaryProtocol.transport = testPrimaryTransport;
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+
+ [secondaryProtocol.protocolDelegateTable addObject:manager];
+ manager.secondaryProtocol = secondaryProtocol;
+ });
+
+ it(@"should transition to Reconnecting state if in state connecting", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConnecting fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onError:[OCMArg any]];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+
+ it(@"should transition to Reconnecting state if in state registered", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMExpect([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onError:[OCMArg any]];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateReconnecting));
+ });
+
+ it(@"should stay in the same state if not in the connecting or registered states", ^{
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateConfigured fromOldState:nil callEnterTransition:NO];
+ });
+
+ OCMReject([testStreamingProtocolDelegate transportClosed]);
+
+ [testSecondaryProtocolMock onError:[OCMArg any]];
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ expect(manager.stateMachine.currentState).toEventually(equal(SDLSecondaryTransportStateConfigured));
+ });
+ });
+
+ describe(@"when the secondary transport is disconnected", ^{
+ __block id mockSecondaryTransport = nil;
+ __block id mockBackgroundTaskManager = nil;
+
+ beforeEach(^{
+ mockSecondaryTransport = OCMProtocolMock(@protocol(SDLTransportType));
+ mockBackgroundTaskManager = OCMClassMock([SDLBackgroundTaskManager class]);
+
+ manager.backgroundTaskManager = mockBackgroundTaskManager;
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(2);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(2);
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+ });
+
+ it(@"should return early if the secondary transport has not yet been established", ^{
+ manager.secondaryTransport = nil;
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager disconnectSecondaryTransportWithCompletionHandler:^{
+ done();
+ }];
+ });
+ });
+ });
+
+ it(@"should shutdown the secondary transport", ^{
+ manager.secondaryTransport = mockSecondaryTransport;
+ OCMExpect([mockSecondaryTransport disconnectWithCompletionHandler:[OCMArg invokeBlock]]);
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager disconnectSecondaryTransportWithCompletionHandler:^{\
+ expect(manager.secondaryTransport).to(beNil());
+ expect(manager.secondaryProtocol).to(beNil());
+ expect(manager.streamingServiceTransportMap).to(beEmpty());
+ OCMVerify([manager.backgroundTaskManager endBackgroundTask]);
+ done();
+ }];
+ });
+ });
+
+ OCMVerifyAllWithDelay(mockSecondaryTransport, 0.5);
+ });
+ });
+
+ describe(@"when stopped", ^{
+ __block SDLProtocol *testSecondaryProtocol = nil;
+
+ beforeEach(^{
+ testSecondaryProtocol = OCMClassMock([SDLProtocol class]);
+ manager.secondaryProtocol = testSecondaryProtocol;
+
+ manager.transportsForAudioService = @[@(SDLTransportClassSecondary)];
+ manager.transportsForVideoService = @[@(SDLTransportClassSecondary)];
+ manager.streamingServiceTransportMap[@(SDLServiceTypeAudio)] = @(2);
+ manager.streamingServiceTransportMap[@(SDLServiceTypeVideo)] = @(2);
+
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager startWithPrimaryProtocol:testPrimaryProtocol];
+ });
+ });
+
+ it(@"if not in the stopped state, it should transition to stopped state", ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateRegistered fromOldState:nil callEnterTransition:NO];
+ OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:testSecondaryProtocol toNewVideoProtocol:nil fromOldAudioProtocol:testSecondaryProtocol toNewAudioProtocol:nil]);
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
+ });
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ });
+
+ it(@"if already in the stopped state, it should stay in the stopped state", ^{
+ [manager.stateMachine setToState:SDLSecondaryTransportStateStopped fromOldState:nil callEnterTransition:NO];
+ OCMExpect([testStreamingProtocolDelegate didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:nil fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:nil]);
+
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ dispatch_sync(testStateMachineQueue, ^{
+ [manager stopWithCompletionHandler:^{
+ expect(manager.stateMachine.currentState).to(equal(SDLSecondaryTransportStateStopped));
+ done();
+ }];
+ });
+ });
+
+ OCMVerifyAllWithDelay(testStreamingProtocolDelegate, 0.5);
+ });
+ });
});
QuickSpecEnd
diff --git a/SmartDeviceLinkTests/SDLStreamingMediaManagerSpec.m b/SmartDeviceLinkTests/SDLStreamingMediaManagerSpec.m
index 2eaddbffb..5d360c851 100644
--- a/SmartDeviceLinkTests/SDLStreamingMediaManagerSpec.m
+++ b/SmartDeviceLinkTests/SDLStreamingMediaManagerSpec.m
@@ -38,11 +38,13 @@ describe(@"the streaming media manager", ^{
__block SDLStreamingMediaManager *testStreamingMediaManager = nil;
__block TestConnectionManager *testConnectionManager = nil;
__block SDLConfiguration *testConfiguration = nil;
- __block SDLSecondaryTransportManager *mockSecondaryTransportManager = nil;
- __block SDLStreamingVideoLifecycleManager *mockVideoLifecycleManager = nil;
- __block SDLStreamingAudioLifecycleManager *mockAudioLifecycleManager = nil;
__block SDLSystemCapabilityManager *mockSystemCapabilityManager = nil;
+ // Need to use `id` data type in order to use `OCMVerifyAllWithDelay`
+ __block id mockSecondaryTransportManager = nil;
+ __block id mockVideoLifecycleManager = nil;
+ __block id mockAudioLifecycleManager = nil;
+
beforeEach(^{
mockSystemCapabilityManager = OCMClassMock([SDLSystemCapabilityManager class]);
testConnectionManager = [[TestConnectionManager alloc] init];
@@ -54,7 +56,7 @@ describe(@"the streaming media manager", ^{
mockAudioLifecycleManager = OCMClassMock([SDLStreamingAudioLifecycleManager class]);
testStreamingMediaManager.audioLifecycleManager = mockAudioLifecycleManager;
- mockSecondaryTransportManager = OCMClassMock([SDLSecondaryTransportManager class]);
+ mockSecondaryTransportManager = OCMStrictClassMock([SDLSecondaryTransportManager class]);
testStreamingMediaManager.secondaryTransportManager = mockSecondaryTransportManager;
});
@@ -176,7 +178,8 @@ describe(@"the streaming media manager", ^{
OCMVerify([mockVideoLifecycleManager isVideoStreamingPaused]);
[testStreamingMediaManager screenSize];
- OCMVerify([mockVideoLifecycleManager.videoScaleManager displayViewportResolution]);
+ SDLStreamingVideoLifecycleManager *streamingVideoLifecycleManager = (SDLStreamingVideoLifecycleManager *)mockVideoLifecycleManager;
+ OCMVerify([streamingVideoLifecycleManager.videoScaleManager displayViewportResolution]);
[testStreamingMediaManager videoFormat];
OCMVerify([mockVideoLifecycleManager videoFormat]);
@@ -218,188 +221,134 @@ describe(@"the streaming media manager", ^{
});
describe(@"when using the secondary transport", ^{
- __block SDLProtocol *mockProtocol = nil;
-
- beforeEach(^{
- mockProtocol = OCMClassMock([SDLProtocol class]);
- });
-
describe(@"starting a service on a transport when none is running", ^{
- beforeEach(^{
- [testStreamingMediaManager startSecondaryTransportWithProtocol:mockProtocol];
+ __block SDLProtocol *mockNewProtocol = nil;
- // Make sure the dispatch_group tasks finish before performing checks
- [NSThread sleepForTimeInterval:0.5];
+ beforeEach(^{
+ mockNewProtocol = OCMClassMock([SDLProtocol class]);
});
it(@"should start both the audio and video stream managers with the protocol", ^{
- OCMVerify([mockAudioLifecycleManager startWithProtocol:mockProtocol]);
- OCMVerify([mockVideoLifecycleManager startWithProtocol:mockProtocol]);
-
- expect(testStreamingMediaManager.audioStarted).to(beTrue());
- expect(testStreamingMediaManager.videoStarted).to(beTrue());
-
- OCMReject([mockSecondaryTransportManager disconnectSecondaryTransport]);
-
+ OCMExpect([mockAudioLifecycleManager startWithProtocol:mockNewProtocol]);
+ OCMExpect([mockVideoLifecycleManager startWithProtocol:mockNewProtocol]);
+ OCMReject([mockSecondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:[OCMArg any]]);
OCMReject([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]);
OCMReject([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]);
- });
- });
- describe(@"stopping a running service on a transport", ^{
- beforeEach(^{
- OCMStub([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
-
- OCMStub([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
+ [testStreamingMediaManager startSecondaryTransportWithProtocol:mockNewProtocol];
- [testStreamingMediaManager didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:nil fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:nil];
+ OCMVerifyAllWithDelay(mockAudioLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockVideoLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockSecondaryTransportManager, 0.5);
- // Make sure the dispatch_group tasks finish before performing checks
- [NSThread sleepForTimeInterval:0.5];
+ expect(testStreamingMediaManager.audioStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.videoStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.audioProtocol).toEventually(equal(mockNewProtocol));
+ expect(testStreamingMediaManager.videoProtocol).toEventually(equal(mockNewProtocol));
});
+ });
+ describe(@"stopping a running service on a transport", ^{
it(@"should stop both the audio and video stream managers", ^{
- OCMVerify([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]);
- OCMVerify([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]);
- expect(testStreamingMediaManager.audioStarted).to(beFalse());
- expect(testStreamingMediaManager.videoStarted).to(beFalse());
-
+ OCMExpect([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockSecondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:[OCMArg invokeBlock]]);
OCMReject([mockAudioLifecycleManager startWithProtocol:[OCMArg any]]);
OCMReject([mockVideoLifecycleManager startWithProtocol:[OCMArg any]]);
- OCMVerify([mockSecondaryTransportManager disconnectSecondaryTransport]);
+ [testStreamingMediaManager didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:nil fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:nil];
+
+ OCMVerifyAllWithDelay(mockAudioLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockVideoLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockSecondaryTransportManager, 0.5);
- expect(testStreamingMediaManager.audioProtocol).to(beNil());
- expect(testStreamingMediaManager.videoProtocol).to(beNil());
+ expect(testStreamingMediaManager.audioStarted).toEventually(beFalse());
+ expect(testStreamingMediaManager.videoStarted).toEventually(beFalse());
+ expect(testStreamingMediaManager.audioProtocol).toEventually(beNil());
+ expect(testStreamingMediaManager.videoProtocol).toEventually(beNil());
});
});
describe(@"switching both the video and audio services to a different transport", ^{
- __block SDLProtocol *mockOldVideoProtocol = nil;
__block SDLProtocol *mockNewVideoProtocol = nil;
- __block SDLProtocol *mockOldAudioProtocol = nil;
__block SDLProtocol *mockNewAudioProtocol = nil;
beforeEach(^{
- mockOldVideoProtocol = OCMClassMock([SDLProtocol class]);
mockNewVideoProtocol = OCMClassMock([SDLProtocol class]);
- mockOldAudioProtocol = OCMClassMock([SDLProtocol class]);
mockNewAudioProtocol = OCMClassMock([SDLProtocol class]);
-
- OCMStub([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
-
- OCMStub([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
-
- [testStreamingMediaManager didUpdateFromOldVideoProtocol:mockOldVideoProtocol toNewVideoProtocol:mockNewVideoProtocol fromOldAudioProtocol:mockOldAudioProtocol toNewAudioProtocol:mockNewAudioProtocol];
-
- // Make sure the dispatch_group tasks finish before performing checks
- [NSThread sleepForTimeInterval:0.5];
});
it(@"should stop both the audio and video stream managers and call the delegate then start a new session", ^{
- OCMVerify([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]);
- OCMVerify([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]);
-
- OCMVerify([mockSecondaryTransportManager disconnectSecondaryTransport]);
-
- OCMVerify([mockAudioLifecycleManager startWithProtocol:mockNewAudioProtocol]);
- OCMVerify([mockVideoLifecycleManager startWithProtocol:mockNewVideoProtocol]);
-
- expect(testStreamingMediaManager.audioStarted).to(beTrue());
- expect(testStreamingMediaManager.videoStarted).to(beTrue());
-
- expect(testStreamingMediaManager.audioProtocol).to(equal(mockNewAudioProtocol));
- expect(testStreamingMediaManager.videoProtocol).to(equal(mockNewVideoProtocol));
+ OCMExpect([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockSecondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockAudioLifecycleManager startWithProtocol:mockNewAudioProtocol]);
+ OCMExpect([mockVideoLifecycleManager startWithProtocol:mockNewVideoProtocol]);
+
+ [testStreamingMediaManager didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:mockNewVideoProtocol fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:mockNewAudioProtocol];
+
+ OCMVerifyAllWithDelay(mockAudioLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockVideoLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockSecondaryTransportManager, 0.5);
+
+ expect(testStreamingMediaManager.audioStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.videoStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.audioProtocol).toEventually(equal(mockNewAudioProtocol));
+ expect(testStreamingMediaManager.videoProtocol).toEventually(equal(mockNewVideoProtocol));
});
});
describe(@"switching only the video service to a different transport", ^{
- __block SDLProtocol *mockOldProtocol = nil;
__block SDLProtocol *mockNewProtocol = nil;
beforeEach(^{
- mockOldProtocol = OCMClassMock([SDLProtocol class]);
mockNewProtocol = OCMClassMock([SDLProtocol class]);
-
- OCMStub([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
-
- [testStreamingMediaManager didUpdateFromOldVideoProtocol:mockOldProtocol toNewVideoProtocol:mockNewProtocol fromOldAudioProtocol:nil toNewAudioProtocol:nil];
-
- // Make sure the dispatch_group tasks finish before performing checks
- [NSThread sleepForTimeInterval:0.5];
});
it(@"should stop the video stream manager but not the audio stream manager", ^{
- OCMVerify([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]);
+ OCMExpect([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg invokeBlock]]);
OCMReject([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]);
+ OCMExpect([mockSecondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockVideoLifecycleManager startWithProtocol:mockNewProtocol]);
+ OCMReject([mockAudioLifecycleManager startWithProtocol:[OCMArg any]]);
- OCMVerify([mockSecondaryTransportManager disconnectSecondaryTransport]);
-
- OCMVerify([mockVideoLifecycleManager startWithProtocol:mockNewProtocol]);
- expect(testStreamingMediaManager.videoStarted).to(beTrue());
+ [testStreamingMediaManager didUpdateFromOldVideoProtocol:[OCMArg any] toNewVideoProtocol:mockNewProtocol fromOldAudioProtocol:nil toNewAudioProtocol:nil];
- OCMReject([mockAudioLifecycleManager startWithProtocol:mockNewProtocol]);
- expect(testStreamingMediaManager.audioStarted).to(beFalse());
+ OCMVerifyAllWithDelay(mockAudioLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockVideoLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockSecondaryTransportManager, 0.5);
- expect(testStreamingMediaManager.audioProtocol).to(beNil());
- expect(testStreamingMediaManager.videoProtocol).to(equal(mockNewProtocol));
+ expect(testStreamingMediaManager.videoStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.audioStarted).toEventually(beFalse());
+ expect(testStreamingMediaManager.audioProtocol).toEventually(beNil());
+ expect(testStreamingMediaManager.videoProtocol).toEventually(equal(mockNewProtocol));
});
});
describe(@"switching only the audio service to a different transport", ^{
- __block SDLProtocol *mockOldProtocol = nil;
__block SDLProtocol *mockNewProtocol = nil;
beforeEach(^{
- mockOldProtocol = OCMClassMock([SDLProtocol class]);
mockNewProtocol = OCMClassMock([SDLProtocol class]);
-
- OCMStub([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]).andDo(^(NSInvocation *invocation) {
- void (^handler)(void);
- [invocation getArgument:&handler atIndex:2];
- handler();
- });
-
- [testStreamingMediaManager didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:nil fromOldAudioProtocol:mockOldProtocol toNewAudioProtocol:mockNewProtocol];
-
- // Make sure the dispatch_group tasks finish before performing checks
- [NSThread sleepForTimeInterval:0.5];
});
it(@"should stop the audio stream manager but not the video stream manager", ^{
- OCMVerify([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg any]]);
+ OCMExpect([mockAudioLifecycleManager endAudioServiceWithCompletionHandler:[OCMArg invokeBlock]]);
OCMReject([mockVideoLifecycleManager endVideoServiceWithCompletionHandler:[OCMArg any]]);
+ OCMExpect([mockSecondaryTransportManager disconnectSecondaryTransportWithCompletionHandler:[OCMArg invokeBlock]]);
+ OCMExpect([mockAudioLifecycleManager startWithProtocol:mockNewProtocol]);
+ OCMReject([mockVideoLifecycleManager startWithProtocol:[OCMArg any]]);
- OCMVerify([mockSecondaryTransportManager disconnectSecondaryTransport]);
-
- OCMVerify([mockAudioLifecycleManager startWithProtocol:mockNewProtocol]);
- expect(testStreamingMediaManager.audioStarted).to(beTrue());
+ [testStreamingMediaManager didUpdateFromOldVideoProtocol:nil toNewVideoProtocol:nil fromOldAudioProtocol:[OCMArg any] toNewAudioProtocol:mockNewProtocol];
- OCMReject([mockVideoLifecycleManager startWithProtocol:mockNewProtocol]);
- expect(testStreamingMediaManager.videoStarted).to(beFalse());
+ OCMVerifyAllWithDelay(mockAudioLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockVideoLifecycleManager, 0.5);
+ OCMVerifyAllWithDelay(mockSecondaryTransportManager, 0.5);
- expect(testStreamingMediaManager.audioProtocol).to(equal(mockNewProtocol));
- expect(testStreamingMediaManager.videoProtocol).to(beNil());
+ expect(testStreamingMediaManager.videoStarted).toEventually(beFalse());
+ expect(testStreamingMediaManager.audioStarted).toEventually(beTrue());
+ expect(testStreamingMediaManager.audioProtocol).toEventually(equal(mockNewProtocol));
+ expect(testStreamingMediaManager.videoProtocol).toEventually(beNil());
});
});
});
diff --git a/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m
index e9a25f834..a23fec3e5 100644
--- a/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m
+++ b/SmartDeviceLinkTests/TransportSpecs/TCP/SDLTCPTransportSpec.m
@@ -48,7 +48,6 @@ describe(@"SDLTCPTransport", ^ {
transport.delegate = nil;
server.delegate = nil;
- [transport disconnect];
transport = nil;
transportDelegateMock = nil;
@@ -73,11 +72,15 @@ describe(@"SDLTCPTransport", ^ {
expect(transport.inputStream != nil);
expect(transport.outputStream != nil);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
- expect(transport.ioThread == nil);
- expect(transport.inputStream == nil);
- expect(transport.outputStream == nil);
+ done();
+ }];
+ });
});
it(@"Should invoke onError delegate when connection is refused", ^ {
@@ -101,11 +104,15 @@ describe(@"SDLTCPTransport", ^ {
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
- expect(transport.ioThread == nil);
- expect(transport.inputStream == nil);
- expect(transport.outputStream == nil);
+ done();
+ }];
+ });
});
it(@"Should invoke onError delegate when connection is timed out", ^ {
@@ -121,13 +128,17 @@ describe(@"SDLTCPTransport", ^ {
[transport connect];
// timeout value should be longer than 'ConnectionTimeoutSecs' in SDLTCPTransport
- OCMVerifyAllWithDelay(transportDelegateMock, 60.0);
+ OCMVerifyAllWithDelay(transportDelegateMock, 30.5);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
- expect(transport.ioThread == nil);
- expect(transport.inputStream == nil);
- expect(transport.outputStream == nil);
+ done();
+ }];
+ });
});
it(@"Should invoke onError delegate when input parameter is invalid", ^ {
@@ -144,11 +155,15 @@ describe(@"SDLTCPTransport", ^ {
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
- expect(transport.ioThread == nil);
- expect(transport.inputStream == nil);
- expect(transport.outputStream == nil);
+ done();
+ }];
+ });
});
it(@"Should send out data when send is called", ^ {
@@ -175,11 +190,17 @@ describe(@"SDLTCPTransport", ^ {
OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
-
- [NSThread sleepForTimeInterval:0.5];
expect([receivedData isEqualToData:testData]);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
+
+ done();
+ }];
+ });
});
it(@"Should send out data even if send is called some time after", ^ {
@@ -209,20 +230,25 @@ describe(@"SDLTCPTransport", ^ {
[transport connect];
// check that transport still sends out data long after NSStreamEventHasSpaceAvailable event
- [NSThread sleepForTimeInterval:1.0];
[transport sendData:testData1];
[transport sendData:testData2];
OCMVerifyAllWithDelay(serverDelegateMock, 0.5);
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
-
- [NSThread sleepForTimeInterval:0.5];
expect([receivedData isEqualToData:expectedData]);
// don't receive further delegate events
server.delegate = nil;
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
+
+ done();
+ }];
+ });
});
it(@"Should invoke onDataReceived delegate when received some data", ^ {
@@ -257,11 +283,17 @@ describe(@"SDLTCPTransport", ^ {
[server send:testData2];
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
-
- [NSThread sleepForTimeInterval:0.5];
expect([receivedData isEqualToData:expectedData]);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
+
+ done();
+ }];
+ });
});
it(@"Should generate disconnected event after peer closed connection", ^ {
@@ -284,7 +316,15 @@ describe(@"SDLTCPTransport", ^ {
OCMVerifyAllWithDelay(transportDelegateMock, 0.5);
- [transport disconnect];
+ waitUntilTimeout(1, ^(void (^done)(void)){
+ [transport disconnectWithCompletionHandler:^{
+ expect(transport.ioThread.isCancelled).to(beTrue());
+ expect(transport.inputStream).to(beNil());
+ expect(transport.outputStream).to(beNil());
+
+ done();
+ }];
+ });
});
});
diff --git a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPDataSessionSpec.m b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPDataSessionSpec.m
index 1d980b139..5c6112373 100644
--- a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPDataSessionSpec.m
+++ b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPDataSessionSpec.m
@@ -23,7 +23,6 @@
@property (weak, nonatomic) id<SDLIAPDataSessionDelegate> delegate;
@property (nonatomic, strong) SDLMutableDataQueue *sendDataQueue;
-@property (nonatomic, strong) dispatch_semaphore_t canceledSemaphore;
@end
@@ -47,7 +46,6 @@ describe(@"SDLIAPDataSession", ^{
it(@"Should init correctly", ^{
expect(dataSession.delegate).to(equal(mockDelegate));
expect(dataSession.sendDataQueue).toNot(beNil());
- expect(dataSession.canceledSemaphore).toNot(beNil());
});
it(@"Should get correctly", ^{
diff --git a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m
index 24d4943c1..808ba33bf 100644
--- a/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m
+++ b/SmartDeviceLinkTests/TransportSpecs/iAP/SDLIAPTransportSpec.m
@@ -42,7 +42,7 @@ describe(@"SDLIAPTransport", ^{
__block EAAccessory *mockAccessory = nil;
beforeEach(^{
- transport = [SDLIAPTransport new];
+ transport = [[SDLIAPTransport alloc] init];
mockTransportDelegate = OCMProtocolMock(@protocol(SDLTransportDelegate));
transport.delegate = mockTransportDelegate;
mockAccessory = [EAAccessory.class sdlCoreMock];
@@ -142,26 +142,22 @@ describe(@"SDLIAPTransport", ^{
__block SDLIAPDataSession *mockDataSession = nil;
beforeEach(^{
- mockDataSession = OCMClassMock([SDLIAPDataSession class]);
+ mockDataSession = OCMStrictClassMock([SDLIAPDataSession class]);
OCMStub([mockDataSession isSessionInProgress]).andReturn(YES);
OCMStub([mockDataSession connectionID]).andReturn(mockAccessory.connectionID);
transport.dataSession = mockDataSession;
transport.controlSession = nil;
-
- [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification];
});
- it(@"It should cleanup on disconnect", ^{
- expect(transport.retryCounter).to(equal(0));
- expect(transport.sessionSetupInProgress).to(beFalse());
- expect(transport.transportDestroyed).to(beTrue());
- });
+ it(@"It should cleanup on disconnect, close and destroy data session, and notify the lifecycle manager that the transport disconnected", ^{
+ OCMExpect([mockDataSession destroySessionWithCompletionHandler:[OCMArg invokeBlock]]);
- it(@"It should close and destroy data session", ^{
- OCMVerify([mockDataSession destroySession]);
- });
+ [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification];
+
+ expect(transport.retryCounter).toEventually(equal(0));
+ expect(transport.sessionSetupInProgress).toEventually(beFalse());
+ expect(transport.transportDestroyed).toEventually(beTrue());
- it(@"It should notify the lifecycle manager that the transport disconnected ", ^{
OCMVerify([mockTransportDelegate onTransportDisconnected]);
});
});
@@ -170,26 +166,22 @@ describe(@"SDLIAPTransport", ^{
__block SDLIAPControlSession *mockControlSession = nil;
beforeEach(^{
- mockControlSession = OCMClassMock([SDLIAPControlSession class]);
+ mockControlSession = OCMStrictClassMock([SDLIAPControlSession class]);
OCMStub([mockControlSession isSessionInProgress]).andReturn(YES);
OCMStub([mockControlSession connectionID]).andReturn(mockAccessory.connectionID);
transport.controlSession = mockControlSession;
transport.dataSession = nil;
-
- [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification];
});
- it(@"It should cleanup on disconnect", ^{
- expect(transport.retryCounter).to(equal(0));
- expect(transport.sessionSetupInProgress).to(beFalse());
- expect(transport.transportDestroyed).to(beFalse());
- });
+ it(@"It should cleanup on disconnect, close and destroy data session, and should not tell the delegate that the transport closed", ^{
+ OCMExpect([mockControlSession destroySessionWithCompletionHandler:[OCMArg invokeBlock]]);
- it(@"It should close and destroy data session", ^{
- OCMVerify([mockControlSession destroySession]);
- });
+ [[NSNotificationCenter defaultCenter] postNotification:accessoryDisconnectedNotification];
+
+ expect(transport.retryCounter).toEventually(equal(0));
+ expect(transport.sessionSetupInProgress).toEventually(beFalse());
+ expect(transport.transportDestroyed).toEventually(beFalse());
- it(@"Should not tell the delegate that the transport closed", ^{
OCMReject([mockTransportDelegate onTransportDisconnected]);
});
});