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
RegisterJni(JNIEnv * env)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
LoadNative(JNIEnv * env,jclass clazz,jobject context)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
GetApiKey(JNIEnv * env,jclass clazz)55 static jstring GetApiKey(JNIEnv* env, jclass clazz) {
56 return ConvertUTF8ToJavaString(
57 env, google_apis::GetAPIKey().c_str()).Release();
58 }
59
GetClientId(JNIEnv * env,jclass clazz)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
GetClientSecret(JNIEnv * env,jclass clazz)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
Connect(JNIEnv * env,jclass clazz,jstring username,jstring authToken,jstring hostJid,jstring hostId,jstring hostPubkey,jstring pairId,jstring pairSecret)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
Disconnect(JNIEnv * env,jclass clazz)91 static void Disconnect(JNIEnv* env, jclass clazz) {
92 remoting::ChromotingJniRuntime::GetInstance()->DisconnectFromHost();
93 }
94
AuthenticationResponse(JNIEnv * env,jclass clazz,jstring pin,jboolean createPair,jstring deviceName)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
ScheduleRedraw(JNIEnv * env,jclass clazz)105 static void ScheduleRedraw(JNIEnv* env, jclass clazz) {
106 remoting::ChromotingJniRuntime::GetInstance()->session()->RedrawDesktop();
107 }
108
SendMouseEvent(JNIEnv * env,jclass clazz,jint x,jint y,jint whichButton,jboolean buttonDown)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
SendMouseWheelEvent(JNIEnv * env,jclass clazz,jint delta_x,jint delta_y)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
SendKeyEvent(JNIEnv * env,jclass clazz,jint keyCode,jboolean keyDown)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
SendTextEvent(JNIEnv * env,jclass clazz,jstring text)140 static void SendTextEvent(JNIEnv* env,
141 jclass clazz,
142 jstring text) {
143 remoting::ChromotingJniRuntime::GetInstance()->session()->SendTextEvent(
144 ConvertJavaStringToUTF8(env, text));
145 }
146
OnThirdPartyTokenFetched(JNIEnv * env,jclass clazz,jstring token,jstring shared_secret)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
GetInstance()162 ChromotingJniRuntime* ChromotingJniRuntime::GetInstance() {
163 return Singleton<ChromotingJniRuntime>::get();
164 }
165
ChromotingJniRuntime()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
~ChromotingJniRuntime()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
ConnectToHost(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)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
DisconnectFromHost()230 void ChromotingJniRuntime::DisconnectFromHost() {
231 DCHECK(ui_task_runner_->BelongsToCurrentThread());
232 if (session_) {
233 session_->Cleanup();
234 session_ = NULL;
235 }
236 }
237
ReportConnectionStatus(protocol::ConnectionToHost::State state,protocol::ErrorCode error)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
DisplayAuthenticationPrompt(bool pairing_supported)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
CommitPairingCredentials(const std::string & host,const std::string & id,const std::string & secret)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
FetchThirdPartyToken(const GURL & token_url,const std::string & client_id,const std::string & scope)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
NewBitmap(webrtc::DesktopSize size)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
UpdateFrameBitmap(jobject bitmap)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
UpdateCursorShape(const protocol::CursorShapeInfo & cursor_shape)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
RedrawCanvas()322 void ChromotingJniRuntime::RedrawCanvas() {
323 DCHECK(display_task_runner_->BelongsToCurrentThread());
324
325 JNIEnv* env = base::android::AttachCurrentThread();
326 Java_JniInterface_redrawGraphicsInternal(env);
327 }
328
DetachFromVmAndSignal(base::WaitableEvent * waiter)329 void ChromotingJniRuntime::DetachFromVmAndSignal(base::WaitableEvent* waiter) {
330 base::android::DetachFromVM();
331 waiter->Signal();
332 }
333 } // namespace remoting
334