Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

WIP: Issue 749 app crashes when usb cord is pulled #752

Closed
6 changes: 6 additions & 0 deletions SmartDeviceLink/SDLFileManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,12 @@ - (void)didEnterStateShutdown {
- (void)didEnterStateFetchingInitialList {
__weak typeof(self) weakSelf = self;
[self sdl_listRemoteFilesWithCompletionHandler:^(BOOL success, NSUInteger bytesAvailable, NSArray<NSString *> *_Nonnull fileNames, NSError *_Nullable error) {
// If the file manager was shutdown before the SDL Core response, exit
if ([weakSelf.stateMachine isCurrentState:SDLFileManagerStateShutdown]) {
SDLLogW(@"The SDL enabled accessory was disconnected while getting the remote files list");
BLOCK_RETURN;
}

// If there was an error, we'll pass the error to the startup handler and cancel out
if (error != nil) {
[weakSelf.stateMachine transitionToState:SDLFileManagerStateStartupError];
Expand Down
12 changes: 8 additions & 4 deletions SmartDeviceLink/SDLIAPSession.m
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ - (void)stop {

long lWait = dispatch_semaphore_wait(self.canceledSemaphore, dispatch_time(DISPATCH_TIME_NOW, StreamThreadWaitSecs * NSEC_PER_SEC));
if (lWait == 0) {
SDLLogW(@"Stream thread cancelled");
SDLLogD(@"Stream thread canceled");
} else {
SDLLogE(@"Failed to cancel stream thread");
}
Expand Down Expand Up @@ -251,7 +251,9 @@ - (SDLStreamOpenHandler)streamOpenedHandler {

// When both streams are open, session initialization is complete. Let the delegate know.
if (strongSelf.isInputStreamOpen && strongSelf.isOutputStreamOpen) {
[strongSelf.delegate onSessionInitializationCompleteForSession:weakSelf];
if (strongSelf.delegate != nil) {
[strongSelf.delegate onSessionInitializationCompleteForSession:weakSelf];
}
}
};
}
Expand All @@ -262,8 +264,10 @@ - (SDLStreamErrorHandler)streamErroredHandler {
return ^(NSStream *stream) {
__strong typeof(weakSelf) strongSelf = weakSelf;

SDLLogW(@"Stream Error: %@", stream);
[strongSelf.delegate onSessionStreamsEnded:strongSelf];
SDLLogW(@"Stream Error: %@", stream.streamError);
if (strongSelf.delegate != nil) {
[strongSelf.delegate onSessionStreamsEnded:strongSelf];
}
};
}

Expand Down
17 changes: 13 additions & 4 deletions SmartDeviceLink/SDLIAPTransport.m
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,9 @@ - (void)sdl_startEventListening {

- (void)sdl_stopEventListening {
SDLLogV(@"SDLIAPTransport Stopped Listening For Events");
[[NSNotificationCenter defaultCenter] removeObserver:self];
[[NSNotificationCenter defaultCenter] removeObserver:self name:EAAccessoryDidConnectNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:EAAccessoryDidDisconnectNotification object:nil];
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationWillEnterForegroundNotification object:nil];
}

- (void)setSessionSetupInProgress:(BOOL)inProgress{
Expand Down Expand Up @@ -129,9 +131,10 @@ - (void)sdl_accessoryDisconnected:(NSNotification *)notification {
// Only check for the data session, the control session is handled separately
EAAccessory *accessory = [notification.userInfo objectForKey:EAAccessoryKey];
if (accessory.connectionID != self.session.accessory.connectionID) {
SDLLogD(@"Accessory Disconnected Event (%@)", accessory);
SDLLogD(@"An unconnected accessory disconnected (%@)", accessory);
}
if ([accessory.serialNumber isEqualToString:self.session.accessory.serialNumber]) {
SDLLogD(@"A connected accessory disconnected (%@)", accessory);
self.sessionSetupInProgress = NO;
[self disconnect];
[self.delegate onTransportDisconnected];
Expand Down Expand Up @@ -181,7 +184,9 @@ - (void)disconnect {
[self.controlSession stop];
self.controlSession.streamDelegate = nil;
self.controlSession = nil;
} else if (self.session != nil) {
}

if (self.session != nil) {
[self.session stop];
self.session.streamDelegate = nil;
self.session = nil;
Expand Down Expand Up @@ -326,6 +331,9 @@ - (void)sdl_retryEstablishSession {
self.session.delegate = nil;
self.session = nil;
}

SDLLogD(@"Starting another attempt to connect with an accessory...");

// No accessory to use this time, search connected accessories
[self sdl_connect:nil];
}
Expand Down Expand Up @@ -545,6 +553,7 @@ - (double)retryDelay {

- (void)sdl_destructObjects {
if (!_alreadyDestructed) {
SDLLogV(@"Destructing SDLIAPTransport objects");
_alreadyDestructed = YES;
self.controlSession = nil;
self.session = nil;
Expand All @@ -554,9 +563,9 @@ - (void)sdl_destructObjects {
}

- (void)dealloc {
SDLLogD(@"SDLIAPTransport dealloc");
[self disconnect];
[self sdl_destructObjects];
SDLLogD(@"SDLIAPTransport dealloc");
}

@end
Expand Down
131 changes: 87 additions & 44 deletions SmartDeviceLink/SDLLifecycleManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#import "SDLAbstractProtocol.h"
#import "SDLConfiguration.h"
#import "SDLConnectionManagerType.h"
#import "SDLGlobals.h"
#import "SDLLogMacros.h"
#import "SDLDisplayCapabilities.h"
#import "SDLError.h"
Expand Down Expand Up @@ -159,21 +160,36 @@ - (SDLState *)lifecycleState {

+ (NSDictionary<SDLState *, SDLAllowableStateTransitions *> *)sdl_stateTransitionDictionary {
return @{
SDLLifecycleStateStopped: @[SDLLifecycleStateStarted],
SDLLifecycleStateStarted: @[SDLLifecycleStateConnected, SDLLifecycleStateStopped, SDLLifecycleStateReconnecting],
SDLLifecycleStateReconnecting: @[SDLLifecycleStateStarted, SDLLifecycleStateStopped],
SDLLifecycleStateConnected: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateRegistered],
SDLLifecycleStateRegistered: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpManagers],
SDLLifecycleStateSettingUpManagers: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpAppIcon],
SDLLifecycleStateSettingUpAppIcon: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpHMI],
SDLLifecycleStateSettingUpHMI: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateReady],
SDLLifecycleStateUnregistering: @[SDLLifecycleStateStopped],
SDLLifecycleStateReady: @[SDLLifecycleStateUnregistering, SDLLifecycleStateStopped, SDLLifecycleStateReconnecting]
};
SDLLifecycleStateStopped: @[SDLLifecycleStateStarted],
SDLLifecycleStateStarted: @[SDLLifecycleStateConnected, SDLLifecycleStateStopped, SDLLifecycleStateReconnecting],
SDLLifecycleStateReconnecting: @[SDLLifecycleStateStarted, SDLLifecycleStateStopped],
SDLLifecycleStateConnected: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateRegistered],
SDLLifecycleStateRegistered: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpManagers],
SDLLifecycleStateSettingUpManagers: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpAppIcon],
SDLLifecycleStateSettingUpAppIcon: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateSettingUpHMI],
SDLLifecycleStateSettingUpHMI: @[SDLLifecycleStateStopped, SDLLifecycleStateReconnecting, SDLLifecycleStateReady],
SDLLifecycleStateUnregistering: @[SDLLifecycleStateStopped],
SDLLifecycleStateReady: @[SDLLifecycleStateUnregistering, SDLLifecycleStateStopped, SDLLifecycleStateReconnecting]
};
}


/**
* Checks if SDL enabled accessory was disconnected.
*
* @return YES if accessory disconnected, NO if accessory is still connected
*/
- (BOOL)sdl_didAccessoryDisconnect {
if ([self.lifecycleStateMachine isCurrentState:SDLLifecycleStateReconnecting] || [self.lifecycleStateMachine isCurrentState:SDLLifecycleStateStopped]) {
SDLLogW(@"Accessory disconnected before entering the ready state. Current state: %@", weakSelf.lifecycleStateMachine.currentState);
return true;
}

return false;
}

- (void)didEnterStateStarted {
// Start up the internal proxy object
// Start up the internal proxy object
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
if (self.configuration.lifecycleConfig.tcpDebugMode) {
Expand Down Expand Up @@ -233,21 +249,30 @@ - (void)didEnterStateConnected {
// Send the request and depending on the response, post the notification
__weak typeof(self) weakSelf = self;
[self sdl_sendRequest:regRequest
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
// If the success BOOL is NO or we received an error at this point, we failed. Call the ready handler and transition to the DISCONNECTED state.
if (error != nil || ![response.success boolValue]) {
SDLLogE(@"Failed to register the app. Error: %@, Response: %@", error, response);
weakSelf.readyHandler(NO, error);
[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateStopped];
return;
}

weakSelf.registerResponse = (SDLRegisterAppInterfaceResponse *)response;
[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateRegistered];
}];
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
// If the success BOOL is NO or we received an error at this point, we failed. Call the ready handler and transition to the DISCONNECTED state.
if (error != nil || ![response.success boolValue]) {
SDLLogE(@"Failed to register the app. Error: %@, Response: %@", error, response);
weakSelf.readyHandler(NO, error);
[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateStopped];
return;
}

weakSelf.registerResponse = (SDLRegisterAppInterfaceResponse *)response;

if ([weakSelf sdl_didAccessoryDisconnect]) {
BLOCK_RETURN;
}

[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateRegistered];
}];
}

- (void)didEnterStateRegistered {
if ([self sdl_didAccessoryDisconnect]) {
return;
}

[self.lifecycleStateMachine transitionToState:SDLLifecycleStateSettingUpManagers];
}

Expand Down Expand Up @@ -280,6 +305,11 @@ - (void)didEnterStateSettingUpManagers {
// We're done synchronously calling all startup methods, so we can now wait.
dispatch_group_leave(managerGroup);

if ([self sdl_didAccessoryDisconnect]) {
SDLLogV(@"Accessory disconnected after setting up managers. Current state: %@", self.lifecycleStateMachine.currentState);
return;
}

// When done, we want to transition, even if there were errors. They may be expected, e.g. on head units that do not support files.
dispatch_group_notify(managerGroup, dispatch_get_main_queue(), ^{
[self.lifecycleStateMachine transitionToState:SDLLifecycleStateSettingUpAppIcon];
Expand All @@ -290,6 +320,11 @@ - (void)didEnterStateSettingUpAppIcon {
// We only want to send the app icon when the file manager is complete, and when that's done, wait for hmi status to be ready
[self sdl_sendAppIcon:self.configuration.lifecycleConfig.appIcon
withCompletion:^{
if ([self sdl_didAccessoryDisconnect]) {
return;
}

SDLLogV(@"App icon set up, ready to start setting up the HMI");
[self.lifecycleStateMachine transitionToState:SDLLifecycleStateSettingUpHMI];
}];
}
Expand All @@ -301,11 +336,19 @@ - (void)didEnterStateSettingUpHMI {
return;
}

if ([self sdl_didAccessoryDisconnect]) {
return;
}

// We are sure to have a HMIStatus, set state to ready
[self.lifecycleStateMachine transitionToState:SDLLifecycleStateReady];
}

- (void)didEnterStateReady {
if ([self sdl_didAccessoryDisconnect]) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we certain having these everywhere is necessary? We are getting a transport disconnect notification (transportDidDisconnect) if the accessory disconnected.

return;
}

SDLResult registerResult = self.registerResponse.resultCode;
NSString *registerInfo = self.registerResponse.info;
NSError *startError = nil;
Expand All @@ -328,13 +371,13 @@ - (void)didEnterStateUnregistering {

__weak typeof(self) weakSelf = self;
[self sdl_sendRequest:unregisterRequest
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
if (error != nil || ![response.success boolValue]) {
SDLLogE(@"SDL Error unregistering, we are going to hard disconnect: %@, response: %@", error, response);
}
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
if (error != nil || ![response.success boolValue]) {
SDLLogE(@"SDL Error unregistering, we are going to hard disconnect: %@, response: %@", error, response);
}

[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateStopped];
}];
[weakSelf.lifecycleStateMachine transitionToState:SDLLifecycleStateStopped];
}];
}


Expand Down Expand Up @@ -364,14 +407,14 @@ - (void)sdl_sendAppIcon:(nullable SDLFile *)appIcon withCompletion:(void (^)(voi
setAppIcon.syncFileName = appIcon.name;

[self sdl_sendRequest:setAppIcon
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
if (error != nil) {
SDLLogW(@"Error setting up app icon: %@", error);
}

// We've succeeded or failed
completion();
}];
withResponseHandler:^(__kindof SDLRPCRequest *_Nullable request, __kindof SDLRPCResponse *_Nullable response, NSError *_Nullable error) {
if (error != nil) {
SDLLogW(@"Error setting up app icon: %@", error);
}

// We've succeeded or failed
completion();
}];
}];
}

Expand Down Expand Up @@ -474,19 +517,19 @@ - (void)hmiStatusDidChange:(SDLRPCNotificationNotification *)notification {
SDLOnHMIStatus *hmiStatusNotification = notification.notification;
SDLHMILevel oldHMILevel = self.hmiLevel;
self.hmiLevel = hmiStatusNotification.hmiLevel;

SDLAudioStreamingState oldStreamingState = self.audioStreamingState;
self.audioStreamingState = hmiStatusNotification.audioStreamingState;

SDLSystemContext oldSystemContext = self.systemContext;
self.systemContext = hmiStatusNotification.systemContext;

if (!self.firstHMINonNoneOccurred && ![self.hmiLevel isEqualToEnum:SDLHMILevelNone]) {
self.firstHMINonNoneOccurred = YES;
[self sdl_onFirstHMINonNone];
}
if ([self.lifecycleStateMachine isCurrentState:SDLLifecycleStateSettingUpHMI]) {

if ([self.lifecycleStateMachine isCurrentState:SDLLifecycleStateSettingUpHMI]) {
[self.lifecycleStateMachine transitionToState:SDLLifecycleStateReady];
}

Expand All @@ -497,12 +540,12 @@ - (void)hmiStatusDidChange:(SDLRPCNotificationNotification *)notification {
if (![oldHMILevel isEqualToEnum:self.hmiLevel]) {
[self.delegate hmiLevel:oldHMILevel didChangeToLevel:self.hmiLevel];
}

if (![oldStreamingState isEqualToEnum:self.audioStreamingState]
&& [self.delegate respondsToSelector:@selector(audioStreamingState:didChangeToState:)]) {
[self.delegate audioStreamingState:oldStreamingState didChangeToState:self.audioStreamingState];
}

if (![oldSystemContext isEqualToEnum:self.systemContext]
&& [self.delegate respondsToSelector:@selector(systemContext:didChangeToContext:)]) {
[self.delegate systemContext:oldSystemContext didChangeToContext:self.systemContext];
Expand Down
30 changes: 28 additions & 2 deletions SmartDeviceLinkTests/DevAPISpecs/SDLLifecycleManagerSpec.m
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,10 @@
#pragma clang diagnostic push
#pragma clang diagnostic ignored "-Wdeprecated-declarations"

@interface SDLLifecycleManager ()
- (void)sdl_sendRequest:(SDLRPCRequest *)request withResponseHandler:(nullable SDLResponseHandler)handler;
@end

QuickConfigurationBegin(SendingRPCsConfiguration)

+ (void)configure:(Configuration *)configuration {
Expand Down Expand Up @@ -87,7 +91,7 @@ + (void)configure:(Configuration *)configuration {
testManager.streamManager = streamingManagerMock;
});

xit(@"should initialize properties", ^{
it(@"should initialize properties", ^{
expect(testManager.configuration).toNot(equal(testConfig)); // This is copied
expect(testManager.delegate).to(equal(managerDelegateMock)); // TODO: Broken on OCMock 3.3.1 & Swift 3 Quick / Nimble
expect(testManager.lifecycleState).to(match(SDLLifecycleStateStopped));
Expand Down Expand Up @@ -164,7 +168,7 @@ + (void)configure:(Configuration *)configuration {
expect(testManager.lifecycleState).toEventuallyNot(match(SDLLifecycleStateStarted));
});
});

describe(@"when started", ^{
__block BOOL readyHandlerSuccess = NO;
__block NSError *readyHandlerError = nil;
Expand Down Expand Up @@ -468,6 +472,28 @@ + (void)configure:(Configuration *)configuration {
});
});
});

describe(@"when a accessory is disconnected during setup", ^{
// __block SDLOnHMIStatus *testHMIStatus = nil;
// __block SDLHMILevel testHMILevel = nil;
// __block id myObjectMock = OCMPartialMock(testManager);
//
// beforeEach(^{
//// OCMStub([myObjectMock sdl_sendRequest:[OCMArg any] withResponseHandler:([OCMArg invokeBlockWithArgs:OCMOCK_ANY, OCMOCK_ANY, OCMOCK_ANY, nil])]);
//
// testHMIStatus = [[SDLOnHMIStatus alloc] init];
// testHMILevel = SDLHMILevelNone;
// testHMIStatus.hmiLevel = testHMILevel;
//
// [testManager.notificationDispatcher postRPCNotificationNotification:SDLDidChangeHMIStatusNotification notification:testHMIStatus];
// });
//
// it(@"", ^{
// [testManager.notificationDispatcher postNotificationName:SDLTransportDidConnect infoObject:nil];
// [NSThread sleepForTimeInterval:0.1];
// [testManager.notificationDispatcher postNotificationName:SDLTransportDidDisconnect infoObject:nil];
// });
});
});

QuickSpecEnd
Expand Down
Loading