// Copyright 2013 The Chromium Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #include "remoting/client/jni/chromoting_jni_runtime.h" #include "base/android/jni_android.h" #include "base/android/jni_array.h" #include "base/android/jni_string.h" #include "base/android/library_loader/library_loader_hooks.h" #include "base/android/scoped_java_ref.h" #include "base/command_line.h" #include "base/memory/singleton.h" #include "base/stl_util.h" #include "base/synchronization/waitable_event.h" #include "jni/JniInterface_jni.h" #include "remoting/base/url_request_context_getter.h" #include "remoting/client/jni/jni_touch_event_data.h" using base::android::ConvertJavaStringToUTF8; using base::android::ConvertUTF8ToJavaString; using base::android::ToJavaByteArray; namespace { const int kBytesPerPixel = 4; } // namespace namespace remoting { bool RegisterChromotingJniRuntime(JNIEnv* env) { return remoting::RegisterNativesImpl(env); } // Implementation of stubs defined in JniInterface_jni.h. These are the entry // points for JNI calls from Java into C++. static void LoadNative(JNIEnv* env, const JavaParamRef& clazz) { base::CommandLine::Init(0, nullptr); // Create the singleton now so that the Chromoting threads will be set up. remoting::ChromotingJniRuntime::GetInstance(); } static void Connect(JNIEnv* env, const JavaParamRef& clazz, const JavaParamRef& username, const JavaParamRef& authToken, const JavaParamRef& hostJid, const JavaParamRef& hostId, const JavaParamRef& hostPubkey, const JavaParamRef& pairId, const JavaParamRef& pairSecret, const JavaParamRef& capabilities, const JavaParamRef& flags) { remoting::ChromotingJniRuntime::GetInstance()->ConnectToHost( ConvertJavaStringToUTF8(env, username), ConvertJavaStringToUTF8(env, authToken), ConvertJavaStringToUTF8(env, hostJid), ConvertJavaStringToUTF8(env, hostId), ConvertJavaStringToUTF8(env, hostPubkey), ConvertJavaStringToUTF8(env, pairId), ConvertJavaStringToUTF8(env, pairSecret), ConvertJavaStringToUTF8(env, capabilities), ConvertJavaStringToUTF8(env, flags)); } static void Disconnect(JNIEnv* env, const JavaParamRef& clazz) { remoting::ChromotingJniRuntime::GetInstance()->DisconnectFromHost(); } static void AuthenticationResponse(JNIEnv* env, const JavaParamRef& clazz, const JavaParamRef& pin, jboolean createPair, const JavaParamRef& deviceName) { remoting::ChromotingJniRuntime::GetInstance()->session()->ProvideSecret( ConvertJavaStringToUTF8(env, pin).c_str(), createPair, ConvertJavaStringToUTF8(env, deviceName)); } static void ScheduleRedraw(JNIEnv* env, const JavaParamRef& clazz) { remoting::ChromotingJniRuntime::GetInstance()->session()->RedrawDesktop(); } static void SendMouseEvent(JNIEnv* env, const JavaParamRef& clazz, jint x, jint y, jint whichButton, jboolean buttonDown) { // Button must be within the bounds of the MouseEvent_MouseButton enum. DCHECK(whichButton >= 0 && whichButton < 5); remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseEvent( x, y, static_cast(whichButton), buttonDown); } static void SendMouseWheelEvent(JNIEnv* env, const JavaParamRef& clazz, jint delta_x, jint delta_y) { remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseWheelEvent( delta_x, delta_y); } static jboolean SendKeyEvent(JNIEnv* env, const JavaParamRef& clazz, jint scanCode, jint keyCode, jboolean keyDown) { return remoting::ChromotingJniRuntime::GetInstance()->session()->SendKeyEvent( scanCode, keyCode, keyDown); } static void SendTextEvent(JNIEnv* env, const JavaParamRef& clazz, const JavaParamRef& text) { remoting::ChromotingJniRuntime::GetInstance()->session()->SendTextEvent( ConvertJavaStringToUTF8(env, text)); } static void SendTouchEvent( JNIEnv* env, const JavaParamRef& clazz, jint eventType, const JavaParamRef& touchEventObjectArray) { protocol::TouchEvent touch_event; touch_event.set_event_type( static_cast(eventType)); // Iterate over the elements in the object array and transfer the data from // the java object to a native event object. jsize length = env->GetArrayLength(touchEventObjectArray); DCHECK_GE(length, 0); for (jsize i = 0; i < length; ++i) { protocol::TouchEventPoint* touch_point = touch_event.add_touch_points(); ScopedJavaLocalRef java_touch_event( env, env->GetObjectArrayElement(touchEventObjectArray, i)); JniTouchEventData::CopyTouchPointData(env, java_touch_event, touch_point); } remoting::ChromotingJniRuntime::GetInstance()->session()->SendTouchEvent( touch_event); } static void EnableVideoChannel(JNIEnv* env, const JavaParamRef& clazz, jboolean enable) { remoting::ChromotingJniRuntime::GetInstance()->session()->EnableVideoChannel( enable); } static void OnThirdPartyTokenFetched( JNIEnv* env, const JavaParamRef& clazz, const JavaParamRef& token, const JavaParamRef& shared_secret) { ChromotingJniRuntime* runtime = remoting::ChromotingJniRuntime::GetInstance(); runtime->network_task_runner()->PostTask(FROM_HERE, base::Bind( &ChromotingJniInstance::HandleOnThirdPartyTokenFetched, runtime->session(), ConvertJavaStringToUTF8(env, token), ConvertJavaStringToUTF8(env, shared_secret))); } static void SendExtensionMessage(JNIEnv* env, const JavaParamRef& clazz, const JavaParamRef& type, const JavaParamRef& data) { remoting::ChromotingJniRuntime::GetInstance()->session()->SendClientMessage( ConvertJavaStringToUTF8(env, type), ConvertJavaStringToUTF8(env, data)); } // ChromotingJniRuntime implementation. // static ChromotingJniRuntime* ChromotingJniRuntime::GetInstance() { return base::Singleton::get(); } ChromotingJniRuntime::ChromotingJniRuntime() { // Grab or create the threads. // TODO(nicholss) We could runtime this as a constructor argument when jni // runtime is not no longer a singleton. if (!base::MessageLoop::current()) { VLOG(1) << "Starting main message loop"; // On Android, the UI thread is managed by Java, so we need to attach and // start a special type of message loop to allow Chromium code to run tasks. ui_loop_.reset(new base::MessageLoopForUI()); ui_loop_->Start(); } else { VLOG(1) << "Using existing main message loop"; ui_loop_.reset(base::MessageLoopForUI::current()); } // Pass the main ui loop already attached to be used for creating threads. runtime_ = ChromotingClientRuntime::Create(ui_loop_.get()); } ChromotingJniRuntime::~ChromotingJniRuntime() { // The singleton should only ever be destroyed on the main thread. DCHECK(ui_task_runner()->BelongsToCurrentThread()); // The session must be shut down first, since it depends on our other // components' still being alive. DisconnectFromHost(); base::WaitableEvent done_event(false, false); network_task_runner()->PostTask( FROM_HERE, base::Bind(&ChromotingJniRuntime::DetachFromVmAndSignal, base::Unretained(this), &done_event)); done_event.Wait(); display_task_runner()->PostTask( FROM_HERE, base::Bind(&ChromotingJniRuntime::DetachFromVmAndSignal, base::Unretained(this), &done_event)); done_event.Wait(); base::android::LibraryLoaderExitHook(); base::android::DetachFromVM(); } void ChromotingJniRuntime::ConnectToHost(const std::string& username, const std::string& auth_token, const std::string& host_jid, const std::string& host_id, const std::string& host_pubkey, const std::string& pairing_id, const std::string& pairing_secret, const std::string& capabilities, const std::string& flags) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); DCHECK(!session_.get()); session_ = new ChromotingJniInstance(this, username, auth_token, host_jid, host_id, host_pubkey, pairing_id, pairing_secret, capabilities, flags); } void ChromotingJniRuntime::DisconnectFromHost() { DCHECK(ui_task_runner()->BelongsToCurrentThread()); if (session_.get()) { session_->Disconnect(); session_ = nullptr; } } void ChromotingJniRuntime::OnConnectionState( protocol::ConnectionToHost::State state, protocol::ErrorCode error) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); Java_JniInterface_onConnectionState(env, state, error); } void ChromotingJniRuntime::DisplayAuthenticationPrompt(bool pairing_supported) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); Java_JniInterface_displayAuthenticationPrompt(env, pairing_supported); } void ChromotingJniRuntime::CommitPairingCredentials(const std::string& host, const std::string& id, const std::string& secret) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef j_host = ConvertUTF8ToJavaString(env, host); ScopedJavaLocalRef j_id = ConvertUTF8ToJavaString(env, id); ScopedJavaLocalRef j_secret = ConvertUTF8ToJavaString(env,secret); Java_JniInterface_commitPairingCredentials( env, j_host.obj(), j_id.obj(), j_secret.obj()); } void ChromotingJniRuntime::FetchThirdPartyToken(const std::string& token_url, const std::string& client_id, const std::string& scope) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef j_url = ConvertUTF8ToJavaString(env, token_url); ScopedJavaLocalRef j_client_id = ConvertUTF8ToJavaString(env, client_id); ScopedJavaLocalRef j_scope = ConvertUTF8ToJavaString(env, scope); Java_JniInterface_fetchThirdPartyToken( env, j_url.obj(), j_client_id.obj(), j_scope.obj()); } void ChromotingJniRuntime::SetCapabilities(const std::string& capabilities) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef j_cap = ConvertUTF8ToJavaString(env, capabilities); Java_JniInterface_setCapabilities(env, j_cap.obj()); } void ChromotingJniRuntime::HandleExtensionMessage(const std::string& type, const std::string& message) { DCHECK(ui_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); ScopedJavaLocalRef j_type = ConvertUTF8ToJavaString(env, type); ScopedJavaLocalRef j_message = ConvertUTF8ToJavaString(env, message); Java_JniInterface_handleExtensionMessage(env, j_type.obj(), j_message.obj()); } base::android::ScopedJavaLocalRef ChromotingJniRuntime::NewBitmap( int width, int height) { JNIEnv* env = base::android::AttachCurrentThread(); return Java_JniInterface_newBitmap(env, width, height); } void ChromotingJniRuntime::UpdateFrameBitmap(jobject bitmap) { DCHECK(display_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); Java_JniInterface_setVideoFrame(env, bitmap); } void ChromotingJniRuntime::UpdateCursorShape( const protocol::CursorShapeInfo& cursor_shape) { DCHECK(display_task_runner()->BelongsToCurrentThread()); // const_cast<> is safe as long as the Java updateCursorShape() method copies // the data out of the buffer without mutating it, and doesn't keep any // reference to the buffer afterwards. Unfortunately, there seems to be no way // to create a read-only ByteBuffer from a pointer-to-const. char* data = string_as_array(const_cast(&cursor_shape.data())); int cursor_total_bytes = cursor_shape.width() * cursor_shape.height() * kBytesPerPixel; JNIEnv* env = base::android::AttachCurrentThread(); base::android::ScopedJavaLocalRef buffer(env, env->NewDirectByteBuffer(data, cursor_total_bytes)); Java_JniInterface_updateCursorShape(env, cursor_shape.width(), cursor_shape.height(), cursor_shape.hotspot_x(), cursor_shape.hotspot_y(), buffer.obj()); } void ChromotingJniRuntime::RedrawCanvas() { DCHECK(display_task_runner()->BelongsToCurrentThread()); JNIEnv* env = base::android::AttachCurrentThread(); Java_JniInterface_redrawGraphicsInternal(env); } void ChromotingJniRuntime::DetachFromVmAndSignal(base::WaitableEvent* waiter) { base::android::DetachFromVM(); waiter->Signal(); } } // namespace remoting