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_runtime.h" 6 7 #include "base/android/jni_android.h" 8 #include "base/android/jni_array.h" 9 #include "base/android/jni_string.h" 10 #include "base/android/scoped_java_ref.h" 11 #include "base/basictypes.h" 12 #include "base/command_line.h" 13 #include "base/memory/singleton.h" 14 #include "base/stl_util.h" 15 #include "base/synchronization/waitable_event.h" 16 #include "google_apis/google_api_keys.h" 17 #include "jni/JniInterface_jni.h" 18 #include "media/base/yuv_convert.h" 19 #include "remoting/base/url_request_context.h" 20 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h" 21 22 using base::android::ConvertJavaStringToUTF8; 23 using base::android::ConvertUTF8ToJavaString; 24 using base::android::ToJavaByteArray; 25 26 namespace { 27 28 const int kBytesPerPixel = 4; 29 30 } // namespace 31 32 namespace remoting { 33 34 bool RegisterJni(JNIEnv* env) { 35 return remoting::RegisterNativesImpl(env); 36 } 37 38 // Implementation of stubs defined in JniInterface_jni.h. These are the entry 39 // points for JNI calls from Java into C++. 40 41 static void LoadNative(JNIEnv* env, jclass clazz, jobject context) { 42 base::android::ScopedJavaLocalRef<jobject> context_activity(env, context); 43 base::android::InitApplicationContext(env, context_activity); 44 45 // The google_apis functions check the command-line arguments to make sure no 46 // runtime API keys have been specified by the environment. Unfortunately, we 47 // neither launch Chromium nor have a command line, so we need to prevent 48 // them from DCHECKing out when they go looking. 49 base::CommandLine::Init(0, NULL); 50 51 // Create the singleton now so that the Chromoting threads will be set up. 52 remoting::ChromotingJniRuntime::GetInstance(); 53 } 54 55 static jstring GetApiKey(JNIEnv* env, jclass clazz) { 56 return ConvertUTF8ToJavaString( 57 env, google_apis::GetAPIKey().c_str()).Release(); 58 } 59 60 static jstring GetClientId(JNIEnv* env, jclass clazz) { 61 return ConvertUTF8ToJavaString( 62 env, google_apis::GetOAuth2ClientID( 63 google_apis::CLIENT_REMOTING).c_str()).Release(); 64 } 65 66 static jstring GetClientSecret(JNIEnv* env, jclass clazz) { 67 return ConvertUTF8ToJavaString( 68 env, google_apis::GetOAuth2ClientSecret( 69 google_apis::CLIENT_REMOTING).c_str()).Release(); 70 } 71 72 static void Connect(JNIEnv* env, 73 jclass clazz, 74 jstring username, 75 jstring authToken, 76 jstring hostJid, 77 jstring hostId, 78 jstring hostPubkey, 79 jstring pairId, 80 jstring pairSecret) { 81 remoting::ChromotingJniRuntime::GetInstance()->ConnectToHost( 82 ConvertJavaStringToUTF8(env, username).c_str(), 83 ConvertJavaStringToUTF8(env, authToken).c_str(), 84 ConvertJavaStringToUTF8(env, hostJid).c_str(), 85 ConvertJavaStringToUTF8(env, hostId).c_str(), 86 ConvertJavaStringToUTF8(env, hostPubkey).c_str(), 87 ConvertJavaStringToUTF8(env, pairId).c_str(), 88 ConvertJavaStringToUTF8(env, pairSecret).c_str()); 89 } 90 91 static void Disconnect(JNIEnv* env, jclass clazz) { 92 remoting::ChromotingJniRuntime::GetInstance()->DisconnectFromHost(); 93 } 94 95 static void AuthenticationResponse(JNIEnv* env, 96 jclass clazz, 97 jstring pin, 98 jboolean createPair, 99 jstring deviceName) { 100 remoting::ChromotingJniRuntime::GetInstance()->session()->ProvideSecret( 101 ConvertJavaStringToUTF8(env, pin).c_str(), createPair, 102 ConvertJavaStringToUTF8(env, deviceName)); 103 } 104 105 static void ScheduleRedraw(JNIEnv* env, jclass clazz) { 106 remoting::ChromotingJniRuntime::GetInstance()->session()->RedrawDesktop(); 107 } 108 109 static void SendMouseEvent(JNIEnv* env, 110 jclass clazz, 111 jint x, 112 jint y, 113 jint whichButton, 114 jboolean buttonDown) { 115 // Button must be within the bounds of the MouseEvent_MouseButton enum. 116 DCHECK(whichButton >= 0 && whichButton < 5); 117 118 remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseEvent( 119 x, y, 120 static_cast<remoting::protocol::MouseEvent_MouseButton>(whichButton), 121 buttonDown); 122 } 123 124 static void SendMouseWheelEvent(JNIEnv* env, 125 jclass clazz, 126 jint delta_x, 127 jint delta_y) { 128 remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseWheelEvent( 129 delta_x, delta_y); 130 } 131 132 static jboolean SendKeyEvent(JNIEnv* env, 133 jclass clazz, 134 jint keyCode, 135 jboolean keyDown) { 136 return remoting::ChromotingJniRuntime::GetInstance()->session()->SendKeyEvent( 137 keyCode, keyDown); 138 } 139 140 static void SendTextEvent(JNIEnv* env, 141 jclass clazz, 142 jstring text) { 143 remoting::ChromotingJniRuntime::GetInstance()->session()->SendTextEvent( 144 ConvertJavaStringToUTF8(env, text)); 145 } 146 147 static void OnThirdPartyTokenFetched(JNIEnv* env, 148 jclass clazz, 149 jstring token, 150 jstring shared_secret) { 151 ChromotingJniRuntime* runtime = remoting::ChromotingJniRuntime::GetInstance(); 152 runtime->network_task_runner()->PostTask(FROM_HERE, base::Bind( 153 &ChromotingJniInstance::HandleOnThirdPartyTokenFetched, 154 runtime->session(), 155 ConvertJavaStringToUTF8(env, token), 156 ConvertJavaStringToUTF8(env, shared_secret))); 157 } 158 159 // ChromotingJniRuntime implementation. 160 161 // static 162 ChromotingJniRuntime* ChromotingJniRuntime::GetInstance() { 163 return Singleton<ChromotingJniRuntime>::get(); 164 } 165 166 ChromotingJniRuntime::ChromotingJniRuntime() { 167 at_exit_manager_.reset(new base::AtExitManager()); 168 169 // On Android, the UI thread is managed by Java, so we need to attach and 170 // start a special type of message loop to allow Chromium code to run tasks. 171 ui_loop_.reset(new base::MessageLoopForUI()); 172 ui_loop_->Start(); 173 174 // TODO(solb) Stop pretending to control the managed UI thread's lifetime. 175 ui_task_runner_ = new AutoThreadTaskRunner(ui_loop_->message_loop_proxy(), 176 base::MessageLoop::QuitClosure()); 177 network_task_runner_ = AutoThread::CreateWithType("native_net", 178 ui_task_runner_, 179 base::MessageLoop::TYPE_IO); 180 display_task_runner_ = AutoThread::Create("native_disp", 181 ui_task_runner_); 182 183 url_requester_ = new URLRequestContextGetter(network_task_runner_); 184 185 // Allows later decoding of video frames. 186 media::InitializeCPUSpecificYUVConversions(); 187 } 188 189 ChromotingJniRuntime::~ChromotingJniRuntime() { 190 // The singleton should only ever be destroyed on the main thread. 191 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 192 193 // The session must be shut down first, since it depends on our other 194 // components' still being alive. 195 DisconnectFromHost(); 196 197 base::WaitableEvent done_event(false, false); 198 network_task_runner_->PostTask(FROM_HERE, base::Bind( 199 &ChromotingJniRuntime::DetachFromVmAndSignal, 200 base::Unretained(this), 201 &done_event)); 202 done_event.Wait(); 203 display_task_runner_->PostTask(FROM_HERE, base::Bind( 204 &ChromotingJniRuntime::DetachFromVmAndSignal, 205 base::Unretained(this), 206 &done_event)); 207 done_event.Wait(); 208 base::android::DetachFromVM(); 209 } 210 211 void ChromotingJniRuntime::ConnectToHost(const char* username, 212 const char* auth_token, 213 const char* host_jid, 214 const char* host_id, 215 const char* host_pubkey, 216 const char* pairing_id, 217 const char* pairing_secret) { 218 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 219 DCHECK(!session_); 220 session_ = new ChromotingJniInstance(this, 221 username, 222 auth_token, 223 host_jid, 224 host_id, 225 host_pubkey, 226 pairing_id, 227 pairing_secret); 228 } 229 230 void ChromotingJniRuntime::DisconnectFromHost() { 231 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 232 if (session_) { 233 session_->Cleanup(); 234 session_ = NULL; 235 } 236 } 237 238 void ChromotingJniRuntime::ReportConnectionStatus( 239 protocol::ConnectionToHost::State state, 240 protocol::ErrorCode error) { 241 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 242 243 JNIEnv* env = base::android::AttachCurrentThread(); 244 Java_JniInterface_reportConnectionStatus(env, state, error); 245 } 246 247 void ChromotingJniRuntime::DisplayAuthenticationPrompt(bool pairing_supported) { 248 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 249 250 JNIEnv* env = base::android::AttachCurrentThread(); 251 Java_JniInterface_displayAuthenticationPrompt(env, pairing_supported); 252 } 253 254 void ChromotingJniRuntime::CommitPairingCredentials(const std::string& host, 255 const std::string& id, 256 const std::string& secret) { 257 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 258 259 JNIEnv* env = base::android::AttachCurrentThread(); 260 ScopedJavaLocalRef<jstring> j_host = ConvertUTF8ToJavaString(env, host); 261 ScopedJavaLocalRef<jbyteArray> j_id = ToJavaByteArray( 262 env, reinterpret_cast<const uint8*>(id.data()), id.size()); 263 ScopedJavaLocalRef<jbyteArray> j_secret = ToJavaByteArray( 264 env, reinterpret_cast<const uint8*>(secret.data()), secret.size()); 265 266 Java_JniInterface_commitPairingCredentials( 267 env, j_host.obj(), j_id.obj(), j_secret.obj()); 268 } 269 270 void ChromotingJniRuntime::FetchThirdPartyToken(const GURL& token_url, 271 const std::string& client_id, 272 const std::string& scope) { 273 DCHECK(ui_task_runner_->BelongsToCurrentThread()); 274 JNIEnv* env = base::android::AttachCurrentThread(); 275 276 ScopedJavaLocalRef<jstring> j_url = 277 ConvertUTF8ToJavaString(env, token_url.spec()); 278 ScopedJavaLocalRef<jstring> j_client_id = 279 ConvertUTF8ToJavaString(env, client_id); 280 ScopedJavaLocalRef<jstring> j_scope = ConvertUTF8ToJavaString(env, scope); 281 282 Java_JniInterface_fetchThirdPartyToken( 283 env, j_url.obj(), j_client_id.obj(), j_scope.obj()); 284 } 285 286 base::android::ScopedJavaLocalRef<jobject> ChromotingJniRuntime::NewBitmap( 287 webrtc::DesktopSize size) { 288 JNIEnv* env = base::android::AttachCurrentThread(); 289 return Java_JniInterface_newBitmap(env, size.width(), size.height()); 290 } 291 292 void ChromotingJniRuntime::UpdateFrameBitmap(jobject bitmap) { 293 DCHECK(display_task_runner_->BelongsToCurrentThread()); 294 295 JNIEnv* env = base::android::AttachCurrentThread(); 296 Java_JniInterface_setVideoFrame(env, bitmap); 297 } 298 299 void ChromotingJniRuntime::UpdateCursorShape( 300 const protocol::CursorShapeInfo& cursor_shape) { 301 DCHECK(display_task_runner_->BelongsToCurrentThread()); 302 303 // const_cast<> is safe as long as the Java updateCursorShape() method copies 304 // the data out of the buffer without mutating it, and doesn't keep any 305 // reference to the buffer afterwards. Unfortunately, there seems to be no way 306 // to create a read-only ByteBuffer from a pointer-to-const. 307 char* data = string_as_array(const_cast<std::string*>(&cursor_shape.data())); 308 int cursor_total_bytes = 309 cursor_shape.width() * cursor_shape.height() * kBytesPerPixel; 310 311 JNIEnv* env = base::android::AttachCurrentThread(); 312 base::android::ScopedJavaLocalRef<jobject> buffer(env, 313 env->NewDirectByteBuffer(data, cursor_total_bytes)); 314 Java_JniInterface_updateCursorShape(env, 315 cursor_shape.width(), 316 cursor_shape.height(), 317 cursor_shape.hotspot_x(), 318 cursor_shape.hotspot_y(), 319 buffer.obj()); 320 } 321 322 void ChromotingJniRuntime::RedrawCanvas() { 323 DCHECK(display_task_runner_->BelongsToCurrentThread()); 324 325 JNIEnv* env = base::android::AttachCurrentThread(); 326 Java_JniInterface_redrawGraphicsInternal(env); 327 } 328 329 void ChromotingJniRuntime::DetachFromVmAndSignal(base::WaitableEvent* waiter) { 330 base::android::DetachFromVM(); 331 waiter->Signal(); 332 } 333 } // namespace remoting 334