package org.gmote.client.android;
import java.net.DatagramSocket;
import java.net.SocketException;
import org.gmote.common.Protocol.Command;
import org.gmote.common.Protocol.MouseEvent;
import org.gmote.common.packet.AbstractPacket;
import org.gmote.common.packet.KeyboardEventPacket;
import org.gmote.common.packet.SimplePacket;
import org.gmote.common.packet.TileClickReq;
import org.gmote.common.packet.TileInfoReply;
import org.gmote.common.packet.TileSetReq;
import org.gmote.common.packet.TileUpdatePacket;
import android.app.Activity;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.os.Bundle;
import android.util.Log;
import android.view.GestureDetector;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
public class RemoteDesktop extends Activity implements BaseActivity {
static final String DEBUG_TAG = "Gmote";
static ActivityUtil mUtil = null;
Remote remoteInstance;
int serverUdpPort;
// bitmap
Bitmap bitmap;
int bmWidth;
int bmHeight;
BitmapFactory.Options bmOptions;
Rect bmRect;
// tile
Canvas tileCanvas;
Rect tileRect;
Object waitForTile = new Object();
int tileSize;
int tileMaxX;
int tileMaxY;
int tileNumVisibleX;
int tileNumVisibleY;
int tile1X = -1;
int tile1Y = -1;
// view
BitmapView main;
int width;
int height;
// motion
GestureDetector gestureDetector = null;
private float mX = 0;
private float mY = 0;
private float prevX = -1;
private float prevY = -1;
private int clickX = -10;
private int clickY = -10;
private long timeOfLastClick = 0;
private long timeOfLastPosX = 0;
private long timeOfLastNegX = 0;
private long timeOfLastPosY = 0;
private long timeOfLastNegY = 0;
private float posXAcceleration = 0;
private float negXAcceleration = 0;
private float posYAcceleration = 0;
private float negYAcceleration = 0;
private static final float ACCELERATION_DECAY = (float) 0.1;
private static final float MOUSE_SENSITIVITY_DEFAULT = (float) -1.4;
private static final float MOUSE_ACCELERATION_DEFAULT = (float) 0.5;
private float mouseSensitivity = MOUSE_SENSITIVITY_DEFAULT;
private float mouseAccelerationDamper = MOUSE_ACCELERATION_DEFAULT;
// Object that we will wait on when the screen is not scrolling.
private Object waitForScroll = new Object();
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
Log.d(DEBUG_TAG, "RemoteDesktop# oncreate");
mUtil = new ActivityUtil();
mUtil.onCreate(icicle, this);
remoteInstance = Remote.getInstance();
main = new BitmapView(this);
tileCanvas = new Canvas();
tileRect = new Rect();
bmRect = new Rect();
gestureDetector = new GestureDetector(gestureListener);
gestureDetector.setIsLongpressEnabled(true);
setContentView(main);
new Thread(new PainterThread()).start();
new Thread(new TilerThread()).start();
}
// ---------------------------------------------------------------------------------------------------
// GRAPHICS
public class PainterThread implements Runnable {
public void run() {
while (true) {
synchronized (waitForScroll) {
try {
waitForScroll.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//Log.d(DEBUG_TAG, "scrolled, main.postInvalidate");
main.postInvalidate();
}
}
}
public class TilerThread implements Runnable {
public void run() {
while (true) {
synchronized (waitForTile) {
try {
waitForTile.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
Log.d(DEBUG_TAG, "got tile, main.postInvalidate");
main.postInvalidate();
}
}
}
private synchronized void updateTile(int idx, int idy, byte[] data) {
Log.d(DEBUG_TAG, "updateTile: " + idx +","+ idy);
tileRect.offsetTo(idx * tileSize, idy * tileSize);
tileCanvas.drawBitmap(BitmapFactory.decodeByteArray(data, 0, data.length, bmOptions), null, tileRect, new Paint());
synchronized(waitForTile) {
waitForTile.notifyAll();
}
//main.postInvalidate(tileRect.left, tileRect.top, tileRect.right, tileRect.bottom);
//main.invalidate();
}
private synchronized void doDraw(Canvas canvas, Paint paint) {
if (bitmap!=null) {
canvas.drawBitmap(bitmap, mX, mY, paint);
//Log.d(DEBUG_TAG, "drawing " + mX + ", " + mY);
}
}
class BitmapView extends View {
Paint paint;
public BitmapView(Context context) {
super(context);
paint = new Paint();
}
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
super.onLayout(changed, left, top, right, bottom);
width = right;
height = bottom;
}
@Override
protected void onDraw(Canvas canvas) {
doDraw(canvas, paint);
}
}
void getTileInfo() {
mUtil.send(new SimplePacket(Command.TILE_INFO_REQ));
}
void initializeGraphics() {
bmRect = new Rect(0, 0, bmWidth, bmHeight);
tileRect = new Rect(0, 0, tileSize, tileSize);
if (bitmap != null) {
bitmap.recycle();
}
bitmap = Bitmap.createBitmap(bmWidth, bmHeight, Bitmap.Config.RGB_565);
tileCanvas.setBitmap(bitmap);
bmOptions = new BitmapFactory.Options();
bmOptions.inPreferredConfig = Bitmap.Config.RGB_565;
tileMaxX = bmWidth/tileSize;
tileMaxY = bmHeight/tileSize;
Log.d(DEBUG_TAG, "width="+width+" height="+height+" tileMaxX="+tileMaxX + " tileMaxY="+tileMaxY + " tileSize="+tileSize);
}
// ---------------------------------------------------------------------------------------------------
// GENERAL
@Override
public void onStart() {
super.onStart();
Log.d(DEBUG_TAG, "RemoteDesktop# onstart");
mUtil.onStart(this);
serverUdpPort = remoteInstance.getServerUdpPort();
getTileInfo();
}
@Override
public void onResume() {
super.onResume();
Log.d(DEBUG_TAG, "RemoteDesktop# resume");
mUtil.onResume();
getTileInfo();
//bitmap = ((BitmapDrawable) getResources().getDrawable(R.drawable.screenshot)).getBitmap();
}
@Override
public void onPause() {
super.onPause();
Log.d(DEBUG_TAG, "RemoteDesktop# onpause");
mUtil.onPause();
}
@Override
public void onStop() {
super.onStop();
Log.d(DEBUG_TAG, "RemoteDesktop# onstop");
mUtil.onStop();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
mUtil.onCreateOptionsMenu(menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
return mUtil.onOptionsItemSelected(item);
}
@Override
protected Dialog onCreateDialog(int id) {
return mUtil.onCreateDialog(id);
}
public void handleReceivedPacket(AbstractPacket reply) {
if (reply.getCommand() == Command.TILE_INFO_REPLY) {
Log.d(DEBUG_TAG, "RemoteDesktop# got tile info ");
TileInfoReply ti = (TileInfoReply) reply;
bmWidth = ti.getScreenWidth();
bmHeight = ti.getScreenHeight();
tileSize = ti.getTileSize();
initializeGraphics();
new Thread(new TileReqSenderThread()).start();
//sendTcpTileReq();
} else if (reply.getCommand() == Command.TILE_UPDATE) {
Log.d(DEBUG_TAG, "RemoteDesktop# got tile update ");
TileUpdatePacket update = (TileUpdatePacket)reply;
updateTile(update.getTileIdX(), update.getTileIdY(), update.getImageData());
} else {
Log.e(DEBUG_TAG,"Unexpected packet in RemoteDesktop: " + reply.getCommand());
return;
}
}
// ---------------------------------------------------------------------------------------------------
// MOTION
GestureDetector.OnGestureListener gestureListener = new GestureDetector.SimpleOnGestureListener() {
public boolean onDown(MotionEvent e) {
return false;
}
public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX,
float velocityY) {
return false;
}
public void onLongPress(MotionEvent e) {
mouseClick((int)e.getRawX(), (int)e.getRawY(), MouseEvent.RIGHT_CLICK);
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
incrementDistance(distanceX * mouseSensitivity, distanceY
* mouseSensitivity);
synchronized (waitForScroll) {
waitForScroll.notifyAll();
}
return true;
}
public void onShowPress(MotionEvent e) {
}
public boolean onSingleTapUp(MotionEvent e) {
mouseClick((int)e.getRawX(), (int)e.getRawY(), MouseEvent.SINGLE_CLICK);
return true;
}
};
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
if (keyCode == KeyEvent.KEYCODE_BACK) {
finish();
} else if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
// mouseClick(MouseEvent.SINGLE_CLICK);
} else {
KeyCharacterMap kmap = KeyCharacterMap.load(event.getDeviceId());
int c = kmap.get(keyCode, event.getMetaState());
if (c != 0) {
mUtil.send(new KeyboardEventPacket(c));
} else {
if (keyCode == KeyEvent.KEYCODE_DEL) {
mUtil.send(new KeyboardEventPacket(KeyboardEventPacket.DELETE_KEYCODE));
} else if (keyCode == KeyEvent.KEYCODE_SEARCH || keyCode == KeyEvent.KEYCODE_SYM) {
mUtil.send(new KeyboardEventPacket(KeyboardEventPacket.SEARCH_KEYCODE));
}
}
}
return false;
}
private synchronized void incrementDistance(float distanceX, float distanceY) {
long currentTime = System.currentTimeMillis();
if (distanceX > 0) {
posXAcceleration = computeAcceleration(timeOfLastPosX, currentTime,
posXAcceleration, distanceX);
timeOfLastPosX = currentTime;
mX += distanceX + posXAcceleration;
} else {
negXAcceleration = computeAcceleration(timeOfLastNegX, currentTime,
negXAcceleration, Math.abs(distanceX));
timeOfLastNegX = currentTime;
mX += distanceX + (negXAcceleration * -1); // * distanceX * -1);
}
if (distanceY > 0) {
posYAcceleration = computeAcceleration(timeOfLastPosY, currentTime,
posYAcceleration, distanceY);
timeOfLastPosY = currentTime;
mY += distanceY + posYAcceleration;
} else {
negYAcceleration = computeAcceleration(timeOfLastNegY, currentTime,
negYAcceleration, Math.abs(distanceY));
timeOfLastNegY = currentTime;
mY += distanceY + (negYAcceleration * -1); // * distanceX * -1);
}
clamp();
}
private void clamp() {
if(mX > 0)
mX = 0;
else if (mX < -bmWidth + width)
mX = -bmWidth + width;
if(mY > 0)
mY = 0;
else if (mY < -bmHeight + height)
mY = -bmHeight + height;
}
private float computeAcceleration(long timeOfLastMove, long currentTime,
float lastAcceleration, float distanceMoved) {
// Decay the current acceleration value.
lastAcceleration -= (currentTime - timeOfLastMove) * ACCELERATION_DECAY;
// Add acceleration based on the current movement.
lastAcceleration += distanceMoved;
if (lastAcceleration < 0) {
lastAcceleration = 0;
}
return lastAcceleration * mouseAccelerationDamper;
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
void mouseClick(int x, int y, MouseEvent evt) {
int idx, idy, offsetx, offsety;
long currentTime = System.currentTimeMillis();
if ((currentTime - timeOfLastClick < 1400) && clickX > 0) {
// for double-clicks
x = clickX;
y = clickY;
} else {
x -= mX;
y -= mY;
clickX = x;
clickY = y;
}
idx = x/tileSize;
idy = y/tileSize;
offsetx = x - idx * tileSize;
offsety = y - idy * tileSize;
mUtil.send(new TileClickReq(idx, idy, offsetx, offsety, evt));
timeOfLastClick = currentTime;
}
public class TileReqSenderThread implements Runnable {
public void run() {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
} catch (SocketException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
}
while (true) {
sendTcpTileReq();
//sendUdpTileReq(socket);
}
}
}
private void sendTcpTileReq() {
if ((mX != prevX || mY != prevY) && (bmRect.contains((int)-mX, (int)-mY))) {
int x, y, x1, y1, x2, y2;
x = (int) -mX / tileSize;
y = (int) -mY / tileSize;
if (x != tile1X || y != tile1Y) {
x1 = Math.max(x, 0);
y1 = Math.max(y, 0);
x2 = Math.min(x + width/tileSize + 1, tileMaxX);
y2 = Math.min(y + height/tileSize + 1, tileMaxY);
mUtil.send(new TileSetReq(x1, y1, x2, y2));
tile1X = x;
tile1Y = y;
}
prevX = mX;
prevY = mY;
}
}
//
// private void sendUdpTileReq(DatagramSocket socket) {
// DatagramPacket packet = makeDatagramPacketIfNeeded();
// if (packet != null) {
// Log.i(DEBUG_TAG, "Sending packet");
// try {
// if (!remoteInstance.isConnected()) {
// remoteInstance.connect(false);
// }
// socket.send(packet);
// Log.i(DEBUG_TAG, "Packet sent");
// } catch (IOException e) {
// Log.e(DEBUG_TAG, e.getMessage(), e);
// }
// } else {
// try {
// synchronized (waitForScroll) {
// waitForScroll.wait();
// }
// } catch (InterruptedException e) {
// Log.e(DEBUG_TAG, e.getMessage(), e);
// return;
// }
// }
// }
// private synchronized DatagramPacket makeDatagramPacketIfNeeded() {
// DatagramPacket packet = null;
//
// // FIXME!
// if (mX != prevX || mY != prevY) {
// byte[] buf = new byte[5];
// packet= new DatagramPacket(buf, buf.length, remoteInstance.getServerInetAddress(), serverUdpPort);
// Log.i(DEBUG_TAG, remoteInstance.getServerIp());
// prevX = mX; prevY = mY;
// }
//
// return packet;
// }
}