OLD | NEW |
---|---|
1 // Copyright 2015 The Chromium Authors. All rights reserved. | 1 // Copyright 2015 The Chromium Authors. All rights reserved. |
2 // Use of this source code is governed by a BSD-style license that can be | 2 // Use of this source code is governed by a BSD-style license that can be |
3 // found in the LICENSE file. | 3 // found in the LICENSE file. |
4 | 4 |
5 package org.chromium.chromoting.jni; | 5 package org.chromium.chromoting.jni; |
6 | 6 |
7 import android.graphics.Bitmap; | |
8 import android.graphics.Point; | |
9 import android.os.Looper; | |
10 | |
11 import org.chromium.base.Log; | |
7 import org.chromium.base.annotations.JNINamespace; | 12 import org.chromium.base.annotations.JNINamespace; |
13 import org.chromium.chromoting.CapabilityManager; | |
14 import org.chromium.chromoting.SessionAuthenticator; | |
15 | |
16 import java.nio.ByteBuffer; | |
17 import java.nio.ByteOrder; | |
8 | 18 |
9 /** | 19 /** |
10 * Class to manage a client connection to the host. This class controls the life time of the | 20 * Class to manage a client connection to the host. This class controls the life time of the |
11 * corresponding C++ object which implements the connection. A new object should be created for | 21 * corresponding C++ object which implements the connection. A new object should be created for |
12 * each connection to the host, so that notifications from a connection are alwa ys sent to the | 22 * each connection to the host, so that notifications from a connection are alwa ys sent to the |
13 * right object. | 23 * right object. |
14 */ | 24 */ |
15 @JNINamespace("remoting") | 25 @JNINamespace("remoting") |
16 public class Client { | 26 public class Client { |
27 private static final String TAG = "Chromoting"; | |
28 | |
17 // Pointer to the C++ object, cast to a |long|. | 29 // Pointer to the C++ object, cast to a |long|. |
18 private long mNativeJniClient; | 30 private long mNativeJniClient; |
19 | 31 |
20 public void init() { | 32 // The global Client instance (may be null). This needs to be a global singl eton so that the |
33 // Client can be passed between Activities. | |
34 private static Client sClient; | |
35 | |
36 // Called on the UI thread. | |
37 public Client() { | |
38 if (sClient != null) { | |
39 throw new RuntimeException("Client instance already created."); | |
40 } | |
41 | |
42 sClient = this; | |
21 mNativeJniClient = nativeInit(); | 43 mNativeJniClient = nativeInit(); |
22 } | 44 } |
23 | 45 |
24 private native long nativeInit(); | 46 private native long nativeInit(); |
25 | 47 |
48 // Called on the UI thread. | |
26 public void destroy() { | 49 public void destroy() { |
27 nativeDestroy(mNativeJniClient); | 50 if (sClient != null) { |
51 disconnectFromHost(); | |
52 nativeDestroy(mNativeJniClient); | |
53 sClient = null; | |
54 } | |
28 } | 55 } |
29 | 56 |
30 private native void nativeDestroy(long nativeJniClient); | 57 private native void nativeDestroy(long nativeJniClient); |
58 | |
59 /** Returns the current Client instance, or null. */ | |
60 public static Client getClient() { | |
Sergey Ulanov
2016/01/30 01:03:23
call it getInstance()?
Lambros
2016/02/03 22:57:43
Done.
| |
61 return sClient; | |
62 } | |
63 | |
64 /** Used for authentication-related UX during connection. Accessed on the UI thread. */ | |
65 private SessionAuthenticator mAuthenticator; | |
66 | |
67 /** Whether the native code is attempting a connection. Accessed on the UI t hread. */ | |
68 private boolean mConnected; | |
69 | |
70 /** Notified upon successful connection or disconnection. Accessed on the UI thread. */ | |
71 private ConnectionListener mConnectionListener; | |
72 | |
73 /** | |
74 * Callback invoked on the graphics thread to repaint the desktop. Accessed on the UI and | |
75 * graphics threads. | |
76 */ | |
77 private Runnable mRedrawCallback; | |
78 | |
79 /** Bitmap holding a copy of the latest video frame. Accessed on the UI and graphics threads. */ | |
80 private Bitmap mFrameBitmap; | |
81 | |
82 /** Protects access to {@link mFrameBitmap}. */ | |
83 private final Object mFrameLock = new Object(); | |
84 | |
85 /** Position of cursor hot-spot. Accessed on the graphics thread. */ | |
86 private Point mCursorHotspot = new Point(); | |
87 | |
88 /** Bitmap holding the cursor shape. Accessed on the graphics thread. */ | |
89 private Bitmap mCursorBitmap; | |
90 | |
91 /** Capability Manager through which capabilities and extensions are handled . */ | |
92 private CapabilityManager mCapabilityManager = new CapabilityManager(); | |
93 | |
94 public CapabilityManager getCapabilityManager() { | |
95 return mCapabilityManager; | |
96 } | |
97 | |
98 /** Returns whether the client is connected. */ | |
99 public boolean isConnected() { | |
100 return mConnected; | |
101 } | |
102 | |
103 /** Attempts to form a connection to the user-selected host. Called on the U I thread. */ | |
104 public void connectToHost(String username, String authToken, String hostJid, | |
105 String hostId, String hostPubkey, ConnectionListener listener, | |
106 SessionAuthenticator authenticator) { | |
107 disconnectFromHost(); | |
108 | |
109 mConnectionListener = listener; | |
110 mAuthenticator = authenticator; | |
111 JniInterface.nativeConnect(username, authToken, hostJid, hostId, hostPub key, | |
112 mAuthenticator.getPairingId(hostId), mAuthenticator.getPairingSe cret(hostId), | |
113 mCapabilityManager.getLocalCapabilities()); | |
114 mConnected = true; | |
115 } | |
116 | |
117 /** Severs the connection and cleans up. Called on the UI thread. */ | |
118 public void disconnectFromHost() { | |
119 if (!mConnected) { | |
120 return; | |
121 } | |
122 | |
123 mConnectionListener.onConnectionState( | |
124 ConnectionListener.State.CLOSED, ConnectionListener.Error.OK); | |
125 | |
126 disconnectFromHostWithoutNotification(); | |
127 } | |
128 | |
129 /** Same as disconnectFromHost() but without notifying the ConnectionListene r. */ | |
130 private void disconnectFromHostWithoutNotification() { | |
131 if (!mConnected) { | |
132 return; | |
133 } | |
134 | |
135 JniInterface.nativeDisconnect(); | |
136 mConnectionListener = null; | |
137 mConnected = false; | |
138 mCapabilityManager.onHostDisconnect(); | |
139 | |
140 // Drop the reference to free the Bitmap for GC. | |
141 synchronized (mFrameLock) { | |
142 mFrameBitmap = null; | |
143 } | |
144 } | |
145 | |
146 /** Called by native code whenever the connection status changes. Called on the UI thread. */ | |
147 void onConnectionState(int stateCode, int errorCode) { | |
148 ConnectionListener.State state = ConnectionListener.State.fromValue(stat eCode); | |
149 ConnectionListener.Error error = ConnectionListener.Error.fromValue(erro rCode); | |
150 mConnectionListener.onConnectionState(state, error); | |
151 if (state == ConnectionListener.State.FAILED || state == ConnectionListe ner.State.CLOSED) { | |
152 // Disconnect from the host here, otherwise the next time connectToH ost() is called, | |
153 // it will try to disconnect, triggering an incorrect status notific ation. | |
154 | |
155 // TODO(lambroslambrou): Connection state notifications for separate sessions should | |
156 // go to separate Client instances. Once this is true, we can remove this line and | |
157 // simplify the disconnectFromHost() code. | |
158 disconnectFromHostWithoutNotification(); | |
159 } | |
160 } | |
161 | |
162 /** | |
163 * Called by JniInterface (from native code) to prompt the user to enter a P IN. Called on the | |
164 * UI thread. | |
165 */ | |
166 void displayAuthenticationPrompt(boolean pairingSupported) { | |
167 mAuthenticator.displayAuthenticationPrompt(pairingSupported); | |
168 } | |
169 | |
170 /** | |
171 * Called by the SessionAuthenticator after the user enters a PIN. | |
172 * @param pin The entered PIN. | |
173 * @param createPair Whether to create a new pairing for this client. | |
174 * @param deviceName The device name to appear in the pairing registry. Only used if createPair | |
175 * is true. | |
176 */ | |
177 public void handleAuthenticationResponse( | |
178 String pin, boolean createPair, String deviceName) { | |
179 assert mConnected; | |
180 JniInterface.nativeAuthenticationResponse(pin, createPair, deviceName); | |
181 } | |
182 | |
183 /** | |
184 * Called by JniInterface (from native code), to save newly-received pairing credentials to | |
185 * permanent storage. Called on the UI thread. | |
186 */ | |
187 void commitPairingCredentials(String host, String id, String secret) { | |
188 mAuthenticator.commitPairingCredentials(host, id, secret); | |
189 } | |
190 | |
191 /** | |
192 * Moves the mouse cursor, possibly while clicking the specified (nonnegativ e) button. Called | |
193 * on the UI thread. | |
194 */ | |
195 public void sendMouseEvent(int x, int y, int whichButton, boolean buttonDown ) { | |
196 if (!mConnected) { | |
197 return; | |
198 } | |
199 | |
200 JniInterface.nativeSendMouseEvent(x, y, whichButton, buttonDown); | |
201 } | |
202 | |
203 /** Injects a mouse-wheel event with delta values. Called on the UI thread. */ | |
204 public void sendMouseWheelEvent(int deltaX, int deltaY) { | |
205 if (!mConnected) { | |
206 return; | |
207 } | |
208 | |
209 JniInterface.nativeSendMouseWheelEvent(deltaX, deltaY); | |
210 } | |
211 | |
212 /** | |
213 * Presses or releases the specified key. Called on the UI thread. If scanCo de is not zero then | |
214 * keyCode is ignored. | |
215 */ | |
216 public boolean sendKeyEvent(int scanCode, int keyCode, boolean keyDown) { | |
217 if (!mConnected) { | |
218 return false; | |
219 } | |
220 | |
221 return JniInterface.nativeSendKeyEvent(scanCode, keyCode, keyDown); | |
222 } | |
223 | |
224 /** Sends TextEvent to the host. Called on the UI thread. */ | |
225 public void sendTextEvent(String text) { | |
226 if (!mConnected) { | |
227 return; | |
228 } | |
229 | |
230 JniInterface.nativeSendTextEvent(text); | |
231 } | |
232 | |
233 /** Sends an array of TouchEvents to the host. Called on the UI thread. */ | |
234 public void sendTouchEvent(TouchEventData.EventType eventType, TouchEventDat a[] data) { | |
235 if (!mConnected) { | |
236 return; | |
237 } | |
238 | |
239 JniInterface.nativeSendTouchEvent(eventType.value(), data); | |
240 } | |
241 | |
242 /** | |
243 * Enables or disables the video channel. Called on the UI thread in respons e to Activity | |
244 * lifecycle events. | |
245 */ | |
246 public void enableVideoChannel(boolean enable) { | |
247 if (!mConnected) { | |
248 return; | |
249 } | |
250 | |
251 JniInterface.nativeEnableVideoChannel(enable); | |
252 } | |
253 | |
254 /** | |
255 * Sets the redraw callback to the provided functor. Provide a value of null whenever the | |
256 * window is no longer visible so that we don't continue to draw onto it. Ca lled on the UI | |
257 * thread. | |
258 */ | |
259 public void provideRedrawCallback(Runnable redrawCallback) { | |
260 mRedrawCallback = redrawCallback; | |
261 } | |
262 | |
263 /** Forces the native graphics thread to redraw to the canvas. Called on the UI thread. */ | |
264 public boolean redrawGraphics() { | |
265 if (!mConnected || mRedrawCallback == null) return false; | |
266 | |
267 JniInterface.nativeScheduleRedraw(); | |
268 return true; | |
269 } | |
270 | |
271 /** | |
272 * Called by JniInterface to perform the redrawing callback requested by | |
273 * {@link #redrawGraphics}. This is a no-op if the window isn't visible (the callback is null). | |
274 * Called on the graphics thread. | |
275 */ | |
276 void redrawGraphicsInternal() { | |
277 Runnable callback = mRedrawCallback; | |
278 if (callback != null) { | |
279 callback.run(); | |
280 } | |
281 } | |
282 | |
283 /** | |
284 * Returns a bitmap of the latest video frame. Called on the native graphics thread when | |
285 * DesktopView is repainted. | |
286 */ | |
287 public Bitmap getVideoFrame() { | |
288 if (Looper.myLooper() == Looper.getMainLooper()) { | |
289 Log.w(TAG, "Canvas being redrawn on UI thread"); | |
290 } | |
291 | |
292 synchronized (mFrameLock) { | |
293 return mFrameBitmap; | |
294 } | |
295 } | |
296 | |
297 /** | |
298 * Called by JniInterface (from native code) to set a new video frame. Calle d on the native | |
299 * graphics thread when a new frame is allocated. | |
300 */ | |
301 void setVideoFrame(Bitmap bitmap) { | |
302 if (Looper.myLooper() == Looper.getMainLooper()) { | |
303 Log.w(TAG, "Video frame updated on UI thread"); | |
304 } | |
305 | |
306 synchronized (mFrameLock) { | |
307 mFrameBitmap = bitmap; | |
308 } | |
309 } | |
310 | |
311 /** | |
312 * Creates a new Bitmap to hold video frame pixels. Called by JniInterface ( from native code), | |
313 * and the returned Bitmap is referenced by native code which writes the dec oded frame pixels | |
314 * to it. | |
315 */ | |
316 static Bitmap newBitmap(int width, int height) { | |
317 return Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); | |
318 } | |
319 | |
320 /** | |
321 * Called by JniInterface (from native code) to update the cursor shape. Thi s is called on the | |
322 * graphics thread when receiving a new cursor shape from the host. | |
323 */ | |
324 void updateCursorShape( | |
325 int width, int height, int hotspotX, int hotspotY, ByteBuffer buffer ) { | |
326 mCursorHotspot = new Point(hotspotX, hotspotY); | |
327 | |
328 int[] data = new int[width * height]; | |
329 buffer.order(ByteOrder.LITTLE_ENDIAN); | |
330 buffer.asIntBuffer().get(data, 0, data.length); | |
331 mCursorBitmap = Bitmap.createBitmap(data, width, height, Bitmap.Config.A RGB_8888); | |
332 } | |
333 | |
334 /** Position of cursor hotspot within cursor image. Called on the graphics t hread. */ | |
335 public Point getCursorHotspot() { | |
336 return mCursorHotspot; | |
337 } | |
338 | |
339 /** Returns the current cursor shape. Called on the graphics thread. */ | |
340 public Bitmap getCursorBitmap() { | |
341 return mCursorBitmap; | |
342 } | |
343 | |
344 // | |
345 // Third Party Authentication | |
346 // | |
347 | |
348 /** | |
349 * Called by JniInterface (from native code), to pop up a third party login page to fetch the | |
350 * token required for authentication. | |
351 */ | |
352 void fetchThirdPartyToken(String tokenUrl, String clientId, String scope) { | |
353 mAuthenticator.fetchThirdPartyToken(tokenUrl, clientId, scope); | |
354 } | |
355 | |
356 /** | |
357 * Called by the SessionAuthenticator to pass the |token| and |sharedSecret| to native code to | |
358 * continue authentication. | |
359 */ | |
360 public void onThirdPartyTokenFetched(String token, String sharedSecret) { | |
361 if (!mConnected) { | |
362 return; | |
363 } | |
364 | |
365 JniInterface.nativeOnThirdPartyTokenFetched(token, sharedSecret); | |
366 } | |
367 | |
368 // | |
369 // Host and Client Capabilities | |
370 // | |
371 | |
372 /** | |
373 * Called by JniInterface (from native code) to set the list of negotiated c apabilities between | |
374 * host and client. Called on the UI thread. | |
375 */ | |
376 void setCapabilities(String capabilities) { | |
377 mCapabilityManager.setNegotiatedCapabilities(capabilities); | |
378 } | |
379 | |
380 // | |
381 // Extension Message Handling | |
382 // | |
383 | |
384 /** | |
385 * Called by JniInterface (from native code), to pass on the deconstructed E xtensionMessage to | |
386 * the app. Called on the UI thread. | |
387 */ | |
388 void handleExtensionMessage(String type, String data) { | |
389 mCapabilityManager.onExtensionMessage(type, data); | |
390 } | |
391 | |
392 /** Sends an extension message to the Chromoting host. Called on the UI thre ad. */ | |
393 public void sendExtensionMessage(String type, String data) { | |
394 if (!mConnected) { | |
395 return; | |
396 } | |
397 | |
398 JniInterface.nativeSendExtensionMessage(type, data); | |
399 } | |
31 } | 400 } |
OLD | NEW |