diff --git a/gpii/node_modules/sensors/index.js b/gpii/node_modules/sensors/index.js new file mode 100644 index 0000000..33c9fda --- /dev/null +++ b/gpii/node_modules/sensors/index.js @@ -0,0 +1,17 @@ +/*! +GPII Android Sensors + +Copyright 2013, 2014 Emergya + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/gpii/universal/LICENSE.txt +*/ + +var fluid = require("universal"); + +var loader = fluid.getLoader(__dirname); + +loader.require("./sensors.js"); diff --git a/gpii/node_modules/sensors/package.json b/gpii/node_modules/sensors/package.json new file mode 100644 index 0000000..ce07eff --- /dev/null +++ b/gpii/node_modules/sensors/package.json @@ -0,0 +1,19 @@ +{ + "name": "sensors", + "description": "Used for interacting with built-in sensors.", + "version": "0.1", + "author": "Javier Hernández", + "bugs": "http://wiki.gpii.net/index.php/Main_Page", + "homepage": "http://gpii.net/", + "dependencies": {}, + "licenses": [ + { + "type": "BSD-3-Clause", + "url": "http://www.opensource.org/licenses/BSD-3-Clause" + } + ], + "keywords": ["gpii", "accessibility", "settings", "fluid"], + "repository": "git://github.com:GPII/android.git", + "main": "./index.js", + "engines": { "node" : ">=0.1.9" } +} diff --git a/gpii/node_modules/sensors/sensors.js b/gpii/node_modules/sensors/sensors.js new file mode 100644 index 0000000..e3361a3 --- /dev/null +++ b/gpii/node_modules/sensors/sensors.js @@ -0,0 +1,140 @@ +/*! +GPII Android Sensors + +Copyright 2013 Emergya + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/gpii/universal/LICENSE.txt +*/ + +// Currently we need to do this in order to work correctly with +// Anode's JS bridge +var thatall = this; + +(function () { + "use strict"; + + var http = require("http"); + var fluid = require("universal"); + var gpii = fluid.registerNamespace("gpii"); + + var bridge = require("bridge"); + bridge.load("net.gpii.AndroidSensorsImpl", thatall); + + fluid.registerNamespace("gpii.androidSensors"); + + gpii.androidSensors.getEndPoint = function () { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + return androidSensors.getEndPoint(); + }; + + gpii.androidSensors.setEndPoint = function (value) { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + androidSensors.setEndPoint(value); + }; + + fluid.defaults("gpii.androidSensors.getSensors", { + gradeNames: "fluid.function" + }); + + gpii.androidSensors.getSensors = function() { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + + // TODO: Provide the full list of available sensors + // and some useful info about them, ie: + // + // [{ + // id: xyz, + // name: getName, + // type: getType, + // maxRange: getMaximumRange, + // minDelay: getMinDelay, + // maxDelay: getMaxDelay, + // resolution: getResolution, + // version: getVerºsion, + // vendor: getVendor, + // power: getPower + // }] + // + // There's an initial implementation of it (in the Java side) + // but requires some work. Check AndroidSensorsImpl for details + // + + var sensors = androidSensors.listSensors(); + return sensors; + }; + + // Light sensor + // + gpii.androidSensors.startLightSensor = function () { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + androidSensors.startLightSensor(); + }; + + gpii.androidSensors.stopLightSensor = function () { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + androidSensors.stopLightSensor(); + }; + + // Noise sensor + // + gpii.androidSensors.noiseIntervalId = null; + + gpii.androidSensors.startNoiseSensor = function () { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + + gpii.androidSensors.noiseIntervalId = setInterval(function () { + var data = { + "http://registry.gpii.net/common/environment/auditory.noise": androidSensors.getAmplitudeEMA() + }; + + var headers = { + "Content-Type": 'application/json', + "Content-Length": JSON.stringify(data).length + }; + + var options = { + host: "localhost", + port: 8081, + path: "/environmentChanged", + method: "PUT", + headers: headers + }; + + var req = http.request(options, function(res) { + res.setEncoding("utf-8"); + + var responseString = ""; + + res.on("data", function(data) { + responseString += data; + }); + + //res.on("end", function() { + // TODO: handle the end event + //}); + }); + + req.on("error", function(e) { + // TODO: handle error + }); + + req.write(JSON.stringify(data)); + req.end(); + + }, 2000); + + androidSensors.startNoiseSensor(); + }; + + gpii.androidSensors.stopNoiseSensor = function () { + var androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + + clearInterval(gpii.androidSensors.noiseIntervalId); + androidSensors.stopNoiseSensor(); + }; + +})(); diff --git a/gpii/node_modules/sensors/sensorsTests.js b/gpii/node_modules/sensors/sensorsTests.js new file mode 100644 index 0000000..a17ac5c --- /dev/null +++ b/gpii/node_modules/sensors/sensorsTests.js @@ -0,0 +1,24 @@ +/* +GPII Android Sensors Tests + +Copyright 2014 Emergya + +Licensed under the New BSD license. You may not use this file except in +compliance with this License. + +You may obtain a copy of the License at +https://github.com/gpii/universal/LICENSE.txt +*/ + +var thatall = this; + +var fluid = require("universal"), + jqUnit = fluid.require("jqUnit"), + gpii = fluid.registerNamespace("gpii"), + bridge = require("bridge"), + androidSensors = bridge.load("net.gpii.AndroidSensorsImpl", thatall); + +// TODO: Add tests + +//var sensors = androidSensors.listSensors(); + diff --git a/index.js b/index.js index ba02fff..1058fda 100644 --- a/index.js +++ b/index.js @@ -3,6 +3,7 @@ * * Copyright 2012 OCAD University * Copyright 2014 Lucendo Development Ltd. + * Copyright 2014 Emergya * * Licensed under the New BSD license. You may not use this file except in * compliance with this License. @@ -19,7 +20,31 @@ var fluid = require("universal"); fluid.module.register("gpii-android", __dirname, require); +// Native modules for settings handlers +// fluid.require("activitymanager", require); fluid.require("androidSettings", require); fluid.require("audioManager", require); fluid.require("persistentConfiguration", require); + +// Native module for sensors +// +fluid.require("sensors", require); + +// Enable sensors and tell where the changes should be reported +// +androidSensors = fluid.registerNamespace("gpii.androidSensors"); + +// This approach produces random crashes, as a workaround the endPoint is being +// set in the Java side of code but it would be better to be able to configure +// it from the javascript side +// +//androidSensors.setEndPoint("http://localhost:8081/environmentChanged"); + +// Enable environmental light sensor +// +androidSensors.startLightSensor(); + +// Enable environmental noise detection +// +androidSensors.startNoiseSensor(); diff --git a/platform/app/AndroidManifest.xml b/platform/app/AndroidManifest.xml index 6ec2fd6..e54171f 100644 --- a/platform/app/AndroidManifest.xml +++ b/platform/app/AndroidManifest.xml @@ -16,6 +16,7 @@ + diff --git a/platform/app/ant.properties b/platform/app/ant.properties index d530201..9c65d16 100644 --- a/platform/app/ant.properties +++ b/platform/app/ant.properties @@ -16,4 +16,4 @@ # The password will be asked during the build when you use the 'release' target. #source.dir=src;${env.ANODE_ROOT}/bridge-java/src;${env.ANODE_ROOT}/libnode/src;../anodeshare/src;../intents/src;../a11yservices/src -source.dir=src;${env.ANODE_ROOT}/bridge-java/src;${env.ANODE_ROOT}/libnode/src;../anodeshare/src;../intents/src;../a11yservices/src;../androidSettings/src;../audioManager/src;../persistentconfig/src;libs +source.dir=src;${env.ANODE_ROOT}/bridge-java/src;${env.ANODE_ROOT}/libnode/src;../anodeshare/src;../intents/src;../a11yservices/src;../androidSettings/src;../audioManager/src;../sensors/src;../persistentconfig/src;libs diff --git a/platform/makestubs.sh b/platform/makestubs.sh index 76b66c4..95213db 100644 --- a/platform/makestubs.sh +++ b/platform/makestubs.sh @@ -6,3 +6,4 @@ java -jar ./anode/sdk/java/tools/stubgen.jar --verbose --out ./androidSettings/s java -jar ./anode/sdk/java/tools/stubgen.jar --verbose --out ./androidSettings/src --classpath ./app/bin/classes net.gpii.AndroidSettings java -jar ./anode/sdk/java/tools/stubgen.jar --verbose --out ./audioManager/src --classpath ./app/bin/classes net.gpii.AndroidAudioManager java -jar ./anode/sdk/java/tools/stubgen.jar --verbose --out ./persistentconfig/src --classpath ./app/bin/classes net.gpii.AndroidPersistentConfiguration +java -jar ./anode/sdk/java/tools/stubgen.jar --verbose --out ./sensors/src --classpath ./app/bin/classes net.gpii.AndroidSensors diff --git a/platform/sensors/src/net/gpii/AndroidSensors.java b/platform/sensors/src/net/gpii/AndroidSensors.java new file mode 100644 index 0000000..a17015c --- /dev/null +++ b/platform/sensors/src/net/gpii/AndroidSensors.java @@ -0,0 +1,29 @@ +package net.gpii; + +import org.meshpoint.anode.bridge.Env; +import org.meshpoint.anode.java.Base; + +import java.util.List; + +public abstract class AndroidSensors extends Base { + private static short classId = Env.getInterfaceId(AndroidSensors.class); + public AndroidSensors() { super(classId); } + + // This is the way we tell the android environmental reporter where the + // changes on the context should be reported. + // + public abstract String getEndPoint(); + public abstract void setEndPoint(String value); + + // Light sensor + // + public abstract String getLightSensor(); + public abstract void startLightSensor(); + public abstract void stopLightSensor(); + + // Noise sensor + // + public abstract void startNoiseSensor(); + public abstract void stopNoiseSensor(); + public abstract double getAmplitudeEMA(); +} diff --git a/platform/sensors/src/net/gpii/AndroidSensorsImpl.java b/platform/sensors/src/net/gpii/AndroidSensorsImpl.java new file mode 100644 index 0000000..08599c7 --- /dev/null +++ b/platform/sensors/src/net/gpii/AndroidSensorsImpl.java @@ -0,0 +1,355 @@ +package net.gpii; + +import net.gpii.SensorDict; + +import org.meshpoint.anode.AndroidContext; +import org.meshpoint.anode.module.IModule; +import org.meshpoint.anode.module.IModuleContext; +import org.meshpoint.anode.idl.Dictionary; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorManager; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.media.MediaRecorder; +import android.os.Handler; +import android.util.Log; + +import java.io.IOException; +import java.io.UnsupportedEncodingException; +import java.net.ConnectException; +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.Map; +import java.util.List; +import java.util.ArrayList; + +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpPut; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.message.BasicHeader; +import org.apache.http.protocol.HTTP; +import org.apache.http.HttpResponse; + +import org.json.JSONObject; + + +public class AndroidSensorsImpl extends AndroidSensors implements IModule { + private static final String TAG = "net.gpii.AndroidSensorsImpl"; + + IModuleContext ctx; + private Context androidContext; + private SensorManager sensorManager; + private float currentLightValue; + + // For the noise sensor + // + private MediaRecorder mRecorder; + private Thread runner = null; + + private static double mEMA = 0.0; + static final private double EMA_FILTER = 0.6; + + final Runnable updater = new Runnable() { + public void run() { + updateNoiseValue(); + } + }; + + private Handler mHandler; + + // Common terms for environmental data + // + private static final String LUMINANCE = "http://registry.gpii.net/common/environment/visual.luminance"; + private static final String NOISE = "http://registry.gpii.net/common/environment/auditory.noise"; + + // endPoint where sensors must report their values when they change + // + private String endPoint = "http://localhost:8081/environmentChanged"; + + private final Map SENSOR_TYPE = + new HashMap() {{ + put("TYPE_ACCELEROMETER", Sensor.TYPE_ACCELEROMETER); + put("TYPE_ALL", Sensor.TYPE_ALL); + put("TYPE_AMBIENT_TEMPERATURE", Sensor.TYPE_AMBIENT_TEMPERATURE); + put("TYPE_GAME_ROTATION_VECTOR", Sensor.TYPE_GAME_ROTATION_VECTOR); + // API Level 19 + //put("TYPE_GEOMAGNETIC_ROTATION_VECTOR", Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR); + put("TYPE_GRAVITY", Sensor.TYPE_GRAVITY); + put("TYPE_GYROSCOPE", Sensor.TYPE_GYROSCOPE); + put("TYPE_GYROSCOPE_UNCALIBRATED", Sensor.TYPE_GYROSCOPE_UNCALIBRATED); + // API Level 20 + //put("TYPE_HEART_RATE", Sensor.TYPE_HEART_RATE); + put("TYPE_LIGHT", Sensor.TYPE_LIGHT); + put("TYPE_LINEAR_ACCELERATION", Sensor.TYPE_LINEAR_ACCELERATION); + put("TYPE_MAGNETIC_FIELD", Sensor.TYPE_MAGNETIC_FIELD); + put("TYPE_MAGNETIC_FIELD_UNCALIBRATED", Sensor.TYPE_MAGNETIC_FIELD_UNCALIBRATED); + put("TYPE_ORIENTATION", Sensor.TYPE_ORIENTATION); + put("TYPE_PRESSURE", Sensor.TYPE_PRESSURE); + put("TYPE_PROXIMITY", Sensor.TYPE_PROXIMITY); + put("TYPE_RELATIVE_HUMIDITY", Sensor.TYPE_RELATIVE_HUMIDITY); + put("TYPE_ROTATION_VECTOR", Sensor.TYPE_ROTATION_VECTOR); + put("TYPE_SIGNIFICANT_MOTION", Sensor.TYPE_SIGNIFICANT_MOTION); + // API Level 19 + //put("TYPE_STEP_COUNTER", Sensor.TYPE_STEP_COUNTER); + //put("TYPE_STEP_DETECTOR", Sensor.TYPE_STEP_DETECTOR); + put("TYPE_TEMPERATURE", Sensor.TYPE_TEMPERATURE); + }}; + + @Override + public Object startModule(IModuleContext ctx) { + Log.v(TAG, "AndroidSensorsImpl.startModule"); + try { + this.ctx = ctx; + androidContext = ((AndroidContext) ctx).getAndroidContext(); + sensorManager = (SensorManager) androidContext.getSystemService(Context.SENSOR_SERVICE); + } + catch (Exception e) { + Log.v(TAG, "AndroidSensorsImpl error starting module: " + e); + } + return this; + } + + @Override + public void stopModule() { + Log.v(TAG, "AndroidSensorsImpl.stopModule"); + } + + // TODO: Transform this into JSON + // + //@Override + //public List listSensors () { + // List sensorsList = new ArrayList(); + + // List sensors = sensorManager.getSensorList(Sensor.TYPE_ALL); + // for (Sensor sensor: sensors) { + // SensorDict s = new SensorDict(); + + // s.name = sensor.getName(); + // s.type = getSensorType(sensor.getType()); + // s.maximumRange = sensor.getMaximumRange(); + // s.minDelay = sensor.getMinDelay(); + // // Commented because this was introduced in API level 21 + // //s.maxDelay = sensor.getMaxDelay(); + // s.resolution = sensor.getResolution(); + // s.version = sensor.getVersion(); + // s.vendor = sensor.getVendor(); + // s.power = sensor.getPower(); + + // sensorsList.add(s); + // } + + // return sensorsList; + //} + + SensorEventListener lightSensorEventListener = new SensorEventListener() { + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + //Log.d("### ACCURACY CHANGED: ", String.valueOf(accuracy)); + } + + @Override + public void onSensorChanged(SensorEvent event) { + //Log.d("[node] ### VALUE CHANGED: ", String.valueOf(event.values[0])); + if (event.sensor.getType() == Sensor.TYPE_LIGHT) { + final float currentReading = event.values[0]; + //Log.d("### VALUES: ", String.valueOf(currentReading)); + currentLightValue = currentReading; + + JSONObject lightValue = new JSONObject(); + try { + lightValue.put(LUMINANCE, currentReading); + } catch (Exception e) { + e.printStackTrace(); + } + + reportChanges(lightValue); + } + } + }; + + @Override + public void startLightSensor() { + Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + sensorManager.registerListener(lightSensorEventListener, + sensor, + 99999999); + } + + @Override + public void stopLightSensor() { + Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + sensorManager.unregisterListener(lightSensorEventListener, sensor); + } + + @Override + public String getLightSensor () { + Sensor sensor = sensorManager.getDefaultSensor(Sensor.TYPE_LIGHT); + + JSONObject sensorJSON = new JSONObject(); + try { + sensorJSON.put("name", sensor.getName()); + sensorJSON.put("type", getSensorType(sensor.getType())); + sensorJSON.put("maximumRange", sensor.getMaximumRange()); + sensorJSON.put("minDelay", sensor.getMinDelay()); + sensorJSON.put("resolution", sensor.getResolution()); + sensorJSON.put("version", sensor.getVersion()); + sensorJSON.put("vendor", sensor.getVendor()); + sensorJSON.put("power", sensor.getPower()); + sensorJSON.put("currentValue", currentLightValue); + } catch (Exception e) { + e.printStackTrace(); + } + + return sensorJSON.toString(); + } + + private String getSensorType (int value) { + for (String key: SENSOR_TYPE.keySet()) { + if (SENSOR_TYPE.get(key).equals(value)) { + return key; + } + } + return "TYPE_UNKNOWN"; + } + + // This function is where we make a PUT reuest to the flow manager + // telling the new value for a concrete sensor + // + public void reportChanges (JSONObject change) { + Log.d(TAG, "[node] on reportChanges"); + + HttpClient client = new DefaultHttpClient(); + HttpPut put = new HttpPut(endPoint.toString()); + StringEntity se = null; + + try { + se = new StringEntity(change.toString()); + } catch (UnsupportedEncodingException e) { + e.printStackTrace(); + }; + + se.setContentType("application/json;charset=UTF-8"); + se.setContentEncoding(new BasicHeader(HTTP.CONTENT_TYPE, "application/json;charset=UTF-8")); + put.setEntity(se); + + // TODO: Improve the handling of exceptions + // + try { + HttpResponse response = client.execute(put); + Log.d(TAG, "[node] after client.execute"); + } catch (ConnectException e) { + //e.printStackTrace(); + } catch (IOException e) { + e.printStackTrace(); + } + } + + @Override + public void setEndPoint(String value) { + endPoint = value; + } + + @Override + public String getEndPoint() { + return endPoint; + } + + @Override + public void startNoiseSensor() { + Log.d(TAG, "[node] Starting noise sensor ...."); + // This approach didn't work, so we're scheduling reads from the + // javascript side of the code by using node's setInterval + // + //mHandler = new Handler(); + //if (runner == null) { + // Not working - is AsyncTask the solution? + //runner = new Thread() { + // public void run() { + // Log.d("NOISE", "[node] RUNNING the Thread"); + // while (runner != null) { + // try { + // Thread.sleep(500); + // Log.d("Noise", "Tick"); + // } catch (InterruptedException e) { + // Log.e("Noise", "" + e.getMessage()); + // } + // mHandler.post(updater); + // } + // } + //}; + //runner.start(); + //Log.d("Noise", "start runner()"); + //} + startRecorder(); + } + + @Override + public void stopNoiseSensor() { + stopRecorder(); + } + + public void startRecorder() { + Log.d(TAG, "[node] on startRecorder"); + if (mRecorder == null) { + mRecorder = new MediaRecorder(); + mRecorder.setAudioSource(MediaRecorder.AudioSource.MIC); + mRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP); + mRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB); + mRecorder.setOutputFile("/dev/null"); + + try { + mRecorder.prepare(); + } catch (java.io.IOException ioe) { + android.util.Log.e("[Monkey]", "IOException: " + android.util.Log.getStackTraceString(ioe)); + } catch (java.lang.SecurityException e) { + android.util.Log.e("[Monkey]", "SecurityException: " + android.util.Log.getStackTraceString(e)); + } + + try { + mRecorder.start(); + } catch (java.lang.SecurityException e) { + android.util.Log.e("[Monkey]", "SecurityException: " + android.util.Log.getStackTraceString(e)); + } + } + } + + public void stopRecorder() { + if (mRecorder != null) { + mRecorder.stop(); + mRecorder.release(); + mRecorder = null; + } + } + + public void updateNoiseValue() { + DecimalFormat df = new DecimalFormat("#.##"); + float currentReading = Float.parseFloat(df.format(getAmplitudeEMA())); + + JSONObject noiseValue = new JSONObject(); + try { + noiseValue.put(NOISE, currentReading); + } catch (Exception e) { + e.printStackTrace(); + } + + reportChanges(noiseValue); + } + + public double getAmplitude() { + if (mRecorder != null) + return (mRecorder.getMaxAmplitude()); + else + return 0; + } + + @Override + public double getAmplitudeEMA() { + double amp = getAmplitude(); + mEMA = EMA_FILTER * amp + (1.0 - EMA_FILTER) * mEMA; + return mEMA; + } + +} diff --git a/platform/sensors/src/net/gpii/SensorDict.java b/platform/sensors/src/net/gpii/SensorDict.java new file mode 100644 index 0000000..82cd71a --- /dev/null +++ b/platform/sensors/src/net/gpii/SensorDict.java @@ -0,0 +1,16 @@ +package net.gpii; + +import org.meshpoint.anode.idl.Dictionary; + +public class SensorDict implements Dictionary { + public String name; + public String type; // This is a float const, but we will use a string + public float maximumRange; + public int minDelay; + // Commented because it's from API level 21 + //public int maxDelay; + public float resolution; + public float version; + public String vendor; + public float power; +} diff --git a/platform/sensors/src/org/meshpoint/anode/stub/gen/platform/Net_gpii_AndroidSensors.java b/platform/sensors/src/org/meshpoint/anode/stub/gen/platform/Net_gpii_AndroidSensors.java new file mode 100644 index 0000000..235d155 --- /dev/null +++ b/platform/sensors/src/org/meshpoint/anode/stub/gen/platform/Net_gpii_AndroidSensors.java @@ -0,0 +1,45 @@ +/* This file has been automatically generated; do not edit */ + +package org.meshpoint.anode.stub.gen.platform; + +public final class Net_gpii_AndroidSensors { + + private static Object[] __args = new Object[1]; + + public static Object[] __getArgs() { return __args; } + + static Object __invoke(net.gpii.AndroidSensors inst, int opIdx, Object[] args) { + Object result = null; + switch(opIdx) { + case 0: /* getAmplitudeEMA */ + result = org.meshpoint.anode.js.JSValue.asJSNumber(inst.getAmplitudeEMA()); + break; + case 1: /* getEndPoint */ + result = inst.getEndPoint(); + break; + case 2: /* getLightSensor */ + result = inst.getLightSensor(); + break; + case 3: /* setEndPoint */ + inst.setEndPoint( + (String)args[0] + ); + break; + case 4: /* startLightSensor */ + inst.startLightSensor(); + break; + case 5: /* startNoiseSensor */ + inst.startNoiseSensor(); + break; + case 6: /* stopLightSensor */ + inst.stopLightSensor(); + break; + case 7: /* stopNoiseSensor */ + inst.stopNoiseSensor(); + break; + default: + } + return result; + } + +}