diff options
Diffstat (limited to 'remoting/android')
16 files changed, 568 insertions, 380 deletions
diff --git a/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java b/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java index e332e83..0a551bf4e 100644 --- a/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java +++ b/remoting/android/java/src/org/chromium/chromoting/CapabilityManager.java @@ -21,10 +21,6 @@ import java.util.List; * The CapabilityManager mirrors how the Chromoting host handles extension messages. For each * incoming extension message, runs through a list of HostExtensionSession objects, giving each one * a chance to handle the message. - * - * The CapabilityManager is a singleton class so we can manage client extensions on an application - * level. The singleton object may be used from multiple Activities, thus allowing it to support - * different capabilities at different stages of the application. */ public class CapabilityManager { /** Used to allow objects to receive notifications when the host capabilites are received. */ @@ -50,12 +46,6 @@ public class CapabilityManager { private static final String TAG = "Chromoting"; - /** Lazily-initialized singleton object that can be used from different Activities. */ - private static CapabilityManager sInstance; - - /** Protects access to |sInstance|. */ - private static final Object sInstanceLock = new Object(); - /** List of all capabilities that are supported by the application. */ private List<String> mLocalCapabilities; @@ -68,7 +58,7 @@ public class CapabilityManager { /** Maintains a list of listeners to notify when host capabilities are received. */ private List<CapabilitiesChangedListener> mCapabilitiesChangedListeners; - private CapabilityManager() { + public CapabilityManager() { mLocalCapabilities = new ArrayList<String>(); mClientExtensions = new ArrayList<ClientExtension>(); @@ -79,18 +69,6 @@ public class CapabilityManager { } /** - * Returns the singleton object. Thread-safe. - */ - public static CapabilityManager getInstance() { - synchronized (sInstanceLock) { - if (sInstance == null) { - sInstance = new CapabilityManager(); - } - return sInstance; - } - } - - /** * Cleans up host specific state when the connection has been terminated. */ public void onHostDisconnect() { diff --git a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java index a10a17f..f53926c 100644 --- a/remoting/android/java/src/org/chromium/chromoting/Chromoting.java +++ b/remoting/android/java/src/org/chromium/chromoting/Chromoting.java @@ -36,6 +36,7 @@ import org.chromium.chromoting.accountswitcher.AccountSwitcher; import org.chromium.chromoting.accountswitcher.AccountSwitcherFactory; import org.chromium.chromoting.help.HelpContext; import org.chromium.chromoting.help.HelpSingleton; +import org.chromium.chromoting.jni.Client; import org.chromium.chromoting.jni.ConnectionListener; import org.chromium.chromoting.jni.JniInterface; @@ -117,6 +118,9 @@ public class Chromoting extends AppCompatActivity implements ConnectionListener, private AccountSwitcher mAccountSwitcher; + /** The currently-connected Client, if any. */ + private Client mClient; + /** Shows a warning explaining that a Google account is required, then closes the activity. */ private void showNoAccountsDialog() { AlertDialog.Builder builder = new AlertDialog.Builder(this); @@ -349,8 +353,14 @@ public class Chromoting extends AppCompatActivity implements ConnectionListener, @Override public void onDestroy() { super.onDestroy(); - JniInterface.disconnectFromHost(); mAccountSwitcher.destroy(); + + // TODO(lambroslambrou): Determine whether we really need to tear down the connection here, + // so we can remove this code. + if (mClient != null) { + mClient.destroy(); + mClient = null; + } } /** Called when a child Activity exits and sends a result back to this Activity. */ @@ -444,6 +454,11 @@ public class Chromoting extends AppCompatActivity implements ConnectionListener, } private void connectToHost(HostInfo host) { + if (mClient != null) { + mClient.destroy(); + } + + mClient = new Client(); mProgressIndicator = ProgressDialog.show( this, host.name, @@ -453,11 +468,15 @@ public class Chromoting extends AppCompatActivity implements ConnectionListener, new DialogInterface.OnCancelListener() { @Override public void onCancel(DialogInterface dialog) { - JniInterface.disconnectFromHost(); + if (mClient != null) { + mClient.destroy(); + mClient = null; + } } }); - SessionConnector connector = new SessionConnector(this, this, mHostListLoader); - mAuthenticator = new SessionAuthenticator(this, host); + + SessionConnector connector = new SessionConnector(mClient, this, this, mHostListLoader); + mAuthenticator = new SessionAuthenticator(this, mClient, host); connector.connectToHost(mAccount, mToken, host, mAuthenticator, getPreferences(MODE_PRIVATE).getString(PREFERENCE_EXPERIMENTAL_FLAGS, "")); } diff --git a/remoting/android/java/src/org/chromium/chromoting/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/Desktop.java index 93e54b1..f9f3c73 100644 --- a/remoting/android/java/src/org/chromium/chromoting/Desktop.java +++ b/remoting/android/java/src/org/chromium/chromoting/Desktop.java @@ -31,7 +31,7 @@ import android.view.inputmethod.InputMethodManager; import org.chromium.chromoting.cardboard.DesktopActivity; import org.chromium.chromoting.help.HelpContext; import org.chromium.chromoting.help.HelpSingleton; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import java.util.List; import java.util.Set; @@ -69,6 +69,8 @@ public class Desktop /** The surface that displays the remote host's desktop feed. */ private DesktopView mRemoteHostDesktop; + private Client mClient; + /** Set of pressed keys for which we've sent TextEvent. */ private Set<Integer> mPressedTextKeys = new TreeSet<Integer>(); @@ -102,11 +104,14 @@ public class Desktop super.onCreate(savedInstanceState); setContentView(R.layout.desktop); + mClient = Client.getInstance(); + mToolbar = (Toolbar) findViewById(R.id.toolbar); setSupportActionBar(mToolbar); mRemoteHostDesktop = (DesktopView) findViewById(R.id.desktop_view); mRemoteHostDesktop.setDesktop(this); + mRemoteHostDesktop.setClient(mClient); mSwitchToCardboardDesktopActivity = false; getSupportActionBar().setDisplayShowTitleEnabled(false); @@ -124,7 +129,7 @@ public class Desktop View decorView = getWindow().getDecorView(); decorView.setOnSystemUiVisibilityChangeListener(this); - mActivityLifecycleListener = CapabilityManager.getInstance().onActivityAcceptingListener( + mActivityLifecycleListener = mClient.getCapabilityManager().onActivityAcceptingListener( this, Capabilities.CAST_CAPABILITY); mActivityLifecycleListener.onActivityCreated(this, savedInstanceState); @@ -163,9 +168,9 @@ public class Desktop protected void onStart() { super.onStart(); mActivityLifecycleListener.onActivityStarted(this); - JniInterface.enableVideoChannel(true); + mClient.enableVideoChannel(true); mRemoteHostDesktop.attachRedrawCallback(); - CapabilityManager.getInstance().addListener(this); + mClient.getCapabilityManager().addListener(this); } @Override @@ -173,7 +178,7 @@ public class Desktop if (isFinishing()) mActivityLifecycleListener.onActivityPaused(this); super.onPause(); if (!mSwitchToCardboardDesktopActivity) { - JniInterface.enableVideoChannel(false); + mClient.enableVideoChannel(false); } stopActionBarAutoHideTimer(); } @@ -182,19 +187,19 @@ public class Desktop public void onResume() { super.onResume(); mActivityLifecycleListener.onActivityResumed(this); - JniInterface.enableVideoChannel(true); + mClient.enableVideoChannel(true); startActionBarAutoHideTimer(); } @Override protected void onStop() { - CapabilityManager.getInstance().removeListener(this); + mClient.getCapabilityManager().removeListener(this); mActivityLifecycleListener.onActivityStopped(this); super.onStop(); if (mSwitchToCardboardDesktopActivity) { mSwitchToCardboardDesktopActivity = false; } else { - JniInterface.enableVideoChannel(false); + mClient.enableVideoChannel(false); } } @@ -489,7 +494,7 @@ public class Desktop return true; } if (id == R.id.actionbar_disconnect || id == android.R.id.home) { - JniInterface.disconnectFromHost(); + mClient.destroy(); return true; } if (id == R.id.actionbar_send_ctrl_alt_del) { @@ -499,10 +504,10 @@ public class Desktop KeyEvent.KEYCODE_FORWARD_DEL, }; for (int key : keys) { - JniInterface.sendKeyEvent(0, key, true); + mClient.sendKeyEvent(0, key, true); } for (int key : keys) { - JniInterface.sendKeyEvent(0, key, false); + mClient.sendKeyEvent(0, key, false); } return true; } @@ -609,7 +614,7 @@ public class Desktop // Dispatch the back button to the system to handle navigation if (keyCode == KeyEvent.KEYCODE_BACK) { - JniInterface.disconnectFromHost(); + mClient.destroy(); return super.dispatchKeyEvent(event); } @@ -621,7 +626,7 @@ public class Desktop // the keyboard layout selected on the client doesn't affect the key // codes sent to the host. if (event.getDeviceId() != KeyCharacterMap.VIRTUAL_KEYBOARD) { - return JniInterface.sendKeyEvent(event.getScanCode(), 0, pressed); + return mClient.sendKeyEvent(event.getScanCode(), 0, pressed); } // Events received from software keyboards generate TextEvent in two @@ -632,7 +637,7 @@ public class Desktop // correspond to what user sees on the screen, while physical keyboard // acts as if it is connected to the remote host. if (event.getAction() == KeyEvent.ACTION_MULTIPLE) { - JniInterface.sendTextEvent(event.getCharacters()); + mClient.sendTextEvent(event.getCharacters()); return true; } @@ -646,7 +651,7 @@ public class Desktop if (pressed && unicode != 0 && no_modifiers) { mPressedTextKeys.add(keyCode); int[] codePoints = { unicode }; - JniInterface.sendTextEvent(new String(codePoints, 0, 1)); + mClient.sendTextEvent(new String(codePoints, 0, 1)); return true; } @@ -661,28 +666,28 @@ public class Desktop // third-party keyboards that may still generate these events. See // https://source.android.com/devices/input/keyboard-devices.html#legacy-unsupported-keys case KeyEvent.KEYCODE_AT: - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_2, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_2, pressed); return true; case KeyEvent.KEYCODE_POUND: - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_3, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_3, pressed); return true; case KeyEvent.KEYCODE_STAR: - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_8, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_8, pressed); return true; case KeyEvent.KEYCODE_PLUS: - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); - JniInterface.sendKeyEvent(0, KeyEvent.KEYCODE_EQUALS, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_SHIFT_LEFT, pressed); + mClient.sendKeyEvent(0, KeyEvent.KEYCODE_EQUALS, pressed); return true; default: // We try to send all other key codes to the host directly. - return JniInterface.sendKeyEvent(0, keyCode, pressed); + return mClient.sendKeyEvent(0, keyCode, pressed); } } } diff --git a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java index 0c2330b..e79065d3 100644 --- a/remoting/android/java/src/org/chromium/chromoting/DesktopView.java +++ b/remoting/android/java/src/org/chromium/chromoting/DesktopView.java @@ -25,7 +25,7 @@ import android.view.inputmethod.InputConnection; import android.view.inputmethod.InputMethodManager; import org.chromium.base.Log; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; /** * The user interface for viewing and interacting with a specific remote host. @@ -46,6 +46,10 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, /** The parent Desktop activity. */ private Desktop mDesktop; + /** The Client connection, used to inject input and fetch the video frames. */ + private Client mClient; + + // Flag to prevent multiple repaint requests from being backed up. Requests for repainting will // be dropped if this is already set to true. This is used by the main thread and the painting // thread, so the access should be synchronized on |mRenderData|. @@ -179,6 +183,10 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, mDesktop = desktop; } + public void setClient(Client client) { + mClient = client; + } + /** See {@link TouchInputHandler#onSoftInputMethodVisibilityChanged} for API details. */ public void onSoftInputMethodVisibilityChanged(boolean inputMethodVisible, Rect bounds) { mInputHandler.onSoftInputMethodVisibilityChanged(inputMethodVisible, bounds); @@ -192,7 +200,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, } mRepaintPending = true; } - JniInterface.redrawGraphics(); + mClient.redrawGraphics(); } /** @@ -207,7 +215,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, Log.w(TAG, "Canvas being redrawn on UI thread"); } - Bitmap image = JniInterface.getVideoFrame(); + Bitmap image = mClient.getVideoFrame(); if (image == null) { // This can happen if the client is connected, but a complete video frame has not yet // been decoded. @@ -220,7 +228,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, synchronized (mRenderData) { if (mRenderData.imageWidth != width || mRenderData.imageHeight != height) { // TODO(lambroslambrou): Move this code into a sizeChanged() callback, to be - // triggered from JniInterface (on the display thread) when the remote screen size + // triggered from native code (on the display thread) when the remote screen size // changes. mRenderData.imageWidth = width; mRenderData.imageHeight = height; @@ -266,9 +274,9 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, } if (drawCursor) { - Bitmap cursorBitmap = JniInterface.getCursorBitmap(); + Bitmap cursorBitmap = mClient.getCursorBitmap(); if (cursorBitmap != null) { - Point hotspot = JniInterface.getCursorHotspot(); + Point hotspot = mClient.getCursorHotspot(); canvas.drawBitmap(cursorBitmap, cursorPosition.x - hotspot.x, cursorPosition.y - hotspot.y, new Paint()); } @@ -319,7 +327,7 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, } public void attachRedrawCallback() { - JniInterface.provideRedrawCallback(new Runnable() { + mClient.provideRedrawCallback(new Runnable() { @Override public void run() { paint(); @@ -415,15 +423,15 @@ public class DesktopView extends SurfaceView implements DesktopViewInterface, switch (inputMode) { case TRACKPAD: - mInputHandler.setInputStrategy(new TrackpadInputStrategy(mRenderData)); + mInputHandler.setInputStrategy(new TrackpadInputStrategy(mRenderData, mClient)); break; case TOUCH: if (hostTouchCapability.isSupported()) { - mInputHandler.setInputStrategy(new TouchInputStrategy(mRenderData)); + mInputHandler.setInputStrategy(new TouchInputStrategy(mRenderData, mClient)); } else { mInputHandler.setInputStrategy( - new SimulatedTouchInputStrategy(mRenderData, getContext())); + new SimulatedTouchInputStrategy(mRenderData, mClient, getContext())); } break; diff --git a/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java b/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java index a18c2f4..ce50a04 100644 --- a/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java +++ b/remoting/android/java/src/org/chromium/chromoting/SessionAuthenticator.java @@ -16,7 +16,7 @@ import android.widget.CheckBox; import android.widget.TextView; import android.widget.Toast; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; /** * This class performs the user-interaction needed to authenticate the session connection. This @@ -29,14 +29,18 @@ public class SessionAuthenticator { */ private Chromoting mApplicationContext; + /** Client connection being authenticated. */ + private final Client mClient; + /** Provides the tokenUrlPatterns for this host during fetchThirdPartyTokens(). */ private HostInfo mHost; /** Object for fetching OAuth2 access tokens from third party authorization servers. */ private ThirdPartyTokenFetcher mTokenFetcher; - public SessionAuthenticator(Chromoting context, HostInfo host) { + public SessionAuthenticator(Chromoting context, Client client, HostInfo host) { mApplicationContext = context; + mClient = client; mHost = host; } @@ -62,8 +66,8 @@ public class SessionAuthenticator { R.string.connect_button, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - if (JniInterface.isConnected()) { - JniInterface.handleAuthenticationResponse( + if (mClient.isConnected()) { + mClient.handleAuthenticationResponse( String.valueOf(pinTextView.getText()), pinCheckBox.isChecked(), Build.MODEL); } else { @@ -78,7 +82,7 @@ public class SessionAuthenticator { R.string.cancel, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { - JniInterface.disconnectFromHost(); + mClient.destroy(); } }); @@ -138,7 +142,7 @@ public class SessionAuthenticator { // authenticate itself with the host using spake. String sharedSecret = accessToken; - JniInterface.onThirdPartyTokenFetched(token, sharedSecret); + mClient.onThirdPartyTokenFetched(token, sharedSecret); } }; mTokenFetcher = new ThirdPartyTokenFetcher(mApplicationContext, mHost.getTokenUrlPatterns(), diff --git a/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java b/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java index 4fa9522..38e4634 100644 --- a/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java +++ b/remoting/android/java/src/org/chromium/chromoting/SessionConnector.java @@ -4,15 +4,15 @@ package org.chromium.chromoting; +import org.chromium.chromoting.jni.Client; import org.chromium.chromoting.jni.ConnectionListener; -import org.chromium.chromoting.jni.JniInterface; /** * This class manages making a connection to a host, with logic for reloading the host list and * retrying the connection in the case of a stale host JID. */ -public class SessionConnector implements ConnectionListener, - HostListLoader.Callback { +public class SessionConnector implements ConnectionListener, HostListLoader.Callback { + private Client mClient; private ConnectionListener mConnectionListener; private HostListLoader.Callback mHostListCallback; private HostListLoader mHostListLoader; @@ -38,8 +38,9 @@ public class SessionConnector implements ConnectionListener, * @param hostListCallback Object to be notified whenever the host list is reloaded. * @param hostListLoader The object used for reloading the host list. */ - public SessionConnector(ConnectionListener connectionListener, + public SessionConnector(Client client, ConnectionListener connectionListener, HostListLoader.Callback hostListCallback, HostListLoader hostListLoader) { + mClient = client; mConnectionListener = connectionListener; mHostListCallback = hostListCallback; mHostListLoader = hostListLoader; @@ -65,7 +66,7 @@ public class SessionConnector implements ConnectionListener, } private void doConnect() { - JniInterface.connectToHost(mAccountName, mAuthToken, mHost.jabberId, mHost.id, + mClient.connectToHost(mAccountName, mAuthToken, mHost.jabberId, mHost.id, mHost.publicKey, mAuthenticator, mFlags, this); } diff --git a/remoting/android/java/src/org/chromium/chromoting/SimulatedTouchInputStrategy.java b/remoting/android/java/src/org/chromium/chromoting/SimulatedTouchInputStrategy.java index 2c3ded2..ccc0dbd 100644 --- a/remoting/android/java/src/org/chromium/chromoting/SimulatedTouchInputStrategy.java +++ b/remoting/android/java/src/org/chromium/chromoting/SimulatedTouchInputStrategy.java @@ -10,7 +10,7 @@ import android.os.SystemClock; import android.view.MotionEvent; import android.view.ViewConfiguration; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; /** * This class receives local touch events and translates them into the appropriate mouse based @@ -22,6 +22,7 @@ public class SimulatedTouchInputStrategy implements InputStrategyInterface { private static final float DOUBLE_TAP_SLOP_SCALE_FACTOR = 0.25f; private final RenderData mRenderData; + private final Client mClient; /** * Stores the time of the most recent left button single tap processed. @@ -48,8 +49,9 @@ public class SimulatedTouchInputStrategy implements InputStrategyInterface { /** Mouse-button currently held down, or BUTTON_UNDEFINED otherwise. */ private int mHeldButton = TouchInputHandlerInterface.BUTTON_UNDEFINED; - public SimulatedTouchInputStrategy(RenderData renderData, Context context) { + public SimulatedTouchInputStrategy(RenderData renderData, Client client, Context context) { mRenderData = renderData; + mClient = client; ViewConfiguration config = ViewConfiguration.get(context); mDoubleTapDurationInMs = config.getDoubleTapTimeout(); @@ -121,7 +123,7 @@ public class SimulatedTouchInputStrategy implements InputStrategyInterface { @Override public void onScroll(float distanceX, float distanceY) { - JniInterface.sendMouseWheelEvent((int) -distanceX, (int) -distanceY); + mClient.sendMouseWheelEvent((int) -distanceX, (int) -distanceY); } @Override @@ -135,7 +137,7 @@ public class SimulatedTouchInputStrategy implements InputStrategyInterface { @Override public void injectCursorMoveEvent(int x, int y) { - JniInterface.sendMouseEvent(x, y, TouchInputHandlerInterface.BUTTON_UNDEFINED, false); + mClient.sendMouseEvent(x, y, TouchInputHandlerInterface.BUTTON_UNDEFINED, false); } @Override @@ -180,6 +182,6 @@ public class SimulatedTouchInputStrategy implements InputStrategyInterface { } private void injectMouseButtonEvent(int button, boolean pressed, Point tapPoint) { - JniInterface.sendMouseEvent(tapPoint.x, tapPoint.y, button, pressed); + mClient.sendMouseEvent(tapPoint.x, tapPoint.y, button, pressed); } } diff --git a/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java b/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java index 488760b..19d70e3 100644 --- a/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java +++ b/remoting/android/java/src/org/chromium/chromoting/TouchInputStrategy.java @@ -8,7 +8,7 @@ import android.graphics.Matrix; import android.view.MotionEvent; import org.chromium.base.VisibleForTesting; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import org.chromium.chromoting.jni.TouchEventData; import java.util.ArrayList; @@ -37,15 +37,15 @@ public class TouchInputStrategy implements InputStrategyInterface { /** * This class provides the default implementation for injecting remote events. */ - private static class DefaultInputInjector implements RemoteInputInjector { + private class DefaultInputInjector implements RemoteInputInjector { @Override public void injectMouseEvent(int x, int y, int button, boolean buttonDown) { - JniInterface.sendMouseEvent(x, y, button, buttonDown); + mClient.sendMouseEvent(x, y, button, buttonDown); } @Override public void injectTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) { - JniInterface.sendTouchEvent(eventType, data); + mClient.sendTouchEvent(eventType, data); } } @@ -77,10 +77,13 @@ public class TouchInputStrategy implements InputStrategyInterface { private final RenderData mRenderData; + private final Client mClient; + private RemoteInputInjector mRemoteInputInjector; - public TouchInputStrategy(RenderData renderData) { + public TouchInputStrategy(RenderData renderData, Client client) { mRenderData = renderData; + mClient = client; mRemoteInputInjector = new DefaultInputInjector(); mQueuedEvents = new LinkedList<MotionEvent>(); diff --git a/remoting/android/java/src/org/chromium/chromoting/TrackpadInputStrategy.java b/remoting/android/java/src/org/chromium/chromoting/TrackpadInputStrategy.java index 326eff9..c9dcf4b 100644 --- a/remoting/android/java/src/org/chromium/chromoting/TrackpadInputStrategy.java +++ b/remoting/android/java/src/org/chromium/chromoting/TrackpadInputStrategy.java @@ -7,7 +7,7 @@ package org.chromium.chromoting; import android.graphics.Point; import android.view.MotionEvent; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; /** * Defines a set of behavior and methods to simulate trackpad behavior when responding to @@ -16,12 +16,14 @@ import org.chromium.chromoting.jni.JniInterface; */ public class TrackpadInputStrategy implements InputStrategyInterface { private final RenderData mRenderData; + private final Client mClient; /** Mouse-button currently held down, or BUTTON_UNDEFINED otherwise. */ private int mHeldButton = TouchInputHandlerInterface.BUTTON_UNDEFINED; - public TrackpadInputStrategy(RenderData renderData) { + public TrackpadInputStrategy(RenderData renderData, Client client) { mRenderData = renderData; + mClient = client; synchronized (mRenderData) { mRenderData.drawCursor = true; @@ -44,7 +46,7 @@ public class TrackpadInputStrategy implements InputStrategyInterface { @Override public void onScroll(float distanceX, float distanceY) { - JniInterface.sendMouseWheelEvent((int) -distanceX, (int) -distanceY); + mClient.sendMouseWheelEvent((int) -distanceX, (int) -distanceY); } @Override @@ -58,7 +60,7 @@ public class TrackpadInputStrategy implements InputStrategyInterface { @Override public void injectCursorMoveEvent(int x, int y) { - JniInterface.sendMouseEvent(x, y, TouchInputHandlerInterface.BUTTON_UNDEFINED, false); + mClient.sendMouseEvent(x, y, TouchInputHandlerInterface.BUTTON_UNDEFINED, false); } @Override @@ -81,6 +83,6 @@ public class TrackpadInputStrategy implements InputStrategyInterface { synchronized (mRenderData) { cursorPosition = mRenderData.getCursorPosition(); } - JniInterface.sendMouseEvent(cursorPosition.x, cursorPosition.y, button, pressed); + mClient.sendMouseEvent(cursorPosition.x, cursorPosition.y, button, pressed); } } diff --git a/remoting/android/java/src/org/chromium/chromoting/cardboard/CardboardRenderer.java b/remoting/android/java/src/org/chromium/chromoting/cardboard/CardboardRenderer.java index 843edd1..e8a05f0 100644 --- a/remoting/android/java/src/org/chromium/chromoting/cardboard/CardboardRenderer.java +++ b/remoting/android/java/src/org/chromium/chromoting/cardboard/CardboardRenderer.java @@ -15,7 +15,7 @@ import com.google.vrtoolkit.cardboard.Eye; import com.google.vrtoolkit.cardboard.HeadTransform; import com.google.vrtoolkit.cardboard.Viewport; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import javax.microedition.khronos.egl.EGLConfig; @@ -65,6 +65,7 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { private static final float EPSILON = 1e-5f; private final Activity mActivity; + private final Client mClient; private float mCameraPosition; @@ -103,8 +104,9 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { // Flag to indicate whether to show menu bar. private boolean mMenuBarVisible; - public CardboardRenderer(Activity activity) { + public CardboardRenderer(Activity activity, Client client) { mActivity = activity; + mClient = client; mCameraPosition = 0.0f; mCameraMatrix = new float[16]; @@ -122,7 +124,7 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { private void initializeRedrawCallback() { mActivity.runOnUiThread(new Runnable() { public void run() { - JniInterface.provideRedrawCallback(new Runnable() { + mClient.provideRedrawCallback(new Runnable() { @Override public void run() { mDesktop.reloadTexture(); @@ -130,7 +132,7 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { } }); - JniInterface.redrawGraphics(); + mClient.redrawGraphics(); } }); } @@ -146,10 +148,10 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { // Enable depth testing. GLES20.glEnable(GLES20.GL_DEPTH_TEST); - mDesktop = new Desktop(); + mDesktop = new Desktop(mClient); mMenuBar = new MenuBar(mActivity); mPhotosphere = new Photosphere(mActivity); - mCursor = new Cursor(); + mCursor = new Cursor(mClient); initializeRedrawCallback(); } @@ -429,4 +431,4 @@ public class CardboardRenderer implements CardboardView.StereoRenderer { return Math.abs(phi) > FARAWAY_ANGLE_RATIO * Math.abs(theta); } -}
\ No newline at end of file +} diff --git a/remoting/android/java/src/org/chromium/chromoting/cardboard/Cursor.java b/remoting/android/java/src/org/chromium/chromoting/cardboard/Cursor.java index 34403d6..534728a 100644 --- a/remoting/android/java/src/org/chromium/chromoting/cardboard/Cursor.java +++ b/remoting/android/java/src/org/chromium/chromoting/cardboard/Cursor.java @@ -13,7 +13,7 @@ import android.graphics.PointF; import android.opengl.GLES20; import org.chromium.chromoting.TouchInputHandler; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import java.nio.FloatBuffer; @@ -52,6 +52,8 @@ public class Cursor { // Threshold to determine whether to send the mouse move event. private static final float CURSOR_MOVE_THRESHOLD = 1.0f; + private final Client mClient; + private FloatBuffer mPositionCoordinates; private int mVertexShaderHandle; @@ -76,7 +78,8 @@ public class Cursor { private PointF mCursorPosition; - public Cursor() { + public Cursor(Client client) { + mClient = client; mHalfFrameSize = new PointF(0.0f, 0.0f); mCursorPosition = new PointF(0.0f, 0.0f); @@ -120,7 +123,7 @@ public class Cursor { */ public void moveTo(PointF position) { if (moveCursor(position)) { - JniInterface.sendMouseEvent((int) position.x, (int) position.y, + mClient.sendMouseEvent((int) position.x, (int) position.y, TouchInputHandler.BUTTON_UNDEFINED, false); } mCursorPosition = position; @@ -137,7 +140,7 @@ public class Cursor { } } - Bitmap cursorBitmap = JniInterface.getCursorBitmap(); + Bitmap cursorBitmap = mClient.getCursorBitmap(); if (cursorBitmap == mCursorBitmap) { // Case when cursor image has not changed. @@ -148,7 +151,7 @@ public class Cursor { } mCursorBitmap = cursorBitmap; - updatePosition(desktop, mCursorBitmap, JniInterface.getCursorHotspot()); + updatePosition(desktop, mCursorBitmap, mClient.getCursorHotspot()); TextureHelper.linkTexture(mTextureDataHandle, cursorBitmap); diff --git a/remoting/android/java/src/org/chromium/chromoting/cardboard/Desktop.java b/remoting/android/java/src/org/chromium/chromoting/cardboard/Desktop.java index ded464b..d987f6b 100644 --- a/remoting/android/java/src/org/chromium/chromoting/cardboard/Desktop.java +++ b/remoting/android/java/src/org/chromium/chromoting/cardboard/Desktop.java @@ -11,7 +11,7 @@ import android.graphics.Bitmap; import android.graphics.Point; import android.opengl.GLES20; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import java.nio.FloatBuffer; @@ -61,6 +61,8 @@ public class Desktop { // Number of vertices passed to glDrawArrays(). private static final int VERTICES_NUMBER = 6; + private final Client mClient; + private int mVertexShaderHandle; private int mFragmentShaderHandle; private int mProgramHandle; @@ -87,7 +89,8 @@ public class Desktop { // Lock to allow multithreaded access to mReloadTexture. private final Object mReloadTextureLock = new Object(); - public Desktop() { + public Desktop(Client client) { + mClient = client; mVertexShaderHandle = ShaderHelper.compileShader(GLES20.GL_VERTEX_SHADER, VERTEX_SHADER); mFragmentShaderHandle = @@ -220,7 +223,7 @@ public class Desktop { } // TODO(shichengfeng): Record the time desktop drawing takes. - Bitmap bitmap = JniInterface.getVideoFrame(); + Bitmap bitmap = mClient.getVideoFrame(); if (bitmap == null) { // This can happen if the client is connected, but a complete video frame has not yet @@ -244,4 +247,4 @@ public class Desktop { mReloadTexture = true; } } -}
\ No newline at end of file +} diff --git a/remoting/android/java/src/org/chromium/chromoting/cardboard/DesktopActivity.java b/remoting/android/java/src/org/chromium/chromoting/cardboard/DesktopActivity.java index 1f37082..7800b4e 100644 --- a/remoting/android/java/src/org/chromium/chromoting/cardboard/DesktopActivity.java +++ b/remoting/android/java/src/org/chromium/chromoting/cardboard/DesktopActivity.java @@ -16,7 +16,7 @@ import com.google.vrtoolkit.cardboard.CardboardView; import org.chromium.chromoting.R; import org.chromium.chromoting.TouchInputHandler; -import org.chromium.chromoting.jni.JniInterface; +import org.chromium.chromoting.jni.Client; import java.util.ArrayList; @@ -28,6 +28,7 @@ public class DesktopActivity extends CardboardActivity { // desktop activity. private boolean mSwitchToDesktopActivity; + private Client mClient; private CardboardRenderer mRenderer; private SpeechRecognizer mSpeechRecognizer; @@ -38,9 +39,12 @@ public class DesktopActivity extends CardboardActivity { public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.cardboard_desktop); + + mClient = Client.getInstance(); + mSwitchToDesktopActivity = false; CardboardView cardboardView = (CardboardView) findViewById(R.id.cardboard_view); - mRenderer = new CardboardRenderer(this); + mRenderer = new CardboardRenderer(this, mClient); mIsListening = false; // Associate a CardboardView.StereoRenderer with cardboard view. @@ -75,9 +79,9 @@ public class DesktopActivity extends CardboardActivity { } else { if (mRenderer.isLookingAtDesktop()) { PointF coordinates = mRenderer.getMouseCoordinates(); - JniInterface.sendMouseEvent((int) coordinates.x, (int) coordinates.y, + mClient.sendMouseEvent((int) coordinates.x, (int) coordinates.y, TouchInputHandler.BUTTON_LEFT, true); - JniInterface.sendMouseEvent((int) coordinates.x, (int) coordinates.y, + mClient.sendMouseEvent((int) coordinates.x, (int) coordinates.y, TouchInputHandler.BUTTON_LEFT, false); } else { if (mRenderer.isLookingFarawayFromDesktop()) { @@ -92,14 +96,14 @@ public class DesktopActivity extends CardboardActivity { @Override protected void onStart() { super.onStart(); - JniInterface.enableVideoChannel(true); + mClient.enableVideoChannel(true); } @Override protected void onPause() { super.onPause(); if (!mSwitchToDesktopActivity) { - JniInterface.enableVideoChannel(false); + mClient.enableVideoChannel(false); } if (mSpeechRecognizer != null) { mSpeechRecognizer.stopListening(); @@ -109,7 +113,7 @@ public class DesktopActivity extends CardboardActivity { @Override protected void onResume() { super.onResume(); - JniInterface.enableVideoChannel(true); + mClient.enableVideoChannel(true); } @Override @@ -118,7 +122,7 @@ public class DesktopActivity extends CardboardActivity { if (mSwitchToDesktopActivity) { mSwitchToDesktopActivity = false; } else { - JniInterface.enableVideoChannel(false); + mClient.enableVideoChannel(false); } if (mSpeechRecognizer != null) { mSpeechRecognizer.stopListening(); @@ -186,7 +190,7 @@ public class DesktopActivity extends CardboardActivity { ArrayList<String> data = results.getStringArrayList(SpeechRecognizer.RESULTS_RECOGNITION); if (!data.isEmpty()) { - JniInterface.sendTextEvent(data.get(0)); + mClient.sendTextEvent(data.get(0)); } } diff --git a/remoting/android/java/src/org/chromium/chromoting/jni/Client.java b/remoting/android/java/src/org/chromium/chromoting/jni/Client.java index 6c67a8c..451a5d4 100644 --- a/remoting/android/java/src/org/chromium/chromoting/jni/Client.java +++ b/remoting/android/java/src/org/chromium/chromoting/jni/Client.java @@ -4,7 +4,18 @@ package org.chromium.chromoting.jni; +import android.graphics.Bitmap; +import android.graphics.Point; +import android.os.Looper; + +import org.chromium.base.Log; import org.chromium.base.annotations.JNINamespace; +import org.chromium.base.annotations.SuppressFBWarnings; +import org.chromium.chromoting.CapabilityManager; +import org.chromium.chromoting.SessionAuthenticator; + +import java.nio.ByteBuffer; +import java.nio.ByteOrder; /** * Class to manage a client connection to the host. This class controls the lifetime of the @@ -14,18 +25,379 @@ import org.chromium.base.annotations.JNINamespace; */ @JNINamespace("remoting") public class Client { + private static final String TAG = "Chromoting"; + // Pointer to the C++ object, cast to a |long|. private long mNativeJniClient; - public void init() { + // The global Client instance (may be null). This needs to be a global singleton so that the + // Client can be passed between Activities. + private static Client sClient; + + // Called on the UI thread. + public Client() { + if (sClient != null) { + throw new RuntimeException("Client instance already created."); + } + + sClient = this; mNativeJniClient = nativeInit(); } private native long nativeInit(); + // Called on the UI thread. Suppress FindBugs warning, since |sClient| is only used on the + // UI thread. + @SuppressFBWarnings("LI_LAZY_INIT_STATIC") public void destroy() { - nativeDestroy(mNativeJniClient); + if (sClient != null) { + disconnectFromHost(); + nativeDestroy(mNativeJniClient); + sClient = null; + } } private native void nativeDestroy(long nativeJniClient); + + /** Returns the current Client instance, or null. */ + public static Client getInstance() { + return sClient; + } + + /** Used for authentication-related UX during connection. Accessed on the UI thread. */ + private SessionAuthenticator mAuthenticator; + + /** Whether the native code is attempting a connection. Accessed on the UI thread. */ + private boolean mConnected; + + /** Notified upon successful connection or disconnection. Accessed on the UI thread. */ + private ConnectionListener mConnectionListener; + + /** + * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and + * graphics threads. + */ + private Runnable mRedrawCallback; + + /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */ + private Bitmap mFrameBitmap; + + /** Protects access to {@link mFrameBitmap}. */ + private final Object mFrameLock = new Object(); + + /** Position of cursor hot-spot. Accessed on the graphics thread. */ + private Point mCursorHotspot = new Point(); + + /** Bitmap holding the cursor shape. Accessed on the graphics thread. */ + private Bitmap mCursorBitmap; + + /** Capability Manager through which capabilities and extensions are handled. */ + private CapabilityManager mCapabilityManager = new CapabilityManager(); + + public CapabilityManager getCapabilityManager() { + return mCapabilityManager; + } + + /** Returns whether the client is connected. */ + public boolean isConnected() { + return mConnected; + } + + /** Attempts to form a connection to the user-selected host. Called on the UI thread. */ + public void connectToHost(String username, String authToken, String hostJid, + String hostId, String hostPubkey, SessionAuthenticator authenticator, String flags, + ConnectionListener listener) { + disconnectFromHost(); + + mConnectionListener = listener; + mAuthenticator = authenticator; + JniInterface.nativeConnect(username, authToken, hostJid, hostId, hostPubkey, + mAuthenticator.getPairingId(hostId), mAuthenticator.getPairingSecret(hostId), + mCapabilityManager.getLocalCapabilities(), flags); + mConnected = true; + } + + /** Severs the connection and cleans up. Called on the UI thread. */ + public void disconnectFromHost() { + if (!mConnected) { + return; + } + + mConnectionListener.onConnectionState( + ConnectionListener.State.CLOSED, ConnectionListener.Error.OK); + + disconnectFromHostWithoutNotification(); + } + + /** Same as disconnectFromHost() but without notifying the ConnectionListener. */ + private void disconnectFromHostWithoutNotification() { + if (!mConnected) { + return; + } + + JniInterface.nativeDisconnect(); + mConnectionListener = null; + mConnected = false; + mCapabilityManager.onHostDisconnect(); + + // Drop the reference to free the Bitmap for GC. + synchronized (mFrameLock) { + mFrameBitmap = null; + } + } + + /** Called by native code whenever the connection status changes. Called on the UI thread. */ + void onConnectionState(int stateCode, int errorCode) { + ConnectionListener.State state = ConnectionListener.State.fromValue(stateCode); + ConnectionListener.Error error = ConnectionListener.Error.fromValue(errorCode); + mConnectionListener.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. + + // TODO(lambroslambrou): Connection state notifications for separate sessions should + // go to separate Client instances. Once this is true, we can remove this line and + // simplify the disconnectFromHost() code. + disconnectFromHostWithoutNotification(); + } + } + + /** + * Called by JniInterface (from native code) to prompt the user to enter a PIN. Called on the + * UI thread. + */ + void displayAuthenticationPrompt(boolean pairingSupported) { + mAuthenticator.displayAuthenticationPrompt(pairingSupported); + } + + /** + * Called by the SessionAuthenticator after the user enters a 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 void handleAuthenticationResponse( + String pin, boolean createPair, String deviceName) { + assert mConnected; + JniInterface.nativeAuthenticationResponse(pin, createPair, deviceName); + } + + /** + * Called by JniInterface (from native code), to save newly-received pairing credentials to + * permanent storage. Called on the UI thread. + */ + void commitPairingCredentials(String host, String id, String secret) { + mAuthenticator.commitPairingCredentials(host, id, secret); + } + + /** + * Moves the mouse cursor, possibly while clicking the specified (nonnegative) button. Called + * on the UI thread. + */ + public void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown) { + if (!mConnected) { + return; + } + + JniInterface.nativeSendMouseEvent(x, y, whichButton, buttonDown); + } + + /** Injects a mouse-wheel event with delta values. Called on the UI thread. */ + public void sendMouseWheelEvent(int deltaX, int deltaY) { + if (!mConnected) { + return; + } + + JniInterface.nativeSendMouseWheelEvent(deltaX, deltaY); + } + + /** + * Presses or releases the specified key. Called on the UI thread. If scanCode is not zero then + * keyCode is ignored. + */ + public boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) { + if (!mConnected) { + return false; + } + + return JniInterface.nativeSendKeyEvent(scanCode, keyCode, keyDown); + } + + /** Sends TextEvent to the host. Called on the UI thread. */ + public void sendTextEvent(String text) { + if (!mConnected) { + return; + } + + JniInterface.nativeSendTextEvent(text); + } + + /** Sends an array of TouchEvents to the host. Called on the UI thread. */ + public void sendTouchEvent(TouchEventData.EventType eventType, TouchEventData[] data) { + if (!mConnected) { + return; + } + + JniInterface.nativeSendTouchEvent(eventType.value(), data); + } + + /** + * Enables or disables the video channel. Called on the UI thread in response to Activity + * lifecycle events. + */ + public void enableVideoChannel(boolean enable) { + if (!mConnected) { + return; + } + + JniInterface.nativeEnableVideoChannel(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 void provideRedrawCallback(Runnable redrawCallback) { + mRedrawCallback = redrawCallback; + } + + /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */ + public boolean redrawGraphics() { + if (!mConnected || mRedrawCallback == null) return false; + + JniInterface.nativeScheduleRedraw(); + return true; + } + + /** + * Called by JniInterface to perform the redrawing callback requested by + * {@link #redrawGraphics}. This is a no-op if the window isn't visible (the callback is null). + * Called on the graphics thread. + */ + void redrawGraphicsInternal() { + Runnable callback = mRedrawCallback; + if (callback != null) { + callback.run(); + } + } + + /** + * Returns a bitmap of the latest video frame. Called on the native graphics thread when + * DesktopView is repainted. + */ + public Bitmap getVideoFrame() { + if (Looper.myLooper() == Looper.getMainLooper()) { + Log.w(TAG, "Canvas being redrawn on UI thread"); + } + + synchronized (mFrameLock) { + return mFrameBitmap; + } + } + + /** + * Called by JniInterface (from native code) to set a new video frame. Called on the native + * graphics thread when a new frame is allocated. + */ + void setVideoFrame(Bitmap bitmap) { + if (Looper.myLooper() == Looper.getMainLooper()) { + Log.w(TAG, "Video frame updated on UI thread"); + } + + synchronized (mFrameLock) { + mFrameBitmap = bitmap; + } + } + + /** + * Creates a new Bitmap to hold video frame pixels. Called by JniInterface (from native code), + * and the returned Bitmap is referenced by native code which writes the decoded frame pixels + * to it. + */ + static Bitmap newBitmap(int width, int height) { + return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + } + + /** + * Called by JniInterface (from native code) to update the cursor shape. This is called on the + * graphics thread when receiving a new cursor shape from the host. + */ + void updateCursorShape( + int width, int height, int hotspotX, int hotspotY, ByteBuffer buffer) { + mCursorHotspot = new Point(hotspotX, hotspotY); + + int[] data = new int[width * height]; + buffer.order(ByteOrder.LITTLE_ENDIAN); + buffer.asIntBuffer().get(data, 0, data.length); + mCursorBitmap = Bitmap.createBitmap(data, width, height, Bitmap.Config.ARGB_8888); + } + + /** Position of cursor hotspot within cursor image. Called on the graphics thread. */ + public Point getCursorHotspot() { + return mCursorHotspot; + } + + /** Returns the current cursor shape. Called on the graphics thread. */ + public Bitmap getCursorBitmap() { + return mCursorBitmap; + } + + // + // Third Party Authentication + // + + /** + * Called by JniInterface (from native code), to pop up a third party login page to fetch the + * token required for authentication. + */ + void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) { + mAuthenticator.fetchThirdPartyToken(tokenUrl, clientId, scope); + } + + /** + * Called by the SessionAuthenticator to pass the |token| and |sharedSecret| to native code to + * continue authentication. + */ + public void onThirdPartyTokenFetched(String token, String sharedSecret) { + if (!mConnected) { + return; + } + + JniInterface.nativeOnThirdPartyTokenFetched(token, sharedSecret); + } + + // + // Host and Client Capabilities + // + + /** + * Called by JniInterface (from native code) to set the list of negotiated capabilities between + * host and client. Called on the UI thread. + */ + void setCapabilities(String capabilities) { + mCapabilityManager.setNegotiatedCapabilities(capabilities); + } + + // + // Extension Message Handling + // + + /** + * Called by JniInterface (from native code), to pass on the deconstructed ExtensionMessage to + * the app. Called on the UI thread. + */ + void handleExtensionMessage(String type, String data) { + mCapabilityManager.onExtensionMessage(type, data); + } + + /** Sends an extension message to the Chromoting host. Called on the UI thread. */ + public void sendExtensionMessage(String type, String data) { + if (!mConnected) { + return; + } + + JniInterface.nativeSendExtensionMessage(type, data); + } } diff --git a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java index 0238702..92a1447 100644 --- a/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java +++ b/remoting/android/java/src/org/chromium/chromoting/jni/JniInterface.java @@ -6,18 +6,12 @@ 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.SessionAuthenticator; import java.nio.ByteBuffer; -import java.nio.ByteOrder; /** * Initializes the Chromium remoting library, and provides JNI calls into it. @@ -25,47 +19,12 @@ import java.nio.ByteOrder; */ @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; - - /* - * 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. @@ -90,203 +49,65 @@ public class JniInterface { 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, SessionAuthenticator authenticator, String flags, - ConnectionListener listener) { - disconnectFromHost(); - - sConnectionListener = listener; - sAuthenticator = authenticator; - nativeConnect(username, authToken, hostJid, hostId, hostPubkey, - sAuthenticator.getPairingId(hostId), sAuthenticator.getPairingSecret(hostId), - sCapabilityManager.getLocalCapabilities(), flags); - sConnected = true; - } - /** Performs the native portion of the connection. */ - private static native void nativeConnect(String username, String authToken, String hostJid, + static native void nativeConnect(String username, String authToken, String hostJid, String hostId, String hostPubkey, String pairId, String pairSecret, String capabilities, String flags); - /** 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(); + 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(); + if (Client.getInstance() != null) { + Client.getInstance().onConnectionState(stateCode, errorCode); } } /** 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); + if (Client.getInstance() != null) { + Client.getInstance().displayAuthenticationPrompt(pairingSupported); + } } - /** Native implementation of handleAuthenticationResponse(). */ - private static native void nativeAuthenticationResponse( + /** Native implementation of Client.handleAuthenticationResponse(). */ + 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; + if (Client.getInstance() != null) { + Client.getInstance().commitPairingCredentials(host, id, secret); } - - nativeSendMouseEvent(x, y, whichButton, buttonDown); } /** Passes mouse information to the native handling code. */ - private static native void nativeSendMouseEvent( + 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); - } + static native void nativeSendMouseWheelEvent(int deltaX, int deltaY); /** * 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); - } + static native boolean nativeSendKeyEvent(int scanCode, int keyCode, boolean keyDown); /** 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); - } + static native void nativeSendTextEvent(String text); /** Passes touch event information to the native handling code. */ - private static native void nativeSendTouchEvent(int eventType, TouchEventData[] data); + 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; - } + /** Native implementation of Client.enableVideoChannel() */ + static native void nativeEnableVideoChannel(boolean enable); /** Schedules a redraw on the native graphics thread. */ - private static native void nativeScheduleRedraw(); + static native void nativeScheduleRedraw(); /** * Performs the redrawing callback. This is a no-op if the window isn't visible. Called on the @@ -294,23 +115,9 @@ public class JniInterface { */ @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; + Client client = Client.getInstance(); + if (client != null) { + client.redrawGraphicsInternal(); } } @@ -319,12 +126,9 @@ public class JniInterface { */ @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; + Client client = Client.getInstance(); + if (client != null) { + client.setVideoFrame(bitmap); } } @@ -334,7 +138,7 @@ public class JniInterface { */ @CalledByNative private static Bitmap newBitmap(int width, int height) { - return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + return Client.newBitmap(width, height); } /** @@ -342,24 +146,12 @@ public class JniInterface { * shape from the host. */ @CalledByNative - public static void updateCursorShape( + private 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; + Client client = Client.getInstance(); + if (client != null) { + client.updateCursorShape(width, height, hotspotX, hotspotY, buffer); + } } // @@ -368,23 +160,14 @@ public class JniInterface { /** 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; + private static void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) { + if (Client.getInstance() != null) { + Client.getInstance().fetchThirdPartyToken(tokenUrl, clientId, scope); } - - nativeOnThirdPartyTokenFetched(token, sharedSecret); } /** Passes authentication data to the native handling code. */ - private static native void nativeOnThirdPartyTokenFetched(String token, String sharedSecret); + static native void nativeOnThirdPartyTokenFetched(String token, String sharedSecret); // // Host and Client Capabilities @@ -392,8 +175,10 @@ public class JniInterface { /** 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); + private static void setCapabilities(String capabilities) { + if (Client.getInstance() != null) { + Client.getInstance().setCapabilities(capabilities); + } } // @@ -402,18 +187,12 @@ public class JniInterface { /** 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; + private static void handleExtensionMessage(String type, String data) { + if (Client.getInstance() != null) { + Client.getInstance().handleExtensionMessage(type, data); } - - nativeSendExtensionMessage(type, data); } - private static native void nativeSendExtensionMessage(String type, String data); + /** Passes extension message to the native code. */ + static native void nativeSendExtensionMessage(String type, String data); } diff --git a/remoting/android/javatests/src/org/chromium/chromoting/TouchInputStrategyTest.java b/remoting/android/javatests/src/org/chromium/chromoting/TouchInputStrategyTest.java index c7774f7..3dea369 100644 --- a/remoting/android/javatests/src/org/chromium/chromoting/TouchInputStrategyTest.java +++ b/remoting/android/javatests/src/org/chromium/chromoting/TouchInputStrategyTest.java @@ -197,7 +197,10 @@ public class TouchInputStrategyTest extends InstrumentationTestCase { public void setUp() { mRenderData = new RenderData(); mRemoteInputInjector = new MockRemoteInputInjector(); - mInputStrategy = new TouchInputStrategy(mRenderData); + + // TODO(lambroslambrou): Provide a mock Client implementation that doesn't call out to JNI, + // and mock the Client methods instead of using MockRemoteInputInjector here. + mInputStrategy = new TouchInputStrategy(mRenderData, null); mInputStrategy.setRemoteInputInjectorForTest(mRemoteInputInjector); mEventGenerator = new TouchEventGenerator(); |