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 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 82 ChromotingJniInstance::~ChromotingJniInstance() {} 83 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 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 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 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 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 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 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 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 215 void ChromotingJniInstance::OnConnectionReady(bool ready) { 216 // We ignore this message, since OnConnectionState tells us the same thing. 217 } 218 219 void ChromotingJniInstance::SetCapabilities(const std::string& capabilities) { 220 NOTIMPLEMENTED(); 221 } 222 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 233 void ChromotingJniInstance::DeliverHostMessage( 234 const protocol::ExtensionMessage& message) { 235 NOTIMPLEMENTED(); 236 } 237 238 protocol::ClipboardStub* ChromotingJniInstance::GetClipboardStub() { 239 return this; 240 } 241 242 protocol::CursorShapeStub* ChromotingJniInstance::GetCursorShapeStub() { 243 return this; 244 } 245 246 scoped_ptr<protocol::ThirdPartyClientAuthenticator::TokenFetcher> 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 252 void ChromotingJniInstance::InjectClipboardEvent( 253 const protocol::ClipboardEvent& event) { 254 NOTIMPLEMENTED(); 255 } 256 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 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 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 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 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 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 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