/*
Android Session view
Copyright 2013 Thincast Technologies GmbH, Author: Martin Fleisz
This Source Code Form is subject to the terms of the Mozilla Public License, v. 2.0.
If a copy of the MPL was not distributed with this file, You can obtain one at
http://mozilla.org/MPL/2.0/.
*/
package com.freerdp.freerdpcore.presentation;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.Canvas;
import android.graphics.Matrix;
import android.graphics.Rect;
import android.graphics.RectF;
import android.graphics.drawable.BitmapDrawable;
import android.util.AttributeSet;
import android.util.Log;
import android.view.InputDevice;
import android.view.KeyEvent;
import android.view.MotionEvent;
import android.view.ScaleGestureDetector;
import android.view.View;
import com.freerdp.freerdpcore.application.SessionState;
import com.freerdp.freerdpcore.services.LibFreeRDP;
import com.freerdp.freerdpcore.utils.DoubleGestureDetector;
import com.freerdp.freerdpcore.utils.GestureDetector;
import com.freerdp.freerdpcore.utils.Mouse;
import java.util.Stack;
public class SessionView extends View
{
public static final float MAX_SCALE_FACTOR = 3.0f;
public static final float MIN_SCALE_FACTOR = 1.0f;
private static final String TAG = "SessionView";
private static final float SCALE_FACTOR_DELTA = 0.0001f;
private static final float TOUCH_SCROLL_DELTA = 10.0f;
private int width;
private int height;
private BitmapDrawable surface;
private Stack<Rect> invalidRegions;
private int touchPointerPaddingWidth = 0;
private int touchPointerPaddingHeight = 0;
private SessionViewListener sessionViewListener = null;
// helpers for scaling gesture handling
private float scaleFactor = 1.0f;
private Matrix scaleMatrix;
private Matrix invScaleMatrix;
private RectF invalidRegionF;
private GestureDetector gestureDetector;
private SessionState currentSession;
// private static final String TAG = "FreeRDP.SessionView";
private DoubleGestureDetector doubleGestureDetector;
public SessionView(Context context)
{
super(context);
initSessionView(context);
}
public SessionView(Context context, AttributeSet attrs)
{
super(context, attrs);
initSessionView(context);
}
public SessionView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initSessionView(context);
}
private void initSessionView(Context context)
{
invalidRegions = new Stack<Rect>();
gestureDetector = new GestureDetector(context, new SessionGestureListener(), null, true);
doubleGestureDetector =
new DoubleGestureDetector(context, null, new SessionDoubleGestureListener());
scaleFactor = 1.0f;
scaleMatrix = new Matrix();
invScaleMatrix = new Matrix();
invalidRegionF = new RectF();
setSystemUiVisibility(View.SYSTEM_UI_FLAG_HIDE_NAVIGATION |
View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY);
}
public void setScaleGestureDetector(ScaleGestureDetector scaleGestureDetector)
{
doubleGestureDetector.setScaleGestureDetector(scaleGestureDetector);
}
public void setSessionViewListener(SessionViewListener sessionViewListener)
{
this.sessionViewListener = sessionViewListener;
}
public void addInvalidRegion(Rect invalidRegion)
{
// correctly transform invalid region depending on current scaling
invalidRegionF.set(invalidRegion);
scaleMatrix.mapRect(invalidRegionF);
invalidRegionF.roundOut(invalidRegion);
invalidRegions.add(invalidRegion);
}
public void invalidateRegion()
{
invalidate(invalidRegions.pop());
}
public void onSurfaceChange(SessionState session)
{
surface = session.getSurface();
Bitmap bitmap = surface.getBitmap();
width = bitmap.getWidth();
height = bitmap.getHeight();
surface.setBounds(0, 0, width, height);
setMinimumWidth(width);
setMinimumHeight(height);
requestLayout();
currentSession = session;
}
public float getZoom()
{
return scaleFactor;
}
public void setZoom(float factor)
{
// calc scale matrix and inverse scale matrix (to correctly transform the view and moues
// coordinates)
scaleFactor = factor;
scaleMatrix.setScale(scaleFactor, scaleFactor);
invScaleMatrix.setScale(1.0f / scaleFactor, 1.0f / scaleFactor);
// update layout
requestLayout();
}
public boolean isAtMaxZoom()
{
return (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA));
}
public boolean isAtMinZoom()
{
return (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA));
}
public boolean zoomIn(float factor)
{
boolean res = true;
scaleFactor += factor;
if (scaleFactor > (MAX_SCALE_FACTOR - SCALE_FACTOR_DELTA))
{
scaleFactor = MAX_SCALE_FACTOR;
res = false;
}
setZoom(scaleFactor);
return res;
}
public boolean zoomOut(float factor)
{
boolean res = true;
scaleFactor -= factor;
if (scaleFactor < (MIN_SCALE_FACTOR + SCALE_FACTOR_DELTA))
{
scaleFactor = MIN_SCALE_FACTOR;
res = false;
}
setZoom(scaleFactor);
return res;
}
public void setTouchPointerPadding(int widht, int height)
{
touchPointerPaddingWidth = widht;
touchPointerPaddingHeight = height;
requestLayout();
}
public int getTouchPointerPaddingWidth()
{
return touchPointerPaddingWidth;
}
public int getTouchPointerPaddingHeight()
{
return touchPointerPaddingHeight;
}
@Override public void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
{
Log.v(TAG, width + "x" + height);
this.setMeasuredDimension((int)(width * scaleFactor) + touchPointerPaddingWidth,
(int)(height * scaleFactor) + touchPointerPaddingHeight);
}
@Override public void onDraw(Canvas canvas)
{
super.onDraw(canvas);
canvas.save();
canvas.concat(scaleMatrix);
surface.draw(canvas);
canvas.restore();
}
// dirty hack: we call back to our activity and call onBackPressed as this doesn't reach us when
// the soft keyboard is shown ...
@Override public boolean dispatchKeyEventPreIme(KeyEvent event)
{
if (event.getKeyCode() == KeyEvent.KEYCODE_BACK &&
event.getAction() == KeyEvent.ACTION_DOWN)
((SessionActivity)this.getContext()).onBackPressed();
return super.dispatchKeyEventPreIme(event);
}
// perform mapping on the touch event's coordinates according to the current scaling
private MotionEvent mapTouchEvent(MotionEvent event)
{
MotionEvent mappedEvent = MotionEvent.obtain(event);
float[] coordinates = { mappedEvent.getX(), mappedEvent.getY() };
invScaleMatrix.mapPoints(coordinates);
mappedEvent.setLocation(coordinates[0], coordinates[1]);
return mappedEvent;
}
// perform mapping on the double touch event's coordinates according to the current scaling
private MotionEvent mapDoubleTouchEvent(MotionEvent event)
{
MotionEvent mappedEvent = MotionEvent.obtain(event);
float[] coordinates = { (mappedEvent.getX(0) + mappedEvent.getX(1)) / 2,
(mappedEvent.getY(0) + mappedEvent.getY(1)) / 2 };
invScaleMatrix.mapPoints(coordinates);
mappedEvent.setLocation(coordinates[0], coordinates[1]);
return mappedEvent;
}
@Override public boolean onTouchEvent(MotionEvent event)
{
boolean res = gestureDetector.onTouchEvent(event);
res |= doubleGestureDetector.onTouchEvent(event);
return res;
}
public interface SessionViewListener {
abstract void onSessionViewBeginTouch();
abstract void onSessionViewEndTouch();
abstract void onSessionViewLeftTouch(int x, int y, boolean down);
abstract void onSessionViewRightTouch(int x, int y, boolean down);
abstract void onSessionViewMove(int x, int y);
abstract void onSessionViewScroll(boolean down);
}
private class SessionGestureListener extends GestureDetector.SimpleOnGestureListener
{
boolean longPressInProgress = false;
public boolean onDown(MotionEvent e)
{
return true;
}
public boolean onUp(MotionEvent e)
{
sessionViewListener.onSessionViewEndTouch();
return true;
}
public void onLongPress(MotionEvent e)
{
MotionEvent mappedEvent = mapTouchEvent(e);
sessionViewListener.onSessionViewBeginTouch();
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
longPressInProgress = true;
}
public void onLongPressUp(MotionEvent e)
{
MotionEvent mappedEvent = mapTouchEvent(e);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
longPressInProgress = false;
sessionViewListener.onSessionViewEndTouch();
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY)
{
if (longPressInProgress)
{
MotionEvent mappedEvent = mapTouchEvent(e2);
sessionViewListener.onSessionViewMove((int)mappedEvent.getX(),
(int)mappedEvent.getY());
return true;
}
return false;
}
public boolean onDoubleTap(MotionEvent e)
{
// send 2nd click for double click
MotionEvent mappedEvent = mapTouchEvent(e);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
return true;
}
public boolean onSingleTapUp(MotionEvent e)
{
// send single click
MotionEvent mappedEvent = mapTouchEvent(e);
sessionViewListener.onSessionViewBeginTouch();
switch (e.getButtonState())
{
case MotionEvent.BUTTON_PRIMARY:
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
break;
case MotionEvent.BUTTON_SECONDARY:
sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
sessionViewListener.onSessionViewLeftTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
break;
}
sessionViewListener.onSessionViewEndTouch();
return true;
}
}
private class SessionDoubleGestureListener
implements DoubleGestureDetector.OnDoubleGestureListener
{
private MotionEvent prevEvent = null;
public boolean onDoubleTouchDown(MotionEvent e)
{
sessionViewListener.onSessionViewBeginTouch();
prevEvent = MotionEvent.obtain(e);
return true;
}
public boolean onDoubleTouchUp(MotionEvent e)
{
if (prevEvent != null)
{
prevEvent.recycle();
prevEvent = null;
}
sessionViewListener.onSessionViewEndTouch();
return true;
}
public boolean onDoubleTouchScroll(MotionEvent e1, MotionEvent e2)
{
// calc if user scrolled up or down (or if any scrolling happened at all)
float deltaY = e2.getY() - prevEvent.getY();
if (deltaY > TOUCH_SCROLL_DELTA)
{
sessionViewListener.onSessionViewScroll(true);
prevEvent.recycle();
prevEvent = MotionEvent.obtain(e2);
}
else if (deltaY < -TOUCH_SCROLL_DELTA)
{
sessionViewListener.onSessionViewScroll(false);
prevEvent.recycle();
prevEvent = MotionEvent.obtain(e2);
}
return true;
}
public boolean onDoubleTouchSingleTap(MotionEvent e)
{
// send single click
MotionEvent mappedEvent = mapDoubleTouchEvent(e);
sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), true);
sessionViewListener.onSessionViewRightTouch((int)mappedEvent.getX(),
(int)mappedEvent.getY(), false);
return true;
}
}
}