| 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; | 5 package org.chromium.chromoting; |
| 6 | 6 |
| 7 import android.graphics.Matrix; | 7 import android.graphics.Matrix; |
| 8 import android.graphics.Point; |
| 8 import android.view.MotionEvent; | 9 import android.view.MotionEvent; |
| 9 | 10 |
| 10 import org.chromium.base.VisibleForTesting; | |
| 11 import org.chromium.chromoting.jni.Client; | |
| 12 import org.chromium.chromoting.jni.TouchEventData; | |
| 13 | |
| 14 import java.util.ArrayList; | |
| 15 import java.util.LinkedList; | 11 import java.util.LinkedList; |
| 16 import java.util.List; | |
| 17 import java.util.Queue; | 12 import java.util.Queue; |
| 18 | 13 |
| 19 /** | 14 /** |
| 20 * This class receives local touch input events and forwards them to the remote
host. | 15 * This class receives local touch input events and forwards them to the remote
host. |
| 21 * A queue of MotionEvents is built up and then either transmitted to the remote
host if one of its | 16 * A queue of MotionEvents is built up and then either transmitted to the remote
host if one of its |
| 22 * remote gesture handler methods is called (such as onScroll) or it is cleared
if the current | 17 * remote gesture handler methods is called (such as onScroll) or it is cleared
if the current |
| 23 * stream of events does not represent a remote gesture. | 18 * stream of events does not represent a remote gesture. |
| 24 * NOTE: Not all touch gestures are remoted. Touch input and gestures outside t
he supported ones | 19 * NOTE: Not all touch gestures are remoted. Touch input and gestures outside t
he supported ones |
| 25 * (which includes tapping and 2 finger panning) will either affect the lo
cal canvas or | 20 * (which includes tapping and 2 finger panning) will either affect the lo
cal canvas or |
| 26 * will be dropped/ignored. | 21 * will be dropped/ignored. |
| 27 */ | 22 */ |
| 28 public class TouchInputStrategy implements InputStrategyInterface { | 23 public class TouchInputStrategy implements InputStrategyInterface { |
| 29 /** | 24 /** |
| 30 * This interface abstracts the injection mechanism to allow for unit testin
g. | |
| 31 */ | |
| 32 public interface RemoteInputInjector { | |
| 33 public void injectMouseEvent(int x, int y, int button, boolean buttonDow
n); | |
| 34 public void injectTouchEvent(TouchEventData.EventType eventType, TouchEv
entData[] data); | |
| 35 } | |
| 36 | |
| 37 /** | |
| 38 * This class provides the default implementation for injecting remote event
s. | |
| 39 */ | |
| 40 private class DefaultInputInjector implements RemoteInputInjector { | |
| 41 @Override | |
| 42 public void injectMouseEvent(int x, int y, int button, boolean buttonDow
n) { | |
| 43 mClient.sendMouseEvent(x, y, button, buttonDown); | |
| 44 } | |
| 45 | |
| 46 @Override | |
| 47 public void injectTouchEvent(TouchEventData.EventType eventType, TouchEv
entData[] data) { | |
| 48 mClient.sendTouchEvent(eventType, data); | |
| 49 } | |
| 50 } | |
| 51 | |
| 52 /** | |
| 53 * Contains the maximum number of MotionEvents to store before cancelling th
e current gesture. | 25 * Contains the maximum number of MotionEvents to store before cancelling th
e current gesture. |
| 54 * The size is ~3x the largest number of events seen during any remotable ge
sture sequence. | 26 * The size is ~3x the largest number of events seen during any remotable ge
sture sequence. |
| 55 */ | 27 */ |
| 56 private static final int QUEUED_EVENT_THRESHOLD = 50; | 28 private static final int QUEUED_EVENT_THRESHOLD = 50; |
| 57 | 29 |
| 58 /** | 30 /** |
| 59 * Contains the set of MotionEvents received for the current gesture candida
te. If one of the | 31 * Contains the set of MotionEvents received for the current gesture candida
te. If one of the |
| 60 * gesture handling methods is called, these queued events will be transmitt
ed to the remote | 32 * gesture handling methods is called, these queued events will be transmitt
ed to the remote |
| 61 * host for injection. The queue has a maximum size determined by |QUEUED_E
VENT_THRESHOLD| to | 33 * host for injection. The queue has a maximum size determined by |QUEUED_E
VENT_THRESHOLD| to |
| 62 * prevent a live memory leak where the queue grows unbounded during a local
gesture (such as | 34 * prevent a live memory leak where the queue grows unbounded during a local
gesture (such as |
| 63 * someone panning the local canvas continuously for several seconds/minutes
). | 35 * someone panning the local canvas continuously for several seconds/minutes
). |
| 64 */ | 36 */ |
| 65 private Queue<MotionEvent> mQueuedEvents; | 37 private Queue<MotionEvent> mQueuedEvents; |
| 66 | 38 |
| 67 /** | 39 /** |
| 68 * Indicates that the events received should be treated as part of an active
remote gesture. | 40 * Indicates that the events received should be treated as part of an active
remote gesture. |
| 69 */ | 41 */ |
| 70 private boolean mInRemoteGesture = false; | 42 private boolean mInRemoteGesture = false; |
| 71 | 43 |
| 72 /** | 44 /** |
| 73 * Indicates whether MotionEvents and gestures should be acted upon or ignor
ed. This flag is | 45 * Indicates whether MotionEvents and gestures should be acted upon or ignor
ed. This flag is |
| 74 * set when we believe that the current sequence of events is not something
we should remote. | 46 * set when we believe that the current sequence of events is not something
we should remote. |
| 75 */ | 47 */ |
| 76 private boolean mIgnoreTouchEvents = false; | 48 private boolean mIgnoreTouchEvents = false; |
| 77 | 49 |
| 78 private final RenderData mRenderData; | 50 private final RenderData mRenderData; |
| 51 private final InputEventSender mInjector; |
| 79 | 52 |
| 80 private final Client mClient; | 53 public TouchInputStrategy(RenderData renderData, InputEventSender injector)
{ |
| 81 | 54 Preconditions.notNull(injector); |
| 82 private RemoteInputInjector mRemoteInputInjector; | |
| 83 | |
| 84 public TouchInputStrategy(RenderData renderData, Client client) { | |
| 85 mRenderData = renderData; | 55 mRenderData = renderData; |
| 86 mClient = client; | 56 mInjector = injector; |
| 87 mRemoteInputInjector = new DefaultInputInjector(); | |
| 88 mQueuedEvents = new LinkedList<MotionEvent>(); | 57 mQueuedEvents = new LinkedList<MotionEvent>(); |
| 89 | 58 |
| 90 synchronized (mRenderData) { | 59 synchronized (mRenderData) { |
| 91 mRenderData.drawCursor = false; | 60 mRenderData.drawCursor = false; |
| 92 } | 61 } |
| 93 } | 62 } |
| 94 | 63 |
| 95 @Override | 64 @Override |
| 96 public boolean onTap(int button) { | 65 public boolean onTap(int button) { |
| 97 if (mQueuedEvents.isEmpty() || mIgnoreTouchEvents) { | 66 if (mQueuedEvents.isEmpty() || mIgnoreTouchEvents) { |
| 98 return false; | 67 return false; |
| 99 } | 68 } |
| 100 | 69 |
| 101 switch (button) { | 70 switch (button) { |
| 102 case TouchInputHandlerInterface.BUTTON_LEFT: | 71 case InputStub.BUTTON_LEFT: |
| 103 injectQueuedEvents(); | 72 injectQueuedEvents(); |
| 104 return true; | 73 return true; |
| 105 | 74 |
| 106 case TouchInputHandlerInterface.BUTTON_RIGHT: | 75 case InputStub.BUTTON_RIGHT: |
| 107 // Using the mouse for right-clicking is consistent across all h
ost platforms. | 76 // Using the mouse for right-clicking is consistent across all h
ost platforms. |
| 108 // Right-click gestures are often platform specific and can be t
ricky to simulate. | 77 // Right-click gestures are often platform specific and can be t
ricky to simulate. |
| 109 | 78 |
| 110 // Grab the first queued event which should be the initial ACTIO
N_DOWN event. | 79 // Grab the first queued event which should be the initial ACTIO
N_DOWN event. |
| 111 MotionEvent downEvent = mQueuedEvents.peek(); | 80 MotionEvent downEvent = mQueuedEvents.peek(); |
| 112 assert downEvent.getActionMasked() == MotionEvent.ACTION_DOWN; | 81 assert downEvent.getActionMasked() == MotionEvent.ACTION_DOWN; |
| 113 | 82 |
| 114 int x = (int) downEvent.getX(); | 83 mInjector.sendMouseClick(new Point((int) downEvent.getX(), (int)
downEvent.getY()), |
| 115 int y = (int) downEvent.getY(); | 84 InputStub.BUTTON_RIGHT); |
| 116 mRemoteInputInjector.injectMouseEvent( | |
| 117 x, y, TouchInputHandlerInterface.BUTTON_RIGHT, true); | |
| 118 mRemoteInputInjector.injectMouseEvent( | |
| 119 x, y, TouchInputHandlerInterface.BUTTON_RIGHT, false); | |
| 120 clearQueuedEvents(); | 85 clearQueuedEvents(); |
| 121 return true; | 86 return true; |
| 122 | 87 |
| 123 default: | 88 default: |
| 124 // Tap gestures for > 2 fingers are not supported. | 89 // Tap gestures for > 2 fingers are not supported. |
| 125 return false; | 90 return false; |
| 126 } | 91 } |
| 127 } | 92 } |
| 128 | 93 |
| 129 @Override | 94 @Override |
| 130 public boolean onPressAndHold(int button) { | 95 public boolean onPressAndHold(int button) { |
| 131 if (button != TouchInputHandlerInterface.BUTTON_LEFT || mQueuedEvents.is
Empty() | 96 if (button != InputStub.BUTTON_LEFT || mQueuedEvents.isEmpty() |
| 132 || mIgnoreTouchEvents) { | 97 || mIgnoreTouchEvents) { |
| 133 return false; | 98 return false; |
| 134 } | 99 } |
| 135 | 100 |
| 136 mInRemoteGesture = true; | 101 mInRemoteGesture = true; |
| 137 injectQueuedEvents(); | 102 injectQueuedEvents(); |
| 138 return true; | 103 return true; |
| 139 } | 104 } |
| 140 | 105 |
| 141 @Override | 106 @Override |
| (...skipping 44 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 186 mQueuedEvents.add(transformToRemoteCoordinates(event)); | 151 mQueuedEvents.add(transformToRemoteCoordinates(event)); |
| 187 } | 152 } |
| 188 break; | 153 break; |
| 189 | 154 |
| 190 case MotionEvent.ACTION_CANCEL: | 155 case MotionEvent.ACTION_CANCEL: |
| 191 case MotionEvent.ACTION_MOVE: | 156 case MotionEvent.ACTION_MOVE: |
| 192 case MotionEvent.ACTION_POINTER_UP: | 157 case MotionEvent.ACTION_POINTER_UP: |
| 193 case MotionEvent.ACTION_UP: | 158 case MotionEvent.ACTION_UP: |
| 194 event = transformToRemoteCoordinates(event); | 159 event = transformToRemoteCoordinates(event); |
| 195 if (mInRemoteGesture) { | 160 if (mInRemoteGesture) { |
| 196 injectTouchEventData(event); | 161 mInjector.sendTouchEvent(event); |
| 197 event.recycle(); | 162 event.recycle(); |
| 198 } else { | 163 } else { |
| 199 mQueuedEvents.add(event); | 164 mQueuedEvents.add(event); |
| 200 } | 165 } |
| 201 break; | 166 break; |
| 202 | 167 |
| 203 default: | 168 default: |
| 204 break; | 169 break; |
| 205 } | 170 } |
| 206 } | 171 } |
| 207 | 172 |
| 208 @Override | 173 @Override |
| 209 public void injectCursorMoveEvent(int x, int y) {} | 174 public void injectCursorMoveEvent(int x, int y) {} |
| 210 | 175 |
| 211 @Override | 176 @Override |
| 212 public DesktopView.InputFeedbackType getShortPressFeedbackType() { | 177 public DesktopView.InputFeedbackType getShortPressFeedbackType() { |
| 213 return DesktopView.InputFeedbackType.NONE; | 178 return DesktopView.InputFeedbackType.NONE; |
| 214 } | 179 } |
| 215 | 180 |
| 216 @Override | 181 @Override |
| 217 public DesktopView.InputFeedbackType getLongPressFeedbackType() { | 182 public DesktopView.InputFeedbackType getLongPressFeedbackType() { |
| 218 return DesktopView.InputFeedbackType.LARGE_ANIMATION; | 183 return DesktopView.InputFeedbackType.LARGE_ANIMATION; |
| 219 } | 184 } |
| 220 | 185 |
| 221 @Override | 186 @Override |
| 222 public boolean isIndirectInputMode() { | 187 public boolean isIndirectInputMode() { |
| 223 return false; | 188 return false; |
| 224 } | 189 } |
| 225 | 190 |
| 226 @VisibleForTesting | |
| 227 protected void setRemoteInputInjectorForTest(RemoteInputInjector injector) { | |
| 228 mRemoteInputInjector = injector; | |
| 229 } | |
| 230 | |
| 231 /** | |
| 232 * Extracts the touch point data from a MotionEvent, converts each point int
o a marshallable | |
| 233 * object and passes the set of points to the JNI layer to be transmitted to
the remote host. | |
| 234 * | |
| 235 * @param event The event to send to the remote host for injection. NOTE: T
his object must be | |
| 236 * updated to represent the remote machine's coordinate system
before calling this | |
| 237 * function. This should be done via the transformToRemoteCoor
dinates() method. | |
| 238 */ | |
| 239 private void injectTouchEventData(MotionEvent event) { | |
| 240 int action = event.getActionMasked(); | |
| 241 TouchEventData.EventType touchEventType = TouchEventData.EventType.fromM
askedAction(action); | |
| 242 List<TouchEventData> touchEventList = new ArrayList<TouchEventData>(); | |
| 243 | |
| 244 if (action == MotionEvent.ACTION_MOVE) { | |
| 245 // In order to process all of the events associated with an ACTION_M
OVE event, we need | |
| 246 // to walk the list of historical events in order and add each event
to our list, then | |
| 247 // retrieve the current move event data. | |
| 248 int pointerCount = event.getPointerCount(); | |
| 249 int historySize = event.getHistorySize(); | |
| 250 for (int h = 0; h < historySize; ++h) { | |
| 251 for (int p = 0; p < pointerCount; ++p) { | |
| 252 touchEventList.add(new TouchEventData(event.getPointerId(p), | |
| 253 event.getHistoricalX(p, h), event.getHistoricalY(p,
h), | |
| 254 event.getHistoricalSize(p, h), event.getHistoricalSi
ze(p, h), | |
| 255 event.getHistoricalOrientation(p, h), | |
| 256 event.getHistoricalPressure(p, h))); | |
| 257 } | |
| 258 } | |
| 259 | |
| 260 for (int p = 0; p < pointerCount; p++) { | |
| 261 touchEventList.add(new TouchEventData(event.getPointerId(p), eve
nt.getX(p), | |
| 262 event.getY(p), event.getSize(p), event.getSize(p), event
.getOrientation(p), | |
| 263 event.getPressure(p))); | |
| 264 } | |
| 265 } else { | |
| 266 // For all other events, we only want to grab the current/active poi
nter. The event | |
| 267 // contains a list of every active pointer but passing all of of the
se to the host can | |
| 268 // cause confusion on the remote OS side and result in broken touch
gestures. | |
| 269 int activePointerIndex = event.getActionIndex(); | |
| 270 touchEventList.add(new TouchEventData(event.getPointerId(activePoint
erIndex), | |
| 271 event.getX(activePointerIndex), event.getY(activePointerInde
x), | |
| 272 event.getSize(activePointerIndex), event.getSize(activePoint
erIndex), | |
| 273 event.getOrientation(activePointerIndex), | |
| 274 event.getPressure(activePointerIndex))); | |
| 275 } | |
| 276 | |
| 277 if (!touchEventList.isEmpty()) { | |
| 278 TouchEventData[] touchEventArray = new TouchEventData[touchEventList
.size()]; | |
| 279 mRemoteInputInjector.injectTouchEvent( | |
| 280 touchEventType, touchEventList.toArray(touchEventArray)); | |
| 281 } | |
| 282 } | |
| 283 | |
| 284 private void injectQueuedEvents() { | 191 private void injectQueuedEvents() { |
| 285 while (!mQueuedEvents.isEmpty()) { | 192 while (!mQueuedEvents.isEmpty()) { |
| 286 MotionEvent event = mQueuedEvents.remove(); | 193 MotionEvent event = mQueuedEvents.remove(); |
| 287 injectTouchEventData(event); | 194 mInjector.sendTouchEvent(event); |
| 288 event.recycle(); | 195 event.recycle(); |
| 289 } | 196 } |
| 290 } | 197 } |
| 291 | 198 |
| 292 private void clearQueuedEvents() { | 199 private void clearQueuedEvents() { |
| 293 while (!mQueuedEvents.isEmpty()) { | 200 while (!mQueuedEvents.isEmpty()) { |
| 294 mQueuedEvents.remove().recycle(); | 201 mQueuedEvents.remove().recycle(); |
| 295 } | 202 } |
| 296 } | 203 } |
| 297 | 204 |
| (...skipping 12 matching lines...) Expand all Loading... |
| 310 | 217 |
| 311 return event; | 218 return event; |
| 312 } | 219 } |
| 313 | 220 |
| 314 private void resetStateData() { | 221 private void resetStateData() { |
| 315 clearQueuedEvents(); | 222 clearQueuedEvents(); |
| 316 mInRemoteGesture = false; | 223 mInRemoteGesture = false; |
| 317 mIgnoreTouchEvents = false; | 224 mIgnoreTouchEvents = false; |
| 318 } | 225 } |
| 319 } | 226 } |
| OLD | NEW |