// // anyRemote android client // a bluetooth/wi-fi remote control for Linux. // // Copyright (C) 2011-2016 Mikhail Fedotov <anyremote@mail.ru> // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 3 of the License, or // (at your option) any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA // package anyremote.client.android; import java.lang.Math; import android.hardware.Sensor; import android.hardware.SensorManager; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.app.Dialog; import android.content.DialogInterface; import android.content.Intent; import android.content.Context; import android.content.DialogInterface.OnDismissListener; import android.os.Bundle; import android.os.Handler; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import android.view.GestureDetector.OnGestureListener; import android.view.GestureDetector; import android.view.Display; import android.view.MenuItem; import android.view.MotionEvent; import android.view.View; import android.view.KeyEvent; import anyremote.client.android.util.BTScanner; import anyremote.client.android.util.IPScanner; import anyremote.client.android.util.InfoMessage; import anyremote.client.android.util.ProtocolMessage; import anyremote.client.android.util.ZCScanner; public class MouseScreen extends arActivity implements View.OnClickListener, // View.OnTouchListener, KeyEvent.Callback, OnGestureListener, SensorEventListener { // private static final int SWIPE_MAX_OFF_PATH = 250; boolean fullscreen = false; boolean skipDismissDialog = false; Dispatcher.ArHandler hdlLocalCopy; ImageButton[] buttons; LinearLayout[] buttonsLayout; private GestureDetector gestureScanner; private SensorManager mSensorManager; private Sensor mAccelerometer = null; private Sensor mGyroscope = null; float mLastX, mLastY, mLastZ; //boolean mInitialized; static final int NUM_BUTTONS = 5; static final float NOISE = 0.16f; static final float NOISE_GYROSCOPE = 0.04f; static final float G_VALUE = 9.81f; static final float NS2S = 1.0f / 1000000000.0f; static final float EPSILON = 16f; private final float[] deltaRotationVector = new float[4]; private float[] rotationCurrent = new float[3]; private long timestamp = 0; static final int[] mBtns = {R.id.mouseButton1, R.id.mouseButton2, R.id.mouseButton3, R.id.mouseButton4, R.id.mouseButton5}; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); prefix = "MouseScreen"; // log stuff log("onCreate"); gestureScanner = new GestureDetector(anyRemote.protocol.context, this); hdlLocalCopy = new Dispatcher.ArHandler(anyRemote.MOUSE_FORM, new Handler(this)); anyRemote.protocol.addMessageHandler(hdlLocalCopy); privateMenu = anyRemote.MOUSE_FORM; mSensorManager = (SensorManager) getSystemService(Context.SENSOR_SERVICE); if (anyRemote.protocol.sensorGyroscope()) { mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); } if (mGyroscope == null) { mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); if (anyRemote.protocol.sensorGyroscope()) { anyRemote.protocol.setSensorType(false); } } //mInitialized = false; } /* * @Override protected void onStart() { log("onStart"); super.onStart(); } */ @Override protected void onPause() { log("onPause"); hidePopup(); super.onPause(); mSensorManager.unregisterListener(this); } @Override protected void onResume() { log("onResume"); super.onResume(); registerSensorListener(); if (anyRemote.status == anyRemote.DISCONNECTED) { log("onResume no connection"); doFinish(""); return; } redraw(); exiting = false; } @Override protected void onStop() { log("onStop"); super.onStop(); mSensorManager.unregisterListener(this); } @Override protected void onDestroy() { log("onDestroy"); anyRemote.protocol.removeMessageHandler(hdlLocalCopy); super.onDestroy(); } @Override protected void onUserLeaveHint() { log("onUserLeaveHint"); // no time to sending events // commandAction(anyRemote.protocol.context.getString(R.string.disconnect_item)); if (!exiting && anyRemote.protocol.messageQueueSize() == 0) { log("onUserLeaveHint - make disconnect"); anyRemote.protocol.disconnect(false); } } public void handleEvent(InfoMessage data) { log("handleEvent " + Dispatcher.cmdStr(data.id)); if (data.stage != ProtocolMessage.FULL && // process only full commands data.stage == ProtocolMessage.FIRST) { return; } if (handleCommonCommand(data.id)) { return; } if (data.id == Dispatcher.CMD_VOLUME) { Toast.makeText(this, "Volume is " + anyRemote.protocol.cfVolume + "%", Toast.LENGTH_SHORT).show(); return; } redraw(); } private void redraw() { anyRemote.protocol.setFullscreen(this); //synchronized (anyRemote.protocol.mouseMenu) { setContentView(R.layout.mouse_form_default); //} Display display = getWindowManager().getDefaultDisplay(); int h = display.getHeight(); int w = display.getWidth(); int iconSize = w / 3; log("redraw icon size "+iconSize); ImageButton[] buttons; buttons = new ImageButton[NUM_BUTTONS]; for (int i = 0; i < NUM_BUTTONS; i++) { buttons[i] = (ImageButton) findViewById(mBtns[i]); buttons[i].setOnClickListener(this); buttons[i].setMaxHeight(iconSize); buttons[i].setMaxWidth(iconSize); buttons[i].setMinimumHeight(iconSize); buttons[i].setMinimumWidth(iconSize); } } public void onClick(View v) { // public boolean onTouch (View v, MotionEvent e) { String key = ""; switch (v.getId()) { case R.id.mouseButton1: key = "1"; break; case R.id.mouseButton2: key = "2"; break; case R.id.mouseButton3: key = "3"; break; case R.id.mouseButton4: key = "4"; break; case R.id.mouseButton5: key = "5"; break; default: log("onClick: Unknown button"); return; // return false; } //clickOn(key); anyRemote.protocol.queueCommand("_MB_("+key+",)"); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { super.dispatchTouchEvent(ev); return gestureScanner.onTouchEvent(ev); } @Override public boolean onTouchEvent(MotionEvent me) { return gestureScanner.onTouchEvent(me); } private String key2str(int keyCode) { log("key2str " + keyCode); switch (keyCode) { case KeyEvent.KEYCODE_1: return "1"; case KeyEvent.KEYCODE_2: return "2"; case KeyEvent.KEYCODE_3: return "3"; case KeyEvent.KEYCODE_4: return "4"; case KeyEvent.KEYCODE_5: return "5"; case KeyEvent.KEYCODE_6: return "6"; case KeyEvent.KEYCODE_7: return "7"; case KeyEvent.KEYCODE_8: return "8"; case KeyEvent.KEYCODE_9: return "9"; case KeyEvent.KEYCODE_STAR: return "*"; case KeyEvent.KEYCODE_0: return "0"; case KeyEvent.KEYCODE_POUND: return "#"; case KeyEvent.KEYCODE_MENU: case KeyEvent.KEYCODE_HOME: case KeyEvent.KEYCODE_BACK: return ""; // do not process them case KeyEvent.KEYCODE_VOLUME_UP: return "VOL+"; case KeyEvent.KEYCODE_VOLUME_DOWN: return "VOL-"; case KeyEvent.KEYCODE_DPAD_UP: return anyRemote.protocol.cfUpEvent; case KeyEvent.KEYCODE_DPAD_DOWN: return anyRemote.protocol.cfDownEvent; case KeyEvent.KEYCODE_DPAD_LEFT: return (!anyRemote.protocol.cfUseJoystick ? "LEFT" : ""); // do not // process // them // if // joystick_only // param // was // set case KeyEvent.KEYCODE_DPAD_RIGHT: return (!anyRemote.protocol.cfUseJoystick ? "RIGHT" : ""); case KeyEvent.KEYCODE_DPAD_CENTER: return (!anyRemote.protocol.cfUseJoystick ? "FIRE" : ""); case KeyEvent.KEYCODE_SEARCH: return "SEARCH"; default: if (keyCode >= 0 && keyCode < 10) { return "K" + String.valueOf(keyCode); } return String.valueOf(keyCode); } } @Override public boolean onKeyUp(int keyCode, KeyEvent event) { log("onKeyUp " + keyCode); boolean lp = longPress; longPress = false; if (keyCode == KeyEvent.KEYCODE_BACK) { if (event.isTracking() && !event.isCanceled() && lp) { log("onKeyUp KEYCODE_BACK long press - show menu"); new Handler().postDelayed(new Runnable() { public void run() { openOptionsMenu(); } }, 1000); return true; } else { log("onKeyUp KEYCODE_BACK"); commandAction(anyRemote.protocol.context.getString(R.string.back_item)); return true; } } String key = key2str(keyCode); if (key.length() > 0) { log("onKeyUp MSG " + key); return true; } log("onKeyUp TRANSFER " + keyCode); return false; } @Override public boolean onKeyDown(int keyCode, KeyEvent event) { log("onKeyDown " + keyCode); switch (keyCode) { case KeyEvent.KEYCODE_BACK: event.startTracking(); return true; } return false; } public void clickOn(String key) { log("clickOn "+key); anyRemote.protocol.queueCommand(key, true); anyRemote.protocol.queueCommand(key, false); } @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getTitle().toString() == getResources().getString(R.string.sensor)) { showDialog(Dispatcher.CMD_SENSOR_DIALOG); } else { commandAction(item.getTitle().toString()); } return true; } // @Override // public void onBackPressed() { // log("onBackPressed"); // commandAction(anyRemote.protocol.context.getString(R.string.disconnect_item)); // } public void commandAction(String command) { log("commandAction " + command); if (command.equals(anyRemote.protocol.context.getString(R.string.back_item))) { doFinish(""); } } @Override protected void doFinish(String action) { log("doFinish " + action); // exiting = true; final Intent intent = new Intent(); intent.putExtra(anyRemote.ACTION, action); setResult(RESULT_OK, intent); finish(); } public boolean onDown(MotionEvent e) { // log("onDown"); return true; } public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { // log("onFling " + e1.getX() + " " + e1.getY() + " " // + e2.getX() + " " + e2.getY() + " " // + velocityX + " " + velocityY); try { // if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) { // return false; // } // right to left swipe if (e1.getX() - e2.getX() > anyRemote.SWIPE_MIN_DISTANCE && Math.abs(velocityX) > anyRemote.SWIPE_THRESHOLD_VELOCITY) { clickOn("SlideLeft"); } else if (e2.getX() - e1.getX() > anyRemote.SWIPE_MIN_DISTANCE && Math.abs(velocityX) > anyRemote.SWIPE_THRESHOLD_VELOCITY) { clickOn("SlideRight"); } else if (e1.getY() - e2.getY() > anyRemote.SWIPE_MIN_DISTANCE && Math.abs(velocityY) > anyRemote.SWIPE_THRESHOLD_VELOCITY) { clickOn("SlideUp"); } else if (e2.getY() - e1.getY() > anyRemote.SWIPE_MIN_DISTANCE && Math.abs(velocityY) > anyRemote.SWIPE_THRESHOLD_VELOCITY) { clickOn("SlideDown"); } } catch (Exception e) { // nothing } return true; } public void onLongPress(MotionEvent e) { // log("onLongPress"); } public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) { // log("onScroll"); return true; } public void onShowPress(MotionEvent e) { // log("onShowPress"); } public boolean onSingleTapUp(MotionEvent e) { // log("onSingleTapUp"); return true; } @Override public final void onAccuracyChanged(Sensor sensor, int accuracy) { // Do something here if sensor accuracy changes. } @Override public final void onSensorChanged(SensorEvent event) { if (event.accuracy == SensorManager.SENSOR_STATUS_UNRELIABLE) { return; } // Many sensors return 3 values, one for each axis. float x = event.values[0]; float y = event.values[1]; float z = event.values[2]; if (mGyroscope != null && event.sensor.getType() == Sensor.TYPE_GYROSCOPE) { if (timestamp != 0) { final float dT = (event.timestamp - timestamp) * NS2S; // Axis of the rotation sample, not normalized yet. float axisX = event.values[0]; float axisY = event.values[2]; float axisZ = event.values[1]; // Calculate the angular speed of the sample float omegaMagnitude = (float) Math.sqrt(axisX * axisX + axisY * axisY + axisZ * axisZ); // Normalize the rotation vector if it's big enough to get the // axis // (that is, EPSILON should represent your maximum allowable // margin of error) if (omegaMagnitude > EPSILON) { axisX /= omegaMagnitude; axisY /= omegaMagnitude; axisZ /= omegaMagnitude; } // Integrate around this axis with the angular speed by the // timestep // in order to get a delta rotation from this sample over the // timestep // We will convert this axis-angle representation of the delta // rotation // into a quaternion before turning it into the rotation matrix. float thetaOverTwo = omegaMagnitude * dT / 2.0f; float sinThetaOverTwo = (float) Math.sin(thetaOverTwo); float cosThetaOverTwo = (float) Math.cos(thetaOverTwo); deltaRotationVector[0] = sinThetaOverTwo * axisX; deltaRotationVector[1] = sinThetaOverTwo * axisY; deltaRotationVector[2] = sinThetaOverTwo * axisZ; deltaRotationVector[3] = cosThetaOverTwo; } else { // Axis of the rotation sample, not normalized yet. float axisX = event.values[0]; float axisY = event.values[2]; float axisZ = event.values[1]; mLastX = axisX; mLastY = axisY; mLastZ = axisZ; } timestamp = event.timestamp; float[] deltaRotationMatrix = new float[9]; SensorManager.getRotationMatrixFromVector(deltaRotationMatrix, deltaRotationVector); // User code should concatenate the delta rotation we computed with // the current rotation // in order to get the updated rotation. // rotationCurrent = rotationCurrent * deltaRotationMatrix; // SensorManager.remapCoordinateSystem(deltaRotationMatrix, // SensorManager.AXIS_X, SensorManager.AXIS_Z, deltaRotationMatrix); SensorManager.getOrientation(deltaRotationMatrix, rotationCurrent); mLastZ = (float) Math.toDegrees(rotationCurrent[0]); mLastY = (float) Math.toDegrees(rotationCurrent[1]); mLastX = (float) Math.toDegrees(rotationCurrent[2]); x = mLastX; y = -mLastY; z = mLastZ; } else if (mAccelerometer != null && event.sensor.getType() == Sensor.TYPE_ACCELEROMETER) { // Accelerometer long actualTime = event.timestamp; if (actualTime - timestamp < 50000000) { // 20 per second return; } timestamp = actualTime; if (z < 0) { z = -z; } float dy = z - G_VALUE; if (dy < 0) { dy = -dy; } if (dy < 0.2) { // threshold return; } // float deltaX = 0; // float deltaY = 0; // float deltaZ = 0; // if (!mInitialized) { mLastX = x; mLastY = y; mLastZ = z; // mInitialized = true; // } else { // deltaX = mLastX - x; // deltaY = mLastY - y; // deltaZ = mLastZ - z; // if (Math.abs(deltaX) < NOISE) deltaX = 0.0f; // if (Math.abs(deltaY) < NOISE) deltaY = 0.0f; // if (Math.abs(deltaZ) < NOISE) deltaZ = 0.0f; // mLastX = x; // mLastY = y; // mLastZ = z; // } } else { return; } /*TextView tx = (TextView) findViewById(R.id.xval); if (tx != null) { tx.setText((mGyroscope == null ? "N" : "G") + (mAccelerometer == null ? "N" : "A") + "X axis" + "\t\t" + mLastX); } TextView ty = (TextView) findViewById(R.id.yval); if (ty != null) { ty.setText("Y axis" + "\t\t" + mLastY); } TextView tz = (TextView) findViewById(R.id.zval); if (tz != null) { tz.setText("Z axis" + "\t\t" + mLastZ); }*/ int mx = 0; int my = 0; float noise = (mGyroscope == null ? NOISE : NOISE_GYROSCOPE); if (Math.abs(x) > noise) { mx = (int) (x * x); if (x > 0) { mx = -mx; } } if (Math.abs(y) > noise) { my = (int) (y * y); if (y > 0) { my = -my; } } if (mx != 0 || my != 0) { anyRemote.protocol.queueCommand("_MM_(" + mx + "," + my + ")"); } } // Got result from SearchDialog dialog ("Ok"/"Cancel" was pressed) public void onDismissSensorDialog (DialogInterface dialog) { if (skipDismissDialog) { skipDismissDialog = false; } else { mSensorManager.unregisterListener(this); boolean useGyro = ((SensorDialog) dialog).useGyroscope(); if (useGyro) { if (mGyroscope == null) { mGyroscope = mSensorManager.getDefaultSensor(Sensor.TYPE_GYROSCOPE); } if (mGyroscope != null) { mAccelerometer = null; } else { Toast.makeText(this, "Gyroscope unavailable ...", Toast.LENGTH_SHORT).show(); } } else { // Accelerometer if (mAccelerometer == null) { mAccelerometer = mSensorManager.getDefaultSensor(Sensor.TYPE_ACCELEROMETER); } if (mAccelerometer != null) { mGyroscope = null; } else { Toast.makeText(this, "Accelerometer unavailable ...", Toast.LENGTH_SHORT).show(); } } anyRemote.protocol.setSensorType(mGyroscope != null); registerSensorListener(); } } private void registerSensorListener() { try { if (mGyroscope != null) { mSensorManager.registerListener(this, mGyroscope, SensorManager.SENSOR_DELAY_GAME); } else if (mAccelerometer != null) { mSensorManager.registerListener(this, mAccelerometer, SensorManager.SENSOR_DELAY_NORMAL); } } catch (Exception e) { } } // Handle "Cancel" press in EditFieldDialog/SearchDialog public void onCancel(DialogInterface dialog) { skipDismissDialog = true; } @Override protected Dialog onCreateDialog(int id) { switch(id){ case Dispatcher.CMD_SENSOR_DIALOG: return new SensorDialog(this); } return null; } @Override protected void onPrepareDialog(int id, Dialog d) { if (d == null) return; skipDismissDialog = false; switch(id){ case Dispatcher.CMD_SENSOR_DIALOG: d.setOnDismissListener(new OnDismissListener() { public void onDismiss(DialogInterface dialog) { onDismissSensorDialog(dialog); } }); } d.setOnCancelListener (this); } }