diff --git a/js/ui/cordova/plugins/cordova-plugin-android-sensor-listeners/www/sensors.js b/js/ui/cordova/plugins/cordova-plugin-android-sensor-listeners/www/sensors.js new file mode 100644 index 0000000..188ec6e --- /dev/null +++ b/js/ui/cordova/plugins/cordova-plugin-android-sensor-listeners/www/sensors.js @@ -0,0 +1,243 @@ +/* Sensor Listener +* +* Example: +* +* function listener(event) { +* console.log("device's rotation is " + event.values.join(",")); +* } +* +* sensors.addSensorListener("ROTATION_VECTOR", "GAME", listener, function(error) { +* if (error) console.error("Could not listen to sensor"); +* }); +* +*/ + +var createRegistry = require('./registry'); + +/** + * @namespace sensors + */ + +/** + * Event emitted from sensors. + * @typedef {Object} SensorEvent + * @property {string} sensor - The type of sensor that is listened to. + * @property {string} sampling - The sampling period the sensor is listened to + * by the receiving event listener. + * @property {int} timeStamp - The time the event was emitted. + * @property {float[]} values - The sensor values. + */ + +/** + * This callback is used to get responses from async calls. It complies with + * nodeJS callback style. + * @callback errorFirstCallback + * @param {*} [err] - the error or undefined if everything went fine + * @param {*} data - the response or the called function + */ + +/** + * This listener is used to receive events from sensors. + * @callback sensorEventListener + * @param {SensorEvent} evt - the event emitted by one of the sensor + */ + +// Small utility that enables to answer to a node-style callback in a Promise- +// like fashion. Make sure the error and result arguments are properly +// distributed. +function unpromisify(nodeStyleCallback, f) { + function resolve(r) { + nodeStyleCallback && nodeStyleCallback(null, r); + } + function reject(error) { + // The error cannot be undefined. + nodeStyleCallback && nodeStyleCallback(error || new Error()); + } + try { + f(resolve, reject); + } catch (error) { + reject(error); + } +} + +// Store the event listeners. +var listenerRegistry = createRegistry(); + +// Status of the connection with Android. +var state = 'init'; + +// Store functions to be called only after the registration is successful. +var registrationQueue = []; + +// Function decorator that makes sure a function is only called after the +// registration. +function afterRegistration(f) { + return function() { + if (state === 'init') { + var args = arguments; + var that = this; + registrationQueue.push(function() { + f.apply(that, args); + }); + } else { + f.apply(this, arguments); + } + }; +} + +/** + * Called when a subscribed sensor sent an event. + * + * @ignore + * @param {SensorEvent} event - The event. + * @return {undefined} + */ +function onSensorEvent(event) { + if (!event.sensor) { + throw new Error('onSensorEvent called with wrong event format.'); + } + if ( + !listenerRegistry.getListenersCaller(event.sensor, event.sampling)(event) + ) { + throw new Error( + 'Received unexpected event from non subscribed sensor: ' + event.sensor + ); + } +} + +// Register the event callback. +document.addEventListener('deviceready', function() { + // This handler is called when Java push data to this plugin. + // It needs to be registered as soon as possible. + var registrationCallback = function handleConfirmation(resp) { + if (resp === 'registered') { + // Once the confirmation has been received, this directly handle events. + registrationCallback = onSensorEvent; + // Update the state and call any pending pending call in the registration + // queue. + state = 'registered'; + var n = registrationQueue.length; + for (var i = 0; i < n; i += 1) { + registrationQueue[i](); + } + registrationQueue = null; + } else { + state = 'error'; + throw new Error( + 'Expecting confirmation for the callback registration but received: ' + + resp + ); + } + }; + cordova.exec( + function() { + registrationCallback.apply(this, arguments); + }, + function(e) { + console.error(e.stack || e); + throw e; + }, + 'Sensors', + 'registerCallback', + [] + ); +}); + +/** + * Add a sensor listener. + * @function + * @memberof sensors + * @name addSensorListener + * @param {string} sensorType - The sensor type's constant name (as defined by + * [Android's Sensor]{@link https://developer.android.com/guide/topics/sensors/sensors_overview.html}, + * but without the prefix `'TYPE_'`). + * @param {string} samplingPeriod - The sampling period's constant name (as + * accepted by [SensorManager#registerListener]{@link https://developer.android.com/reference/android/hardware/SensorManager.html#registerListener(android.hardware.SensorEventListener,%20android.hardware.Sensor,%20int)}, + * but without the prefix `'SENSOR_DELAY_'`). + * @param {sensorEventListener} listener - The listener to register. + * @param {errorFirstCallback} [callback] - A node-style callback to be called + * upon success or failure of the operation. + * @return {undefined} + * @example + * function listener(event) { + * console.log("device's rotation is " + event.values.join(",")); + * } + * + * sensors.addSensorListener('ROTATION_VECTOR', 'GAME', listener, function(error) { + * if (error) console.error('Could not listen to sensor'); + * }); + */ +module.exports.addSensorListener = afterRegistration(function( + sensorType, + samplingPeriod, + listener, + callback +) { + unpromisify(callback, function(resolve, reject) { + if (listenerRegistry.addListener(sensorType, samplingPeriod, listener)) { + // `listenerRegistry.addListener` returns true if there was no listener + // of this type when the listener has been added. In this case, we + // subscribe to the sensor notifications. + cordova.exec( + resolve, + function() { + listenerRegistry.removeListener(sensorType, samplingPeriod, listener); + reject.apply(this, arguments); + }, + 'Sensors', + 'subscribe', + [sensorType, samplingPeriod] + ); + } else { + // If there is other listeners registered for this type, we immediately + // resolve; + resolve(); + } + }); +}); + +/** + * Remove a sensor listener. + * @function + * @memberof sensors + * @name removeSensorListener + * @param {string} sensorType - The type of the sensor as registered when + * the listener was added (see {@link sensors.addSensorListener}). + * @param {string} samplingPeriod - The sampling period as registered when + * the listener was added (see {@link sensors.addSensorListener}). + * @param {sensorEventListener} listener - The listener to remove. + * @param {errorFirstCallback} [callback] - A node-style callback to be called + * upon success or failure of the operation. + * @return {undefined} + * @example + * sensors.removeSensorListener('ROTATION_VECTOR', 'GAME', listener, function(error) { + * if (error) console.error('Could not stop listening to sensor'); + * }); + */ +module.exports.removeSensorListener = afterRegistration(function( + sensorType, + samplingPeriod, + listener, + callback +) { + unpromisify(callback, function(resolve, reject) { + if ( + listenerRegistry.containsListener(sensorType, samplingPeriod, listener) + ) { + cordova.exec( + function(result) { + // Do not remove the listener before receiving the confirmation as we + // may still receive events in the meantime. + listenerRegistry.removeListener(sensorType, samplingPeriod, listener); + resolve(result); + }, + reject, + 'Sensors', + 'unsubscribe', + [sensorType, samplingPeriod] + ); + } else { + resolve(); + } + }); +});