summaryrefslogtreecommitdiffstats
path: root/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java
blob: 288fd7836400db791069363322acc3a576624259 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
// Copyright 2013 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

package org.chromium.chromoting.jni;

import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Point;
import android.os.Looper;

import org.chromium.base.ContextUtils;
import org.chromium.base.Log;
import org.chromium.base.annotations.CalledByNative;
import org.chromium.base.annotations.JNINamespace;
import org.chromium.chromoting.CapabilityManager;
import org.chromium.chromoting.R;
import org.chromium.chromoting.SessionAuthenticator;

import java.nio.ByteBuffer;
import java.nio.ByteOrder;

/**
 * Initializes the Chromium remoting library, and provides JNI calls into it.
 * All interaction with the native code is centralized in this class.
 */
@JNINamespace("remoting")
public class JniInterface {
    private static final String TAG = "Chromoting";

    /*
     * Library-loading state machine.
     */
    /** Whether the library has been loaded. Accessed on the UI thread. */
    private static boolean sLoaded = false;

    /** Used for authentication-related UX during connection. Accessed on the UI thread. */
    private static SessionAuthenticator sAuthenticator;

    /** Interface used for connection state notifications. */
    public interface ConnectionListener {
        /**
         * This enum must match the C++ enumeration remoting::protocol::ConnectionToHost::State.
         */
        public enum State {
            INITIALIZING(0),
            CONNECTING(1),
            AUTHENTICATED(2),
            CONNECTED(3),
            FAILED(4),
            CLOSED(5);

            private final int mValue;

            State(int value) {
                mValue = value;
            }

            public int value() {
                return mValue;
            }

            public static State fromValue(int value) {
                return values()[value];
            }
        }

        /**
         * This enum must match the C++ enumeration remoting::protocol::ErrorCode.
         */
        public enum Error {
            OK(0, 0),
            PEER_IS_OFFLINE(1, R.string.error_host_is_offline),
            SESSION_REJECTED(2, R.string.error_invalid_access_code),
            INCOMPATIBLE_PROTOCOL(3, R.string.error_incompatible_protocol),
            AUTHENTICATION_FAILED(4, R.string.error_invalid_access_code),
            CHANNEL_CONNECTION_ERROR(5, R.string.error_p2p_failure),
            SIGNALING_ERROR(6, R.string.error_p2p_failure),
            SIGNALING_TIMEOUT(7, R.string.error_p2p_failure),
            HOST_OVERLOAD(8, R.string.error_host_overload),
            MAX_SESSION_LENGTH(9, R.string.error_max_session_length),
            HOST_CONFIGURATION_ERROR(10, R.string.error_host_configuration_error),
            UNKNOWN_ERROR(11, R.string.error_unexpected);

            private final int mValue;
            private final int mMessage;

            Error(int value, int message) {
                mValue = value;
                mMessage = message;
            }

            public int value() {
                return mValue;
            }

            public int message() {
                return mMessage;
            }

            public static Error fromValue(int value) {
                return values()[value];
            }
        }

        /**
         * Notified on connection state change.
         * @param state The new connection state.
         * @param error The error code, if state is STATE_FAILED.
         */
        void onConnectionState(State state, Error error);
    }

    /*
     * Connection-initiating state machine.
     */
    /** Whether the native code is attempting a connection. Accessed on the UI thread. */
    private static boolean sConnected = false;

    /** Notified upon successful connection or disconnection. Accessed on the UI thread. */
    private static ConnectionListener sConnectionListener = null;

    /**
     * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and
     * graphics threads.
     */
    private static Runnable sRedrawCallback = null;

    /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */
    private static Bitmap sFrameBitmap = null;

    /** Protects access to sFrameBitmap. */
    private static final Object sFrameLock = new Object();

    /** Position of cursor hot-spot. Accessed on the graphics thread. */
    private static Point sCursorHotspot = new Point();

    /** Bitmap holding the cursor shape. Accessed on the graphics thread. */
    private static Bitmap sCursorBitmap = null;

    /** Capability Manager through which capabilities and extensions are handled. */
    private static CapabilityManager sCapabilityManager = CapabilityManager.getInstance();

    /**
     * To be called once from the main Activity. Loads and initializes the native code.
     * Called on the UI thread.
     */
    public static void loadLibrary(Context context) {
        if (sLoaded) return;

        System.loadLibrary("remoting_client_jni");

        ContextUtils.initApplicationContext(context.getApplicationContext());
        nativeLoadNative();
        sLoaded = true;
    }

    /** Performs the native portion of the initialization. */
    private static native void nativeLoadNative();

    /*
     * API/OAuth2 keys access.
     */
    public static native String nativeGetApiKey();
    public static native String nativeGetClientId();
    public static native String nativeGetClientSecret();

    /** Returns whether the client is connected. */
    public static boolean isConnected() {
        return sConnected;
    }

    /** Attempts to form a connection to the user-selected host. Called on the UI thread. */
    public static void connectToHost(String username, String authToken, String hostJid,
            String hostId, String hostPubkey, ConnectionListener listener,
            SessionAuthenticator authenticator) {
        disconnectFromHost();

        sConnectionListener = listener;
        sAuthenticator = authenticator;
        nativeConnect(username, authToken, hostJid, hostId, hostPubkey,
                sAuthenticator.getPairingId(hostId), sAuthenticator.getPairingSecret(hostId),
                sCapabilityManager.getLocalCapabilities());
        sConnected = true;
    }

    /** Performs the native portion of the connection. */
    private static native void nativeConnect(String username, String authToken, String hostJid,
            String hostId, String hostPubkey, String pairId, String pairSecret,
            String capabilities);

    /** Severs the connection and cleans up. Called on the UI thread. */
    public static void disconnectFromHost() {
        if (!sConnected) {
            return;
        }

        sConnectionListener.onConnectionState(
                ConnectionListener.State.CLOSED, ConnectionListener.Error.OK);

        disconnectFromHostWithoutNotification();
    }

    /** Same as disconnectFromHost() but without notifying the ConnectionListener. */
    private static void disconnectFromHostWithoutNotification() {
        if (!sConnected) {
            return;
        }

        nativeDisconnect();
        sConnectionListener = null;
        sConnected = false;
        sCapabilityManager.onHostDisconnect();

        // Drop the reference to free the Bitmap for GC.
        synchronized (sFrameLock) {
            sFrameBitmap = null;
        }
    }

    /** Performs the native portion of the cleanup. */
    private static native void nativeDisconnect();

    /** Called by native code whenever the connection status changes. Called on the UI thread. */
    @CalledByNative
    private static void onConnectionState(int stateCode, int errorCode) {
        ConnectionListener.State state = ConnectionListener.State.fromValue(stateCode);
        ConnectionListener.Error error = ConnectionListener.Error.fromValue(errorCode);
        sConnectionListener.onConnectionState(state, error);
        if (state == ConnectionListener.State.FAILED || state == ConnectionListener.State.CLOSED) {
            // Disconnect from the host here, otherwise the next time connectToHost() is called,
            // it will try to disconnect, triggering an incorrect status notification.
            disconnectFromHostWithoutNotification();
        }
    }

    /** Prompts the user to enter a PIN. Called on the UI thread. */
    @CalledByNative
    private static void displayAuthenticationPrompt(boolean pairingSupported) {
        sAuthenticator.displayAuthenticationPrompt(pairingSupported);
    }

    /**
     * Performs the native response to the user's PIN.
     * @param pin The entered PIN.
     * @param createPair Whether to create a new pairing for this client.
     * @param deviceName The device name to appear in the pairing registry. Only used if createPair
     *                   is true.
     */
    public static void handleAuthenticationResponse(
            String pin, boolean createPair, String deviceName) {
        assert sConnected;
        nativeAuthenticationResponse(pin, createPair, deviceName);
    }

    /** Native implementation of handleAuthenticationResponse(). */
    private static native void nativeAuthenticationResponse(
            String pin, boolean createPair, String deviceName);

    /** Saves newly-received pairing credentials to permanent storage. Called on the UI thread. */
    @CalledByNative
    private static void commitPairingCredentials(String host, String id, String secret) {
        sAuthenticator.commitPairingCredentials(host, id, secret);
    }

    /**
     * Moves the mouse cursor, possibly while clicking the specified (nonnegative) button. Called
     * on the UI thread.
     */
    public static void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) {
        if (!sConnected) {
            return;
        }

        nativeSendMouseEvent(x, y, whichButton, buttonDown);
    }

    /** Passes mouse information to the native handling code. */
    private static native void nativeSendMouseEvent(
            int x, int y, int whichButton, boolean buttonDown);

    /** Injects a mouse-wheel event with delta values. Called on the UI thread. */
    public static void sendMouseWheelEvent(int deltaX, int deltaY) {
        if (!sConnected) {
            return;
        }

        nativeSendMouseWheelEvent(deltaX, deltaY);
    }

    /** Passes mouse-wheel information to the native handling code. */
    private static native void nativeSendMouseWheelEvent(int deltaX, int deltaY);

    /**
     * Presses or releases the specified (nonnegative) key. Called on the UI thread. If scanCode
     * is not zero then keyCode is ignored.
     */
    public static boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) {
        if (!sConnected) {
            return false;
        }

        return nativeSendKeyEvent(scanCode, keyCode, keyDown);
    }

    /**
     * Passes key press information to the native handling code.
     */
    private static native boolean nativeSendKeyEvent(int scanCode, int keyCode, boolean keyDown);

    /** Sends TextEvent to the host. Called on the UI thread. */
    public static void sendTextEvent(String text) {
        if (!sConnected) {
            return;
        }

        nativeSendTextEvent(text);
    }

    /** Passes text event information to the native handling code. */
    private static native void nativeSendTextEvent(String text);

    /** Sends an array of TouchEvents to the host. Called on the UI thread. */
    public static void sendTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) {
        nativeSendTouchEvent(eventType.value(), data);
    }

    /** Passes touch event information to the native handling code. */
    private static native void nativeSendTouchEvent(int eventType, TouchEventData[] data);

    /**
     * Enables or disables the video channel. Called on the UI thread in response to Activity
     * lifecycle events.
     */
    public static void enableVideoChannel(boolean enable) {
        if (!sConnected) {
            return;
        }

        nativeEnableVideoChannel(enable);
    }

    /** Native implementation of enableVideoChannel() */
    private static native void nativeEnableVideoChannel(boolean enable);

    /**
     * Sets the redraw callback to the provided functor. Provide a value of null whenever the
     * window is no longer visible so that we don't continue to draw onto it. Called on the UI
     * thread.
     */
    public static void provideRedrawCallback(Runnable redrawCallback) {
        sRedrawCallback = redrawCallback;
    }

    /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */
    public static boolean redrawGraphics() {
        if (!sConnected || sRedrawCallback == null) return false;

        nativeScheduleRedraw();
        return true;
    }

    /** Schedules a redraw on the native graphics thread. */
    private static native void nativeScheduleRedraw();

    /**
     * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the
     * graphics thread.
     */
    @CalledByNative
    private static void redrawGraphicsInternal() {
        Runnable callback = sRedrawCallback;
        if (callback != null) {
            callback.run();
        }
    }

    /**
     * Returns a bitmap of the latest video frame. Called on the native graphics thread when
     * DesktopView is repainted.
     */
    public static Bitmap getVideoFrame() {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "Canvas being redrawn on UI thread");
        }

        synchronized (sFrameLock) {
            return sFrameBitmap;
        }
    }

    /**
     * Sets a new video frame. Called on the native graphics thread when a new frame is allocated.
     */
    @CalledByNative
    private static void setVideoFrame(Bitmap bitmap) {
        if (Looper.myLooper() == Looper.getMainLooper()) {
            Log.w(TAG, "Video frame updated on UI thread");
        }

        synchronized (sFrameLock) {
            sFrameBitmap = bitmap;
        }
    }

    /**
     * Creates a new Bitmap to hold video frame pixels. Called by native code which stores a global
     * reference to the Bitmap and writes the decoded frame pixels to it.
     */
    @CalledByNative
    private static Bitmap newBitmap(int width, int height) {
        return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
    }

    /**
     * Updates the cursor shape. This is called on the graphics thread when receiving a new cursor
     * shape from the host.
     */
    @CalledByNative
    public static void updateCursorShape(
            int width, int height, int hotspotX, int hotspotY, ByteBuffer buffer) {
        sCursorHotspot = new Point(hotspotX, hotspotY);

        int[] data = new int[width * height];
        buffer.order(ByteOrder.LITTLE_ENDIAN);
        buffer.asIntBuffer().get(data, 0, data.length);
        sCursorBitmap = Bitmap.createBitmap(data, width, height, Bitmap.Config.ARGB_8888);
    }

    /** Position of cursor hotspot within cursor image. Called on the graphics thread. */
    public static Point getCursorHotspot() {
        return sCursorHotspot;
    }

    /** Returns the current cursor shape. Called on the graphics thread. */
    public static Bitmap getCursorBitmap() {
        return sCursorBitmap;
    }

    //
    // Third Party Authentication
    //

    /** Pops up a third party login page to fetch the token required for authentication. */
    @CalledByNative
    public static void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) {
        sAuthenticator.fetchThirdPartyToken(tokenUrl, clientId, scope);
    }

    /**
     * Notify the native code to continue authentication with the |token| and the |sharedSecret|.
     */
    public static void onThirdPartyTokenFetched(String token, String sharedSecret) {
        if (!sConnected) {
            return;
        }

        nativeOnThirdPartyTokenFetched(token, sharedSecret);
    }

    /** Passes authentication data to the native handling code. */
    private static native void nativeOnThirdPartyTokenFetched(String token, String sharedSecret);

    //
    // Host and Client Capabilities
    //

    /** Set the list of negotiated capabilities between host and client. Called on the UI thread. */
    @CalledByNative
    public static void setCapabilities(String capabilities) {
        sCapabilityManager.setNegotiatedCapabilities(capabilities);
    }

    //
    // Extension Message Handling
    //

    /** Passes on the deconstructed ExtensionMessage to the app. Called on the UI thread. */
    @CalledByNative
    public static void handleExtensionMessage(String type, String data) {
        sCapabilityManager.onExtensionMessage(type, data);
    }

    /** Sends an extension message to the Chromoting host. Called on the UI thread. */
    public static void sendExtensionMessage(String type, String data) {
        if (!sConnected) {
            return;
        }

        nativeSendExtensionMessage(type, data);
    }

    private static native void nativeSendExtensionMessage(String type, String data);
}