1// Copyright 2013 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
5#include "remoting/client/jni/chromoting_jni_instance.h"
6
7#include <android/log.h>
8
9#include "base/bind.h"
10#include "base/logging.h"
11#include "jingle/glue/thread_wrapper.h"
12#include "net/socket/client_socket_factory.h"
13#include "remoting/base/service_urls.h"
14#include "remoting/client/audio_player.h"
15#include "remoting/client/client_status_logger.h"
16#include "remoting/client/jni/android_keymap.h"
17#include "remoting/client/jni/chromoting_jni_runtime.h"
18#include "remoting/client/software_video_renderer.h"
19#include "remoting/client/token_fetcher_proxy.h"
20#include "remoting/protocol/chromium_port_allocator.h"
21#include "remoting/protocol/chromium_socket_factory.h"
22#include "remoting/protocol/host_stub.h"
23#include "remoting/protocol/libjingle_transport_factory.h"
24#include "remoting/protocol/negotiating_client_authenticator.h"
25#include "remoting/protocol/network_settings.h"
26#include "remoting/signaling/server_log_entry.h"
27
28namespace remoting {
29
30namespace {
31
32// TODO(solb) Move into location shared with client plugin.
33const char* const kXmppServer = "talk.google.com";
34const int kXmppPort = 5222;
35const bool kXmppUseTls = true;
36
37// Interval at which to log performance statistics, if enabled.
38const int kPerfStatsIntervalMs = 60000;
39
40}
41
42ChromotingJniInstance::ChromotingJniInstance(ChromotingJniRuntime* jni_runtime,
43                                             const char* username,
44                                             const char* auth_token,
45                                             const char* host_jid,
46                                             const char* host_id,
47                                             const char* host_pubkey,
48                                             const char* pairing_id,
49                                             const char* pairing_secret,
50                                             const char* capabilities)
51    : jni_runtime_(jni_runtime),
52      host_id_(host_id),
53      host_jid_(host_jid),
54      create_pairing_(false),
55      stats_logging_enabled_(false),
56      capabilities_(capabilities),
57      weak_factory_(this) {
58  DCHECK(jni_runtime_->ui_task_runner()->BelongsToCurrentThread());
59
60  // Intialize XMPP config.
61  xmpp_config_.host = kXmppServer;
62  xmpp_config_.port = kXmppPort;
63  xmpp_config_.use_tls = kXmppUseTls;
64  xmpp_config_.username = username;
65  xmpp_config_.auth_token = auth_token;
66  xmpp_config_.auth_service = "oauth2";
67
68  // Initialize |authenticator_|.
69  scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
70      token_fetcher(new TokenFetcherProxy(
71          base::Bind(&ChromotingJniInstance::FetchThirdPartyToken,
72                     weak_factory_.GetWeakPtr()),
73          host_pubkey));
74
75  std::vector<protocol::AuthenticationMethod> auth_methods;
76  auth_methods.push_back(protocol::AuthenticationMethod::Spake2Pair());
77  auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
78      protocol::AuthenticationMethod::HMAC_SHA256));
79  auth_methods.push_back(protocol::AuthenticationMethod::Spake2(
80      protocol::AuthenticationMethod::NONE));
81  auth_methods.push_back(protocol::AuthenticationMethod::ThirdParty());
82
83  authenticator_.reset(new protocol::NegotiatingClientAuthenticator(
84      pairing_id, pairing_secret, host_id_,
85      base::Bind(&ChromotingJniInstance::FetchSecret, this),
86      token_fetcher.Pass(), auth_methods));
87
88  // Post a task to start connection
89  jni_runtime_->display_task_runner()->PostTask(
90      FROM_HERE,
91      base::Bind(&ChromotingJniInstance::ConnectToHostOnDisplayThread,
92                 this));
93}
94
95ChromotingJniInstance::~ChromotingJniInstance() {
96  // This object is ref-counted, so this dtor can execute on any thread.
97  // Ensure that all these objects have been freed already, so they are not
98  // destroyed on some random thread.
99  DCHECK(!view_);
100  DCHECK(!client_context_);
101  DCHECK(!video_renderer_);
102  DCHECK(!authenticator_);
103  DCHECK(!client_);
104  DCHECK(!signaling_);
105  DCHECK(!client_status_logger_);
106}
107
108void ChromotingJniInstance::Disconnect() {
109  if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
110    jni_runtime_->display_task_runner()->PostTask(
111        FROM_HERE,
112        base::Bind(&ChromotingJniInstance::Disconnect, this));
113    return;
114  }
115
116  // This must be destroyed on the display thread before the producer is gone.
117  view_.reset();
118
119  // The weak pointers must be invalidated on the same thread they were used.
120  view_weak_factory_->InvalidateWeakPtrs();
121
122  jni_runtime_->network_task_runner()->PostTask(
123      FROM_HERE,
124      base::Bind(&ChromotingJniInstance::DisconnectFromHostOnNetworkThread,
125                 this));
126}
127
128void ChromotingJniInstance::FetchThirdPartyToken(
129    const GURL& token_url,
130    const std::string& client_id,
131    const std::string& scope,
132    base::WeakPtr<TokenFetcherProxy> token_fetcher_proxy) {
133  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
134  DCHECK(!token_fetcher_proxy_.get());
135
136  __android_log_print(ANDROID_LOG_INFO,
137                      "ThirdPartyAuth",
138                      "Fetching Third Party Token from user.");
139
140  token_fetcher_proxy_ = token_fetcher_proxy;
141  jni_runtime_->ui_task_runner()->PostTask(
142      FROM_HERE,
143      base::Bind(&ChromotingJniRuntime::FetchThirdPartyToken,
144                 base::Unretained(jni_runtime_),
145                 token_url,
146                 client_id,
147                 scope));
148}
149
150void ChromotingJniInstance::HandleOnThirdPartyTokenFetched(
151    const std::string& token,
152    const std::string& shared_secret) {
153  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
154
155  __android_log_print(
156      ANDROID_LOG_INFO, "ThirdPartyAuth", "Third Party Token Fetched.");
157
158  if (token_fetcher_proxy_.get()) {
159    token_fetcher_proxy_->OnTokenFetched(token, shared_secret);
160    token_fetcher_proxy_.reset();
161  } else {
162    __android_log_print(
163        ANDROID_LOG_WARN,
164        "ThirdPartyAuth",
165        "Ignored OnThirdPartyTokenFetched() without a pending fetch.");
166  }
167}
168
169void ChromotingJniInstance::ProvideSecret(const std::string& pin,
170                                          bool create_pairing,
171                                          const std::string& device_name) {
172  DCHECK(jni_runtime_->ui_task_runner()->BelongsToCurrentThread());
173  DCHECK(!pin_callback_.is_null());
174
175  create_pairing_ = create_pairing;
176
177  if (create_pairing)
178    SetDeviceName(device_name);
179
180  jni_runtime_->network_task_runner()->PostTask(FROM_HERE,
181                                                base::Bind(pin_callback_, pin));
182}
183
184void ChromotingJniInstance::RedrawDesktop() {
185  if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
186    jni_runtime_->display_task_runner()->PostTask(
187        FROM_HERE,
188        base::Bind(&ChromotingJniInstance::RedrawDesktop, this));
189    return;
190  }
191
192  jni_runtime_->RedrawCanvas();
193}
194
195void ChromotingJniInstance::SendMouseEvent(
196    int x, int y,
197    protocol::MouseEvent_MouseButton button,
198    bool button_down) {
199  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
200    jni_runtime_->network_task_runner()->PostTask(
201        FROM_HERE, base::Bind(&ChromotingJniInstance::SendMouseEvent,
202                              this, x, y, button, button_down));
203    return;
204  }
205
206  protocol::MouseEvent event;
207  event.set_x(x);
208  event.set_y(y);
209  event.set_button(button);
210  if (button != protocol::MouseEvent::BUTTON_UNDEFINED)
211    event.set_button_down(button_down);
212
213  client_->input_stub()->InjectMouseEvent(event);
214}
215
216void ChromotingJniInstance::SendMouseWheelEvent(int delta_x, int delta_y) {
217  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
218    jni_runtime_->network_task_runner()->PostTask(
219        FROM_HERE,
220        base::Bind(&ChromotingJniInstance::SendMouseWheelEvent, this,
221                   delta_x, delta_y));
222    return;
223  }
224
225  protocol::MouseEvent event;
226  event.set_wheel_delta_x(delta_x);
227  event.set_wheel_delta_y(delta_y);
228  client_->input_stub()->InjectMouseEvent(event);
229}
230
231bool ChromotingJniInstance::SendKeyEvent(int key_code, bool key_down) {
232  uint32 usb_key_code = AndroidKeycodeToUsbKeycode(key_code);
233  if (!usb_key_code) {
234    LOG(WARNING) << "Ignoring unknown keycode: " << key_code;
235    return false;
236  }
237
238  SendKeyEventInternal(usb_key_code, key_down);
239  return true;
240}
241
242void ChromotingJniInstance::SendTextEvent(const std::string& text) {
243  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
244    jni_runtime_->network_task_runner()->PostTask(
245        FROM_HERE,
246        base::Bind(&ChromotingJniInstance::SendTextEvent, this, text));
247    return;
248  }
249
250  protocol::TextEvent event;
251  event.set_text(text);
252  client_->input_stub()->InjectTextEvent(event);
253}
254
255void ChromotingJniInstance::SendClientMessage(const std::string& type,
256                                              const std::string& data) {
257  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
258    jni_runtime_->network_task_runner()->PostTask(
259        FROM_HERE,
260        base::Bind(
261            &ChromotingJniInstance::SendClientMessage, this, type, data));
262    return;
263  }
264
265  protocol::ExtensionMessage extension_message;
266  extension_message.set_type(type);
267  extension_message.set_data(data);
268  client_->host_stub()->DeliverClientMessage(extension_message);
269}
270
271void ChromotingJniInstance::RecordPaintTime(int64 paint_time_ms) {
272  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
273    jni_runtime_->network_task_runner()->PostTask(
274        FROM_HERE, base::Bind(&ChromotingJniInstance::RecordPaintTime, this,
275                              paint_time_ms));
276    return;
277  }
278
279  if (stats_logging_enabled_)
280    video_renderer_->GetStats()->video_paint_ms()->Record(paint_time_ms);
281}
282
283void ChromotingJniInstance::OnConnectionState(
284    protocol::ConnectionToHost::State state,
285    protocol::ErrorCode error) {
286  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
287
288  EnableStatsLogging(state == protocol::ConnectionToHost::CONNECTED);
289
290  client_status_logger_->LogSessionStateChange(state, error);
291
292  if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
293    protocol::PairingRequest request;
294    DCHECK(!device_name_.empty());
295    request.set_client_name(device_name_);
296    client_->host_stub()->RequestPairing(request);
297  }
298
299  jni_runtime_->ui_task_runner()->PostTask(
300      FROM_HERE,
301      base::Bind(&ChromotingJniRuntime::OnConnectionState,
302                 base::Unretained(jni_runtime_),
303                 state,
304                 error));
305}
306
307void ChromotingJniInstance::OnConnectionReady(bool ready) {
308  // We ignore this message, since OnConnectionState tells us the same thing.
309}
310
311void ChromotingJniInstance::OnRouteChanged(
312    const std::string& channel_name,
313    const protocol::TransportRoute& route) {
314  std::string message = "Channel " + channel_name + " using " +
315      protocol::TransportRoute::GetTypeString(route.type) + " connection.";
316  __android_log_print(ANDROID_LOG_INFO, "route", "%s", message.c_str());
317}
318
319void ChromotingJniInstance::SetCapabilities(const std::string& capabilities) {
320  jni_runtime_->ui_task_runner()->PostTask(
321      FROM_HERE,
322      base::Bind(&ChromotingJniRuntime::SetCapabilities,
323                 base::Unretained(jni_runtime_),
324                 capabilities));
325}
326
327void ChromotingJniInstance::SetPairingResponse(
328    const protocol::PairingResponse& response) {
329
330  jni_runtime_->ui_task_runner()->PostTask(
331      FROM_HERE,
332      base::Bind(&ChromotingJniRuntime::CommitPairingCredentials,
333                 base::Unretained(jni_runtime_),
334                 host_id_, response.client_id(), response.shared_secret()));
335}
336
337void ChromotingJniInstance::DeliverHostMessage(
338    const protocol::ExtensionMessage& message) {
339  jni_runtime_->ui_task_runner()->PostTask(
340      FROM_HERE,
341      base::Bind(&ChromotingJniRuntime::HandleExtensionMessage,
342                 base::Unretained(jni_runtime_),
343                 message.type(),
344                 message.data()));
345}
346
347protocol::ClipboardStub* ChromotingJniInstance::GetClipboardStub() {
348  return this;
349}
350
351protocol::CursorShapeStub* ChromotingJniInstance::GetCursorShapeStub() {
352  return this;
353}
354
355void ChromotingJniInstance::InjectClipboardEvent(
356    const protocol::ClipboardEvent& event) {
357  NOTIMPLEMENTED();
358}
359
360void ChromotingJniInstance::SetCursorShape(
361    const protocol::CursorShapeInfo& shape) {
362  if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
363    jni_runtime_->display_task_runner()->PostTask(
364        FROM_HERE,
365        base::Bind(&ChromotingJniInstance::SetCursorShape, this, shape));
366    return;
367  }
368
369  jni_runtime_->UpdateCursorShape(shape);
370}
371
372void ChromotingJniInstance::ConnectToHostOnDisplayThread() {
373  DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread());
374
375  view_.reset(new JniFrameConsumer(jni_runtime_, this));
376  view_weak_factory_.reset(new base::WeakPtrFactory<JniFrameConsumer>(
377      view_.get()));
378  frame_consumer_ = new FrameConsumerProxy(jni_runtime_->display_task_runner(),
379                                           view_weak_factory_->GetWeakPtr());
380
381  jni_runtime_->network_task_runner()->PostTask(
382      FROM_HERE,
383      base::Bind(&ChromotingJniInstance::ConnectToHostOnNetworkThread,
384                 this));
385}
386
387void ChromotingJniInstance::ConnectToHostOnNetworkThread() {
388  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
389
390  jingle_glue::JingleThreadWrapper::EnsureForCurrentMessageLoop();
391
392  client_context_.reset(new ClientContext(
393      jni_runtime_->network_task_runner().get()));
394  client_context_->Start();
395
396  SoftwareVideoRenderer* renderer =
397      new SoftwareVideoRenderer(client_context_->main_task_runner(),
398                                client_context_->decode_task_runner(),
399                                frame_consumer_);
400  view_->set_frame_producer(renderer);
401  video_renderer_.reset(renderer);
402
403  client_.reset(new ChromotingClient(client_context_.get(),
404                                     this,
405                                     video_renderer_.get(),
406                                     scoped_ptr<AudioPlayer>()));
407
408  signaling_.reset(new XmppSignalStrategy(
409      net::ClientSocketFactory::GetDefaultFactory(),
410      jni_runtime_->url_requester(), xmpp_config_));
411
412  client_status_logger_.reset(
413      new ClientStatusLogger(ServerLogEntry::ME2ME,
414                             signaling_.get(),
415                             ServiceUrls::GetInstance()->directory_bot_jid()));
416
417  protocol::NetworkSettings network_settings(
418      protocol::NetworkSettings::NAT_TRAVERSAL_FULL);
419
420  // Use Chrome's network stack to allocate ports for peer-to-peer channels.
421  scoped_ptr<protocol::ChromiumPortAllocator> port_allocator(
422      protocol::ChromiumPortAllocator::Create(jni_runtime_->url_requester(),
423                                              network_settings));
424
425  scoped_ptr<protocol::TransportFactory> transport_factory(
426      new protocol::LibjingleTransportFactory(
427          signaling_.get(),
428          port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
429          network_settings));
430
431  client_->Start(signaling_.get(), authenticator_.Pass(),
432                 transport_factory.Pass(), host_jid_, capabilities_);
433}
434
435void ChromotingJniInstance::DisconnectFromHostOnNetworkThread() {
436  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
437
438  host_id_.clear();
439
440  stats_logging_enabled_ = false;
441
442  // |client_| must be torn down before |signaling_|.
443  client_.reset();
444  client_status_logger_.reset();
445  client_context_.reset();
446  video_renderer_.reset();
447  authenticator_.reset();
448  signaling_.reset();
449}
450
451void ChromotingJniInstance::FetchSecret(
452    bool pairable,
453    const protocol::SecretFetchedCallback& callback) {
454  if (!jni_runtime_->ui_task_runner()->BelongsToCurrentThread()) {
455    jni_runtime_->ui_task_runner()->PostTask(
456        FROM_HERE, base::Bind(&ChromotingJniInstance::FetchSecret,
457                              this, pairable, callback));
458    return;
459  }
460
461  // Delete pairing credentials if they exist.
462  jni_runtime_->CommitPairingCredentials(host_id_, "", "");
463
464  pin_callback_ = callback;
465  jni_runtime_->DisplayAuthenticationPrompt(pairable);
466}
467
468void ChromotingJniInstance::SetDeviceName(const std::string& device_name) {
469  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
470    jni_runtime_->network_task_runner()->PostTask(
471        FROM_HERE, base::Bind(&ChromotingJniInstance::SetDeviceName, this,
472                              device_name));
473    return;
474  }
475
476  device_name_ = device_name;
477}
478
479void ChromotingJniInstance::SendKeyEventInternal(int usb_key_code,
480                                                 bool key_down) {
481  if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
482    jni_runtime_->network_task_runner()->PostTask(
483        FROM_HERE, base::Bind(&ChromotingJniInstance::SendKeyEventInternal,
484                              this, usb_key_code, key_down));
485    return;
486  }
487
488
489  protocol::KeyEvent event;
490  event.set_usb_keycode(usb_key_code);
491  event.set_pressed(key_down);
492  client_->input_stub()->InjectKeyEvent(event);
493}
494
495void ChromotingJniInstance::EnableStatsLogging(bool enabled) {
496  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
497
498  if (enabled && !stats_logging_enabled_) {
499    jni_runtime_->network_task_runner()->PostDelayedTask(
500        FROM_HERE, base::Bind(&ChromotingJniInstance::LogPerfStats, this),
501        base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
502  }
503  stats_logging_enabled_ = enabled;
504}
505
506void ChromotingJniInstance::LogPerfStats() {
507  DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
508
509  if (!stats_logging_enabled_)
510    return;
511
512  ChromotingStats* stats = video_renderer_->GetStats();
513  __android_log_print(ANDROID_LOG_INFO, "stats",
514                      "Bandwidth:%.0f FrameRate:%.1f Capture:%.1f Encode:%.1f "
515                      "Decode:%.1f Render:%.1f Latency:%.0f",
516                      stats->video_bandwidth()->Rate(),
517                      stats->video_frame_rate()->Rate(),
518                      stats->video_capture_ms()->Average(),
519                      stats->video_encode_ms()->Average(),
520                      stats->video_decode_ms()->Average(),
521                      stats->video_paint_ms()->Average(),
522                      stats->round_trip_ms()->Average());
523
524  client_status_logger_->LogStatistics(stats);
525
526  jni_runtime_->network_task_runner()->PostDelayedTask(
527      FROM_HERE, base::Bind(&ChromotingJniInstance::LogPerfStats, this),
528      base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
529}
530
531}  // namespace remoting
532