Updating trunk VERSION from 2139.0 to 2140.0
[chromium-blink-merge.git] / remoting / android / java / src / org / chromium / chromoting / jni / JniInterface.java
blob9304ee34165e6f9a6db6d91c6fb5dbd10e9e43b2
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.app.Activity;
8 import android.app.AlertDialog;
9 import android.content.Context;
10 import android.content.DialogInterface;
11 import android.content.SharedPreferences;
12 import android.graphics.Bitmap;
13 import android.graphics.Point;
14 import android.os.Build;
15 import android.os.Looper;
16 import android.util.Log;
17 import android.view.KeyEvent;
18 import android.view.View;
19 import android.widget.CheckBox;
20 import android.widget.TextView;
21 import android.widget.Toast;
23 import org.chromium.base.CalledByNative;
24 import org.chromium.base.JNINamespace;
25 import org.chromium.chromoting.CapabilityManager;
26 import org.chromium.chromoting.Chromoting;
27 import org.chromium.chromoting.R;
29 import java.nio.ByteBuffer;
30 import java.nio.ByteOrder;
32 /**
33 * Initializes the Chromium remoting library, and provides JNI calls into it.
34 * All interaction with the native code is centralized in this class.
36 @JNINamespace("remoting")
37 public class JniInterface {
39 * Library-loading state machine.
41 /** Whether the library has been loaded. Accessed on the UI thread. */
42 private static boolean sLoaded = false;
44 /** The application context. Accessed on the UI thread. */
45 private static Activity sContext = null;
47 /** Interface used for connection state notifications. */
48 public interface ConnectionListener {
49 /**
50 * This enum must match the C++ enumeration remoting::protocol::ConnectionToHost::State.
52 public enum State {
53 INITIALIZING(0),
54 CONNECTING(1),
55 AUTHENTICATED(2),
56 CONNECTED(3),
57 FAILED(4),
58 CLOSED(5);
60 private final int mValue;
62 State(int value) {
63 mValue = value;
66 public int value() {
67 return mValue;
70 public static State fromValue(int value) {
71 return values()[value];
75 /**
76 * This enum must match the C++ enumeration remoting::protocol::ErrorCode.
78 public enum Error {
79 OK(0, 0),
80 PEER_IS_OFFLINE(1, R.string.error_host_is_offline),
81 SESSION_REJECTED(2, R.string.error_invalid_access_code),
82 INCOMPATIBLE_PROTOCOL(3, R.string.error_incompatible_protocol),
83 AUTHENTICATION_FAILED(4, R.string.error_invalid_access_code),
84 CHANNEL_CONNECTION_ERROR(5, R.string.error_p2p_failure),
85 SIGNALING_ERROR(6, R.string.error_p2p_failure),
86 SIGNALING_TIMEOUT(7, R.string.error_p2p_failure),
87 HOST_OVERLOAD(8, R.string.error_host_overload),
88 UNKNOWN_ERROR(9, R.string.error_unexpected);
90 private final int mValue;
91 private final int mMessage;
93 Error(int value, int message) {
94 mValue = value;
95 mMessage = message;
98 public int value() {
99 return mValue;
102 public int message() {
103 return mMessage;
106 public static Error fromValue(int value) {
107 return values()[value];
113 * Notified on connection state change.
114 * @param state The new connection state.
115 * @param error The error code, if state is STATE_FAILED.
117 void onConnectionState(State state, Error error);
121 * Connection-initiating state machine.
123 /** Whether the native code is attempting a connection. Accessed on the UI thread. */
124 private static boolean sConnected = false;
126 /** Notified upon successful connection or disconnection. Accessed on the UI thread. */
127 private static ConnectionListener sConnectionListener = null;
130 * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and
131 * graphics threads.
133 private static Runnable sRedrawCallback = null;
135 /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */
136 private static Bitmap sFrameBitmap = null;
138 /** Protects access to sFrameBitmap. */
139 private static final Object sFrameLock = new Object();
141 /** Position of cursor hot-spot. Accessed on the graphics thread. */
142 private static Point sCursorHotspot = new Point();
144 /** Bitmap holding the cursor shape. Accessed on the graphics thread. */
145 private static Bitmap sCursorBitmap = null;
147 /** Capability Manager through which capabilities and extensions are handled. */
148 private static CapabilityManager sCapabilityManager = CapabilityManager.getInstance();
151 * To be called once from the main Activity. Any subsequent calls will update the application
152 * context, but not reload the library. This is useful e.g. when the activity is closed and the
153 * user later wants to return to the application. Called on the UI thread.
155 public static void loadLibrary(Activity context) {
156 sContext = context;
158 if (sLoaded) return;
160 System.loadLibrary("remoting_client_jni");
162 nativeLoadNative(context);
163 sLoaded = true;
166 /** Performs the native portion of the initialization. */
167 private static native void nativeLoadNative(Context context);
170 * API/OAuth2 keys access.
172 public static native String nativeGetApiKey();
173 public static native String nativeGetClientId();
174 public static native String nativeGetClientSecret();
176 /** Attempts to form a connection to the user-selected host. Called on the UI thread. */
177 public static void connectToHost(String username, String authToken,
178 String hostJid, String hostId, String hostPubkey, ConnectionListener listener) {
179 disconnectFromHost();
181 sConnectionListener = listener;
182 SharedPreferences prefs = sContext.getPreferences(Activity.MODE_PRIVATE);
183 nativeConnect(username, authToken, hostJid, hostId, hostPubkey,
184 prefs.getString(hostId + "_id", ""), prefs.getString(hostId + "_secret", ""),
185 sCapabilityManager.getLocalCapabilities());
186 sConnected = true;
189 /** Performs the native portion of the connection. */
190 private static native void nativeConnect(String username, String authToken, String hostJid,
191 String hostId, String hostPubkey, String pairId, String pairSecret,
192 String capabilities);
194 /** Severs the connection and cleans up. Called on the UI thread. */
195 public static void disconnectFromHost() {
196 if (!sConnected) {
197 return;
200 sConnectionListener.onConnectionState(ConnectionListener.State.CLOSED,
201 ConnectionListener.Error.OK);
203 disconnectFromHostWithoutNotification();
206 /** Same as disconnectFromHost() but without notifying the ConnectionListener. */
207 private static void disconnectFromHostWithoutNotification() {
208 if (!sConnected) {
209 return;
212 nativeDisconnect();
213 sConnectionListener = null;
214 sConnected = false;
216 // Drop the reference to free the Bitmap for GC.
217 synchronized (sFrameLock) {
218 sFrameBitmap = null;
222 /** Performs the native portion of the cleanup. */
223 private static native void nativeDisconnect();
225 /** Called by native code whenever the connection status changes. Called on the UI thread. */
226 @CalledByNative
227 private static void onConnectionState(int stateCode, int errorCode) {
228 ConnectionListener.State state = ConnectionListener.State.fromValue(stateCode);
229 ConnectionListener.Error error = ConnectionListener.Error.fromValue(errorCode);
230 sConnectionListener.onConnectionState(state, error);
231 if (state == ConnectionListener.State.FAILED || state == ConnectionListener.State.CLOSED) {
232 // Disconnect from the host here, otherwise the next time connectToHost() is called,
233 // it will try to disconnect, triggering an incorrect status notification.
234 disconnectFromHostWithoutNotification();
238 /** Prompts the user to enter a PIN. Called on the UI thread. */
239 @CalledByNative
240 private static void displayAuthenticationPrompt(boolean pairingSupported) {
241 AlertDialog.Builder pinPrompt = new AlertDialog.Builder(sContext);
242 pinPrompt.setTitle(sContext.getString(R.string.title_authenticate));
243 pinPrompt.setMessage(sContext.getString(R.string.pin_message_android));
244 pinPrompt.setIcon(android.R.drawable.ic_lock_lock);
246 final View pinEntry = sContext.getLayoutInflater().inflate(R.layout.pin_dialog, null);
247 pinPrompt.setView(pinEntry);
249 final TextView pinTextView = (TextView)pinEntry.findViewById(R.id.pin_dialog_text);
250 final CheckBox pinCheckBox = (CheckBox)pinEntry.findViewById(R.id.pin_dialog_check);
252 if (!pairingSupported) {
253 pinCheckBox.setChecked(false);
254 pinCheckBox.setVisibility(View.GONE);
257 pinPrompt.setPositiveButton(
258 R.string.connect_button, new DialogInterface.OnClickListener() {
259 @Override
260 public void onClick(DialogInterface dialog, int which) {
261 Log.i("jniiface", "User provided a PIN code");
262 if (sConnected) {
263 nativeAuthenticationResponse(String.valueOf(pinTextView.getText()),
264 pinCheckBox.isChecked(), Build.MODEL);
265 } else {
266 String message = sContext.getString(R.string.error_network_error);
267 Toast.makeText(sContext, message, Toast.LENGTH_LONG).show();
272 pinPrompt.setNegativeButton(
273 R.string.cancel, new DialogInterface.OnClickListener() {
274 @Override
275 public void onClick(DialogInterface dialog, int which) {
276 Log.i("jniiface", "User canceled pin entry prompt");
277 disconnectFromHost();
281 final AlertDialog pinDialog = pinPrompt.create();
283 pinTextView.setOnEditorActionListener(
284 new TextView.OnEditorActionListener() {
285 @Override
286 public boolean onEditorAction(TextView v, int actionId, KeyEvent event) {
287 // The user pressed enter on the keypad (equivalent to the connect button).
288 pinDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick();
289 pinDialog.dismiss();
290 return true;
294 pinDialog.setOnCancelListener(
295 new DialogInterface.OnCancelListener() {
296 @Override
297 public void onCancel(DialogInterface dialog) {
298 // The user backed out of the dialog (equivalent to the cancel button).
299 pinDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick();
303 pinDialog.show();
307 * Performs the native response to the user's PIN.
308 * @param pin The entered PIN.
309 * @param createPair Whether to create a new pairing for this client.
310 * @param deviceName The device name to appear in the pairing registry. Only used if createPair
311 * is true.
313 private static native void nativeAuthenticationResponse(String pin, boolean createPair,
314 String deviceName);
316 /** Saves newly-received pairing credentials to permanent storage. Called on the UI thread. */
317 @CalledByNative
318 private static void commitPairingCredentials(String host, String id, String secret) {
319 // Empty |id| indicates that pairing needs to be removed.
320 if (id.isEmpty()) {
321 sContext.getPreferences(Activity.MODE_PRIVATE).edit().
322 remove(host + "_id").
323 remove(host + "_secret").
324 apply();
325 } else {
326 sContext.getPreferences(Activity.MODE_PRIVATE).edit().
327 putString(host + "_id", id).
328 putString(host + "_secret", secret).
329 apply();
334 * Moves the mouse cursor, possibly while clicking the specified (nonnegative) button. Called
335 * on the UI thread.
337 public static void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) {
338 if (!sConnected) {
339 return;
342 nativeSendMouseEvent(x, y, whichButton, buttonDown);
345 /** Passes mouse information to the native handling code. */
346 private static native void nativeSendMouseEvent(int x, int y, int whichButton,
347 boolean buttonDown);
349 /** Injects a mouse-wheel event with delta values. Called on the UI thread. */
350 public static void sendMouseWheelEvent(int deltaX, int deltaY) {
351 if (!sConnected) {
352 return;
355 nativeSendMouseWheelEvent(deltaX, deltaY);
358 /** Passes mouse-wheel information to the native handling code. */
359 private static native void nativeSendMouseWheelEvent(int deltaX, int deltaY);
361 /** Presses or releases the specified (nonnegative) key. Called on the UI thread. */
362 public static boolean sendKeyEvent(int keyCode, boolean keyDown) {
363 if (!sConnected) {
364 return false;
367 return nativeSendKeyEvent(keyCode, keyDown);
370 /** Passes key press information to the native handling code. */
371 private static native boolean nativeSendKeyEvent(int keyCode, boolean keyDown);
373 /** Sends TextEvent to the host. Called on the UI thread. */
374 public static void sendTextEvent(String text) {
375 if (!sConnected) {
376 return;
379 nativeSendTextEvent(text);
382 /** Passes text event information to the native handling code. */
383 private static native void nativeSendTextEvent(String text);
386 * Sets the redraw callback to the provided functor. Provide a value of null whenever the
387 * window is no longer visible so that we don't continue to draw onto it. Called on the UI
388 * thread.
390 public static void provideRedrawCallback(Runnable redrawCallback) {
391 sRedrawCallback = redrawCallback;
394 /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */
395 public static boolean redrawGraphics() {
396 if (!sConnected || sRedrawCallback == null) return false;
398 nativeScheduleRedraw();
399 return true;
402 /** Schedules a redraw on the native graphics thread. */
403 private static native void nativeScheduleRedraw();
406 * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the
407 * graphics thread.
409 @CalledByNative
410 private static void redrawGraphicsInternal() {
411 Runnable callback = sRedrawCallback;
412 if (callback != null) {
413 callback.run();
418 * Returns a bitmap of the latest video frame. Called on the native graphics thread when
419 * DesktopView is repainted.
421 public static Bitmap getVideoFrame() {
422 if (Looper.myLooper() == Looper.getMainLooper()) {
423 Log.w("jniiface", "Canvas being redrawn on UI thread");
426 synchronized (sFrameLock) {
427 return sFrameBitmap;
432 * Sets a new video frame. Called on the native graphics thread when a new frame is allocated.
434 @CalledByNative
435 private static void setVideoFrame(Bitmap bitmap) {
436 if (Looper.myLooper() == Looper.getMainLooper()) {
437 Log.w("jniiface", "Video frame updated on UI thread");
440 synchronized (sFrameLock) {
441 sFrameBitmap = bitmap;
446 * Creates a new Bitmap to hold video frame pixels. Called by native code which stores a global
447 * reference to the Bitmap and writes the decoded frame pixels to it.
449 @CalledByNative
450 private static Bitmap newBitmap(int width, int height) {
451 return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
455 * Updates the cursor shape. This is called on the graphics thread when receiving a new cursor
456 * shape from the host.
458 @CalledByNative
459 public static void updateCursorShape(int width, int height, int hotspotX, int hotspotY,
460 ByteBuffer buffer) {
461 sCursorHotspot = new Point(hotspotX, hotspotY);
463 int[] data = new int[width * height];
464 buffer.order(ByteOrder.LITTLE_ENDIAN);
465 buffer.asIntBuffer().get(data, 0, data.length);
466 sCursorBitmap = Bitmap.createBitmap(data, width, height, Bitmap.Config.ARGB_8888);
469 /** Position of cursor hotspot within cursor image. Called on the graphics thread. */
470 public static Point getCursorHotspot() { return sCursorHotspot; }
472 /** Returns the current cursor shape. Called on the graphics thread. */
473 public static Bitmap getCursorBitmap() { return sCursorBitmap; }
476 // Third Party Authentication
479 /** Pops up a third party login page to fetch the token required for authentication. */
480 @CalledByNative
481 public static void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) {
482 Chromoting app = (Chromoting) sContext;
483 app.fetchThirdPartyToken(tokenUrl, clientId, scope);
487 * Notify the native code to continue authentication with the |token| and the |sharedSecret|.
489 public static void onThirdPartyTokenFetched(String token, String sharedSecret) {
490 if (!sConnected) {
491 return;
494 nativeOnThirdPartyTokenFetched(token, sharedSecret);
497 /** Passes authentication data to the native handling code. */
498 private static native void nativeOnThirdPartyTokenFetched(String token, String sharedSecret);
501 // Host and Client Capabilities
504 /** Set the list of negotiated capabilities between host and client. Called on the UI thread. */
505 @CalledByNative
506 public static void setCapabilities(String capabilities) {
507 sCapabilityManager.setNegotiatedCapabilities(capabilities);
511 // Extension Message Handling
514 /** Passes on the deconstructed ExtensionMessage to the app. Called on the UI thread. */
515 @CalledByNative
516 public static void handleExtensionMessage(String type, String data) {
517 sCapabilityManager.onExtensionMessage(type, data);
520 /** Sends an extension message to the Chromoting host. Called on the UI thread. */
521 public static void sendExtensionMessage(String type, String data) {
522 if (!sConnected) {
523 return;
526 nativeSendExtensionMessage(type, data);
529 private static native void nativeSendExtensionMessage(String type, String data);