Upstream version 9.38.198.0
[platform/framework/web/crosswalk.git] / src / remoting / android / java / src / org / chromium / chromoting / jni / JniInterface.java
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 package org.chromium.chromoting.jni;
6
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;
22
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;
28
29 import java.nio.ByteBuffer;
30 import java.nio.ByteOrder;
31
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.
35  */
36 @JNINamespace("remoting")
37 public class JniInterface {
38     /*
39      * Library-loading state machine.
40      */
41     /** Whether the library has been loaded. Accessed on the UI thread. */
42     private static boolean sLoaded = false;
43
44     /** The application context. Accessed on the UI thread. */
45     private static Activity sContext = null;
46
47     /** Interface used for connection state notifications. */
48     public interface ConnectionListener {
49         /**
50          * This enum must match the C++ enumeration remoting::protocol::ConnectionToHost::State.
51          */
52         public enum State {
53             INITIALIZING(0),
54             CONNECTING(1),
55             AUTHENTICATED(2),
56             CONNECTED(3),
57             FAILED(4),
58             CLOSED(5);
59
60             private final int mValue;
61
62             State(int value) {
63                 mValue = value;
64             }
65
66             public int value() {
67                 return mValue;
68             }
69
70             public static State fromValue(int value) {
71                 return values()[value];
72             }
73         }
74
75         /**
76          * This enum must match the C++ enumeration remoting::protocol::ErrorCode.
77          */
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);
89
90             private final int mValue;
91             private final int mMessage;
92
93             Error(int value, int message) {
94                 mValue = value;
95                 mMessage = message;
96             }
97
98             public int value() {
99                 return mValue;
100             }
101
102             public int message() {
103                 return mMessage;
104             }
105
106             public static Error fromValue(int value) {
107                 return values()[value];
108             }
109         }
110
111
112         /**
113          * Notified on connection state change.
114          * @param state The new connection state.
115          * @param error The error code, if state is STATE_FAILED.
116          */
117         void onConnectionState(State state, Error error);
118     }
119
120     /*
121      * Connection-initiating state machine.
122      */
123     /** Whether the native code is attempting a connection. Accessed on the UI thread. */
124     private static boolean sConnected = false;
125
126     /** Notified upon successful connection or disconnection. Accessed on the UI thread. */
127     private static ConnectionListener sConnectionListener = null;
128
129     /**
130      * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and
131      * graphics threads.
132      */
133     private static Runnable sRedrawCallback = null;
134
135     /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */
136     private static Bitmap sFrameBitmap = null;
137
138     /** Protects access to sFrameBitmap. */
139     private static final Object sFrameLock = new Object();
140
141     /** Position of cursor hot-spot. Accessed on the graphics thread. */
142     private static Point sCursorHotspot = new Point();
143
144     /** Bitmap holding the cursor shape. Accessed on the graphics thread. */
145     private static Bitmap sCursorBitmap = null;
146
147     /** Capability Manager through which capabilities and extensions are handled. */
148     private static CapabilityManager sCapabilityManager = CapabilityManager.getInstance();
149
150     /**
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.
154      */
155     public static void loadLibrary(Activity context) {
156         sContext = context;
157
158         if (sLoaded) return;
159
160         System.loadLibrary("remoting_client_jni");
161
162         nativeLoadNative(context);
163         sLoaded = true;
164     }
165
166     /** Performs the native portion of the initialization. */
167     private static native void nativeLoadNative(Context context);
168
169     /*
170      * API/OAuth2 keys access.
171      */
172     public static native String nativeGetApiKey();
173     public static native String nativeGetClientId();
174     public static native String nativeGetClientSecret();
175
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();
180
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;
187     }
188
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);
193
194     /** Severs the connection and cleans up. Called on the UI thread. */
195     public static void disconnectFromHost() {
196         if (!sConnected) {
197             return;
198         }
199
200         sConnectionListener.onConnectionState(ConnectionListener.State.CLOSED,
201                 ConnectionListener.Error.OK);
202
203         disconnectFromHostWithoutNotification();
204     }
205
206     /** Same as disconnectFromHost() but without notifying the ConnectionListener. */
207     private static void disconnectFromHostWithoutNotification() {
208         if (!sConnected) {
209             return;
210         }
211
212         nativeDisconnect();
213         sConnectionListener = null;
214         sConnected = false;
215
216         // Drop the reference to free the Bitmap for GC.
217         synchronized (sFrameLock) {
218             sFrameBitmap = null;
219         }
220     }
221
222     /** Performs the native portion of the cleanup. */
223     private static native void nativeDisconnect();
224
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();
235         }
236     }
237
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);
245
246         final View pinEntry = sContext.getLayoutInflater().inflate(R.layout.pin_dialog, null);
247         pinPrompt.setView(pinEntry);
248
249         final TextView pinTextView = (TextView)pinEntry.findViewById(R.id.pin_dialog_text);
250         final CheckBox pinCheckBox = (CheckBox)pinEntry.findViewById(R.id.pin_dialog_check);
251
252         if (!pairingSupported) {
253             pinCheckBox.setChecked(false);
254             pinCheckBox.setVisibility(View.GONE);
255         }
256
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();
268                         }
269                     }
270                 });
271
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();
278                     }
279                 });
280
281         final AlertDialog pinDialog = pinPrompt.create();
282
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;
291                     }
292                 });
293
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();
300                     }
301                 });
302
303         pinDialog.show();
304     }
305
306     /**
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.
312      */
313     private static native void nativeAuthenticationResponse(String pin, boolean createPair,
314             String deviceName);
315
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();
330         }
331     }
332
333     /**
334      * Moves the mouse cursor, possibly while clicking the specified (nonnegative) button. Called
335      * on the UI thread.
336      */
337     public static void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) {
338         if (!sConnected) {
339             return;
340         }
341
342         nativeSendMouseEvent(x, y, whichButton, buttonDown);
343     }
344
345     /** Passes mouse information to the native handling code. */
346     private static native void nativeSendMouseEvent(int x, int y, int whichButton,
347             boolean buttonDown);
348
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;
353         }
354
355         nativeSendMouseWheelEvent(deltaX, deltaY);
356     }
357
358     /** Passes mouse-wheel information to the native handling code. */
359     private static native void nativeSendMouseWheelEvent(int deltaX, int deltaY);
360
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;
365         }
366
367         return nativeSendKeyEvent(keyCode, keyDown);
368     }
369
370     /** Passes key press information to the native handling code. */
371     private static native boolean nativeSendKeyEvent(int keyCode, boolean keyDown);
372
373     /** Sends TextEvent to the host. Called on the UI thread. */
374     public static void sendTextEvent(String text) {
375         if (!sConnected) {
376             return;
377         }
378
379         nativeSendTextEvent(text);
380     }
381
382     /** Passes text event information to the native handling code. */
383     private static native void nativeSendTextEvent(String text);
384
385     /**
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.
389      */
390     public static void provideRedrawCallback(Runnable redrawCallback) {
391         sRedrawCallback = redrawCallback;
392     }
393
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;
397
398         nativeScheduleRedraw();
399         return true;
400     }
401
402     /** Schedules a redraw on the native graphics thread. */
403     private static native void nativeScheduleRedraw();
404
405     /**
406      * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the
407      * graphics thread.
408      */
409     @CalledByNative
410     private static void redrawGraphicsInternal() {
411         Runnable callback = sRedrawCallback;
412         if (callback != null) {
413             callback.run();
414         }
415     }
416
417     /**
418      * Returns a bitmap of the latest video frame. Called on the native graphics thread when
419      * DesktopView is repainted.
420      */
421     public static Bitmap getVideoFrame() {
422         if (Looper.myLooper() == Looper.getMainLooper()) {
423             Log.w("jniiface", "Canvas being redrawn on UI thread");
424         }
425
426         synchronized (sFrameLock) {
427             return sFrameBitmap;
428         }
429     }
430
431     /**
432      * Sets a new video frame. Called on the native graphics thread when a new frame is allocated.
433      */
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");
438         }
439
440         synchronized (sFrameLock) {
441             sFrameBitmap = bitmap;
442         }
443     }
444
445     /**
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.
448      */
449     @CalledByNative
450     private static Bitmap newBitmap(int width, int height) {
451         return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
452     }
453
454     /**
455      * Updates the cursor shape. This is called on the graphics thread when receiving a new cursor
456      * shape from the host.
457      */
458     @CalledByNative
459     public static void updateCursorShape(int width, int height, int hotspotX, int hotspotY,
460                                          ByteBuffer buffer) {
461         sCursorHotspot = new Point(hotspotX, hotspotY);
462
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);
467     }
468
469     /** Position of cursor hotspot within cursor image. Called on the graphics thread. */
470     public static Point getCursorHotspot() { return sCursorHotspot; }
471
472     /** Returns the current cursor shape. Called on the graphics thread. */
473     public static Bitmap getCursorBitmap() { return sCursorBitmap; }
474
475     //
476     // Third Party Authentication
477     //
478
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);
484     }
485
486     /**
487      * Notify the native code to continue authentication with the |token| and the |sharedSecret|.
488      */
489     public static void onThirdPartyTokenFetched(String token, String sharedSecret) {
490         if (!sConnected) {
491             return;
492         }
493
494         nativeOnThirdPartyTokenFetched(token, sharedSecret);
495     }
496
497     /** Passes authentication data to the native handling code. */
498     private static native void nativeOnThirdPartyTokenFetched(String token, String sharedSecret);
499
500     //
501     // Host and Client Capabilities
502     //
503
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);
508     }
509
510     //
511     // Extension Message Handling
512     //
513
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);
518     }
519
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;
524         }
525
526         nativeSendExtensionMessage(type, data);
527     }
528
529     private static native void nativeSendExtensionMessage(String type, String data);
530 }