Created
January 2, 2016 04:10
-
-
Save jaredsburrows/430b469c81c5a7bbb40c to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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.app.Activity; | |
import android.app.AlertDialog; | |
import android.content.Context; | |
import android.content.DialogInterface; | |
import android.content.SharedPreferences; | |
import android.graphics.Bitmap; | |
import android.graphics.Point; | |
import android.os.Build; | |
import android.os.Looper; | |
import android.util.Log; | |
import android.view.KeyEvent; | |
import android.view.View; | |
import android.widget.CheckBox; | |
import android.widget.TextView; | |
import android.widget.Toast; | |
import org.chromium.base.CalledByNative; | |
import org.chromium.base.JNINamespace; | |
import org.chromium.chromoting.CapabilityManager; | |
import org.chromium.chromoting.Chromoting; | |
import org.chromium.chromoting.R; | |
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 { | |
/* | |
* Library-loading state machine. | |
*/ | |
/** Whether the library has been loaded. Accessed on the UI thread. */ | |
private static boolean sLoaded = false; | |
/** The application context. Accessed on the UI thread. */ | |
private static Activity sContext = null; | |
/** 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), | |
UNKNOWN_ERROR(9, 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. Any subsequent calls will update the application | |
* context, but not reload the library. This is useful e.g. when the activity is closed and the | |
* user later wants to return to the application. Called on the UI thread. | |
*/ | |
public static void loadLibrary(Activity context) { | |
sContext = context; | |
if (sLoaded) return; | |
System.loadLibrary("remoting_client_jni"); | |
nativeLoadNative(context); | |
sLoaded = true; | |
} | |
/** Performs the native portion of the initialization. */ | |
private static native void nativeLoadNative(Context context); | |
/* | |
* API/OAuth2 keys access. | |
*/ | |
public static native String nativeGetApiKey(); | |
public static native String nativeGetClientId(); | |
public static native String nativeGetClientSecret(); | |
/** 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) { | |
disconnectFromHost(); | |
sConnectionListener = listener; | |
SharedPreferences prefs = sContext.getPreferences(Activity.MODE_PRIVATE); | |
nativeConnect(username, authToken, hostJid, hostId, hostPubkey, | |
prefs.getString(hostId + "_id", ""), prefs.getString(hostId + "_secret", ""), | |
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; | |
// 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) { | |
AlertDialog.Builder pinPrompt = new AlertDialog.Builder(sContext); | |
pinPrompt.setTitle(sContext.getString(R.string.title_authenticate)); | |
pinPrompt.setMessage(sContext.getString(R.string.pin_message_android)); | |
pinPrompt.setIcon(android.R.drawable.ic_lock_lock); | |
final View pinEntry = sContext.getLayoutInflater().inflate(R.layout.pin_dialog, null); | |
pinPrompt.setView(pinEntry); | |
final TextView pinTextView = (TextView)pinEntry.findViewById(R.id.pin_dialog_text); | |
final CheckBox pinCheckBox = (CheckBox)pinEntry.findViewById(R.id.pin_dialog_check); | |
if (!pairingSupported) { | |
pinCheckBox.setChecked(false); | |
pinCheckBox.setVisibility(View.GONE); | |
} | |
pinPrompt.setPositiveButton( | |
R.string.connect_button, new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialog, int which) { | |
Log.i("jniiface", "User provided a PIN code"); | |
if (sConnected) { | |
nativeAuthenticationResponse(String.valueOf(pinTextView.getText()), | |
pinCheckBox.isChecked(), Build.MODEL); | |
} else { | |
String message = sContext.getString(R.string.error_network_error); | |
Toast.makeText(sContext, message, Toast.LENGTH_LONG).show(); | |
} | |
} | |
}); | |
pinPrompt.setNegativeButton( | |
R.string.cancel, new DialogInterface.OnClickListener() { | |
@Override | |
public void onClick(DialogInterface dialog, int which) { | |
Log.i("jniiface", "User canceled pin entry prompt"); | |
disconnectFromHost(); | |
} | |
}); | |
final AlertDialog pinDialog = pinPrompt.create(); | |
pinTextView.setOnEditorActionListener( | |
new TextView.OnEditorActionListener() { | |
@Override | |
public boolean onEditorAction(TextView v, int actionId, KeyEvent event) { | |
// The user pressed enter on the keypad (equivalent to the connect button). | |
pinDialog.getButton(AlertDialog.BUTTON_POSITIVE).performClick(); | |
pinDialog.dismiss(); | |
return true; | |
} | |
}); | |
pinDialog.setOnCancelListener( | |
new DialogInterface.OnCancelListener() { | |
@Override | |
public void onCancel(DialogInterface dialog) { | |
// The user backed out of the dialog (equivalent to the cancel button). | |
pinDialog.getButton(AlertDialog.BUTTON_NEGATIVE).performClick(); | |
} | |
}); | |
pinDialog.show(); | |
} | |
/** | |
* 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. | |
*/ | |
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) { | |
// Empty |id| indicates that pairing needs to be removed. | |
if (id.isEmpty()) { | |
sContext.getPreferences(Activity.MODE_PRIVATE).edit(). | |
remove(host + "_id"). | |
remove(host + "_secret"). | |
apply(); | |
} else { | |
sContext.getPreferences(Activity.MODE_PRIVATE).edit(). | |
putString(host + "_id", id). | |
putString(host + "_secret", secret). | |
apply(); | |
} | |
} | |
/** | |
* 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. */ | |
public static boolean sendKeyEvent(int keyCode, boolean keyDown) { | |
if (!sConnected) { | |
return false; | |
} | |
return nativeSendKeyEvent(keyCode, keyDown); | |
} | |
/** Passes key press information to the native handling code. */ | |
private static native boolean nativeSendKeyEvent(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); | |
/** | |
* 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("jniiface", "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("jniiface", "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) { | |
Chromoting app = (Chromoting) sContext; | |
app.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); | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
https://chromium.googlesource.com/chromium/src.git/+/39.0.2164.0/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java?autodive=0%2F%2F