Home | History | Annotate | Download | only in jni
      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