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
.annotations
.CalledByNative
;
14 import org
.chromium
.base
.annotations
.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
);
290 * Presses or releases the specified (nonnegative) key. Called on the UI thread. If scanCode
291 * is not zero then keyCode is ignored.
293 public static boolean sendKeyEvent(int scanCode
, int keyCode
, boolean keyDown
) {
298 return nativeSendKeyEvent(scanCode
, keyCode
, keyDown
);
302 * Passes key press information to the native handling code.
304 private static native boolean nativeSendKeyEvent(int scanCode
, int keyCode
, boolean keyDown
);
306 /** Sends TextEvent to the host. Called on the UI thread. */
307 public static void sendTextEvent(String text
) {
312 nativeSendTextEvent(text
);
315 /** Passes text event information to the native handling code. */
316 private static native void nativeSendTextEvent(String text
);
319 * Enables or disables the video channel. Called on the UI thread in response to Activity
322 public static void enableVideoChannel(boolean enable
) {
327 nativeEnableVideoChannel(enable
);
330 /** Native implementation of enableVideoChannel() */
331 private static native void nativeEnableVideoChannel(boolean enable
);
334 * Sets the redraw callback to the provided functor. Provide a value of null whenever the
335 * window is no longer visible so that we don't continue to draw onto it. Called on the UI
338 public static void provideRedrawCallback(Runnable redrawCallback
) {
339 sRedrawCallback
= redrawCallback
;
342 /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */
343 public static boolean redrawGraphics() {
344 if (!sConnected
|| sRedrawCallback
== null) return false;
346 nativeScheduleRedraw();
350 /** Schedules a redraw on the native graphics thread. */
351 private static native void nativeScheduleRedraw();
354 * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the
358 private static void redrawGraphicsInternal() {
359 Runnable callback
= sRedrawCallback
;
360 if (callback
!= null) {
366 * Returns a bitmap of the latest video frame. Called on the native graphics thread when
367 * DesktopView is repainted.
369 public static Bitmap
getVideoFrame() {
370 if (Looper
.myLooper() == Looper
.getMainLooper()) {
371 Log
.w("jniiface", "Canvas being redrawn on UI thread");
374 synchronized (sFrameLock
) {
380 * Sets a new video frame. Called on the native graphics thread when a new frame is allocated.
383 private static void setVideoFrame(Bitmap bitmap
) {
384 if (Looper
.myLooper() == Looper
.getMainLooper()) {
385 Log
.w("jniiface", "Video frame updated on UI thread");
388 synchronized (sFrameLock
) {
389 sFrameBitmap
= bitmap
;
394 * Creates a new Bitmap to hold video frame pixels. Called by native code which stores a global
395 * reference to the Bitmap and writes the decoded frame pixels to it.
398 private static Bitmap
newBitmap(int width
, int height
) {
399 return Bitmap
.createBitmap(width
, height
, Bitmap
.Config
.ARGB_8888
);
403 * Updates the cursor shape. This is called on the graphics thread when receiving a new cursor
404 * shape from the host.
407 public static void updateCursorShape(int width
, int height
, int hotspotX
, int hotspotY
,
409 sCursorHotspot
= new Point(hotspotX
, hotspotY
);
411 int[] data
= new int[width
* height
];
412 buffer
.order(ByteOrder
.LITTLE_ENDIAN
);
413 buffer
.asIntBuffer().get(data
, 0, data
.length
);
414 sCursorBitmap
= Bitmap
.createBitmap(data
, width
, height
, Bitmap
.Config
.ARGB_8888
);
417 /** Position of cursor hotspot within cursor image. Called on the graphics thread. */
418 public static Point
getCursorHotspot() {
419 return sCursorHotspot
;
422 /** Returns the current cursor shape. Called on the graphics thread. */
423 public static Bitmap
getCursorBitmap() {
424 return sCursorBitmap
;
428 // Third Party Authentication
431 /** Pops up a third party login page to fetch the token required for authentication. */
433 public static void fetchThirdPartyToken(String tokenUrl
, String clientId
, String scope
) {
434 sAuthenticator
.fetchThirdPartyToken(tokenUrl
, clientId
, scope
);
438 * Notify the native code to continue authentication with the |token| and the |sharedSecret|.
440 public static void onThirdPartyTokenFetched(String token
, String sharedSecret
) {
445 nativeOnThirdPartyTokenFetched(token
, sharedSecret
);
448 /** Passes authentication data to the native handling code. */
449 private static native void nativeOnThirdPartyTokenFetched(String token
, String sharedSecret
);
452 // Host and Client Capabilities
455 /** Set the list of negotiated capabilities between host and client. Called on the UI thread. */
457 public static void setCapabilities(String capabilities
) {
458 sCapabilityManager
.setNegotiatedCapabilities(capabilities
);
462 // Extension Message Handling
465 /** Passes on the deconstructed ExtensionMessage to the app. Called on the UI thread. */
467 public static void handleExtensionMessage(String type
, String data
) {
468 sCapabilityManager
.onExtensionMessage(type
, data
);
471 /** Sends an extension message to the Chromoting host. Called on the UI thread. */
472 public static void sendExtensionMessage(String type
, String data
) {
477 nativeSendExtensionMessage(type
, data
);
480 private static native void nativeSendExtensionMessage(String type
, String data
);