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 "net/socket/client_socket_factory.h"
12 #include "remoting/client/audio_player.h"
13 #include "remoting/client/jni/android_keymap.h"
14 #include "remoting/client/jni/chromoting_jni_runtime.h"
15 #include "remoting/jingle_glue/chromium_port_allocator.h"
16 #include "remoting/jingle_glue/chromium_socket_factory.h"
17 #include "remoting/jingle_glue/network_settings.h"
18 #include "remoting/protocol/host_stub.h"
19 #include "remoting/protocol/libjingle_transport_factory.h"
20
21 namespace remoting {
22
23 namespace {
24
25 // TODO(solb) Move into location shared with client plugin.
26 const char* const kXmppServer = "talk.google.com";
27 const int kXmppPort = 5222;
28 const bool kXmppUseTls = true;
29
30 // Interval at which to log performance statistics, if enabled.
31 const int kPerfStatsIntervalMs = 10000;
32
33 }
34
ChromotingJniInstance(ChromotingJniRuntime * jni_runtime,const char * username,const char * auth_token,const char * host_jid,const char * host_id,const char * host_pubkey,const char * pairing_id,const char * pairing_secret)35 ChromotingJniInstance::ChromotingJniInstance(ChromotingJniRuntime* jni_runtime,
36 const char* username,
37 const char* auth_token,
38 const char* host_jid,
39 const char* host_id,
40 const char* host_pubkey,
41 const char* pairing_id,
42 const char* pairing_secret)
43 : jni_runtime_(jni_runtime),
44 host_id_(host_id),
45 create_pairing_(false),
46 stats_logging_enabled_(false) {
47 DCHECK(jni_runtime_->ui_task_runner()->BelongsToCurrentThread());
48
49 // Intialize XMPP config.
50 xmpp_config_.host = kXmppServer;
51 xmpp_config_.port = kXmppPort;
52 xmpp_config_.use_tls = kXmppUseTls;
53 xmpp_config_.username = username;
54 xmpp_config_.auth_token = auth_token;
55 xmpp_config_.auth_service = "oauth2";
56
57 // Initialize ClientConfig.
58 client_config_.host_jid = host_jid;
59 client_config_.host_public_key = host_pubkey;
60
61 client_config_.fetch_secret_callback =
62 base::Bind(&ChromotingJniInstance::FetchSecret, this);
63 client_config_.authentication_tag = host_id_;
64
65 client_config_.client_pairing_id = pairing_id;
66 client_config_.client_paired_secret = pairing_secret;
67
68 client_config_.authentication_methods.push_back(
69 protocol::AuthenticationMethod::FromString("spake2_pair"));
70 client_config_.authentication_methods.push_back(
71 protocol::AuthenticationMethod::FromString("spake2_hmac"));
72 client_config_.authentication_methods.push_back(
73 protocol::AuthenticationMethod::FromString("spake2_plain"));
74
75 // Post a task to start connection
76 jni_runtime_->display_task_runner()->PostTask(
77 FROM_HERE,
78 base::Bind(&ChromotingJniInstance::ConnectToHostOnDisplayThread,
79 this));
80 }
81
~ChromotingJniInstance()82 ChromotingJniInstance::~ChromotingJniInstance() {}
83
Cleanup()84 void ChromotingJniInstance::Cleanup() {
85 if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
86 jni_runtime_->display_task_runner()->PostTask(
87 FROM_HERE,
88 base::Bind(&ChromotingJniInstance::Cleanup, this));
89 return;
90 }
91
92 // This must be destroyed on the display thread before the producer is gone.
93 view_.reset();
94
95 // The weak pointers must be invalidated on the same thread they were used.
96 view_weak_factory_->InvalidateWeakPtrs();
97
98 jni_runtime_->network_task_runner()->PostTask(
99 FROM_HERE,
100 base::Bind(&ChromotingJniInstance::DisconnectFromHostOnNetworkThread,
101 this));
102 }
103
ProvideSecret(const std::string & pin,bool create_pairing)104 void ChromotingJniInstance::ProvideSecret(const std::string& pin,
105 bool create_pairing) {
106 DCHECK(jni_runtime_->ui_task_runner()->BelongsToCurrentThread());
107 DCHECK(!pin_callback_.is_null());
108
109 create_pairing_ = create_pairing;
110
111 jni_runtime_->network_task_runner()->PostTask(FROM_HERE,
112 base::Bind(pin_callback_, pin));
113 }
114
RedrawDesktop()115 void ChromotingJniInstance::RedrawDesktop() {
116 if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
117 jni_runtime_->display_task_runner()->PostTask(
118 FROM_HERE,
119 base::Bind(&ChromotingJniInstance::RedrawDesktop, this));
120 return;
121 }
122
123 jni_runtime_->RedrawCanvas();
124 }
125
PerformMouseAction(int x,int y,protocol::MouseEvent_MouseButton button,bool button_down)126 void ChromotingJniInstance::PerformMouseAction(
127 int x, int y,
128 protocol::MouseEvent_MouseButton button,
129 bool button_down) {
130 if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
131 jni_runtime_->network_task_runner()->PostTask(
132 FROM_HERE, base::Bind(&ChromotingJniInstance::PerformMouseAction,
133 this, x, y, button, button_down));
134 return;
135 }
136
137 protocol::MouseEvent action;
138 action.set_x(x);
139 action.set_y(y);
140 action.set_button(button);
141 if (button != protocol::MouseEvent::BUTTON_UNDEFINED)
142 action.set_button_down(button_down);
143
144 connection_->input_stub()->InjectMouseEvent(action);
145 }
146
PerformMouseWheelDeltaAction(int delta_x,int delta_y)147 void ChromotingJniInstance::PerformMouseWheelDeltaAction(int delta_x,
148 int delta_y) {
149 if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
150 jni_runtime_->network_task_runner()->PostTask(
151 FROM_HERE,
152 base::Bind(&ChromotingJniInstance::PerformMouseWheelDeltaAction, this,
153 delta_x, delta_y));
154 return;
155 }
156
157 protocol::MouseEvent action;
158 action.set_wheel_delta_x(delta_x);
159 action.set_wheel_delta_y(delta_y);
160 connection_->input_stub()->InjectMouseEvent(action);
161 }
162
PerformKeyboardAction(int key_code,bool key_down)163 void ChromotingJniInstance::PerformKeyboardAction(int key_code, bool key_down) {
164 if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
165 jni_runtime_->network_task_runner()->PostTask(
166 FROM_HERE, base::Bind(&ChromotingJniInstance::PerformKeyboardAction,
167 this, key_code, key_down));
168 return;
169 }
170
171 uint32 usb_code = AndroidKeycodeToUsbKeycode(key_code);
172 if (usb_code) {
173 protocol::KeyEvent action;
174 action.set_usb_keycode(usb_code);
175 action.set_pressed(key_down);
176 connection_->input_stub()->InjectKeyEvent(action);
177 } else {
178 LOG(WARNING) << "Ignoring unknown keycode: " << key_code;
179 }
180 }
181
RecordPaintTime(int64 paint_time_ms)182 void ChromotingJniInstance::RecordPaintTime(int64 paint_time_ms) {
183 if (!jni_runtime_->network_task_runner()->BelongsToCurrentThread()) {
184 jni_runtime_->network_task_runner()->PostTask(
185 FROM_HERE, base::Bind(&ChromotingJniInstance::RecordPaintTime, this,
186 paint_time_ms));
187 return;
188 }
189
190 if (stats_logging_enabled_)
191 client_->GetStats()->video_paint_ms()->Record(paint_time_ms);
192 }
193
OnConnectionState(protocol::ConnectionToHost::State state,protocol::ErrorCode error)194 void ChromotingJniInstance::OnConnectionState(
195 protocol::ConnectionToHost::State state,
196 protocol::ErrorCode error) {
197 DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
198
199 EnableStatsLogging(state == protocol::ConnectionToHost::CONNECTED);
200
201 if (create_pairing_ && state == protocol::ConnectionToHost::CONNECTED) {
202 protocol::PairingRequest request;
203 request.set_client_name("Android");
204 connection_->host_stub()->RequestPairing(request);
205 }
206
207 jni_runtime_->ui_task_runner()->PostTask(
208 FROM_HERE,
209 base::Bind(&ChromotingJniRuntime::ReportConnectionStatus,
210 base::Unretained(jni_runtime_),
211 state,
212 error));
213 }
214
OnConnectionReady(bool ready)215 void ChromotingJniInstance::OnConnectionReady(bool ready) {
216 // We ignore this message, since OnConnectionState tells us the same thing.
217 }
218
SetCapabilities(const std::string & capabilities)219 void ChromotingJniInstance::SetCapabilities(const std::string& capabilities) {
220 NOTIMPLEMENTED();
221 }
222
SetPairingResponse(const protocol::PairingResponse & response)223 void ChromotingJniInstance::SetPairingResponse(
224 const protocol::PairingResponse& response) {
225
226 jni_runtime_->ui_task_runner()->PostTask(
227 FROM_HERE,
228 base::Bind(&ChromotingJniRuntime::CommitPairingCredentials,
229 base::Unretained(jni_runtime_),
230 host_id_, response.client_id(), response.shared_secret()));
231 }
232
DeliverHostMessage(const protocol::ExtensionMessage & message)233 void ChromotingJniInstance::DeliverHostMessage(
234 const protocol::ExtensionMessage& message) {
235 NOTIMPLEMENTED();
236 }
237
GetClipboardStub()238 protocol::ClipboardStub* ChromotingJniInstance::GetClipboardStub() {
239 return this;
240 }
241
GetCursorShapeStub()242 protocol::CursorShapeStub* ChromotingJniInstance::GetCursorShapeStub() {
243 return this;
244 }
245
246 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>
GetTokenFetcher(const std::string & host_public_key)247 ChromotingJniInstance::GetTokenFetcher(const std::string& host_public_key) {
248 // Return null to indicate that third-party authentication is unsupported.
249 return scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher>();
250 }
251
InjectClipboardEvent(const protocol::ClipboardEvent & event)252 void ChromotingJniInstance::InjectClipboardEvent(
253 const protocol::ClipboardEvent& event) {
254 NOTIMPLEMENTED();
255 }
256
SetCursorShape(const protocol::CursorShapeInfo & shape)257 void ChromotingJniInstance::SetCursorShape(
258 const protocol::CursorShapeInfo& shape) {
259 if (!jni_runtime_->display_task_runner()->BelongsToCurrentThread()) {
260 jni_runtime_->display_task_runner()->PostTask(
261 FROM_HERE,
262 base::Bind(&ChromotingJniInstance::SetCursorShape, this, shape));
263 return;
264 }
265
266 jni_runtime_->UpdateCursorShape(shape);
267 }
268
ConnectToHostOnDisplayThread()269 void ChromotingJniInstance::ConnectToHostOnDisplayThread() {
270 DCHECK(jni_runtime_->display_task_runner()->BelongsToCurrentThread());
271
272 view_.reset(new JniFrameConsumer(jni_runtime_, this));
273 view_weak_factory_.reset(new base::WeakPtrFactory<JniFrameConsumer>(
274 view_.get()));
275 frame_consumer_ = new FrameConsumerProxy(jni_runtime_->display_task_runner(),
276 view_weak_factory_->GetWeakPtr());
277
278 jni_runtime_->network_task_runner()->PostTask(
279 FROM_HERE,
280 base::Bind(&ChromotingJniInstance::ConnectToHostOnNetworkThread,
281 this));
282 }
283
ConnectToHostOnNetworkThread()284 void ChromotingJniInstance::ConnectToHostOnNetworkThread() {
285 DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
286
287 client_context_.reset(new ClientContext(
288 jni_runtime_->network_task_runner().get()));
289 client_context_->Start();
290
291 connection_.reset(new protocol::ConnectionToHost(true));
292
293 client_.reset(new ChromotingClient(
294 client_config_, client_context_.get(), connection_.get(),
295 this, frame_consumer_, scoped_ptr<AudioPlayer>()));
296
297 view_->set_frame_producer(client_->GetFrameProducer());
298
299 signaling_.reset(new XmppSignalStrategy(
300 net::ClientSocketFactory::GetDefaultFactory(),
301 jni_runtime_->url_requester(), xmpp_config_));
302
303 NetworkSettings network_settings(NetworkSettings::NAT_TRAVERSAL_ENABLED);
304
305 // Use Chrome's network stack to allocate ports for peer-to-peer channels.
306 scoped_ptr<ChromiumPortAllocator> port_allocator(
307 ChromiumPortAllocator::Create(jni_runtime_->url_requester(),
308 network_settings));
309
310 scoped_ptr<protocol::TransportFactory> transport_factory(
311 new protocol::LibjingleTransportFactory(
312 signaling_.get(),
313 port_allocator.PassAs<cricket::HttpPortAllocatorBase>(),
314 network_settings));
315
316 client_->Start(signaling_.get(), transport_factory.Pass());
317 }
318
DisconnectFromHostOnNetworkThread()319 void ChromotingJniInstance::DisconnectFromHostOnNetworkThread() {
320 DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
321
322 host_id_.clear();
323
324 stats_logging_enabled_ = false;
325
326 // |client_| must be torn down before |signaling_|.
327 connection_.reset();
328 client_.reset();
329 }
330
FetchSecret(bool pairable,const protocol::SecretFetchedCallback & callback)331 void ChromotingJniInstance::FetchSecret(
332 bool pairable,
333 const protocol::SecretFetchedCallback& callback) {
334 if (!jni_runtime_->ui_task_runner()->BelongsToCurrentThread()) {
335 jni_runtime_->ui_task_runner()->PostTask(
336 FROM_HERE, base::Bind(&ChromotingJniInstance::FetchSecret,
337 this, pairable, callback));
338 return;
339 }
340
341 if (!client_config_.client_pairing_id.empty()) {
342 // We attempted to connect using an existing pairing that was rejected.
343 // Unless we forget about the stale credentials, we'll continue trying them.
344 jni_runtime_->CommitPairingCredentials(host_id_, "", "");
345 }
346
347 pin_callback_ = callback;
348 jni_runtime_->DisplayAuthenticationPrompt(pairable);
349 }
350
EnableStatsLogging(bool enabled)351 void ChromotingJniInstance::EnableStatsLogging(bool enabled) {
352 DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
353
354 if (enabled && !stats_logging_enabled_) {
355 jni_runtime_->network_task_runner()->PostDelayedTask(
356 FROM_HERE, base::Bind(&ChromotingJniInstance::LogPerfStats, this),
357 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
358 }
359 stats_logging_enabled_ = enabled;
360 }
361
LogPerfStats()362 void ChromotingJniInstance::LogPerfStats() {
363 DCHECK(jni_runtime_->network_task_runner()->BelongsToCurrentThread());
364
365 if (!stats_logging_enabled_)
366 return;
367
368 ChromotingStats* stats = client_->GetStats();
369 __android_log_print(ANDROID_LOG_INFO, "stats",
370 "Bandwidth:%.0f FrameRate:%.1f Capture:%.1f Encode:%.1f "
371 "Decode:%.1f Render:%.1f Latency:%.0f",
372 stats->video_bandwidth()->Rate(),
373 stats->video_frame_rate()->Rate(),
374 stats->video_capture_ms()->Average(),
375 stats->video_encode_ms()->Average(),
376 stats->video_decode_ms()->Average(),
377 stats->video_paint_ms()->Average(),
378 stats->round_trip_ms()->Average());
379
380 jni_runtime_->network_task_runner()->PostDelayedTask(
381 FROM_HERE, base::Bind(&ChromotingJniInstance::LogPerfStats, this),
382 base::TimeDelta::FromMilliseconds(kPerfStatsIntervalMs));
383 }
384
385 } // namespace remoting
386