summaryrefslogtreecommitdiff
path: root/SmartDeviceLink/SDLSecondaryTransportManager.m
diff options
context:
space:
mode:
Diffstat (limited to 'SmartDeviceLink/SDLSecondaryTransportManager.m')
-rw-r--r--SmartDeviceLink/SDLSecondaryTransportManager.m409
1 files changed, 252 insertions, 157 deletions
diff --git a/SmartDeviceLink/SDLSecondaryTransportManager.m b/SmartDeviceLink/SDLSecondaryTransportManager.m
index 9cd1af232..f6054494e 100644
--- a/SmartDeviceLink/SDLSecondaryTransportManager.m
+++ b/SmartDeviceLink/SDLSecondaryTransportManager.m
@@ -11,6 +11,7 @@
#import "SDLSecondaryTransportManager.h"
+#import "SDLBackgroundTaskManager.h"
#import "SDLControlFramePayloadConstants.h"
#import "SDLControlFramePayloadRPCStartServiceAck.h"
#import "SDLControlFramePayloadTransportEventUpdate.h"
@@ -19,9 +20,9 @@
#import "SDLOnHMIStatus.h"
#import "SDLProtocol.h"
#import "SDLProtocolHeader.h"
+#import "SDLProtocolMessage.h"
#import "SDLNotificationConstants.h"
#import "SDLRPCNotificationNotification.h"
-#import "SDLSecondaryTransportPrimaryProtocolHandler.h"
#import "SDLStateMachine.h"
#import "SDLTCPTransport.h"
#import "SDLTimer.h"
@@ -52,6 +53,9 @@ SDLSecondaryTransportState *const SDLSecondaryTransportStateConnecting = @"Conne
SDLSecondaryTransportState *const SDLSecondaryTransportStateRegistered = @"Registered";
SDLSecondaryTransportState *const SDLSecondaryTransportStateReconnecting = @"Reconnecting";
+/// Name for the background task started when the device app is backgrounded.
+NSString *const BackgroundTaskSecondaryTransportName = @"com.sdl.transport.secondaryTransportBackgroundTask";
+
// Timeout for receiving Register Secondary Transport ACK frame
static const float RegisterTransportTime = 10.0;
@@ -61,18 +65,23 @@ static const float RetryConnectionDelay = 5.0;
// Indicates that a TCP port is not specified (unconfigured).
static const int TCPPortUnspecified = -1;
+/// The old and new protocols being used when the transport switches. If the old protocol is `nil`, the transport is not yet started; newProtocol is `nil` the transport will be stopped.
+struct TransportProtocolUpdated {
+ SDLProtocol * _Nullable oldProtocol;
+ SDLProtocol * _Nullable newProtocol;
+} transportUpdated;
@interface SDLSecondaryTransportManager ()
-// State of this manager.
-@property (strong, nonatomic, readwrite) SDLStateMachine *stateMachine;
+/// State of this manager.
+@property (strong, nonatomic) SDLStateMachine *stateMachine;
// Dedicated queue that the state machine will run on.
@property (copy, nonatomic) dispatch_queue_t stateMachineQueue;
// Instance of the protocol that runs on primary transport.
@property (strong, nonatomic) SDLProtocol *primaryProtocol;
-// A class to catch Start Service ACK and Transport Config Update frames.
-@property (strong, nonatomic) SDLSecondaryTransportPrimaryProtocolHandler *primaryProtocolHandler;
+/// The header of the Start Service ACK frame received on primary transport
+@property (copy, nonatomic) SDLProtocolHeader *primaryRPCHeader;
// Selected type of secondary transport. If 'SDLSecondaryTransportTypeDisabled' then secondary transport is disabled.
@property (assign, nonatomic) SDLSecondaryTransportType secondaryTransportType;
@@ -98,8 +107,18 @@ static const int TCPPortUnspecified = -1;
// TCP port number of SDL Core. If the information isn't available then TCPPortUnspecified is stored.
@property (assign, nonatomic) int tcpPort;
+/// 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
@@ -124,7 +143,18 @@ static const int TCPPortUnspecified = -1;
@(SDLServiceTypeVideo):@(SDLTransportClassInvalid)} mutableCopy];
_tcpPort = TCPPortUnspecified;
+ _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;
}
@@ -143,13 +173,16 @@ static const int TCPPortUnspecified = -1;
}
self.primaryProtocol = primaryProtocol;
- self.primaryProtocolHandler = [[SDLSecondaryTransportPrimaryProtocolHandler alloc] initWithSecondaryTransportManager:self primaryProtocol:primaryProtocol];
+
+ @synchronized(self.primaryProtocol.protocolDelegateTable) {
+ [self.primaryProtocol.protocolDelegateTable addObject:self];
+ }
[self.stateMachine transitionToState:SDLSecondaryTransportStateStarted];
}
-- (void)stop {
- SDLLogD(@"SDLSecondaryTransportManager stop");
+- (void)stopWithCompletionHandler:(void (^)(void))completionHandler {
+ SDLLogD(@"Stopping manager");
// this method must be called in SDLLifecycleManager's state machine queue
if (strcmp(dispatch_queue_get_label(DISPATCH_CURRENT_QUEUE_LABEL), dispatch_queue_get_label(self.stateMachineQueue)) != 0) {
@@ -160,36 +193,20 @@ static const int TCPPortUnspecified = -1;
SDLLogD(@"Stopping audio / video services on both transports");
[self sdl_handleTransportUpdateWithPrimaryAvailable:NO secondaryAvailable:NO];
- [self.stateMachine transitionToState:SDLSecondaryTransportStateStopped];
-}
-
-#pragma mark - Manager Lifecycle
+ if ([self.stateMachine.currentState isEqualToEnum:SDLSecondaryTransportStateStopped]) {
+ return 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];
+ // 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;
- [self.primaryProtocolHandler start];
+ [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.primaryProtocolHandler stop];
-
- 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
@@ -250,12 +267,25 @@ static const int TCPPortUnspecified = -1;
- (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 {
@@ -266,6 +296,8 @@ static const int TCPPortUnspecified = -1;
|| 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);
}
}
@@ -280,81 +312,40 @@ static const int TCPPortUnspecified = -1;
self.registerTransportTimer = nil;
}
-- (void)willTransitionFromStateConnectingToStateConfigured {
- [self sdl_disconnectSecondaryTransport];
-}
-
-- (void)willTransitionFromStateConnectingToStateReconnecting {
- [self sdl_disconnectSecondaryTransport];
-}
-
-- (void)willTransitionFromStateConnectingToStateStopped {
- [self sdl_disconnectSecondaryTransport];
-}
-
- (void)didEnterStateRegistered {
SDLLogD(@"Secondary transport is registered");
[self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:YES];
}
- (void)willTransitionFromStateRegisteredToStateConfigured {
- // before disconnecting Secondary Transport, stop running services
- SDLLogD(@"Stopping services on secondary transport");
+ SDLLogD(@"Manger is closing transport but is configured to resume the secondary transport. Stopping services on secondary transport");
[self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
-
- [self sdl_disconnectSecondaryTransport];
-}
-
-- (void)willTransitionFromStateRegisteredToStateReconnecting {
- SDLLogD(@"Stopping services on secondary transport");
- [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
-
- [self sdl_disconnectSecondaryTransport];
-}
-
-- (void)willTransitionFromStateRegisteredToStateStopped {
- // sdl_handleTransportUpdateWithPrimaryAvailable is called in stop method
- [self sdl_disconnectSecondaryTransport];
}
- (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];
}
});
}
+
#pragma mark - Starting / Stopping / Restarting services
- (void)sdl_handleTransportUpdateWithPrimaryAvailable:(BOOL)primaryAvailable secondaryAvailable:(BOOL)secondaryAvailable {
- // update audio service
- [self sdl_updateService:SDLServiceTypeAudio
- allowedTransports:self.transportsForAudioService
- primaryAvailable:primaryAvailable
- secondaryAvailable:secondaryAvailable
- transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
- [self.streamingProtocolDelegate audioServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
- }];
+ struct TransportProtocolUpdated audioTransportUpdated = [self sdl_updateService:SDLServiceTypeAudio allowedTransports:self.transportsForAudioService primaryAvailable:primaryAvailable secondaryAvailable:secondaryAvailable];
+ struct TransportProtocolUpdated videoTransportUpdated = [self sdl_updateService:SDLServiceTypeVideo allowedTransports:self.transportsForVideoService primaryAvailable:primaryAvailable secondaryAvailable:secondaryAvailable];
- // update video service
- [self sdl_updateService:SDLServiceTypeVideo
- allowedTransports:self.transportsForVideoService
- primaryAvailable:primaryAvailable
- secondaryAvailable:secondaryAvailable
- transportUpdatedBlock:^(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol) {
- [self.streamingProtocolDelegate videoServiceProtocolDidUpdateFromOldProtocol:oldProtocol toNewProtocol:newProtocol];
- }];
+ if (audioTransportUpdated.newProtocol == audioTransportUpdated.oldProtocol && videoTransportUpdated.newProtocol == videoTransportUpdated.oldProtocol) { return; }
+
+ [self.streamingProtocolDelegate didUpdateFromOldVideoProtocol:videoTransportUpdated.oldProtocol toNewVideoProtocol:videoTransportUpdated.newProtocol fromOldAudioProtocol:audioTransportUpdated.oldProtocol toNewAudioProtocol:audioTransportUpdated.newProtocol];
}
-- (void)sdl_updateService:(UInt8)service
- allowedTransports:(nonnull NSArray<SDLTransportClassBox *> *)transportList
- primaryAvailable:(BOOL)primaryTransportAvailable
- secondaryAvailable:(BOOL)secondaryTransportAvailable
- transportUpdatedBlock:(void (^)(SDLProtocol * _Nullable oldProtocol, SDLProtocol * _Nullable newProtocol))transportUpdatedBlock {
+- (struct TransportProtocolUpdated)sdl_updateService:(SDLServiceType)service allowedTransports:(nonnull NSArray<SDLTransportClassBox *> *)transportList primaryAvailable:(BOOL)primaryTransportAvailable secondaryAvailable:(BOOL)secondaryTransportAvailable {
SDLTransportClass newTransport = SDLTransportClassInvalid;
// the list is in preferred order, so take a look from the beginning
for (SDLTransportClassBox *transport in transportList) {
@@ -380,10 +371,14 @@ static const int TCPPortUnspecified = -1;
} else if (newTransport != SDLTransportClassInvalid) {
SDLLogD(@"Starting service 0x%X on %@ transport", service, [self sdl_getTransportClassName:newTransport]);
}
-
- transportUpdatedBlock([self sdl_getProtocolFromTransportClass:oldTransport],
- [self sdl_getProtocolFromTransportClass:newTransport]);
+ } else {
+ SDLLogV(@"Transport was not updated");
}
+
+ struct TransportProtocolUpdated transportUpdated;
+ transportUpdated.oldProtocol = [self sdl_getProtocolFromTransportClass:oldTransport];
+ transportUpdated.newProtocol = [self sdl_getProtocolFromTransportClass:newTransport];
+ return transportUpdated;
}
- (nullable SDLProtocol *)sdl_getProtocolFromTransportClass:(SDLTransportClass)transportClass {
@@ -405,6 +400,32 @@ static const int TCPPortUnspecified = -1;
#pragma mark - Transport management
+#pragma mark Primary transport
+
+- (void)protocol:(SDLProtocol *)protocol didReceiveStartServiceACK:(SDLProtocolMessage *)startServiceACK {
+ if (startServiceACK.header.serviceType != SDLServiceTypeRPC || self.primaryProtocol != protocol) { return; }
+
+ SDLLogV(@"Received Start Service ACK header of RPC service on primary (%@) transport", protocol.transport);
+
+ // Keep header to acquire Session ID
+ self.primaryRPCHeader = startServiceACK.header;
+
+ SDLControlFramePayloadRPCStartServiceAck *payload = [[SDLControlFramePayloadRPCStartServiceAck alloc] initWithData:startServiceACK.payload];
+
+ [self sdl_onStartServiceAckReceived:payload];
+}
+
+- (void)protocol:(SDLProtocol *)protocol didReceiveTransportEventUpdate:(SDLProtocolMessage *)transportEventUpdate {
+ if (self.primaryProtocol != protocol) { return; }
+
+ SDLControlFramePayloadTransportEventUpdate *payload = [[SDLControlFramePayloadTransportEventUpdate alloc] initWithData:transportEventUpdate.payload];
+ SDLLogV(@"Recieved transport event update on primary (%@) transport: %@", protocol.transport, payload);
+
+ [self sdl_onTransportEventUpdateReceived:payload];
+}
+
+#pragma mark Secondary transport
+
// try establishing secondary transport. Returns NO if failed
- (BOOL)sdl_connectSecondaryTransport {
if (self.secondaryTransport != nil) {
@@ -423,17 +444,24 @@ static const int TCPPortUnspecified = -1;
}
}
-- (BOOL)sdl_disconnectSecondaryTransport {
+- (void)disconnectSecondaryTransportWithCompletionHandler:(void (^)(void))completionHandler {
if (self.secondaryTransport == nil) {
- SDLLogW(@"Attempted to disconnect secondary transport, but it's already stopped.");
- return NO;
+ SDLLogW(@"Attempted to disconnect secondary transport but it's already stopped.");
+ return completionHandler();
}
- SDLLogD(@"Disconnect secondary transport");
- [self.secondaryTransport disconnect];
- self.secondaryTransport = nil;
+ __weak typeof(self) weakSelf = self;
+ [self.secondaryTransport disconnectWithCompletionHandler:^{
+ SDLLogD(@"Disconnecting secondary transport manager");
- return YES;
+ __strong typeof(weakSelf) strongSelf = weakSelf;
+ strongSelf.secondaryTransport = nil;
+ strongSelf.secondaryProtocol = nil;
+ [strongSelf.streamingServiceTransportMap removeAllObjects];
+
+ [strongSelf.backgroundTaskManager endBackgroundTask];
+ return completionHandler();
+ }];
}
- (BOOL)sdl_startTCPSecondaryTransport {
@@ -447,20 +475,21 @@ static const int TCPPortUnspecified = -1;
SDLTCPTransport *transport = [[SDLTCPTransport alloc] init];
transport.hostName = self.ipAddress;
transport.portNumber = [NSString stringWithFormat:@"%d", self.tcpPort];
- SDLProtocol *protocol = [[SDLProtocol alloc] init];
- transport.delegate = protocol;
- protocol.transport = transport;
- [protocol.protocolDelegateTable addObject:self];
- protocol.securityManager = self.primaryProtocol.securityManager;
+ SDLProtocol *protocol = [[SDLProtocol alloc] initWithTransport:transport encryptionManager:self.primaryProtocol.encryptionLifecycleManager];
+ protocol.securityManager = self.primaryProtocol.securityManager;
self.secondaryProtocol = protocol;
self.secondaryTransport = transport;
+ @synchronized(self.secondaryProtocol.protocolDelegateTable) {
+ [self.secondaryProtocol.protocolDelegateTable addObject:self];
+ }
+
// we reuse Session ID acquired from primary transport's protocol
// this is for Register Secondary Transport frame
- [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeControl];
+ [self.secondaryProtocol storeHeader:self.primaryRPCHeader forServiceType:SDLServiceTypeControl];
// this is for video and audio services
- [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
+ [self.secondaryProtocol storeHeader:self.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
[self.secondaryTransport connect];
return YES;
@@ -470,20 +499,20 @@ static const int TCPPortUnspecified = -1;
SDLLogD(@"Starting Secondary Transport: iAP");
SDLIAPTransport *transport = [[SDLIAPTransport alloc] init];
- SDLProtocol *protocol = [[SDLProtocol alloc] init];
- transport.delegate = protocol;
- protocol.transport = transport;
- [protocol.protocolDelegateTable addObject:self];
+ SDLProtocol *protocol = [[SDLProtocol alloc] initWithTransport:transport encryptionManager:self.primaryProtocol.encryptionLifecycleManager];
protocol.securityManager = self.primaryProtocol.securityManager;
-
self.secondaryProtocol = protocol;
self.secondaryTransport = transport;
+ @synchronized(self.secondaryProtocol.protocolDelegateTable) {
+ [self.secondaryProtocol.protocolDelegateTable addObject:self];
+ }
+
// we reuse Session ID acquired from primary transport's protocol
// this is for Register Secondary Transport frame
- [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeControl];
+ [self.secondaryProtocol storeHeader:self.primaryRPCHeader forServiceType:SDLServiceTypeControl];
// this is for video and audio services
- [self.secondaryProtocol storeHeader:self.primaryProtocolHandler.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
+ [self.secondaryProtocol storeHeader:self.primaryRPCHeader forServiceType:SDLServiceTypeRPC];
[self.secondaryTransport connect];
return YES;
@@ -503,7 +532,7 @@ static const int TCPPortUnspecified = -1;
return NO;
}
- if (self.sdl_getAppState != UIApplicationStateActive) {
+ if (self.currentApplicationState != UIApplicationStateActive) {
SDLLogD(@"App state is not Active, TCP transport is not ready");
return NO;
}
@@ -511,23 +540,12 @@ static const int TCPPortUnspecified = -1;
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
+#pragma mark - SDLProtocolDelegate Implementation
// called on transport's thread, notifying that the transport is established
-- (void)onProtocolOpened {
+- (void)protocolDidOpen:(SDLProtocol *)protocol {
+ if (self.secondaryProtocol != protocol) { return; }
+
SDLLogD(@"Secondary transport connected");
self.registerTransportTimer = [[SDLTimer alloc] initWithDuration:RegisterTransportTime repeat:NO];
@@ -540,10 +558,12 @@ static const int TCPPortUnspecified = -1;
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(@"Could not register the secondary transport with the module. The services will not be started on the secondary transport.");
}
});
};
@@ -552,22 +572,44 @@ static const int TCPPortUnspecified = -1;
[self.secondaryProtocol registerSecondaryTransport];
}
-// 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");
+/// Called on the transport's thread, notifying that the transport has errored before a connection was established.
+/// @param error The error
+/// @param protocol The protocol
+- (void)protocol:(SDLProtocol *)protocol transportDidError:(NSError *)error {
+ if (self.secondaryProtocol != protocol) { return; }
+ SDLLogE(@"The secondary transport errored.");
+ [self sdl_transportClosed];
+}
+
+/// Called on transport's thread, notifying that the transport is disconnected.
+/// @discussion When the transport's disconnect method is called, this method will not be called.
+/// @param protocol The protocol
+- (void)protocolDidClose:(SDLProtocol *)protocol {
+ if (self.secondaryProtocol != protocol) { return; }
+
+ 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");
}
});
}
// called from SDLProtocol's _receiveQueue of secondary transport
-- (void)handleProtocolRegisterSecondaryTransportACKMessage:(SDLProtocolMessage *)registerSecondaryTransportACK {
- SDLLogD(@"Received Register Secondary Transport ACK frame");
+- (void)protocol:(SDLProtocol *)protocol didReceiveRegisterSecondaryTransportACK:(SDLProtocolMessage *)registerSecondaryTransportACK {
+ if (self.secondaryProtocol != protocol) { return; }
+ SDLLogD(@"Received Register Secondary Transport ACK on the secondary (%@) transport", protocol.transport);
dispatch_async(self.stateMachineQueue, ^{
// secondary transport is now ready
[self.stateMachine transitionToState:SDLSecondaryTransportStateRegistered];
@@ -575,16 +617,21 @@ static const int TCPPortUnspecified = -1;
}
// called from SDLProtocol's _receiveQueue of secondary transport
-- (void)handleProtocolRegisterSecondaryTransportNAKMessage:(SDLProtocolMessage *)registerSecondaryTransportNAK {
- SDLLogW(@"Received Register Secondary Transport NAK frame");
+- (void)protocol:(SDLProtocol *)protocol didReceiveRegisterSecondaryTransportNAK:(SDLProtocolMessage *)registerSecondaryTransportNAK {
+ if (self.secondaryProtocol != protocol) { return; }
+ SDLLogW(@"Received Register Secondary Transport NAK on the secondary (%@) transport", protocol.transport);
dispatch_async(self.stateMachineQueue, ^{
+ if ([self.stateMachine.currentState isEqualToEnum:SDLSecondaryTransportStateRegistered]) {
+ [self sdl_handleTransportUpdateWithPrimaryAvailable:YES secondaryAvailable:NO];
+ }
[self.stateMachine transitionToState:SDLSecondaryTransportStateReconnecting];
});
}
-// called from SDLProtocol's _receiveQueue of "primary" transport
-- (void)onStartServiceAckReceived:(SDLControlFramePayloadRPCStartServiceAck *)payload {
+/// Called when a Start Service ACK control frame is received on the primary transport.
+/// @param payload The payload of Start Service ACK frame received on the primary transport.
+- (void)sdl_onStartServiceAckReceived:(SDLControlFramePayloadRPCStartServiceAck *)payload {
NSMutableArray<SDLSecondaryTransportTypeBox *> *secondaryTransports = nil;
if (payload.secondaryTransports != nil) {
secondaryTransports = [NSMutableArray array];
@@ -604,8 +651,9 @@ static const int TCPPortUnspecified = -1;
});
}
-// called from SDLProtocol's _receiveQueue of "primary" transport
-- (void)onTransportEventUpdateReceived:(SDLControlFramePayloadTransportEventUpdate *)payload {
+/// Called when a Transport Event Update control frame is received on the primary transport.
+/// @param payload the payload of Transport Event Update frame
+- (void)sdl_onTransportEventUpdateReceived:(SDLControlFramePayloadTransportEventUpdate *)payload {
dispatch_async(_stateMachineQueue, ^{
BOOL updated = NO;
@@ -655,25 +703,71 @@ static const int TCPPortUnspecified = -1;
#pragma mark - App state handling
-- (void)sdl_onAppStateUpdated:(NSNotification *)notification {
- dispatch_async(_stateMachineQueue, ^{
- if (notification.name == UIApplicationWillResignActiveNotification) {
- if ([self sdl_isTransportOpened] && self.secondaryTransportType == SDLSecondaryTransportTypeTCP) {
- SDLLogD(@"Disconnecting TCP transport since the app will go to background");
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
- }
- } else if (notification.name == UIApplicationDidBecomeActiveNotification) {
- if ([self.stateMachine isCurrentState:SDLSecondaryTransportStateConfigured]
- && self.secondaryTransportType == SDLSecondaryTransportTypeTCP
- && [self sdl_isTCPReady]
- && [self sdl_isHMILevelNonNone]) {
- SDLLogD(@"Resuming TCP transport since the app came into the foreground");
- [self.stateMachine transitionToState:SDLSecondaryTransportStateConnecting];
- }
+/// 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_appStateDidBecomeActive:(NSNotification *)notification {
+ self.currentApplicationState = UIApplicationStateActive;
+
+ __weak typeof(self) weakSelf = self;
+ dispatch_async(self.stateMachineQueue, ^{
+ __strong typeof(self) strongSelf = weakSelf;
+ 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.");
}
});
}
+/// Handles a notification that the background task is about to expire. If the app is still backgrounded we must close the TCP socket, which can take a few moments to complete. When this manager transitons to the Configured state, the `SDLStreamingMediaManager` is notified that the secondary transport wants to shutdown via the `streamingProtocolDelegate`. The `SDLStreamingMediaManager` sends an end video service control frame and an end audio service control frame and waits for responses to both requests from the module. Once the module has responded to both end service requests, the `SDLStreamingMediaManager` notifies us that the TCP socket can be shutdown by calling the `disconnectSecondaryTransport` method. Finally, once we know the socket has shutdown, we can end the background task. To ensure that all the shutdown steps are performed, we must delay shutting down the background task, otherwise some of the steps might not complete due to the app being suspended. Improper shutdown can cause trouble when establishing a new streaming session as either the new TCP connection will fail (due to the TCP socket's I/O streams not shutting down) or restarting the video and audio streams can fail (due to Core not receiving the end service requests). On the other hand, we can end the background task immediately if the app has re-entered the foreground or the manager has shutdown as no cleanup needs to be performed.
+/// @return A background task ended handler
+- (nullable BOOL (^)(void))sdl_backgroundTaskEndedHandler {
+ __weak typeof(self) weakSelf = self;
+ return ^{
+ __strong typeof(self) strongSelf = weakSelf;
+ 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;
+ } else if ([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 manager has been stopped.");
+ return NO;
+ } else {
+ // Return YES as we want to delay ending the background task until shutdown of the secondary transport has finished. Transitoning to the Configured state starts the process of shutting down the streaming services and the TCP socket which can take a few moments to complete. Once the streaming services have shutdown, the `SDLStreamingMediaManager` calls the `disconnectSecondaryTransport` method. The `disconnectSecondaryTransport` takes care of destroying the background task after disconnecting the TCP transport.
+ SDLLogD(@"Performing cleanup due to the background task expiring: disconnecting the TCP transport.");
+ [strongSelf.stateMachine transitionToState:SDLSecondaryTransportStateConfigured];
+ return YES;
+ }
+ };
+}
+
#pragma mark - Utility methods
- (SDLSecondaryTransportType)sdl_convertTransportType:(NSString *)transportString {
@@ -732,6 +826,7 @@ static const int TCPPortUnspecified = -1;
}
#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