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.
5 #include "remoting/client/jni/chromoting_jni_runtime.h"
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_getter.h"
20 #include "third_party/webrtc/modules/desktop_capture/desktop_frame.h"
22 using base::android::ConvertJavaStringToUTF8
;
23 using base::android::ConvertUTF8ToJavaString
;
24 using base::android::ToJavaByteArray
;
28 const int kBytesPerPixel
= 4;
34 bool RegisterJni(JNIEnv
* env
) {
35 return remoting::RegisterNativesImpl(env
);
38 // Implementation of stubs defined in JniInterface_jni.h. These are the entry
39 // points for JNI calls from Java into C++.
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
);
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, nullptr);
51 // Create the singleton now so that the Chromoting threads will be set up.
52 remoting::ChromotingJniRuntime::GetInstance();
55 static jstring
GetApiKey(JNIEnv
* env
, jclass clazz
) {
56 return ConvertUTF8ToJavaString(
57 env
, google_apis::GetAPIKey().c_str()).Release();
60 static jstring
GetClientId(JNIEnv
* env
, jclass clazz
) {
61 return ConvertUTF8ToJavaString(
62 env
, google_apis::GetOAuth2ClientID(
63 google_apis::CLIENT_REMOTING
).c_str()).Release();
66 static jstring
GetClientSecret(JNIEnv
* env
, jclass clazz
) {
67 return ConvertUTF8ToJavaString(
68 env
, google_apis::GetOAuth2ClientSecret(
69 google_apis::CLIENT_REMOTING
).c_str()).Release();
72 static void Connect(JNIEnv
* env
,
81 jstring capabilities
) {
82 remoting::ChromotingJniRuntime::GetInstance()->ConnectToHost(
83 ConvertJavaStringToUTF8(env
, username
).c_str(),
84 ConvertJavaStringToUTF8(env
, authToken
).c_str(),
85 ConvertJavaStringToUTF8(env
, hostJid
).c_str(),
86 ConvertJavaStringToUTF8(env
, hostId
).c_str(),
87 ConvertJavaStringToUTF8(env
, hostPubkey
).c_str(),
88 ConvertJavaStringToUTF8(env
, pairId
).c_str(),
89 ConvertJavaStringToUTF8(env
, pairSecret
).c_str(),
90 ConvertJavaStringToUTF8(env
, capabilities
).c_str());
93 static void Disconnect(JNIEnv
* env
, jclass clazz
) {
94 remoting::ChromotingJniRuntime::GetInstance()->DisconnectFromHost();
97 static void AuthenticationResponse(JNIEnv
* env
,
101 jstring deviceName
) {
102 remoting::ChromotingJniRuntime::GetInstance()->session()->ProvideSecret(
103 ConvertJavaStringToUTF8(env
, pin
).c_str(), createPair
,
104 ConvertJavaStringToUTF8(env
, deviceName
));
107 static void ScheduleRedraw(JNIEnv
* env
, jclass clazz
) {
108 remoting::ChromotingJniRuntime::GetInstance()->session()->RedrawDesktop();
111 static void SendMouseEvent(JNIEnv
* env
,
116 jboolean buttonDown
) {
117 // Button must be within the bounds of the MouseEvent_MouseButton enum.
118 DCHECK(whichButton
>= 0 && whichButton
< 5);
120 remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseEvent(
122 static_cast<remoting::protocol::MouseEvent_MouseButton
>(whichButton
),
126 static void SendMouseWheelEvent(JNIEnv
* env
,
130 remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseWheelEvent(
134 static jboolean
SendKeyEvent(JNIEnv
* env
,
138 return remoting::ChromotingJniRuntime::GetInstance()->session()->SendKeyEvent(
142 static void SendTextEvent(JNIEnv
* env
,
145 remoting::ChromotingJniRuntime::GetInstance()->session()->SendTextEvent(
146 ConvertJavaStringToUTF8(env
, text
));
149 static void EnableVideoChannel(JNIEnv
* env
,
152 remoting::ChromotingJniRuntime::GetInstance()->session()->EnableVideoChannel(
156 static void OnThirdPartyTokenFetched(JNIEnv
* env
,
159 jstring shared_secret
) {
160 ChromotingJniRuntime
* runtime
= remoting::ChromotingJniRuntime::GetInstance();
161 runtime
->network_task_runner()->PostTask(FROM_HERE
, base::Bind(
162 &ChromotingJniInstance::HandleOnThirdPartyTokenFetched
,
164 ConvertJavaStringToUTF8(env
, token
),
165 ConvertJavaStringToUTF8(env
, shared_secret
)));
168 static void SendExtensionMessage(JNIEnv
* env
,
172 remoting::ChromotingJniRuntime::GetInstance()->session()->SendClientMessage(
173 ConvertJavaStringToUTF8(env
, type
),
174 ConvertJavaStringToUTF8(env
, data
));
177 // ChromotingJniRuntime implementation.
180 ChromotingJniRuntime
* ChromotingJniRuntime::GetInstance() {
181 return Singleton
<ChromotingJniRuntime
>::get();
184 ChromotingJniRuntime::ChromotingJniRuntime() {
185 at_exit_manager_
.reset(new base::AtExitManager());
187 // On Android, the UI thread is managed by Java, so we need to attach and
188 // start a special type of message loop to allow Chromium code to run tasks.
189 ui_loop_
.reset(new base::MessageLoopForUI());
192 // TODO(solb) Stop pretending to control the managed UI thread's lifetime.
193 ui_task_runner_
= new AutoThreadTaskRunner(ui_loop_
->message_loop_proxy(),
194 base::MessageLoop::QuitClosure());
195 network_task_runner_
= AutoThread::CreateWithType("native_net",
197 base::MessageLoop::TYPE_IO
);
198 display_task_runner_
= AutoThread::Create("native_disp",
202 new URLRequestContextGetter(network_task_runner_
, network_task_runner_
);
204 // Allows later decoding of video frames.
205 media::InitializeCPUSpecificYUVConversions();
208 ChromotingJniRuntime::~ChromotingJniRuntime() {
209 // The singleton should only ever be destroyed on the main thread.
210 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
212 // The session must be shut down first, since it depends on our other
213 // components' still being alive.
214 DisconnectFromHost();
216 base::WaitableEvent
done_event(false, false);
217 network_task_runner_
->PostTask(FROM_HERE
, base::Bind(
218 &ChromotingJniRuntime::DetachFromVmAndSignal
,
219 base::Unretained(this),
222 display_task_runner_
->PostTask(FROM_HERE
, base::Bind(
223 &ChromotingJniRuntime::DetachFromVmAndSignal
,
224 base::Unretained(this),
227 base::android::DetachFromVM();
230 void ChromotingJniRuntime::ConnectToHost(const char* username
,
231 const char* auth_token
,
232 const char* host_jid
,
234 const char* host_pubkey
,
235 const char* pairing_id
,
236 const char* pairing_secret
,
237 const char* capabilities
) {
238 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
239 DCHECK(!session_
.get());
240 session_
= new ChromotingJniInstance(this,
251 void ChromotingJniRuntime::DisconnectFromHost() {
252 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
253 if (session_
.get()) {
254 session_
->Disconnect();
259 void ChromotingJniRuntime::OnConnectionState(
260 protocol::ConnectionToHost::State state
,
261 protocol::ErrorCode error
) {
262 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
264 JNIEnv
* env
= base::android::AttachCurrentThread();
265 Java_JniInterface_onConnectionState(env
, state
, error
);
268 void ChromotingJniRuntime::DisplayAuthenticationPrompt(bool pairing_supported
) {
269 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
271 JNIEnv
* env
= base::android::AttachCurrentThread();
272 Java_JniInterface_displayAuthenticationPrompt(env
, pairing_supported
);
275 void ChromotingJniRuntime::CommitPairingCredentials(const std::string
& host
,
276 const std::string
& id
,
277 const std::string
& secret
) {
278 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
280 JNIEnv
* env
= base::android::AttachCurrentThread();
281 ScopedJavaLocalRef
<jstring
> j_host
= ConvertUTF8ToJavaString(env
, host
);
282 ScopedJavaLocalRef
<jstring
> j_id
= ConvertUTF8ToJavaString(env
, id
);
283 ScopedJavaLocalRef
<jstring
> j_secret
= ConvertUTF8ToJavaString(env
,secret
);
285 Java_JniInterface_commitPairingCredentials(
286 env
, j_host
.obj(), j_id
.obj(), j_secret
.obj());
289 void ChromotingJniRuntime::FetchThirdPartyToken(const GURL
& token_url
,
290 const std::string
& client_id
,
291 const std::string
& scope
) {
292 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
293 JNIEnv
* env
= base::android::AttachCurrentThread();
295 ScopedJavaLocalRef
<jstring
> j_url
=
296 ConvertUTF8ToJavaString(env
, token_url
.spec());
297 ScopedJavaLocalRef
<jstring
> j_client_id
=
298 ConvertUTF8ToJavaString(env
, client_id
);
299 ScopedJavaLocalRef
<jstring
> j_scope
= ConvertUTF8ToJavaString(env
, scope
);
301 Java_JniInterface_fetchThirdPartyToken(
302 env
, j_url
.obj(), j_client_id
.obj(), j_scope
.obj());
305 void ChromotingJniRuntime::SetCapabilities(const std::string
& capabilities
) {
306 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
307 JNIEnv
* env
= base::android::AttachCurrentThread();
309 ScopedJavaLocalRef
<jstring
> j_cap
=
310 ConvertUTF8ToJavaString(env
, capabilities
);
312 Java_JniInterface_setCapabilities(env
, j_cap
.obj());
315 void ChromotingJniRuntime::HandleExtensionMessage(const std::string
& type
,
316 const std::string
& message
) {
317 DCHECK(ui_task_runner_
->BelongsToCurrentThread());
318 JNIEnv
* env
= base::android::AttachCurrentThread();
320 ScopedJavaLocalRef
<jstring
> j_type
= ConvertUTF8ToJavaString(env
, type
);
321 ScopedJavaLocalRef
<jstring
> j_message
= ConvertUTF8ToJavaString(env
, message
);
323 Java_JniInterface_handleExtensionMessage(env
, j_type
.obj(), j_message
.obj());
326 base::android::ScopedJavaLocalRef
<jobject
> ChromotingJniRuntime::NewBitmap(
327 webrtc::DesktopSize size
) {
328 JNIEnv
* env
= base::android::AttachCurrentThread();
329 return Java_JniInterface_newBitmap(env
, size
.width(), size
.height());
332 void ChromotingJniRuntime::UpdateFrameBitmap(jobject bitmap
) {
333 DCHECK(display_task_runner_
->BelongsToCurrentThread());
335 JNIEnv
* env
= base::android::AttachCurrentThread();
336 Java_JniInterface_setVideoFrame(env
, bitmap
);
339 void ChromotingJniRuntime::UpdateCursorShape(
340 const protocol::CursorShapeInfo
& cursor_shape
) {
341 DCHECK(display_task_runner_
->BelongsToCurrentThread());
343 // const_cast<> is safe as long as the Java updateCursorShape() method copies
344 // the data out of the buffer without mutating it, and doesn't keep any
345 // reference to the buffer afterwards. Unfortunately, there seems to be no way
346 // to create a read-only ByteBuffer from a pointer-to-const.
347 char* data
= string_as_array(const_cast<std::string
*>(&cursor_shape
.data()));
348 int cursor_total_bytes
=
349 cursor_shape
.width() * cursor_shape
.height() * kBytesPerPixel
;
351 JNIEnv
* env
= base::android::AttachCurrentThread();
352 base::android::ScopedJavaLocalRef
<jobject
> buffer(env
,
353 env
->NewDirectByteBuffer(data
, cursor_total_bytes
));
354 Java_JniInterface_updateCursorShape(env
,
355 cursor_shape
.width(),
356 cursor_shape
.height(),
357 cursor_shape
.hotspot_x(),
358 cursor_shape
.hotspot_y(),
362 void ChromotingJniRuntime::RedrawCanvas() {
363 DCHECK(display_task_runner_
->BelongsToCurrentThread());
365 JNIEnv
* env
= base::android::AttachCurrentThread();
366 Java_JniInterface_redrawGraphicsInternal(env
);
369 void ChromotingJniRuntime::DetachFromVmAndSignal(base::WaitableEvent
* waiter
) {
370 base::android::DetachFromVM();
373 } // namespace remoting