| OLD | NEW |
| 1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
| 2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
| 3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
| 4 | 4 |
| 5 package org.chromium.chromoting.jni; | 5 package org.chromium.chromoting.jni; |
| 6 | 6 |
| 7 import android.graphics.Bitmap; | |
| 8 import android.graphics.Point; | |
| 9 import android.os.Looper; | |
| 10 | |
| 11 import org.chromium.base.Log; | |
| 12 import org.chromium.base.annotations.JNINamespace; | 7 import org.chromium.base.annotations.JNINamespace; |
| 13 import org.chromium.chromoting.CapabilityManager; | |
| 14 import org.chromium.chromoting.SessionAuthenticator; | |
| 15 | |
| 16 import java.nio.ByteBuffer; | |
| 17 import java.nio.ByteOrder; | |
| 18 | 8 |
| 19 /** | 9 /** |
| 20 * Class to manage a client connection to the host. This class controls the life
time of the | 10 * Class to manage a client connection to the host. This class controls the life
time of the |
| 21 * corresponding C++ object which implements the connection. A new object should
be created for | 11 * corresponding C++ object which implements the connection. A new object should
be created for |
| 22 * each connection to the host, so that notifications from a connection are alwa
ys sent to the | 12 * each connection to the host, so that notifications from a connection are alwa
ys sent to the |
| 23 * right object. | 13 * right object. |
| 24 */ | 14 */ |
| 25 @JNINamespace("remoting") | 15 @JNINamespace("remoting") |
| 26 public class Client { | 16 public class Client { |
| 27 private static final String TAG = "Chromoting"; | |
| 28 | |
| 29 // Pointer to the C++ object, cast to a |long|. | 17 // Pointer to the C++ object, cast to a |long|. |
| 30 private long mNativeJniClient; | 18 private long mNativeJniClient; |
| 31 | 19 |
| 32 // The global Client instance (may be null). This needs to be a global singl
eton so that the | 20 public void init() { |
| 33 // Client can be passed between Activities. | |
| 34 private static Client sClient; | |
| 35 | |
| 36 // Called on the UI thread. | |
| 37 public Client() { | |
| 38 if (sClient != null) { | |
| 39 throw new RuntimeException("Client instance already created."); | |
| 40 } | |
| 41 | |
| 42 sClient = this; | |
| 43 mNativeJniClient = nativeInit(); | 21 mNativeJniClient = nativeInit(); |
| 44 } | 22 } |
| 45 | 23 |
| 46 private native long nativeInit(); | 24 private native long nativeInit(); |
| 47 | 25 |
| 48 // Called on the UI thread. | |
| 49 public void destroy() { | 26 public void destroy() { |
| 50 if (sClient != null) { | 27 nativeDestroy(mNativeJniClient); |
| 51 disconnectFromHost(); | |
| 52 nativeDestroy(mNativeJniClient); | |
| 53 sClient = null; | |
| 54 } | |
| 55 } | 28 } |
| 56 | 29 |
| 57 private native void nativeDestroy(long nativeJniClient); | 30 private native void nativeDestroy(long nativeJniClient); |
| 58 | |
| 59 /** Returns the current Client instance, or null. */ | |
| 60 public static Client getInstance() { | |
| 61 return sClient; | |
| 62 } | |
| 63 | |
| 64 /** Used for authentication-related UX during connection. Accessed on the UI
thread. */ | |
| 65 private SessionAuthenticator mAuthenticator; | |
| 66 | |
| 67 /** Whether the native code is attempting a connection. Accessed on the UI t
hread. */ | |
| 68 private boolean mConnected; | |
| 69 | |
| 70 /** Notified upon successful connection or disconnection. Accessed on the UI
thread. */ | |
| 71 private ConnectionListener mConnectionListener; | |
| 72 | |
| 73 /** | |
| 74 * Callback invoked on the graphics thread to repaint the desktop. Accessed
on the UI and | |
| 75 * graphics threads. | |
| 76 */ | |
| 77 private Runnable mRedrawCallback; | |
| 78 | |
| 79 /** Bitmap holding a copy of the latest video frame. Accessed on the UI and
graphics threads. */ | |
| 80 private Bitmap mFrameBitmap; | |
| 81 | |
| 82 /** Protects access to {@link mFrameBitmap}. */ | |
| 83 private final Object mFrameLock = new Object(); | |
| 84 | |
| 85 /** Position of cursor hot-spot. Accessed on the graphics thread. */ | |
| 86 private Point mCursorHotspot = new Point(); | |
| 87 | |
| 88 /** Bitmap holding the cursor shape. Accessed on the graphics thread. */ | |
| 89 private Bitmap mCursorBitmap; | |
| 90 | |
| 91 /** Capability Manager through which capabilities and extensions are handled
. */ | |
| 92 private CapabilityManager mCapabilityManager = new CapabilityManager(); | |
| 93 | |
| 94 public CapabilityManager getCapabilityManager() { | |
| 95 return mCapabilityManager; | |
| 96 } | |
| 97 | |
| 98 /** Returns whether the client is connected. */ | |
| 99 public boolean isConnected() { | |
| 100 return mConnected; | |
| 101 } | |
| 102 | |
| 103 /** Attempts to form a connection to the user-selected host. Called on the U
I thread. */ | |
| 104 public void connectToHost(String username, String authToken, String hostJid, | |
| 105 String hostId, String hostPubkey, SessionAuthenticator authenticator
, String flags, | |
| 106 ConnectionListener listener) { | |
| 107 disconnectFromHost(); | |
| 108 | |
| 109 mConnectionListener = listener; | |
| 110 mAuthenticator = authenticator; | |
| 111 JniInterface.nativeConnect(username, authToken, hostJid, hostId, hostPub
key, | |
| 112 mAuthenticator.getPairingId(hostId), mAuthenticator.getPairingSe
cret(hostId), | |
| 113 mCapabilityManager.getLocalCapabilities(), flags); | |
| 114 mConnected = true; | |
| 115 } | |
| 116 | |
| 117 /** Severs the connection and cleans up. Called on the UI thread. */ | |
| 118 public void disconnectFromHost() { | |
| 119 if (!mConnected) { | |
| 120 return; | |
| 121 } | |
| 122 | |
| 123 mConnectionListener.onConnectionState( | |
| 124 ConnectionListener.State.CLOSED, ConnectionListener.Error.OK); | |
| 125 | |
| 126 disconnectFromHostWithoutNotification(); | |
| 127 } | |
| 128 | |
| 129 /** Same as disconnectFromHost() but without notifying the ConnectionListene
r. */ | |
| 130 private void disconnectFromHostWithoutNotification() { | |
| 131 if (!mConnected) { | |
| 132 return; | |
| 133 } | |
| 134 | |
| 135 JniInterface.nativeDisconnect(); | |
| 136 mConnectionListener = null; | |
| 137 mConnected = false; | |
| 138 mCapabilityManager.onHostDisconnect(); | |
| 139 | |
| 140 // Drop the reference to free the Bitmap for GC. | |
| 141 synchronized (mFrameLock) { | |
| 142 mFrameBitmap = null; | |
| 143 } | |
| 144 } | |
| 145 | |
| 146 /** Called by native code whenever the connection status changes. Called on
the UI thread. */ | |
| 147 void onConnectionState(int stateCode, int errorCode) { | |
| 148 ConnectionListener.State state = ConnectionListener.State.fromValue(stat
eCode); | |
| 149 ConnectionListener.Error error = ConnectionListener.Error.fromValue(erro
rCode); | |
| 150 mConnectionListener.onConnectionState(state, error); | |
| 151 if (state == ConnectionListener.State.FAILED || state == ConnectionListe
ner.State.CLOSED) { | |
| 152 // Disconnect from the host here, otherwise the next time connectToH
ost() is called, | |
| 153 // it will try to disconnect, triggering an incorrect status notific
ation. | |
| 154 | |
| 155 // TODO(lambroslambrou): Connection state notifications for separate
sessions should | |
| 156 // go to separate Client instances. Once this is true, we can remove
this line and | |
| 157 // simplify the disconnectFromHost() code. | |
| 158 disconnectFromHostWithoutNotification(); | |
| 159 } | |
| 160 } | |
| 161 | |
| 162 /** | |
| 163 * Called by JniInterface (from native code) to prompt the user to enter a P
IN. Called on the | |
| 164 * UI thread. | |
| 165 */ | |
| 166 void displayAuthenticationPrompt(boolean pairingSupported) { | |
| 167 mAuthenticator.displayAuthenticationPrompt(pairingSupported); | |
| 168 } | |
| 169 | |
| 170 /** | |
| 171 * Called by the SessionAuthenticator after the user enters a PIN. | |
| 172 * @param pin The entered PIN. | |
| 173 * @param createPair Whether to create a new pairing for this client. | |
| 174 * @param deviceName The device name to appear in the pairing registry. Only
used if createPair | |
| 175 * is true. | |
| 176 */ | |
| 177 public void handleAuthenticationResponse( | |
| 178 String pin, boolean createPair, String deviceName) { | |
| 179 assert mConnected; | |
| 180 JniInterface.nativeAuthenticationResponse(pin, createPair, deviceName); | |
| 181 } | |
| 182 | |
| 183 /** | |
| 184 * Called by JniInterface (from native code), to save newly-received pairing
credentials to | |
| 185 * permanent storage. Called on the UI thread. | |
| 186 */ | |
| 187 void commitPairingCredentials(String host, String id, String secret) { | |
| 188 mAuthenticator.commitPairingCredentials(host, id, secret); | |
| 189 } | |
| 190 | |
| 191 /** | |
| 192 * Moves the mouse cursor, possibly while clicking the specified (nonnegativ
e) button. Called | |
| 193 * on the UI thread. | |
| 194 */ | |
| 195 public void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown
) { | |
| 196 if (!mConnected) { | |
| 197 return; | |
| 198 } | |
| 199 | |
| 200 JniInterface.nativeSendMouseEvent(x, y, whichButton, buttonDown); | |
| 201 } | |
| 202 | |
| 203 /** Injects a mouse-wheel event with delta values. Called on the UI thread.
*/ | |
| 204 public void sendMouseWheelEvent(int deltaX, int deltaY) { | |
| 205 if (!mConnected) { | |
| 206 return; | |
| 207 } | |
| 208 | |
| 209 JniInterface.nativeSendMouseWheelEvent(deltaX, deltaY); | |
| 210 } | |
| 211 | |
| 212 /** | |
| 213 * Presses or releases the specified key. Called on the UI thread. If scanCo
de is not zero then | |
| 214 * keyCode is ignored. | |
| 215 */ | |
| 216 public boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) { | |
| 217 if (!mConnected) { | |
| 218 return false; | |
| 219 } | |
| 220 | |
| 221 return JniInterface.nativeSendKeyEvent(scanCode, keyCode, keyDown); | |
| 222 } | |
| 223 | |
| 224 /** Sends TextEvent to the host. Called on the UI thread. */ | |
| 225 public void sendTextEvent(String text) { | |
| 226 if (!mConnected) { | |
| 227 return; | |
| 228 } | |
| 229 | |
| 230 JniInterface.nativeSendTextEvent(text); | |
| 231 } | |
| 232 | |
| 233 /** Sends an array of TouchEvents to the host. Called on the UI thread. */ | |
| 234 public void sendTouchEvent(TouchEventData.EventType eventType, TouchEventDat
a[] data) { | |
| 235 if (!mConnected) { | |
| 236 return; | |
| 237 } | |
| 238 | |
| 239 JniInterface.nativeSendTouchEvent(eventType.value(), data); | |
| 240 } | |
| 241 | |
| 242 /** | |
| 243 * Enables or disables the video channel. Called on the UI thread in respons
e to Activity | |
| 244 * lifecycle events. | |
| 245 */ | |
| 246 public void enableVideoChannel(boolean enable) { | |
| 247 if (!mConnected) { | |
| 248 return; | |
| 249 } | |
| 250 | |
| 251 JniInterface.nativeEnableVideoChannel(enable); | |
| 252 } | |
| 253 | |
| 254 /** | |
| 255 * Sets the redraw callback to the provided functor. Provide a value of null
whenever the | |
| 256 * window is no longer visible so that we don't continue to draw onto it. Ca
lled on the UI | |
| 257 * thread. | |
| 258 */ | |
| 259 public void provideRedrawCallback(Runnable redrawCallback) { | |
| 260 mRedrawCallback = redrawCallback; | |
| 261 } | |
| 262 | |
| 263 /** Forces the native graphics thread to redraw to the canvas. Called on the
UI thread. */ | |
| 264 public boolean redrawGraphics() { | |
| 265 if (!mConnected || mRedrawCallback == null) return false; | |
| 266 | |
| 267 JniInterface.nativeScheduleRedraw(); | |
| 268 return true; | |
| 269 } | |
| 270 | |
| 271 /** | |
| 272 * Called by JniInterface to perform the redrawing callback requested by | |
| 273 * {@link #redrawGraphics}. This is a no-op if the window isn't visible (the
callback is null). | |
| 274 * Called on the graphics thread. | |
| 275 */ | |
| 276 void redrawGraphicsInternal() { | |
| 277 Runnable callback = mRedrawCallback; | |
| 278 if (callback != null) { | |
| 279 callback.run(); | |
| 280 } | |
| 281 } | |
| 282 | |
| 283 /** | |
| 284 * Returns a bitmap of the latest video frame. Called on the native graphics
thread when | |
| 285 * DesktopView is repainted. | |
| 286 */ | |
| 287 public Bitmap getVideoFrame() { | |
| 288 if (Looper.myLooper() == Looper.getMainLooper()) { | |
| 289 Log.w(TAG, "Canvas being redrawn on UI thread"); | |
| 290 } | |
| 291 | |
| 292 synchronized (mFrameLock) { | |
| 293 return mFrameBitmap; | |
| 294 } | |
| 295 } | |
| 296 | |
| 297 /** | |
| 298 * Called by JniInterface (from native code) to set a new video frame. Calle
d on the native | |
| 299 * graphics thread when a new frame is allocated. | |
| 300 */ | |
| 301 void setVideoFrame(Bitmap bitmap) { | |
| 302 if (Looper.myLooper() == Looper.getMainLooper()) { | |
| 303 Log.w(TAG, "Video frame updated on UI thread"); | |
| 304 } | |
| 305 | |
| 306 synchronized (mFrameLock) { | |
| 307 mFrameBitmap = bitmap; | |
| 308 } | |
| 309 } | |
| 310 | |
| 311 /** | |
| 312 * Creates a new Bitmap to hold video frame pixels. Called by JniInterface (
from native code), | |
| 313 * and the returned Bitmap is referenced by native code which writes the dec
oded frame pixels | |
| 314 * to it. | |
| 315 */ | |
| 316 static Bitmap newBitmap(int width, int height) { | |
| 317 return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | |
| 318 } | |
| 319 | |
| 320 /** | |
| 321 * Called by JniInterface (from native code) to update the cursor shape. Thi
s is called on the | |
| 322 * graphics thread when receiving a new cursor shape from the host. | |
| 323 */ | |
| 324 void updateCursorShape( | |
| 325 int width, int height, int hotspotX, int hotspotY, ByteBuffer buffer
) { | |
| 326 mCursorHotspot = new Point(hotspotX, hotspotY); | |
| 327 | |
| 328 int[] data = new int[width * height]; | |
| 329 buffer.order(ByteOrder.LITTLE_ENDIAN); | |
| 330 buffer.asIntBuffer().get(data, 0, data.length); | |
| 331 mCursorBitmap = Bitmap.createBitmap(data, width, height, Bitmap.Config.A
RGB_8888); | |
| 332 } | |
| 333 | |
| 334 /** Position of cursor hotspot within cursor image. Called on the graphics t
hread. */ | |
| 335 public Point getCursorHotspot() { | |
| 336 return mCursorHotspot; | |
| 337 } | |
| 338 | |
| 339 /** Returns the current cursor shape. Called on the graphics thread. */ | |
| 340 public Bitmap getCursorBitmap() { | |
| 341 return mCursorBitmap; | |
| 342 } | |
| 343 | |
| 344 // | |
| 345 // Third Party Authentication | |
| 346 // | |
| 347 | |
| 348 /** | |
| 349 * Called by JniInterface (from native code), to pop up a third party login
page to fetch the | |
| 350 * token required for authentication. | |
| 351 */ | |
| 352 void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) { | |
| 353 mAuthenticator.fetchThirdPartyToken(tokenUrl, clientId, scope); | |
| 354 } | |
| 355 | |
| 356 /** | |
| 357 * Called by the SessionAuthenticator to pass the |token| and |sharedSecret|
to native code to | |
| 358 * continue authentication. | |
| 359 */ | |
| 360 public void onThirdPartyTokenFetched(String token, String sharedSecret) { | |
| 361 if (!mConnected) { | |
| 362 return; | |
| 363 } | |
| 364 | |
| 365 JniInterface.nativeOnThirdPartyTokenFetched(token, sharedSecret); | |
| 366 } | |
| 367 | |
| 368 // | |
| 369 // Host and Client Capabilities | |
| 370 // | |
| 371 | |
| 372 /** | |
| 373 * Called by JniInterface (from native code) to set the list of negotiated c
apabilities between | |
| 374 * host and client. Called on the UI thread. | |
| 375 */ | |
| 376 void setCapabilities(String capabilities) { | |
| 377 mCapabilityManager.setNegotiatedCapabilities(capabilities); | |
| 378 } | |
| 379 | |
| 380 // | |
| 381 // Extension Message Handling | |
| 382 // | |
| 383 | |
| 384 /** | |
| 385 * Called by JniInterface (from native code), to pass on the deconstructed E
xtensionMessage to | |
| 386 * the app. Called on the UI thread. | |
| 387 */ | |
| 388 void handleExtensionMessage(String type, String data) { | |
| 389 mCapabilityManager.onExtensionMessage(type, data); | |
| 390 } | |
| 391 | |
| 392 /** Sends an extension message to the Chromoting host. Called on the UI thre
ad. */ | |
| 393 public void sendExtensionMessage(String type, String data) { | |
| 394 if (!mConnected) { | |
| 395 return; | |
| 396 } | |
| 397 | |
| 398 JniInterface.nativeSendExtensionMessage(type, data); | |
| 399 } | |
| 400 } | 31 } |
| OLD | NEW |