/**
* Copyright 2009 Marc Stogaitis and Mimi Sun
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.gmote.client.android;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.SocketException;
import org.gmote.common.Protocol.MouseEvent;
import org.gmote.common.Protocol.UdpPacketTypes;
import org.gmote.common.packet.AbstractPacket;
import org.gmote.common.packet.KeyboardEventPacket;
import org.gmote.common.packet.MouseClickPacket;
import org.gmote.common.packet.MouseWheelPacket;
import android.app.Activity;
import android.app.Dialog;
import android.app.ProgressDialog;
import android.content.SharedPreferences;
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.MenuInflater;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
public class Touchpad extends Activity implements BaseActivity{
private static final String DEBUG_TAG = "Gmote";
GestureDetector gestureDetector = null;
ActivityUtil mUtil = null;
ProgressDialog mDialog = null;
View mContentView = null;
View mPasswordEntryView = null;
private float mX = 0;
private float mY = 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;
private Remote remoteInstance;
// Object that we will wait on when the mouse is not moving.
private Object waitForMouseMove = new Object();
private int serverUdpPort;
@Override
public void onCreate(Bundle icicle) {
super.onCreate(icicle);
mUtil = new ActivityUtil();
mUtil.onCreate(icicle, this);
remoteInstance = Remote.getInstance();
gestureDetector = new GestureDetector(gestureListener);
gestureDetector.setIsLongpressEnabled(true);
setContentView(R.layout.touchpad);
mContentView = findViewById(R.id.touchpad);
mContentView.setFocusable(true);
mContentView.setFocusableInTouchMode(true);
loadMouseSettings();
MouseSendingThread mst = new MouseSendingThread();
new Thread(mst).start();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
super.onCreateOptionsMenu(menu);
mUtil.onCreateOptionsMenu(menu);
menu.removeItem(R.id.menui_touchpad);
menu.removeItem(R.id.menui_settings);
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.touchpad_settings, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
super.onOptionsItemSelected(item);
if (item.getItemId() == R.id.menui_touchpad_settings) {
mUtil.startActivityByClass(TouchpadSettings.class);
return true;
} else {
return mUtil.onOptionsItemSelected(item);
}
}
@Override
protected Dialog onCreateDialog(int id) {
return mUtil.onCreateDialog(id);
}
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(MouseEvent.RIGHT_CLICK);
}
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX,
float distanceY) {
incrementDistance(distanceX * mouseSensitivity, distanceY * mouseSensitivity);
synchronized(waitForMouseMove) {
waitForMouseMove.notify();
}
return true;
}
public void onShowPress(MotionEvent e) {
}
public boolean onSingleTapUp(MotionEvent e) {
mouseClick(MouseEvent.SINGLE_CLICK);
return true;
}
@Override
public boolean onDoubleTap(MotionEvent e) {
Log.i(DEBUG_TAG, "Double Tap");
mouseClick(MouseEvent.SINGLE_CLICK);
return true;
}
};
void mouseClick(MouseEvent evt) {
mUtil.send(new MouseClickPacket(evt));
}
@Override
public void onStart() {
super.onStart();
mUtil.onStart(this);
serverUdpPort = Remote.getInstance().getServerUdpPort();
}
@Override
public void onStop() {
super.onStop();
mUtil.onStop();
}
@Override
public void onResume() {
super.onResume();
mUtil.onResume();
}
@Override
public void onPause() {
super.onPause();
mUtil.onPause();
}
public void handleReceivedPacket(AbstractPacket reply) {
}
@Override
public boolean onTouchEvent(MotionEvent event) {
return gestureDetector.onTouchEvent(event);
}
@Override
public boolean dispatchTrackballEvent(MotionEvent event) {
if (event.getAction() == MotionEvent.ACTION_DOWN) {
mouseClick(MouseEvent.LEFT_MOUSE_DOWN);
return true;
} else if (event.getAction() == MotionEvent.ACTION_UP) {
mouseClick(MouseEvent.LEFT_MOUSE_UP);
return true;
}
final float scaleY = event.getYPrecision();
final float y = 0 - event.getY()* scaleY;
if (y < 0) {
mouseWheelMove(1);
} else if (y > 0) {
mouseWheelMove(-1);
}
return true;
}
void mouseWheelMove(int wheelAmount) {
remoteInstance.queuePacket(new MouseWheelPacket(wheelAmount));
}
@Override
public boolean onKeyDown(int keyCode, KeyEvent event) {
// Log.i("onKeyDown", event.toString());
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);
}
}
private synchronized DatagramPacket makeDatagramPacketIfNeeded() {
DatagramPacket packet = null;
if (mX != 0 || mY != 0) {
byte[] buf = new byte[5];
short tempX = (short)mX;
short tempY = (short)mY;
buf[0] = UdpPacketTypes.MOUSE_MOVE.getId();
buf[1] = (byte)tempX;
buf[2] = (byte)(tempX >>> 8);
buf[3] = (byte)tempY;
buf[4] = (byte)(tempY >>> 8);
//Log.i(DEBUG_TAG, "mX=" + mX + " mY=" + mY + " shortX=" + tempX + " shortY=" + tempY);
packet= new DatagramPacket(buf, buf.length, remoteInstance.getServerInetAddress(), serverUdpPort);
Log.i(DEBUG_TAG, remoteInstance.getServerIp());
mX = 0; mY = 0;
}
return packet;
}
public class MouseSendingThread implements Runnable {
public void run() {
DatagramSocket socket = null;
try {
socket = new DatagramSocket();
} catch (SocketException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
}
while (true) {
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 (waitForMouseMove) {
waitForMouseMove.wait();
}
} catch (InterruptedException e) {
Log.e(DEBUG_TAG, e.getMessage(), e);
return;
}
}
}
}
}
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;
}
public void loadMouseSettings() {
SharedPreferences prefs = getSharedPreferences(GmoteClient.PREFS, MODE_WORLD_READABLE);
int mouseSensitivityPref = prefs.getInt(TouchpadSettings.MOUSE_SENSITIVITY_SETTINGS_KEY, 50);
int mouseAccelerationPref = prefs.getInt(TouchpadSettings.MOUSE_ACCELERATION_SETTINGS_KEY, 50);
mouseSensitivity = MOUSE_SENSITIVITY_DEFAULT * ((float)mouseSensitivityPref / 50);
mouseAccelerationDamper = MOUSE_ACCELERATION_DEFAULT * ((float)mouseAccelerationPref / 50);
Log.i(DEBUG_TAG, "Setting Prefs: sens=" + mouseSensitivityPref + " accel=" + mouseAccelerationPref);
Log.i(DEBUG_TAG, "Setting Mouse to: sens=" + mouseSensitivity + " accel=" + mouseAccelerationDamper);
}
}