blob: 36b423cbeaf9ddd7117ecaa987b9f4929c0aba46 [file] [log] [blame]
// 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 "google_apis/google_api_keys.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<jclass>& clazz) {
// The google_apis functions check the command-line arguments to make sure no
// runtime API keys have been specified by the environment. Unfortunately, we
// neither launch Chromium nor have a command line, so we need to prevent
// them from DCHECKing out when they go looking.
base::CommandLine::Init(0, nullptr);
// Create the singleton now so that the Chromoting threads will be set up.
remoting::ChromotingJniRuntime::GetInstance();
}
static ScopedJavaLocalRef<jstring> GetApiKey(
JNIEnv* env,
const JavaParamRef<jclass>& clazz) {
return ConvertUTF8ToJavaString(env, google_apis::GetAPIKey().c_str());
}
static ScopedJavaLocalRef<jstring> GetClientId(
JNIEnv* env,
const JavaParamRef<jclass>& clazz) {
return ConvertUTF8ToJavaString(
env,
google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING).c_str());
}
static ScopedJavaLocalRef<jstring> GetClientSecret(
JNIEnv* env,
const JavaParamRef<jclass>& clazz) {
return ConvertUTF8ToJavaString(
env,
google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING).c_str());
}
static void Connect(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& username,
const JavaParamRef<jstring>& authToken,
const JavaParamRef<jstring>& hostJid,
const JavaParamRef<jstring>& hostId,
const JavaParamRef<jstring>& hostPubkey,
const JavaParamRef<jstring>& pairId,
const JavaParamRef<jstring>& pairSecret,
const JavaParamRef<jstring>& capabilities) {
remoting::ChromotingJniRuntime::GetInstance()->ConnectToHost(
ConvertJavaStringToUTF8(env, username).c_str(),
ConvertJavaStringToUTF8(env, authToken).c_str(),
ConvertJavaStringToUTF8(env, hostJid).c_str(),
ConvertJavaStringToUTF8(env, hostId).c_str(),
ConvertJavaStringToUTF8(env, hostPubkey).c_str(),
ConvertJavaStringToUTF8(env, pairId).c_str(),
ConvertJavaStringToUTF8(env, pairSecret).c_str(),
ConvertJavaStringToUTF8(env, capabilities).c_str());
}
static void Disconnect(JNIEnv* env, const JavaParamRef<jclass>& clazz) {
remoting::ChromotingJniRuntime::GetInstance()->DisconnectFromHost();
}
static void AuthenticationResponse(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& pin,
jboolean createPair,
const JavaParamRef<jstring>& deviceName) {
remoting::ChromotingJniRuntime::GetInstance()->session()->ProvideSecret(
ConvertJavaStringToUTF8(env, pin).c_str(), createPair,
ConvertJavaStringToUTF8(env, deviceName));
}
static void ScheduleRedraw(JNIEnv* env, const JavaParamRef<jclass>& clazz) {
remoting::ChromotingJniRuntime::GetInstance()->session()->RedrawDesktop();
}
static void SendMouseEvent(JNIEnv* env,
const JavaParamRef<jclass>& 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<remoting::protocol::MouseEvent_MouseButton>(whichButton),
buttonDown);
}
static void SendMouseWheelEvent(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
jint delta_x,
jint delta_y) {
remoting::ChromotingJniRuntime::GetInstance()->session()->SendMouseWheelEvent(
delta_x, delta_y);
}
static jboolean SendKeyEvent(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
jint scanCode,
jint keyCode,
jboolean keyDown) {
return remoting::ChromotingJniRuntime::GetInstance()->session()->SendKeyEvent(
scanCode, keyCode, keyDown);
}
static void SendTextEvent(JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& text) {
remoting::ChromotingJniRuntime::GetInstance()->session()->SendTextEvent(
ConvertJavaStringToUTF8(env, text));
}
static void SendTouchEvent(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
jint eventType,
const JavaParamRef<jobjectArray>& touchEventObjectArray) {
protocol::TouchEvent touch_event;
touch_event.set_event_type(
static_cast<protocol::TouchEvent::TouchEventType>(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<jobject> 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<jclass>& clazz,
jboolean enable) {
remoting::ChromotingJniRuntime::GetInstance()->session()->EnableVideoChannel(
enable);
}
static void OnThirdPartyTokenFetched(
JNIEnv* env,
const JavaParamRef<jclass>& clazz,
const JavaParamRef<jstring>& token,
const JavaParamRef<jstring>& 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<jclass>& clazz,
const JavaParamRef<jstring>& type,
const JavaParamRef<jstring>& data) {
remoting::ChromotingJniRuntime::GetInstance()->session()->SendClientMessage(
ConvertJavaStringToUTF8(env, type),
ConvertJavaStringToUTF8(env, data));
}
// ChromotingJniRuntime implementation.
// static
ChromotingJniRuntime* ChromotingJniRuntime::GetInstance() {
return base::Singleton<ChromotingJniRuntime>::get();
}
ChromotingJniRuntime::ChromotingJniRuntime() {
// 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();
// TODO(solb) Stop pretending to control the managed UI thread's lifetime.
ui_task_runner_ = new AutoThreadTaskRunner(
ui_loop_->task_runner(), base::MessageLoop::QuitWhenIdleClosure());
network_task_runner_ = AutoThread::CreateWithType("native_net",
ui_task_runner_,
base::MessageLoop::TYPE_IO);
display_task_runner_ = AutoThread::Create("native_disp",
ui_task_runner_);
url_requester_ =
new URLRequestContextGetter(network_task_runner_, network_task_runner_);
}
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 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,
const char* capabilities) {
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);
}
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<jstring> j_host = ConvertUTF8ToJavaString(env, host);
ScopedJavaLocalRef<jstring> j_id = ConvertUTF8ToJavaString(env, id);
ScopedJavaLocalRef<jstring> j_secret = ConvertUTF8ToJavaString(env,secret);
Java_JniInterface_commitPairingCredentials(
env, j_host.obj(), j_id.obj(), j_secret.obj());
}
void ChromotingJniRuntime::FetchThirdPartyToken(const GURL& token_url,
const std::string& client_id,
const std::string& scope) {
DCHECK(ui_task_runner_->BelongsToCurrentThread());
JNIEnv* env = base::android::AttachCurrentThread();
ScopedJavaLocalRef<jstring> j_url =
ConvertUTF8ToJavaString(env, token_url.spec());
ScopedJavaLocalRef<jstring> j_client_id =
ConvertUTF8ToJavaString(env, client_id);
ScopedJavaLocalRef<jstring> 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<jstring> 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<jstring> j_type = ConvertUTF8ToJavaString(env, type);
ScopedJavaLocalRef<jstring> j_message = ConvertUTF8ToJavaString(env, message);
Java_JniInterface_handleExtensionMessage(env, j_type.obj(), j_message.obj());
}
base::android::ScopedJavaLocalRef<jobject> 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<std::string*>(&cursor_shape.data()));
int cursor_total_bytes =
cursor_shape.width() * cursor_shape.height() * kBytesPerPixel;
JNIEnv* env = base::android::AttachCurrentThread();
base::android::ScopedJavaLocalRef<jobject> 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