blob: 52bb0d7c5bb910338293bdcdfcfdd32f06d2d20c [file] [log] [blame]
// Copyright 2017 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#if !defined(__has_feature) || !__has_feature(objc_arc)
#error "This file requires ARC support."
#endif
#import "remoting/ios/session/remoting_client.h"
#include <memory>
#import "base/mac/bind_objc_block.h"
#import "ios/third_party/material_components_ios/src/components/Snackbar/src/MaterialSnackbar.h"
#import "remoting/ios/audio/audio_player_ios.h"
#import "remoting/ios/display/gl_display_handler.h"
#import "remoting/ios/domain/client_session_details.h"
#import "remoting/ios/domain/host_info.h"
#import "remoting/ios/keychain_wrapper.h"
#import "remoting/ios/persistence/remoting_preferences.h"
#include "base/strings/sys_string_conversions.h"
#include "remoting/client/chromoting_client_runtime.h"
#include "remoting/client/chromoting_session.h"
#include "remoting/client/connect_to_host_info.h"
#include "remoting/client/display/renderer_proxy.h"
#include "remoting/client/gesture_interpreter.h"
#include "remoting/client/input/keyboard_interpreter.h"
#include "remoting/ios/session/remoting_client_session_delegate.h"
#include "remoting/protocol/session.h"
#include "remoting/protocol/video_renderer.h"
NSString* const kHostSessionStatusChanged = @"kHostSessionStatusChanged";
NSString* const kHostSessionPinProvided = @"kHostSessionPinProvided";
NSString* const kSessionDetails = @"kSessionDetails";
NSString* const kSessionSupportsPairing = @"kSessionSupportsPairing";
NSString* const kSessonStateErrorCode = @"kSessonStateErrorCode";
NSString* const kHostSessionCreatePairing = @"kHostSessionCreatePairing";
NSString* const kHostSessionHostName = @"kHostSessionHostName";
NSString* const kHostSessionPin = @"kHostSessionPin";
@interface RemotingClient () {
remoting::ChromotingClientRuntime* _runtime;
std::unique_ptr<remoting::RemotingClientSessonDelegate> _sessonDelegate;
ClientSessionDetails* _sessionDetails;
// Call _secretFetchedCallback on the network thread.
remoting::protocol::SecretFetchedCallback _secretFetchedCallback;
std::unique_ptr<remoting::RendererProxy> _renderer;
std::unique_ptr<remoting::GestureInterpreter> _gestureInterpreter;
std::unique_ptr<remoting::KeyboardInterpreter> _keyboardInterpreter;
std::unique_ptr<remoting::AudioPlayerIos> _audioPlayer;
std::unique_ptr<remoting::ChromotingSession> _session;
}
@end
@implementation RemotingClient
@synthesize displayHandler = _displayHandler;
- (instancetype)init {
self = [super init];
if (self) {
_runtime = remoting::ChromotingClientRuntime::GetInstance();
_sessonDelegate.reset(new remoting::RemotingClientSessonDelegate(self));
_sessionDetails = [[ClientSessionDetails alloc] init];
[[NSNotificationCenter defaultCenter]
addObserver:self
selector:@selector(hostSessionPinProvided:)
name:kHostSessionPinProvided
object:nil];
}
return self;
}
- (void)dealloc {
[self disconnectFromHost];
}
- (void)connectToHost:(HostInfo*)hostInfo
username:(NSString*)username
accessToken:(NSString*)accessToken {
DCHECK(_runtime->ui_task_runner()->BelongsToCurrentThread());
DCHECK(hostInfo);
DCHECK(hostInfo.jabberId);
DCHECK(hostInfo.hostId);
DCHECK(hostInfo.publicKey);
_sessionDetails.hostInfo = hostInfo;
remoting::ConnectToHostInfo info;
info.username = base::SysNSStringToUTF8(username);
info.auth_token = base::SysNSStringToUTF8(accessToken);
info.host_jid = base::SysNSStringToUTF8(hostInfo.jabberId);
info.host_id = base::SysNSStringToUTF8(hostInfo.hostId);
info.host_pubkey = base::SysNSStringToUTF8(hostInfo.publicKey);
info.host_os = base::SysNSStringToUTF8(hostInfo.hostOs);
info.host_os_version = base::SysNSStringToUTF8(hostInfo.hostOsVersion);
info.host_version = base::SysNSStringToUTF8(hostInfo.hostVersion);
NSDictionary* pairing =
[KeychainWrapper.instance pairingCredentialsForHost:hostInfo.hostId];
if (pairing) {
info.pairing_id =
base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingId]);
info.pairing_secret =
base::SysNSStringToUTF8([pairing objectForKey:kKeychainPairingSecret]);
} else {
info.pairing_id = "";
info.pairing_secret = "";
}
info.capabilities = "";
if ([RemotingPreferences.instance boolForFlag:RemotingFlagUseWebRTC]) {
info.flags = "useWebrtc";
[MDCSnackbarManager
showMessage:[MDCSnackbarMessage messageWithText:@"Using WebRTC"]];
}
remoting::protocol::ClientAuthenticationConfig client_auth_config;
client_auth_config.host_id = info.host_id;
client_auth_config.pairing_client_id = info.pairing_id;
client_auth_config.pairing_secret = info.pairing_secret;
// ChromotingClient keeps strong reference to |client_auth_config| through its
// lifetime.
__weak RemotingClient* weakSelf = self;
client_auth_config.fetch_secret_callback = base::BindBlockArc(
^(bool pairing_supported, const remoting::protocol::SecretFetchedCallback&
secret_fetched_callback) {
RemotingClient* strongSelf = weakSelf;
if (!strongSelf) {
return;
}
strongSelf->_secretFetchedCallback = secret_fetched_callback;
strongSelf->_sessionDetails.state = SessionPinPrompt;
// Notification will be received on the thread they are posted, so we
// need to post the notification on UI thread.
strongSelf->_runtime->ui_task_runner()->PostTask(
FROM_HERE, base::BindBlockArc(^() {
[NSNotificationCenter.defaultCenter
postNotificationName:kHostSessionStatusChanged
object:weakSelf
userInfo:@{
kSessionDetails : strongSelf->_sessionDetails,
kSessionSupportsPairing :
[NSNumber numberWithBool:pairing_supported],
}];
}));
});
_audioPlayer = remoting::AudioPlayerIos::CreateAudioPlayer(
_runtime->audio_task_runner());
_displayHandler = [[GlDisplayHandler alloc] init];
_displayHandler.delegate = self;
_session.reset(new remoting::ChromotingSession(
_sessonDelegate->GetWeakPtr(), [_displayHandler CreateCursorShapeStub],
[_displayHandler CreateVideoRenderer],
_audioPlayer->GetAudioStreamConsumer(), info, client_auth_config));
_renderer = [_displayHandler CreateRendererProxy];
_gestureInterpreter.reset(
new remoting::GestureInterpreter(_renderer.get(), _session.get()));
_keyboardInterpreter.reset(new remoting::KeyboardInterpreter(_session.get()));
_session->Connect();
_audioPlayer->Start();
}
- (void)disconnectFromHost {
if (_session) {
_session->Disconnect();
_runtime->network_task_runner()->DeleteSoon(FROM_HERE, _session.release());
}
_displayHandler = nil;
if (_audioPlayer) {
_audioPlayer->Invalidate();
_runtime->audio_task_runner()->DeleteSoon(FROM_HERE,
_audioPlayer.release());
}
// This needs to be deleted on the display thread since GlDisplayHandler binds
// its WeakPtrFactory to the display thread.
// TODO(yuweih): Ideally this constraint can be removed once we allow
// GlRenderer to be created on the UI thread before being used.
if (_renderer) {
_runtime->display_task_runner()->DeleteSoon(FROM_HERE, _renderer.release());
}
_gestureInterpreter.reset();
_keyboardInterpreter.reset();
}
#pragma mark - Eventing
- (void)hostSessionPinProvided:(NSNotification*)notification {
NSString* pin = [[notification userInfo] objectForKey:kHostSessionPin];
NSString* name = UIDevice.currentDevice.name;
BOOL createPairing = [[[notification userInfo]
objectForKey:kHostSessionCreatePairing] boolValue];
// TODO(nicholss): Look into refactoring ProvideSecret. It is mis-named and
// does not use pin.
if (_session) {
_session->ProvideSecret(base::SysNSStringToUTF8(pin),
(createPairing == YES),
base::SysNSStringToUTF8(name));
}
if (_secretFetchedCallback) {
remoting::protocol::SecretFetchedCallback callback = _secretFetchedCallback;
_runtime->network_task_runner()->PostTask(
FROM_HERE, base::BindBlockArc(^{
callback.Run(base::SysNSStringToUTF8(pin));
}));
_secretFetchedCallback.Reset();
}
}
#pragma mark - Properties
- (HostInfo*)hostInfo {
return _sessionDetails.hostInfo;
}
- (remoting::GestureInterpreter*)gestureInterpreter {
return _gestureInterpreter.get();
}
- (remoting::KeyboardInterpreter*)keyboardInterpreter {
return _keyboardInterpreter.get();
}
#pragma mark - ChromotingSession::Delegate
- (void)onConnectionState:(remoting::protocol::ConnectionToHost::State)state
error:(remoting::protocol::ErrorCode)error {
switch (state) {
case remoting::protocol::ConnectionToHost::INITIALIZING:
_sessionDetails.state = SessionInitializing;
break;
case remoting::protocol::ConnectionToHost::CONNECTING:
_sessionDetails.state = SessionConnecting;
break;
case remoting::protocol::ConnectionToHost::AUTHENTICATED:
_sessionDetails.state = SessionAuthenticated;
break;
case remoting::protocol::ConnectionToHost::CONNECTED:
_sessionDetails.state = SessionConnected;
break;
case remoting::protocol::ConnectionToHost::FAILED:
_sessionDetails.state = SessionFailed;
break;
case remoting::protocol::ConnectionToHost::CLOSED:
_sessionDetails.state = SessionClosed;
[self disconnectFromHost];
break;
default:
LOG(ERROR) << "onConnectionState, unknown state: " << state;
}
switch (error) {
case remoting::protocol::ErrorCode::OK:
_sessionDetails.error = SessionErrorOk;
break;
case remoting::protocol::ErrorCode::PEER_IS_OFFLINE:
_sessionDetails.error = SessionErrorPeerIsOffline;
break;
case remoting::protocol::ErrorCode::SESSION_REJECTED:
_sessionDetails.error = SessionErrorSessionRejected;
break;
case remoting::protocol::ErrorCode::INCOMPATIBLE_PROTOCOL:
_sessionDetails.error = SessionErrorIncompatibleProtocol;
break;
case remoting::protocol::ErrorCode::AUTHENTICATION_FAILED:
_sessionDetails.error = SessionErrorAuthenticationFailed;
break;
case remoting::protocol::ErrorCode::INVALID_ACCOUNT:
_sessionDetails.error = SessionErrorInvalidAccount;
break;
case remoting::protocol::ErrorCode::CHANNEL_CONNECTION_ERROR:
_sessionDetails.error = SessionErrorChannelConnectionError;
break;
case remoting::protocol::ErrorCode::SIGNALING_ERROR:
_sessionDetails.error = SessionErrorSignalingError;
break;
case remoting::protocol::ErrorCode::SIGNALING_TIMEOUT:
_sessionDetails.error = SessionErrorSignalingTimeout;
break;
case remoting::protocol::ErrorCode::HOST_OVERLOAD:
_sessionDetails.error = SessionErrorHostOverload;
break;
case remoting::protocol::ErrorCode::MAX_SESSION_LENGTH:
_sessionDetails.error = SessionErrorMaxSessionLength;
break;
case remoting::protocol::ErrorCode::HOST_CONFIGURATION_ERROR:
_sessionDetails.error = SessionErrorHostConfigurationError;
break;
default:
_sessionDetails.error = SessionErrorUnknownError;
break;
}
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged
object:self
userInfo:[NSDictionary dictionaryWithObject:_sessionDetails
forKey:kSessionDetails]];
}
- (void)commitPairingCredentialsForHost:(NSString*)host
id:(NSString*)id
secret:(NSString*)secret {
[KeychainWrapper.instance commitPairingCredentialsForHost:host
id:id
secret:secret];
}
- (void)fetchThirdPartyTokenForUrl:(NSString*)tokenUrl
clientId:(NSString*)clientId
scope:(NSString*)scope {
// Not supported for iOS yet.
_sessionDetails.state = SessionFailed;
_sessionDetails.error = SessionErrorThirdPartyAuthNotSupported;
_session->DisconnectForReason(
remoting::protocol::ErrorCode::AUTHENTICATION_FAILED);
[[NSNotificationCenter defaultCenter]
postNotificationName:kHostSessionStatusChanged
object:self
userInfo:[NSDictionary dictionaryWithObject:_sessionDetails
forKey:kSessionDetails]];
}
- (void)setCapabilities:(NSString*)capabilities {
DCHECK(capabilities.length == 0) << "No capability has been implemented on "
<< "iOS yet";
}
- (void)handleExtensionMessageOfType:(NSString*)type
message:(NSString*)message {
NOTREACHED() << "handleExtensionMessageOfType is unimplemented. " << type
<< ":" << message;
}
- (void)setHostResolution:(CGSize)dipsResolution scale:(int)scale {
_session->SendClientResolution(dipsResolution.width, dipsResolution.height,
scale);
}
#pragma mark - GlDisplayHandlerDelegate
- (void)canvasSizeChanged:(CGSize)size {
if (_gestureInterpreter) {
_gestureInterpreter->OnDesktopSizeChanged(size.width, size.height);
}
}
- (void)rendererTicked {
if (_gestureInterpreter) {
_gestureInterpreter->ProcessAnimations();
}
}
@end