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.
5 package org
.chromium
.chromoting
.jni
;
7 import android
.content
.Context
;
8 import android
.graphics
.Bitmap
;
9 import android
.graphics
.Point
;
10 import android
.os
.Looper
;
11 import android
.util
.Log
;
13 import org
.chromium
.base
.CalledByNative
;
14 import org
.chromium
.base
.JNINamespace
;
15 import org
.chromium
.chromoting
.CapabilityManager
;
16 import org
.chromium
.chromoting
.R
;
17 import org
.chromium
.chromoting
.SessionAuthenticator
;
19 import java
.nio
.ByteBuffer
;
20 import java
.nio
.ByteOrder
;
23 * Initializes the Chromium remoting library, and provides JNI calls into it.
24 * All interaction with the native code is centralized in this class.
26 @JNINamespace("remoting")
27 public class JniInterface
{
29 * Library-loading state machine.
31 /** Whether the library has been loaded. Accessed on the UI thread. */
32 private static boolean sLoaded
= false;
34 /** Used for authentication-related UX during connection. Accessed on the UI thread. */
35 private static SessionAuthenticator sAuthenticator
;
37 /** Interface used for connection state notifications. */
38 public interface ConnectionListener
{
40 * This enum must match the C++ enumeration remoting::protocol::ConnectionToHost::State.
50 private final int mValue
;
60 public static State
fromValue(int value
) {
61 return values()[value
];
66 * This enum must match the C++ enumeration remoting::protocol::ErrorCode.
70 PEER_IS_OFFLINE(1, R
.string
.error_host_is_offline
),
71 SESSION_REJECTED(2, R
.string
.error_invalid_access_code
),
72 INCOMPATIBLE_PROTOCOL(3, R
.string
.error_incompatible_protocol
),
73 AUTHENTICATION_FAILED(4, R
.string
.error_invalid_access_code
),
74 CHANNEL_CONNECTION_ERROR(5, R
.string
.error_p2p_failure
),
75 SIGNALING_ERROR(6, R
.string
.error_p2p_failure
),
76 SIGNALING_TIMEOUT(7, R
.string
.error_p2p_failure
),
77 HOST_OVERLOAD(8, R
.string
.error_host_overload
),
78 UNKNOWN_ERROR(9, R
.string
.error_unexpected
);
80 private final int mValue
;
81 private final int mMessage
;
83 Error(int value
, int message
) {
92 public int message() {
96 public static Error
fromValue(int value
) {
97 return values()[value
];
103 * Notified on connection state change.
104 * @param state The new connection state.
105 * @param error The error code, if state is STATE_FAILED.
107 void onConnectionState(State state
, Error error
);
111 * Connection-initiating state machine.
113 /** Whether the native code is attempting a connection. Accessed on the UI thread. */
114 private static boolean sConnected
= false;
116 /** Notified upon successful connection or disconnection. Accessed on the UI thread. */
117 private static ConnectionListener sConnectionListener
= null;
120 * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and
123 private static Runnable sRedrawCallback
= null;
125 /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */
126 private static Bitmap sFrameBitmap
= null;
128 /** Protects access to sFrameBitmap. */
129 private static final Object sFrameLock
= new Object();
131 /** Position of cursor hot-spot. Accessed on the graphics thread. */
132 private static Point sCursorHotspot
= new Point();
134 /** Bitmap holding the cursor shape. Accessed on the graphics thread. */
135 private static Bitmap sCursorBitmap
= null;
137 /** Capability Manager through which capabilities and extensions are handled. */
138 private static CapabilityManager sCapabilityManager
= CapabilityManager
.getInstance();
141 * To be called once from the main Activity. Any subsequent calls will update the application
142 * context, but not reload the library. This is useful e.g. when the activity is closed and the
143 * user later wants to return to the application. Called on the UI thread.
145 public static void loadLibrary(Context context
) {
148 System
.loadLibrary("remoting_client_jni");
150 nativeLoadNative(context
);
154 /** Performs the native portion of the initialization. */
155 private static native void nativeLoadNative(Context context
);
158 * API/OAuth2 keys access.
160 public static native String
nativeGetApiKey();
161 public static native String
nativeGetClientId();
162 public static native String
nativeGetClientSecret();
164 /** Returns whether the client is connected. */
165 public static boolean isConnected() {
169 /** Attempts to form a connection to the user-selected host. Called on the UI thread. */
170 public static void connectToHost(String username
, String authToken
,
171 String hostJid
, String hostId
, String hostPubkey
, ConnectionListener listener
,
172 SessionAuthenticator authenticator
) {
173 disconnectFromHost();
175 sConnectionListener
= listener
;
176 sAuthenticator
= authenticator
;
177 nativeConnect(username
, authToken
, hostJid
, hostId
, hostPubkey
,
178 sAuthenticator
.getPairingId(hostId
), sAuthenticator
.getPairingSecret(hostId
),
179 sCapabilityManager
.getLocalCapabilities());
183 /** Performs the native portion of the connection. */
184 private static native void nativeConnect(String username
, String authToken
, String hostJid
,
185 String hostId
, String hostPubkey
, String pairId
, String pairSecret
,
186 String capabilities
);
188 /** Severs the connection and cleans up. Called on the UI thread. */
189 public static void disconnectFromHost() {
194 sConnectionListener
.onConnectionState(ConnectionListener
.State
.CLOSED
,
195 ConnectionListener
.Error
.OK
);
197 disconnectFromHostWithoutNotification();
200 /** Same as disconnectFromHost() but without notifying the ConnectionListener. */
201 private static void disconnectFromHostWithoutNotification() {
207 sConnectionListener
= null;
210 // Drop the reference to free the Bitmap for GC.
211 synchronized (sFrameLock
) {
216 /** Performs the native portion of the cleanup. */
217 private static native void nativeDisconnect();
219 /** Called by native code whenever the connection status changes. Called on the UI thread. */
221 private static void onConnectionState(int stateCode
, int errorCode
) {
222 ConnectionListener
.State state
= ConnectionListener
.State
.fromValue(stateCode
);
223 ConnectionListener
.Error error
= ConnectionListener
.Error
.fromValue(errorCode
);
224 sConnectionListener
.onConnectionState(state
, error
);
225 if (state
== ConnectionListener
.State
.FAILED
|| state
== ConnectionListener
.State
.CLOSED
) {
226 // Disconnect from the host here, otherwise the next time connectToHost() is called,
227 // it will try to disconnect, triggering an incorrect status notification.
228 disconnectFromHostWithoutNotification();
232 /** Prompts the user to enter a PIN. Called on the UI thread. */
234 private static void displayAuthenticationPrompt(boolean pairingSupported
) {
235 sAuthenticator
.displayAuthenticationPrompt(pairingSupported
);
239 * Performs the native response to the user's PIN.
240 * @param pin The entered PIN.
241 * @param createPair Whether to create a new pairing for this client.
242 * @param deviceName The device name to appear in the pairing registry. Only used if createPair
245 public static void handleAuthenticationResponse(String pin
, boolean createPair
,
248 nativeAuthenticationResponse(pin
, createPair
, deviceName
);
251 /** Native implementation of handleAuthenticationResponse(). */
252 private static native void nativeAuthenticationResponse(String pin
, boolean createPair
,
255 /** Saves newly-received pairing credentials to permanent storage. Called on the UI thread. */
257 private static void commitPairingCredentials(String host
, String id
, String secret
) {
258 sAuthenticator
.commitPairingCredentials(host
, id
, secret
);
262 * Moves the mouse cursor, possibly while clicking the specified (nonnegative) button. Called
265 public static void sendMouseEvent(int x
, int y
, int whichButton
, boolean buttonDown
) {
270 nativeSendMouseEvent(x
, y
, whichButton
, buttonDown
);
273 /** Passes mouse information to the native handling code. */
274 private static native void nativeSendMouseEvent(int x
, int y
, int whichButton
,
277 /** Injects a mouse-wheel event with delta values. Called on the UI thread. */
278 public static void sendMouseWheelEvent(int deltaX
, int deltaY
) {
283 nativeSendMouseWheelEvent(deltaX
, deltaY
);
286 /** Passes mouse-wheel information to the native handling code. */
287 private static native void nativeSendMouseWheelEvent(int deltaX
, int deltaY
);
289 /** Presses or releases the specified (nonnegative) key. Called on the UI thread. */
290 public static boolean sendKeyEvent(int keyCode
, boolean keyDown
) {
295 return nativeSendKeyEvent(keyCode
, keyDown
);
298 /** Passes key press information to the native handling code. */
299 private static native boolean nativeSendKeyEvent(int keyCode
, boolean keyDown
);
301 /** Sends TextEvent to the host. Called on the UI thread. */
302 public static void sendTextEvent(String text
) {
307 nativeSendTextEvent(text
);
310 /** Passes text event information to the native handling code. */
311 private static native void nativeSendTextEvent(String text
);
314 * Enables or disables the video channel. Called on the UI thread in response to Activity
317 public static void enableVideoChannel(boolean enable
) {
322 nativeEnableVideoChannel(enable
);
325 /** Native implementation of enableVideoChannel() */
326 private static native void nativeEnableVideoChannel(boolean enable
);
329 * Sets the redraw callback to the provided functor. Provide a value of null whenever the
330 * window is no longer visible so that we don't continue to draw onto it. Called on the UI
333 public static void provideRedrawCallback(Runnable redrawCallback
) {
334 sRedrawCallback
= redrawCallback
;
337 /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */
338 public static boolean redrawGraphics() {
339 if (!sConnected
|| sRedrawCallback
== null) return false;
341 nativeScheduleRedraw();
345 /** Schedules a redraw on the native graphics thread. */
346 private static native void nativeScheduleRedraw();
349 * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the
353 private static void redrawGraphicsInternal() {
354 Runnable callback
= sRedrawCallback
;
355 if (callback
!= null) {
361 * Returns a bitmap of the latest video frame. Called on the native graphics thread when
362 * DesktopView is repainted.
364 public static Bitmap
getVideoFrame() {
365 if (Looper
.myLooper() == Looper
.getMainLooper()) {
366 Log
.w("jniiface", "Canvas being redrawn on UI thread");
369 synchronized (sFrameLock
) {
375 * Sets a new video frame. Called on the native graphics thread when a new frame is allocated.
378 private static void setVideoFrame(Bitmap bitmap
) {
379 if (Looper
.myLooper() == Looper
.getMainLooper()) {
380 Log
.w("jniiface", "Video frame updated on UI thread");
383 synchronized (sFrameLock
) {
384 sFrameBitmap
= bitmap
;
389 * Creates a new Bitmap to hold video frame pixels. Called by native code which stores a global
390 * reference to the Bitmap and writes the decoded frame pixels to it.
393 private static Bitmap
newBitmap(int width
, int height
) {
394 return Bitmap
.createBitmap(width
, height
, Bitmap
.Config
.ARGB_8888
);
398 * Updates the cursor shape. This is called on the graphics thread when receiving a new cursor
399 * shape from the host.
402 public static void updateCursorShape(int width
, int height
, int hotspotX
, int hotspotY
,
404 sCursorHotspot
= new Point(hotspotX
, hotspotY
);
406 int[] data
= new int[width
* height
];
407 buffer
.order(ByteOrder
.LITTLE_ENDIAN
);
408 buffer
.asIntBuffer().get(data
, 0, data
.length
);
409 sCursorBitmap
= Bitmap
.createBitmap(data
, width
, height
, Bitmap
.Config
.ARGB_8888
);
412 /** Position of cursor hotspot within cursor image. Called on the graphics thread. */
413 public static Point
getCursorHotspot() {
414 return sCursorHotspot
;
417 /** Returns the current cursor shape. Called on the graphics thread. */
418 public static Bitmap
getCursorBitmap() {
419 return sCursorBitmap
;
423 // Third Party Authentication
426 /** Pops up a third party login page to fetch the token required for authentication. */
428 public static void fetchThirdPartyToken(String tokenUrl
, String clientId
, String scope
) {
429 sAuthenticator
.fetchThirdPartyToken(tokenUrl
, clientId
, scope
);
433 * Notify the native code to continue authentication with the |token| and the |sharedSecret|.
435 public static void onThirdPartyTokenFetched(String token
, String sharedSecret
) {
440 nativeOnThirdPartyTokenFetched(token
, sharedSecret
);
443 /** Passes authentication data to the native handling code. */
444 private static native void nativeOnThirdPartyTokenFetched(String token
, String sharedSecret
);
447 // Host and Client Capabilities
450 /** Set the list of negotiated capabilities between host and client. Called on the UI thread. */
452 public static void setCapabilities(String capabilities
) {
453 sCapabilityManager
.setNegotiatedCapabilities(capabilities
);
457 // Extension Message Handling
460 /** Passes on the deconstructed ExtensionMessage to the app. Called on the UI thread. */
462 public static void handleExtensionMessage(String type
, String data
) {
463 sCapabilityManager
.onExtensionMessage(type
, data
);
466 /** Sends an extension message to the Chromoting host. Called on the UI thread. */
467 public static void sendExtensionMessage(String type
, String data
) {
472 nativeSendExtensionMessage(type
, data
);
475 private static native void nativeSendExtensionMessage(String type
, String data
);