44 public static final float MAX_SCALE_FACTOR = 3.0f;
45 public static final float MIN_SCALE_FACTOR = 0.75f;
46 private static final String TAG =
"SessionView";
47 private static final float SCALE_FACTOR_DELTA = 0.0001f;
48 private static final float TOUCH_SCROLL_DELTA = 10.0f;
51 private BitmapDrawable surface;
52 private Stack<Rect> invalidRegions;
53 private int touchPointerPaddingWidth = 0;
54 private int touchPointerPaddingHeight = 0;
57 private boolean railMode =
false;
59 private float scaleFactor = 1.0f;
60 private Matrix scaleMatrix;
61 private Matrix invScaleMatrix;
62 private RectF invalidRegionF;
63 private GestureDetector gestureDetector;
64 private SessionState currentSession;
67 private float notchPxX = 1.0f;
68 private float notchPxY = 1.0f;
69 private float touchpadScrollX = 0;
70 private float touchpadScrollY = 0;
72 private int[] cursorPixels =
null;
73 private int cursorWidth = 0;
74 private int cursorHeight = 0;
75 private int cursorHotX = 0;
76 private int cursorHotY = 0;
79 private DoubleGestureDetector doubleGestureDetector;
83 initSessionView(context);
86 public SessionView(Context context, AttributeSet attrs)
88 super(context, attrs);
89 initSessionView(context);
92 public SessionView(Context context, AttributeSet attrs,
int defStyle)
94 super(context, attrs, defStyle);
95 initSessionView(context);
98 private void initSessionView(Context context)
102 setFocusableInTouchMode(
true);
104 invalidRegions =
new Stack<>();
105 gestureDetector =
new GestureDetector(context,
new SessionGestureListener(),
null,
true);
106 ViewConfiguration vc = ViewConfiguration.get(context);
107 notchPxX = Math.max(1.0f, vc.getScaledHorizontalScrollFactor());
108 notchPxY = Math.max(1.0f, vc.getScaledVerticalScrollFactor());
109 doubleGestureDetector =
110 new DoubleGestureDetector(context,
null,
new SessionDoubleGestureListener());
113 scaleMatrix =
new Matrix();
114 invScaleMatrix =
new Matrix();
115 invalidRegionF =
new RectF();
119 @Override
public boolean onHoverEvent(MotionEvent event)
121 if (event.getAction() == MotionEvent.ACTION_HOVER_MOVE)
124 float x =
event.getX();
125 float y =
event.getY();
127 MotionEvent mappedEvent = mapTouchEvent(event);
128 sessionViewListener.onSessionViewMouseMove((
int)mappedEvent.getX(),
129 (
int)mappedEvent.getY());
130 mappedEvent.recycle();
136 public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
138 doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector);
143 this.sessionViewListener = sessionViewListener;
146 public void addInvalidRegion(Rect invalidRegion)
149 invalidRegionF.set(invalidRegion);
150 scaleMatrix.mapRect(invalidRegionF);
151 invalidRegionF.roundOut(invalidRegion);
153 invalidRegions.add(invalidRegion);
156 public void invalidateRegion()
158 invalidate(invalidRegions.pop());
161 public void onSurfaceChange(SessionState session)
163 surface = session.getSurface();
164 Bitmap bitmap = surface.getBitmap();
165 width = bitmap.getWidth();
166 height = bitmap.getHeight();
167 surface.setBounds(0, 0, width, height);
169 setMinimumWidth(width);
170 setMinimumHeight(height);
173 currentSession = session;
176 public float getZoom()
181 public void setRailMode(
boolean rail)
183 if (railMode == rail)
191 void onZoomChanged(
float zoom);
196 zoomChangedListener = l;
199 public void setZoom(
float factor)
201 scaleFactor = factor;
202 scaleMatrix.setScale(scaleFactor, scaleFactor);
203 invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor);
205 if (cursorPixels !=
null)
208 if (zoomChangedListener !=
null)
209 zoomChangedListener.onZoomChanged(scaleFactor);
214 public boolean isAtMaxZoom()
216 return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA));
219 public boolean isAtMinZoom()
221 return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA));
224 public boolean zoomIn(
float factor)
227 scaleFactor += factor;
228 if (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA))
230 scaleFactor = MAX_SCALE_FACTOR;
233 setZoom(scaleFactor);
237 public boolean zoomOut(
float factor)
240 scaleFactor -= factor;
241 if (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA))
243 scaleFactor = MIN_SCALE_FACTOR;
246 setZoom(scaleFactor);
250 public void setTouchPointerPadding(
int width,
int height)
252 touchPointerPaddingWidth = width;
253 touchPointerPaddingHeight = height;
257 public int getTouchPointerPaddingWidth()
259 return touchPointerPaddingWidth;
262 public int getTouchPointerPaddingHeight()
264 return touchPointerPaddingHeight;
267 @Override
public void onMeasure(
int widthMeasureSpec,
int heightMeasureSpec)
269 Log.v(TAG, width +
"x" + height);
270 this.setMeasuredDimension((
int)(width * scaleFactor) + touchPointerPaddingWidth,
271 (
int)(height * scaleFactor) + touchPointerPaddingHeight);
274 @Override
public void onDraw(@NonNull Canvas canvas)
276 super.onDraw(canvas);
279 canvas.concat(scaleMatrix);
280 canvas.drawColor(Color.BLACK);
281 if (!railMode && surface !=
null)
283 surface.draw(canvas);
289 private MotionEvent mapTouchEvent(MotionEvent event)
291 MotionEvent mappedEvent = MotionEvent.obtain(event);
292 float[] coordinates = { mappedEvent.getX(), mappedEvent.getY() };
293 invScaleMatrix.mapPoints(coordinates);
294 mappedEvent.setLocation(coordinates[0], coordinates[1]);
299 private MotionEvent mapDoubleTouchEvent(MotionEvent event)
301 MotionEvent mappedEvent = MotionEvent.obtain(event);
302 float[] coordinates = { (mappedEvent.getX(0) + mappedEvent.getX(1)) / 2,
303 (mappedEvent.getY(0) + mappedEvent.getY(1)) / 2 };
304 invScaleMatrix.mapPoints(coordinates);
305 mappedEvent.setLocation(coordinates[0], coordinates[1]);
309 @Override
public boolean onTouchEvent(MotionEvent event)
314 if (event.isFromSource(InputDevice.SOURCE_MOUSE))
316 int action =
event.getActionMasked();
317 if (event.getClassification() == MotionEvent.CLASSIFICATION_TWO_FINGER_SWIPE)
319 handleTouchpadScroll(event);
322 if (action == MotionEvent.ACTION_MOVE && event.getButtonState() != 0)
324 MotionEvent mapped = mapTouchEvent(event);
325 sessionViewListener.onSessionViewMouseMove((
int)mapped.getX(), (
int)mapped.getY());
332 boolean res = gestureDetector.onTouchEvent(event);
333 res |= doubleGestureDetector.onTouchEvent(event);
338 private void handleTouchpadScroll(MotionEvent event)
340 if (event.getActionMasked() != MotionEvent.ACTION_MOVE)
347 touchpadScrollY += gestureDistance(event, MotionEvent.AXIS_GESTURE_SCROLL_Y_DISTANCE);
348 while (Math.abs(touchpadScrollY) >= notchPxY)
350 final boolean down = touchpadScrollY > 0;
351 touchpadScrollY += down ? -notchPxY : notchPxY;
352 sessionViewListener.onSessionViewScroll(down);
355 touchpadScrollX += gestureDistance(event, MotionEvent.AXIS_GESTURE_SCROLL_X_DISTANCE);
356 while (Math.abs(touchpadScrollX) >= notchPxX)
358 final boolean right = touchpadScrollX > 0;
359 touchpadScrollX += right ? -notchPxX : notchPxX;
360 sessionViewListener.onSessionViewHScroll(right);
365 private static float gestureDistance(MotionEvent event,
int axis)
368 for (
int i = 0; i <
event.getHistorySize(); i++)
369 distance += event.getHistoricalAxisValue(axis, 0, i);
370 return distance +
event.getAxisValue(axis);
374 @Override
public boolean onGenericMotionEvent(MotionEvent event)
376 final boolean isPointer =
event.isFromSource(InputDevice.SOURCE_CLASS_POINTER);
380 final boolean isMouse =
event.isFromSource(InputDevice.SOURCE_MOUSE);
381 int action =
event.getActionMasked();
383 if (isMouse && (action == MotionEvent.ACTION_BUTTON_PRESS ||
384 action == MotionEvent.ACTION_BUTTON_RELEASE))
386 boolean down = action == MotionEvent.ACTION_BUTTON_PRESS;
387 MotionEvent mapped = mapTouchEvent(event);
388 int x = (int)mapped.getX();
389 int y = (int)mapped.getY();
392 switch (event.getActionButton())
394 case MotionEvent.BUTTON_PRIMARY:
396 sessionViewListener.onSessionViewBeginTouch();
397 sessionViewListener.onSessionViewLeftTouch(x, y, down);
399 sessionViewListener.onSessionViewEndTouch();
401 case MotionEvent.BUTTON_SECONDARY:
403 sessionViewListener.onSessionViewBeginTouch();
404 sessionViewListener.onSessionViewRightTouch(x, y, down);
406 case MotionEvent.BUTTON_TERTIARY:
407 sessionViewListener.onSessionViewMiddleTouch(x, y, down);
414 if (action == MotionEvent.ACTION_SCROLL)
416 float vScroll =
event.getAxisValue(MotionEvent.AXIS_VSCROLL);
417 float hScroll =
event.getAxisValue(MotionEvent.AXIS_HSCROLL);
419 sessionViewListener.onSessionViewScroll(vScroll > 0);
421 sessionViewListener.onSessionViewHScroll(hScroll > 0);
430 void onSessionViewBeginTouch();
432 void onSessionViewEndTouch();
434 void onSessionViewLeftTouch(
int x,
int y,
boolean down);
436 void onSessionViewMiddleTouch(
int x,
int y,
boolean down);
438 void onSessionViewRightTouch(
int x,
int y,
boolean down);
440 void onSessionViewMove(
int x,
int y);
442 void onSessionViewMouseMove(
int x,
int y);
444 void onSessionViewScroll(
boolean down);
446 void onSessionViewHScroll(
boolean right);
449 public void setRemoteCursor(
int[] pixels,
int width,
int height,
int hotX,
int hotY)
451 if (pixels ==
null || width == 0 || height == 0)
454 setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_NULL));
457 cursorPixels = pixels;
459 cursorHeight = height;
465 private void applyScaledCursor()
467 int scaledWidth = Math.max(1, (
int)(cursorWidth * scaleFactor));
468 int scaledHeight = Math.max(1, (
int)(cursorHeight * scaleFactor));
470 Bitmap.createBitmap(cursorPixels, cursorWidth, cursorHeight, Bitmap.Config.ARGB_8888);
471 Bitmap scaled = Bitmap.createScaledBitmap(bm, scaledWidth, scaledHeight,
true);
473 PointerIcon.create(scaled, cursorHotX * scaleFactor, cursorHotY * scaleFactor);
474 setPointerIcon(icon);
477 public void setDefaultCursor()
479 setPointerIcon(PointerIcon.getSystemIcon(getContext(), PointerIcon.TYPE_ARROW));
482 private class SessionGestureListener
extends GestureDetector.SimpleOnGestureListener
484 boolean longPressInProgress =
false;
486 public boolean onDown(MotionEvent e)
491 public boolean onUp(MotionEvent e)
493 sessionViewListener.onSessionViewEndTouch();
497 public void onLongPress(MotionEvent e)
499 MotionEvent mappedEvent = mapTouchEvent(e);
500 sessionViewListener.onSessionViewBeginTouch();
501 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
502 (
int)mappedEvent.getY(),
true);
503 longPressInProgress =
true;
506 public void onLongPressUp(MotionEvent e)
508 MotionEvent mappedEvent = mapTouchEvent(e);
509 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
510 (
int)mappedEvent.getY(),
false);
511 longPressInProgress =
false;
512 sessionViewListener.onSessionViewEndTouch();
515 public boolean onScroll(MotionEvent e1, MotionEvent e2,
float distanceX,
float distanceY)
517 if (longPressInProgress)
519 MotionEvent mappedEvent = mapTouchEvent(e2);
520 sessionViewListener.onSessionViewMove((
int)mappedEvent.getX(),
521 (
int)mappedEvent.getY());
528 public boolean onDoubleTap(MotionEvent e)
531 MotionEvent mappedEvent = mapTouchEvent(e);
532 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
533 (
int)mappedEvent.getY(),
true);
534 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
535 (
int)mappedEvent.getY(),
false);
539 public boolean onSingleTapUp(MotionEvent e)
543 if (e.getButtonState() != 0)
547 MotionEvent mappedEvent = mapTouchEvent(e);
548 sessionViewListener.onSessionViewBeginTouch();
549 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
550 (
int)mappedEvent.getY(),
true);
551 sessionViewListener.onSessionViewLeftTouch((
int)mappedEvent.getX(),
552 (
int)mappedEvent.getY(),
false);
553 sessionViewListener.onSessionViewEndTouch();
558 private class SessionDoubleGestureListener
559 implements DoubleGestureDetector.OnDoubleGestureListener
561 private MotionEvent prevEvent =
null;
563 public boolean onDoubleTouchDown(MotionEvent e)
565 sessionViewListener.onSessionViewBeginTouch();
566 prevEvent = MotionEvent.obtain(e);
570 public boolean onDoubleTouchUp(MotionEvent e)
572 if (prevEvent !=
null)
577 sessionViewListener.onSessionViewEndTouch();
581 public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2)
584 float deltaY = e2.getY() - prevEvent.getY();
585 if (deltaY > TOUCH_SCROLL_DELTA)
587 sessionViewListener.onSessionViewScroll(
true);
589 prevEvent = MotionEvent.obtain(e2);
591 else if (deltaY < -TOUCH_SCROLL_DELTA)
593 sessionViewListener.onSessionViewScroll(
false);
595 prevEvent = MotionEvent.obtain(e2);
600 public boolean onDoubleTouchSingleTap(MotionEvent e)
603 MotionEvent mappedEvent = mapDoubleTouchEvent(e);
604 sessionViewListener.onSessionViewRightTouch((
int)mappedEvent.getX(),
605 (
int)mappedEvent.getY(),
true);
606 sessionViewListener.onSessionViewRightTouch((
int)mappedEvent.getX(),
607 (
int)mappedEvent.getY(),
false);
612 @Override
public InputConnection onCreateInputConnection(EditorInfo outAttrs)
614 outAttrs.actionLabel =
null;
615 outAttrs.inputType = InputType.TYPE_NULL;
616 outAttrs.imeOptions = EditorInfo.IME_ACTION_NONE | EditorInfo.IME_FLAG_NO_EXTRACT_UI |
617 EditorInfo.IME_FLAG_NO_FULLSCREEN;
618 return new BaseInputConnection(
this,
false);