package com.motorolasolutions.rho.sensor; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import com.rho.sensor.ISensor; import com.rho.sensor.ISensorSingleton; import com.rho.sensor.SensorBase; import com.rhomobile.rhodes.Logger; import com.rhomobile.rhodes.RhodesActivity; import com.rhomobile.rhodes.api.IMethodResult; public class Sensor extends SensorBase implements ISensor { private String TAG = "Rho::" + Sensor.class.getSimpleName(); /** * Sensor Manager variable for enabling/disabling all the sensors */ private SensorManager mSensorManager; /** * Sensor Navigation URLs for all the sensors */ private IMethodResult sensorUrl; /** * Minimum Interval between any two sensor event updates */ private int minimumInterval; /** * Type of the Sensor */ private String mType; /** * Status of the sensor */ private String mStatus = ISensorSingleton.SENSOR_STATUS_NOT_READY; /** * Variable that records the event sending data to avoid sending another event before minimum interval */ private long dwQuietStart; /** * The default minimem gap specified in the sensor.xml * Cannot be set to any value lower than 200. */ static private final int DEFAULT_MINIMUM_GAP = 200; @Override public void getMinimumGap(IMethodResult result) { result.set(minimumInterval); } @Override public void setMinimumGap(int duration, IMethodResult result) { minimumInterval = duration >= DEFAULT_MINIMUM_GAP ? duration : DEFAULT_MINIMUM_GAP; } @Override public void getType(IMethodResult result) { result.set(mType); } @Override public void getStatus(IMethodResult result) { result.set(mStatus); } @Override public void getAllProperties(IMethodResult result) { Logger.D(TAG, "getProperty API [" + minimumInterval + ", " + mType + ", " + mStatus + "]"); Map props = new HashMap(); props.put("minimumGap", Integer.toString(minimumInterval)); props.put("type", mType); props.put("status", mStatus); result.set(props); } @Override public void getProperty(String name, IMethodResult result) { Logger.D(TAG, "getProperty API"); if (name.equalsIgnoreCase("minimumGap")) result.set(Integer.toString(minimumInterval)); else if (name.equalsIgnoreCase("type")) result.set(mType); else if (name.equalsIgnoreCase("status")) result.set(mStatus); else result.setError("Invalid attribute"); } @Override public void getProperties(List names, IMethodResult result) { Logger.D(TAG, "getProperties API"); Map props = new HashMap(); for (String name: names) { if (name.equalsIgnoreCase("minimumGap")) props.put("minimumGap", Integer.toString(minimumInterval)); else if (name.equalsIgnoreCase("type")) props.put("type", mType); else if (name.equalsIgnoreCase("status")) props.put("status", mStatus); else result.setError("Invalid attribute"); } result.set(props); } @Override public void setProperty(String name, String val, IMethodResult result) { Logger.D(TAG, "setProperty API"); if (name.equalsIgnoreCase("minimumGap")) { int minGap = DEFAULT_MINIMUM_GAP; try { minGap = Integer.parseInt(val); minimumInterval = (minGap >= DEFAULT_MINIMUM_GAP) ? minGap : DEFAULT_MINIMUM_GAP; } catch(NumberFormatException nfex) { Logger.E(TAG, "setProperties API, Invalid minimumGap " + val); result.setError("Invalid minimumGap " + val + " , must be greater than " + DEFAULT_MINIMUM_GAP); } } else result.setError("Invalid attribute"); } @Override public void setProperties(Map props, IMethodResult result) { Logger.D(TAG, "setProperties API"); for (Map.Entry entry: props.entrySet()) { if (entry.getKey().equalsIgnoreCase("minimumGap")) { int minGap = DEFAULT_MINIMUM_GAP; try { minGap = Integer.parseInt(entry.getValue()); minimumInterval = (minGap >= DEFAULT_MINIMUM_GAP) ? minGap : DEFAULT_MINIMUM_GAP; } catch(NumberFormatException nfex) { Logger.E(TAG, "setProperties API, Invalid minimumGap " + entry.getValue()); result.setError("Invalid minimumGap " + entry.getValue() + " , must be greater than " + DEFAULT_MINIMUM_GAP); } } else result.setError("Invalid attribute"); } } // @Override // public void clearAllProperties(IMethodResult result) // { // Logger.D(TAG, "clearAllProperties API"); // // resetProperties(); // } public void resetProperties() { sensorUrl = null; dwQuietStart = System.currentTimeMillis(); minimumInterval = DEFAULT_MINIMUM_GAP; //Default - 200 milli seconds mStatus = ISensorSingleton.SENSOR_STATUS_NOT_READY; } private final SensorEventListener sensorListener = new SensorEventListener() { @Override public void onSensorChanged(SensorEvent arg0) { sensorChanged(arg0); } @Override public void onAccuracyChanged(android.hardware.Sensor sensor, int accuracy) { // TODO Auto-generated method stub } }; /** * Handle the data from Accelerometer sensor * @param event */ private void sensorChanged(SensorEvent event) { Map props = new HashMap(); double accel_x = event.values[0]; double accel_y = event.values[1]; double accel_z = event.values[2]; int sensorType = event.sensor.getType(); long currentTimeMillis = System.currentTimeMillis(); //Logger.D(TAG, "Sensor event fired for type:" + sensorType); switch (sensorType) { case android.hardware.Sensor.TYPE_ACCELEROMETER: //Logger.D(TAG, ISensorSingleton.SENSOR_TYPE_ACCELEROMETER + ">>>" + mType + " [" + accel_x + ", " + accel_y + ", " + accel_z + "]"); //Logger.D(TAG, "Accelerometer Sensor change detected"); if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ACCELEROMETER) && (sensorUrl != null)) { //Logger.D(TAG, "Acceleromert fire the event"); //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; Logger.D(TAG, "Accelerometer>>> elapsed " + elapsed + ", " + currentTimeMillis); if (elapsed >= minimumInterval) { //Logger.D(TAG, "Accelerometer minimum interval surpassed"); props.put("accelerometer_x", accel_x); props.put("accelerometer_y", accel_y); props.put("accelerometer_z", accel_z); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); //Logger.T(TAG, "Accelerometer event fired: "+ sensorUrl +":" + props); dwQuietStart = currentTimeMillis; } } if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_TILT_ANGLE) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { double PI = 3.14159265; // This algorithm is taken from the Windows Sensor API double tilt_y = (Math.atan( accel_x/Math.sqrt( accel_y*accel_y + accel_z*accel_z ) ) * 180 / PI); double tilt_x = (Math.atan( accel_y/Math.sqrt( accel_x*accel_x + accel_z*accel_z ) ) * 180 / PI); double tilt_z = (Math.atan( accel_z/Math.sqrt( accel_x*accel_x + accel_y*accel_y ) ) * 180 / PI); props.put("tiltangle_x", String.valueOf(tilt_x)); props.put("tiltangle_y", String.valueOf(tilt_y)); props.put("tiltangle_z", String.valueOf(tilt_z)); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_MAGNETIC_FIELD: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_MAGNETOMETER) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("magnetometer_x", event.values[0]); props.put("magnetometer_y", event.values[1]); props.put("magnetometer_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_GYROSCOPE: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_GYROSCOPE) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("gyroscope_x", event.values[0]); props.put("gyroscope_y", event.values[1]); props.put("gyroscope_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_LIGHT: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_AMBIENT_LIGHT) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("ambientlight_value", event.values[0]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_PROXIMITY: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_PROXIMITY) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("proximity_value", event.values[0]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_PRESSURE: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_PRESSURE) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("pressure_value", event.values[0]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_TEMPERATURE: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_TEMPERATURE) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("temperature_value", event.values[0]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_GRAVITY: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_GRAVITY) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("gravity_x", event.values[0]); props.put("gravity_y", event.values[1]); props.put("gravity_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_LINEAR_ACCELERATION: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_LINEAR_ACCELERATION) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("linearacceleration_x", event.values[0]); props.put("linearacceleration_y", event.values[1]); props.put("linearacceleration_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_ROTATION_VECTOR: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ROTATION) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("rotation_x", event.values[0]); props.put("rotation_y", event.values[1]); props.put("rotation_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; case android.hardware.Sensor.TYPE_ORIENTATION: if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ORIENTATION) && (sensorUrl != null)) { //get time from the last event long elapsed = currentTimeMillis - dwQuietStart; if (elapsed >= minimumInterval) { props.put("orientation_x", event.values[0]); props.put("orientation_y", event.values[1]); props.put("orientation_z", event.values[2]); props.put("status", "ok"); if (isCallback(sensorUrl)) sensorUrl.set(props); dwQuietStart = currentTimeMillis; } } break; default: break; } } public Sensor(String id) { super(id); mSensorManager = (SensorManager) RhodesActivity.safeGetInstance().getSystemService("sensor"); sensorUrl = null; mType = id; //ISensorSingleton.SENSOR_TYPE_ACCELEROMETER; // initial the start time to zero as the first event will get propgated to the JS/Ruby layer dwQuietStart = 0;//System.currentTimeMillis(); minimumInterval = DEFAULT_MINIMUM_GAP; //Default - 200 milli seconds // List the available sensors List sensors = mSensorManager.getSensorList(android.hardware.Sensor.TYPE_ALL); for (android.hardware.Sensor sensor : sensors) { Logger.I(TAG, "Available Sensor: " + sensor.getName()); } mStatus = ISensorSingleton.SENSOR_STATUS_READY; Logger.I(TAG, "Creating object for Sensor type: " + mType); } @Override public void start(IMethodResult result) { sensorUrl = result; if (mStatus == ISensorSingleton.SENSOR_STATUS_STARTED) { Logger.I(TAG, "Sensor type " + mType + " is already started"); return; } // reset the start time to zero as the first event will get propgated to the JS/Ruby layer dwQuietStart = 0; if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ACCELEROMETER)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_TILT_ANGLE)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ACCELEROMETER), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_MAGNETOMETER)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_MAGNETIC_FIELD), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_GYROSCOPE)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_GYROSCOPE), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_AMBIENT_LIGHT)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_LIGHT), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_PROXIMITY)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_PROXIMITY), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_PRESSURE)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_PRESSURE), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_TEMPERATURE)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_TEMPERATURE), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_GRAVITY)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_GRAVITY), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_LINEAR_ACCELERATION)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_LINEAR_ACCELERATION), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ROTATION)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ROTATION_VECTOR), SensorManager.SENSOR_DELAY_NORMAL); } else if (mType.equalsIgnoreCase(ISensorSingleton.SENSOR_TYPE_ORIENTATION)) { mSensorManager.registerListener(sensorListener, mSensorManager.getDefaultSensor(android.hardware.Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL); } else { Logger.E(TAG, "Sensor type " + mType + " not yet set or not supported on this platform"); } mStatus = ISensorSingleton.SENSOR_STATUS_STARTED; Logger.D(TAG, "Sensor registered successfully for type: " + mType); } @Override public void readData(IMethodResult result) { Map props = new HashMap(); props.put("status", "error"); props.put("message", "readData method not supported on Android platform"); result.set(props); //result.setError("readData method not supported on Android platform"); } @Override public void stop(IMethodResult result) { if (mStatus == ISensorSingleton.SENSOR_STATUS_STARTED) { mSensorManager.unregisterListener(sensorListener); Logger.D(TAG, "Sensor unregistered successfully"); mStatus = ISensorSingleton.SENSOR_STATUS_READY; } } private boolean isCallback(IMethodResult result) { return (result != null && result.hasCallback()); } }