client_connection_view_controller.mm 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653
  1. // Copyright 2017 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #import "remoting/ios/app/client_connection_view_controller.h"
  5. #import <MaterialComponents/MDCActivityIndicator.h>
  6. #import <MaterialComponents/MDCNavigationBar.h>
  7. #import <MaterialComponents/MaterialButtons.h>
  8. #import <MaterialComponents/MaterialCollections.h>
  9. #import <MaterialComponents/MaterialSnackbar.h>
  10. #include "base/logging.h"
  11. #include "base/strings/sys_string_conversions.h"
  12. #include "remoting/base/string_resources.h"
  13. #import "remoting/ios/app/help_and_feedback.h"
  14. #import "remoting/ios/app/host_view_controller.h"
  15. #import "remoting/ios/app/pin_entry_view.h"
  16. #import "remoting/ios/app/remoting_theme.h"
  17. #import "remoting/ios/app/session_reconnect_view.h"
  18. #import "remoting/ios/app/view_utils.h"
  19. #import "remoting/ios/domain/client_session_details.h"
  20. #import "remoting/ios/domain/host_info.h"
  21. #import "remoting/ios/facade/remoting_authentication.h"
  22. #import "remoting/ios/facade/remoting_service.h"
  23. #import "remoting/ios/session/remoting_client.h"
  24. #include "remoting/protocol/client_authentication_config.h"
  25. #include "ui/base/l10n/l10n_util.h"
  26. #if !defined(__has_feature) || !__has_feature(objc_arc)
  27. #error "This file requires ARC support."
  28. #endif
  29. static const CGFloat kIconRadius = 30.f;
  30. static const CGFloat kActivityIndicatorStrokeWidth = 3.f;
  31. static const CGFloat kActivityIndicatorRadius = 33.f;
  32. static const CGFloat kPinEntryViewWidth = 240.f;
  33. static const CGFloat kPinEntryViewHeight = 90.f;
  34. static const CGFloat kReconnectViewWidth = 240.f;
  35. static const CGFloat kReconnectViewHeight = 90.f;
  36. static const CGFloat kPadding = 20.f;
  37. static const CGFloat kMargin = 20.f;
  38. static const CGFloat kKeyboardAnimationTime = 0.3;
  39. static NSString* const kConnectionErrorFeedbackContext =
  40. @"ConnectionErrorFeedbackContext";
  41. using EntryPoint = remoting::ChromotingEvent::SessionEntryPoint;
  42. @interface ClientConnectionViewController ()<PinEntryDelegate,
  43. SessionReconnectViewDelegate> {
  44. UIImageView* _iconView;
  45. MDCActivityIndicator* _activityIndicator;
  46. NSLayoutConstraint* _activityIndicatorTopConstraintFull;
  47. NSLayoutConstraint* _activityIndicatorTopConstraintKeyboard;
  48. UILabel* _statusLabel;
  49. MDCNavigationBar* _navBar;
  50. PinEntryView* _pinEntryView;
  51. SessionReconnectView* _reconnectView;
  52. NSString* _remoteHostName;
  53. RemotingClient* _client;
  54. SessionErrorCode _lastError;
  55. HostInfo* _hostInfo;
  56. BOOL _hasViewAppeared;
  57. }
  58. @property(nonatomic, assign) SessionErrorCode lastError;
  59. @end
  60. @implementation ClientConnectionViewController
  61. @synthesize state = _state;
  62. @synthesize lastError = _lastError;
  63. - (instancetype)initWithHostInfo:(HostInfo*)hostInfo {
  64. self = [super init];
  65. if (self) {
  66. _hostInfo = hostInfo;
  67. _remoteHostName = hostInfo.hostName;
  68. _hasViewAppeared = NO;
  69. // TODO(yuweih): This logic may be reused by other views.
  70. UIButton* cancelButton = [UIButton buttonWithType:UIButtonTypeSystem];
  71. [cancelButton setTitle:l10n_util::GetNSString(IDS_CANCEL).uppercaseString
  72. forState:UIControlStateNormal];
  73. [cancelButton
  74. setImage:[RemotingTheme
  75. .backIcon imageFlippedForRightToLeftLayoutDirection]
  76. forState:UIControlStateNormal];
  77. [cancelButton addTarget:self
  78. action:@selector(didTapCancel:)
  79. forControlEvents:UIControlEventTouchUpInside];
  80. self.navigationItem.leftBarButtonItem =
  81. [[UIBarButtonItem alloc] initWithCustomView:cancelButton];
  82. _navBar = [[MDCNavigationBar alloc] initWithFrame:CGRectZero];
  83. [_navBar observeNavigationItem:self.navigationItem];
  84. [_navBar setBackgroundColor:RemotingTheme.connectionViewBackgroundColor];
  85. MDCNavigationBarTextColorAccessibilityMutator* mutator =
  86. [[MDCNavigationBarTextColorAccessibilityMutator alloc] init];
  87. [mutator mutate:_navBar];
  88. [self.view addSubview:_navBar];
  89. _navBar.translatesAutoresizingMaskIntoConstraints = NO;
  90. // Attach navBar to the top of the view.
  91. UILayoutGuide* layoutGuide =
  92. remoting::SafeAreaLayoutGuideForView(self.view);
  93. [NSLayoutConstraint activateConstraints:@[
  94. [_navBar.topAnchor constraintEqualToAnchor:layoutGuide.topAnchor],
  95. [_navBar.leadingAnchor constraintEqualToAnchor:layoutGuide.leadingAnchor],
  96. [_navBar.trailingAnchor
  97. constraintEqualToAnchor:layoutGuide.trailingAnchor],
  98. ]];
  99. }
  100. return self;
  101. }
  102. - (void)dealloc {
  103. [[NSNotificationCenter defaultCenter] removeObserver:self];
  104. }
  105. #pragma mark - UIViewController
  106. - (void)viewDidLoad {
  107. [super viewDidLoad];
  108. self.view.backgroundColor = RemotingTheme.connectionViewBackgroundColor;
  109. _activityIndicator = [[MDCActivityIndicator alloc] initWithFrame:CGRectZero];
  110. _activityIndicator.radius = kActivityIndicatorRadius;
  111. _activityIndicator.trackEnabled = YES;
  112. _activityIndicator.strokeWidth = kActivityIndicatorStrokeWidth;
  113. _activityIndicator.cycleColors =
  114. @[ RemotingTheme.connectionViewForegroundColor ];
  115. _activityIndicator.translatesAutoresizingMaskIntoConstraints = NO;
  116. [self.view addSubview:_activityIndicator];
  117. _statusLabel = [[UILabel alloc] initWithFrame:CGRectZero];
  118. _statusLabel.numberOfLines = 1;
  119. _statusLabel.lineBreakMode = NSLineBreakByTruncatingTail;
  120. _statusLabel.textColor = RemotingTheme.connectionViewForegroundColor;
  121. _statusLabel.textAlignment = NSTextAlignmentCenter;
  122. _statusLabel.translatesAutoresizingMaskIntoConstraints = NO;
  123. [self.view addSubview:_statusLabel];
  124. _iconView = [[UIImageView alloc] initWithFrame:CGRectZero];
  125. _iconView.contentMode = UIViewContentModeCenter;
  126. _iconView.alpha = 0.87f;
  127. _iconView.backgroundColor = RemotingTheme.hostOnlineColor;
  128. _iconView.layer.cornerRadius = kIconRadius;
  129. _iconView.layer.masksToBounds = YES;
  130. _iconView.image = RemotingTheme.desktopIcon;
  131. _iconView.translatesAutoresizingMaskIntoConstraints = NO;
  132. [self.view addSubview:_iconView];
  133. _reconnectView = [[SessionReconnectView alloc] initWithFrame:CGRectZero];
  134. _reconnectView.hidden = YES;
  135. _reconnectView.translatesAutoresizingMaskIntoConstraints = NO;
  136. [self.view addSubview:_reconnectView];
  137. _reconnectView.delegate = self;
  138. _pinEntryView = [[PinEntryView alloc] init];
  139. _pinEntryView.hidden = YES;
  140. _pinEntryView.translatesAutoresizingMaskIntoConstraints = NO;
  141. [self.view addSubview:_pinEntryView];
  142. _pinEntryView.delegate = self;
  143. [self
  144. initializeLayoutConstraintsWithViews:NSDictionaryOfVariableBindings(
  145. _activityIndicator, _statusLabel,
  146. _iconView, _reconnectView,
  147. _pinEntryView)];
  148. [[NSNotificationCenter defaultCenter]
  149. addObserver:self
  150. selector:@selector(hostSessionStatusChanged:)
  151. name:kHostSessionStatusChanged
  152. object:nil];
  153. [self attemptConnectionToHostWithEntryPoint:EntryPoint::CONNECT_BUTTON];
  154. // Although keyboard listeners are registered here, they won't work properly
  155. // if the keyboard shows/hides before the view appears.
  156. [[NSNotificationCenter defaultCenter]
  157. addObserver:self
  158. selector:@selector(keyboardWillShow:)
  159. name:UIKeyboardWillShowNotification
  160. object:nil];
  161. [[NSNotificationCenter defaultCenter]
  162. addObserver:self
  163. selector:@selector(keyboardWillHide:)
  164. name:UIKeyboardWillHideNotification
  165. object:nil];
  166. }
  167. - (void)initializeLayoutConstraintsWithViews:(NSDictionary*)views {
  168. // Metrics to use in visual format strings.
  169. NSDictionary* layoutMetrics = @{
  170. @"padding" : @(kPadding),
  171. @"margin" : @(kMargin),
  172. @"iconDiameter" : @(kIconRadius * 2),
  173. @"pinEntryViewWidth" : @(kPinEntryViewWidth),
  174. @"pinEntryViewHeight" : @(kPinEntryViewHeight),
  175. @"reconnectViewWidth" : @(kReconnectViewWidth),
  176. @"reconnectViewHeight" : @(kReconnectViewHeight),
  177. };
  178. [_activityIndicator sizeToFit];
  179. NSString* f;
  180. // Horizontal constraints:
  181. [self.view addConstraints:
  182. [NSLayoutConstraint
  183. constraintsWithVisualFormat:@"H:[_iconView(iconDiameter)]"
  184. options:0
  185. metrics:layoutMetrics
  186. views:views]];
  187. [self.view addConstraints:[NSLayoutConstraint
  188. constraintsWithVisualFormat:
  189. @"H:|-margin-[_statusLabel]-margin-|"
  190. options:0
  191. metrics:layoutMetrics
  192. views:views]];
  193. [self.view addConstraints:[NSLayoutConstraint
  194. constraintsWithVisualFormat:
  195. @"H:[_pinEntryView(pinEntryViewWidth)]"
  196. options:0
  197. metrics:layoutMetrics
  198. views:views]];
  199. [self.view addConstraints:[NSLayoutConstraint
  200. constraintsWithVisualFormat:
  201. @"H:[_reconnectView(reconnectViewWidth)]"
  202. options:0
  203. metrics:layoutMetrics
  204. views:views]];
  205. // Anchors:
  206. _activityIndicatorTopConstraintFull = [_activityIndicator.bottomAnchor
  207. constraintEqualToAnchor:self.view.centerYAnchor];
  208. _activityIndicatorTopConstraintFull.active = YES;
  209. [_iconView.centerYAnchor
  210. constraintEqualToAnchor:_activityIndicator.centerYAnchor]
  211. .active = YES;
  212. // Vertical constraints:
  213. [self.view addConstraints:
  214. [NSLayoutConstraint
  215. constraintsWithVisualFormat:@"V:[_iconView(iconDiameter)]"
  216. options:0
  217. metrics:layoutMetrics
  218. views:views]];
  219. [self.view addConstraints:
  220. [NSLayoutConstraint
  221. constraintsWithVisualFormat:
  222. @"V:[_activityIndicator]-(padding)-[_statusLabel]"
  223. options:NSLayoutFormatAlignAllCenterX
  224. metrics:layoutMetrics
  225. views:views]];
  226. [self.view addConstraints:
  227. [NSLayoutConstraint
  228. constraintsWithVisualFormat:
  229. @"V:[_iconView]-(padding)-[_statusLabel]"
  230. options:NSLayoutFormatAlignAllCenterX
  231. metrics:layoutMetrics
  232. views:views]];
  233. f = @"V:[_statusLabel]-(padding)-[_pinEntryView(pinEntryViewHeight)]";
  234. [self.view addConstraints:
  235. [NSLayoutConstraint
  236. constraintsWithVisualFormat:f
  237. options:NSLayoutFormatAlignAllCenterX
  238. metrics:layoutMetrics
  239. views:views]];
  240. f = @"V:[_statusLabel]-padding-[_reconnectView(reconnectViewHeight)]";
  241. [self.view addConstraints:
  242. [NSLayoutConstraint
  243. constraintsWithVisualFormat:f
  244. options:NSLayoutFormatAlignAllCenterX
  245. metrics:layoutMetrics
  246. views:views]];
  247. [self.view setNeedsUpdateConstraints];
  248. }
  249. - (void)viewWillAppear:(BOOL)animated {
  250. [super viewWillAppear:animated];
  251. [self.navigationController setNavigationBarHidden:YES animated:animated];
  252. }
  253. - (void)viewDidAppear:(BOOL)animated {
  254. [super viewDidAppear:animated];
  255. [_activityIndicator startAnimating];
  256. _hasViewAppeared = YES;
  257. self.state = _state;
  258. }
  259. - (void)viewWillDisappear:(BOOL)animated {
  260. [super viewWillDisappear:animated];
  261. [_activityIndicator stopAnimating];
  262. }
  263. - (BOOL)prefersStatusBarHidden {
  264. return YES;
  265. }
  266. #pragma mark - Keyboard
  267. - (void)keyboardWillShow:(NSNotification*)notification {
  268. CGSize keyboardSize =
  269. [[[notification userInfo] objectForKey:UIKeyboardFrameEndUserInfoKey]
  270. CGRectValue]
  271. .size;
  272. CGFloat newHeight = self.view.frame.size.height - keyboardSize.height;
  273. CGFloat overlap = newHeight - (_pinEntryView.frame.origin.y +
  274. _pinEntryView.frame.size.height + kPadding);
  275. if (overlap > 0) {
  276. overlap = 0;
  277. }
  278. _activityIndicatorTopConstraintKeyboard.active = NO;
  279. _activityIndicatorTopConstraintKeyboard = [_activityIndicator.topAnchor
  280. constraintEqualToAnchor:self.view.topAnchor
  281. constant:_activityIndicator.frame.origin.y + overlap];
  282. _activityIndicatorTopConstraintFull.active = NO;
  283. _activityIndicatorTopConstraintKeyboard.active = YES;
  284. [UIView animateWithDuration:kKeyboardAnimationTime
  285. animations:^{
  286. [self.view layoutIfNeeded];
  287. }];
  288. }
  289. - (void)keyboardWillHide:(NSNotification*)notification {
  290. _activityIndicatorTopConstraintKeyboard.active = NO;
  291. _activityIndicatorTopConstraintFull.active = YES;
  292. [UIView animateWithDuration:kKeyboardAnimationTime
  293. animations:^{
  294. [self.view layoutIfNeeded];
  295. }];
  296. }
  297. #pragma mark - Properties
  298. - (void)setState:(ClientConnectionViewState)state {
  299. _state = state;
  300. if (!_hasViewAppeared) {
  301. // Showing different state will re-layout the view, which will be broken if
  302. // the view is not shown yet.
  303. return;
  304. }
  305. switch (_state) {
  306. case ClientViewConnecting:
  307. [self showConnectingState];
  308. break;
  309. case ClientViewPinPrompt:
  310. [self showPinPromptState];
  311. break;
  312. case ClientViewConnected:
  313. [self showConnectedState];
  314. break;
  315. case ClientViewReconnect:
  316. [self showReconnect];
  317. break;
  318. case ClientViewClosed:
  319. [self.navigationController popToRootViewControllerAnimated:YES];
  320. break;
  321. case ClientViewError:
  322. [self showError];
  323. break;
  324. }
  325. }
  326. #pragma mark - SessionReconnectViewDelegate
  327. - (void)didTapReconnect {
  328. [self attemptConnectionToHostWithEntryPoint:EntryPoint::RECONNECT_BUTTON];
  329. }
  330. - (void)didTapReport {
  331. [_client createFeedbackDataWithCallback:^(
  332. const remoting::FeedbackData& feedbackData) {
  333. [HelpAndFeedback.instance
  334. presentFeedbackFlowWithContext:kConnectionErrorFeedbackContext
  335. feedbackData:feedbackData];
  336. }];
  337. }
  338. #pragma mark - Private
  339. - (void)attemptConnectionToHostWithEntryPoint:(EntryPoint)entryPoint {
  340. _client = [[RemotingClient alloc] init];
  341. __weak ClientConnectionViewController* weakSelf = self;
  342. __weak RemotingClient* weakClient = _client;
  343. __weak HostInfo* weakHostInfo = _hostInfo;
  344. [RemotingService.instance.authentication
  345. callbackWithAccessToken:^(RemotingAuthenticationStatus status,
  346. NSString* userEmail, NSString* accessToken) {
  347. if (status == RemotingAuthenticationStatusSuccess) {
  348. [weakClient connectToHost:weakHostInfo
  349. username:userEmail
  350. accessToken:accessToken
  351. entryPoint:entryPoint];
  352. } else {
  353. LOG(ERROR) << "Failed to fetch access token for connectToHost. ("
  354. << status << ")";
  355. weakSelf.lastError = SessionErrorOAuthTokenInvalid;
  356. weakSelf.state = ClientViewError;
  357. }
  358. }];
  359. self.state = ClientViewConnecting;
  360. }
  361. - (void)showConnectingState {
  362. [_pinEntryView endEditing:YES];
  363. _statusLabel.text =
  364. [self stringWithHostNameForId:IDS_CONNECTING_TO_HOST_MESSAGE];
  365. [self focusOnStatusLabel];
  366. _pinEntryView.hidden = YES;
  367. _reconnectView.hidden = YES;
  368. _iconView.backgroundColor = RemotingTheme.hostOnlineColor;
  369. [_activityIndicator stopAnimating];
  370. _activityIndicator.cycleColors =
  371. @[ RemotingTheme.connectionViewForegroundColor ];
  372. _activityIndicator.indicatorMode = MDCActivityIndicatorModeIndeterminate;
  373. _activityIndicator.hidden = NO;
  374. [_activityIndicator startAnimating];
  375. }
  376. - (void)showPinPromptState {
  377. _statusLabel.text = [NSString stringWithFormat:@"%@", _remoteHostName];
  378. _iconView.backgroundColor = RemotingTheme.hostOnlineColor;
  379. [_activityIndicator stopAnimating];
  380. _activityIndicator.hidden = YES;
  381. _pinEntryView.hidden = NO;
  382. _reconnectView.hidden = YES;
  383. _reconnectView.hidden = YES;
  384. // TODO(yuweih): This may be called before viewDidAppear and miss the keyboard
  385. // callback.
  386. [_pinEntryView becomeFirstResponder];
  387. }
  388. - (void)showConnectedState {
  389. [_pinEntryView endEditing:YES];
  390. _statusLabel.text =
  391. [self stringWithHostNameForId:IDS_CONNECTED_TO_HOST_MESSAGE];
  392. [self focusOnStatusLabel];
  393. _pinEntryView.hidden = YES;
  394. [_pinEntryView clearPinEntry];
  395. _iconView.backgroundColor = RemotingTheme.hostOnlineColor;
  396. _activityIndicator.progress = 0.0;
  397. _activityIndicator.hidden = NO;
  398. _activityIndicator.indicatorMode = MDCActivityIndicatorModeDeterminate;
  399. _activityIndicator.cycleColors = @[ RemotingTheme.hostOnlineColor ];
  400. [_activityIndicator startAnimating];
  401. _activityIndicator.progress = 1.0;
  402. _reconnectView.hidden = YES;
  403. _reconnectView.hidden = YES;
  404. HostViewController* hostViewController =
  405. [[HostViewController alloc] initWithClient:_client];
  406. [self.navigationController pushViewController:hostViewController animated:NO];
  407. }
  408. // TODO(yuweih): Unused. Remove this method and the ClientViewReconnect enum.
  409. - (void)showReconnect {
  410. _statusLabel.text =
  411. [self stringWithHostNameForId:IDS_CONNECTION_CLOSED_FOR_HOST_MESSAGE];
  412. [self focusOnStatusLabel];
  413. _iconView.backgroundColor = RemotingTheme.hostErrorColor;
  414. [_activityIndicator stopAnimating];
  415. _activityIndicator.hidden = YES;
  416. _pinEntryView.hidden = YES;
  417. _reconnectView.hidden = NO;
  418. _reconnectView.errorText =
  419. l10n_util::GetNSString(IDS_MESSAGE_SESSION_FINISHED);
  420. [self.navigationController popToViewController:self animated:YES];
  421. }
  422. - (void)showError {
  423. // Error may happen after the session is connected. In this case we should
  424. // pop back to the client connection VC.
  425. if (self.navigationController.topViewController != self) {
  426. [self.navigationController popToViewController:self animated:YES];
  427. }
  428. _statusLabel.text =
  429. [self stringWithHostNameForId:IDS_ERROR_CONNECTING_TO_HOST_MESSAGE];
  430. _pinEntryView.hidden = YES;
  431. _iconView.backgroundColor = RemotingTheme.hostErrorColor;
  432. _activityIndicator.hidden = YES;
  433. NSString* message = nil;
  434. switch (_lastError) {
  435. case SessionErrorOk:
  436. // Do nothing.
  437. break;
  438. case SessionErrorPeerIsOffline:
  439. message = l10n_util::GetNSString(IDS_ERROR_HOST_IS_OFFLINE);
  440. break;
  441. case SessionErrorSessionRejected:
  442. message = l10n_util::GetNSString(IDS_ERROR_INVALID_ACCOUNT);
  443. break;
  444. case SessionErrorIncompatibleProtocol:
  445. message = l10n_util::GetNSString(IDS_ERROR_INCOMPATIBLE_PROTOCOL);
  446. break;
  447. case SessionErrorAuthenticationFailed:
  448. message = l10n_util::GetNSString(IDS_ERROR_INVALID_ACCESS_CODE);
  449. [_pinEntryView clearPinEntry];
  450. break;
  451. case SessionErrorInvalidAccount:
  452. message = l10n_util::GetNSString(IDS_ERROR_INVALID_ACCOUNT);
  453. break;
  454. case SessionErrorChannelConnectionError:
  455. message = l10n_util::GetNSString(IDS_ERROR_NETWORK_FAILURE);
  456. break;
  457. case SessionErrorSignalingError:
  458. message = l10n_util::GetNSString(IDS_ERROR_P2P_FAILURE);
  459. break;
  460. case SessionErrorSignalingTimeout:
  461. message = l10n_util::GetNSString(IDS_ERROR_HOST_IS_OFFLINE);
  462. break;
  463. case SessionErrorHostOverload:
  464. message = l10n_util::GetNSString(IDS_ERROR_HOST_OVERLOAD);
  465. break;
  466. case SessionErrorMaxSessionLength:
  467. message = l10n_util::GetNSString(IDS_ERROR_MAX_SESSION_LENGTH);
  468. break;
  469. case SessionErrorHostConfigurationError:
  470. message = l10n_util::GetNSString(IDS_ERROR_HOST_CONFIGURATION_ERROR);
  471. break;
  472. case SessionErrorUnknownError:
  473. message = l10n_util::GetNSString(IDS_ERROR_UNEXPECTED);
  474. break;
  475. case SessionErrorOAuthTokenInvalid:
  476. message = l10n_util::GetNSString(IDS_ERROR_OAUTH_TOKEN_INVALID);
  477. break;
  478. case SessionErrorThirdPartyAuthNotSupported:
  479. message = l10n_util::GetNSString(IDS_THIRD_PARTY_AUTH_NOT_SUPPORTED);
  480. break;
  481. }
  482. if (message) {
  483. _reconnectView.errorText = message;
  484. }
  485. _reconnectView.hidden = NO;
  486. remoting::SetAccessibilityFocusElement(_reconnectView);
  487. }
  488. - (void)didProvidePin:(NSString*)pin createPairing:(BOOL)createPairing {
  489. [[NSNotificationCenter defaultCenter]
  490. postNotificationName:kHostSessionPinProvided
  491. object:self
  492. userInfo:@{
  493. kHostSessionHostName : _remoteHostName,
  494. kHostSessionPin : pin,
  495. kHostSessionCreatePairing : @(createPairing)
  496. }];
  497. }
  498. - (void)didTapCancel:(id)sender {
  499. _client = nil;
  500. [self.navigationController popViewControllerAnimated:YES];
  501. }
  502. - (void)hostSessionStatusChanged:(NSNotification*)notification {
  503. NSLog(@"hostSessionStatusChanged: %@", [notification userInfo]);
  504. ClientConnectionViewState state;
  505. ClientSessionDetails* sessionDetails =
  506. [[notification userInfo] objectForKey:kSessionDetails];
  507. switch (sessionDetails.state) {
  508. case SessionInitializing:
  509. // Same as HostConnecting in UI. Fall-though.
  510. case SessionAuthenticated:
  511. // Same as HostConnecting in UI. Fall-though.
  512. case SessionConnecting:
  513. state = ClientViewConnecting;
  514. break;
  515. case SessionPinPrompt:
  516. _pinEntryView.supportsPairing = [[[notification userInfo]
  517. objectForKey:kSessionSupportsPairing] boolValue];
  518. state = ClientViewPinPrompt;
  519. break;
  520. case SessionConnected:
  521. state = ClientViewConnected;
  522. break;
  523. case SessionFailed:
  524. state = ClientViewError;
  525. break;
  526. case SessionClosed:
  527. // If the session is closed by the host, just go back to the host list and
  528. // show a toast.
  529. state = ClientViewClosed;
  530. [MDCSnackbarManager.defaultManager
  531. showMessage:[MDCSnackbarMessage
  532. messageWithText:l10n_util::GetNSString(
  533. IDS_MESSAGE_SESSION_FINISHED)]];
  534. break;
  535. default:
  536. LOG(ERROR) << "Unknown State for Session, " << sessionDetails.state;
  537. return;
  538. }
  539. _lastError = sessionDetails.error;
  540. [[NSOperationQueue mainQueue] addOperationWithBlock:^{
  541. self.state = state;
  542. }];
  543. }
  544. - (NSString*)stringWithHostNameForId:(int)messageId {
  545. return l10n_util::GetNSStringF(messageId,
  546. base::SysNSStringToUTF16(_remoteHostName));
  547. }
  548. - (void)focusOnStatusLabel {
  549. remoting::SetAccessibilityFocusElement(_statusLabel);
  550. }
  551. @end