public class CameraLightSensor extends BaseDataProducer{

	/**
	 * Callback that handles a new light value datapoint and sends it to the MsgHandler.
	 */
	private class NewCameraLightValue implements CameraLightValueCallback {

		@Override
		public void lightValueCallback(float lightValue, int camera_id) {
			// send the light value to commonSense
			String sensorDisplayName = "Camera Light";
			String sensorName = SensorNames.CAMERA_LIGHT;
			String sensorDescription = "camera " + camera_id + " average luminance";
			HashMap<String, Object> dataFields = new HashMap<String, Object>();
			dataFields.put("lux", lightValue);
			JSONObject jsonObj = new JSONObject(dataFields);
			String jsonString = jsonObj.toString();

			notifySubscribers();
			SensorDataPoint dataPoint = new SensorDataPoint(jsonObj);
			dataPoint.sensorName = sensorName;
			dataPoint.sensorDescription = sensorDescription;
			dataPoint.timeStamp = SNTP.getInstance().getTime();        
			sendToSubscribers(dataPoint);

			// pass message to the MsgHandler
			Intent i = new Intent(context.getString(R.string.action_sense_new_data));
			i.putExtra(DataPoint.SENSOR_NAME, sensorName);
			i.putExtra(DataPoint.DISPLAY_NAME, sensorDisplayName);
			i.putExtra(DataPoint.VALUE, jsonString);
			i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensorDescription);
			i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
			i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
			i.setPackage(context.getPackageName());
			context.startService(i);
			// Log.e(TAG, "Sent new camera licht values, camera: "+camera_id+" value: "+lightValue);
			nextUpdate(camera_id);
		}
	}

	/**
	 * Task to capture a new light value from a camera. Reschedules itself when done.
	 */
	private class UpdateAndSendCameraLightValues implements Runnable {
		int camera_id;

		public UpdateAndSendCameraLightValues(int camera_id) {
			this.camera_id = camera_id;
		}

		@Override
		public void run() {
			try {
				if (!cameraLightValue.getLightValue(camera_id, new NewCameraLightValue())) {
					// something failed, probably camera.open so no callback
					nextUpdate(camera_id);
				}
			} catch (Exception e) {
				Log.w(TAG, "Failed to update camera light sensor: " + e);
			}
		}
	}

	private Context context;
	private CameraLightValue cameraLightValue;
	private int sampleDelay;
	private HandlerThread sensorHandlerThread = null;
	private Handler cameraLightValueHandler = null;
	private UpdateAndSendCameraLightValues updateAndSendValues = null;
	private boolean sensorEnabled;

	private String TAG = "Camera Light Sensor";

	private CameraLightSensor(Context context) {
		this.context = context;
		cameraLightValue = new CameraLightValue();
	}

	private static CameraLightSensor instance = null;

	public static CameraLightSensor getInstance(Context context) {
		if(instance == null) {
			instance = new CameraLightSensor(context);
		}
		return instance;
	}

	private void nextUpdate(int camera_id) {
		// check if there are more camera's else wait
		// add again for another run if the sensor is still enabled
		if (sensorEnabled && cameraLightValueHandler != null) {
			// last camera wait schedule a new run
			if (camera_id == cameraLightValue.getNumberOfCameras() - 1) {
				int deplayMilisec = sampleDelay == -1 ? 1000 : sampleDelay;
				cameraLightValueHandler.postDelayed(
						updateAndSendValues = new UpdateAndSendCameraLightValues(0), deplayMilisec);
			} else {
				++camera_id;
				cameraLightValueHandler
				.post(updateAndSendValues = new UpdateAndSendCameraLightValues(camera_id));
			}
		}
	}

	/**
	 * Starts sampling the cameras periodically.
	 * 
	 * @param sampleDelay
	 *            The time between samples (milliseconds)
	 */
	public void startLightSensing(int sampleDelay) {
		try {
			this.sampleDelay = sampleDelay;
			sensorEnabled = true;
			// create a new thread to run this sensor on
			if (sensorHandlerThread == null)
				sensorHandlerThread = new HandlerThread("CameraLightSensor");
			// start the thread;
			if (!sensorHandlerThread.isAlive())
				sensorHandlerThread.start();
			// create a handler on the thread to run the update and send functions
			if (cameraLightValueHandler == null)
				cameraLightValueHandler = new Handler(sensorHandlerThread.getLooper());
			// add the runnable
			if (updateAndSendValues == null)
				cameraLightValueHandler
				.post(updateAndSendValues = new UpdateAndSendCameraLightValues(0));
		} catch (Exception e) {
			Log.e(TAG, "Error in starting the Camera Light sensor: " + e.getMessage());
		}
	}

	public void stopLightSensing() {
		try {
			sensorEnabled = false;
			if (cameraLightValueHandler != null)
				cameraLightValueHandler.removeCallbacks(updateAndSendValues);
			updateAndSendValues = null;
			cameraLightValueHandler = null;
			if (sensorHandlerThread != null)
				sensorHandlerThread.getLooper().quit();
			sensorHandlerThread = null;
		} catch (Exception e) {
			Log.e(TAG, "Error in stopping the camera light sensor");
		}
	}
} public class HumiditySensor extends BaseSensor implements SensorEventListener,
        PeriodicPollingSensor {

    private static HumiditySensor sInstance = null;
    private static final String TAG = "HumiditySensor";

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static HumiditySensor getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new HumiditySensor(context);
        }
        return sInstance;
    }

    private boolean mActive;
    private Context mContext;
    private PeriodicPollAlarmReceiver mSampleAlarmReceiver;
    private SensorManager mSensorMgr;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected HumiditySensor(Context context) {
        this.mContext = context;
        mSampleAlarmReceiver = new PeriodicPollAlarmReceiver(this);
        mSensorMgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void doSample() {
        //Log.v(TAG, "Do sample");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            Sensor sensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
            if (null != sensor) {
                mSensorMgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    }

    @Override
    public boolean isActive() {
        return mActive;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // nothing to do
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;

        String sensorName = SensorNames.RELATIVE_HUMIDITY;

        // relative humidity in percent
        float value = BigDecimal.valueOf(event.values[0]).setScale(2, 0).floatValue();

        this.notifySubscribers();
        SensorDataPoint dataPoint = new SensorDataPoint(value);
        dataPoint.sensorName = sensorName;
        dataPoint.sensorDescription = sensor.getName();
        dataPoint.timeStamp = SNTP.getInstance().getTime();
        this.sendToSubscribers(dataPoint);

        // send msg to MsgHandler
        Intent i = new Intent(mContext.getString(R.string.action_sense_new_data));
        i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
        i.putExtra(DataPoint.VALUE, value);
        i.putExtra(DataPoint.SENSOR_NAME, sensorName);
        i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
        i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
        i.setPackage(mContext.getPackageName());
        mContext.startService(i);

        // done with sample
        stopSample();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void startSensing(long sampleDelay) {
        Log.v(TAG, "Start sensing");

        boolean found = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            Sensor sensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_RELATIVE_HUMIDITY);
            if (null != sensor) {
                found = true;
            }
        }
        if (!found) {
            Log.w(TAG, "No humidity sensor available!");
            return;
        }

        setSampleRate(sampleDelay);

        // start polling
        mActive = true;
        mSampleAlarmReceiver.start(mContext);

        // do the first sample immediately
        doSample();
    }

    private void stopSample() {
        Log.v(TAG, "Stop sample");
        try {
            mSensorMgr.unregisterListener(this);
        } catch (Exception e) {
            Log.e(TAG, "Error stopping humidity sensor: " + e);
        }
    }

    @Override
    public void stopSensing() {
        Log.v(TAG, "Stop sensing");

        // stop polling
        mSampleAlarmReceiver.stop(mContext);
        mActive = false;

        // stop sample
        stopSample();
    }
} public class LightSensor extends BaseSensor implements SensorEventListener, PeriodicPollingSensor {

    private static final String TAG = "Sense Light Sensor";
    private static LightSensor instance = null;

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static LightSensor getInstance(Context context) {
        if (instance == null) {
            instance = new LightSensor(context);
        }

        return instance;
    }

    private long[] lastSampleTimes = new long[50];
    private Context context;
    private List<Sensor> sensors;
    private SensorManager smgr;
    private PeriodicPollAlarmReceiver pollAlarmReceiver;
    private Controller controller;
    private boolean active;
    private boolean listening;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected LightSensor(Context context) {
        this.context = context;
        smgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        pollAlarmReceiver = new PeriodicPollAlarmReceiver(this);
        controller = Controller.getController(context);
        sensors = new ArrayList<Sensor>();
        if (null != smgr.getDefaultSensor(Sensor.TYPE_LIGHT)) {
            sensors.add(smgr.getDefaultSensor(Sensor.TYPE_LIGHT));
        }
    }

    @Override
    public void doSample() {
       //Log.v(TAG, "Do sample");
        for (Sensor sensor : sensors) {
            if (sensor.getType() == Sensor.TYPE_LIGHT) {
                // Log.d(TAG, "Register for sensor " + sensor.getName());

                if (listening) {
                    stopSample();
                }

                listening = smgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
                if (!listening) {
                    Log.w(TAG, "Failed to register for light sensor!");
                }
            }
        }
    }

    @Override
    public boolean isActive() {
        return active;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // do nothing
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;
        if (SNTP.getInstance().getTime() > lastSampleTimes[sensor.getType()] + getSampleRate()) {
            lastSampleTimes[sensor.getType()] = SNTP.getInstance().getTime();

            String sensorName = "";
            if (sensor.getType() == Sensor.TYPE_LIGHT) {
                sensorName = SensorNames.LIGHT;
            }

            String jsonString = "{";
            int x = 0;
            for (float value : event.values) {
                if (x == 0) {
                    if (sensor.getType() == Sensor.TYPE_LIGHT) {
                        jsonString += "\"lux\":" + value;
                        controller.checkLightSensor(value);
                    }
                }
                x++;
            }
            jsonString += "}";

            long time = SNTP.getInstance().getTime();
            try {
                JSONObject jsonObj = new JSONObject(jsonString);
                this.notifySubscribers();
                SensorDataPoint dataPoint = new SensorDataPoint(jsonObj);
                dataPoint.sensorName = sensorName;
                dataPoint.sensorDescription = sensor.getName();
                dataPoint.timeStamp = time;
                this.sendToSubscribers(dataPoint);
            } catch (Exception e) {
                Log.e(TAG, "Error in send to subscribers of the light sensor");
            }
            // pass message to the MsgHandler
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.SENSOR_NAME, sensorName);
            i.putExtra(DataPoint.VALUE, jsonString);
            i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
            i.putExtra(DataPoint.TIMESTAMP, time);
            i.setPackage(context.getPackageName());
            this.context.startService(i);

            stopSample();
        }
    }

    @Override
    public void startSensing(long sampleDelay) {
        //Log.v(TAG, "Start sensing");

        // check if the sensor is physically present on this phone
        boolean found = false;
        for (Sensor sensor : sensors) {
            if (sensor.getType() == Sensor.TYPE_LIGHT) {
                found = true;
                break;
            }
        }
        if (!found) {
            Log.w(TAG, "No light field sensor!");
            return;
        }

        setSampleRate(sampleDelay);

        active = true;
        pollAlarmReceiver.start(context);

        // do the first sample immediately
        doSample();
    }

    /**
     * Unregisters the listener
     */
    private void stopSample() {
        //Log.v(TAG, "Stop sample");
        try {
            smgr.unregisterListener(this);
            listening = false;
        } catch (Exception e) {
            Log.e(TAG, "Failed to stop light sensor sample!", e);
        }
    }

    @Override
    public void stopSensing() {
        //Log.v(TAG, "Stop sensing");
        pollAlarmReceiver.stop(context);
        stopSample();
        active = false;
    }
} public class MagneticFieldSensor extends BaseSensor implements SensorEventListener,
        PeriodicPollingSensor {

    private static final String SENSOR_DISPLAY_NAME = "magnetic field";
    private static MagneticFieldSensor sInstance;
    private static final String TAG = "MagneticFieldSensor";

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static MagneticFieldSensor getInstance(Context context) {
        if (null == sInstance) {
            sInstance = new MagneticFieldSensor(context);
        }
        return sInstance;
    }

    private boolean mActive;
    private Context mContext;
    private PeriodicPollAlarmReceiver mSampleAlarmReceiver;
    private SensorManager mSensorMgr;
    private List<Sensor> mSensors;
    private WakeLock mWakeLock;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected MagneticFieldSensor(Context context) {
        mContext = context;
        mSensorMgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        mSensors = new ArrayList<Sensor>();
        if (null != mSensorMgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD)) {
            mSensors.add(mSensorMgr.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD));
        }
        mSampleAlarmReceiver = new PeriodicPollAlarmReceiver(this);
    }

    @Override
    public void doSample() {
        //Log.v(TAG, "Do sample");

        // acquire wake lock
        if (null == mWakeLock) {
            PowerManager powerMgr = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            mWakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
            mWakeLock.setReferenceCounted(false);
        }
        if (!mWakeLock.isHeld()) {
            mWakeLock.acquire();
        } else {
            // wake lock already held
        }

        // register as sensor listener
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
                // Log.v(TAG, "registering for sensor " + sensor.getName());
                mSensorMgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    }

    @Override
    public boolean isActive() {
        return mActive;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // not used
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;

        String sensorName = "";
        if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
            sensorName = SensorNames.MAGNETIC_FIELD;

            double x = event.values[0];
            // scale to three decimal precision
            x = BigDecimal.valueOf(x).setScale(3, 0).doubleValue();
            double y = event.values[1];
            // scale to three decimal precision
            y = BigDecimal.valueOf(y).setScale(3, 0).doubleValue();
            double z = event.values[2];
            // scale to three decimal precision
            z = BigDecimal.valueOf(z).setScale(3, 0).doubleValue();

            HashMap<String, Object> dataFields = new HashMap<String, Object>();
            dataFields.put("x", x);
            dataFields.put("y", y);
            dataFields.put("z", z);
            JSONObject jsonObj = new JSONObject(dataFields);
            String jsonString = jsonObj.toString();

            this.notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(jsonObj);
            dataPoint.sensorName = sensorName;
            dataPoint.sensorDescription = sensor.getName();
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            this.sendToSubscribers(dataPoint);

            // send msg to MsgHandler
            Intent i = new Intent(mContext.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
            i.putExtra(DataPoint.VALUE, jsonString);
            i.putExtra(DataPoint.SENSOR_NAME, sensorName);
            i.putExtra(DataPoint.DISPLAY_NAME, SENSOR_DISPLAY_NAME);
            i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(mContext.getPackageName());
            mContext.startService(i);

            // sample is successful: unregister the listener
            stopSample();
        }
    }

    /**
     * Sets the delay between samples. The sensor registers itself for periodic sampling bursts, and
     * unregisters after it received a sample.
     * 
     * @param sampleDelay
     *            Sample delay in milliseconds
     */
    @Override
    public void setSampleRate(long sampleDelay) {
        stopPolling();
        super.setSampleRate(sampleDelay);
        startPolling();
    }

    private void startPolling() {
        //Log.v(TAG, "Start polling");
        mSampleAlarmReceiver.start(mContext);
    }

    /**
     * Starts sensing by registering for updates at the Android SensorManager. The sensor registers
     * for updates at the rate specified by the sampleDelay parameter.
     * 
     * @param sampleDelay
     *            Delay between samples in milliseconds
     */
    @Override
    public void startSensing(long sampleRate) {
        //Log.v(TAG, "Start sensing");

        // check if the sensor is physically present on this phone
        boolean found = false;
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_MAGNETIC_FIELD) {
                found = true;
                break;
            }
        }
        if (!found) {
            Log.w(TAG, "No magnetic field sensor!");
            return;
        }

        mActive = true;
        setSampleRate(sampleRate);

        // do the first sample immediately
        doSample();
    }

    private void stopPolling() {
        //Log.v(TAG, "Stop polling");
        mSampleAlarmReceiver.stop(mContext);
        stopSample();
    }

    private void stopSample() {
        //Log.v(TAG, "Stop sample");

        // release wake lock
        if (null != mWakeLock && mWakeLock.isHeld()) {
            mWakeLock.release();
        }

        // unregister sensor listener
        try {
            mSensorMgr.unregisterListener(this);
        } catch (Exception e) {
            Log.e(TAG, "Failed to stop magnetic field sample!", e);
        }

    }

    /**
     * Stops the periodic sampling.
     */
    @Override
    public void stopSensing() {
        //Log.v(TAG, "Stop sensing");
        mActive = false;
        stopPolling();

    }
} public class NoiseSensor extends BaseSensor implements PeriodicPollingSensor {

	/**
	 * Runnable that performs one noise sample. Starts the recording, reads the buffer contents,
	 * calculates the noise power and sends the measurement to the {@link MsgHandler}. Also
	 * schedules the next sample job.
	 */
	private class NoiseSampleJob implements Runnable {

		private static final int DEFAULT_SAMPLE_RATE = 44100;
		/*
		 * samples per second * 2 seconds, 2 bytes
		 */
		private static final int RECORDING_TIME_NOISE = 5000;
		private static final int BYTES_PER_SAMPLE = 2;
		private static final int BUFFER_SIZE = (int) ((float) DEFAULT_SAMPLE_RATE
				* (float) BYTES_PER_SAMPLE * ((float) RECORDING_TIME_NOISE / 1000f));
		private AudioRecord audioRecord;
		private int FFT_BANDWITH_RESOLUTION = 50;
		private int FFT_MAX_HZ = 8000;

		// power of 2

		private float[] audioToFloat(byte[] buffer, int readBytes) {
			float[] samples = new float[readBytes / 2];
			int cnt = 0;
			for (int x = 0; x < readBytes - 1; x = x + 2) {
				double sample = 0;
				for (int b = 0; b < BYTES_PER_SAMPLE; b++) {
					int v = (int) buffer[x + b];
					if (b < BYTES_PER_SAMPLE - 1 || BYTES_PER_SAMPLE == 1) {
						v &= 0xFF;
					}
					sample += v << (b * 8);
				}
				samples[cnt++] = (float) sample;
			}
			return samples;
		}

		/**
		 * @param samples
		 *            The sound data float values to calculate the power for.
		 * 
		 * @return the noise power of the current buffer. In case of an error, -1 is returned.
		 */
		private double calculateDb(float[] samples) {

			double dB = 0;
			try {
				if (!active) {
					Log.w(TAG, "Noise sensor is disabled, skipping noise power calculation...");
					return -1;
				}

				if (samples.length <= 0) {
					Log.e(TAG, "Error reading AudioRecord buffer: " + samples.length);
					return -1;
				}
				double ldb = 0;
				for (int x = 0; x < samples.length; ++x) {
					ldb += ((double) samples[x] * (double) samples[x]);
				}

				ldb /= (double) samples.length;
				dB = 10.0 * Math.log10(ldb);

			} catch (Exception e) {
				Log.e(TAG, "Exception calculating noise Db!", e);
				return -1;
			}

			// Log.d(TAG, "noise in db " + dB);
			return dB;
		}

		private double[] calculateSpectrum(float[] samples) {
			if (samples == null || samples.length == 0)
				return null;
			int nrSamples = (int) Math.pow(2, (int) (Math.log(samples.length) / Math.log(2)));
			int nrBands = (int) Math.ceil((float)DEFAULT_SAMPLE_RATE/2f / (float)FFT_BANDWITH_RESOLUTION);
			if(nrBands > (int) Math.ceil((float)FFT_MAX_HZ/(float)FFT_BANDWITH_RESOLUTION))
				nrBands = (int) Math.ceil((float)FFT_MAX_HZ/(float)FFT_BANDWITH_RESOLUTION);

			//remove the dc-component
			double mean = 0;
			for (int i = 0; i < samples.length; i++)
				mean += samples[i];									
			mean /= samples.length;

			FFT fft = new FFT(nrSamples, DEFAULT_SAMPLE_RATE);	                        
			fft.forward(copyOfRange(samples, 0, nrSamples,(float)mean));

			double[] bins = new double[nrBands];	            	           
			// computing averages does not work with small sample sizes, compute our own
			for (int i = 0; i < nrBands; i++)
			{	            	
				float avg = 0;
				for (int j = 0; j < FFT_BANDWITH_RESOLUTION; j++) {
					avg += fft.getFreq(i*FFT_BANDWITH_RESOLUTION+j);					
				}
				avg /= FFT_BANDWITH_RESOLUTION;	            	
				bins[i] = 10.0 * Math.log10(avg);           	
			}
			return bins;
		}

		// java versions before 6 don't have Arrays.copyOfRange, so make our own
		private float[] copyOfRange(float[] array, int start, int end, float mean) {
			if (end < start || start < 0)
				throw new IndexOutOfBoundsException(); // isn't there a
			// RangeException??
			if (array.length < end - start)
				throw new IndexOutOfBoundsException();
			float[] copy = new float[end - start];
			for (int i = start, j = 0; i < end; i++, j++)
				copy[j] = array[i]-mean;

			return copy;
		}

		/**
		 * @return <code>true</code> if {@link #audioRecord} was initialized successfully
		 */
		private boolean initAudioRecord() {
			// Log.d(TAG, "Initializing AudioRecord instance");

			if (null != audioRecord) {
				Log.w(TAG, "AudioRecord object is already present! Release it");
				// release the audioRecord object and stop any recordings that
				// are running
				stopSampling();
			}

			// create the AudioRecord
			try {
				int audioSource = -1, channelConfig = -1;
				if (calling) {
					audioSource = MediaRecorder.AudioSource.VOICE_UPLINK;
					channelConfig = AudioFormat.CHANNEL_IN_DEFAULT;

				} else {
					// Using VOICE_RECOGNITION (value 6) as AudioSource.
					// Android Compatibility Definition require VOICE_RECOGNITION
					// use case to disable all pre-processing effect.
					audioSource = MediaRecorder.AudioSource.VOICE_RECOGNITION;
					channelConfig = AudioFormat.CHANNEL_IN_MONO;

				}
				audioRecord = new AudioRecord(audioSource, DEFAULT_SAMPLE_RATE, channelConfig,
						AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE);
			} catch (IllegalArgumentException e) {
				Log.e(TAG, "Failed to create the audiorecord!", e);
				return false;
			}

			if (audioRecord.getState() == AudioRecord.STATE_UNINITIALIZED) {
				Log.w(TAG, "Failed to create AudioRecord!");
				// Log.d(TAG, "format: " + audioRecord.getAudioFormat() +
				// " source: " +
				// audioRecord.getAudioSource() + " channel: " +
				// audioRecord.getChannelConfiguration() + " buffer size: " +
				// BUFFER_SIZE);
				return false;
			}

			// initialized OK
			return true;
		}

		@Override
		public void run() {

			if (active && !calling) {

				boolean init = initAudioRecord();

				if (init) {
					long startTimestamp = SNTP.getInstance().getTime();
					try {
						Log.i(TAG, "Start recording for sound level measurement...");
						audioRecord.startRecording();

						// schedule task to stop recording and calculate the
						// noise
						long now = SNTP.getInstance().getTime();
						byte[] totalBuffer = new byte[BUFFER_SIZE];
						int readCount = 0;
						while (audioRecord != null
								&& SNTP.getInstance().getTime() < now + RECORDING_TIME_NOISE) {
							int chunkSize = Math.min(256, totalBuffer.length - readCount);
							int readResult = audioRecord.read(totalBuffer, readCount, chunkSize);
							if (readResult < 0) {
								Log.e(TAG, "Error reading AudioRecord: " + readResult);
								readCount = readResult;
								break;
							} else {
								// Log.d(TAG, "Read " + readResult + " bytes");
								readCount += readResult;
								if (readCount >= totalBuffer.length) {
									// Log.d(TAG, "Buffer overflow");
									break;
								}
							}
						}

						float[] samples = audioToFloat(totalBuffer, readCount);
						double dB = -1;
						double[] spectrum = null;
						if (samples != null) {
							SharedPreferences mainPrefs = context.getSharedPreferences(
									SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
							if (mainPrefs.getBoolean(Ambience.MIC, true)) {
								dB = calculateDb(samples);                                
								controller.checkNoiseSensor(dB);
								if (mainPrefs.getBoolean(Ambience.BURSTMODE, false)) {
									noiseBurstSensor.addData(samples, RECORDING_TIME_NOISE, startTimestamp);                                	
								}
							}
							if (mainPrefs.getBoolean(Ambience.AUDIO_SPECTRUM, true))
								spectrum = calculateSpectrum(samples);
						}

						if (dB != -1 && !Double.valueOf(dB).isNaN()) {
							// Log.d(TAG, "Sampled noise level: " + dB);

							notifySubscribers();
							SensorDataPoint dataPoint = new SensorDataPoint(dB);
							dataPoint.sensorName = SensorNames.NOISE;
							dataPoint.sensorDescription = SensorNames.NOISE;
							dataPoint.timeStamp = startTimestamp;
							sendToSubscribers(dataPoint);

							// pass message to the MsgHandler
							Intent sensorData = new Intent(
									context.getString(R.string.action_sense_new_data));
							sensorData.putExtra(DataPoint.SENSOR_NAME, SensorNames.NOISE);
							sensorData.putExtra(DataPoint.VALUE,
									BigDecimal.valueOf(dB).setScale(2, 0).floatValue());
							sensorData.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
							sensorData.putExtra(DataPoint.TIMESTAMP, startTimestamp);
							sensorData.setPackage(context.getPackageName());
							context.startService(sensorData);
						}

						if (spectrum != null) {
							JSONObject jsonSpectrum = new JSONObject();
							jsonSpectrum.put("bandwidth", FFT_BANDWITH_RESOLUTION);                            
							JSONArray jsonSpectrumArray = new JSONArray();
							for (int i = 0; i < spectrum.length; i++)
							{
								if (spectrum[i] == Double.POSITIVE_INFINITY)
									jsonSpectrumArray.put(140);
								else if (spectrum[i] != Double.NaN && spectrum[i] != Double.NEGATIVE_INFINITY) // normal case
									jsonSpectrumArray.put(Math.round(spectrum[i]));
								else	
									jsonSpectrumArray.put(0);                                
							}

							jsonSpectrum.put("spectrum", jsonSpectrumArray);
							notifySubscribers();
							SensorDataPoint dataPoint = new SensorDataPoint(jsonSpectrum);
							dataPoint.sensorName = SensorNames.AUDIO_SPECTRUM;
							dataPoint.sensorDescription = "audio spectrum (dB)";
							dataPoint.timeStamp = startTimestamp;
							sendToSubscribers(dataPoint);

							Intent sensorData = new Intent(
									context.getString(R.string.action_sense_new_data));
							sensorData.putExtra(DataPoint.SENSOR_NAME, SensorNames.AUDIO_SPECTRUM);
							sensorData
							.putExtra(DataPoint.SENSOR_DESCRIPTION, "audio spectrum (dB)");
							sensorData.putExtra(DataPoint.VALUE, jsonSpectrum.toString());
							sensorData.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
							sensorData.putExtra(DataPoint.TIMESTAMP, startTimestamp);
							sensorData.setPackage(context.getPackageName());
							context.startService(sensorData);
						}

						if (dB != -1 && !Double.valueOf(dB).isNaN()) {
							loudnessSensor.onNewNoise(startTimestamp, dB);
							autoCalibratedNoiseSensor.onNewNoise(startTimestamp, dB);
						}

					} catch (Exception e) {
						Log.e(TAG, "Exception starting noise recording! " + e);
					} finally {
						stopRecording();
						// if real time, post, since alarm won't repeat
						if (getSampleRate() == -1) {
							/*
							 * Some code to align on the second, but it doesn't seem to work :-(
							 * Calendar now = Calendar.getInstance(); // calculate offset of the
							 * local clock int offset = (int) (System.currentTimeMillis() - SNTP
							 * .getInstance().getTime()); // align the start time on a second
							 * Calendar startTime = (Calendar) now.clone();
							 * startTime.set(Calendar.SECOND, 0);
							 * startTime.set(Calendar.MILLISECOND, 0); // correct for the difference
							 * in local time and ntp // time startTime.roll(Calendar.MILLISECOND,
							 * offset);
							 * 
							 * // advance to the next second until the start time // is at least 100
							 * ms in the future while (startTime.getTimeInMillis() -
							 * now.getTimeInMillis() <= 100) { startTime.roll(Calendar.SECOND, 1); }
							 * noiseSampleJob = new NoiseSampleJob();
							 * noiseSampleHandler.postAtTime(noiseSampleJob,
							 * startTime.getTimeInMillis());
							 */
							noiseSampleHandler.post(noiseSampleJob);
						}
					}

				} else {
					Log.w(TAG, "Did not start recording: AudioRecord could not be initialized!");
				}

			} else {
				// did not start recording: noise sensor is disabled
			}
		}

		/**
		 * Stops the recording and releases the AudioRecord object, making it unusable.
		 */
		protected void stopRecording() {

			try {
				if (audioRecord != null) {
					if (audioRecord.getState() == AudioRecord.STATE_INITIALIZED) {

						try {
							audioRecord.stop();
							Log.i(TAG, "Stopped recording for sound level measurement...");
						} catch (IllegalStateException e) {
							// audioRecord is probably already stopped..?
						}
					}
					audioRecord.release();
					audioRecord = null;

				}
			} catch (Exception e) {
				Log.e(TAG, "Exception while stopping noise sample recording. "+ e);
			}
		}
	}

	/**
	 * Runnable that starts one sound stream recording. Afterwards, the recording is sent to the
	 * {@link MsgHandler}. Also schedules the next sample job.
	 */
	private class SoundStreamJob implements Runnable {
		private static final int RECORDING_TIME_STREAM = 5 * 60 * 1000; // 5 minutes audio
		private MediaRecorder recorder = null;
		private String recordFileNamePrefix = Environment.getExternalStorageDirectory().getAbsolutePath()
				+ "/sense/audio";
		public String fileName;

		public SoundStreamJob() {           
			// create directory to put the sound recording
			new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/sense")
			.mkdir();
			recorder = new MediaRecorder();
		}

		@SuppressLint("InlinedApi")
		@Override
		public void run() {

			try {
				Log.d(TAG, "Start sound sample job");			
				if (calling) {
					SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
					if(mainPrefs.getBoolean(Ambience.RECORD_TWO_SIDED_VOICE_CALL, true))					
						recorder.setAudioSource(MediaRecorder.AudioSource.VOICE_CALL);
					else
						recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
				} else {
					recorder.setAudioSource(MediaRecorder.AudioSource.MIC);
				}
				recorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);

				if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.GINGERBREAD_MR1)
					recorder.setAudioEncoder(MediaRecorder.AudioEncoder.DEFAULT);
				else
					recorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_WB);

				DateFormat df = new SimpleDateFormat("_dd-MM-yyyy_HH:mm:ss", new Locale("nl"));				
				Date now = Calendar.getInstance().getTime();   
				String date = df.format(now);
				if(calling)
					fileName = recordFileNamePrefix + "_call"+ date + ".3gp";
				else
					fileName = recordFileNamePrefix + date + ".3gp";
				Log.d(TAG, "writing to file: "+fileName);
				recorder.setOutputFile(fileName);
				recorder.setMaxDuration(RECORDING_TIME_STREAM);
				recorder.setOnInfoListener(new OnInfoListener() {

					@Override
					public void onInfo(MediaRecorder mr, int what, int extra) {
						if (what == MediaRecorder.MEDIA_RECORDER_INFO_MAX_DURATION_REACHED) 
						{
							sendAudio();
							// wait until finished otherwise it will be overwritten
							SoundStreamJob tmp = soundStreamJob;
							if (isActive() && getSampleRate() == -1 && tmp.equals(soundStreamJob)) {							
								soundStreamJob = new SoundStreamJob();
								soundStreamHandler.post(soundStreamJob);
							}
						}

					}
				});

				recorder.prepare();
				recorder.start();

			} catch (final Exception e) {
				Log.e(TAG, "Error while recording sound:", e);
			}
		}

		/**
		 * Stops the recording and releases the MediaRecorder object, making it unusable.
		 */
		public void stopRecording() {

			// clean up the MediaRecorder if the mic sensor was using it
			if (recorder != null) {
				try {
					sendAudio();
				} catch (IllegalStateException e) {
					// probably already stopped
					Log.e(TAG, "Error stopping audio", e);
				}				
			}
		}

		public void sendAudio()
		{
			try {
				// recording is done, upload file
				recorder.stop();
				recorder.reset();

				notifySubscribers();
				SensorDataPoint dataPoint = new SensorDataPoint(fileName);
				dataPoint.sensorName = SensorNames.MIC;
				dataPoint.sensorDescription = SensorNames.MIC;
				dataPoint.setDataType(DataType.FILE);
				dataPoint.timeStamp = SNTP.getInstance().getTime();
				sendToSubscribers(dataPoint);

				// pass message to the MsgHandler
				Intent i = new Intent(context
						.getString(R.string.action_sense_new_data));
				i.putExtra(DataPoint.SENSOR_NAME, SensorNames.MIC);
				i.putExtra(DataPoint.VALUE, fileName);
				i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FILE);
				i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
				i.setPackage(context.getPackageName());
				context.startService(i);				

			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

	public class NoiseBurstSensor extends BaseDataProducer {
		private int INTERVAL = 100; // ms 
		public NoiseBurstSensor() {

		}
		public void addData(float [] samples, int recording_time, long start_time)
		{
			if(samples.length <= 0)
				return;
			JSONObject burst = new JSONObject();

			try {
				burst.put("interval", INTERVAL);
				burst.put("data", calculateDb(samples, (int)(((double)samples.length/(double)recording_time)*(double)INTERVAL)));
				sendData(burst, start_time);
			} catch (JSONException e) {
				Log.e(TAG,"Error in computing burst data" ,e);
			}    		
		}

		private JSONArray calculateDb(float[] samples, int step_size) 
		{
			JSONArray noise_burst = new JSONArray();    	
			try
			{
				for (int i = 0; i < samples.length; i+=step_size) {

					double dB = 0;
					double ldb = 0;

					for (int x = 0; x < step_size ; ++x) {
						ldb += ((double) samples[i+x] * (double) samples[i+x]);
					}

					ldb /= (double) step_size;
					dB = 10.0 * Math.log10(ldb);

					if (dB == Double.POSITIVE_INFINITY)
						noise_burst.put(140);
					else if (dB != Double.NaN && dB != Double.NEGATIVE_INFINITY) // normal case
						noise_burst.put(Math.round(dB));
					else	
						noise_burst.put(0);  

				}
			}
			catch(Exception e)
			{
				Log.e(TAG, "Error in calculating burst noise", e);
			}
			return  noise_burst;
		}

		public void sendData(JSONObject data, long startTimestamp)
		{
			notifySubscribers();
			SensorDataPoint dataPoint = new SensorDataPoint(data);
			dataPoint.sensorName = SensorNames.NOISE_BURST;
			dataPoint.sensorDescription =  "noise (dB)";
			dataPoint.timeStamp = startTimestamp;
			sendToSubscribers(dataPoint);

			// pass message to the MsgHandler
			// get sample rate from preferences
			SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
			if(!mainPrefs.getBoolean(Ambience.DONT_UPLOAD_BURSTS, true))
			{
				Intent sensorData = new Intent(context.getString(R.string.action_sense_new_data));
				sensorData.putExtra(DataPoint.SENSOR_NAME, SensorNames.NOISE_BURST);
				sensorData.putExtra(DataPoint.SENSOR_DESCRIPTION, "noise (dB)");
				sensorData.putExtra(DataPoint.VALUE, data.toString());
				sensorData.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
				sensorData.putExtra(DataPoint.TIMESTAMP, startTimestamp);
				sensorData.setPackage(context.getPackageName());
				context.startService(sensorData);
			}
		}
	};

	private static NoiseSensor instance = null;

	private static final String TAG = "Sense NoiseSensor";

	/**
	 * Factory method to get the singleton instance.
	 * 
	 * @param context
	 * @return instance
	 */
	public static NoiseSensor getInstance(Context context) {
		if (instance == null) {
			instance = new NoiseSensor(context);
		}
		return instance;
	}

	private boolean active = false;
	private boolean calling = false;
	private Context context;
	private Handler soundStreamHandler = new Handler(Looper.getMainLooper());
	private SoundStreamJob soundStreamJob = null;
	private Handler noiseSampleHandler = new Handler();
	private NoiseSampleJob noiseSampleJob = null;
	private LoudnessSensor loudnessSensor;
	private NoiseBurstSensor noiseBurstSensor = new NoiseBurstSensor();
	private AutoCalibratedNoiseSensor autoCalibratedNoiseSensor;
	private Controller controller;
	private PeriodicPollAlarmReceiver pollAlarmReceiver;
	private PhoneStateListener phoneStateListener = new PhoneStateListener() {

		/**
		 * Pauses sensing when the phone is used for calling, and starts it again after the call.
		 */
		@Override
		public void onCallStateChanged(int state, String incomingNumber) {
			// Log.d(TAG, "Call state changed");
			try {
				if (state == TelephonyManager.CALL_STATE_OFFHOOK) {
					calling = true;
				} else {
					calling = false;
				}

				stopSampling();

				if (isActive() ) {
					startSampling();
				}

			} catch (Exception e) {
				Log.e(TAG, "Exception in onCallStateChanged!", e);
			}
		}
	};

	/**
	 * Constructor.
	 * 
	 * @param context
	 * @see #getInstance(Context)
	 */
	protected NoiseSensor(Context context) {
		this.context = context;
		pollAlarmReceiver = new PeriodicPollAlarmReceiver(this);
		controller = Controller.getController(context);
		loudnessSensor = LoudnessSensor.getInstance(context);
		autoCalibratedNoiseSensor = AutoCalibratedNoiseSensor.getInstance(context);
	}

	@Override
	public void doSample() {
		Log.v(TAG, "Do sample");

		// clear old sample jobs
		if (noiseSampleJob != null) {
			noiseSampleJob.stopRecording();
			noiseSampleHandler.removeCallbacks(noiseSampleJob);
		}

		// start sample job
		if (active /* && listenInterval != -1 */) {
			noiseSampleJob = new NoiseSampleJob();
			// noiseSampleHandler = new Handler();
			noiseSampleHandler.post(noiseSampleJob);
		}
	}

	public DataProducer getNoiseBurstSensor()
	{
		return noiseBurstSensor;
	}

	public DataProducer getLoudnessSensor()
	{
		return loudnessSensor;
	}
	public DataProducer getAutoCalibratedNoiseSensor() {
		return autoCalibratedNoiseSensor;
	}

	@Override
	public boolean isActive() {
		return active;
	}

	@Override
	public long getSampleRate() {
		SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
		if (mainPrefs.getBoolean(Ambience.RECORD_AUDIO, false))		
			return -1;
	
		return super.getSampleRate();
	}
	
	/**
	 * Starts the sound sensing jobs.
	 */
	private void startSampling() {
		Log.v(TAG, "Start sound sensor sampling");

		try {

			// different job if the listen interval is "real-time"
			if (getSampleRate() == -1) {
				// start recording
				if (soundStreamJob != null) {
					soundStreamHandler.removeCallbacks(soundStreamJob);
				}
				soundStreamJob = new SoundStreamJob();
				soundStreamHandler.post(soundStreamJob);

			} else {

				// register for periodic polls at the scheduler
				pollAlarmReceiver.start(context);

				// do first sample immediately
				doSample();
			}

		} catch (Exception e) {
			Log.e(TAG, "Exception in startSensing:" + e.getMessage());
		}
	}

	/**
	 * Enables the noise sensor, starting the sound recording and registering it as phone state
	 * listener.
	 */
	@Override
	public void startSensing(long sampleDelay) {
		Log.v(TAG, "Start sensing");
		stopSensing();
		setSampleRate(sampleDelay);
		active = true;

		// registering the phone state listener will trigger a call to startListening()
		TelephonyManager telMgr = (TelephonyManager) context
				.getSystemService(Context.TELEPHONY_SERVICE);
		telMgr.listen(phoneStateListener, PhoneStateListener.LISTEN_CALL_STATE);
	}

	/**
	 * Stops any active sensing jobs, and stops and cleans up the AudioRecord.
	 */
	private void stopSampling() {
		Log.v(TAG, "Stop sound sensor sample");

		try {

			// stop the sound recordings/*
			if (soundStreamJob != null) {
				soundStreamJob.stopRecording();
				soundStreamHandler.removeCallbacks(soundStreamJob);
				soundStreamJob = null;
			}
			if (noiseSampleJob != null) {
				noiseSampleJob.stopRecording();
				noiseSampleHandler.removeCallbacks(noiseSampleJob);
				noiseSampleJob = null;
			}

		} catch (Exception e) {
			Log.e(TAG, "Exception in pauseListening!", e);
		}
	}

	/**
	 * Disables the noise sensor, stopping the sound recording and unregistering it as phone state
	 * listener.
	 */
	@Override
	public void stopSensing() {
		Log.v(TAG, "Stop sensing");

		active = false;
		stopSampling();
		pollAlarmReceiver.stop(context);
		TelephonyManager telMgr = (TelephonyManager) context
				.getSystemService(Context.TELEPHONY_SERVICE);
		telMgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);
	}
} public class PressureSensor extends BaseSensor implements SensorEventListener,
        PeriodicPollingSensor {

    private static PressureSensor sInstance = null;
    private static final String TAG = "PressureSensor";

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static PressureSensor getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new PressureSensor(context);
        }
        return sInstance;
    }

    private boolean mActive = false;
    private PeriodicPollAlarmReceiver mAlarmReceiver;
    private Context mContext;
    private long[] mLastSampleTimes = new long[50];
    private SensorManager mSensorMgr;
    private List<Sensor> mSensors;
    private WakeLock mWakeLock;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected PressureSensor(Context context) {
        mContext = context;
        mSensorMgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        mSensors = new ArrayList<Sensor>();
        if (null != mSensorMgr.getDefaultSensor(Sensor.TYPE_PRESSURE)) {
            mSensors.add(mSensorMgr.getDefaultSensor(Sensor.TYPE_PRESSURE));
        }

        mAlarmReceiver = new PeriodicPollAlarmReceiver(this);
    }

    @Override
    public void doSample() {
       //Log.v(TAG, "Do sample");

        // acquire wake lock
        if (null == mWakeLock) {
            PowerManager powerMgr = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            mWakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        }
        if (!mWakeLock.isHeld()) {
            mWakeLock.acquire();
        } else {
            // Log.v(TAG, "Wake lock already held");
        }

        // register as sensor listener
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_PRESSURE) {
                // Log.d(TAG, "registering for sensor " + sensor.getName());
                mSensorMgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    }

    /**
     * @return The delay between samples in milliseconds
     */
    @Override
    public boolean isActive() {
        return mActive;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // do nothing
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;
        if (SNTP.getInstance().getTime() > mLastSampleTimes[sensor.getType()] + getSampleRate()) {
            mLastSampleTimes[sensor.getType()] = SNTP.getInstance().getTime();

            String sensorName = SensorNames.PRESSURE;

            // value is millibar, convert to Pascal
            float millibar = event.values[0];
            float pascal = millibar * 100;
            float value = BigDecimal.valueOf(pascal).setScale(3, 0).floatValue();

            this.notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(value);
            dataPoint.sensorName = sensorName;
            dataPoint.sensorDescription = sensor.getName();
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            this.sendToSubscribers(dataPoint);

            // send msg to MsgHandler
            Intent i = new Intent(mContext.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
            i.putExtra(DataPoint.VALUE, value);
            i.putExtra(DataPoint.SENSOR_NAME, sensorName);
            i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(mContext.getPackageName());
            mContext.startService(i);

            // sample is successful: unregister the listener
            stopSample();
        }
    }

    /**
     * Sets the delay between samples. The sensor registers itself for periodic sampling bursts, and
     * unregisters after it received a sample.
     * 
     * @param sampleDelay
     *            Sample delay in milliseconds
     */
    @Override
    public void setSampleRate(long sampleDelay) {
        super.setSampleRate(sampleDelay);
        stopPolling();
        startPolling();
    }

    private void startPolling() {
        Log.v(TAG, "Start polling");
        mAlarmReceiver.start(mContext);
    }

    /**
     * Starts sensing by registering for updates at the Android SensorManager. The sensor registers
     * for updates at the rate specified by the sampleDelay parameter.
     * 
     * @param sampleDelay
     *            Delay between samples in milliseconds
     */
    @Override
    public void startSensing(long sampleRate) {
        Log.v(TAG, "Start sensing");

        // check if the sensor is physically present on this phone
        boolean found = false;
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_PRESSURE) {
                found = true;
                break;
            }
        }
        if (!found) {
            Log.w(TAG, "No pressure sensor!");
            return;
        }

        mActive = true;
        setSampleRate(sampleRate);

        // do the first sample immediately
        doSample();
    }

    private void stopPolling() {
        Log.v(TAG, "Stop polling");
        mAlarmReceiver.stop(mContext);
        stopSample();
    }

    private void stopSample() {
        Log.v(TAG, "Stop sample");

        // release wake lock
        if (null != mWakeLock && mWakeLock.isHeld()) {
            mWakeLock.release();
        }

        // unregister sensor listener
        try {
            mSensorMgr.unregisterListener(this);
        } catch (Exception e) {
            // ignore
        }
    }

    /**
     * Stops the periodic sampling.
     */
    @Override
    public void stopSensing() {
        Log.v(TAG, "Stop sensing");
        stopPolling();
        mActive = false;
    }
} public class TemperatureSensor extends BaseSensor implements SensorEventListener,
        PeriodicPollingSensor {

    private static TemperatureSensor sInstance = null;
    private static final String TAG = "TemperatureSensor";

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static TemperatureSensor getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new TemperatureSensor(context);
        }
        return sInstance;
    }

    private boolean mActive;
    private Context mContext;
    private PeriodicPollAlarmReceiver mSampleAlarmReceiver;
    private SensorManager mSensorMgr;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected TemperatureSensor(Context context) {
        mContext = context;
        mSampleAlarmReceiver = new PeriodicPollAlarmReceiver(this);
        mSensorMgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void doSample() {
       //Log.v(TAG, "Do sample");
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            Sensor sensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
            if (null != sensor) {
                mSensorMgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }
    }

    @Override
    public boolean isActive() {
        return mActive;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // nothing to do
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;

        String sensorName = SensorNames.AMBIENT_TEMPERATURE;

        // temperature in degrees Celsius
        float value = BigDecimal.valueOf(event.values[0]).setScale(2, 0).floatValue();

        this.notifySubscribers();
        SensorDataPoint dataPoint = new SensorDataPoint(value);
        dataPoint.sensorName = sensorName;
        dataPoint.sensorDescription = sensor.getName();
        dataPoint.timeStamp = SNTP.getInstance().getTime();
        this.sendToSubscribers(dataPoint);

        // send msg to MsgHandler
        Intent i = new Intent(mContext.getString(R.string.action_sense_new_data));
        i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
        i.putExtra(DataPoint.VALUE, value);
        i.putExtra(DataPoint.SENSOR_NAME, sensorName);
        i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
        i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
        i.setPackage(mContext.getPackageName());
        mContext.startService(i);

        // done with sample
        stopSample();
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void startSensing(long sampleDelay) {
        Log.v(TAG, "Start sensing");

        boolean found = false;
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
            Sensor sensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_AMBIENT_TEMPERATURE);
            if (null != sensor) {
                found = true;
            }
        }
        if (!found) {
            Log.w(TAG, "No temperature sensor available!");
            return;
        }

        setSampleRate(sampleDelay);

        // start polling
        mActive = true;
        mSampleAlarmReceiver.start(mContext);

        // do the first sample immediately
        doSample();
    }

    private void stopSample() {
        Log.v(TAG, "Stop sample");
        try {
            mSensorMgr.unregisterListener(this);
        } catch (Exception e) {
            Log.e(TAG, "Error stopping temperature sensor: " + e);
        }
    }

    @Override
    public void stopSensing() {
        Log.v(TAG, "Stop sensing");
        // stop polling
        mSampleAlarmReceiver.stop(mContext);
        mActive = false;

        // stop sample
        stopSample();
    }
} public class DefaultSensorRegistrationService extends IntentService {

	private static final String TAG = "DefaultSensorRegistrationService";
    private static final boolean USE_GCM = false;
	private SensorRegistrator verifier = new DefaultSensorRegistrator(this);

	public DefaultSensorRegistrationService() {
		super("DefaultSensorRegistrationService");
	}

	@Override
	protected void onHandleIntent(Intent intent) {

		SharedPreferences mainPrefs = getSharedPreferences(SensePrefs.MAIN_PREFS, MODE_PRIVATE);
		long lastVerified = mainPrefs.getLong(SensePrefs.Main.LAST_VERIFIED_SENSORS, 0);
		if (SNTP.getInstance().getTime() - lastVerified < 1000l * 60) { // 1 minute
			// registered sensors were already recently checked
			Log.v(TAG, "Sensor IDs were just verified already");
			return;
		}

		String deviceType = SenseApi.getDefaultDeviceType(this);
		String deviceUuid = SenseApi.getDefaultDeviceUuid(this);

        if (USE_GCM) {
            registerGCM();
        } else {
            // TODO: we have disabled the GCM: it conflicts with other GCM registrations in the app
            Log.w(TAG, "CommonSense GCM registration is disabled!");
        }

		if (verifier.verifySensorIds(deviceType, deviceUuid)) {
			Log.v(TAG, "Sensor IDs verified");
			mainPrefs.edit()
					.putLong(SensePrefs.Main.LAST_VERIFIED_SENSORS, SNTP.getInstance().getTime())
					.commit();
		} else {
			// hopefully the IDs will be checked again
		}
	}

	@Override
	public void onDestroy() {
        if (USE_GCM) {
            GCMRegistrar.onDestroy(this); // so that it will not leak
        } else {
            // do not unregister is it was not registered
        }
		super.onDestroy();
	}

    /**
     * Register the device to use Google GCM
     */
    private void registerGCM() {

        // get the registration ID
        String registrationId = null;
        try {
            GCMRegistrar.checkDevice(this);
            GCMRegistrar.checkManifest(this);
            registrationId = GCMRegistrar.getRegistrationId(this);
        } catch (IllegalStateException e) {
            Log.w(TAG, "This application does not have the GCM permission");
            return;
        } catch (UnsupportedOperationException e) {
            Log.w(TAG, "This device does not have GCM support");
            return;
        }

        if (registrationId.equals("")) {
            Log.v(TAG, "Device is not registered to Google Cloud Messaging, registering");
            GCMRegistrar.register(this, SENDER_ID);

        } else {
            try {
                SenseApi.registerGCMId(this, registrationId);
            } catch (IOException e) {
                Log.w(TAG, "Failed to register Google Cloud Messaging! " + e);
            } catch (JSONException e) {
                Log.w(TAG, "Failed to register Google Cloud Messaging! " + e);
            } catch (Exception e) {
                Log.w(TAG, "Failed to register Google Cloud Messaging! " + e);
            }
        }
    }
} public class SenseApi {

    private static final long CACHE_REFRESH = 1000l * 60 * 60; // 1 hour
    private static final String TAG = "SenseApi";
    /** Device UUID for sensors that are not physical sensors, i.e. not connected to any device */
    public static final String NO_DEVICE_UUID = "no_device_uuid";
    /**
     * Key for getting the http response code from the Map object that is returned by
     * {@link SenseApi#request(Context, String, JSONObject, String)}
     */
    public static final String RESPONSE_CODE = "http response code";
    /**
     * Key for getting the response content from the Map object that is returned by
     * {@link SenseApi#request(Context, String, JSONObject, String)}
     */
    public static final String RESPONSE_CONTENT = "content";

    private static SharedPreferences sAuthPrefs;
    private static SharedPreferences sMainPrefs;
    private static TelephonyManager sTelManager;
    private static String APPLICATION_KEY;

    /**
     * Gets a list of all registered sensors for a user at the CommonSense API. Uses caching for
     * increased performance.
     * 
     * @param context
     *            Application context, used for getting preferences.
     * @return The list of sensors
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     */
    public static JSONArray getAllSensors(Context context) throws IOException, JSONException {

        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }

        // try to get list of sensors from the cache
        try {
            String cachedSensors = sAuthPrefs.getString(Auth.SENSOR_LIST_COMPLETE, null);
            long cacheTime = sAuthPrefs.getLong(Auth.SENSOR_LIST_COMPLETE_TIME, 0);
            boolean isOutdated = SNTP.getInstance().getTime() - cacheTime > CACHE_REFRESH;

            // return cached list of it is still valid
            if (false == isOutdated && null != cachedSensors) {
                return new JSONArray(cachedSensors);
            }

        } catch (Exception e) {
            // unlikely to ever happen. Just get the list from CommonSense instead
            Log.w(TAG, "Failed to get list of sensors from cache!", e);
        }

        // if we make it here, the list was not in the cache
        Log.v(TAG, "List of sensor IDs is missing or outdated, refreshing...");

        boolean done = false;
        JSONArray result = new JSONArray();
        int page = 0;
        while (!done) {
            // request fresh list of sensors for this device from CommonSense
            String cookie;
            try {
                cookie = getCookie(context);
            } catch (IllegalAccessException e) {
                cookie = null;
            }
            boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
            if (devMode) {
                Log.v(TAG, "Using development server to get registered sensors");
            }
            String url = devMode ? SenseUrls.ALL_SENSORS_DEV : SenseUrls.ALL_SENSORS;
            url += "&page=" + page;
            Map<String, String> response = SenseApi.request(context, url, null, cookie);

            String responseCode = response.get(RESPONSE_CODE);
            if (!"200".equals(responseCode)) {
                Log.w(TAG, "Failed to get list of sensors! Response code: " + responseCode);
                throw new IOException("Incorrect response from CommonSense: " + responseCode);
            }

            // parse response and store the list
            JSONObject content = new JSONObject(response.get(RESPONSE_CONTENT));
            JSONArray sensorList = content.getJSONArray("sensors");

            // put the sensor list in the result array
            for (int i = 0; i < sensorList.length(); i++) {
                result.put(sensorList.getJSONObject(i));
            }

            if (sensorList.length() < SenseUrls.PAGE_SIZE) {
                // all sensors received
                done = true;
            } else {
                // get the next page
                page++;
            }
        }

        // store the new sensor list
        Editor authEditor = sAuthPrefs.edit();
        authEditor.putString(Auth.SENSOR_LIST_COMPLETE, result.toString());
        authEditor.putLong(Auth.SENSOR_LIST_COMPLETE_TIME, SNTP.getInstance().getTime());
        authEditor.commit();

        return result;
    }

    /**
     * Gets the sensors that are connected to another sensor. Typically used
     * 
     * @param context
     * @param sensorId
     * @return List of IDs for connected sensors
     * @throws IOException
     * @throws JSONException
     */
    public static List<String> getConnectedSensors(Context context, String sensorId)
            throws IOException, JSONException {

        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        // request fresh list of sensors for this device from CommonSense
        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        if (devMode) {
            Log.v(TAG, "Using development server to get connected sensors");
        }
        String url = devMode ? SenseUrls.CONNECTED_SENSORS_DEV : SenseUrls.CONNECTED_SENSORS;
        url = url.replace("%1", sensorId);
        Map<String, String> response = SenseApi.request(context, url, null, cookie);

        String responseCode = response.get(RESPONSE_CODE);
        if (!"200".equals(responseCode)) {
            Log.w(TAG, "Failed to get list of connected sensors! Response code: " + responseCode);
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }

        // parse response and store the list
        JSONObject content = new JSONObject(response.get(RESPONSE_CONTENT));
        JSONArray sensorList = content.getJSONArray("sensors");

        List<String> result = new ArrayList<String>();
        for (int i = 0; i < sensorList.length(); i++) {
            JSONObject sensor = sensorList.getJSONObject(i);
            result.add(sensor.getString("id"));
        }
        return result;
    }

    /**
     * @param context
     *            Context for accessing phone details
     * @return The default device type, i.e. the phone's model String
     */
    public static String getDefaultDeviceType(Context context) {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        return sAuthPrefs.getString(Auth.DEVICE_TYPE, Build.MODEL);
    }

    /**
     * @param context
     *            Context for accessing phone details
     * @return The default device UUID, e.g. the phone's IMEI String
     */
    @TargetApi(9)
    public static String getDefaultDeviceUuid(Context context) {
        if (null == sTelManager) {
            sTelManager = ((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
        }
        String uuid = sTelManager.getDeviceId();
        if (null == uuid) {
            // device has no IMEI, try using the Android serial code
            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.GINGERBREAD) {
                uuid = Build.SERIAL;
            } else {
                Log.w(TAG, "Cannot get reliable device UUID!");
            }
        }
        return uuid;
    }

    /**
     * Get device configuration from commonSense
     * 
     * @throws JSONException
     * @throws IOException
     */
    public static String getDeviceConfiguration(Context context) throws IOException, JSONException {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        String url = devMode ? SenseUrls.DEVICE_CONFIGURATION_DEV : SenseUrls.DEVICE_CONFIGURATION;

        // Get the device ID
        String device_id = getDeviceId(context);

        url = url.replaceFirst("%1", device_id);

        Map<String, String> response = SenseApi.request(context, url, null, cookie);
        String responseCode = response.get(RESPONSE_CODE);
        if (!"200".equals(responseCode)) {
            Log.w(TAG, "Failed to get device configuration! Response code: " + responseCode);
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }

        return response.get(RESPONSE_CONTENT);
    }

    /**
     * Get specific configuration from commonSense
     * 
     * @throws JSONException
     * @throws IOException
     * 
     */
    public static String getDeviceConfiguration(Context context, String configuration_id)
            throws IOException, JSONException {
        final SharedPreferences prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = prefs.getBoolean(Advanced.DEV_MODE, false);
        String url = devMode ? SenseUrls.CONFIGURATION_DEV : SenseUrls.CONFIGURATION;

        url = url.replaceFirst("%1", configuration_id);

        Map<String, String> response = SenseApi.request(context, url, null, cookie);
        String responseCode = response.get(RESPONSE_CODE);
        if (!"200".equals(responseCode)) {
            Log.w(TAG, "Failed to get Requirement! Response code: " + responseCode);
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }

        return response.get(RESPONSE_CONTENT);
    }

    /**
     * Get this device_id registered in common sense
     * 
     * @param context
     * @throws IOException
     * @throws JSONException
     */
    public static String getDeviceId(Context context) throws IOException, JSONException {
        String device_id = null;

        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        String url = devMode ? SenseUrls.DEVICES_DEV : SenseUrls.DEVICES;

        Map<String, String> response = SenseApi.request(context, url, null, cookie);
        String responseCode = response.get(RESPONSE_CODE);
        if (!"200".equals(responseCode)) {
            Log.w(TAG, "Failed to get devices data: " + responseCode);
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }

        // check registration result
        JSONObject res = new JSONObject(response.get(RESPONSE_CONTENT));
        JSONArray arr = res.getJSONArray("devices");

        String deviceType = getDefaultDeviceType(context);
        String deviceUUID = getDefaultDeviceUuid(context);

        for (int i = 0; i < arr.length(); i++) {
            JSONObject row = arr.getJSONObject(i);
            String cid = row.getString("id");
            String ctype = row.getString("type");
            String cuuid = row.getString("uuid");
            if (deviceType.equals(ctype) && deviceUUID.equals(cuuid)) {
                device_id = cid;
                break;
            }
        }

        return device_id;
    }

    private static List<JSONObject> getMatchingSensors(Context context, String name,
            String description, String dataType) throws IOException, JSONException {

        // get list of all registered sensors for this device
        JSONArray sensors = getAllSensors(context);

        // check sensors with similar names and descriptions in the list
        List<JSONObject> result = new ArrayList<JSONObject>();
        JSONObject sensor;
        for (int i = 0; i < sensors.length(); i++) {
            sensor = (JSONObject) sensors.get(i);

            if (sensor.getString("name").equalsIgnoreCase(name)
                    && ((null == description) || sensor.getString("device_type").equalsIgnoreCase(
                            description))
                    && ((null == dataType) || sensor.getString("data_type").equalsIgnoreCase(
                            dataType))) {
                result.add(sensor);

            } else if (name.equals(SensorNames.ACCELEROMETER) || name.equals(SensorNames.ORIENT)
                    || name.equals(SensorNames.GYRO) || name.equals(SensorNames.LIN_ACCELERATION)
                    || name.equals(SensorNames.MAGNETIC_FIELD)
                    || name.equals(SensorNames.ACCELEROMETER_EPI)
                    || name.equals(SensorNames.ACCELEROMETER_BURST)
                    || name.equals(SensorNames.GYRO_BURST) || name.equals(SensorNames.LINEAR_BURST)) {
                // special case to take care of changed motion sensor descriptions since Gingerbread
                if (name.equals(sensor.getString("name"))) {
                    // use inexact match
                    result.add(sensor);
                }
            }
        }

        return result;
    }

    /**
     * Gets the sensor ID at CommonSense , which can be used to modify the sensor information and
     * data.
     * 
     * @param context
     *            Context for getting preferences
     * @param name
     *            Sensor name, to match with registered sensors.
     * @param description
     *            Sensor description (previously 'device_type'), to match with registered sensors.
     * @param dataType
     *            Sensor data type, to match with registered sensors.
     * @param deviceUuid
     *            (Optional) UUID of the device that should hold the sensor.
     * @return String with the sensor's ID, or null if the sensor does not exist at CommonSense
     *         (yet).
     * @throws IOException
     *             If the request to CommonSense failed.
     * @throws JSONException
     *             If the response from CommonSense could not be parsed.
     */
    public static String getSensorId(Context context, String name, String description,
            String dataType, String deviceUuid) throws IOException, JSONException {

        // get list of sensors with matching description
        List<JSONObject> sensors = getMatchingSensors(context, name, description, dataType);

        // check the devices that the sensors are connected to
        String id = null;
        JSONObject device;
        for (JSONObject sensor : sensors) {
            if (null != deviceUuid && !NO_DEVICE_UUID.equals(deviceUuid)) {
                // check if the device UUID matches
                device = sensor.optJSONObject("device");
                if (null != device && deviceUuid.equals(device.optString("uuid"))) {
                    id = sensor.getString("id");
                    break;
                }
            } else {
                // we do not care about the device, just accept the first match we find
                id = sensor.getString("id");
                break;
            }
        }
        if (null == id) {
            Log.d(TAG, "missing id for " + name + " (" + description + ") @ " + deviceUuid);
        }
        return id;
    }

    /**
     * Gets the URL at CommonSense to which the data must be sent.
     * 
     * @param context
     *            Context for getting preferences
     * @param name
     *            Sensor name, to match with registered sensors.
     * @param description
     *            Sensor description (previously 'device_type'), to match with registered sensors.
     * @param dataType
     *            Sensor data type, to match with registered sensors.
     * @param deviceUuid
     *            (Optional) UUID of the device that holds the sensor. Set null to use the default
     *            device.
     * @return String with the sensor's URL, or null if sensor does not have an ID (yet)
     * @throws JSONException
     *             If there was unexpected response getting the sensor ID.
     * @throws IOException
     *             If there was a problem during communication with CommonSense.
     */
    public static String getSensorUrl(Context context, String name, String description,
            String dataType, String deviceUuid) throws IOException, JSONException {

        String id = getSensorId(context, name, description, dataType, deviceUuid);

        if (id == null) {
            Log.w(TAG, "Failed to get URL for sensor '" + name + "': sensor ID is not available");
            return null;
        }

        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // found the right sensor
        if (dataType.equals(SenseDataTypes.FILE)) {
            String url = devMode ? SenseUrls.SENSOR_FILE_DEV : SenseUrls.SENSOR_FILE;
            return url.replaceFirst("%1", id);
        } else {
            String url = devMode ? SenseUrls.SENSOR_DATA_DEV : SenseUrls.SENSOR_DATA;
            return url.replaceFirst("%1", id);
        }
    }
    
    /**
    * @param context
    *            Context for getting preferences
    * @return The current CommonSense session ID
    * @throws IllegalAccessException
    *             if the app ID is not valid
    */
    public static String getCookie(Context context) throws IllegalAccessException {
    	if (null == sAuthPrefs) {
    		sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
    	}
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie = sAuthPrefs.getString(Auth.LOGIN_COOKIE, null);

        boolean encrypt_credential = sMainPrefs.getBoolean(Advanced.ENCRYPT_CREDENTIAL, false);
        if (encrypt_credential) {
            EncryptionHelper decryptor = new EncryptionHelper(context);
            try {
                cookie = decryptor.decrypt(cookie);
            } catch (EncryptionHelper.EncryptionHelperException e) {
                Log.w(TAG, "Error decrypting cookie. Assume data is not encrypted");
            }
        }

    	return cookie;
    }
    
    /**
     * @param context
     *            Context for getting preferences
     * @return The current CommonSense session ID
     * @throws IllegalAccessException
     *             if the app ID is not valid
     */
    public static String getSessionId(Context context) throws IllegalAccessException {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String session_id = sAuthPrefs.getString(Auth.LOGIN_SESSION_ID, null);

        boolean encrypt_credential = sMainPrefs.getBoolean(Advanced.ENCRYPT_CREDENTIAL, false);
        if (encrypt_credential) {
            EncryptionHelper decryptor = new EncryptionHelper(context);
            try {
                session_id = decryptor.decrypt(session_id);
            } catch (EncryptionHelper.EncryptionHelperException e) {
                Log.w(TAG, "Error decrypting session id. Assume data is not encrypted");
            }
        }

        return session_id;
    }

    /**
     * Gets user details from CommonSense
     * 
     * @param context
     *            Context for getting preferences
     * @return JSONObject with user if successful, null otherwise
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     * @throws IOException
     *             In case of communication failure to CommonSense
     */
    public static JSONObject getUser(Context context) throws IOException, JSONException {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        String url = devMode ? SenseUrls.CURRENT_USER_DEV : SenseUrls.CURRENT_USER;

        // perform actual request
        Map<String, String> response = SenseApi.request(context, url, null, cookie);

        String responseCode = response.get(RESPONSE_CODE);
        JSONObject result = null;
        if ("200".equalsIgnoreCase(responseCode)) {
            result = new JSONObject(response.get(RESPONSE_CONTENT)).getJSONObject("user");
        } else {
            Log.w(TAG, "Failed to get user! Response code: " + responseCode);
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }
        return result;
    }

    /**
     * @param hashMe
     *            "clear" password String to be hashed before sending it to CommonSense
     * @return Hashed String
     */
    public static String hashPassword(String hashMe) {
        final byte[] unhashedBytes = hashMe.getBytes();
        try {
            final MessageDigest algorithm = MessageDigest.getInstance("MD5");
            algorithm.reset();
            algorithm.update(unhashedBytes);
            final byte[] hashedBytes = algorithm.digest();

            final StringBuffer hexString = new StringBuffer();
            for (final byte element : hashedBytes) {
                final String hex = Integer.toHexString(0xFF & element);
                if (hex.length() == 1) {
                    hexString.append(0);
                }
                hexString.append(hex);
            }
            return hexString.toString();
        } catch (final NoSuchAlgorithmException e) {
            e.printStackTrace();
            return null;
        }
    }

    /**
     * Joins a group
     * 
     * @param context
     *            Context for getting preferences
     * @param groupId
     *            Id of the group to join
     * @return true if joined successfully, false otherwise
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     * @throws IOException
     *             In case of communication failure to CommonSense
     */
    public static boolean joinGroup(Context context, String groupId) throws JSONException,
            IOException {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // get userId
        String userId = getUser(context).getString("id");

        String url = devMode ? SenseUrls.GROUP_USERS_DEV : SenseUrls.GROUP_USERS;
        url = url.replaceFirst("%1", groupId);

        // create JSON object to POST
        final JSONObject data = new JSONObject();
        final JSONArray users = new JSONArray();
        final JSONObject item = new JSONObject();
        final JSONObject user = new JSONObject();
        user.put("id", userId);
        item.put("user", user);
        users.put(item);
        data.put("users", users);

        // perform actual request
        Map<String, String> response = SenseApi.request(context, url, data, cookie);

        String responseCode = response.get(RESPONSE_CODE);
        boolean result = false;
        if ("201".equalsIgnoreCase(responseCode)) {
            result = true;
        } else {
            Log.w(TAG, "Failed to join group! Response code: " + responseCode + "Response: "
                    + response);
            result = false;
        }

        return result;
    }

    /**
     * Tries to log in at CommonSense using the supplied username and password. After login, the
     * cookie containing the session ID is stored in the preferences.
     * 
     * @param context
     *            Context for getting preferences
     * @param username
     *            Username for authentication
     * @param password
     *            Hashed password for authentication
     * @return 0 if login completed successfully, -2 if login was forbidden, and -1 for any other
     *         errors.
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @see SenseServiceStub#changeLogin(String, String, nl.sense_os.service.ISenseServiceCallback)
     */
    public static int login(Context context, String username, String password)
            throws JSONException, IOException {

        // preferences
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        if (devMode) {
            Log.v(TAG, "Using development server to log in");
        }
        final String url = devMode ? SenseUrls.LOGIN_DEV : SenseUrls.LOGIN;
        final JSONObject user = new JSONObject();
        user.put("username", username);
        user.put("password", password);

        // TODO disable compressed login
        Boolean useCompressed = sMainPrefs.getBoolean(SensePrefs.Main.Advanced.COMPRESS, false);
        sMainPrefs.edit().putBoolean(SensePrefs.Main.Advanced.COMPRESS, false).commit();
        // perform actual request
        // set the application id for the login call
        APPLICATION_KEY = sMainPrefs.getString(SensePrefs.Main.APPLICATION_KEY, null);
        Map<String, String> response = request(context, url, user, null);
        APPLICATION_KEY = null;
        // set previous value
        sMainPrefs.edit().putBoolean(SensePrefs.Main.Advanced.COMPRESS, useCompressed).commit();

        // if response code is not 200 (OK), the login was incorrect
        String responseCode = response.get(RESPONSE_CODE);
        int result = -1;
        if ("403".equalsIgnoreCase(responseCode)) {
            Log.w(TAG, "CommonSense login refused! Response: forbidden!");
            result = -2;
        } else if (!"200".equalsIgnoreCase(responseCode)) {
            Log.w(TAG, "CommonSense login failed! Response: " + responseCode);
            result = -1;
        } else {
            // received 200 response
            result = 0;
        }

        // create a cookie from the session_id
        String session_id = response.get("session-id");
        String cookie = "";
        if (result == 0 && session_id == null) {
            // something went horribly wrong
            Log.w(TAG, "CommonSense login failed: no cookie received?!");
            result = -1;
        }
        else
            cookie = "session_id="+session_id+"; domain=.sense-os.nl";

        // handle result
        Editor authEditor = sAuthPrefs.edit();
        switch (result) {
        case 0: // logged in
            boolean encrypt_credential = sMainPrefs.getBoolean(Advanced.ENCRYPT_CREDENTIAL, false);
            if (encrypt_credential) {
                EncryptionHelper encryptor = new EncryptionHelper(context);
                cookie = encryptor.encrypt(cookie);
                session_id = encryptor.encrypt(session_id);
            }
            authEditor.putString(Auth.LOGIN_COOKIE, cookie);
            authEditor.putString(Auth.LOGIN_SESSION_ID, session_id);
            authEditor.commit();
            break;
        case -1: // error
            break;
        case -2: // unauthorized
            authEditor.remove(Auth.LOGIN_COOKIE);
            authEditor.remove(Auth.LOGIN_SESSION_ID);
            authEditor.commit();
            break;
        default:
            Log.e(TAG, "Unexpected login result: " + result);
        }

        return result;
    }

    /**
     * Push GCM Registration ID for current device
     * 
     * @param context
     *            Application context, used to read preferences.
     * @param registrationId
     *            Registration ID given by google
     * @throws IOException
     * @throws JSONException
     * @throws IllegalStateException
     */
    public static void registerGCMId(Context context, String registrationId) throws IOException,
            JSONException {

        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        // Check if already synced with common sense
        String pref_registration_id = sAuthPrefs.getString(Auth.GCM_REGISTRATION_ID, "");

        if (registrationId.equals(pref_registration_id)) {
            // GCM registration id is already sync with commonSense
            return;
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        String url = devMode ? SenseUrls.REGISTER_GCM_ID_DEV : SenseUrls.REGISTER_GCM_ID;

        // Get the device ID
        String device_id = getDeviceId(context);

        if (device_id == null) {
            // register GCM ID failed, can not get the device ID for this device
            return;
        }

        url = url.replaceFirst("%1", device_id);

        final JSONObject data = new JSONObject();
        data.put("registration_id", registrationId);

        Map<String, String> response = SenseApi.request(context, url, data, cookie);
        String responseCode = response.get(RESPONSE_CODE);
        if (!"200".equals(responseCode)) {
            throw new IOException("Incorrect response from CommonSense: " + responseCode);
        }

        // check registration result
        JSONObject res = new JSONObject(response.get(RESPONSE_CONTENT));
        if (!registrationId.equals(res.getJSONObject("device").getString(GCMReceiver.KEY_GCM_ID))) {
            throw new IllegalStateException("GCM registration_id not match with response");
        }

        Editor authEditor = sAuthPrefs.edit();
        authEditor.putString(Auth.GCM_REGISTRATION_ID, registrationId);
        authEditor.commit();
    }

    /**
     * Registers a new sensor for this device at CommonSense. Also connects the sensor to this
     * device.
     * 
     * @param context
     *            The application context, used to retrieve preferences.
     * @param name
     *            The name of the sensor.
     * @param displayName
     *            The sensor's pretty display name.
     * @param description
     *            The sensor description (previously "device_type").
     * @param dataType
     *            The sensor data type.
     * @param value
     *            An example sensor value, used to determine the data structure for JSON type
     *            sensors.
     * @param deviceType
     *            (Optional) Type of the device that holds the sensor. Set null to use the default
     *            device.
     * @param deviceUuid
     *            (Optional) UUID of the device that holds the sensor. Set null to use the default
     *            device.
     * @return The new sensor ID at CommonSense, or <code>null</code> if the registration failed.
     * @throws JSONException
     *             In case of invalid sensor details or if the request returned unparseable
     *             response.
     * @throws IOException
     *             In case of communication failure during creation of the sensor.
     */
    public static String registerSensor(Context context, String name, String displayName,
            String description, String dataType, String value, String deviceType, String deviceUuid)
            throws JSONException, IOException {

        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // prepare request to create new sensor
        String url = devMode ? SenseUrls.CREATE_SENSOR_DEV : SenseUrls.SENSORS;
        JSONObject postData = new JSONObject();
        JSONObject sensor = new JSONObject();
        sensor.put("name", name);
        sensor.put("device_type", description);
        sensor.put("display_name", displayName);
        sensor.put("pager_type", "");
        sensor.put("data_type", dataType);
        if (dataType.compareToIgnoreCase(SenseDataTypes.JSON) == 0
                || dataType.compareToIgnoreCase(SenseDataTypes.JSON_TIME_SERIES) == 0) {
            JSONObject dataStructJSon = null;
            try {
                dataStructJSon = new JSONObject(value);
                JSONArray fieldNames = dataStructJSon.names();
                for (int x = 0; x < fieldNames.length(); x++) {
                    String fieldName = fieldNames.getString(x);
                    int start = dataStructJSon.get(fieldName).getClass().getName().lastIndexOf(".");
                    dataStructJSon.put(fieldName, dataStructJSon.get(fieldName).getClass()
                            .getName().substring(start + 1));
                }
            } catch (JSONException e) {
                // apparently the data structure cannot be parsed from the value
                dataStructJSon = new JSONObject();
            }
            sensor.put("data_structure", dataStructJSon.toString().replaceAll("\"", "\\\""));
        }
        postData.put("sensor", sensor);

        // perform actual request
        Map<String, String> response = request(context, url, postData, cookie);

        // check response code
        String code = response.get(RESPONSE_CODE);
        if (!"201".equals(code)) {
            Log.w(TAG, "Failed to register sensor at CommonSense! Response code: " + code);
            throw new IOException("Incorrect response from CommonSense: " + code);
        }

        // retrieve the newly created sensor ID
        String locationHeader = response.get("location");
        String[] split = locationHeader.split("/");
        String id = split[split.length - 1];

        // see if sensor should also be connected to a device at CommonSense
        if (NO_DEVICE_UUID.equals(deviceUuid)) {
            JSONObject device = new JSONObject();
            device.put("type", deviceType);
            device.put("uuid", deviceUuid);
            postData.put("device", device);
            // store the new sensor in the preferences
            sensor.put("id", id);
            sensor.put("device", device);
            JSONArray sensors = getAllSensors(context);
            sensors.put(sensor);
            Editor authEditor = sAuthPrefs.edit();
            authEditor.putString(Auth.SENSOR_LIST_COMPLETE, sensors.toString());
            authEditor.commit();
            return id;
        }

        // get device properties from preferences, so it matches the properties in CommonSense
        if (null == deviceUuid) {
            deviceUuid = getDefaultDeviceUuid(context);
            deviceType = getDefaultDeviceType(context);
        }

        // add sensor to this device at CommonSense
        url = devMode ? SenseUrls.SENSOR_DEVICE_DEV : SenseUrls.SENSOR_DEVICE;
        url = url.replaceFirst("%1", id);
        postData = new JSONObject();
        JSONObject device = new JSONObject();
        device.put("type", deviceType);
        device.put("uuid", deviceUuid);
        postData.put("device", device);

        response = request(context, url, postData, cookie);

        // check response code
        code = response.get(RESPONSE_CODE);
        if (!"201".equals(code)) {
            Log.w(TAG, "Failed to add sensor to device at CommonSense! Response code: " + code);
            throw new IOException("Incorrect response from CommonSense: " + code);
        }

        // store the new sensor in the preferences
        sensor.put("id", id);
        sensor.put("device", device);
        JSONArray sensors = getAllSensors(context);
        sensors.put(sensor);
        Editor authEditor = sAuthPrefs.edit();
        authEditor.putString(Auth.SENSOR_LIST_COMPLETE, sensors.toString());
        authEditor.commit();

        Log.v(TAG, "Created sensor: '" + name + "' for device: '" + deviceType + "'");

        // return the new sensor ID
        return id;
    }

    /**
     * Tries to register a new user at CommonSense.
     * 
     * @param context
     *            Context for getting preferences
     * @param username
     *            Username to register
     * @param password
     *            Hashed password for the new user
     * @return 0 if registration completed successfully, -2 if the user already exists, and -1 for
     *         any other unexpected responses.
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @see SenseServiceStub#register(String, String, String, String, String, String, String,
     *      String, String, nl.sense_os.service.ISenseServiceCallback)
     */
    public static int registerUser(Context context, String username, String password, String name,
            String surname, String email, String mobile) throws JSONException, IOException {

        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        final String url = devMode ? SenseUrls.REGISTER_DEV : SenseUrls.REGISTER;

        // create JSON object to POST
        final JSONObject data = new JSONObject();
        final JSONObject user = new JSONObject();
        user.put("username", username);
        user.put("password", password);
        if (null != name) {
            user.put("name", name);
        }
        if (null != surname) {
            user.put("surname", surname);
        }
        if (null != email) {
            user.put("email", email);
        }
        if (null != mobile) {
            user.put("mobile", mobile);
        }
        data.put("user", user);

        // perform actual request
        Map<String, String> response = SenseApi.request(context, url, data, null);

        String responseCode = response.get(RESPONSE_CODE);
        int result = -1;
        if ("201".equalsIgnoreCase(responseCode)) {
            result = 0;
        } else if ("409".equalsIgnoreCase(responseCode)) {
            Log.w(TAG, "Failed to register new user! User already exists");
            result = -2;
        } else {
            Log.w(TAG, "Failed to register new user! Response code: " + responseCode);
            result = -1;
        }

        return result;
    }

    /**
     * Performs request at CommonSense API. Returns the response code, content, and headers.
     * 
     * @param context
     *            Application context, used to read preferences.
     * @param urlString
     *            Complete URL to perform request to.
     * @param content
     *            (Optional) Content for the request. If the content is not null, the request method
     *            is automatically POST. The default method is GET.
     * @param cookie
     *            (Optional) Cookie header for the request.
     * @return Map with SenseApi.KEY_CONTENT and SenseApi.KEY_RESPONSE_CODE fields, plus fields for
     *         all response headers.
     * @throws IOException
     */
    public static Map<String, String> request(Context context, String urlString,
            JSONObject content, String cookie) throws IOException {

        HttpURLConnection urlConnection = null;
        HashMap<String, String> result = new HashMap<String, String>();
        try {

            // get compression preference
            if (null == sMainPrefs) {
                sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                        Context.MODE_PRIVATE);
            }
            final boolean compress = sMainPrefs.getBoolean(Advanced.COMPRESS, true);

            // open new URL connection channel.
            URL url = new URL(urlString);
            if ("https".equals(url.getProtocol().toLowerCase(Locale.ENGLISH))) {
                HttpsURLConnection https = (HttpsURLConnection) url.openConnection();
                https.setHostnameVerifier(SSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER);
                urlConnection = https;
            } else {
                urlConnection = (HttpURLConnection) url.openConnection();
            }

            // some parameters
            urlConnection.setUseCaches(false);
            urlConnection.setInstanceFollowRedirects(false);
            urlConnection.setRequestProperty("Accept", "application/json");

            // set cookie (if available)
            if (null != cookie) {
                urlConnection.setRequestProperty("Cookie", cookie);
            }

            // set the application id
            if(null != APPLICATION_KEY)
                urlConnection.setRequestProperty("APPLICATION-KEY", APPLICATION_KEY);

            // send content (if available)
            if (null != content) {
                urlConnection.setDoOutput(true);
                // When no charset is given in the Content-Type header "ISO-8859-1" should be
                // assumed (see http://www.w3.org/Protocols/rfc2616/rfc2616-sec3.html#sec3.7.1).
                // Because we're uploading UTF-8 the charset should be set to UTF-8.
                urlConnection.setRequestProperty("Content-Type", "application/json; charset=utf-8");

                // send content
                DataOutputStream printout;
                if (compress) {
                    // do not set content size
                    // use chunked transfer mode instead
                    urlConnection.setChunkedStreamingMode(0);
                    urlConnection.setRequestProperty("Content-Encoding", "gzip");
                    GZIPOutputStream zipStream = new GZIPOutputStream(
                            urlConnection.getOutputStream());
                    printout = new DataOutputStream(zipStream);
                } else {
                    // set content size

                    // The content length should be in bytes. We cannot use string length here
                    // because that counts the number of chars while not accounting for multibyte
                    // chars
                    int contentLength = content.toString().getBytes("UTF-8").length;
                    urlConnection.setFixedLengthStreamingMode(contentLength);
                    urlConnection.setRequestProperty("Content-Length", "" + contentLength);
                    printout = new DataOutputStream(urlConnection.getOutputStream());
                }
                // Write the string in UTF-8 encoding
                printout.write(content.toString().getBytes("UTF-8"));
                printout.flush();
                printout.close();
            }

            // get response, or read error message
            InputStream inputStream;
            try {
                inputStream = urlConnection.getInputStream();
            } catch (IOException e) {
                inputStream = urlConnection.getErrorStream();
            }
            if (null == inputStream) {
                throw new IOException("could not get InputStream");
            }
            BufferedReader reader = new BufferedReader(new InputStreamReader(inputStream), 1024);
            String line;
            StringBuffer responseContent = new StringBuffer();
            while ((line = reader.readLine()) != null) {
                responseContent.append(line);
                responseContent.append('\r');
            }
            result.put(RESPONSE_CONTENT, responseContent.toString());
            result.put(RESPONSE_CODE, Integer.toString(urlConnection.getResponseCode()));

            // clean up
            reader.close();
            reader = null;
            inputStream.close();
            inputStream = null;

            // get headers
            Map<String, List<String>> headerFields = urlConnection.getHeaderFields();
            String key, valueString;
            List<String> value;
            for (Entry<String, List<String>> entry : headerFields.entrySet()) {
                key = entry.getKey();
                value = entry.getValue();
                if (null != key && null != value) {
                    key = key.toLowerCase(Locale.ENGLISH);
                    valueString = value.toString();
                    valueString = valueString.substring(1, valueString.length() - 1);
                    result.put(key, valueString);
                }
            }

            return result;

        } finally {
            if (urlConnection != null) {
                urlConnection.disconnect();
            }
        }
    }

    /**
     * Request a password reset for the given email address.
     * 
     * This function does not use the authentication API and is therefore deprecated
     * 
     * @param context
     *            Application context, used for getting preferences.
     * @param email
     *            Email address for the account that you want to regain access to.
     * @return <code>true</code> if the request wasw accepted
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     */
    @Deprecated
    public static boolean resetPassword(Context context, String email) throws IOException,
            JSONException {

        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // prepare request
        String url = devMode ? SenseUrls.FORGOT_PASSWORD_DEV : SenseUrls.FORGOT_PASSWORD;
        JSONObject content = new JSONObject();
        content.put("email", email);

        // perform request
        Map<String, String> result = request(context, url, content, null);

        // check response code
        String responseCode = result.get(RESPONSE_CODE);
        if ("200".equals(responseCode)) {
            return true;
        } else {
            Log.w(TAG, "Failed to request password reset! Response code " + responseCode);
            return false;
        }
    }

    /**
     * Request a password reset for the given username.
     * 
     * @param context
     *            Application context, used for getting preferences.
     * @param email
     *            Email address for the account that you want to regain access to.
     * @return <code>true</code> if the request wasw accepted
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     */
    public static boolean resetPasswordRequest(Context context, String username) throws IOException,
            JSONException {
        return resetPasswordRequest(context, username, Locale.US);
    }

    /**
     * Request a password reset for the given username.
     * 
     * @param context
     *            Application context, used for getting preferences.
     * @param email
     *            Email address for the account that you want to regain access to.
     * @param language
     *            Language the reset password email will be send as
     * @return <code>true</code> if the request wasw accepted
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     */
    public static boolean resetPasswordRequest(Context context, String username, Locale language) throws IOException,
            JSONException {

        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // prepare request
        String url = devMode ? SenseUrls.RESET_PASSWORD_REQUEST_DEV : SenseUrls.RESET_PASSWORD_REQUEST;
        JSONObject content = new JSONObject();
        content.put("username", username);
        content.put("language", language.toString());

        // TODO disable compressed 
        Boolean useCompressed = sMainPrefs.getBoolean(SensePrefs.Main.Advanced.COMPRESS, false);
        sMainPrefs.edit().putBoolean(SensePrefs.Main.Advanced.COMPRESS, false).commit();
        // perform actual request
        // set the application id for the login call
        APPLICATION_KEY = sMainPrefs.getString(SensePrefs.Main.APPLICATION_KEY, null);
        // perform request
        Map<String, String> result = request(context, url, content, null);
        APPLICATION_KEY = null;
        // set previous value
        sMainPrefs.edit().putBoolean(SensePrefs.Main.Advanced.COMPRESS, useCompressed).commit();

        // check response code
        String responseCode = result.get(RESPONSE_CODE);
        if ("202".equals(responseCode)) {
            return true;
        } else {
            Log.w(TAG, "Failed to request password reset! Response code " + responseCode);
            return false;
        }
    }


    /**
     * Change the password of the current user.
     *
     * @param context
     *            Application context, used for getting preferences.
     * @param current_password
     *            The current (hashed) password of the user
     * @param new_password
     *            The new (hashed) password of the user
     * @return <code>true</code> if the password was changed
     * @throws IOException
     *             In case of communication failure to CommonSense
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     */
    public static boolean changePassword(Context context, String current_password, String new_password) throws IOException,
    JSONException
    {
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);

        // prepare request
        String url = devMode ? SenseUrls.CHANGE_PASSWORD_DEV : SenseUrls.CHANGE_PASSWORD;
        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        JSONObject content = new JSONObject();
        content.put("current_password", current_password);
        content.put("new_password", new_password);

        // perform request
        Map<String, String> result = request(context, url, content, cookie);

        // check response code
        String responseCode = result.get(RESPONSE_CODE);
        if ("200".equals(responseCode)) {
            return true;
        } else {
            Log.w(TAG, "Failed to change the password! Response code " + responseCode);
            return false;
        }
    }

    /**
     * Shares a sensor with a user or group
     * 
     * @param context
     *            Context for getting preferences
     * @param sensorId
     *            Id of the sensor to share
     * @param userId
     *            Id of the user or group to share the sensor with
     * @return true if shared successfully, false otherwise
     * @throws JSONException
     *             In case of unparseable response from CommonSense
     * @throws IOException
     *             In case of communication failure to CommonSense
     */
    public static boolean shareSensor(Context context, String sensorId, String userId)
            throws JSONException, IOException {
        if (null == sAuthPrefs) {
            sAuthPrefs = context.getSharedPreferences(SensePrefs.AUTH_PREFS, Context.MODE_PRIVATE);
        }
        if (null == sMainPrefs) {
            sMainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        }

        String cookie;
        try {
            cookie = getCookie(context);
        } catch (IllegalAccessException e) {
            cookie = null;
        }
        boolean devMode = sMainPrefs.getBoolean(Advanced.DEV_MODE, false);
        String url = devMode ? SenseUrls.SENSOR_USERS_DEV : SenseUrls.SENSOR_USERS;
        url = url.replaceFirst("%1", sensorId);

        // create JSON object to POST
        final JSONObject data = new JSONObject();
        final JSONObject user = new JSONObject();
        user.put("id", userId);
        data.put("user", user);

        // perform actual request
        Map<String, String> response = SenseApi.request(context, url, data, cookie);

        String responseCode = response.get(RESPONSE_CODE);
        boolean result = false;
        if ("201".equalsIgnoreCase(responseCode)) {
            result = true;
        } else {
            Log.w(TAG, "Failed to share sensor! Response code: " + responseCode);
            result = false;
        }

        return result;
    }
} public class SensePrefs {
    /**
     * Keys for the authentication-related preferences of the Sense Platform
     */
    public static class Auth {
        /**
         * Key for login preference for session cookie.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String LOGIN_COOKIE = "login_cookie";
        /**
         *Key for login preference for session id.
         *  
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String LOGIN_SESSION_ID = "session_id";
        /**
         * Key for login preference for email address.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String LOGIN_USERNAME = "login_mail";
        /**
         * Key for login preference for hashed password.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String LOGIN_PASS = "login_pass";
        /**
         * Key for storing the online sensor list for this device (type of JSONArray).
         * 
         * @see #SENSOR_LIST_COMPLETE
         * @see SensePrefs#AUTH_PREFS
         * @deprecated
         */
        public static final String SENSOR_LIST = "sensor_list";
        /**
         * Key for storing the online sensor list for this user (type of JSONArray).
         * 
         * @see #SENSOR_LIST
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String SENSOR_LIST_COMPLETE = "sensor_list_complete";
        /**
         * Key for storing the retrieval time of device's online sensor list.
         * 
         * @see #SENSOR_LIST_COMPLETE_TIME
         * @see SensePrefs#AUTH_PREFS
         * @deprecated
         */
        public static final String SENSOR_LIST_TIME = "sensor_list_timestamp";
        /**
         * Key for storing the retrieval time of complete online sensor list.
         * 
         * @see #SENSOR_LIST_TIME
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String SENSOR_LIST_COMPLETE_TIME = "sensor_list_complete_timestamp";
        /**
         * Key for storing the online device id.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String DEVICE_ID = "device_id";
        /**
         * Key for storing the retrieval time of the online device id.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String DEVICE_ID_TIME = "device_id_timestamp";
        /**
         * Key for storing the online device type.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String DEVICE_TYPE = "device_type";
        /**
         * Key for storing the IMEI of the phone.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String PHONE_IMEI = "phone_imei";
        /**
         * Key for storing the type of the phone.
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String PHONE_TYPE = "phone_type";
        /**
         * Key for storing if gcm registration_id
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String GCM_REGISTRATION_ID = "gcm_registration_id";

    }

    /**
     * Keys for the main Sense Platform service preferences
     */
    public static class Main {

        public static class Advanced {
            /**
             * Key to use the development version of CommonSense.
             * 
             * @see SensePrefs#AUTH_PREFS
             */
            public static final String DEV_MODE = "devmode";
            /**
             * Key for preference that toggles use of compression for transmission. Default is true.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String COMPRESS = "compression";
            /**
             * Key for preference that enables local storage, making the sensor data available to
             * other apps through a ContentProvider. Default is true.
             * 
             * @see SensePrefs#MAIN_PREFS
             * @deprecated Local storage is always on.
             */
            public static final String LOCAL_STORAGE = "local_storage";
            /**
             * Key for preference that enables communication with CommonSense. Disable this to work
             * in local-only mode. Default is true.
             */
            public static final String USE_COMMONSENSE = "use_commonsense";
            /**
             * Key for preference that enables the location feedback sensor. Enable this to
             * participate in Pim's location feedback test. Default is false.
             */
            public static final String LOCATION_FEEDBACK = "location_feedback";
            /**
             * Key for preference that enables Agostino mode. Enable this to participate in
             * Agostino's saliency test. Default is false.
             */
            public static final String AGOSTINO = "agostino_mode";
            /**
             * Key for preference that enables energy saving mode when using mobile Internet. 
             * When energy saving mode is on data will be uploaded every half an hour.
             * Default is true.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  MOBILE_INTERNET_ENERGY_SAVING_MODE = "mobile_internet_energy_saving_mode";
            /**
             * Key for preference that enables data upload on wifi only.
             * Default is false.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  WIFI_UPLOAD_ONLY = "wifi_upload_only";
            /**
             * Key for value of cache duration for local data in hours.
             * Default is 24.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  RETENTION_HOURS = "retention_hours";
            /**
             * Key for preference that enable database encryption.
             * Default is false.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  ENCRYPT_DATABASE = "encrypt_database";
            /**
             * Key for value of salt for database encryption
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String  ENCRYPT_DATABASE_SALT = "encrypt_database_salt";
            /**
             * Key for preference that enables encryption for credential data.
             * Default is false.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  ENCRYPT_CREDENTIAL = "encrypt_credential";
            /**
             * Key for value of salt for credential encryption.
             * @see SensePrefs#MAIN_PREFS
             */            
            public static final String  ENCRYPT_CREDENTIAL_SALT = "encrypt_credential_salt";
        }
        

        public static class Ambience {
            /**
             * Key for preference that toggles use of light sensor in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String LIGHT = "ambience_light";
            /**
             * Key for preference that toggles use of camera light sensor in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String CAMERA_LIGHT = "ambience_camera_light";
            /**
             * Key for preference that toggles use of the microphone in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String MIC = "ambience_mic";
            /**
             * Key for preference that toggles audio recording ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String RECORD_AUDIO = "ambience_record_audio";
            /**
             * Key for preference that toggles use of the audio spectrum in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String AUDIO_SPECTRUM = "ambience_audio_spectrum";
            /**
             * Key for preference that toggles use of the pressure sensor in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String PRESSURE = "ambience_pressure";
            /**
             * Key for preference that toggles use of the temperature sensor in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String TEMPERATURE = "ambience_temperature";
            /**
             * Key for preference that toggles use of the magnetic field sensor in ambience sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String MAGNETIC_FIELD = "ambience_magnetic_field";
            /**
             * Key for preference that toggles use of the relative humidity sensor in ambience
             * sensing.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String HUMIDITY = "ambience_humidity";
            /**
             * Key for preference that toggles "burst-mode" for the noise sensor
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BURSTMODE = "ambience_burstmode";
            /**
             * Key for preference that toggles whether to upload and store burst samples.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String DONT_UPLOAD_BURSTS = "ambience_dont_upload_bursts";
            /**
             * Key for preference that toggles whether to record one sided or two sided voice calls 
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String RECORD_TWO_SIDED_VOICE_CALL = "record_two_sided_voice_call";
        }

        public static class DevProx {
            /**
             * Key for preference that toggles use of Bluetooth in the Device Proximity sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BLUETOOTH = "proximity_bt";
            /**
             * Key for preference that toggles use of Wi-Fi in the Device Proximity sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String WIFI = "proximity_wifi";
            /**
             * Key for preference that toggles use of NFC in the Device Proximity sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String NFC = "proximity_nfc";
        }

        public static class External {

            public static class MyGlucoHealth {
                /**
                 * Key for preference that toggles use of the MyGlucohealth sensor.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String MAIN = "myglucohealth";
            }

            public static class TanitaScale {
                /**
                 * Key for preference that toggles use of the Tanita scale sensor.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String MAIN = "tanita_scale";
            }

            public static class ZephyrBioHarness {

                /**
                 * Key for preference that toggles use of the Zephyr BioHarness.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String MAIN = "zephyrBioHarness";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness Accelerometer.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String ACC = "zephyrBioHarness_acc";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness Heart rate.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String HEART_RATE = "zephyrBioHarness_heartRate";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness Temperature.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String TEMP = "zephyrBioHarness_temp";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness Respiration rate.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String RESP = "zephyrBioHarness_resp";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness worn status.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String WORN_STATUS = "zephyrBioHarness_wornStatus";
                /**
                 * Key for preference that toggles use of the Zephyr BioHarness battery level.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String BATTERY = "zephyrBioHarness_battery";
            }

            public static class ZephyrHxM {
                /**
                 * Key for preference that toggles use of the Zephyr HxM.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String MAIN = "zephyrHxM";
                /**
                 * Key for preference that toggles use of the Zephyr HxM speed.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String SPEED = "zephyrHxM_speed";
                /**
                 * Key for preference that toggles use of the Zephyr HxM heart rate.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String HEART_RATE = "zephyrHxM_heartRate";
                /**
                 * Key for preference that toggles use of the Zephyr HxM battery.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String BATTERY = "zephyrHxM_battery";
                /**
                 * Key for preference that toggles use of the Zephyr HxM distance.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String DISTANCE = "zephyrHxM_distance";
                /**
                 * Key for preference that toggles use of the Zephyr HxM strides.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String STRIDES = "zephyrHxM_strides";
            }

            public static class OBD2Sensor {
                /**
                 * Key for preference that toggles use of the OBD-II sensor.
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String MAIN = "obd2sensor";
            }
        }

        public static class Location {
            /**
             * Key for preference that toggles use of GPS in location sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             * @deprecated Selecting the location provider manually will replaced by the fused location provider.
             */
            @Deprecated
            public static final String GPS = "location_gps";
            /**
             * Key for preference that toggles use of Network in location sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             * @deprecated Selecting the location provider manually will replaced by the fused location provider.
             */
            @Deprecated
            public static final String NETWORK = "location_network";
            /**
             * Key for preference that toggles use of sensor fusion to toggle the GPS usage.
             * 
             * @see SensePrefs#MAIN_PREFS
             * @deprecated Selecting the GPS as provider automatically will be replaced by the fused location provider.
             */
            @Deprecated
            public static final String AUTO_GPS = "automatic_gps";
            /**
             * Key for preference that toggles use of the time zone sensor
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String TIME_ZONE = "time_zone";
            /**
             * Key for preference that toggles the use of the fused location provider.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String FUSED_PROVIDER = "location_fused";
            /**
             * Key for preference that sets the priority for the fused location provider.
             * By default the BALANCED priority is used.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String FUSED_PROVIDER_PRIORITY = "location_fused_priority";

            public static class FusedProviderPriority
            {
                /**
                 * Key for preference that toggles the use of the fused location provider with a high power consumption and accuracy.
                 * Accuracy: 1m (most precise location possible)
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String ACCURATE = "location_fused_accurate";
                /**
                 * Key for preference that toggles the use of the fused location provider with a balance between power consumption and accuracy.
                 * Accuracy: 100 (city block)
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String BALANCED = "location_fused_balanced";
                /**
                 * Key for preference that toggles the use of the fused location provider with low power consumption and accuracy.
                 * Accuracy: 10km (city level)
                 * 
                 * @see SensePrefs#MAIN_PREFS
                 */
                public static final String LOW_POWER = "location_fused_low_power";
            }
        }

        public static class Motion {
            /**
             * Key for preference that toggles use of Bluetooth in the DeviceProximity sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String FALL_DETECT = "motion_fall_detector";
            /**
             * Key for preference that toggles use of Bluetooth in the DeviceProximity sensor.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String FALL_DETECT_DEMO = "motion_fall_detector_demo";
            /**
             * Key for preference that toggles "epi-mode", drastically changing motion sensing
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String EPIMODE = "epimode";
            /**
             * Key for preference that toggles "burst-mode", drastically changing motion sensing
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BURSTMODE = "burstmode";
            /**
             * Key for preference that determines whether to unregister the motion sensor between
             * samples. Nota bene: unregistering the sensor breaks the screen rotation on some
             * phones (e.g. Nexus S).
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            
            /**
             * Key for preference that toggles whether to upload and store burst samples.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String DONT_UPLOAD_BURSTS = "dont upload bursts";
            
            /**
             * Key for preference that determines the burst duration. Duration is in milliseconds.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BURST_DURATION = "burst_duration";
            
            public static final String UNREG = "motion_unregister";
            /**
             * Key for preference that toggles motion energy sensing, which measures average kinetic
             * energy over a sample period.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String MOTION_ENERGY = "motion_energy";
            /**
             * Key for preference that enables fix that re-registers the motion sensor when the
             * screen turns off.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SCREENOFF_FIX = "screenoff_fix";
            /**
             * Key for preference that toggles the use of the gyroscope
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String GYROSCOPE = "gyroscope";
            /**
             * Key for preference that toggles the use of the accelerometer
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String ACCELEROMETER = "accelerometer";
            /**
             * Key for preference that toggles the use of the orientation sensor
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String ORIENTATION = "orientation";
            /**
             * Key for preference that toggles the use of the linear acceleration sensor
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String LINEAR_ACCELERATION = "linear_acceleration";
        }

        public static class PhoneState {
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BATTERY = "phonestate_battery";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SCREEN_ACTIVITY = "phonestate_screen_activity";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String PROXIMITY = "phonestate_proximity";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String IP_ADDRESS = "phonestate_ip";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String DATA_CONNECTION = "phonestate_data_connection";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String UNREAD_MSG = "phonestate_unread_msg";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SERVICE_STATE = "phonestate_service_state";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SIGNAL_STRENGTH = "phonestate_signal_strength";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String CALL_STATE = "phonestate_call_state";
            /**
             * @see SensePrefs#MAIN_PREFS
             *              */
            public static final String FOREGROUND_APP = "foreground_app";
            /**
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String INSTALLED_APPS = "installed_apps";
            
            /**
             * @see SensePrefs#MAIN_PREFS
             */
			public static final String APP_INFO = "app_info";
        }

        public static class Quiz {
            /**
             * Key for preference that sets the interval between pop quizzes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String RATE = "popquiz_rate";
            /**
             * Key for preference that sets the silent mode for pop quizzes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SILENT_MODE = "popquiz_silent_mode";
            /**
             * Key for generic preference that starts an update of the quiz questions when clicked.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SYNC = "popquiz_sync";
            /**
             * Key for preference that holds the last update time of the quiz questions with
             * CommonSense.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String SYNC_TIME = "popquiz_sync_time";
        }

        public static class SampleRate {
            /**
             * Key for the preference that sets the sample interval to every 15 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String RARELY = "1";

            /**
             * Key for the preference that sets the sample interval to every 3 minutes.
             * For the Location sensors this is 5 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String BALANCED = "2";

            /**
             * Key for the preference that sets the sample interval to every minute.
             * For the Location sensors this is 5 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String NORMAL = "0";

            /**
             * Key for the preference that sets the sample interval to every 10 seconds.
             * For the Location sensors this is every 30 seconds.
             * For the External sensors this is every 5 seconds.
             * For the Motion sensors this is every 5 seconds.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String OFTEN = "-1";

            /**
             * Key for the preference that sets the sample interval to every second.
             * For the Ambience sensors this is as fast as possible.
             * For the Noise sensor this enabled the recording of audio files.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String REAL_TIME = "-2";
        }

        public static class SyncRate {
            /**
             * Key for the preference that enables data buffering and sets the upload interval to every 15 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String RARELY = "2";
            /**
             * Key for the preference that enables data buffering and sets the upload interval to every 30 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String ECO_MODE = "1";

            /**
             * Key for the preference that enables data buffering and sets the upload interval to every 5 minutes.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String NORMAL = "0";

            /**
             * Key for the preference that enables data buffering and sets the upload interval to every minute.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String OFTEN = "-1";

            /**
             * Key for the preference that disables data buffering and uploads every data point immediately.
             * 
             * @see SensePrefs#MAIN_PREFS
             */
            public static final String REAL_TIME = "-2";
        }

        /**
         * Key for preference that controls sample frequency of the sensors.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String SAMPLE_RATE = "commonsense_rate";
        /**
         * Key for preference that controls sync frequency with CommonSense.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String SYNC_RATE = "sync_rate";
        /**
         * Key for preference that saves the last running services.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String LAST_STATUS = "last_status";
        /**
         * Key for preference that stores a flag for first login.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String LAST_LOGGED_IN = "never_logged_in";
        /**
         * Key for preference that stores a timestamp for last time the sensors registration was
         * verified
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String LAST_VERIFIED_SENSORS = "verified_sensors";
        /**
         * Key for storing the application key
         * 
         * @see SensePrefs#AUTH_PREFS
         */
        public static final String APPLICATION_KEY = "application_key";
    }

    /**
     * Keys for the status preferences of the Sense Platform service
     */
    public static class Status {
        /**
         * Key for the main status of the sensors. Set to <code>false</code> to disable all the
         * sensing components.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String MAIN = "main service status";
        /**
         * Key for the status of the "ambience" sensors. Set to <code>true</code> to enable sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String AMBIENCE = "ambience component status";
        /**
         * Key for the status of the "device proximity" sensors. Set to <code>true</code> to enable
         * sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String DEV_PROX = "device proximity component status";
        /**
         * Key for the status of the external Bluetooth sensors. Set to <code>true</code> to enable
         * sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String EXTERNAL = "external services component status";
        /**
         * Key for the status of the location sensors. Set to <code>true</code> to enable sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String LOCATION = "location component status";
        /**
         * Key for the status of the motion sensors. Set to <code>true</code> to enable sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String MOTION = "motion component status";
        /**
         * Key for the status of the "phone state" sensors. Set to <code>true</code> to enable
         * sensing.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String PHONESTATE = "phone state component status";
        /**
         * Key for the status of the questionnaire. Set to <code>true</code> to enable it.
         * 
         * @see SensePrefs#STATUS_PREFS
         * @deprecated Sense does not support the questionnaire anymore
         */
        public static final String POPQUIZ = "pop quiz component status";
        /**
         * Key for preference to automatically start the Sense service on boot.
         * 
         * @see SensePrefs#STATUS_PREFS
         */
        public static final String AUTOSTART = "autostart";
        /**
        * Key for preference to pause sensing until the next charge.
        * 
        * @see SensePrefs#STATUS_PREFS
        */
        public static final String PAUSED_UNTIL_NEXT_CHARGE = "paused until next charge status";
    }

    public static class SensorSpecifics {
        public static class Loudness {
            /**
             * Key for learned value of total silence..
             */
            public static final String TOTAL_SILENCE = "total_silence";
            /**
             * Key for learned value of highest loudness.
             */
            public static final String LOUDEST = "loudest";
        }

        public static class AutoCalibratedNoise {
            /**
             * Key for learned value of total silence..
             */
            public static final String TOTAL_SILENCE = "AutoCalibratedNoise.total_silence";
            /**
             * Key for learned value of highest loudness.
             */
            public static final String LOUDEST = "AutoCalibratedNoise.loudest";
        }
    }

    /**
     * Name of the shared preferences file used for storing CommonSense authentication data. Use
     * {@link Context#MODE_PRIVATE}.
     * 
     * @see #MAIN_PREFS_PREFS
     * @see #STATUS_PREFS
     */
    public static final String AUTH_PREFS = "authentication";// "login";
    /**
     * Name of the main preference file, used for storing the settings for the Sense service.
     * 
     * @see #AUTH_PREFS
     * @see #STATUS_PREFS
     */
    public static final String MAIN_PREFS = "main";
    /**
     * Name of shared preferences file holding the desired status of the Sense service.
     * 
     * @see #AUTH_PREFS
     * @see #MAIN_PREFS
     */
    public static final String STATUS_PREFS = "service_status_prefs";
    /**
     * Name of the sensor specifics file, used for storing the settings for the Sense service.
     * 
     * @see #AUTH_PREFS
     * @see #STATUS_PREFS
     */
    public static final String SENSOR_SPECIFICS = "sensor_specifics";

    private SensePrefs() {
        // private constructor to prevent instantiation
    }
}
 public static class Auth {
    /**
     * Key for login preference for session cookie.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String LOGIN_COOKIE = "login_cookie";
    /**
     *Key for login preference for session id.
     *  
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String LOGIN_SESSION_ID = "session_id";
    /**
     * Key for login preference for email address.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String LOGIN_USERNAME = "login_mail";
    /**
     * Key for login preference for hashed password.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String LOGIN_PASS = "login_pass";
    /**
     * Key for storing the online sensor list for this device (type of JSONArray).
     * 
     * @see #SENSOR_LIST_COMPLETE
     * @see SensePrefs#AUTH_PREFS
     * @deprecated
     */
    public static final String SENSOR_LIST = "sensor_list";
    /**
     * Key for storing the online sensor list for this user (type of JSONArray).
     * 
     * @see #SENSOR_LIST
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String SENSOR_LIST_COMPLETE = "sensor_list_complete";
    /**
     * Key for storing the retrieval time of device's online sensor list.
     * 
     * @see #SENSOR_LIST_COMPLETE_TIME
     * @see SensePrefs#AUTH_PREFS
     * @deprecated
     */
    public static final String SENSOR_LIST_TIME = "sensor_list_timestamp";
    /**
     * Key for storing the retrieval time of complete online sensor list.
     * 
     * @see #SENSOR_LIST_TIME
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String SENSOR_LIST_COMPLETE_TIME = "sensor_list_complete_timestamp";
    /**
     * Key for storing the online device id.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String DEVICE_ID = "device_id";
    /**
     * Key for storing the retrieval time of the online device id.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String DEVICE_ID_TIME = "device_id_timestamp";
    /**
     * Key for storing the online device type.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String DEVICE_TYPE = "device_type";
    /**
     * Key for storing the IMEI of the phone.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String PHONE_IMEI = "phone_imei";
    /**
     * Key for storing the type of the phone.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String PHONE_TYPE = "phone_type";
    /**
     * Key for storing if gcm registration_id
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String GCM_REGISTRATION_ID = "gcm_registration_id";

}
 public static class Advanced {
    /**
     * Key to use the development version of CommonSense.
     * 
     * @see SensePrefs#AUTH_PREFS
     */
    public static final String DEV_MODE = "devmode";
    /**
     * Key for preference that toggles use of compression for transmission. Default is true.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String COMPRESS = "compression";
    /**
     * Key for preference that enables local storage, making the sensor data available to
     * other apps through a ContentProvider. Default is true.
     * 
     * @see SensePrefs#MAIN_PREFS
     * @deprecated Local storage is always on.
     */
    public static final String LOCAL_STORAGE = "local_storage";
    /**
     * Key for preference that enables communication with CommonSense. Disable this to work
     * in local-only mode. Default is true.
     */
    public static final String USE_COMMONSENSE = "use_commonsense";
    /**
     * Key for preference that enables the location feedback sensor. Enable this to
     * participate in Pim's location feedback test. Default is false.
     */
    public static final String LOCATION_FEEDBACK = "location_feedback";
    /**
     * Key for preference that enables Agostino mode. Enable this to participate in
     * Agostino's saliency test. Default is false.
     */
    public static final String AGOSTINO = "agostino_mode";
    /**
     * Key for preference that enables energy saving mode when using mobile Internet. 
     * When energy saving mode is on data will be uploaded every half an hour.
     * Default is true.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  MOBILE_INTERNET_ENERGY_SAVING_MODE = "mobile_internet_energy_saving_mode";
    /**
     * Key for preference that enables data upload on wifi only.
     * Default is false.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  WIFI_UPLOAD_ONLY = "wifi_upload_only";
    /**
     * Key for value of cache duration for local data in hours.
     * Default is 24.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  RETENTION_HOURS = "retention_hours";
    /**
     * Key for preference that enable database encryption.
     * Default is false.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  ENCRYPT_DATABASE = "encrypt_database";
    /**
     * Key for value of salt for database encryption
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String  ENCRYPT_DATABASE_SALT = "encrypt_database_salt";
    /**
     * Key for preference that enables encryption for credential data.
     * Default is false.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  ENCRYPT_CREDENTIAL = "encrypt_credential";
    /**
     * Key for value of salt for credential encryption.
     * @see SensePrefs#MAIN_PREFS
     */            
    public static final String  ENCRYPT_CREDENTIAL_SALT = "encrypt_credential_salt";
}
 public static class Ambience {
    /**
     * Key for preference that toggles use of light sensor in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String LIGHT = "ambience_light";
    /**
     * Key for preference that toggles use of camera light sensor in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String CAMERA_LIGHT = "ambience_camera_light";
    /**
     * Key for preference that toggles use of the microphone in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String MIC = "ambience_mic";
    /**
     * Key for preference that toggles audio recording ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String RECORD_AUDIO = "ambience_record_audio";
    /**
     * Key for preference that toggles use of the audio spectrum in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String AUDIO_SPECTRUM = "ambience_audio_spectrum";
    /**
     * Key for preference that toggles use of the pressure sensor in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String PRESSURE = "ambience_pressure";
    /**
     * Key for preference that toggles use of the temperature sensor in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String TEMPERATURE = "ambience_temperature";
    /**
     * Key for preference that toggles use of the magnetic field sensor in ambience sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String MAGNETIC_FIELD = "ambience_magnetic_field";
    /**
     * Key for preference that toggles use of the relative humidity sensor in ambience
     * sensing.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String HUMIDITY = "ambience_humidity";
    /**
     * Key for preference that toggles "burst-mode" for the noise sensor
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String BURSTMODE = "ambience_burstmode";
    /**
     * Key for preference that toggles whether to upload and store burst samples.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String DONT_UPLOAD_BURSTS = "ambience_dont_upload_bursts";
    /**
     * Key for preference that toggles whether to record one sided or two sided voice calls 
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String RECORD_TWO_SIDED_VOICE_CALL = "record_two_sided_voice_call";
}
 public static class External {

    public static class MyGlucoHealth {
        /**
         * Key for preference that toggles use of the MyGlucohealth sensor.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String MAIN = "myglucohealth";
    }

    public static class TanitaScale {
        /**
         * Key for preference that toggles use of the Tanita scale sensor.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String MAIN = "tanita_scale";
    }

    public static class ZephyrBioHarness {

        /**
         * Key for preference that toggles use of the Zephyr BioHarness.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String MAIN = "zephyrBioHarness";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness Accelerometer.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String ACC = "zephyrBioHarness_acc";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness Heart rate.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String HEART_RATE = "zephyrBioHarness_heartRate";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness Temperature.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String TEMP = "zephyrBioHarness_temp";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness Respiration rate.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String RESP = "zephyrBioHarness_resp";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness worn status.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String WORN_STATUS = "zephyrBioHarness_wornStatus";
        /**
         * Key for preference that toggles use of the Zephyr BioHarness battery level.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String BATTERY = "zephyrBioHarness_battery";
    }

    public static class ZephyrHxM {
        /**
         * Key for preference that toggles use of the Zephyr HxM.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String MAIN = "zephyrHxM";
        /**
         * Key for preference that toggles use of the Zephyr HxM speed.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String SPEED = "zephyrHxM_speed";
        /**
         * Key for preference that toggles use of the Zephyr HxM heart rate.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String HEART_RATE = "zephyrHxM_heartRate";
        /**
         * Key for preference that toggles use of the Zephyr HxM battery.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String BATTERY = "zephyrHxM_battery";
        /**
         * Key for preference that toggles use of the Zephyr HxM distance.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String DISTANCE = "zephyrHxM_distance";
        /**
         * Key for preference that toggles use of the Zephyr HxM strides.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String STRIDES = "zephyrHxM_strides";
    }

    public static class OBD2Sensor {
        /**
         * Key for preference that toggles use of the OBD-II sensor.
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String MAIN = "obd2sensor";
    }
}
 public static class Location {
    /**
     * Key for preference that toggles use of GPS in location sensor.
     * 
     * @see SensePrefs#MAIN_PREFS
     * @deprecated Selecting the location provider manually will replaced by the fused location provider.
     */
    @Deprecated
    public static final String GPS = "location_gps";
    /**
     * Key for preference that toggles use of Network in location sensor.
     * 
     * @see SensePrefs#MAIN_PREFS
     * @deprecated Selecting the location provider manually will replaced by the fused location provider.
     */
    @Deprecated
    public static final String NETWORK = "location_network";
    /**
     * Key for preference that toggles use of sensor fusion to toggle the GPS usage.
     * 
     * @see SensePrefs#MAIN_PREFS
     * @deprecated Selecting the GPS as provider automatically will be replaced by the fused location provider.
     */
    @Deprecated
    public static final String AUTO_GPS = "automatic_gps";
    /**
     * Key for preference that toggles use of the time zone sensor
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String TIME_ZONE = "time_zone";
    /**
     * Key for preference that toggles the use of the fused location provider.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String FUSED_PROVIDER = "location_fused";
    /**
     * Key for preference that sets the priority for the fused location provider.
     * By default the BALANCED priority is used.
     * 
     * @see SensePrefs#MAIN_PREFS
     */
    public static final String FUSED_PROVIDER_PRIORITY = "location_fused_priority";

    public static class FusedProviderPriority
    {
        /**
         * Key for preference that toggles the use of the fused location provider with a high power consumption and accuracy.
         * Accuracy: 1m (most precise location possible)
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String ACCURATE = "location_fused_accurate";
        /**
         * Key for preference that toggles the use of the fused location provider with a balance between power consumption and accuracy.
         * Accuracy: 100 (city block)
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String BALANCED = "location_fused_balanced";
        /**
         * Key for preference that toggles the use of the fused location provider with low power consumption and accuracy.
         * Accuracy: 10km (city level)
         * 
         * @see SensePrefs#MAIN_PREFS
         */
        public static final String LOW_POWER = "location_fused_low_power";
    }
}
      public static class PhoneState {
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String BATTERY = "phonestate_battery";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String SCREEN_ACTIVITY = "phonestate_screen_activity";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String PROXIMITY = "phonestate_proximity";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String IP_ADDRESS = "phonestate_ip";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String DATA_CONNECTION = "phonestate_data_connection";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String UNREAD_MSG = "phonestate_unread_msg";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String SERVICE_STATE = "phonestate_service_state";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String SIGNAL_STRENGTH = "phonestate_signal_strength";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String CALL_STATE = "phonestate_call_state";
         /**
          * @see SensePrefs#MAIN_PREFS
          *              */
         public static final String FOREGROUND_APP = "foreground_app";
         /**
          * @see SensePrefs#MAIN_PREFS
          */
         public static final String INSTALLED_APPS = "installed_apps";
            
         /**
          * @see SensePrefs#MAIN_PREFS
          */
public static final String APP_INFO = "app_info";
     }
 public static class Status {
    /**
     * Key for the main status of the sensors. Set to <code>false</code> to disable all the
     * sensing components.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String MAIN = "main service status";
    /**
     * Key for the status of the "ambience" sensors. Set to <code>true</code> to enable sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String AMBIENCE = "ambience component status";
    /**
     * Key for the status of the "device proximity" sensors. Set to <code>true</code> to enable
     * sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String DEV_PROX = "device proximity component status";
    /**
     * Key for the status of the external Bluetooth sensors. Set to <code>true</code> to enable
     * sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String EXTERNAL = "external services component status";
    /**
     * Key for the status of the location sensors. Set to <code>true</code> to enable sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String LOCATION = "location component status";
    /**
     * Key for the status of the motion sensors. Set to <code>true</code> to enable sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String MOTION = "motion component status";
    /**
     * Key for the status of the "phone state" sensors. Set to <code>true</code> to enable
     * sensing.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String PHONESTATE = "phone state component status";
    /**
     * Key for the status of the questionnaire. Set to <code>true</code> to enable it.
     * 
     * @see SensePrefs#STATUS_PREFS
     * @deprecated Sense does not support the questionnaire anymore
     */
    public static final String POPQUIZ = "pop quiz component status";
    /**
     * Key for preference to automatically start the Sense service on boot.
     * 
     * @see SensePrefs#STATUS_PREFS
     */
    public static final String AUTOSTART = "autostart";
    /**
    * Key for preference to pause sensing until the next charge.
    * 
    * @see SensePrefs#STATUS_PREFS
    */
    public static final String PAUSED_UNTIL_NEXT_CHARGE = "paused until next charge status";
}
 public class SenseUrls {
    /** Host name of CommonSense Authentication API */
    public static final String AUTH_API = "https://auth-api.sense-os.nl/v1/";
    /** Host name of CommonSense Staging Authentication API */
    public static final String AUTH_STAGING_API = "http://auth-api.staging.sense-os.nl/v1/";
    /** Host name of CommonSense API */
    public static final String API = "https://api.sense-os.nl/";
    /** Host name of CommonSense dev API */
    public static final String API_DEV = "http://api.staging.sense-os.nl/";
    /** Default page size for getting lists at CommonSense */
    public static final int PAGE_SIZE = 1000;

    public static final String ALL_SENSORS = API + "sensors" + "?per_page=" + PAGE_SIZE
            + "&details=full";
    public static final String ALL_SENSORS_DEV = API_DEV + "sensors" + "?per_page=" + PAGE_SIZE
            + "&details=full";
    public static final String CONFIGURATION = API + "configurations/%1";
    public static final String CONFIGURATION_DEV = API_DEV + "configurations/%1";
    public static final String CONNECTED_SENSORS = API + "sensors/%1/sensors" + "?per_page="
            + PAGE_SIZE + "&details=full";
    public static final String CONNECTED_SENSORS_DEV = API_DEV + "sensors/%1/sensors"
            + "?per_page=" + PAGE_SIZE + "&details=full";
    public static final String CREATE_SENSOR_DEV = API_DEV + "sensors";
    public static final String CURRENT_USER = API + "users/current";
    public static final String CURRENT_USER_DEV = API_DEV + "users/current";
    public static final String DEVICE_CONFIGURATION = API + "devices/%1/configuration";
    public static final String DEVICE_CONFIGURATION_DEV = API_DEV + "devices/%1/configuration";
    public static final String DEVICE_SENSORS = API + "devices/%1/sensors";
    public static final String DEVICE_SENSORS_DEV = API_DEV + "devices/%1/sensors";
    public static final String DEVICES = API + "devices";
    public static final String DEVICES_DEV = API_DEV + "devices";
    public static final String FORGOT_PASSWORD = API + "requestPasswordReset";
    public static final String FORGOT_PASSWORD_DEV = API_DEV + "requestPasswordReset";
    public static final String GROUP_USERS = API + "groups/%1/users";
    public static final String GROUP_USERS_DEV = API_DEV + "groups/%1/users";
    public static final String LOGIN = AUTH_API + "login";
    public static final String LOGIN_DEV = AUTH_STAGING_API + "login";
    public static final String MANUAL_LEARN = API + "sensors/%1/services/%2/manualLearn";
    public static final String MANUAL_LEARN_DEV = API_DEV + "sensors/%1/services/%2/manualLearn";
    public static final String REGISTER = API + "users";
    public static final String REGISTER_DEV = API_DEV + "users";
    public static final String REGISTER_GCM_ID = API + "devices/%1/push/register";
    public static final String REGISTER_GCM_ID_DEV = API_DEV + "devices/%1/push/register";
    public static final String SENSOR_DATA = API + "sensors/%1/data";
    public static final String SENSOR_DATA_DEV = API_DEV + "sensors/%1/data";
    public static final String SENSOR_DATA_MULTIPLE = API + "sensors/data";
    public static final String SENSOR_DATA_MULTIPLE_DEV = API_DEV + "sensors/data";
    public static final String SENSOR_DEVICE = API + "sensors/%1/device";
    public static final String SENSOR_DEVICE_DEV = API_DEV + "sensors/%1/device";
    public static final String SENSOR_FILE = API + "sensors/%1/file";
    public static final String SENSOR_FILE_DEV = API_DEV + "sensors/%1/file";
    public static final String SENSOR_USERS = API + "sensors/%1/users";
    public static final String SENSOR_USERS_DEV = API_DEV + "sensors/%1/users";
    public static final String SENSORS = API + "sensors";
    public static final String VERSION = "http://data.sense-os.nl/senseapp/version.php";
    public static final String DATAPROCESSOR_FILE = API + "dataprocessors/files";
    public static final String DATAPROCESSOR_FILE_DEV = API_DEV + "dataprocessors/files";
    public static final String CHANGE_PASSWORD = API + "change_password";
    public static final String CHANGE_PASSWORD_DEV = API_DEV + "change_password";
    public static final String RESET_PASSWORD_REQUEST = AUTH_API + "reset_password/request";
    public static final String RESET_PASSWORD_REQUEST_DEV = AUTH_STAGING_API + "reset_password/request";

    private SenseUrls() {
        // private constructor to prevent instantiation
    }
} public static class SensorNames {

    /**
     * Fall detector sensor. Can be a real fall or a regular free fall for demo's. Part of the
     * Motion sensors.
     */
    public static final String FALL_DETECTOR = "fall_detector";

    /** Noise level sensor. Part of the Ambience sensors. */
    public static final String NOISE = "noise_sensor";

    /** Noise level sensor (Burst-mode). Part of the Ambience sensors. */
    public static final String NOISE_BURST = "noise_sensor (burst-mode)";

    /** Audio spectrum sensor. Part of the Ambience sensors. */
    public static final String AUDIO_SPECTRUM = "audio_spectrum";

    /** Microphone output. Part of the Ambience sensors (real-time mode only). */
    public static final String MIC = "microphone";

    /** Light sensor. Part of the Ambience sensors. */
    public static final String LIGHT = "light";

    /** Camera Light sensor. Part of the Ambience sensors. */
    public static final String CAMERA_LIGHT = "camera_light";

    /** Bluetooth discovery sensor. Part of the Neighboring Devices sensors. */
    public static final String BLUETOOTH_DISCOVERY = "bluetooth_discovery";

    /** Wi-Fi scan sensor. Part of the Neighboring Devices sensors. */
    public static final String WIFI_SCAN = "wifi scan";

    /** NFC sensor. Part of the Neighboring Devices sensors. */
    public static final String NFC_SCAN = "nfc_scan";

    /** Acceleration sensor. Part of the Motion sensors, also used in Zephyr BioHarness */
    public static final String ACCELEROMETER = "accelerometer";

    /** Motion sensor. The basis for the other Motion sensors. */
    public static final String MOTION = "motion";

    /** Linear acceleration sensor name. Part of the Motion sensors. */
    public static final String LIN_ACCELERATION = "linear acceleration";

    /** Gyroscope sensor name. Part of the Motion sensors. */
    public static final String GYRO = "gyroscope";

    /** Magnetic field sensor name. Part of the Ambience sensors. */
    public static final String MAGNETIC_FIELD = "magnetic field";

    /** Orientation sensor name. Part of the Motion sensors. */
    public static final String ORIENT = "orientation";

    /** Epi-mode accelerometer sensor. Special part of the Motion sensors. */
    public static final String ACCELEROMETER_EPI = "accelerometer (epi-mode)";

    /** Burst-mode accelerometer sensor. Special part of the Motion sensors. */
    public static final String ACCELEROMETER_BURST = "accelerometer (burst-mode)";

    /** Burst-mode gyroscope sensor. Special part of the Motion sensors. */
    public static final String GYRO_BURST = "gyroscope (burst-mode)";

    /** Burst-mode linear acceleration sensor. Special part of the Motion sensors. */
    public static final String LINEAR_BURST = "linear acceleration (burst-mode)";

    /** Motion energy sensor name. Special part of the Motion sensors. */
    public static final String MOTION_ENERGY = "motion energy";

    /*** battery sensor for Zephyr BioHarness external sensor */
    public static final String BATTERY_LEVEL = "battery level";

    /** heart rate sensor for Zephyr BioHarness and HxM external sensors */
    public static final String HEART_RATE = "heart rate";

    /** respiration rate sensor for Zephyr BioHarness external sensor */
    public static final String RESPIRATION = "respiration rate";

    /** temperature sensor for Zephyr BioHarness external sensor */
    public static final String TEMPERATURE = "temperature";

    /** worn status for Zephyr BioHarness external sensor */
    public static final String WORN_STATUS = "worn status";

    /** blood pressure sensor */
    public static final String BLOOD_PRESSURE = "blood_pressure";

    /** reaction time sensor */
    public static final String REACTION_TIME = "reaction_time";

    /** speed sensor for Zephyr HxM external sensor */
    public static final String SPEED = "speed";

    /** distance sensor for Zephyr HxM external sensor */
    public static final String DISTANCE = "distance";

    /** battery sensor for Zephyr HxM external sensor */
    public static final String BATTERY_CHARGE = "battery charge";

    /** strides sensor (stappenteller) for Zephyr HxM external sensor */
    public static final String STRIDES = "strides";

    /** Location sensor. */
    public static final String LOCATION = "position";

    /** TimeZone sensor. */
    public static final String TIME_ZONE = "time_zone";

    /** Battery sensor. Part of the Phone State sensors. */
    public static final String BATTERY_SENSOR = "battery sensor";
        
    /** App info sensor. Part of the Phone State sensors. */
    public static final String APP_INFO_SENSOR = "app_info";

    /** Screen activity sensor. Part of the Phone State sensors. */
    public static final String SCREEN_ACTIVITY = "screen activity";

    /** Pressure sensor. Part of the Phone State sensors. */
    public static final String PRESSURE = "pressure";

    /** Proximity sensor. Part of the Phone State sensors. */
    public static final String PROXIMITY = "proximity";

    /** Call state sensor. Part of the Phone State sensors. */
    public static final String CALL_STATE = "call state";

    /** Data connection state sensor. Part of the Phone State sensors. */
    public static final String DATA_CONN = "data connection";

    /** IP address sensor. Part of the Phone State sensors. */
    public static final String IP_ADDRESS = "ip address";

    /** Mobile service state sensor. Part of the Phone State sensors. */
    public static final String SERVICE_STATE = "service state";

    /** Mobile signal strength sensor. Part of the Phone State sensors. */
    public static final String SIGNAL_STRENGTH = "signal strength";

    /** Unread messages sensor. Part of the Phone State sensors. */
    public static final String UNREAD_MSG = "unread msg";

    /** Data connection type sensor. Part of the Phone State sensors. */
    public static final String CONN_TYPE = "connection type";

    /** Monitor status since DTCs cleared. Part of the OBD-II sensors. */
    public static final String MONITOR_STATUS = "monitor status";

    /** Fuel system status. Part of the OBD-II sensors. */
    public static final String FUEL_SYSTEM_STATUS = "fuel system status";

    /** Calculated engine load. Part of the OBD-II sensors. */
    public static final String ENGINE_LOAD = "calculated engine load value";

    /** Engine coolant. Part of the OBD-II sensors. */
    public static final String ENGINE_COOLANT = "engine coolant";

    /** Short/Long term fuel trim bank 1 & 2. Part of the OBD-II sensors. */
    public static final String FUEL_TRIM = "fuel trim";

    /** Fuel Pressure. Part of the OBD-II sensors. */
    public static final String FUEL_PRESSURE = "fuel pressure";

    /** Intake manifold absolute pressure. Part of the OBD-II sensors. */
    public static final String INTAKE_PRESSURE = "intake manifold absolute pressure";

    /** Engine RPM. Part of the OBD-II sensors. */
    public static final String ENGINE_RPM = "engine RPM";

    /** Vehicle speed. Part of the OBD-II sensors. */
    public static final String VEHICLE_SPEED = "vehicle speed";

    /** Timing advance. Part of the OBD-II sensors. */
    public static final String TIMING_ADVANCE = "timing advance";

    /** Intake air temperature. Part of the OBD-II sensors. */
    public static final String INTAKE_TEMPERATURE = "intake air temperature";

    /** MAF air flow rate. Part of the OBD-II sensors. */
    public static final String MAF_AIRFLOW = "MAF air flow rate";

    /** Throttle position. Part of the OBD-II sensors. */
    public static final String THROTTLE_POSITION = "throttle position";

    /** Commanded secondary air status. Part of the OBD-II sensors. */
    public static final String AIR_STATUS = "commanded secondary air status";

    /** Oxygen sensors. Part of the OBD-II sensors. */
    public static final String OXYGEN_SENSORS = "oxygen sensors";

    /** OBD standards. Part of the OBD-II sensors. */
    public static final String OBD_STANDARDS = "OBD standards";

    /** Auxiliary input status. Part of the OBD-II sensors. */
    public static final String AUXILIARY_INPUT = "auxiliary input status";

    /** Run time since engine start. Part of the OBD-II sensors. */
    public static final String RUN_TIME = "run time";

    /** Ambient temperature sensor. Part of the ambience sensors. From API >= 14 only */
    public static final String AMBIENT_TEMPERATURE = "ambient_temperature";

    /** Relative humidity sensor. Part of the ambience sensors. From API >= 14 only */
    public static final String RELATIVE_HUMIDITY = "relative_humidity";

    /** Bluetooth number of neighbours, count sensor */
    public static final String BLUETOOTH_NEIGHBOURS_COUNT = "bluetooth neighbours count";

    /** Traveled distance for each day */
    public static final String TRAVELED_DISTANCE_24H = "traveled distance 24h";

    /** Traveled distance for each hour */
    public static final String TRAVELED_DISTANCE_1H = "traveled distance 1h";

    public static final String LOUDNESS = "loudness";

    public static final String ATTACHED_TO_MYRIANODE = "attachedToMyriaNode";

    public static final String APP_INSTALLED = "installed_apps";

    public static final String APP_FOREGROUND = "foreground_app";

    private SensorNames() {
        // class should not be instantiated
    }
} public abstract class Controller {

    private class Intervals {
        static final long ECO = AlarmManager.INTERVAL_HALF_HOUR;
        static final long RARELY = 1000 * 60 * 15; 
        static final long NORMAL = 1000 * 60 * 5;
        static final long OFTEN = 1000 * 60 * 1;
        static final long BALANCED = 1000 * 60 * 3;
    }

    private static final String TAG = "Controller";
    private static Controller sInstance;

    /**
     * Returns a controller instance
     * 
     * @param context
     *            Context of the Sense service
     * @return the existed controller if any, otherwise the one which has just been created
     */
    public static synchronized Controller getController(Context context) {
        if (sInstance == null) {
            sInstance = new CtrlDefault(context);
        }
        return sInstance;
    }

    private Context mContext;

    protected Controller(Context context) {
        mContext = context;
    }

    /**
     * Retains the mode of Light sensor, and defines it as changed or idle, if the last light
     * measurement had a noticeable difference compared to the previous one or not, respectively
     * 
     * @param value
     *            Last light measurement, in lux.
     */
    public abstract void checkLightSensor(float value);

    /**
     * Retains the mode of Noise sensor, and defines it as loud or idle, if the last noise
     * measurement had a noticeable difference compared to the previous one or not, respectively
     * 
     * @param dB
     *            Last noise measurement, in db.
     */
    public abstract void checkNoiseSensor(double dB);

    /**
     * Checks to see if the sensor is still doing a useful job or whether it is better if we disable
     * it for a while. This method is a callback for a periodic alarm to check the sensor status.
     * 
     * @param isGpsAllowed
     *            True if Gps usage is allowed by the user.
     * @param isListeningNw
     *            True if we currently listen for Network fix.
     * @param isListeningGps
     *            True if we currently listen for Gps fix.
     * @param time
     *            The time between location refresh attempts.
     * @param lastGpsFix
     *            The last fix by Gps provider.
     * @param listenGpsStart
     *            The last time the Gps provider was enabled.
     * @param lastNwFix
     *            The last fix by Network provider.
     * @param listenNwStart
     *            The last time the Network provider was enabled.
     * @param listenGpsStop
     *            The last time the Gps provider was disabled.
     * @param listenNwStop
     *            The last time the Network provider was disabled.
     * 
     * @see #alarmReceiver
     */
    public void checkSensorSettings(boolean isGpsAllowed, boolean isListeningNw,
            boolean isListeningGps, long time, Location lastGpsFix, long listenGpsStart,
            Location lastNwFix, long listenNwStart, long listenGpsStop, long listenNwStop) {
    }
  
    /**
     * Starts periodic transmission of the buffered sensor data.
     */
    public void scheduleTransmissions() {
        Log.v(TAG, "Schedule transmissions");

        SharedPreferences mainPrefs = mContext.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);
        int syncRate = Integer.parseInt(mainPrefs.getString(Main.SYNC_RATE, SensePrefs.Main.SyncRate.NORMAL));
        int sampleRate = Integer.parseInt(mainPrefs.getString(Main.SAMPLE_RATE, SensePrefs.Main.SampleRate.NORMAL));

        // pick transmission interval
        long txInterval;
        switch (syncRate) {
        case 2: // rarely, every 15 minutes
        	txInterval = Intervals.RARELY;
        	break;
        case 1: // eco-mode
            txInterval = Intervals.ECO;
            break;
        case 0: // 5 minute
            txInterval = Intervals.NORMAL;
            break;
        case -1: // 60 seconds
            txInterval = Intervals.OFTEN;
            break;
        case -2: // real-time: schedule transmission based on sample time
            switch (sampleRate) {
            case 2: // balanced
                txInterval = Intervals.BALANCED * 3;
                break;
            case 1: // rarely
                txInterval = Intervals.ECO * 3;
                break;
            case 0: // normal
                txInterval = Intervals.NORMAL * 3;
                break;
            case -1: // often
                txInterval = Intervals.OFTEN * 3;
                break;
            case -2: // real time
                txInterval = Intervals.OFTEN;
                break;
            default:
                Log.w(TAG, "Unexpected sample rate value: " + sampleRate);
                return;
            }
            break;
        default:
            Log.w(TAG, "Unexpected sync rate value: " + syncRate);
            return;
        }

        // pick transmitter task interval
        long txTaskInterval;
        switch (sampleRate) {
        case -2: // real time
            txTaskInterval = 0;
            break;
        case -1: // often
            txTaskInterval = 10 * 1000;
            break;
        case 0: // normal
            txTaskInterval = 60 * 1000;
            break;
        case 1: // rarely (15 minutes)
            txTaskInterval = 15 * 60 * 1000;
            break;
        case 2: // balanced (3 minutes)
            txTaskInterval = 3 * 60 * 1000;
            break;
        default:
            Log.w(TAG, "Unexpected sample rate value: " + sampleRate);
            return;
        }

        DataTransmitter transmitter = DataTransmitter.getInstance(mContext);
        transmitter.startTransmissions(txInterval, txTaskInterval);
    }
} public class DeviceProximity {

    private Context context;
    private boolean isScanEnabled = false;
    private BluetoothDeviceProximity bluetoothDP;
    private WIFIDeviceProximity wifiDP;
    private boolean isBtEnabled;
    private boolean isWifiEnabled;
    private int scanInterval = 0;

    public DeviceProximity(Context context) {
        this.context = context;
        bluetoothDP = new BluetoothDeviceProximity(context);
        wifiDP = new WIFIDeviceProximity(context);
    }

    public boolean getScanEnabled() {
        return isScanEnabled;
    }

    public int getScanInterval() {
        return scanInterval;
    }

    public void setScanInterval(int scanInterval) {
        this.scanInterval = scanInterval;
    }

    public void startEnvironmentScanning(int interval) {
        scanInterval = interval;
        isScanEnabled = true;

        final SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);

        isBtEnabled = mainPrefs.getBoolean(DevProx.BLUETOOTH, true);
        if (isBtEnabled) {
            bluetoothDP.startEnvironmentScanning(interval);
        }

        isWifiEnabled = mainPrefs.getBoolean(DevProx.WIFI, true);
        if (isWifiEnabled) {
            wifiDP.startEnvironmentScanning(interval);
        }
    }
    
    public DataProducer getBluetoothDeviceProximity()
    {
        return bluetoothDP;
    }
    
    public DataProducer getWIFIDeviceProximity()
    {
        return wifiDP;
    }

    public void stopEnvironmentScanning() {
        isScanEnabled = false;
        if (isBtEnabled) {
            bluetoothDP.stopEnvironmentScanning();
        }
        if (isWifiEnabled) {
            wifiDP.stopEnvironmentScanning();
        }
    }
} public class NewOBD2DeviceConnector extends BaseDataProducer implements Runnable {
	protected final String TAG = "OBD-II";
	protected final UUID serial_uuid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");

	// remember the state you are in
	enum State {
		AWAITING_BLUETOOTH, BLUETOOTH_ENABLED, CONNECTION_READY, DEVICE_POWERED, READY, STOPPED
	}

	protected State previousState, currentState;
	protected boolean sensorsenabled = false;

	// handling threads
	protected Handler stateMachineHandler = null;
	protected HandlerThread stateMachineHandlerThread = null;
	protected StateMachine stateMachine = null;

	// global variables
	protected final Context context;
	protected final int interval;
	protected BluetoothDevice device;
	protected String databuffer = "";

	public NewOBD2DeviceConnector(Context context, BluetoothDevice device, int interval) {
		this.context = context;
		this.device = device;
		if (interval < 0)
			this.interval = 1;
		else
			this.interval = interval;

	}

	public NewOBD2DeviceConnector(Context context, int interval) {
		this(context, null, interval);
	}

	@Override
	public void run() {
		sensorsenabled = true;
		currentState = State.AWAITING_BLUETOOTH;

		if (stateMachineHandlerThread == null) {
			stateMachineHandlerThread = new HandlerThread(TAG);
			stateMachineHandlerThread.start();
		}

		if (stateMachineHandler == null) {
			stateMachineHandler = new Handler(stateMachineHandlerThread.getLooper());
		}

		stateMachineHandler.post(stateMachine = new StateMachine());
	}

	public void stop() {
		sensorsenabled = false;
		Log.v(TAG, "stopping OBD2DeviceConnector");

		try {
			if (stateMachine != null) {
				stateMachine.stop();
			}
			if (stateMachineHandler != null) {
				stateMachineHandler.removeCallbacks(stateMachine);
				stateMachineHandler = null;
			}
			if (stateMachineHandlerThread != null) {
				stateMachineHandlerThread.getLooper().quit();
				stateMachineHandlerThread = null;
			}
		} catch (Exception e) {
			Log.e(TAG, "Exception in stopping BluetoothDeviceRegistrator:", e);
		}
	}

	public class StateMachine implements Runnable {
		protected long runstart = SNTP.getInstance().getTime();

		// connection variables
		protected BluetoothAdapter adapter;
		protected BTReceiver btReceiver;
		protected BluetoothSocket socket;
		protected InputStream input;
		protected OutputStream output;

		// Hayes Command set: turn automatic formatting ON, turn headers OFF
		final String[] hayescommands = { "AT E0", /* "AT CAF 1", */"AT H 0" };

		// OBD variables
		private ArrayList<String> verifiedsensors = new ArrayList<String>();
		private EmptyRegistrator registrator = new EmptyRegistrator();

		// OBD timer variables
		private final int timeout = 20000;
		private final int sleeptime = 500;

		@Override
		public void run() {
			while (sensorsenabled && currentState != State.STOPPED) {
				runstart = SNTP.getInstance().getTime();
				// Log.d(TAG, "Current State: " + currentState + " (past:" + previousState + ")");
				previousState = currentState;
				switch (currentState) {
				case AWAITING_BLUETOOTH:
					currentState = doCheckBluetooth();
					break;
				case BLUETOOTH_ENABLED:
					currentState = doConnectSocket();
					break;
				case CONNECTION_READY:
					Log.v(TAG, "now trying to make sense of the car");
					currentState = doWaitForBoot();
					break;
				case DEVICE_POWERED:
					currentState = doInitializeUsingHayes();
					break;
				case READY:
					currentState = doPollSensors();
					break;
				case STOPPED:
					try {
						if (socket != null)
							socket.close();
					} catch (Exception e) {
						Log.e(TAG, "Error closing socket in stopped state");
					}
					socket = null;
					break;
				default:
					currentState = State.AWAITING_BLUETOOTH;
					break;
				}
				long sleeptime = interval - (SNTP.getInstance().getTime() - runstart);
				// Log.v(TAG, "currentState == previousState? " + (currentState == previousState));
				// Log.v(TAG, "sleeptime: " + sleeptime + " milliseconds");
				if (currentState == previousState && sleeptime > 0) {
					try {
						Thread.sleep(sleeptime);
					} catch (InterruptedException e) {
						Log.e(TAG, "Interrupted while sleeping: ", e);
						currentState = State.STOPPED;
					}
				}
			}
			Log.v(TAG, "Stopping StateMachine, while in state " + currentState);
			stop();
		}

		public void stop() {
			Log.v(TAG, "stopping the StateMachine");

			// close and remove the socket
			try {
				if (socket != null)
					socket.close();
			} catch (Exception e) {
				Log.e(TAG, "error while closing socket");
			}
			socket = null;

			// removing the btListener
			try {
				if (btReceiver != null)
					context.unregisterReceiver(btReceiver);
			} catch (Exception e) {
				Log.e(TAG, "error while closing socket");
			}
			btReceiver = null;

			currentState = State.STOPPED;
		}

		protected State doCheckBluetooth() {
			// try to locate the default bluetooth adapter, go to State STOPPED if not found
			try {
				adapter = BluetoothAdapter.getDefaultAdapter();
			} catch (Exception e) {
				return State.STOPPED;
			}

			if (adapter != null) {

				// listen to adapter changes. btReceiver will handle state changes
				if (btReceiver == null)
					btReceiver = new BTReceiver();
				context.registerReceiver(btReceiver, new IntentFilter(
						BluetoothAdapter.ACTION_STATE_CHANGED));

				// look at the current state of the adapter
				// if the adapter is turned on, set the state to BLUETOOTH_ENABLED
				if (adapter.getState() == BluetoothAdapter.STATE_ON) {
					return State.BLUETOOTH_ENABLED;
				} else if (adapter.getState() != BluetoothAdapter.STATE_TURNING_ON) {
					Log.v(TAG, "TURNING ON");
					// if the adapter is not being turned on, ask user permission to turn it on
					Intent startBt = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
					startBt.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
					context.startActivity(startBt);
				}
				return State.AWAITING_BLUETOOTH;
			}
			// no good finding bluetooth, then stop the whole StateMachine
			else
				return State.STOPPED;
		}

		protected State doConnectSocket() {
			// try to connect to an OBD2 Dongle
			socket = connectSocket();
			if (socket == null)
				return State.BLUETOOTH_ENABLED;
			// Log.v(TAG, "socket.isConnected(): "+ socket.isConnected());
			try {
				input = socket.getInputStream();
				output = socket.getOutputStream();
				verifiedsensors = new ArrayList<String>();
				return State.CONNECTION_READY;
			} catch (IOException e) {
				// Stop if no good socket was created
				Log.e(TAG, "Error in getting stream in doconnectsocket", e);
				return State.STOPPED;
			}
		}

		protected State doWaitForBoot() {
			if (sendCommand("AT WS", "ELM327"))
				return State.DEVICE_POWERED;
			return State.CONNECTION_READY;
		}

		protected State doInitializeUsingHayes() {
			// if the ELM unit is powered and connected, initialize using a set of Hayes commands
			for (String command : hayescommands) {
				if (!sendHayesCommand(command))
					return State.DEVICE_POWERED;
			}
			return State.READY;
		}

		protected State doPollSensors() {
			{

				// TODO select which sensors to poll depending on preferences
				// TODO implementing pollMonitorStatus (is bit-encoded)
				// pollMonitorStatus();
				pollEngineLoad();
				pollEngineCoolant();
				pollFuelPressure(); // gives NumberFormatException when run in simulator
				pollIntakeManifoldPressure();
				pollEngineRPM();
				pollVehicleSpeed();
				pollIntakeAirTemperature();
				pollThrottlePosition();
				// TODO: 01 1F en verder volgens aangekruisde waardes
				/*
				 * pollRunTime(); pollDistanceTraveledWithMIL(); pollFuelLevelInput();
				 * pollBarometricPressure(); pollRelativeThrottlePosition();
				 * pollAmbientAirTemperature();
				 */
				// TODO: 01 47 en verder volgens aangekruisde waardes
				return State.READY;
			}
		}

		protected class BTReceiver extends BroadcastReceiver {
			@Override
			public void onReceive(Context context, Intent intent) {
				Log.v(TAG, "onReceive method, intent: " + intent.getAction());
				if (sensorsenabled) {
					String action = intent.getAction();
					final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
							BluetoothAdapter.STATE_OFF);
					if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
						if (currentState == State.AWAITING_BLUETOOTH) {
							if (state == BluetoothAdapter.STATE_ON) {
								currentState = State.BLUETOOTH_ENABLED;
							}
						} else {
							if (state != BluetoothAdapter.STATE_ON) {
								currentState = State.AWAITING_BLUETOOTH;
							}
						}
					}
				} else {
					Log.e(TAG,
							"why is this method still running, stop method is not functioning properly");
				}
			}
		}

		private BluetoothSocket connectSocket() {
			// try to create a connection to a bluetooth device
			BluetoothSocket tempsocket;
			if (device != null) {
				tempsocket = connectSocket(device);
				if (tempsocket != null) {
					return tempsocket;
				}
			}

			Set<BluetoothDevice> paireddevices = adapter.getBondedDevices();
			for (BluetoothDevice tempdev : paireddevices) {
				if (tempdev.getName().contains("OBD")) {
					tempsocket = connectSocket(tempdev);
					if (tempsocket != null) {
						device = tempdev;
						return tempsocket;
					}
				}
			}
			Log.e(TAG, "no device available");
			return null;
		}

		/**
		 * 
		 * @return whether or not a Socket connection was established
		 */
		private BluetoothSocket connectSocket(BluetoothDevice dev) {
			BluetoothSocket tempsocket;
			try {
				tempsocket = dev.createRfcommSocketToServiceRecord(serial_uuid);
				tempsocket.connect();
				Log.i(TAG, "Connected to obd2 via normal method");
				return tempsocket;
			} catch (IOException e) {
				try {
					Method m = dev.getClass().getMethod("createRfcommSocket",
							new Class[] { int.class });
					tempsocket = (BluetoothSocket) m.invoke(dev, Integer.valueOf(1));
					tempsocket.connect();
					Log.v(TAG, "Connected to " + dev.getName() + " via reflection work aroud");
					return tempsocket;
				}
				// if all has failed, stop this sensor
				catch (Exception ex) {
					Log.e(TAG, "No socket connected to " + dev.getName(), ex);
					return null;
				}
			} catch (Exception ex) {
				Log.e(TAG, "Failed to connect socket to " + dev.getName(), ex);
				return null;
			}
		}

		// The ‘>’ character indicates that the device is in the idle state, ready to receive
		// characters.
		private void readUntilPrompt() {
			try {
				if (socket != null && input != null) {
					int currentint = -1;
					char currentchar = 0;
					while (input != null) {
						try {
							currentint = input.read();
							if (currentchar == -1) {
								return;
							}
							currentchar = (char) currentint;
						} catch (IOException e) {
							return;
						}
						if (currentchar == '>')
							return;
						// valid characters for the response
						else if (currentchar >= 32 && currentchar <= 127) {
							databuffer += currentchar;
						}
						// represent CR/LF characters as |
						else if (currentchar == 13 || currentchar == 10) {
							databuffer += "|";
						}
					}
					Log.v(TAG, "DATABUFFER: " + databuffer);
				}
			} catch (Exception e) {
				Log.e(TAG, "Exception in readUntilPrompt, buffer thusfar: " + databuffer);
			}
		}

		private void clearBuffer() {
			int index = databuffer.lastIndexOf("\\|");
			if (index > -1 && index < databuffer.length())
				databuffer = databuffer.substring(index);
		}

		/**
		 * 
		 * @param command
		 *            the Hayes command (AT command) to be sent
		 * @return whether or not the execution of this command was successful
		 */
		private boolean sendHayesCommand(String command) {
			return sendCommand(command, "OK");
		}

		/**
		 * 
		 * @param command
		 *            the Mode and PID for the OBD-command to be checked
		 * @return the formatted response gotten, or null iff the command was invalid
		 */
		private String[] sendOBDCommand(String command) {
			if (command != null && command.length() >= 2) {
				String validresponse = (char) (command.toCharArray()[0] + 4) + command.substring(1);
				sendCommand(command, validresponse);
				String[] responses = databuffer.split("\\|");
				for (String response : responses) {
					if (response != null && response.contains(validresponse)) {
						// Log.d(TAG, "response found to "+command+ ", response: "+response);
						return response.split(" ");
					}
				}
				// Log.d(TAG, "No response found to "+command+ " in buffer "+databuffer);
			}
			return null;
		}

		private boolean sendCommand(String command, String validresponse) {
			clearBuffer();
			long deadline = SNTP.getInstance().getTime() + timeout;

			boolean commandsent = false;
			// first try and send the command
			while (SNTP.getInstance().getTime() < deadline) {
				commandsent = trySend(command);
				if (commandsent)
					break;
				try {
					Thread.sleep(sleeptime);
				} catch (InterruptedException e) {
					Log.e(TAG, "InterruptedException while sleeping in sendCommand");
				}
			}

			// try to find the reply which indicates a correct reading of the command
			while (SNTP.getInstance().getTime() < deadline) {
				readUntilPrompt();
				if (databuffer.contains(validresponse)) {
					databuffer = databuffer.substring(databuffer.indexOf(validresponse));
					return true;
				}
				try {
					Thread.sleep(sleeptime);
				} catch (InterruptedException e) {
					Log.e(TAG, "InterruptedException while sleeping in sendCommand");
				}
			}
			return false;
		}

		// messages to the ELM327 must be terminated with a CR character (#0D) before it will be
		// acted upon
		private boolean trySend(String data) {
			try {
				if (socket != null) {
					byte bytestosend[] = (data + "\r").getBytes();
					output.write(bytestosend);
					return true;
				}
			} catch (IOException e) {
				Log.e(TAG, "IOException sending (" + data + ")");
				currentState = State.AWAITING_BLUETOOTH;
			}
			return false;
		}

		@SuppressWarnings("unused")
		// TODO is this method required?
		private void pollMonitorStatus() {
			String[] hexbytes = sendOBDCommand("01 01");
			Integer.parseInt(hexbytes[2], 2);
			// TODO: handle this bit-encoded PID
		}

		private void pollEngineLoad() {
			String[] hexbytes = sendOBDCommand("01 04");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					float a = Integer.parseInt(hexbytes[2], 16);
					float value = a * 100f / 255f;
					SendDataPoint(SensorNames.ENGINE_LOAD, "percent", value, SenseDataTypes.FLOAT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollEngineLoad NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private void pollEngineCoolant() {
			String[] hexbytes = sendOBDCommand("01 05");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					int a = Integer.parseInt(hexbytes[2], 16);
					int value = a - 40;
					SendDataPoint(SensorNames.ENGINE_COOLANT, "degrees celcius", value,
							SenseDataTypes.INT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollEngineCoolant NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private void pollFuelPressure() {
			String[] hexbytes = sendOBDCommand("01 0A");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					int a = Integer.parseInt(hexbytes[2], 16);
					int value = a * 3;
					SendDataPoint(SensorNames.FUEL_PRESSURE, "kPa (gauge)", value,
							SenseDataTypes.INT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollFuelPressure NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2]);
				}
			}

		}

		private void pollIntakeManifoldPressure() {
			String[] hexbytes = sendOBDCommand("01 0B");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					int a = Integer.parseInt(hexbytes[2], 16);
					SendDataPoint(SensorNames.INTAKE_PRESSURE, "kPa (absolute)", a,
							SenseDataTypes.INT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollFuelPressure NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private void pollEngineRPM() {
			String[] hexbytes = sendOBDCommand("01 0C");
			if (hexbytes != null && hexbytes.length == 4) {
				try {
					float a = Integer.parseInt(hexbytes[2], 16);
					float b = Integer.parseInt(hexbytes[3], 16);
					float value = ((a * 256f) + b) / 4f;
					SendDataPoint(SensorNames.ENGINE_RPM, "rpm", value, SenseDataTypes.FLOAT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollEngineRPM NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2] + " " + hexbytes[3]);
				}
			}
		}

		private void pollVehicleSpeed() {
			String[] hexbytes = sendOBDCommand("01 0D");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					int value = Integer.parseInt(hexbytes[2], 16);
					SendDataPoint(SensorNames.VEHICLE_SPEED, "kmh", value, SenseDataTypes.INT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollVehicleSpeed NumberFormatException on : " + hexbytes[0] + " "
							+ hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private void pollIntakeAirTemperature() {
			String[] hexbytes = sendOBDCommand("01 0F");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					int a = Integer.parseInt(hexbytes[2], 16);
					int value = a - 40;
					SendDataPoint(SensorNames.INTAKE_TEMPERATURE, "degrees celcius", value,
							SenseDataTypes.INT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollIntakeAirTemperature NumberFormatException on : " + hexbytes[0]
							+ " " + hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private void pollThrottlePosition() {
			String[] hexbytes = sendOBDCommand("01 11");
			if (hexbytes != null && hexbytes.length == 3) {
				try {
					float a = Integer.parseInt(hexbytes[2], 16);
					float value = (a * 100) / 255;
					SendDataPoint(SensorNames.THROTTLE_POSITION, "percent", value,
							SenseDataTypes.FLOAT);
				} catch (NumberFormatException ex) {
					Log.e(TAG, "pollThrottlePosition NumberFormatException on : " + hexbytes[0]
							+ " " + hexbytes[1] + " " + hexbytes[2]);
				}
			}
		}

		private boolean SendDataPoint(String sensorName, String sensorDescription, Object value,
				String dataType) {
			// if necessary, register the sensor
			if (!verifiedsensors.contains(sensorName)) {
				if (registrator.checkSensor(sensorName, sensorName, dataType, sensorDescription,
						value.toString(), device.getName(), device.getAddress())) {
					verifiedsensors.add(sensorName);
				}
			}

			SensorDataPoint dataPoint = new SensorDataPoint(0);
			if (dataType.equals(SenseDataTypes.BOOL)) {
				dataPoint =  new  SensorDataPoint((Boolean) value);
			} else if (dataType.equals(SenseDataTypes.FLOAT)) {
				dataPoint =  new  SensorDataPoint((Float) value);
			} else if (dataType.equals(SenseDataTypes.INT)) {
				dataPoint =  new  SensorDataPoint((Integer) value);
			} else if (dataType.equals(SenseDataTypes.JSON)) {
				dataPoint =  new  SensorDataPoint((String) value);
			} else if (dataType.equals(SenseDataTypes.STRING)) {
				dataPoint =  new  SensorDataPoint((String) value);
			} else {
				dataPoint = null;
			}
			if(dataPoint != null)	    	
			{
				notifySubscribers();
				dataPoint.sensorName = sensorName;
				dataPoint.sensorDescription = sensorDescription;
				dataPoint.timeStamp = SNTP.getInstance().getTime();        
				sendToSubscribers(dataPoint);
			}
			// build the intent and send it to commonsense
			Intent intent = new Intent(context.getString(R.string.action_sense_new_data));
			intent.putExtra(DataPoint.SENSOR_NAME, sensorName);
			intent.putExtra(DataPoint.SENSOR_DESCRIPTION, sensorDescription);
			intent.putExtra(DataPoint.DATA_TYPE, dataType);
			intent.putExtra(DataPoint.DEVICE_UUID, device.getAddress());
			if (dataType.equals(SenseDataTypes.BOOL)) {
				intent.putExtra(DataPoint.VALUE, (Boolean) value);
			} else if (dataType.equals(SenseDataTypes.FLOAT)) {
				intent.putExtra(DataPoint.VALUE, (Float) value);
			} else if (dataType.equals(SenseDataTypes.INT)) {
				intent.putExtra(DataPoint.VALUE, (Integer) value);
			} else if (dataType.equals(SenseDataTypes.JSON)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else if (dataType.equals(SenseDataTypes.STRING)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else {
				Log.w(TAG, "Error sending data point: unexpected data type! '" + dataType + "'");
			}
			intent.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
			intent.setPackage(context.getPackageName());
			boolean itemsent = (context.startService(intent) != null);
			if (!itemsent)
				Log.e(TAG, "Sending of DataPoint: " + sensorName + " " + value + " "
						+ sensorDescription + "    FAILED !!!");
			return itemsent;
		}

		private class EmptyRegistrator extends SensorRegistrator {
			public EmptyRegistrator() {
				super(NewOBD2DeviceConnector.this.context);
			}

			@Override
			public boolean verifySensorIds(String deviceType, String deviceUuid) {
				Log.e(TAG,
						"DO NOT USE verifySensorIds, USE checkSensor methods seperately, because of dynamic implementation");
				return false;
			}
		}
	}
} public class ZephyrBioHarness extends BaseDataProducer {

	/*
	 * Scan thread
	 */
	private class BioHarnessConnectThread implements Runnable {

		private BroadcastReceiver btReceiver = new BroadcastReceiver() {

			@Override
			public void onReceive(Context context, Intent intent) {

				if (!bioHarnessEnabled) {
					return;
				}

				String action = intent.getAction();

				if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
					final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
							BluetoothAdapter.STATE_OFF);
					if (state == BluetoothAdapter.STATE_ON) {
						stop();
						connectHandler
						.post(bioHarnessConnectThread = new BioHarnessConnectThread());
						return;
					}
				}
			}
		};

		public BioHarnessConnectThread() {
			// send address
			try {
				btAdapter = BluetoothAdapter.getDefaultAdapter();
			} catch (Exception e) {
				Log.e(TAG, "Exception preparing Bluetooth scan thread:", e);
			}
		}

		@Override
		public void run() {
			if (bioHarnessEnabled) {
				streamEnabled = false;
				if (btAdapter.isEnabled()) {

					// check if there is a paired device with the name BioHarness
					Set<BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();

					// If there are paired devices
					boolean connected = false;
					if (pairedDevices.size() > 0) {
						// Loop through paired devices
						for (BluetoothDevice device : pairedDevices) {
							// Add the name and address to an array adapter to show in a ListView
							if (device.getName().startsWith("BH ZBH")
									&& device.getAddress().startsWith("00:07:80")) {
								// Log.v(TAG, "Connecting to BioHarness:" + device.getName());
								// Get a BluetoothSocket to connect with the given BluetoothDevice
								try {
									btSocket = device.createRfcommSocketToServiceRecord(serial_uid);
									btSocket.connect();

									final String deviceType = device.getName();
									final String deviceUuid = device.getAddress();

									// check sensor IDs
									new Thread(){public void run(){
										new ZephyrBioHarnessRegistrator(context).verifySensorIds(
												deviceType,deviceUuid);
									}}.start();

									processZBHMessage = new ProcessZephyrBioHarnessMessage(
											device.getName(), device.getAddress());
									updateHandler.post(updateThread = new UpdateThread());
									connected = true;
								} catch (Exception e) {
									Log.e(TAG,
											"Error connecting to BioHarness device: "
													+ e.getMessage());
								}
							}
						}
					}
					if (!connected) {
						// Log.v(TAG, "No Paired BioHarness device found. Sleeping for 10 seconds");
						connectHandler.postDelayed(
								bioHarnessConnectThread = new BioHarnessConnectThread(), 10000);
					}
				} else if (btAdapter.getState() == BluetoothAdapter.STATE_TURNING_ON) {
					// listen for the adapter state to change to STATE_ON
					context.registerReceiver(btReceiver, new IntentFilter(
							BluetoothAdapter.ACTION_STATE_CHANGED));
				} else {
					// ask user for permission to start bluetooth
					// Log.v(TAG, "Asking user to start bluetooth");
					Intent startBt = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
					startBt.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
					context.startActivity(startBt);

					// listen for the adapter state to change to STATE_ON
					context.registerReceiver(btReceiver, new IntentFilter(
							BluetoothAdapter.ACTION_STATE_CHANGED));
				}
			} else {
				stop();
			}
		}

		public void stop() {
			try {
				// Log.v(TAG, "Stopping the BioHarness service");
				updateHandler.removeCallbacks(updateThread);
				btSocket.close();

				context.unregisterReceiver(btReceiver);

			} catch (Exception e) {
				Log.e(TAG, "Error in stopping bioHarness service" + e.getMessage());
			}
		}

	}

	/*
	 * Process message class
	 */
	private class ProcessZephyrBioHarnessMessage {
		private String deviceType;
		private String deviceUuid;
		private SharedPreferences prefs = null;

		public ProcessZephyrBioHarnessMessage(String deviceType, String deviceUuid) {
			this.deviceType = deviceType;
			this.deviceUuid = deviceUuid;
			prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
		}

		public boolean processMessage(byte[] buffer) throws Exception {
			// received general data
			if (buffer[0] == 0x02 && buffer[1] == 0x20 && buffer[2] == 53) {
				// check that payload is not completely empty
				boolean hasPayload = false;
				for (int i = 12; i < 58; i++) {
					if (buffer[i] != 0) {
						hasPayload = true;
						break;
					}
				}
				if (false == hasPayload) {
					Log.w(TAG, "No sensor data payload received");
					// return true because the message type was correct, only the data is wrong
					return true;
				}

				// send acceleration data in m/s^2
				if (prefs.getBoolean(External.ZephyrBioHarness.ACC, true)) {
					float g = 9.80665f;
					JSONObject json = new JSONObject();
					Short xmin = (short) (buffer[32] | buffer[33] << 8);
					Short xmax = (short) (buffer[34] | buffer[35] << 8);
					float x = (xmin.floatValue() + xmax.floatValue()) / 200.0f * g;
					Short ymin = (short) (buffer[36] | buffer[37] << 8);
					Short ymax = (short) (buffer[38] | buffer[39] << 8);
					float y = (ymin.floatValue() + ymax.floatValue()) / 200.0f * g;
					Short zmin = (short) (buffer[40] | buffer[41] << 8);
					Short zmax = (short) (buffer[42] | buffer[43] << 8);
					float z = (zmin.floatValue() + zmax.floatValue()) / 200.0f * g;
					// Log.v(TAG, "x:" + x + " y:" + y + " z:" + z);

					json.put("x-axis", x);
					json.put("y-axis", y);
					json.put("z-axis", z);
					sendDataPoint(SensorNames.ACCELEROMETER, "BioHarness " + deviceType,
							json.toString(), SenseDataTypes.JSON);
				}

				// send heart rate
				if (prefs.getBoolean(External.ZephyrBioHarness.HEART_RATE, true)) {
					Short heartRate = (short) (buffer[12] | buffer[13] << 8);
					// if(heartRate < (short)0)
					// heartRate = (short)(heartRate+(short)255);

					// Log.v(TAG, "Heart rate:" + heartRate);
					sendDataPoint(SensorNames.HEART_RATE, "BioHarness " + deviceType,
							heartRate.intValue(), SenseDataTypes.INT);
				}

				// send respiration rate
				if (prefs.getBoolean(External.ZephyrBioHarness.RESP, true)) {
					Short respirationRate = (short) (buffer[14] | buffer[15] << 8);
					if (respirationRate < 0) {
						respirationRate = (short) (respirationRate + (short) 255);
					}
					float respirationRateF = (respirationRate.floatValue() / 10.0f);

					// Log.v(TAG, "Respiration rate:" + respirationRateF);
					sendDataPoint(SensorNames.RESPIRATION, "BioHarness " + deviceType,
							respirationRateF, SenseDataTypes.FLOAT);
				}

				// send skin temperature
				if (prefs.getBoolean(External.ZephyrBioHarness.TEMP, true)) {
					Short skinTemperature = (short) (buffer[16] | buffer[17] << 8);
					if (skinTemperature < 0) {
						skinTemperature = (short) (skinTemperature + (short) 255);
					}
					float skinTemperatureF = (skinTemperature.floatValue() / 10.0f);

					// Log.v(TAG, "Skin temperature:" + skinTemperatureF);
					sendDataPoint(SensorNames.TEMPERATURE, "BioHarness " + deviceType,
							skinTemperatureF, SenseDataTypes.FLOAT);
				}

				// send battery level
				if (prefs.getBoolean(External.ZephyrBioHarness.BATTERY, true)) {
					int batteryLevel = buffer[54];
					// Log.v(TAG, "Battery level:" + batteryLevel);
					sendDataPoint(SensorNames.BATTERY_LEVEL, "BioHarness " + deviceType,
							batteryLevel, SenseDataTypes.INT);
				}

				// send worn status
				if (prefs.getBoolean(External.ZephyrBioHarness.WORN_STATUS, true)) {
					boolean wornStatusB = (buffer[55] & 0x10000000) == 0x10000000;

					// Log.v(TAG, "Worn status:" + wornStatusB);
					sendDataPoint(SensorNames.WORN_STATUS, "BioHarness " + deviceType, wornStatusB,
							SenseDataTypes.BOOL);
				}

				return true;

			} else {
				return false;
			}
		}

		private void sendDataPoint(String sensorName, String description, Object value, String dataType) 
		{
			try
			{
				SensorDataPoint dataPoint = new SensorDataPoint(0);
				if (dataType.equals(SenseDataTypes.BOOL)) {
					dataPoint =  new  SensorDataPoint((Boolean) value);
				} else if (dataType.equals(SenseDataTypes.FLOAT)) {
					dataPoint =  new  SensorDataPoint((Float) value);
				} else if (dataType.equals(SenseDataTypes.INT)) {
					dataPoint =  new  SensorDataPoint((Integer) value);
				} else if (dataType.equals(SenseDataTypes.JSON)) {
					dataPoint =  new  SensorDataPoint(new JSONObject((String)value));
				} else if (dataType.equals(SenseDataTypes.STRING)) {
					dataPoint =  new  SensorDataPoint((String) value);
				} else {
					dataPoint = null;
				}
				if(dataPoint != null)	    	
				{
					notifySubscribers();
					dataPoint.sensorName = sensorName;
					dataPoint.sensorDescription = description;
					dataPoint.timeStamp = SNTP.getInstance().getTime();        
					sendToSubscribers(dataPoint);
				}
			}catch(Exception e)
			{
				Log.e(TAG, "Error sending data point to subscribers of the ZephyrBioHarness");
			}
			Intent intent = new Intent(context.getString(R.string.action_sense_new_data));
			intent.putExtra(DataPoint.SENSOR_NAME, sensorName);
			intent.putExtra(DataPoint.SENSOR_DESCRIPTION, description);
			intent.putExtra(DataPoint.DATA_TYPE, dataType);
			intent.putExtra(DataPoint.DEVICE_UUID, deviceUuid);
			if (dataType.equals(SenseDataTypes.BOOL)) {
				intent.putExtra(DataPoint.VALUE, (Boolean) value);
			} else if (dataType.equals(SenseDataTypes.FLOAT)) {
				intent.putExtra(DataPoint.VALUE, (Float) value);
			} else if (dataType.equals(SenseDataTypes.INT)) {
				intent.putExtra(DataPoint.VALUE, (Integer) value);
			} else if (dataType.equals(SenseDataTypes.JSON)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else if (dataType.equals(SenseDataTypes.STRING)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else {
				Log.w(TAG, "Error sending data point: unexpected data type! '" + dataType + "'");
			}
			intent.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
			intent.setPackage(context.getPackageName());
			context.startService(intent);
		}
	}

	/*
	 * Update thread
	 */
	private class UpdateThread extends Thread {

		private InputStream mmInStream;
		private OutputStream mmOutStream;

		public UpdateThread() {
			if (mmInStream == null || mmOutStream == null) {
				InputStream tmpIn = null;
				OutputStream tmpOut = null;

				// Get the input and output streams, using temp objects because member streams are
				// final
				try {
					tmpIn = btSocket.getInputStream();
					tmpOut = btSocket.getOutputStream();
				} catch (Exception e) {
					Log.e(TAG, "Error in update thread constructor:" + e.getMessage());
				}
				mmInStream = tmpIn;
				mmOutStream = tmpOut;
			}
		}

		/* Call this from the main Activity to shutdown the connection */
		public void cancel() {
			try {
				Log.i(TAG, "Stopping the BioHarness service");
				setEnableGeneralData(false);
				btSocket.close();
			} catch (Exception e) {
				Log.e(TAG, e.getMessage());
			}
		}

		@Override
		public void run() {
			// Keep listening to the InputStream until an exception occurs
			boolean readMessage = false;
			if (bioHarnessEnabled) {
				try {
					if (!streamEnabled) {
						streamEnabled = setEnableGeneralData(true);
						updateHandler.post(updateThread = new UpdateThread());
						return;
					}
					// check connection
					if (!sendConnectionAlive()) {
						connectHandler.postDelayed(
								bioHarnessConnectThread = new BioHarnessConnectThread(), 1000);
						return;
					}

					if (SNTP.getInstance().getTime() > lastSampleTime + updateInterval) {
						while (!readMessage) {
							lastSampleTime = SNTP.getInstance().getTime();
							// Read from the InputStream
							byte[] buffer = new byte[80];
							int bytes; // bytes returned from read()
							// check connection
							if (!sendConnectionAlive()) {
								connectHandler.postDelayed(
										bioHarnessConnectThread = new BioHarnessConnectThread(),
										1000);
								return;
							}
							bytes = mmInStream.read(buffer);
							if (bytes > 0) {
								readMessage = processZBHMessage.processMessage(buffer);
							}
							buffer = null;
						}
					}
					// update every second
					updateHandler.postDelayed(updateThread = new UpdateThread(), 1000);
				} catch (Exception e) {
					Log.e(TAG, "Error in receiving BioHarness data:" + e.getMessage());
					e.printStackTrace();
					// re-connect
					connectHandler.postDelayed(
							bioHarnessConnectThread = new BioHarnessConnectThread(), 1000);
				}
			} else {
				cancel();
			}
		}

		public boolean sendConnectionAlive() {
			try {
				byte[] writeBuffer = new byte[128];

				writeBuffer[0] = 0x02; // STC
				writeBuffer[1] = 0x23; // MSG_ID // alive
				writeBuffer[2] = 0; // DLC
				writeBuffer[3] = 0; // Payload
				writeBuffer[4] = 0; // CRC
				writeBuffer[5] = 0x03; // ETX
				return write(writeBuffer);
			} catch (Exception e) {
				Log.e(TAG, "Error in setEnableGeneralData:" + e.getMessage());
				return false;
			}
		}

		public boolean setEnableGeneralData(boolean enable) {
			try {
				if (enable) {
					// Log.v(TAG, "Enabling general data...");
				} else {
					// Log.v(TAG, "Disabling general data...");
				}
				byte[] buffer = new byte[128];
				byte[] writeBuffer = new byte[128];
				byte[] data = new byte[1];
				if (enable) {
					data[0] = (byte) 1;
				} else {
					data[0] = (byte) 0;
				}

				writeBuffer[0] = 0x02; // STC
				writeBuffer[1] = 0x14; // MSG_ID // general packet enable
				writeBuffer[2] = 1; // DLC
				writeBuffer[3] = data[0];
				writeBuffer[4] = CRC8.checksum2(data); // CRC
				writeBuffer[5] = 0x03; // ETX
				write(writeBuffer);
				int nrBytes = mmInStream.read(buffer);
				// Log.v(TAG,
				// "Enable received:" + Integer.toString(buffer[0] & 0xFF, 16) + ","
				// + Integer.toString(buffer[1] & 0xFF, 16) + ","
				// + Integer.toString(buffer[2] & 0xFF, 16));
				if (nrBytes > 0 && buffer[1] == 0x14 && buffer[4] == 0x06) // ACK
				{
					if (enable) {
						// Log.v(TAG, "General data enabled");
					} else {
						// Log.v(TAG, "General data disabled");
					}
					return true;
				} else {
					return false;
				}
			} catch (Exception e) {
				Log.e(TAG, "Error in setEnableGeneralData:" + e.getMessage());
				return false;
			}
		}

		/* Call this from the main Activity to send data to the remote device */
		public boolean write(byte[] bytes) {
			try {
				mmOutStream.write(bytes);
			} catch (Exception e) {
				Log.e(TAG, "Error in write:" + e.getMessage());
				return false;
			}
			return true;
		}
	}

	private static final String TAG = "Zephyr BioHarness";
	private BluetoothAdapter btAdapter;
	private final Context context;
	private boolean bioHarnessEnabled = false;
	private UUID serial_uid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
	private final Handler connectHandler = new Handler(Looper.getMainLooper());
	private Handler updateHandler = new Handler(Looper.getMainLooper());
	private int updateInterval = 0;
	private UpdateThread updateThread = null;
	private long lastSampleTime = 0;
	private BioHarnessConnectThread bioHarnessConnectThread = null;
	private boolean streamEnabled = false;
	private BluetoothSocket btSocket = null;
	private ProcessZephyrBioHarnessMessage processZBHMessage = null;

	public ZephyrBioHarness(Context context) {
		this.context = context;
	}

	public int getUpdateInterval() {
		return updateInterval;
	}

	public void setUpdateInterval(int scanInterval) {
		updateInterval = scanInterval;
	}

	public void startBioHarness(int interval) {
		updateInterval = interval;
		bioHarnessEnabled = true;

		Thread t = new Thread() {

			@Override
			public void run() {

				connectHandler.post(bioHarnessConnectThread = new BioHarnessConnectThread());
			}
		};
		connectHandler.post(t);
	}

	public void stopBioHarness() {
		bioHarnessEnabled = false;
		try {
			if (bioHarnessConnectThread != null) {
				bioHarnessConnectThread.stop();
				connectHandler.removeCallbacks(bioHarnessConnectThread);
			}

		} catch (Exception e) {
			Log.e(TAG, "Exception in stopping Bluetooth scan thread:", e);
		}
	}
} public class ZephyrHxM extends BaseDataProducer {

	/*
	 * Scan thread
	 */
	private class HxMConnectThread implements Runnable {

		private BroadcastReceiver btReceiver = new BroadcastReceiver() {

			@Override
			public void onReceive(Context context, Intent intent) {

				if (!hxmEnabled) {
					return;
				}

				String action = intent.getAction();

				if (BluetoothAdapter.ACTION_STATE_CHANGED.equals(action)) {
					Log.v(TAG, "Bluetooth state changed");

					final int state = intent.getIntExtra(BluetoothAdapter.EXTRA_STATE,
							BluetoothAdapter.STATE_OFF);
					if (state == BluetoothAdapter.STATE_ON) {
						stop();
						connectHandler.post(hxmConnectThread = new HxMConnectThread());
						return;
					}
				}
			}
		};

		public HxMConnectThread() {
			// send address
			try {
				btAdapter = BluetoothAdapter.getDefaultAdapter();
			} catch (Exception e) {
				Log.e(TAG, "Exception preparing Bluetooth scan thread:", e);
			}
		}

		@Override
		public void run() {

			// check is sensor is enabled
			if (!hxmEnabled) {
				// sensor is not enabled, skip update thread
				stop();
				return;
			}

			streamEnabled = false;
			if (btAdapter.isEnabled()) {

				// check if there is a paired device with the name HxM
				Set<android.bluetooth.BluetoothDevice> pairedDevices = btAdapter.getBondedDevices();
				// If there are paired devices
				boolean connected = false;
				if (pairedDevices != null) {
					// Loop through paired devices
					for (BluetoothDevice device : pairedDevices) {
						Log.v(TAG, "Found paired device '" + device.getName() + "'");

						// Add the name and address to an array adapter to show in a ListView
						if (device.getName().startsWith("HXM")
								&& device.getAddress().startsWith("00:07:80")) {

							// Get a BluetoothSocket to connect with the BluetoothDevice
							Log.v(TAG, "Connecting to device: " + device.getName());
							try {
								btSocket = device.createRfcommSocketToServiceRecord(serial_uid);
								btSocket.connect();

								processZHxMMessage = new ProcessZephyrHxMMessage(device.getName(),
										device.getAddress());
								updateHandler.post(updateThread = new UpdateThread());

								final String deviceType = device.getName();
								final String deviceUuid = device.getAddress();

								// check sensor IDs
								new Thread(){public void run(){
									new ZephyrHxmRegistrator(context).verifySensorIds(
											deviceType,deviceUuid);
								}}.start();

								connected = true;

							} catch (Exception e) {
								connectionErrorCount++;
								if (connectionErrorCount > 30) { // set to 5 minutes => 30
									sendNotification("Error connecting to Zephyr HxM module, please enable the device.");
									connectionErrorCount = 0;
								}
								Log.e(TAG, "Error in connecting to HxM device!", e);
							}
						}
					}
				}

				if (!connected) {
					Log.v(TAG, "Failed to connect to HxM device. Sleeping for 10 seconds");
					connectHandler.postDelayed(hxmConnectThread = new HxMConnectThread(), 10000);
				}

			} else if (btAdapter.getState() == BluetoothAdapter.STATE_TURNING_ON) {
				// listen for the adapter state to change to STATE_ON
				Log.v(TAG, "Bluetooth is not enabled! Wait for it to change states...");
				context.registerReceiver(btReceiver, new IntentFilter(
						BluetoothAdapter.ACTION_STATE_CHANGED));
			} else {
				// ask user for permission to start Bluetooth
				Log.v(TAG, "Bluetooth is not enabled! Asking user to start Bluetooth...");
				Intent startBt = new Intent(BluetoothAdapter.ACTION_REQUEST_ENABLE);
				startBt.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
				context.startActivity(startBt);

				// listen for the adapter state to change to STATE_ON
				context.registerReceiver(btReceiver, new IntentFilter(
						BluetoothAdapter.ACTION_STATE_CHANGED));
			}
		}

		public void stop() {
			try {
				Log.i(TAG, "Stopping the HxM service");
				updateHandler.removeCallbacks(updateThread);
				btSocket.close();

				context.unregisterReceiver(btReceiver);

			} catch (Exception e) {
				Log.e(TAG, "Error in stopping the HxM service:" + e.getMessage());
			}
		}

	}

	/*
	 * Process message class
	 */
	private class ProcessZephyrHxMMessage {
		private String deviceType;
		private String deviceUuid;
		private SharedPreferences prefs = null;
		private Vector<Byte> bufferBuffer;
		private int gotStart = -1;
		int msgSize = 60;

		public ProcessZephyrHxMMessage(String deviceType, String deviceUuid) {
			bufferBuffer = new Vector<Byte>();
			this.deviceType = deviceType;
			this.deviceUuid = deviceUuid;
			prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
		}

		public byte[] getMessage(byte[] buffer, boolean useBuffer) {
			byte[] msgBuffer = new byte[msgSize];
			// check if we have the start from the previous run
			if (gotStart > -1 && useBuffer) {
				for (int i = 0; i < msgSize; i++) {
					if (gotStart + i < bufferBuffer.size()) {
						msgBuffer[i] = bufferBuffer.elementAt(gotStart + i);
					} else {
						if (gotStart + i - bufferBuffer.size() < buffer.length) {
							msgBuffer[i] = buffer[gotStart + i - bufferBuffer.size()];
						} else {
							// msg to short store and bailout
							bufferBuffer.clear();
							for (byte element : msgBuffer) {
								bufferBuffer.add(element);
								gotStart = 0;
							}
							return new byte[0];
						}
					}
				}
				// check for the remaining part
				int rest = msgSize - (bufferBuffer.size() - gotStart);
				if (rest < buffer.length) {
					bufferBuffer.clear();
					gotStart = -1;
					for (int i = rest; i < buffer.length; i++) {
						bufferBuffer.add(buffer[i]);
						if (i > 1 && buffer[i - 2] == 0x02 && buffer[i - 1] == 0x26
								&& buffer[i] == 55 && gotStart < 0) {
							gotStart = bufferBuffer.size() - 3;
						}
					}
				}
				if (msgBuffer.length == 60) {
					return msgBuffer;
				} else {
					return new byte[0];
				}
			} else {
				if (!useBuffer) {
					bufferBuffer.clear();
				}

				// combine old buffer with new one
				for (byte element : buffer) {
					bufferBuffer.add(element);
				}
				// find the start in the current buffer
				for (int i = 0; i < bufferBuffer.size(); i++) {
					// got the start copy the rest
					if (i + 2 < bufferBuffer.size() && bufferBuffer.get(i) == 0x02
							&& bufferBuffer.get(i + 1) == 0x26 && bufferBuffer.get(i + 2) == 55) {
						// can it hold a message
						if (bufferBuffer.size() - i >= 60) {
							Vector<Byte> tempBuffer = new Vector<Byte>();
							for (int j = 0; j + i < bufferBuffer.size(); j++) {
								if (j < 60) {
									msgBuffer[j] = bufferBuffer.get(i + j);
								} else { // copy the remaining part in the temp buffer
									if (i + j + 2 < bufferBuffer.size()
											&& bufferBuffer.get(i + j) == 0x02
											&& bufferBuffer.get(i + j + 1) == 0x26
											&& bufferBuffer.get(i + j + 2) == 55 && gotStart < 0) {
										gotStart = 60 - j;
									}
									tempBuffer.add(bufferBuffer.get(i + j));
								}
							}
							// copy the tempbuffer in the main buffer
							bufferBuffer.clear();
							for (int j = 0; j < tempBuffer.size(); j++) {
								bufferBuffer.addElement(tempBuffer.get(j));
							}
							return msgBuffer;
						} else {
							return new byte[0];
						}
					}
				}
			}
			return new byte[0];
		}

		public boolean processMessage(byte[] inputBuffer, boolean useBuffer) {
			try {
				byte[] buffer = getMessage(inputBuffer, useBuffer);

				if (buffer.length == 0) {
					return false;
				}
				// received general data
				if (buffer[0] == 0x02 && buffer[1] == 0x26 && buffer[2] == 55) {
					// found start of message

					// send heart rate
					if (prefs.getBoolean(External.ZephyrHxM.HEART_RATE, true)) {
						int heartRate = Byte.valueOf(buffer[12]).intValue();
						if (heartRate < 0) {
							heartRate = heartRate + 255;
						}

						// Log.v(TAG, "Heart rate:" + heartRate);
						sendDataPoint(SensorNames.HEART_RATE, "HxM " + deviceType, heartRate,
								SenseDataTypes.INT);

					}
					// send speed
					if (prefs.getBoolean(External.ZephyrHxM.SPEED, true)) {
						int speed = buffer[52] | buffer[53] << 8;
						float speedF = speed / 256f;

						// Log.v(TAG, "Speed:" + speedF);
						sendDataPoint(SensorNames.SPEED, "HxM " + deviceType, speedF,
								SenseDataTypes.FLOAT);
					}
					// send distance
					if (prefs.getBoolean(External.ZephyrHxM.DISTANCE, true)) {
						int distance = buffer[50] | buffer[51] << 8;
						float distanceF = distance / 16F;

						// Log.v(TAG, "Distance:" + distanceF);
						sendDataPoint(SensorNames.DISTANCE, "HxM " + deviceType, distanceF,
								SenseDataTypes.FLOAT);
					}
					// send battery charge
					if (prefs.getBoolean(External.ZephyrHxM.BATTERY, true)) {
						int battery = Byte.valueOf(buffer[11]).intValue();

						// Log.v(TAG, "Battery charge:" + battery.intValue());
						sendDataPoint(SensorNames.BATTERY_CHARGE, "HxM " + deviceType, battery,
								SenseDataTypes.INT);

						if (notifyOnEmptyBattery && battery < 5 && battery != 0
								&& SNTP.getInstance().getTime() - lastEmptyBatteryNotify > 300000) {
							sendNotification("Zephyr HxM empty battery warning: " + battery + "%");
							lastEmptyBatteryNotify = SNTP.getInstance().getTime();
						}
						// every 5 min
						if (battery == 0
								&& SNTP.getInstance().getTime() - lastNoChestConnectionNofify > 300000) {
							sendNotification("Zephyr HxM not properly connected to the chest, make sure the strap is wet enough.");
							lastNoChestConnectionNofify = SNTP.getInstance().getTime();
						}
					}
					// send strides count
					if (prefs.getBoolean(External.ZephyrHxM.STRIDES, true)) {
						int strides = buffer[54];
						strides = strides < 0 ? strides + 256 : strides;
						// Short strides = (short)buffer[54];

						// Log.v(TAG, "Battery charge:" + battery.intValue());
						sendDataPoint(SensorNames.STRIDES, "HxM " + deviceType, strides,
								SenseDataTypes.INT);
					}
					return true;

				} else {
					// find the rest of the data
					Log.w(TAG,
							"Unexpected first three bytes: { "
									+ Integer.toHexString(inputBuffer[0] & 0xFF) + ", "
									+ Integer.toHexString(inputBuffer[1] & 0xFF) + ", "
									+ Integer.toHexString(inputBuffer[2] & 0xFF) + " }");

					// String totalBuffer = "";
					// for (byte b : inputBuffer) {
					// String s = Integer.toHexString(b & 0xFF);
					// if (s.length() == 1) {
					// s = "0" + s;
					// }
					// totalBuffer += s + ", ";
					// }
					// Log.d(TAG, "Total buffer content:\n" + totalBuffer);

				}
				if (bufferBuffer.size() >= msgSize) {
					return processMessage(new byte[0], true);
				}

			} catch (Exception e) {
				Log.e(TAG, "Error in ProcessZephyrHxMMessage:" + e.getMessage());
				return false;
			}
			return false;
		}

		private void sendDataPoint(String sensorName, String description, Object value,
				String dataType) {
			try {
				SensorDataPoint dataPoint = new SensorDataPoint(0);
				if (dataType.equals(SenseDataTypes.BOOL)) {
					dataPoint = new SensorDataPoint((Boolean) value);
				} else if (dataType.equals(SenseDataTypes.FLOAT)) {
					dataPoint = new SensorDataPoint((Float) value);
				} else if (dataType.equals(SenseDataTypes.INT)) {
					dataPoint = new SensorDataPoint((Integer) value);
				} else if (dataType.equals(SenseDataTypes.JSON)) {
					dataPoint = new SensorDataPoint(new JSONObject((String) value));
				} else if (dataType.equals(SenseDataTypes.STRING)) {
					dataPoint = new SensorDataPoint((String) value);
				} else {
					dataPoint = null;
				}
				if (dataPoint != null) {
					notifySubscribers();
					dataPoint.sensorName = sensorName;
					dataPoint.sensorDescription = description;
					dataPoint.timeStamp = SNTP.getInstance().getTime();
					sendToSubscribers(dataPoint);
				}
			} catch (Exception e) {
				Log.e(TAG, "Error sending data point to subscribers of the ZephyrBioHarness");
			}

			Intent intent = new Intent(context.getString(R.string.action_sense_new_data));
			intent.putExtra(DataPoint.SENSOR_NAME, sensorName);
			intent.putExtra(DataPoint.SENSOR_DESCRIPTION, description);
			intent.putExtra(DataPoint.DATA_TYPE, dataType);
			intent.putExtra(DataPoint.DEVICE_UUID, deviceUuid);
			if (dataType.equals(SenseDataTypes.BOOL)) {
				intent.putExtra(DataPoint.VALUE, (Boolean) value);
			} else if (dataType.equals(SenseDataTypes.FLOAT)) {
				intent.putExtra(DataPoint.VALUE, (Float) value);
			} else if (dataType.equals(SenseDataTypes.INT)) {
				intent.putExtra(DataPoint.VALUE, (Integer) value);
			} else if (dataType.equals(SenseDataTypes.JSON)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else if (dataType.equals(SenseDataTypes.STRING)) {
				intent.putExtra(DataPoint.VALUE, (String) value);
			} else {
				Log.w(TAG, "Error sending data point: unexpected data type! '" + dataType + "'");
			}
			intent.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
			intent.setPackage(context.getPackageName());
			context.startService(intent);
		}
	}

	/*
	 * Update thread
	 */
	private class UpdateThread extends Thread {

		private InputStream mmInStream;
		private OutputStream mmOutStream;

		public UpdateThread() {

			if (mmInStream == null || mmOutStream == null) {
				try {
					mmInStream = btSocket.getInputStream();
					mmOutStream = btSocket.getOutputStream();
				} catch (Exception e) {
					Log.e(TAG, "Error in update thread constructor:" + e.getMessage());
				}
			}
		}

		/* Call this from the main Activity to shutdown the connection */
		public void cancel() {
			try {
				Log.i(TAG, "Stopping the HxM service");
				processZHxMMessage = null;

				btSocket.close();
				btSocket = null;

			} catch (Exception e) {
				Log.e(TAG, "Error in stopping the servicve:" + e.getMessage());
			}
		}

		@Override
		public void run() {
			// Keep listening to the InputStream until an exception occurs
			if (hxmEnabled) {
				try {
					boolean firstRun = true;
					boolean done = false;
					while (!done) {
						Log.v(TAG, "Try to read from Bluetooth input stream...");
						lastSampleTime = SNTP.getInstance().getTime();
						// Read from the InputStream
						// the msg size is 60
						byte[] buffer = new byte[60];
						int bytes; // bytes returned from read()
						bytes = mmInStream.read(buffer);
						if (bytes > 0) {
							// Log.d(TAG, "Read " + bytes + " bytes from Bluetooth input stream");

							// copy the buffer
							byte[] newBuffer = new byte[bytes];
							for (int i = 0; i < bytes; i++) {
								// int byteNr = buffer[i];
								// byteNr = byteNr < 0 ? byteNr+256:byteNr;
								// Log.d(TAG, ""+ byteNr);
								newBuffer[i] = buffer[i];
							}

							done = processZHxMMessage.processMessage(newBuffer, !firstRun);
							firstRun = false;
						} else {
							// no bytes read from Bluetooth input stream
						}
						buffer = null;
					}

					updateHandler.postDelayed(updateThread = new UpdateThread(), updateInterval);

				} catch (Exception e) {
					Log.e(TAG, "Error in receiving HxM data: ", e);

					// re-connect
					processZHxMMessage = null;
					connectHandler.post(hxmConnectThread = new HxMConnectThread());
					btSocket = null;
				}
			} else {
				Log.v(TAG, "HxM not enabled. Cancelling update thread");
				cancel();
			}
		}
	}

	private static final String TAG = "Zephyr HxM";

	private BluetoothAdapter btAdapter;
	private final Context context;
	private boolean hxmEnabled = false;
	private UUID serial_uid = UUID.fromString("00001101-0000-1000-8000-00805f9b34fb");
	private HandlerThread connectHT = null;
	private HandlerThread updateHT = null;
	private Handler connectHandler = null;// new Handler(Looper.getMainLooper());
	private Handler updateHandler = null; // new Handler(Looper.getMainLooper());
	private int updateInterval = 0;
	private UpdateThread updateThread = null;
	@SuppressWarnings("unused")
	private long lastSampleTime = 0;
	private HxMConnectThread hxmConnectThread = null;
	@SuppressWarnings("unused")
	private boolean streamEnabled = false; // TODO use this
	private BluetoothSocket btSocket = null;
	private ProcessZephyrHxMMessage processZHxMMessage = null;
	@SuppressWarnings("unused")
	private boolean notifyOnNoConnection = true;
	private boolean notifyOnEmptyBattery = true;
	private int connectionErrorCount = 0;
	private long lastEmptyBatteryNotify = 0;
	private long lastNoChestConnectionNofify = 0;

	public ZephyrHxM(Context context) {
		this.context = context;
	}

	public int getUpdateInterval() {
		return updateInterval;
	}

	private void sendNotification(String text) {

		NotificationCompat.Builder builder = new NotificationCompat.Builder(context);
		builder.setSmallIcon(R.drawable.ic_stat_sense_warning);
		builder.setContentTitle(context.getString(R.string.stat_notify_title));
		builder.setTicker(text);
		builder.setWhen(System.currentTimeMillis());

		// set flags
		builder.setOnlyAlertOnce(true);
		builder.setDefaults(Notification.DEFAULT_ALL);
		builder.setAutoCancel(true);

		// start sense when notification is pressed
		final Intent notifIntent = new Intent(context.getString(R.string.action_sense_app));
		notifIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
		PendingIntent contentIntent = PendingIntent.getActivity(context, 0, notifIntent, 0);
		builder.setContentIntent(contentIntent);

		NotificationManager mgr = (NotificationManager) context
				.getSystemService(Context.NOTIFICATION_SERVICE);
		mgr.notify(2, builder.build());
	}

	public void setUpdateInterval(int updateInterval) {
		this.updateInterval = updateInterval;
	}

	public void startHxM(int interval) {
		Log.v(TAG, "Start HxM...");

		updateInterval = interval;
		hxmEnabled = true;
		// create the handler threads
		connectHT = new HandlerThread("Hxm Connect Handler Thread");
		updateHT = new HandlerThread("Hxm Update Handler Thread");
		connectHT.start();
		updateHT.start();

		// get the looper
		connectHandler = new Handler(connectHT.getLooper());
		updateHandler = new Handler(updateHT.getLooper());

		Thread t = new Thread() {

			@Override
			public void run() {
				connectHandler.post(hxmConnectThread = new HxMConnectThread());
			}
		};
		connectHandler.post(t);
	}

	public void stopHxM() {
		Log.v(TAG, "Stop HxM...");

		hxmEnabled = false;
		try {
			if (hxmConnectThread != null) {
				hxmConnectThread.stop();
				connectHandler.removeCallbacks(hxmConnectThread);
			}
			connectHT.getLooper().quit();
			updateHT.getLooper().quit();
		} catch (Exception e) {
			Log.e(TAG, "Exception in stopping Bluetooth scan thread:", e);
		}
	}
} public class FusedLocationSensor extends BaseSensor implements PeriodicPollingSensor, com.google.android.gms.location.LocationListener, ConnectionCallbacks, OnConnectionFailedListener {

    private static final String TAG = "Sense FusedLocationSensor";
    private static final String DISTANCE_ALARM_ACTION = "nl.sense_os.service.LocationAlarm.distanceAlarm";
    private static final int DISTANCE_ALARM_ID = 70;
    private static FusedLocationSensor instance = null;
    private Context context;
    private GoogleApiClient googleApiClient;
    private boolean active;
    private TraveledDistanceEstimator distanceEstimator;
    private PeriodicPollAlarmReceiver pollAlarmReceiver;

    private static final long BALANCED_FASTEST_INTERVAL_MILISECONDS = 1*60*1000;
    private static final long BALANCED_INTERVAL_MILISECONDS         = 5*60*1000;

    private static final long HIGH_ACCURACY_FASTEST_INTERVAL_MILISECONDS = 1*1000;
    private static final long HIGH_ACCURACY_INTERVAL_MILISECONDS         = 5*1000;




    /**
     * Constructor.
     * 
     * @param context an Android Context object
     * @param googleApiClient a connected GoogleApiClient
     */
    protected FusedLocationSensor(Context context) {
        this.context = context;
        pollAlarmReceiver = new PeriodicPollAlarmReceiver(this);
        distanceEstimator = new TraveledDistanceEstimator();
        initGoogleApiClient();
    }

    private void initGoogleApiClient()
    {
        googleApiClient = new GoogleApiClient.Builder(context)
        .addConnectionCallbacks(this)
        .addOnConnectionFailedListener(this)
        .addApi(LocationServices.API)
        .build();
    }

    @Override
    public void onConnectionFailed(ConnectionResult arg0)
    {
        Log.e(TAG, "Failed to connect to Google Api"+ arg0.toString());
    }

    @Override
    public void onConnected(Bundle arg0)
    {
        // if active start the location updates
        if(active)
            LocationServices.FusedLocationApi.requestLocationUpdates(googleApiClient, createLocationRequest(), this);
    }

    @Override
    public void onConnectionSuspended(int arg0)
    {
        Log.e(TAG, "Connection to Google Api is suspended");
    }


    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static FusedLocationSensor getInstance(Context context) {
        if (instance == null) {
            instance = new FusedLocationSensor(context);
        }
        return instance;
    }

    /**
     * Set the location request
     * The interval of the location updates are set to the sample rate.<br>
     * There is no limit to the fastest interval at which the sensor can receive location updates.<br>
     * The priority (battery vs accuracy) is selected based on the main location preferences:<br>
     * SensePrefs.Main.Location.FUSED_PROVIDER_ACCURATE,<br>
     * SensePrefs.Main.Location.FUSED_PROVIDER_BALANCED,<br>
     * SensePrefs.Main.Location.FUSED_PROVIDER_LOW_POWER.
     * @return The location Request object based on the current settings for requesting location updates.
     */
    protected LocationRequest createLocationRequest() {
        LocationRequest locationRequest = new LocationRequest();
        locationRequest.setInterval(getSampleRate());
        SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
        String priority = mainPrefs.getString(SensePrefs.Main.Location.FUSED_PROVIDER_PRIORITY, SensePrefs.Main.Location.FusedProviderPriority.BALANCED);
        if(priority.equals(SensePrefs.Main.Location.FusedProviderPriority.LOW_POWER))
            locationRequest.setPriority(LocationRequest.PRIORITY_LOW_POWER);
        else if(priority.equals(SensePrefs.Main.Location.FusedProviderPriority.ACCURATE)){
            locationRequest.setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY);
            locationRequest.setFastestInterval(HIGH_ACCURACY_FASTEST_INTERVAL_MILISECONDS);
            locationRequest.setInterval(HIGH_ACCURACY_INTERVAL_MILISECONDS);
        }
        else{
            locationRequest.setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY);
            locationRequest.setFastestInterval(BALANCED_FASTEST_INTERVAL_MILISECONDS);
            locationRequest.setInterval(BALANCED_INTERVAL_MILISECONDS);
        }
        return locationRequest;
    }

    @Override
    public void onLocationChanged(Location fix)
    {
        JSONObject json = new JSONObject();
        try {
            json.put("latitude", fix.getLatitude());
            json.put("longitude", fix.getLongitude());

            // always include all JSON fields, or we get problems with varying data_structure
            json.put("accuracy", fix.hasAccuracy() ? fix.getAccuracy() : -1.0d);
            json.put("altitude", fix.hasAltitude() ? fix.getAltitude() : -1.0);
            json.put("speed", fix.hasSpeed() ? fix.getSpeed() : -1.0d);
            json.put("bearing", fix.hasBearing() ? fix.getBearing() : -1.0d);
            json.put("provider", null != fix.getProvider() ? fix.getProvider() : "unknown");

        } catch (JSONException e) {
            Log.e(TAG, "JSONException in onLocationChanged", e);
            return;
        }

        // use SNTP time
        long timestamp = SNTP.getInstance().getTime();
        notifySubscribers();
        SensorDataPoint dataPoint = new SensorDataPoint(json);
        dataPoint.sensorName = SensorNames.LOCATION;
        dataPoint.sensorDescription = SensorNames.LOCATION;
        dataPoint.timeStamp = timestamp;
        sendToSubscribers(dataPoint);

        // pass message to the MsgHandler
        Intent i = new Intent(context.getString(R.string.action_sense_new_data));
        i.putExtra(DataPoint.SENSOR_NAME, SensorNames.LOCATION);
        i.putExtra(DataPoint.VALUE, json.toString());
        i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
        i.putExtra(DataPoint.TIMESTAMP, timestamp);
        i.setPackage(context.getPackageName());
        context.startService(i);
        distanceEstimator.addPoint(fix);
    }

    /**
     * Receiver for periodic alarms to calculate distance values.
     */
    public final BroadcastReceiver distanceAlarmReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            double distance = distanceEstimator.getTraveledDistance();

            notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(distance);
            dataPoint.sensorName = SensorNames.TRAVELED_DISTANCE_1H;
            dataPoint.sensorDescription = SensorNames.TRAVELED_DISTANCE_1H;
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            sendToSubscribers(dataPoint);

            // pass message to the MsgHandler
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.SENSOR_NAME, SensorNames.TRAVELED_DISTANCE_1H);
            i.putExtra(DataPoint.VALUE, (float) distance);
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(context.getPackageName());
            context.startService(i);

            // start counting again, from the last location
            distanceEstimator.reset();
        }
    };

    @Override
    public void doSample() {
        // check if there is still a connection to the Google Play Services
        if(active && googleApiClient != null && !googleApiClient.isConnected() && !googleApiClient.isConnecting())
            googleApiClient.connect();
    }

    @Override
    public boolean isActive() {
        return active;
    }

    @Override
    public void setSampleRate(long sampleDelay) {
        super.setSampleRate(sampleDelay);
        stopPolling();
        startPolling();
    }

    private void startAlarms() {
        // register to receive the alarm
        context.getApplicationContext().registerReceiver(distanceAlarmReceiver, new IntentFilter(DISTANCE_ALARM_ACTION));

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        // start periodic for distance
        Intent distanceAlarm = new Intent(DISTANCE_ALARM_ACTION);
        PendingIntent operation2 = PendingIntent.getBroadcast(context, DISTANCE_ALARM_ID, distanceAlarm, 0);
        am.cancel(operation2);
        am.setRepeating(AlarmManager.RTC_WAKEUP, SNTP.getInstance().getTime(), 60L * 60 * 1000, operation2);
    }

    /**
     * Registers for periodic polling using the scheduler.
     */
    private void startPolling() {
        pollAlarmReceiver.start(context);
    }

    @Override
    public void startSensing(long sampleDelay) {
        setSampleRate(sampleDelay);
        active = true;
        if(!googleApiClient.isConnected() && !googleApiClient.isConnecting())
            googleApiClient.connect();
        startAlarms();
    }

    private void stopAlarms() {
        // unregister the receiver
        try {
            context.unregisterReceiver(distanceAlarmReceiver);
        } catch (IllegalArgumentException e) {
            // do nothing
        }

        // stop the alarm
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent distanceAlarm = new Intent(DISTANCE_ALARM_ACTION);
        PendingIntent operation2 = PendingIntent.getBroadcast(context, DISTANCE_ALARM_ID, distanceAlarm, 0);
        am.cancel(operation2);
    }


    private void stopPolling() {
        pollAlarmReceiver.stop(context);
    }

    @Override
    public void stopSensing() {
        if(googleApiClient.isConnected()) {
            LocationServices.FusedLocationApi.removeLocationUpdates(googleApiClient, this);
            googleApiClient.disconnect();
        }

        active = false;
        stopAlarms();
        stopPolling();
    }
} public class LocationSensor extends BaseSensor implements PeriodicPollingSensor {

    private class MyLocationListener implements LocationListener {

        @Override
        public void onLocationChanged(Location fix) {

            if (null != lastGpsFix) {
                if (SNTP.getInstance().getTime() - lastGpsFix.getTime() < MIN_SAMPLE_DELAY) {
                    // Log.v(TAG, "New location fix is too fast after last one");
                    return;
                }
            }

            // controller = Controller.getController(context);
            JSONObject json = new JSONObject();
            try {
                json.put("latitude", fix.getLatitude());
                json.put("longitude", fix.getLongitude());

                // always include all JSON fields, or we get problems with varying data_structure
                json.put("accuracy", fix.hasAccuracy() ? fix.getAccuracy() : -1.0d);
                json.put("altitude", fix.hasAltitude() ? fix.getAltitude() : -1.0);
                json.put("speed", fix.hasSpeed() ? fix.getSpeed() : -1.0d);
                json.put("bearing", fix.hasBearing() ? fix.getBearing() : -1.0d);
                json.put("provider", null != fix.getProvider() ? fix.getProvider() : "unknown");

                if (fix.getProvider().equals(LocationManager.GPS_PROVIDER)) {
                    lastGpsFix = fix;
                } else if (fix.getProvider().equals(LocationManager.NETWORK_PROVIDER)) {
                    lastNwFix = fix;
                } else {
                    // do nothing
                }

            } catch (JSONException e) {
                Log.e(TAG, "JSONException in onLocationChanged", e);
                return;
            }

            // use SNTP time
            long timestamp = SNTP.getInstance().getTime();
            notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(json);
            dataPoint.sensorName = SensorNames.LOCATION;
            dataPoint.sensorDescription = SensorNames.LOCATION;
            dataPoint.timeStamp = timestamp;
            sendToSubscribers(dataPoint);

            // pass message to the MsgHandler
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.SENSOR_NAME, SensorNames.LOCATION);
            i.putExtra(DataPoint.VALUE, json.toString());
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
            i.putExtra(DataPoint.TIMESTAMP, timestamp);
            i.setPackage(context.getPackageName());
            context.startService(i);

            distanceEstimator.addPoint(fix);
        }

        @Override
        public void onProviderDisabled(String provider) {
            controller.checkSensorSettings(isGpsAllowed, isListeningNw, isListeningGps,
                    getSampleRate(), lastGpsFix, listenGpsStart, lastNwFix, listenNwStart,
                    listenGpsStop, listenNwStop);
        }

        @Override
        public void onProviderEnabled(String provider) {
            controller.checkSensorSettings(isGpsAllowed, isListeningNw, isListeningGps,
                    getSampleRate(), lastGpsFix, listenGpsStart, lastNwFix, listenNwStart,
                    listenGpsStop, listenNwStop);
        }

        @Override
        public void onStatusChanged(String provider, int status, Bundle extras) {
            // do nothing
        }
    }

    private static final String TAG = "Sense LocationSensor";
    private static final String DISTANCE_ALARM_ACTION = "nl.sense_os.service.LocationAlarm.distanceAlarm";
    public static final long MIN_SAMPLE_DELAY = 5000; // 5 sec
    private static final int DISTANCE_ALARM_ID = 70;
    private static final float MIN_DISTANCE = 0;
    private static LocationSensor instance = null;

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static LocationSensor getInstance(Context context) {
        if (instance == null) {
            instance = new LocationSensor(context);
        }
        return instance;
    }

    /**
     * Receiver for periodic alarms to calculate distance values.
     */
    public final BroadcastReceiver distanceAlarmReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            double distance = distanceEstimator.getTraveledDistance();

            notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(distance);
            dataPoint.sensorName = SensorNames.TRAVELED_DISTANCE_1H;
            dataPoint.sensorDescription = SensorNames.TRAVELED_DISTANCE_1H;
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            sendToSubscribers(dataPoint);

            // pass message to the MsgHandler
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.SENSOR_NAME, SensorNames.TRAVELED_DISTANCE_1H);
            i.putExtra(DataPoint.VALUE, (float) distance);
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(context.getPackageName());
            context.startService(i);

            // start counting again, from the last location
            distanceEstimator.reset();
        }
    };

    private Controller controller;
    private Context context;
    private LocationManager locMgr;
    private final MyLocationListener gpsListener;
    private final MyLocationListener nwListener;
    private final MyLocationListener pasListener;
    private boolean isGpsAllowed;
    private boolean isNetworkAllowed;
    private boolean isListeningGps;
    private boolean isListeningNw;
    private long listenGpsStart;
    private long listenGpsStop;
    private Location lastGpsFix;
    private Location lastNwFix;
    private long listenNwStart;
    private long listenNwStop;
    private boolean active;
    private TraveledDistanceEstimator distanceEstimator;
    private PeriodicPollAlarmReceiver pollAlarmReceiver;

    /**
     * Constructor.
     * 
     * @param context
     */
    protected LocationSensor(Context context) {
        this.context = context;
        pollAlarmReceiver = new PeriodicPollAlarmReceiver(this);
        locMgr = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
        controller = Controller.getController(context);
        gpsListener = new MyLocationListener();
        nwListener = new MyLocationListener();
        pasListener = new MyLocationListener();
        distanceEstimator = new TraveledDistanceEstimator();
    }

    @Override
    public void doSample() {
        controller.checkSensorSettings(isGpsAllowed, isListeningNw, isListeningGps,
                getSampleRate(), lastGpsFix, listenGpsStart, lastNwFix, listenNwStart,
                listenGpsStop, listenNwStop);
    }

    /**
     * Gets the last known location fixes from the location providers, and sends it to the location
     * listener as first data point.
     */
    private void getLastKnownLocation() {

        // get the most recent location fixes
        Location gpsFix = locMgr.getLastKnownLocation(LocationManager.GPS_PROVIDER);
        Location nwFix = locMgr.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);

        // see which provides the best recent fix
        Location bestFix = gpsFix;
        if (null != nwFix) {
            // see if network provider has better recent fix
            if (null == bestFix) {
                // gps did not provide a recent fix
                bestFix = nwFix;
            } else if (nwFix.getTime() < bestFix.getTime()
                    && nwFix.getAccuracy() < bestFix.getAccuracy() + 100) {
                // network fix is more recent and pretty accurate
                bestFix = nwFix;
            }
        }

        // send best recent fix to location listener
        if (null != bestFix) {
            // check that the fix is not too old or from the future
            long presentTime = SNTP.getInstance().getTime();
            long oldTime = presentTime - 1000 * 60 * 5;
            if (bestFix.getTime() > oldTime && bestFix.getTime() < presentTime) {
                // use last known location as first sensor value
                gpsListener.onLocationChanged(bestFix);
            }
        }
    }

    @Override
    public boolean isActive() {
        return active;
    }

    public void notifyListeningRestarted(String msg) {
        // not used
        // TODO: clean up
    }

    public void notifyListeningStopped(String msg) {
        // not used
        // TODO: clean up
    }

    public void setGpsListening(boolean listen) {
        if (listen) {
            locMgr.requestLocationUpdates(LocationManager.GPS_PROVIDER, getSampleRate(),
                    MIN_DISTANCE, gpsListener);
            isListeningGps = true;
            listenGpsStart = SNTP.getInstance().getTime();
            lastGpsFix = null;
        } else {
            locMgr.removeUpdates(gpsListener);
            listenGpsStop = SNTP.getInstance().getTime();
            isListeningGps = false;
        }
    }

    public void setNetworkListening(boolean listen) {
        if (listen/* && isNetworkAllowed */) {
            try {
                locMgr.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, getSampleRate(),
                        MIN_DISTANCE, nwListener);
                isListeningNw = true;
                listenNwStart = SNTP.getInstance().getTime();
                lastNwFix = null;
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Failed to start listening to network provider! " + e);
                listenNwStop = SNTP.getInstance().getTime();
                isListeningNw = false;
            }
        } else {
            locMgr.removeUpdates(nwListener);
            listenNwStop = SNTP.getInstance().getTime();
            isListeningNw = false;
        }
    }

    /**
     * Listens to passive location provider. Should only be called in Android versions where the
     * provider is available.
     */
    @TargetApi(Build.VERSION_CODES.FROYO)
    private void setPassiveListening(boolean listen) {
        if (listen) {
            locMgr.requestLocationUpdates(LocationManager.PASSIVE_PROVIDER, getSampleRate(),
                    MIN_DISTANCE, pasListener);
        } else {
            locMgr.removeUpdates(pasListener);
        }
    }

    @Override
    public void setSampleRate(long sampleDelay) {
        super.setSampleRate(sampleDelay);
        stopPolling();
        startPolling();
    }

    private void startAlarms() {

        // register to receive the alarm
        context.getApplicationContext().registerReceiver(distanceAlarmReceiver,
                new IntentFilter(DISTANCE_ALARM_ACTION));

        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        // start periodic for distance
        Intent distanceAlarm = new Intent(DISTANCE_ALARM_ACTION);
        PendingIntent operation2 = PendingIntent.getBroadcast(context, DISTANCE_ALARM_ID,
                distanceAlarm, 0);
        am.cancel(operation2);
        am.setRepeating(AlarmManager.RTC_WAKEUP, SNTP.getInstance().getTime(), 60L * 60 * 1000,
                operation2);

        /*
         * TODO: this also for 24 hours PendingIntent operation3 =
         * PendingIntent.getBroadcast(context, DISTANCE_ALARM_ID, distanceAlarm, 0);
         * am.cancel(operation3); am.setRepeating(AlarmManager.RTC_WAKEUP,
         * SNTP.getInstance().getTime(), 24L * 60 * 60 * 1000, operation3);
         */
    }

    /**
     * Registers for updates from the location providers.
     */
    private void startListening() {

        SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);
        isGpsAllowed = mainPrefs.getBoolean(Main.Location.GPS, true);
        isNetworkAllowed = mainPrefs.getBoolean(Main.Location.NETWORK, true);

        // start listening to GPS and/or Network location
        if (isGpsAllowed) {
            setGpsListening(true);
        }
        if (isNetworkAllowed) {
            // Log.v(TAG, "Start listening to location updates from Network");
            setNetworkListening(true);
        }

        if (!isGpsAllowed && !isNetworkAllowed
                && Build.VERSION.SDK_INT >= Build.VERSION_CODES.FROYO) {
            Log.v(TAG, "Start passively listening to location updates for other apps");
            setPassiveListening(true);
        }
    }

    /**
     * Registers for periodic polling using the scheduler.
     */
    private void startPolling() {
        // Log.v(TAG, "start polling");
        pollAlarmReceiver.start(context);
    }

    @Override
    public void startSensing(long sampleDelay) {
        setSampleRate(sampleDelay);
        active = true;

        startListening();
        startAlarms();
        getLastKnownLocation();
    }

    private void stopAlarms() {
        // unregister the receiver
        try {
            context.unregisterReceiver(distanceAlarmReceiver);
        } catch (IllegalArgumentException e) {
            // do nothing
        }

        // stop the alarm
        AlarmManager am = (AlarmManager) context.getSystemService(Context.ALARM_SERVICE);

        Intent distanceAlarm = new Intent(DISTANCE_ALARM_ACTION);
        PendingIntent operation2 = PendingIntent.getBroadcast(context, DISTANCE_ALARM_ID,
                distanceAlarm, 0);
        am.cancel(operation2);
    }

    /**
     * Stops listening for location updates.
     */
    private void stopListening() {
        setGpsListening(false);
        setNetworkListening(false);
        setPassiveListening(false);
    }

    private void stopPolling() {
        // Log.v(TAG, "stop polling");
        pollAlarmReceiver.stop(context);
    }

    @Override
    public void stopSensing() {
        active = false;
        stopListening();
        stopAlarms();
        stopPolling();
    }
} public class TimeZoneSensor extends BaseDataProducer 
{
    public static String TAG = Class.class.getSimpleName();
    TimeZoneChangeReceiver tzChangeReceiver;
    boolean active = false;
    protected static TimeZoneSensor instance;
    protected Context context;

    protected TimeZoneSensor(Context context) 
    {
        this.context = context;
    }

    /**
     * Start sensing
     * 
     * register for time zone changes
     * send the current time zone
     */
    public void startSensing() 
    {
        if(tzChangeReceiver != null)
            return;

        tzChangeReceiver = new TimeZoneChangeReceiver();
        context.registerReceiver(tzChangeReceiver, new IntentFilter(Intent.ACTION_TIMEZONE_CHANGED));
        sendTimeZone();
    }

    /*
     * Stop sensing
     * 
     * unregister for time zone changes
     */
    public void stopSensing() 
    {
        if(tzChangeReceiver == null)
            return;

        context.unregisterReceiver(tzChangeReceiver);
        tzChangeReceiver = null;
    }

    public static TimeZoneSensor getInstance(Context context)
    {
        if(instance == null)
            instance = new TimeZoneSensor(context);
        return instance;
    }

    protected void sendTimeZone()
    {
        try
        {
            // send to subscribers
            this.notifySubscribers();

            TimeZone timeZone = TimeZone.getDefault();
            JSONObject timeZoneJson = new JSONObject();
            timeZoneJson.put("offset", timeZone.getOffset(SNTP.getInstance().getTime())/1000l);
            timeZoneJson.put("id", timeZone.getID());
            SensorDataPoint dataPoint = new SensorDataPoint(timeZoneJson);
            dataPoint.sensorName = SensorNames.TIME_ZONE;
            dataPoint.sensorDescription = "Current time zone";
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            //Log.d(TAG,"time zone subscribers: " + this.);
            this.sendToSubscribers(dataPoint);

            // pass message to the MsgHandler
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.SENSOR_NAME, SensorNames.TIME_ZONE);
            i.putExtra(DataPoint.VALUE, timeZoneJson.toString());
            i.putExtra(DataPoint.SENSOR_DESCRIPTION, dataPoint.sensorDescription);
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(context.getPackageName());
            this.context.startService(i);
        }
        catch(Exception e)
        {
            Log.e(TAG, "Error in sending TimeZone");
        }
    }

    private class TimeZoneChangeReceiver extends BroadcastReceiver
    {
        @Override
        public void onReceive(Context context, Intent intent)
        {
            // time zone has changed
            if (!Intent.ACTION_TIMEZONE_CHANGED.equals(intent.getAction()))
                return;

            TimeZoneSensor.this.sendTimeZone();
            Log.d(TAG, "TimeZone change");
        }
    };
} public class MotionSensor extends BaseSensor implements SensorEventListener, PeriodicPollingSensor {

    /**
     * BroadcastReceiver that listens for screen state changes. Re-registers the motion sensor when
     * the screen turns off.
     */
    private class ScreenOffListener extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            // Check action just to be on the safe side.
            if (false == intent.getAction().equals(Intent.ACTION_SCREEN_OFF)) {
                return;
            }

            SharedPreferences prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                    Context.MODE_PRIVATE);
            boolean useFix = prefs.getBoolean(Motion.SCREENOFF_FIX, false);
            if (useFix) {
                // wait half a second and re-register
                Runnable restartSensing = new Runnable() {

                    @Override
                    public void run() {
                        // Unregisters the motion listener and registers it again.
                        stopSensing();
                        startSensing(getSampleRate());
                    };
                };
                new Handler().postDelayed(restartSensing, 500);
            }
        }
    }

    private static final long DELAY_AFTER_REGISTRATION = 500;
    private static final String TAG = "Sense Motion";

    private static MotionSensor instance = null;

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static MotionSensor getInstance(Context context) {
        if (instance == null) {
            instance = new MotionSensor(context);
        }
        return instance;
    }

    private final BroadcastReceiver screenOffListener = new ScreenOffListener();
    private final Context context;
    private boolean isFallDetectMode;
    private boolean isEnergyMode;
    private boolean isEpiMode;
    private boolean isBurstMode;
    private boolean isUnregisterWhenIdle;
    private boolean firstStart = true;
    private List<Sensor> sensors;
    private boolean active = false;
    private WakeLock wakeLock;
    private boolean registered;
    private PeriodicPollAlarmReceiver alarmReceiver;
    // TODO:
    // Should be moved to the service where all the sensors are registered
    // and added as data processor when the preference is selected
    // or at least out of startSensing, does not need to create this coupling at startSensing
    private EpilepsySensor epi;
    private FallDetector fall;
    private MotionEnergySensor energy;
    private StandardMotionSensor standard = null;
    private MotionBurstSensor accelerometerBurstSensor;
    private MotionBurstSensor gyroBurstSensor;
    private MotionBurstSensor linearBurstSensor;
    private SubscriptionManager mSubscrMgr;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected MotionSensor(Context context) {
        this.context = context;
        alarmReceiver = new PeriodicPollAlarmReceiver(this);
        mSubscrMgr = SubscriptionManager.getInstance();
    }

    @Override
    public void doSample() {
        //Log.v(TAG, "Do sample");

        // get wake lock
        if (null == wakeLock) {
            PowerManager powerMgr = (PowerManager) context.getSystemService(Context.POWER_SERVICE);
            wakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        }
        if (!wakeLock.isHeld()) {
            //Log.i(TAG, "Acquire wake lock");
            wakeLock.acquire();
        } else {
            // Log.v(TAG, "Wake lock already held");
        }

        // notify all special sensors
        notifySubscribers();

        registerSensors();
    }

    private void enableScreenOffListener(boolean enable) {
        if (enable) {
            // Register the receiver for SCREEN OFF events
            IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_OFF);
            context.registerReceiver(screenOffListener, filter);
        } else {
            // Unregister the receiver for SCREEN OFF events
            try {
                context.unregisterReceiver(screenOffListener);
            } catch (IllegalArgumentException e) {
                // Log.v(TAG, "Ignoring exception when unregistering screen off listener");
            }
        }
    }

    /**
     * Initializes the burst-mode data processors.
     */
    @TargetApi(Build.VERSION_CODES.GINGERBREAD)
    private void initBurstDataProcessors() {
        final SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);

        if (mainPrefs.getBoolean(Motion.ACCELEROMETER, true)) {
            accelerometerBurstSensor = new MotionBurstSensor(context, Sensor.TYPE_ACCELEROMETER,
                    SensorNames.ACCELEROMETER_BURST);
            addSubscriber(this.accelerometerBurstSensor);
            mSubscrMgr.registerProducer(SensorNames.ACCELEROMETER_BURST,
                    accelerometerBurstSensor);
        }
        if (mainPrefs.getBoolean(Motion.GYROSCOPE, true)) {
            gyroBurstSensor = new MotionBurstSensor(context, Sensor.TYPE_GYROSCOPE,
                    SensorNames.GYRO_BURST);
            addSubscriber(this.gyroBurstSensor);
            mSubscrMgr.registerProducer(SensorNames.GYRO_BURST, gyroBurstSensor);
        }
        if (mainPrefs.getBoolean(Motion.LINEAR_ACCELERATION, true)) {
            if (!MotionSensorUtils.isFakeLinearRequired( context )) {
                linearBurstSensor = new MotionBurstSensor(context,
                        Sensor.TYPE_LINEAR_ACCELERATION,
                        SensorNames.LINEAR_BURST);
                addSubscriber(this.linearBurstSensor);
                mSubscrMgr.registerProducer(SensorNames.LINEAR_BURST,
                        linearBurstSensor);
            }else{
                  linearBurstSensor = new MotionBurstSensor(context,
                  Sensor.TYPE_ACCELEROMETER, SensorNames.LINEAR_BURST);
                  addSubscriber(this.linearBurstSensor);
                  mSubscrMgr.registerProducer(SensorNames.LINEAR_BURST,
                        linearBurstSensor);
                  Log.d( TAG, "Fake linearBurst is registered" );
            }
        }
    }

    /**
     * Initializes the epilepsy data processor.
     */
    private void initEpiDataProcessor() {

        // only listen to accelerometer in epi mode
        sensors = new ArrayList<Sensor>();

        SensorManager sm = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        sensors.add(sm.getDefaultSensor(Sensor.TYPE_ACCELEROMETER));

        // super fast sample delay
        setSampleRate(0);

        // separate service to check epilepsy (not used anymore?)
        context.startService(new Intent(context, EpiStateMonitor.class));

        // only add epilepsy data processor
        this.epi = new EpilepsySensor(context);
        // register the sensor at the sense Service
        // TODO: connect to the service in a different manner
        mSubscrMgr.registerProducer(SensorNames.ACCELEROMETER_EPI, epi);
        addSubscriber(this.epi);
    }

    /**
     * Initializes the fall detection data processor.
     * 
     * @param demo
     *            boolean to enable the fall detection demo mode
     */
    private void initFallDataProcessor(boolean demo) {

        // add fall detector data processor
        fall = new FallDetector(context);
        fall.demo = demo;

        // Example how to subscribe via the service
        // Context should not be used for this
        mSubscrMgr.subscribeConsumer(SensorNames.MOTION, fall);
        mSubscrMgr.registerProducer(SensorNames.FALL_DETECTOR, fall);

        fall.sendFallMessage(false);
        firstStart = false;
    }

    /**
     * Initializes the standard data processors that create data for the acceleration, orientation,
     * magnetic field rotation rate, linear acceleration and motion energy sensors.
     */
    private void initStandardDataProcessors() {
        standard = new StandardMotionSensor(context);
        mSubscrMgr.registerProducer(SensorNames.ACCELEROMETER, standard);
        mSubscrMgr.registerProducer(SensorNames.ORIENT, standard);
        mSubscrMgr.registerProducer(SensorNames.MAGNETIC_FIELD, standard);
        mSubscrMgr.registerProducer(SensorNames.GYRO, standard);
        mSubscrMgr.registerProducer(SensorNames.LIN_ACCELERATION, standard);
        addSubscriber(this.standard);

        // optional motion energy data processor
        if (isEnergyMode) {
            energy = new MotionEnergySensor(context);
            mSubscrMgr.registerProducer(SensorNames.MOTION_ENERGY, energy);
            addSubscriber(this.energy);
        }
    }

    @Override
    public boolean isActive() {
        return active;
    }

    /**
     * @return true if all active sensors have recently passed a data point.
     */
    private boolean isTimeToUnregister() {

        boolean unregister = isUnregisterWhenIdle;
        // only unregister when sample delay is large enough
        unregister &= getSampleRate() > DELAY_AFTER_REGISTRATION;
        unregister &= this.checkSubscribers();
        return unregister;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // do nothing
    }

    @Override
    public void onSensorChanged(SensorEvent event) {

        if (!registered) {
            // ignore
        }
        if (!active) {
           // Log.w(TAG, "Motion sensor value received when sensor is inactive!");
            stopSample();
            return;
        }

        SensorDataPoint dataPoint = new SensorDataPoint(event);
        dataPoint.sensorName = SensorNames.MOTION;
        dataPoint.sensorDescription = SensorNames.MOTION;
        dataPoint.timeStamp = SNTP.getInstance().getTime();
        this.sendToSubscribers(dataPoint);

        // unregister sensor listener when we can
        if (isTimeToUnregister()) {

            // unregister the listener and start again in sampleDelay seconds
            stopSample();
        }
    }

    /**
     * Registers for updates from the device's motion sensors.
     */
    private synchronized void registerSensors() {

        if (!registered) {
            // Log.v(TAG, "Register the motion sensor for updates");

            SensorManager mgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);

            int delay = isFallDetectMode || isEpiMode || isBurstMode || isEnergyMode ? SensorManager.SENSOR_DELAY_GAME
                    : SensorManager.SENSOR_DELAY_NORMAL;

            for (Sensor sensor : sensors) {
                mgr.registerListener(this, sensor, delay);
            }

            registered = true;

        } else {
            // Log.v(TAG, "Did not register for motion sensor updates: already registered");
        }
    }

    @Override
    public void setSampleRate(long sampleDelay) {
        super.setSampleRate(sampleDelay);
        stopPolling();
        startPolling();
    }

    private void startPolling() {
        //Log.v(TAG, "start polling");
        alarmReceiver.start(context);
    }

    @Override
    public void startSensing(long sampleDelay) {
        //Log.v(TAG, "Start sensing");

        final SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);
        isEpiMode = mainPrefs.getBoolean(Motion.EPIMODE, false);
        isEnergyMode = mainPrefs.getBoolean(Motion.MOTION_ENERGY, false);
        isBurstMode = mainPrefs.getBoolean(Motion.BURSTMODE, false);
        isUnregisterWhenIdle = mainPrefs.getBoolean(Motion.UNREG, true);

        // check if the fall detector is enabled
        boolean isFallDetectDemo = mainPrefs.getBoolean(Motion.FALL_DETECT_DEMO, false);
        isFallDetectMode = isFallDetectDemo || mainPrefs.getBoolean(Motion.FALL_DETECT, false);

        sensors = MotionSensorUtils.getAvailableMotionSensors(context);
        if (isEpiMode) {
            initEpiDataProcessor();

        } else {
            // add standard data processor
            initStandardDataProcessors();

            // add fall detection data processor
            if (firstStart && isFallDetectMode) {
                initFallDataProcessor(isFallDetectDemo);
            }

            // add burst mode
            if (isBurstMode) {                
                initBurstDataProcessors();
            }
        }

        active = true;
        setSampleRate(sampleDelay);

        // do the first sample immediately
        doSample();
    }

    public void stopPolling() {
        //Log.v(TAG, "stop polling");
        alarmReceiver.stop(context);
    }

    private void stopSample() {
        //Log.v(TAG, "Stop sample");

        // release wake lock
        if (null != wakeLock && wakeLock.isHeld()) {
            wakeLock.release();
        }

        unregisterSensors();
    }

    /**
     * Unregisters the listener for updates from the motion sensors, and stops waking up the device
     * for sampling.
     */
    @Override
    public void stopSensing() {
        //Log.v(TAG, "Stop sensing");
        stopSample();
        stopPolling();
        enableScreenOffListener(false);
        active = false;

        // only remove the DataProcessors added in the class, keep external DataProcessors
        if (this.epi != null)
            removeSubscriber(epi);
        if (this.standard != null)
            removeSubscriber(standard);
        if (this.fall != null)
            removeSubscriber(fall);
        if (this.energy != null)
            removeSubscriber(energy);
        if (this.accelerometerBurstSensor != null)
            removeSubscriber(accelerometerBurstSensor);
        if (this.linearBurstSensor != null)
            removeSubscriber(linearBurstSensor);
        if (this.gyroBurstSensor != null)
            removeSubscriber(gyroBurstSensor);

        epi = null;
        standard = null;
        fall = null;
        energy = null;
        accelerometerBurstSensor = null;
        linearBurstSensor = null;
        gyroBurstSensor = null;

        // stop the epi state monitor service
        // TODO: remove the epi mode and epi state monitor service
        if (isEpiMode || isFallDetectMode || isBurstMode) {
            context.stopService(new Intent(context, EpiStateMonitor.class));
        }
    }

    private synchronized void unregisterSensors() {

        if (registered) {
            // Log.v(TAG, "Unregister the motion sensor for updates");
            ((SensorManager) context.getSystemService(Context.SENSOR_SERVICE))
                    .unregisterListener(this);
        } else {
            // Log.v(TAG, "Did not unregister for motion sensor updates: already unregistered");
        }

        registered = false;
    }
} public class AppsSensor extends BaseDataProducer implements PeriodicPollingSensor {

	private static final String TAG = "Sense Apps Sensor";
	private static AppsSensor instance;

	public static AppsSensor getInstance(Context context) {
		if (null == instance) {
			instance = new AppsSensor(context);
		}
		return instance;
	}

	private final Context context;
	private final PeriodicPollAlarmReceiver alarmReceiver;
	private boolean active;
	private long sampleDelay;

	private AppsSensor(Context context) {
		this.context = context;
		this.alarmReceiver = new PeriodicPollAlarmReceiver(this);
	}

	@Override
	public void doSample() {
		//Log.v(TAG, "do sample");
		List<ResolveInfo> installedApps = getInstalledApps();
		SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
		if (mainPrefs.getBoolean(PhoneState.FOREGROUND_APP, true)) {
			// get app info from package manager					
			JSONObject fapp = getForgroundApp(installedApps);		
			if(fapp != null)
				sendForegroundApp(fapp);
		}
		// send data about all installed apps
		if (mainPrefs.getBoolean(PhoneState.INSTALLED_APPS, false)) {
			PackageManager pm = context.getPackageManager();
			List<String> installedAppLabels = new ArrayList<String>();
			for (ResolveInfo installedApp : installedApps) {
				String app = installedApp.loadLabel(pm).toString();
				// TODO fix filthy way of filtering out an unparseable character ('tm')
				app = app.replaceAll("\u2122", "");
				installedAppLabels.add(app);
			}
			sendInstalledApps(installedAppLabels);
		}

	}

	private void sendInstalledApps(List<String> apps) {

		try {

			// create value JSON object
			JSONObject value = new JSONObject();
			value.put("installed", new JSONArray(apps));
			// TODO figure out a better way to send an array of objects			
			notifySubscribers();
			SensorDataPoint dataPoint = new SensorDataPoint(value);
			dataPoint.sensorName = SensorNames.APP_INSTALLED;
			dataPoint.sensorDescription = SensorNames.APP_INSTALLED;
			dataPoint.timeStamp = SNTP.getInstance().getTime();        
			sendToSubscribers(dataPoint);

			// send data point
			Intent i = new Intent(context.getString(R.string.action_sense_new_data));
			i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
			i.putExtra(DataPoint.VALUE, value.toString());
			i.putExtra(DataPoint.SENSOR_NAME, SensorNames.APP_INSTALLED);
			i.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
			i.setPackage(context.getPackageName());
			context.startService(i);

		} catch (JSONException e) {
			Log.e(TAG, "Failed to create data point for installed apps sensor!", e);
		}
	}

	private void sendForegroundApp(JSONObject foreGroundApp) {		
		notifySubscribers();
		SensorDataPoint dataPoint = new SensorDataPoint(foreGroundApp);
		dataPoint.sensorName = SensorNames.APP_FOREGROUND;
		dataPoint.sensorDescription = SensorNames.APP_FOREGROUND;
		dataPoint.timeStamp = SNTP.getInstance().getTime();        
		sendToSubscribers(dataPoint);

		Intent i = new Intent(context.getString(R.string.action_sense_new_data));
		i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
		i.putExtra(DataPoint.VALUE, foreGroundApp.toString());
		i.putExtra(DataPoint.SENSOR_NAME, SensorNames.APP_FOREGROUND);
		i.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
		i.setPackage(context.getPackageName());
		context.startService(i);
	}


	private List<ResolveInfo> getInstalledApps() {
		Intent mainIntent = new Intent(Intent.ACTION_MAIN, null);
		mainIntent.addCategory(Intent.CATEGORY_LAUNCHER);
		List<ResolveInfo> result = context.getPackageManager().queryIntentActivities(mainIntent, 0);

		return result;
	}


	private JSONObject getForgroundApp(List<ResolveInfo> installedApps) {
		ActivityManager actvityManager = (ActivityManager) context
				.getSystemService(Context.ACTIVITY_SERVICE);

		List<RunningTaskInfo> result = actvityManager.getRunningTasks(1);

		JSONObject foregroundApp = new JSONObject();
		// send data about important apps
		PackageManager pm = context.getPackageManager();
		for (ResolveInfo installedApp : installedApps) {
			if(installedApp.activityInfo.processName.compareTo(result.get(0).baseActivity.getPackageName()) == 0)
			{				
				try {
					foregroundApp.put("label",  installedApp.loadLabel(pm));
					foregroundApp.put("process", installedApp.activityInfo.processName);
					foregroundApp.put("activity", result.get(0).topActivity.getShortClassName().replaceFirst(".",""));
					return foregroundApp;
				} catch (JSONException e) {					
					Log.e(TAG, e.getMessage());
				}

			}
		}
		return null;
	}

	@Override
	public boolean isActive() {
		return active;
	}

	@Override
	public void startSensing(long sampleDelay) {
		// TODO Auto-generated method stub
		this.sampleDelay = sampleDelay;
		alarmReceiver.start(context);
		active = true;
	}

	@Override
	public void stopSensing() {
		// TODO Auto-generated method stub
		alarmReceiver.stop(context);
		active = false;
	}

	@Override
	public long getSampleRate() {
		// TODO Auto-generated method stub
		return sampleDelay;
	}

	@Override
	public void setSampleRate(long sampleDelay) {
		// TODO Auto-generated method stub
		this.sampleDelay = sampleDelay;		
	}

}
 public class BatterySensor extends BaseDataProducer{
    private static final String TAG = "Sense Battery sensor";
    private long sampleDelay = 0; // in milliseconds
    private long lastSampleTime;
    private Context context;

    protected BatterySensor(Context context) {
        this.context = context;
    }
    
    private static BatterySensor instance = null;
    
    public static BatterySensor getInstance(Context context) {
	    if(instance == null) {
	       instance = new BatterySensor(context);
	    }
	    return instance;
    }

    private BroadcastReceiver batteryChangeReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            // Log.d(TAG, "BatteryChangeReceiver received broadcast");

            boolean gotData = false;

            if (SNTP.getInstance().getTime() > lastSampleTime + sampleDelay) {

                // Send a message when the battery state has changed
                JSONObject json = new JSONObject();
                if (intent.getAction().equals(Intent.ACTION_BATTERY_CHANGED)) {

                    gotData = true;
                    int level = intent.getIntExtra("level", 0);
                    int scale = intent.getIntExtra("scale", 100);
                    try {
                        json.put("level", String.valueOf(level * 100 / scale));
                    } catch (JSONException e) {
                        Log.e(TAG, "JSONException preparing screen activity data");
                    }
                    int plugType = intent.getIntExtra("plugged", 0);
                    int status = intent
                            .getIntExtra("status", BatteryManager.BATTERY_STATUS_UNKNOWN);
                    String statusString;
                    if (status == BatteryManager.BATTERY_STATUS_CHARGING) {
                        statusString = "charging";
                        if (plugType > 0) {
                            statusString = statusString
                                    + " "
                                    + ((plugType == BatteryManager.BATTERY_PLUGGED_AC) ? "AC"
                                            : "USB");
                        }
                        try {
                            json.put("status", statusString);
                        } catch (JSONException e) {
                            Log.e(TAG, "JSONException preparing screen activity data");
                        }

                    } else if (status == BatteryManager.BATTERY_STATUS_DISCHARGING) {
                        try {
                            json.put("status", "discharging");
                        } catch (JSONException e) {
                            Log.e(TAG, "JSONException preparing screen activity data");
                        }
                    } else if (status == BatteryManager.BATTERY_STATUS_NOT_CHARGING) {
                        try {
                            json.put("status", "not charging");
                        } catch (JSONException e) {
                            Log.e(TAG, "JSONException preparing screen activity data");
                        }
                    } else if (status == BatteryManager.BATTERY_STATUS_FULL) {
                        try {
                            json.put("status", "full");
                        } catch (JSONException e) {
                            Log.e(TAG, "JSONException preparing screen activity data");
                        }
                    } else {
                        try {
                            json.put("status", "unknown");
                        } catch (JSONException e) {
                            Log.e(TAG, "JSONException preparing screen activity data");
                        }
                    }
                } else if (intent.getAction().equals(Intent.ACTION_BATTERY_LOW)) {
                    gotData = true;
                    try {
                        json.put("status", "low");
                    } catch (JSONException e) {
                        Log.e(TAG, "JSONException preparing screen activity data");
                    }
                }

                if (gotData) {
                    // Log.v(TAG, "Transmit battery state: " + json.toString());

                	notifySubscribers();
                	SensorDataPoint dataPoint = new SensorDataPoint(json);
                	dataPoint.sensorName = SensorNames.BATTERY_SENSOR;
                	dataPoint.sensorDescription = SensorNames.BATTERY_SENSOR;
                	dataPoint.timeStamp = SNTP.getInstance().getTime();        
                	sendToSubscribers(dataPoint);
                	
                    Intent i = new Intent(context.getString(R.string.action_sense_new_data));
                    i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
                    i.putExtra(DataPoint.VALUE, json.toString());
                    i.putExtra(DataPoint.SENSOR_NAME, SensorNames.BATTERY_SENSOR);
                    i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
                    lastSampleTime = SNTP.getInstance().getTime();
                    i.setPackage(context.getPackageName());
                    context.startService(i);
                }

            } else {
                // Log.v(TAG, "Skipped battery state update: too soon after last transmission...");
            }
        }
    };

    public void setSampleDelay(long _sampleDelay) {
        sampleDelay = _sampleDelay;
    }

    public void startBatterySensing(long _sampleDelay) {
        setSampleDelay(_sampleDelay);
        IntentFilter filter = new IntentFilter(Intent.ACTION_BATTERY_CHANGED);
        filter.addAction(Intent.ACTION_BATTERY_LOW);
        context.registerReceiver(batteryChangeReceiver, filter);
    }

    public void stopBatterySensing() {
        try {
            context.unregisterReceiver(batteryChangeReceiver);
        } catch (Exception e) {
            Log.e(TAG, e.getMessage());
        }

    }

    public long getSampleDelay() {
        return sampleDelay;
    }
} public class PhoneActivitySensor extends BaseDataProducer{

    private static final String TAG = "Sense Screen Activity";
    private final Context context;
        
    private static PhoneActivitySensor instance = null;
    
    public static PhoneActivitySensor getInstance(Context context) {
	    if(instance == null) {
	       instance = new PhoneActivitySensor(context);
	    }
	    return instance;
    }

    private BroadcastReceiver screenActivityReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {

            // Send a message when the screen state has changed
            String screen = "";
            if (Intent.ACTION_SCREEN_OFF.equals(intent.getAction())) {
                screen = "off";
            } else if (Intent.ACTION_SCREEN_ON.equals(intent.getAction())) {
                screen = "on";
            } else {
                Log.w(TAG, "Unexpected broadcast action: " + intent.getAction());
                return;
            }

            sendData(screen);
        }
    };
    
    private void sendData(String screen)
    {
    	 // create new data point
        JSONObject json = new JSONObject();
        try {
            json.put("screen", screen);

        } catch (JSONException e) {
            Log.e(TAG, "JSONException preparing screen activity data");
        }

        notifySubscribers();
        SensorDataPoint dataPoint = new SensorDataPoint(json);
        dataPoint.sensorName = SensorNames.SCREEN_ACTIVITY;
        dataPoint.sensorDescription = SensorNames.SCREEN_ACTIVITY;
        dataPoint.timeStamp = SNTP.getInstance().getTime();        
        sendToSubscribers(dataPoint);
        
        Intent i = new Intent(context.getString(R.string.action_sense_new_data));
        i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
        i.putExtra(DataPoint.VALUE, json.toString());
        i.putExtra(DataPoint.SENSOR_NAME, SensorNames.SCREEN_ACTIVITY);
        i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
        i.setPackage(context.getPackageName());
        context.startService(i);
    
    }

    protected PhoneActivitySensor(Context context) {
        this.context = context;
    }

    public void startPhoneActivitySensing(long sampleDelay) {
        IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_ON);        
        filter.addAction(Intent.ACTION_SCREEN_OFF);
        context.registerReceiver(screenActivityReceiver, filter);
        PowerManager pm = (PowerManager) context.getSystemService(Context.POWER_SERVICE);  
        sendData(pm.isScreenOn()?"on":"off");
    }

    public void stopPhoneActivitySensing() {
        try {
            context.unregisterReceiver(screenActivityReceiver);
        } catch (IllegalArgumentException e) {
            // probably was not registered
        }
    }
} public class ProximitySensor extends BaseSensor implements SensorEventListener,
        PeriodicPollingSensor {

    private static ProximitySensor sInstance = null;
    private static final String TAG = "ProximitySensor";
    private static final int REQ_CODE = 1;
    private static final String ACTION_STOP_SAMPLE = ProximitySensor.class.getName() + ".STOP";

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static ProximitySensor getInstance(Context context) {
        if (sInstance == null) {
            sInstance = new ProximitySensor(context);
        }
        return sInstance;
    }

    private boolean mActive;
    private Context mContext;
    private SensorManager mSensorMgr;
    private List<Sensor> mSensors;
    private WakeLock mWakeLock;
    private float mLatestValue;
    private PeriodicPollAlarmReceiver mStartSampleReceiver;
    private BroadcastReceiver mStopSampleReceiver = new BroadcastReceiver() {

        @Override
        public void onReceive(Context context, Intent intent) {
            handleLatestValue();
            stopSample();
        }
    };

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected ProximitySensor(Context context) {
        mContext = context;
        mSensorMgr = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
        mSensors = new ArrayList<Sensor>();
        if (null != mSensorMgr.getDefaultSensor(Sensor.TYPE_PROXIMITY)) {
            mSensors.add(mSensorMgr.getDefaultSensor(Sensor.TYPE_PROXIMITY));
        }
        mStartSampleReceiver = new PeriodicPollAlarmReceiver(this);
    }

    @Override
    public void doSample() {
       //Log.v(TAG, "Do sample");

        // acquire wake lock
        if (null == mWakeLock) {
            PowerManager powerMgr = (PowerManager) mContext.getSystemService(Context.POWER_SERVICE);
            mWakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
        }
        if (!mWakeLock.isHeld()) {
            mWakeLock.acquire();
        } else {
            // wake lock already held
        }

        // register as sensor listener
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_PROXIMITY) {
                // Log.d(TAG, "registering for sensor " + sensor.getName());
                mSensorMgr.registerListener(this, sensor, SensorManager.SENSOR_DELAY_NORMAL);
            }
        }

        // reset sample state
        mLatestValue = Float.NaN;

        // set alarm for stop the sample
        mContext.registerReceiver(mStopSampleReceiver, new IntentFilter(ACTION_STOP_SAMPLE));
        Intent intent = new Intent(ACTION_STOP_SAMPLE);
        PendingIntent operation = PendingIntent.getBroadcast(mContext, REQ_CODE, intent,
                PendingIntent.FLAG_CANCEL_CURRENT);
        AlarmManager alarmMgr = (AlarmManager) mContext.getSystemService(Context.ALARM_SERVICE);
        alarmMgr.set(AlarmManager.ELAPSED_REALTIME_WAKEUP, SystemClock.elapsedRealtime() + 500,
                operation);
    }

    /**
     * Accepts the latest sensor value and send it to the MsgHandler.
     */
    private void handleLatestValue() {

        // if the value is NaN, no sample was collected: assume nothing in proximity
        float value = Float.isNaN(mLatestValue) || mLatestValue > 0 ? 1 : 0;

        Sensor sensor = mSensorMgr.getDefaultSensor(Sensor.TYPE_PROXIMITY);

        try {
            notifySubscribers();
            SensorDataPoint dataPoint = new SensorDataPoint(value);
            dataPoint.sensorName = SensorNames.PROXIMITY;
            dataPoint.sensorDescription = sensor.getName();
            dataPoint.timeStamp = SNTP.getInstance().getTime();
            sendToSubscribers(dataPoint);
        } catch (Exception e) {
            Log.e(TAG, "Error in send data to subscribers in ProximitySensor");
        }

        // pass message to the MsgHandler
        Intent i = new Intent(mContext.getString(R.string.action_sense_new_data));
        i.putExtra(DataPoint.SENSOR_NAME, SensorNames.PROXIMITY);
        i.putExtra(DataPoint.SENSOR_DESCRIPTION, sensor.getName());
        i.putExtra(DataPoint.VALUE, value);
        i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
        i.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
        i.setPackage(mContext.getPackageName());
        mContext.startService(i);
    }

    /**
     * @return The delay between samples in milliseconds
     */
    @Override
    public boolean isActive() {
        return mActive;
    }

    @Override
    public void onAccuracyChanged(Sensor sensor, int accuracy) {
        // do nothing
    }

    @Override
    public void onSensorChanged(SensorEvent event) {
        Sensor sensor = event.sensor;

        if (event.values.length < 1 || sensor.getType() != Sensor.TYPE_PROXIMITY
                || Float.isNaN(event.values[0])) {
            // unusable value
            return;
        }

        // scale to meters
        mLatestValue = event.values[0];
    }

    /**
     * Sets the delay between samples. The sensor registers itself for periodic sampling bursts, and
     * unregisters after it received a sample.
     * 
     * @param sampleDelay
     *            Sample delay in milliseconds
     */
    @Override
    public void setSampleRate(long sampleDelay) {
        super.setSampleRate(sampleDelay);
        stopPolling();
        startPolling();
    }

    private void startPolling() {
        //Log.v(TAG, "Start polling");
        mStartSampleReceiver.start(mContext);
    }

    /**
     * Starts sensing by registering for updates at the Android SensorManager. The sensor registers
     * for updates at the rate specified by the sampleDelay parameter.
     * 
     * @param sampleDelay
     *            Delay between samples in milliseconds
     */
    @Override
    public void startSensing(long sampleRate) {
        //Log.v(TAG, "Start sensing");

        // check if the sensor is physically present on this phone
        boolean found = false;
        for (Sensor sensor : mSensors) {
            if (sensor.getType() == Sensor.TYPE_PROXIMITY) {
                found = true;
                break;
            }
        }
        if (!found) {
            Log.w(TAG, "No proximity sensor!");
            return;
        }

        mActive = true;
        setSampleRate(sampleRate);

        // do the first sample immediately
        doSample();
    }

    private void stopPolling() {
        //Log.v(TAG, "Stop polling");
        mStartSampleReceiver.stop(mContext);
        stopSample();
    }

    private void stopSample() {
        //Log.v(TAG, "Stop sample");

        // release wake lock
        if (null != mWakeLock && mWakeLock.isHeld()) {
            mWakeLock.release();
        }

        // unregister sensor listener
        try {
            mSensorMgr.unregisterListener(this);
        } catch (Exception e) {
            Log.e(TAG, "Failed to stop proximity field sample!", e);
        }
        // unregister broadcast receiver
        try {
            mContext.unregisterReceiver(mStopSampleReceiver);
        } catch (IllegalArgumentException e) {
            // ignore
        }
    }

    @Override
    public void stopSensing() {
        //Log.v(TAG, "Stop sensing");
        stopPolling();
        mActive = false;
    }
} public class SensePhoneState extends BaseSensor implements PeriodicPollingSensor {

    private class OutgoingCallReceiver extends BroadcastReceiver {

        @Override
        public void onReceive(Context context, Intent intent) {
            String outgoingNumber = intent.getStringExtra(Intent.EXTRA_PHONE_NUMBER);
            if (null == outgoingNumber) {
                Log.w(TAG, "Did not get outgoing number!");
            }

            try {
                JSONObject json = new JSONObject();
                json.put("state", "dialing");
                if (null != outgoingNumber) {
                    json.put("outgoingNumber", outgoingNumber);
                }
                sendDataPoint(SensorNames.CALL_STATE, json.toString(), SenseDataTypes.JSON);
            } catch (JSONException e) {
                Log.w(TAG, "Failed to create data point for outgoing call. " + e);
            }
        }
    }

    private class SensePhoneStateListener extends PhoneStateListener {

        @Override
        public void onCallStateChanged(int state, String incomingNumber) {

            JSONObject json = new JSONObject();
            try {
                switch (state) {
                case TelephonyManager.CALL_STATE_IDLE:
                    json.put("state", "idle");
                    break;
                case TelephonyManager.CALL_STATE_OFFHOOK:
                    json.put("state", "calling");
                    break;
                case TelephonyManager.CALL_STATE_RINGING:
                    json.put("state", "ringing");
                    json.put("incomingNumber", incomingNumber);
                    break;
                default:
                    Log.e(TAG, "Unexpected call state: " + state);
                    return;
                }
            } catch (JSONException e) {
                Log.e(TAG, "JSONException in onCallChanged", e);
                return;
            }

            // immediately send data point
            sendDataPoint(SensorNames.CALL_STATE, json.toString(), SenseDataTypes.JSON);
        }

        @Override
        public void onCellLocationChanged(CellLocation location) {
            // TODO: Catch listen cell location!
        }

        @Override
        public void onDataActivity(int direction) {
            // not used to prevent a loop
        }

        @Override
        public void onDataConnectionStateChanged(int state) {
            // Log.v(TAG, "Connection state changed...");

            String strState = "";
            switch (state) {
            case TelephonyManager.DATA_CONNECTED:
                // send the URL on which the phone can be reached
                String ip = "";
                try {
                    Enumeration<NetworkInterface> nis = NetworkInterface.getNetworkInterfaces();
                    while (nis.hasMoreElements()) {
                        NetworkInterface ni = nis.nextElement();
                        Enumeration<InetAddress> iis = ni.getInetAddresses();
                        while (iis.hasMoreElements()) {
                            InetAddress ia = iis.nextElement();
                            if (ni.getDisplayName().equalsIgnoreCase("rmnet0")) {
                                ip = ia.getHostAddress();
                            }
                        }
                    }
                } catch (Exception e) {
                    Log.e(TAG, "Error getting my own IP:", e);
                }
                if (ip.length() > 1) {
                    lastIp = ip;
                }

                strState = "connected";

                break;
            case TelephonyManager.DATA_CONNECTING:
                strState = "connecting";
                break;
            case TelephonyManager.DATA_DISCONNECTED:
                strState = "disconnected";
                break;
            case TelephonyManager.DATA_SUSPENDED:
                strState = "suspended";
                break;
            default:
                Log.e(TAG, "Unexpected data connection state: " + state);
                return;
            }

            lastDataConnectionState = strState;

            // check network type
            ConnectivityManager connectivityManager = (ConnectivityManager) context
                    .getSystemService(Context.CONNECTIVITY_SERVICE);
            NetworkInfo active = connectivityManager.getActiveNetworkInfo();
            String typeName;
            int type = -1;
            if (active == null)
                typeName = "none";
            else {
                typeName = active.getTypeName();
                type = active.getType();
            }

            // only send changes. Note that this method is also called when another part of the
            // state
            // changed.
            if (previousConnectionType != type) {
                previousConnectionType = type;

                // send data point immediately
                sendDataPoint(SensorNames.CONN_TYPE, typeName, SenseDataTypes.STRING);
            }
        }

        @Override
        public void onMessageWaitingIndicatorChanged(boolean unreadMsgs) {
            // Log.v(TAG, "Message waiting changed...");
            lastMsgIndicatorState = unreadMsgs;
            msgIndicatorUpdated = true;
        }

        @Override
        public void onServiceStateChanged(ServiceState serviceState) {

            JSONObject json = new JSONObject();
            try {
                switch (serviceState.getState()) {
                case ServiceState.STATE_EMERGENCY_ONLY:
                    json.put("state", "emergency calls only");
                    break;
                case ServiceState.STATE_IN_SERVICE:
                    json.put("state", "in service");
                    String number = ((TelephonyManager) context
                            .getSystemService(Context.TELEPHONY_SERVICE)).getLine1Number();
                    json.put("phone number", number);
                    break;
                case ServiceState.STATE_OUT_OF_SERVICE:
                    json.put("state", "out of service");
                    break;
                case ServiceState.STATE_POWER_OFF:
                    json.put("state", "power off");
                    break;
                }

                json.put("manualSet", serviceState.getIsManualSelection() ? true : false);

            } catch (JSONException e) {
                Log.e(TAG, "JSONException in onServiceStateChanged", e);
                return;
            }

            lastServiceState = json.toString();
        }

        @Override
        public void onSignalStrengthsChanged(SignalStrength signalStrength) {

            JSONObject json = new JSONObject();
            try {
                if (signalStrength.isGsm()) {
                    json.put("GSM signal strength", signalStrength.getGsmSignalStrength());
                    json.put("GSM bit error rate", signalStrength.getGsmBitErrorRate());
                } else {
                    json.put("CDMA dBm", signalStrength.getCdmaDbm());
                    json.put("EVDO dBm", signalStrength.getEvdoDbm());
                }
            } catch (JSONException e) {
                Log.e(TAG, "JSONException in onSignalStrengthsChanged", e);
                return;
            }

            lastSignalStrength = json.toString();
        }
    }

    private static final String TAG = "Sense PhoneStateListener";
    private static SensePhoneState instance = null;

    /**
     * Factory method to get the singleton instance.
     * 
     * @param context
     * @return instance
     */
    public static SensePhoneState getInstance(Context context) {
        if (instance == null) {
            instance = new SensePhoneState(context);
        }
        return instance;
    }

    private Context context;
    private BroadcastReceiver outgoingCallReceiver;
    private TelephonyManager telMgr;
    private boolean lastMsgIndicatorState;
    private boolean msgIndicatorUpdated = false;
    private String lastServiceState;
    private String lastSignalStrength;
    private String lastDataConnectionState;
    private String lastIp;
    private int previousConnectionType = -2; // used to detect changes in connection type
    private PhoneStateListener phoneStateListener = new SensePhoneStateListener();
    private boolean active;
    private PeriodicPollAlarmReceiver alarmReceiver;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance(Context)
     */
    protected SensePhoneState(Context context) {
        super();
        this.context = context;
        telMgr = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
        alarmReceiver = new PeriodicPollAlarmReceiver(this);
    }

    @Override
    public void doSample() {
       //Log.v(TAG, "Do sample");
        transmitLatestState();
    }

    @Override
    public boolean isActive() {
        return active;
    }

    private void sendDataPoint(String sensorName, Object value, String dataType) {
        try {
            SensorDataPoint dataPoint = new SensorDataPoint(0);
            if (dataType.equals(SenseDataTypes.BOOL)) {
                dataPoint = new SensorDataPoint((Boolean) value);
            } else if (dataType.equals(SenseDataTypes.FLOAT)) {
                dataPoint = new SensorDataPoint((Float) value);
            } else if (dataType.equals(SenseDataTypes.INT)) {
                dataPoint = new SensorDataPoint((Integer) value);
            } else if (dataType.equals(SenseDataTypes.JSON)) {
                dataPoint = new SensorDataPoint(new JSONObject((String) value));
            } else if (dataType.equals(SenseDataTypes.STRING)) {
                dataPoint = new SensorDataPoint((String) value);
            } else {
                dataPoint = null;
            }
            if (dataPoint != null) {
                notifySubscribers();
                dataPoint.sensorName = sensorName;
                dataPoint.sensorDescription = sensorName;
                dataPoint.timeStamp = SNTP.getInstance().getTime();
                sendToSubscribers(dataPoint);
            }
        } catch (Exception e) {
            Log.e(TAG, "Error sending data point to subscribers of the ZephyrBioHarness");
        }

        Intent intent = new Intent(context.getString(R.string.action_sense_new_data));
        intent.putExtra(DataPoint.SENSOR_NAME, sensorName);
        intent.putExtra(DataPoint.DATA_TYPE, dataType);
        if (dataType.equals(SenseDataTypes.BOOL)) {
            intent.putExtra(DataPoint.VALUE, (Boolean) value);
        } else if (dataType.equals(SenseDataTypes.FLOAT)) {
            intent.putExtra(DataPoint.VALUE, (Float) value);
        } else if (dataType.equals(SenseDataTypes.INT)) {
            intent.putExtra(DataPoint.VALUE, (Integer) value);
        } else if (dataType.equals(SenseDataTypes.JSON)) {
            intent.putExtra(DataPoint.VALUE, (String) value);
        } else if (dataType.equals(SenseDataTypes.STRING)) {
            intent.putExtra(DataPoint.VALUE, (String) value);
        } else {
            Log.w(TAG, "Error sending data point: unexpected data type! '" + dataType + "'");
        }
        intent.putExtra(DataPoint.TIMESTAMP, SNTP.getInstance().getTime());
        intent.setPackage(context.getPackageName());
        context.startService(intent);
    }

    /**
     * Starts sensing and schedules periodic transmission of the phone state.
     */
    @Override
    public void startSensing(final long interval) {
        Log.v(TAG, "Start sensing");

        SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);

        // listen to events as defined in the preferences
        int events = 0;
        if (mainPrefs.getBoolean(PhoneState.CALL_STATE, true)) {
            events |= PhoneStateListener.LISTEN_CALL_STATE;

            // listen to outgoing calls
            outgoingCallReceiver = new OutgoingCallReceiver();
            context.registerReceiver(outgoingCallReceiver, new IntentFilter(
                    Intent.ACTION_NEW_OUTGOING_CALL));
        }
        if (mainPrefs.getBoolean(PhoneState.DATA_CONNECTION, true)) {
            events |= PhoneStateListener.LISTEN_DATA_CONNECTION_STATE;
        }
        if (mainPrefs.getBoolean(PhoneState.UNREAD_MSG, true)) {
            events |= PhoneStateListener.LISTEN_MESSAGE_WAITING_INDICATOR;
        }
        if (mainPrefs.getBoolean(PhoneState.SERVICE_STATE, true)) {
            events |= PhoneStateListener.LISTEN_SERVICE_STATE;
        }
        if (mainPrefs.getBoolean(PhoneState.SIGNAL_STRENGTH, true)) {
            events |= PhoneStateListener.LISTEN_SIGNAL_STRENGTHS;
        }

        // start if there actually are events that we are interested in
        if (0 != events) {

            active = true;
            setSampleRate(interval);

            // start listening to the phone state
            /* THIS TRIGGERS AN EXCEPTION WHEN STOPPING AND STARTING THE SENSOR */
            telMgr.listen(phoneStateListener, events);

            alarmReceiver.start(context);

            // do the first sample immediately
            doSample();

        } else {
            Log.w(TAG, "Phone state sensor is started but is not registered for any events");
        }

    }

    /**
     * Stops listening and stops transmission of the phone state.
     */
    @Override
    public void stopSensing() {
        Log.v(TAG, "Stop sensing");

        // listen to nothing
        telMgr.listen(phoneStateListener, PhoneStateListener.LISTEN_NONE);

        // stop periodic alarm
        alarmReceiver.stop(context);

        // clean up outgoing call receiver
        if (null != outgoingCallReceiver) {
            try {
                context.unregisterReceiver(outgoingCallReceiver);
            } catch (IllegalArgumentException e) {
                // ignore
            }
        }

        active = false;
    }

    /**
     * Transmits the latest phone state data in a new Thread.
     */
    private void transmitLatestState() {

        if (null != lastIp || null != lastDataConnectionState || true == msgIndicatorUpdated
                || null != lastServiceState || null != lastSignalStrength) {
            // Log.v(TAG, "Transmit the latest phone state...");
        }

        // IP address
        if (null != lastIp) {
            // Log.d(TAG, "Transmit IP address...");
            sendDataPoint(SensorNames.IP_ADDRESS, lastIp, SenseDataTypes.STRING);
            lastIp = null;
        }

        // data connection state
        if (null != lastDataConnectionState) {
            // Log.d(TAG, "Transmit data connection state...");
            sendDataPoint(SensorNames.DATA_CONN, lastDataConnectionState, SenseDataTypes.STRING);
            lastDataConnectionState = null;
        }

        // message waiting indicator
        if (msgIndicatorUpdated) {
            // Log.d(TAG, "Transmit unread messages indicator...");
            sendDataPoint(SensorNames.UNREAD_MSG, lastMsgIndicatorState, SenseDataTypes.BOOL);
            msgIndicatorUpdated = false;
        }

        // service state
        if (null != lastServiceState) {
            // Log.d(TAG, "Transmit service state...");
            sendDataPoint(SensorNames.SERVICE_STATE, lastServiceState, SenseDataTypes.JSON);
            lastServiceState = null;
        }

        // signal strength
        if (null != lastSignalStrength) {
            // Log.d(TAG, "Transmit signal strength...");
            sendDataPoint(SensorNames.SIGNAL_STRENGTH, lastSignalStrength, SenseDataTypes.JSON);
            lastSignalStrength = null;
        }
    }
} public class AppInfoSensor extends BaseDataProducer {
	private static final String TAG = "Sense app info sensor";
	private Context context;
	private static AppInfoSensor instance = null;
	
	private static final String ACTION_AFTER_LOGIN = "nl.sense_os.service.phonestate.AFTER_LOGIN";
	
//	public AppInfoSensor() {}
	
	protected AppInfoSensor(Context context) {
		this.context = context;
	}
	
	public static AppInfoSensor getInstance(Context context) {
		if(instance == null)
			instance = new AppInfoSensor(context);
		return instance;
	}
	
	private BroadcastReceiver appInfoReceiver = new BroadcastReceiver() {
		@Override
		public void onReceive(Context context, Intent intent) {
			Log.d(TAG,"Received app info broadcast, will send sensor data.");
			
			String action = intent.getAction();
			
			//Language settings were changed
			if(action.equals(Intent.ACTION_LOCALE_CHANGED)) {
				Log.d(TAG,"Locale was changed in settings, sending app info to sensor");
				sendAppInfo();
			}
			
			//Package was updated/replaced
			if(action.equals(Intent.ACTION_PACKAGE_REPLACED)
					&& intent.getData().getSchemeSpecificPart().equals(context.getPackageName())) {
				Log.d(TAG,"Application was updated, sending app info to sensor");
				sendAppInfo();
			}
			
			//Sensing has started (user has logged in)
			if(action.equals(ACTION_AFTER_LOGIN)) {
				Log.d(TAG,"User has logged in, sending app info to sensor");
				sendAppInfo();
			}
		}
		
		private void sendAppInfo() {
			String appBuild              = ""; //set below
			String appName               = ""; //set below
			String appVersion            = ""; //set below
			String locale                = context.getResources().getConfiguration().locale.toString();
			String os                    = "Android";
			String os_version            = android.os.Build.VERSION.RELEASE;
			String device_model          = android.os.Build.MODEL;
			String sense_library_version = AppInfoVersion.SENSE_LIBRARY_VERSION;
			String sense_cortex_version  = AppInfoVersion.CORTEX_VERSION;
			
			try{
				PackageInfo pInfo = context.getPackageManager().getPackageInfo(context.getPackageName(),0);
				appName = pInfo.packageName;
				appBuild = Integer.toString(pInfo.versionCode); //TODO change this to simply versionName?
			    appVersion = pInfo.versionName;
			} catch(NameNotFoundException e) {
				Log.e(TAG, "Cannot get package info. Will send empty application name / version.");
			}
			
			JSONObject json = new JSONObject();
			try {
				json.put("app_build",    appBuild);
				json.put("app_name",     appName);
				json.put("app_version",  appVersion);
				json.put("locale",       locale);
				json.put("os",           os);
				json.put("os_version",   os_version);
				json.put("device_model", device_model);
				json.put("sense_library_version", sense_library_version);
				json.put("sense_cortex_version",  sense_cortex_version);
			} catch(JSONException e) {
				Log.e(TAG,"JSON Exception occurred while sending app info.");
			}
			
			notifySubscribers();
        	SensorDataPoint dataPoint = new SensorDataPoint(json);
        	dataPoint.sensorName = SensorNames.APP_INFO_SENSOR;
        	dataPoint.sensorDescription = SensorNames.APP_INFO_SENSOR;
        	dataPoint.timeStamp = SNTP.getInstance().getTime();        
        	Log.d(TAG,"Going to sendToSubscribers");
        	sendToSubscribers(dataPoint);
        	
            Intent i = new Intent(context.getString(R.string.action_sense_new_data));
            i.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.JSON);
            i.putExtra(DataPoint.VALUE, json.toString());
            i.putExtra(DataPoint.SENSOR_NAME, SensorNames.APP_INFO_SENSOR);
            i.putExtra(DataPoint.TIMESTAMP, dataPoint.timeStamp);
            i.setPackage(context.getPackageName());
            context.startService(i);
		}
	};
	
	public void startAppInfoSensing() {
		//listen for when the user has changed language settings,
		//or when the app is updated through Play Store (these two
		//actions do not necessarily require the user to re-log)
		IntentFilter filter = new IntentFilter(Intent.ACTION_LOCALE_CHANGED);
		filter.addAction(Intent.ACTION_PACKAGE_REPLACED);
		context.registerReceiver(appInfoReceiver, filter);
		
		//Also immediately send the app info (this happens e.g. after first login, reboot, etc)
		Intent intent = new Intent(ACTION_AFTER_LOGIN);
		context.sendBroadcast(intent);
	}
	
	public void stopAppInfoSensing() {
		try{
			context.unregisterReceiver(appInfoReceiver);
		} catch(Exception e) {
			Log.e(TAG, e.getMessage());
		}
	}
} public class SNTP {
    private static final String TAG = "SNTP";

    private static final int ORIGINATE_TIME_OFFSET = 24;
    private static final int RECEIVE_TIME_OFFSET = 32;
    private static final int TRANSMIT_TIME_OFFSET = 40;
    private static final int NTP_PACKET_SIZE = 48;

    private static final int NTP_PORT = 123;
    private static final int NTP_MODE_CLIENT = 3;
    private static final int NTP_VERSION = 3;
    public static final String HOST_WORLDWIDE = "pool.ntp.org";
    public static final String HOST_ASIA = "asia.pool.ntp.org";
    public static final String HOST_EUROPE = "europe.pool.ntp.org";
    public static final String HOST_NORTH_AMERICA = "north-america.pool.ntp.org";
    public static final String HOST_SOUTH_AMERICA = "south-america.pool.ntp.org";
    public static final String HOST_OCEANIA = "oceania.pool.ntp.org";
    private boolean useSimulateTime = false;

    // Number of seconds between Jan 1, 1900 and Jan 1, 1970
    // 70 years plus 17 leap days
    private static final long OFFSET_1900_TO_1970 = ((365L * 70L) + 17L) * 24L * 60L * 60L;

    // system time computed from NTP server response
    private long mNtpTime;

    // value of SystemClock.elapsedRealtime() corresponding to mNtpTime
    private long mNtpTimeReference;

    // round trip time in milliseconds
    private long mRoundTripTime;

    private long clockOffset = -1;

    private static SNTP sntp = null;

    /**
     * Returns whether time simulation is used
     * @return True is simulate time is being used
     */
    public boolean isSimulatingTime()
    {
        return useSimulateTime;
    }

    /**
     * Sets the current simulation time
     *
     * Will set the clock to the provided simulation time.<br>
     * #getTime will give the updated time based on this simulation time<br>
     * @param time The epoch time in ms to use as current time
     */
    public void setCurrentSimulationTime(long time)
    {
        clockOffset = time - System.currentTimeMillis();
        useSimulateTime = true;
    }

    /**
     * Disables the time simulation
     */
    public void disableTimeSimulation()
    {
        if(!useSimulateTime)
            return;
        clockOffset = -1;
        useSimulateTime = false;
    }
    /*
     * Nice singleton :)
     */
    public synchronized static SNTP getInstance() {
        if (sntp == null)
            sntp = new SNTP();
        return sntp;
    }

    /**
     * Sends an SNTP request to the given host and processes the response.
     * 
     * @param host
     *            host name of the server.
     * @param timeout
     *            network timeout in milliseconds.
     * @return true if the transaction was successful.
     */
    public boolean requestTime(String host, int timeout) {
	try {
	    DatagramSocket socket = new DatagramSocket();
	    socket.setSoTimeout(timeout);
	    InetAddress address = InetAddress.getByName(host);
	    byte[] buffer = new byte[NTP_PACKET_SIZE];
	    DatagramPacket request = new DatagramPacket(buffer, buffer.length, address, NTP_PORT);

	    // set mode = 3 (client) and version = 3
	    // mode is in low 3 bits of first byte
	    // version is in bits 3-5 of first byte
	    buffer[0] = NTP_MODE_CLIENT | (NTP_VERSION << 3);

	    // get current time and write it to the request packet
	    long requestTime = System.currentTimeMillis();
	    long requestTicks = SystemClock.elapsedRealtime();
	    writeTimeStamp(buffer, TRANSMIT_TIME_OFFSET, requestTime);

	    socket.send(request);

	    // read the response
	    DatagramPacket response = new DatagramPacket(buffer, buffer.length);
	    socket.receive(response);
	    long responseTicks = SystemClock.elapsedRealtime();
	    long responseTime = requestTime + (responseTicks - requestTicks);
	    socket.close();

	    // extract the results
	    long originateTime = readTimeStamp(buffer, ORIGINATE_TIME_OFFSET);
	    long receiveTime = readTimeStamp(buffer, RECEIVE_TIME_OFFSET);
	    long transmitTime = readTimeStamp(buffer, TRANSMIT_TIME_OFFSET);
	    long roundTripTime = responseTicks - requestTicks - (transmitTime - receiveTime);
	    // receiveTime = originateTime + transit + skew
	    // responseTime = transmitTime + transit - skew
	    // clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime))/2
	    // = ((originateTime + transit + skew - originateTime) +
	    // (transmitTime - (transmitTime + transit - skew)))/2
	    // = ((transit + skew) + (transmitTime - transmitTime - transit + skew))/2
	    // = (transit + skew - transit + skew)/2
	    // = (2 * skew)/2 = skew
	    clockOffset = ((receiveTime - originateTime) + (transmitTime - responseTime)) / 2;
	    // if (Config.LOGD) Log.d(TAG, "round trip: " + roundTripTime + " ms");
	    // if (Config.LOGD) Log.d(TAG, "clock offset: " + clockOffset + " ms");

	    // save our results - use the times on this side of the network latency
	    // (response rather than request time)
	    mNtpTime = responseTime + clockOffset;
	    mNtpTimeReference = responseTicks;
	    mRoundTripTime = roundTripTime;
	} catch (Exception e) {
	    Log.w(TAG, "Request time failed: " + e);
	    return false;
	}

	return true;
    }

    /**
     * Get the current time, based on NTP clockOffset if or System time
     * 
     * @return the ntp time from the worldwide pool if available else the system time
     */
    public long getTime() {
	// get the clock offset
	if (clockOffset == -1) {
	    if (requestTime(HOST_WORLDWIDE, 1000)) {
		return getNtpTime();
	    } else {
		clockOffset = 0;
	    }
	}

	return System.currentTimeMillis() + clockOffset;
    }

    /**
     * Returns the time computed from the NTP transaction.
     * 
     * @return time value computed from NTP server response.
     */
    public long getNtpTime() {
    	//Log.d(TAG,"SNTP time requested");
    	//return 1412175540000l;
    	return mNtpTime;
    }

    /**
     * Returns the reference clock value (value of SystemClock.elapsedRealtime()) corresponding to
     * the NTP time.
     * 
     * @return reference clock corresponding to the NTP time.
     */
    public long getNtpTimeReference() {
	return mNtpTimeReference;
    }

    /**
     * Returns the round trip time of the NTP transaction
     * 
     * @return round trip time in milliseconds.
     */
    public long getRoundTripTime() {
	return mRoundTripTime;
    }

    /**
     * Reads an unsigned 32 bit big endian number from the given offset in the buffer.
     */
    private long read32(byte[] buffer, int offset) {
	byte b0 = buffer[offset];
	byte b1 = buffer[offset + 1];
	byte b2 = buffer[offset + 2];
	byte b3 = buffer[offset + 3];

	// convert signed bytes to unsigned values
	int i0 = ((b0 & 0x80) == 0x80 ? (b0 & 0x7F) + 0x80 : b0);
	int i1 = ((b1 & 0x80) == 0x80 ? (b1 & 0x7F) + 0x80 : b1);
	int i2 = ((b2 & 0x80) == 0x80 ? (b2 & 0x7F) + 0x80 : b2);
	int i3 = ((b3 & 0x80) == 0x80 ? (b3 & 0x7F) + 0x80 : b3);

	return ((long) i0 << 24) + ((long) i1 << 16) + ((long) i2 << 8) + (long) i3;
    }

    /**
     * Reads the NTP time stamp at the given offset in the buffer and returns it as a system time
     * (milliseconds since January 1, 1970).
     */
    private long readTimeStamp(byte[] buffer, int offset) {
	long seconds = read32(buffer, offset);
	long fraction = read32(buffer, offset + 4);
	return ((seconds - OFFSET_1900_TO_1970) * 1000) + ((fraction * 1000L) / 0x100000000L);
    }

    /**
     * Writes system time (milliseconds since January 1, 1970) as an NTP time stamp at the given
     * offset in the buffer.
     */
    private void writeTimeStamp(byte[] buffer, int offset, long time) {
	long seconds = time / 1000L;
	long milliseconds = time - seconds * 1000L;
	seconds += OFFSET_1900_TO_1970;

	// write seconds in big endian format
	buffer[offset++] = (byte) (seconds >> 24);
	buffer[offset++] = (byte) (seconds >> 16);
	buffer[offset++] = (byte) (seconds >> 8);
	buffer[offset++] = (byte) (seconds >> 0);

	long fraction = milliseconds * 0x100000000L / 1000L;
	// write fraction in big endian format
	buffer[offset++] = (byte) (fraction >> 24);
	buffer[offset++] = (byte) (fraction >> 16);
	buffer[offset++] = (byte) (fraction >> 8);
	// low order bits should be random data
	buffer[offset++] = (byte) (Math.random() * 255.0);
    }
} public class SubscriptionManager {

    /** Singleton instance */
    private static SubscriptionManager sInstance;

    /**
     * Factory method for singleton pattern
     * 
     * @return The singleton instance of the subscription manager
     */
    public static SubscriptionManager getInstance() {
        if (null == sInstance) {
            sInstance = new SubscriptionManager();
        }
        return sInstance;
    }

    /** All DataConsumers, mapped by the name of the producer they are subscribed to. */
    private Map<String, List<DataConsumer>> mConsumers;

    /** All registered DataProducers, mapped by their name. */
    private Map<String, List<DataProducer>> mProducers;

    /**
     * Constructor.
     * 
     * @param context
     * @see #getInstance()
     */
    protected SubscriptionManager() {
        mConsumers = new HashMap<String, List<DataConsumer>>();
        mProducers = new HashMap<String, List<DataProducer>>();
    }

    /**
     * @param name
     *            The name of the DataProducer
     * @return A List of data producers that are registered under the given sensor name
     */
    public List<DataProducer> getRegisteredProducers(String name) {
        List<DataProducer> original = mProducers.get(name);
        if (null != original) {
            // return a copy of the list
            return new ArrayList<DataProducer>(original);
        } else {
            return null;
        }
    }

    /**
     * @param name
     *            The name of the DataConsumer
     * @return A List of data consumers that are subscribed to the given sensor name
     */
    public List<DataConsumer> getSubscribedConsumers(String name) {
        List<DataConsumer> original = mConsumers.get(name);
        if (null != original) {
            // return a copy of the list
            return new ArrayList<DataConsumer>(original);
        } else {
            return null;
        }
    }

    /**
     * @param name
     *            The name of the DataProducer
     * @param consumer
     *            The data consumer instance to check for
     * @return <code>true</code> if this consumer is already subscribed for producers with this name
     */
    public boolean isConsumerSubscribed(String name, DataConsumer consumer) {

        if(consumer == null)
            return false;

        // check if data consumer is in the list of subscribed consumers
        List<DataConsumer> consumers = getSubscribedConsumers(name);
        if (null != consumers) {
            for (DataConsumer registeredConsumer : consumers) {
                if (registeredConsumer != null && registeredConsumer.equals(consumer)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * @param name
     *            The name of the DataProducer
     * @return true if any data producer is already registered under this sensor name
     */
    public boolean isProducerRegistered(String name) {
        return mProducers.containsKey(name);
    }

    /**
     * @param name
     *            The name of the DataProducer
     * @param producer
     *            The DataProducer instance to check for
     * @return true if the data producer is already registered under this sensor name
     */
    public boolean isProducerRegistered(String name, DataProducer producer) {

        if(producer == null)
            return false;

        // check if producer is already in the list of registered producers
        List<DataProducer> producers = getRegisteredProducers(name);
        if (null != producers) {
            for (DataProducer registeredProducer : producers) {
                if (registeredProducer!= null && registeredProducer.equals(producer)) {
                    return true;
                }
            }
        }
        return false;
    }

    /**
     * <p>
     * Registers a DataProducer with the given name at the SenseService.
     * </p>
     * <p>
     * When a data producer is registered, data consumers can subscribe to its sensor data.
     * Registering a data producer with an existing name will add the new data producer only if this
     * data producer is a different instance from the other data producer.
     * </p>
     * 
     * @param name
     *            The name of the data producer
     * @param producer
     *            The data producer
     */
    public synchronized void registerProducer(String name, DataProducer producer) {

        if (isProducerRegistered(name, producer)) {
            // data producer is already registered
            return;
        }

        if(producer == null)
            return;

        // add the producer to the list of registered producers
        List<DataProducer> producers = getRegisteredProducers(name);
        if (null == producers) {
            producers = new ArrayList<DataProducer>();
        }
        producers.add(producer);
        mProducers.put(name, producers);

        // see if there are any data consumers subscribed to this sensor name
        List<DataConsumer> subscribers = getSubscribedConsumers(name);
        if (null != subscribers) {
            // subscribe existing DataProcessors to the new DataProducer
            for (DataConsumer subscriber : subscribers) {
                if(subscriber != null)
                    producer.addSubscriber(subscriber);
            }
        }
    }

    /**
     * <p>
     * Subscribes a DataConsumer to a DataProducer
     * </p>
     * <p>
     * This method subscribes a DataProcessor to receive SensorDataPoints from a DataProducer. If
     * the DataProducer with name to subscribe to is not registered yet then the data processor will
     * be put in the queue and will be subscribed to the DataProducer when it is registered.
     * </p>
     * 
     * @param name
     *            The name of the registered DataProducer
     * @param consumer
     *            The DataConsumer that receives the sensor data
     * @return true if the DataConsumer successfully subscribed to the DataProducer.
     */
    public synchronized boolean subscribeConsumer(String name, DataConsumer consumer) {

        if (isConsumerSubscribed(name, consumer)) {
            // consumer is already subscribed
            return true;
        }

        if(consumer == null)
            return false;

        // add the consumer to the list of subscribed consumers
        List<DataConsumer> consumers = getSubscribedConsumers(name);
        if (null == consumers) {
            consumers = new ArrayList<DataConsumer>();
        }
        consumers.add(consumer);
        mConsumers.put(name, consumers);

        // see if there are any producers for this sensor name
        List<DataProducer> producers = getRegisteredProducers(name);
        if (producers == null) {
            // nothing to subscribe to (yet)
            return true;
        } else {
            // subscribe the new processor to the existing producers
            boolean subscribed = false;
            for (DataProducer producer : producers) {
                if(producer != null)
                    subscribed |= producer.addSubscriber(consumer);
            }
            return subscribed;
        }
    }

    /**
     * Unregisters a DataProducer.<br/>
     * <br/>
     * No new data consumers can subscribe to the DataProducer anymore, but DataConsumer which have
     * already subscribed to the DataProducer will remain subscribed.
     * 
     * @param name
     *            The name that the DataProducer is registered under
     * @param producer
     *            The DataProducer to unregister
     */
    public synchronized void unregisterProducer(String name, DataProducer producer) {

        if (!mProducers.containsKey(name)) {
            // this producer is not registered under this name
            return;
        }

        // remove the producer from the list of registered producers for this sensor name
        List<DataProducer> dataProducers = getRegisteredProducers(name);
        if (null == dataProducers) {
            // apparently the list is already gone
            return;
        }
        dataProducers.remove(producer);

        // update the map of registered producers
        if (dataProducers.size() == 0) {
            mProducers.remove(name);
        } else {
            mProducers.put(name, dataProducers);
        }
    }

    /**
     * Unsubscribes a data consumer from a data producer.
     * 
     * @param name
     *            The name of the DataProducer that the consumer registered for
     * @param consumer
     *            The DataConsumer that receives the sensor data
     */
    public synchronized void unsubscribeConsumer(String name, DataConsumer consumer) {

        if (!mConsumers.containsKey(name)) {
            // there are no consumers registered for this name (?)
            return;
        }

        // remove the consumer from the list of subscribed consumer
        List<DataConsumer> consumers = getSubscribedConsumers(name);
        if (null == consumers) {
            // apparently the list is already gone
            return;
        }
        consumers.remove(consumer);

        // update the map of consumers
        if (consumers.size() == 0) {
            mConsumers.remove(name);
        } else {
            mConsumers.put(name, consumers);
        }

        // unsubscribe the consumer from the producers for this sensor name
        List<DataProducer> producers = getRegisteredProducers(name);
        if (null != producers) {
            for (DataProducer registeredProducer : producers) {
                if(registeredProducer != null)
                    registeredProducer.removeSubscriber(consumer);
            }
        }
    }
} public class LocalStorage {

    /**
     * Minimum time to retain data points. If data is not sent to CommonSense, it will be retained
     * longer.
     */
  private static final int DEFAULT_RETENTION_HOURS = 24;

    /**
     * Default projection for rows of data points
     */
    private static final String[] DEFAULT_PROJECTION = new String[] { BaseColumns._ID,
            DataPoint.SENSOR_NAME, DataPoint.DISPLAY_NAME, DataPoint.SENSOR_DESCRIPTION,
            DataPoint.DATA_TYPE, DataPoint.VALUE, DataPoint.TIMESTAMP, DataPoint.DEVICE_UUID,
            DataPoint.TRANSMIT_STATE };

    private static final int LOCAL_VALUES_URI = 1;
    private static final int REMOTE_VALUES_URI = 2;

    private static final String TAG = "LocalStorage";

    private static final int DEFAULT_LIMIT = 100;

    private static LocalStorage instance;

    /**
     * @param context
     *            Context for lazy creating the LocalStorage.
     * @return Singleton instance of the LocalStorage
     */
    public static LocalStorage getInstance(Context context) {
        // Log.v(TAG, "Get local storage instance");
        if (null == instance) {
            instance = new LocalStorage(context.getApplicationContext());
        }
        return instance;
    }

    private final RemoteStorage commonSense;
    private final SQLiteStorage inMemory;
    private final SQLiteStorage persisted;

    private Context context;

    private LocalStorage(Context context) {
        Log.i(TAG, "Construct new local storage instance");
        this.context = context;
        persisted = new SQLiteStorage(context, true);
        inMemory = new SQLiteStorage(context, false);
        commonSense = new RemoteStorage(context);
    }

    public int delete(Uri uri, String where, String[] selectionArgs) {
        switch (matchUri(uri)) {
        case LOCAL_VALUES_URI:
            int nrDeleted = 0;
            nrDeleted += inMemory.delete(where, selectionArgs);
            nrDeleted += persisted.delete(where, selectionArgs);
            return nrDeleted;
        case REMOTE_VALUES_URI:
            throw new IllegalArgumentException("Cannot delete values from CommonSense!");
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    /**
     * Removes old data from the persistent storage.
     * 
     * @return The number of data points deleted
     */
    private int deleteOldData() {
        Log.i(TAG, "Delete old data points from persistent storage");


        SharedPreferences prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
                Context.MODE_PRIVATE);
        // set max retention time
        int retentionHours = prefs.getInt( Main.Advanced.RETENTION_HOURS, DEFAULT_RETENTION_HOURS );
        long retentionLimit = SNTP.getInstance().getTime() - getMilliSencondsOfHours( retentionHours );
        // check preferences to see if the data needs to be sent to CommonSense
        boolean useCommonSense = prefs.getBoolean(Main.Advanced.USE_COMMONSENSE, true);

        String where = null;
        if (useCommonSense) {
            // delete data older than maximum retention time if it had been transmitted
            where = DataPoint.TIMESTAMP + "<" + retentionLimit + " AND " + DataPoint.TRANSMIT_STATE
                    + "==1";
        } else {
            // not using CommonSense: delete all data older than maximum retention time
            where = DataPoint.TIMESTAMP + "<" + retentionLimit;
        }
        int deleted = persisted.delete(where, null);

        return deleted;
    }

    public String getType(Uri uri) {
        int uriType = matchUri(uri);
        if (uriType == LOCAL_VALUES_URI || uriType == REMOTE_VALUES_URI) {
            return DataPoint.CONTENT_TYPE;
        } else {
            throw new IllegalArgumentException("Unknown URI " + uri);
        }
    }

    public Uri insert(Uri uri, ContentValues values) {

        // check the URI
        switch (matchUri(uri)) {
        case LOCAL_VALUES_URI:
            // implementation below
            break;
        case REMOTE_VALUES_URI:
            throw new IllegalArgumentException(
                    "Cannot insert into CommonSense through this ContentProvider");
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        // insert in the in-memory database
        long rowId = 0;
        try {
            rowId = inMemory.insert(values);
        } catch (BufferOverflowException e) {
            // in-memory storage is full!
            deleteOldData();
            persistRecentData();

            // try again
            rowId = inMemory.insert(values);
        }

        // notify any listeners (does this work properly?)
        Uri contentUri = Uri.parse("content://"
                + context.getString(R.string.local_storage_authority) + DataPoint.CONTENT_URI_PATH);
        Uri rowUri = ContentUris.withAppendedId(contentUri, rowId);
        context.getContentResolver().notifyChange(rowUri, null);

        return rowUri;
    }

    private int matchUri(Uri uri) {
        if (DataPoint.CONTENT_URI_PATH.equals(uri.getPath())) {
            return LOCAL_VALUES_URI;
        } else if (DataPoint.CONTENT_REMOTE_URI_PATH.equals(uri.getPath())) {
            return REMOTE_VALUES_URI;
        } else {
            return -1;
        }
    }

    private int persistRecentData() {
        Log.i(TAG, "Persist recent data points from in-memory storage");

        Cursor recentPoints = null;
        int nrRecentPoints = 0;
        try {
            SharedPreferences prefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS, Context.MODE_PRIVATE);
            int retentionHours = prefs.getInt( Main.Advanced.RETENTION_HOURS, DEFAULT_RETENTION_HOURS );
            long retentionLimit = SNTP.getInstance().getTime() - getMilliSencondsOfHours( retentionHours );

            // get unsent or very recent data from the memory
            String selectUnsent = DataPoint.TRANSMIT_STATE + "!=1" + " OR " + DataPoint.TIMESTAMP
                    + ">" + retentionLimit;
            recentPoints = inMemory.query(DEFAULT_PROJECTION, selectUnsent, null, null);
            nrRecentPoints = recentPoints.getCount();

            // bulk insert the new data
            persisted.bulkInsert(recentPoints);

            // remove all the in-memory data
            inMemory.delete(null, null);

        } finally {
            if (null != recentPoints) {
                recentPoints.close();
                recentPoints = null;
            }
        }
        return nrRecentPoints;
    }

    public Cursor query(Uri uri, String[] projection, String where, String[] selectionArgs,
            String sortOrder) {
        return query(uri, projection, where, selectionArgs, DEFAULT_LIMIT, sortOrder);
    }

    public Cursor query(Uri uri, String[] projection, String where, String[] selectionArgs,
            int limit, String sortOrder) {
        // Log.v(TAG, "Query data points in local storage");

        // check URI
        switch (matchUri(uri)) {
        case LOCAL_VALUES_URI:
            // implementation below
            break;
        case REMOTE_VALUES_URI:
            try {
                return commonSense.query(uri, projection, where, selectionArgs, limit, sortOrder);
            } catch (Exception e) {
                Log.e(TAG, "Failed to query the CommonSense data points", e);
                return null;
            }
        default:
            Log.e(TAG, "Unknown URI: " + uri);
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        // use default projection if needed
        if (projection == null) {
            projection = DEFAULT_PROJECTION;
        }

        // query both databases
        Cursor inMemoryCursor = inMemory.query(projection, where, selectionArgs, sortOrder);
        Cursor persistedCursor = persisted.query(projection, where, selectionArgs, sortOrder);

        if (inMemoryCursor.getCount() > 0) {
            if (persistedCursor.getCount() > 0) {
                // merge cursors
                if (sortOrder != null && sortOrder.toLowerCase(Locale.ENGLISH).contains("desc")) {
                    // assume that data from inMemoryCursor is newer than from persistedCursor
                    return new MergeCursor(new Cursor[] { inMemoryCursor, persistedCursor });
                } else {
                    // assume that data from persistedCursor is newer than from inMemoryCursor
                    return new MergeCursor(new Cursor[] { persistedCursor, inMemoryCursor });
                }
            } else {
                persistedCursor.close();
                return inMemoryCursor;
            }

        } else {
            inMemoryCursor.close();
            return persistedCursor;
        }
    }

    public int update(Uri uri, ContentValues newValues, String where, String[] selectionArgs) {

        // check URI
        switch (matchUri(uri)) {
        case LOCAL_VALUES_URI:
            // implementation below
            break;
        case REMOTE_VALUES_URI:
            throw new IllegalArgumentException("Cannot update data points in CommonSense");
        default:
            throw new IllegalArgumentException("Unknown URI " + uri);
        }

        // persist parameter is used to initiate persisting the volatile data
        boolean persist = "true".equals(uri.getQueryParameter("persist"));

        int result = 0;
        if (!persist) {
            // Log.v(TAG, "Update data points in local storage");
            int updated = 0;
            updated += inMemory.update(newValues, where, selectionArgs);
            updated += persisted.update(newValues, where, selectionArgs);
            return updated;
        } else {
            deleteOldData();
            persistRecentData();
        }

        // notify content observers
        context.getContentResolver().notifyChange(uri, null);

        return result;
    }
    
    public static long getMilliSencondsOfHours(int hours){
        return 1000l* 60 * 60 * hours;
    }

}
import java.net.URLEncoder;
import java.util.Map;
import nl.sense_os.service.ambience.CameraLightSensor;
import nl.sense_os.service.ambience.HumiditySensor;
import nl.sense_os.service.ambience.LightSensor;
import nl.sense_os.service.ambience.MagneticFieldSensor;
import nl.sense_os.service.ambience.NoiseSensor;
import nl.sense_os.service.ambience.PressureSensor;
import nl.sense_os.service.ambience.TemperatureSensor;
import nl.sense_os.service.commonsense.DefaultSensorRegistrationService;
import nl.sense_os.service.commonsense.SenseApi;
import nl.sense_os.service.constants.SensePrefs;
import nl.sense_os.service.constants.SensePrefs.Auth;
import nl.sense_os.service.constants.SensePrefs.Main.Advanced;
import nl.sense_os.service.constants.SensePrefs.Main.Ambience;
import nl.sense_os.service.constants.SensePrefs.Main.External;
import nl.sense_os.service.constants.SensePrefs.Main.Location;
import nl.sense_os.service.constants.SensePrefs.Main.PhoneState;
import nl.sense_os.service.constants.SensePrefs.Status;
import nl.sense_os.service.constants.SenseUrls;
import nl.sense_os.service.constants.SensorData.SensorNames;
import nl.sense_os.service.ctrl.Controller;
import nl.sense_os.service.deviceprox.DeviceProximity;
import nl.sense_os.service.external_sensors.NewOBD2DeviceConnector;
import nl.sense_os.service.external_sensors.ZephyrBioHarness;
import nl.sense_os.service.external_sensors.ZephyrHxM;
import nl.sense_os.service.location.FusedLocationSensor;
import nl.sense_os.service.location.LocationSensor;
import nl.sense_os.service.location.TimeZoneSensor;
import nl.sense_os.service.motion.MotionSensor;
import nl.sense_os.service.phonestate.AppsSensor;
import nl.sense_os.service.phonestate.BatterySensor;
import nl.sense_os.service.phonestate.PhoneActivitySensor;
import nl.sense_os.service.phonestate.ProximitySensor;
import nl.sense_os.service.phonestate.SensePhoneState;
import nl.sense_os.service.phonestate.AppInfoSensor;
import nl.sense_os.service.provider.SNTP;
import nl.sense_os.service.scheduler.ScheduleAlarmTool;
import nl.sense_os.service.subscription.SubscriptionManager;
import nl.sense_os.service.storage.LocalStorage;
import org.json.JSONObject;
import android.app.Activity;
import android.app.Notification;
import android.app.Service;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.SharedPreferences.Editor;
import android.content.pm.PackageInfo;
import android.os.Binder;
import android.os.Build;
import android.os.Handler;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
/**************************************************************************************************
 * Copyright (C) 2010 Sense Observation Systems, Rotterdam, the Netherlands. All rights reserved. *
 *************************************************************************************************/
package nl.sense_os.service;





/**
 * Main Sense service class.<br/>
 * <br/>
 * Activities can bind to this service and call functions to:
 * <ul>
 * <li>log in;</li>
 * <li>register;</li>
 * <li>start sensing;</li>
 * <li>start/stop individual sensor modules;</li>
 * <li>set and get properties;</li>
 * </ul>
 * When the {@link #toggleMain(boolean)} method is called to start the sensing, the service starts
 * itself and registers itself as a foreground service so it does not get easily killed by Android.
 * 
 * @author Steven Mulder <steven@sense-os.nl>
 */
public class SenseService extends Service {

    /**
     * Class used for the client Binder. Because we know this service always runs in the same
     * process as its clients, we don't need to deal with IPC.
     * 
     * @see http://developer.android.com/guide/components/bound-services.html
     */
    public class SenseBinder extends Binder {

        public SenseServiceStub getService() {
            return new SenseServiceStub(SenseService.this);
        }
    }

    private static final String TAG = "Sense Service";

    /**
     * Intent action to force a re-login attempt when the service is started.
     */
    public static final String EXTRA_RELOGIN = "relogin";
    
    /**
     * Intent action to notify that the service is started, 
     * boolean extra for of the status changed broadcast  R.string.action_sense_service_broadcast.
     */
    public static final String EXTRA_SERVICE_STARTED = "service_started";

    private IBinder binder = new SenseBinder();

    private ServiceStateHelper state;

    private BatterySensor batterySensor;
    private DeviceProximity deviceProximity;
    private LightSensor lightSensor;
    private CameraLightSensor cameraLightSensor;
    private TemperatureSensor temperatureSensor;
    private HumiditySensor humiditySensor;
    private LocationSensor locListener;
    private Controller controller;
    private MotionSensor motionSensor;
    private NoiseSensor noiseSensor;
    private PhoneActivitySensor phoneActivitySensor;
    private PressureSensor pressureSensor;
    private ProximitySensor proximitySensor;
    private SensePhoneState phoneStateListener;
    private ZephyrBioHarness es_bioHarness;
    private ZephyrHxM es_HxM;
    private NewOBD2DeviceConnector es_obd2sensor;
    private MagneticFieldSensor magneticFieldSensor;
    private DataTransmitter transmitter;
    private SubscriptionManager mSubscrMgr;
    private AppsSensor appsSensor;