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 BufferTransmitHandler extends Handler {

    class SensorDataEntry {
        String sensorId;
        String sensorName;
        String sensorDescription;
        JSONArray data;
    }

	private static final String TAG = "BatchDataTransmitHandler";
	private static final int MAX_POST_DATA = 100;
    private final Uri contentUri;
    private final WeakReference<Context> ctxRef;
    private final WeakReference<LocalStorage> storageRef;
	private final String url;
	private final DecimalFormatSymbols symbols = new DecimalFormatSymbols(Locale.ENGLISH);
	private final NumberFormat dateFormatter = new DecimalFormat("##########.###", symbols);

	public BufferTransmitHandler(Context context, LocalStorage storage, Looper looper) {
		super(looper);
		this.ctxRef = new WeakReference<Context>(context);
		this.storageRef = new WeakReference<LocalStorage>(storage);

        contentUri = Uri.parse("content://" + context.getString(R.string.local_storage_authority)
                + DataPoint.CONTENT_URI_PATH);

		SharedPreferences mainPrefs = context.getSharedPreferences(SensePrefs.MAIN_PREFS,
				Context.MODE_PRIVATE);
		boolean devMode = mainPrefs.getBoolean(Main.Advanced.DEV_MODE, false);
		url = devMode ? SenseUrls.SENSOR_DATA_MULTIPLE_DEV : SenseUrls.SENSOR_DATA_MULTIPLE;
	}

	/**
	 * Cleans up after transmission is over. Closes the Cursor with the data and releases the wake
	 * lock. Should always be called after transmission, even if the attempt failed.
	 * 
	 * @param cursor
	 */
	private void cleanup(Cursor cursor, WakeLock wakeLock) {
		if (null != cursor) {
			cursor.close();
			cursor = null;
		}
		if (null != wakeLock) {
			wakeLock.release();
			wakeLock = null;
		}
	}

    private List<SensorDataEntry> getSensorDataList(Cursor cursor) throws IOException,
            JSONException {

        // map of transmission entries, indexed by the sensor name and description
        Map<String, SensorDataEntry> map = new HashMap<String, SensorDataEntry>();
        String name, description, dataType, value, deviceUuid;
        long timestamp;
        int points = 0;
        while ((points < MAX_POST_DATA) && !cursor.isAfterLast()) {

            // get the data point details
            try {
                name = cursor.getString(cursor.getColumnIndexOrThrow(DataPoint.SENSOR_NAME));
                description = cursor.getString(cursor
                        .getColumnIndexOrThrow(DataPoint.SENSOR_DESCRIPTION));
                dataType = cursor.getString(cursor.getColumnIndexOrThrow(DataPoint.DATA_TYPE));
                value = cursor.getString(cursor.getColumnIndexOrThrow(DataPoint.VALUE));
                timestamp = cursor.getLong(cursor.getColumnIndexOrThrow(DataPoint.TIMESTAMP));
                deviceUuid = cursor.getString(cursor.getColumnIndexOrThrow(DataPoint.DEVICE_UUID));

                // set default sensor ID if it is missing
                deviceUuid = deviceUuid != null ? deviceUuid : SenseApi.getDefaultDeviceUuid(ctxRef
                        .get());

            } catch (IllegalArgumentException e) {
                // something is wrong with this data point, skip it
                Log.w(TAG, "Exception getting data point details from cursor: '" + e.getMessage()
                        + "'. Skip data point...");
                cursor.moveToNext();
                continue;
            }

            /*
             * "normal" data is added to the map until we reach the max amount of points
             */
            if (!dataType.equals(SenseDataTypes.FILE)) {

                // construct JSON representation of the value
                JSONObject jsonDataPoint = new JSONObject();
                jsonDataPoint.put("date", dateFormatter.format(timestamp / 1000d));
                jsonDataPoint.put("value", value);

                // put the new value Object in the appropriate sensor's data
                String key = name + description;
                SensorDataEntry sensorEntry = map.get(key);
                JSONArray data = null;
                if (sensorEntry == null) {
                    sensorEntry = new SensorDataEntry();
                    String id = SenseApi.getSensorId(ctxRef.get(), name, description, dataType,
                            deviceUuid);
                    if (null == id) {
                        // skip sensor data that does not have a sensor ID yet
                        Log.w(TAG, "cannot find sensor ID for " + name + " (" + description + ")");
                        cursor.moveToNext();
                        continue;
                    }
                    sensorEntry.sensorId = id;
                    sensorEntry.sensorName = name;
                    sensorEntry.sensorDescription = description;
                    data = new JSONArray();
                } else {
                    data = sensorEntry.data;
                }
                data.put(jsonDataPoint);
                sensorEntry.data = data;
                map.put(key, sensorEntry);

                // count the added point to the total number of sensor data
                points++;

            } else {
                // if the data type is a "file", we need special handling
                sendFile(name, description, dataType, deviceUuid, value, timestamp);

            }

            cursor.moveToNext();
        }

        return new ArrayList<BufferTransmitHandler.SensorDataEntry>(map.values());
    }

	/**
	 * @return Cursor with the data points that have to be sent to CommonSense.
	 */
    private Cursor getUnsentData() {
        try {
            String where = DataPoint.TRANSMIT_STATE + "=0";
            String sortOrder = DataPoint.TIMESTAMP + " ASC";
            Cursor unsent = storageRef.get().query(contentUri, null, where, null, sortOrder);
            if (null != unsent) {
                Log.v(TAG, "Found " + unsent.getCount() + " unsent data points in local storage");
            } else {
                Log.w(TAG, "Failed to get unsent recent data points from local storage");
            }
            return unsent;
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Error querying Local Storage!", e);
            return null;
        }
    }

	@Override
	public void handleMessage(Message msg) {

		String cookie = msg.getData().getString("cookie");

		// check if our references are still valid
		if (null == ctxRef.get() || null == storageRef.get()) {
			// parent service has died
			return;
		}

		WakeLock wakeLock = null;
		Cursor cursor = null;
		try {
			// make sure the device stays awake while transmitting
			PowerManager powerMgr = (PowerManager) ctxRef.get().getSystemService(
					Context.POWER_SERVICE);
			wakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
			wakeLock.acquire();

			cursor = getUnsentData();
            if (null != cursor && cursor.moveToFirst()) {
				transmit(cursor, cookie);
			} else {
				// nothing to transmit
			}

		} catch (Exception e) {
			if (null != e.getMessage()) {
				Log.e(TAG, "Exception sending buffered data: '" + e.getMessage()
						+ "'. Data will be resent later.");
			} else {
				Log.e(TAG, "Exception sending cursor data. Data will be resent later.", e);
			}

		} finally {
			cleanup(cursor, wakeLock);
		}
	}

    /**
     * Performs cleanup tasks after transmission was successfully completed. Should update the data
     * point records to show that they have been sent to CommonSense.
     * 
     * @param sensorDatas
     *            List of data that was sent to CommonSense. Contains all the data points that were
     *            transmitted.
     * @throws Exception
     */
    private void onTransmitSuccess(List<SensorDataEntry> sensorDatas)
            throws JSONException{
        // log our great success
        Log.i(TAG, "Sent recent sensor data from the local storage!");

        // new content values with updated transmit state
        ContentValues values = new ContentValues();
        values.put(DataPoint.TRANSMIT_STATE, 1);

        for (SensorDataEntry sensorData : sensorDatas) {

            // get the name of the sensor, to use in the ContentResolver query
            String sensorName = sensorData.sensorName;
            String description = sensorData.sensorDescription;

            // select points for this sensor, between the first and the last time stamp
            JSONArray dataPoints = sensorData.data;
            String frstTimeStamp = dataPoints.getJSONObject(0).getString("date");
            String lastTimeStamp = dataPoints.getJSONObject(dataPoints.length() - 1).getString(
                    "date");
            long min = Math.round(Double.parseDouble(frstTimeStamp) * 1000);
            long max = Math.round(Double.parseDouble(lastTimeStamp) * 1000);
            String where = DataPoint.SENSOR_NAME + "='" + sensorName + "'" + " AND "
                    + DataPoint.SENSOR_DESCRIPTION + "='" + description + "'" + " AND "
                    + DataPoint.TIMESTAMP + ">=" + min + " AND " + DataPoint.TIMESTAMP + " <="
                    + max;

            // update points in local storage
            try {
                int updated = storageRef.get().update(contentUri, values, where, null);
                if (updated == dataPoints.length()) {
                    // Log.v(TAG, "Updated all " + updated + " '" + sensorName
                    // + "' data points in the local storage");
                } else {
                    Log.w(TAG, "Wrong number of '" + sensorName
                            + "' data points updated after transmission! " + updated + " vs. "
                            + dataPoints.length());
                }
            } catch (IllegalArgumentException e) {
                Log.e(TAG, "Error updating points in Local Storage!", e);
            }
        }
    }

    /**
     * POSTs the sensor data points to the main sensor data URL at CommonSense.
     * 
     * @param cookie
     * 
     * @param transmission
     *            JSON Object with data points for transmission
     * @return true if successfully sent
     * @throws JSONException
     * @throws MalformedURLException
     */
    private boolean postData(String cookie, JSONObject transmission) throws JSONException,
            MalformedURLException {

        Map<String, String> response = null;
        try {
            response = SenseApi.request(ctxRef.get(), url, transmission, cookie);
        } catch (IOException e) {
            // handle failure later
        }

        boolean result = false;

        if (response == null) {
            // Error when sending
            Log.w(TAG, "Failed to send buffered data points.\nData will be retried later.");
            result = false;

        } else if (response.get(SenseApi.RESPONSE_CODE).compareToIgnoreCase("201") != 0) {
            // incorrect status code
            String statusCode = response.get(SenseApi.RESPONSE_CODE);

            // if un-authorized: relogin
            if (statusCode.compareToIgnoreCase("403") == 0) {
                final Intent serviceIntent = new Intent(ctxRef.get().getString(
                        R.string.action_sense_service));
                serviceIntent.putExtra(SenseService.EXTRA_RELOGIN, true);
                serviceIntent.setPackage(ctxRef.get().getPackageName());
                ctxRef.get().startService(serviceIntent);
            }

            // Show the HTTP response Code
            Log.w(TAG, "Failed to send buffered data points: " + statusCode
                    + ", Response content: '" + response.get(SenseApi.RESPONSE_CONTENT) + "'\n"
                    + "Data will be retried later");

            result = false;

        } else {
            // Data sent successfully
            result = true;
        }

        return result;
    }

	private void sendFile(String name, String description, String dataType, String deviceUuid,
            String value, long timestamp) throws JSONException {

        // create sensor data JSON object with only 1 data point
        JSONObject sensorData = new JSONObject();
        JSONArray dataArray = new JSONArray();
        JSONObject data = new JSONObject();
        data.put("value", value);
        data.put("date", dateFormatter.format(timestamp / 1000d));
        dataArray.put(data);
        sensorData.put("data", dataArray);

        // send data point through MsgHandler
        Context context = ctxRef.get();
        MsgHandler.sendSensorData(context, name, description, dataType, deviceUuid, sensorData);
    }

    /**
     * Transmits the data points from {@link #cursor} to CommonSense. Any "file" type data points
     * will be sent separately via
     * {@link MsgHandler#sendSensorData(String, String, String, JSONObject)}.
     * 
     * @param cookie
     * @param cursor
     * 
     * @throws JSONException
     * @throws IOException
     */
    private void transmit(Cursor cursor, String cookie) throws JSONException, IOException {

        // continue until all points in the cursor have been sent
        List<SensorDataEntry> sensorDataList = null;
        while (!cursor.isAfterLast()) {

            // organize the data into a hash map sorted by sensor
            sensorDataList = getSensorDataList(cursor);

            if (sensorDataList.size() < 1) {
                // nothing to transmit
                continue;
            }

            // prepare the main JSON object for transmission
            JSONArray sensors = new JSONArray();
            for (SensorDataEntry sensorDataEntry : sensorDataList) {
                JSONObject transmissionEntry = new JSONObject();
                transmissionEntry.put("sensor_id", sensorDataEntry.sensorId);
                transmissionEntry.put("sensor_name", sensorDataEntry.sensorName);
                transmissionEntry.put("data", sensorDataEntry.data);
                sensors.put(transmissionEntry);
            }
            JSONObject transmission = new JSONObject();
            transmission.put("sensors", sensors);

            // perform the actual POST request
            boolean result = postData(cookie, transmission);

            if (result) {
                onTransmitSuccess(sensorDataList);
            } else {
                // abort! abort!
                break;
            }
        }
    }
} public class DataTransmitHandler extends Handler {

    private static final String TAG = "DataTransmitHandler";
    private final WeakReference<Context> ctxRef;
    private final WeakReference<LocalStorage> storageRef;

    public DataTransmitHandler(Context context, LocalStorage storage, Looper looper) {
        super(looper);
        ctxRef = new WeakReference<Context>(context);
        storageRef = new WeakReference<LocalStorage>(storage);
    }

    private void cleanup(WakeLock wakeLock) {
        if (null != wakeLock) {
            wakeLock.release();
            wakeLock = null;
        }
    }

    @Override
    public void handleMessage(Message msg) {

        // get arguments from message
        Bundle args = msg.getData();
        String name = args.getString("name");
        String description = args.getString("description");
        String dataType = args.getString("dataType");
        String deviceUuid = args.getString("deviceUuid");
        String cookie = args.getString("cookie");
        JSONObject json = (JSONObject) msg.obj;

        // check if our references are still valid
        if (null == ctxRef.get() || null == storageRef.get()) {
            // parent service has died
            return;
        }

        WakeLock wakeLock = null;
        try {
            // make sure the device stays awake while transmitting
            PowerManager powerMgr = (PowerManager) ctxRef.get().getSystemService(
                    Context.POWER_SERVICE);
            wakeLock = powerMgr.newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, TAG);
            wakeLock.acquire();

            // get sensor URL at CommonSense
            String url = SenseApi.getSensorUrl(ctxRef.get(), name, description, dataType,
                    deviceUuid);

            if (url == null) {
                Log.w(TAG, "No sensor ID for '" + name + "' (yet): data will be retried.");
                return;
            }

            Map<String, String> response = SenseApi.request(ctxRef.get(), url, json, cookie);

            // Error when sending
            if ((response == null) || !response.get(SenseApi.RESPONSE_CODE).equals("201")) {

                // if un-authorized: relogin
                if ((response != null) && response.get(SenseApi.RESPONSE_CODE).equals("403")) {
                    final Intent serviceIntent = new Intent(ctxRef.get().getString(
                            R.string.action_sense_service));
                    serviceIntent.putExtra(SenseService.EXTRA_RELOGIN, true);
                    serviceIntent.setPackage(ctxRef.get().getPackageName());
                    ctxRef.get().startService(serviceIntent);
                }

                // Show the HTTP response Code
                if (response != null) {
                    Log.w(TAG,
                            "Failed to send '" + name + "' data. Response code:"
                                    + response.get(SenseApi.RESPONSE_CODE) + ", Response content: '"
                                    + response.get(SenseApi.RESPONSE_CONTENT) + "'\nData will be retried");
                } else {
                    Log.w(TAG, "Failed to send '" + name + "' data.\nData will be retried.");
                }
            }

            // Data sent successfully
            else {
                int bytes = json.toString().getBytes().length;
                Log.i(TAG, "Sent '" + name + "' data! Raw data size: " + bytes + " bytes");

                onTransmitSuccess(name, description, json);
            }

        } catch (Exception e) {
            if (null != e.getMessage()) {
                Log.e(TAG,
                        "Exception sending '" + name + "' data, data will be retried: "
                                + e.getMessage());
            } else {
                Log.e(TAG, "Exception sending '" + name + "' data, data will be retried.", e);
            }

        } finally {
            cleanup(wakeLock);
        }
    }

    private void onTransmitSuccess(String name, String description, JSONObject json)
            throws JSONException {
        // new content values with updated transmit state
        ContentValues values = new ContentValues();
        values.put(DataPoint.TRANSMIT_STATE, 1);

        // select points for this sensor, between the fist and the last time stamp
        JSONArray dataPoints = json.getJSONArray("data");
        String frstTimeStamp = dataPoints.getJSONObject(0).getString("date");
        String lastTimeStamp = dataPoints.getJSONObject(dataPoints.length() - 1).getString("date");
        long min = Math.round(Double.parseDouble(frstTimeStamp) * 1000);
        long max = Math.round(Double.parseDouble(lastTimeStamp) * 1000);
        String where = DataPoint.SENSOR_NAME + "='" + name + "'" + " AND "
                + DataPoint.SENSOR_DESCRIPTION + "='" + description + "'" + " AND "
                + DataPoint.TIMESTAMP + ">=" + min + " AND " + DataPoint.TIMESTAMP + " <=" + max;

        try {
            Uri contentUri = Uri.parse("content://"
                    + ctxRef.get().getString(R.string.local_storage_authority)
                    + DataPoint.CONTENT_URI_PATH);
            int updated = storageRef.get().update(contentUri, values, where, null);
            if (updated == dataPoints.length()) {
                // Log.v(TAG, "Updated all " + updated + " rows in the local storage");
            } else {
                Log.w(TAG, "Wrong number of local storage points updated! " + updated + " vs. "
                        + dataPoints.length());
            }
        } catch (IllegalArgumentException e) {
            Log.e(TAG, "Error updating points in Local Storage!", e);
        }
    }
} 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 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";
 }
 public static class DataPoint implements BaseColumns {

    public static final String CONTENT_TYPE = ContentResolver.CURSOR_DIR_BASE_TYPE
            + "/vnd.sense_os.data_point";
    public static final String CONTENT_ITEM_TYPE = ContentResolver.CURSOR_ITEM_BASE_TYPE
            + "/vnd.sense_os.data_point";
    public static final String CONTENT_URI_PATH = "/recent_values";
    @Deprecated
    public static final String CONTENT_PERSISTED_URI_PATH = "/persisted_values";
    public static final String CONTENT_REMOTE_URI_PATH = "/remote_values";

    /**
     * The name of the sensor that generated the data point. <br>
     * <br>
     * TYPE: String
     */
    public static final String SENSOR_NAME = "sensor_name";
    /**
     * Description of the sensor that generated the data point. Can either be the hardware name,
     * or any other useful description. <br>
     * <br>
     * TYPE: String
     */
    public static final String SENSOR_DESCRIPTION = "sensor_description";
    /**
     * The data type of the data point. <br>
     * <br>
     * TYPE: String
     * 
     * @see SenseDataTypes
     */
    public static final String DATA_TYPE = "data_type";
    /**
     * The human readable display name of the sensor that generated the data point. <br>
     * <br>
     * TYPE: String
     * 
     * @see SenseDataTypes
     */
    public static final String DISPLAY_NAME = "display_name";
    /**
     * Time stamp for the data point, in milliseconds. <br>
     * <br>
     * TYPE: long
     */
    public static final String TIMESTAMP = "timestamp";
    /**
     * Data point value. <br>
     * <br>
     * TYPE: String
     */
    public static final String VALUE = "value";
    /**
     * Transmit state of the data point, signalling whether the point has been sent to
     * CommonSense already.<br>
     * <br>
     * TYPE: integer status code: 0 (not sent), or 1 (sent)
     */
    public static final String TRANSMIT_STATE = "transmit_state";
    /**
     * Device UUID of the sensor. Use this for sensors that are originated from
     * "external sensors", or leave <code>null</code> to use the phone as default device.<br>
     * <br>
     * TYPE: String
     */
    public static final String DEVICE_UUID = "device_uuid";

    private DataPoint() {
        // class should not be instantiated
    }
} 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 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;
    }

} public class EncryptionHelper {

	public static class EncryptionHelperException extends RuntimeException {

        private static final long serialVersionUID = -4628390225138450908L;

        public EncryptionHelperException(Throwable e) {
			super(e);
		}

	}

	private static final String TRANSFORMATION = "AES/CBC/PKCS5Padding";
	private static final String SECRET_KEY_HASH_TRANSFORMATION = "SHA-256";
	private static final String DEFAULT_KEY_SALT = "3XnMxOy3E&jsdSSHWM941D89yK!RlRVH";

	private static SharedPreferences sMainPrefs;

	private final Cipher writer;
	private final Cipher reader;

	public EncryptionHelper(Context context) throws EncryptionHelperException {
		this(context, null);
	}

	/**
	 * This will initialize an instance of the EncryptionHelper class
	 * @param context your current context.
	 * @param secureKey the key used for encryption, finding a good key scheme is hard. 
	 * Hardcoding your key in the application is bad, but better than plaintext preferences. Having the user enter the key upon application launch is a safe(r) alternative, but annoying to the user.
	 * @throws EncryptionHelperException
	 */
	public EncryptionHelper(Context context, String secureKey) throws EncryptionHelperException {
		try {
			this.writer = Cipher.getInstance(TRANSFORMATION);
			this.reader = Cipher.getInstance(TRANSFORMATION);

			initCiphers(context, secureKey);
		}
		catch (GeneralSecurityException e) {
			throw new EncryptionHelperException(e);
		}
	}

	protected void initCiphers(Context context, String secureKey) throws NoSuchAlgorithmException, InvalidKeyException,
			InvalidAlgorithmParameterException {
		if (null == secureKey) {
			TelephonyManager telephonyManager = (TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE);
			String imei = telephonyManager.getDeviceId();

			secureKey = imei;
		}

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

		String salt = sMainPrefs.getString(Advanced.ENCRYPT_CREDENTIAL_SALT, DEFAULT_KEY_SALT);

		IvParameterSpec ivSpec = getIv();
		SecretKeySpec secretKey = getSecretKey(secureKey+salt);

		writer.init(Cipher.ENCRYPT_MODE, secretKey, ivSpec);
		reader.init(Cipher.DECRYPT_MODE, secretKey, ivSpec);
	}

	protected IvParameterSpec getIv() {
		byte[] iv = new byte[writer.getBlockSize()];
		System.arraycopy("fldsjfodasjifudslfjdsaofshaufihadsf".getBytes(), 0, iv, 0, writer.getBlockSize());
		return new IvParameterSpec(iv);
	}

	protected SecretKeySpec getSecretKey(String key) throws NoSuchAlgorithmException {
		byte[] keyBytes = createKeyBytes(key);
		return new SecretKeySpec(keyBytes, TRANSFORMATION);
	}

	protected byte[] createKeyBytes(String key) throws NoSuchAlgorithmException {
		MessageDigest md = MessageDigest.getInstance(SECRET_KEY_HASH_TRANSFORMATION);
		md.reset();
		byte[] keyBytes = md.digest(key.getBytes());
		return keyBytes;
	}

	public String encrypt(String value) throws EncryptionHelperException {
		byte[] secureValue;
		secureValue = convert(writer, value.getBytes());
		String secureValueEncoded = Base64.encodeToString(secureValue, Base64.NO_WRAP);
		return secureValueEncoded;
	}

	public String decrypt(String securedEncodedValue) throws EncryptionHelperException {
		if (securedEncodedValue == null || securedEncodedValue == "") {
                  return "";
		}
		try {
			byte[] securedValue = Base64.decode(securedEncodedValue, Base64.NO_WRAP);
			byte[] value = convert(reader, securedValue);
			return new String(value);
		} catch (IllegalArgumentException e) {
			// maybe data is not encrypted
                	throw new EncryptionHelperException(e);
		} catch (EncryptionHelperException e) {
                        throw e;
                }
	}

	private static byte[] convert(Cipher cipher, byte[] bs) throws EncryptionHelperException {
		try {
			return cipher.doFinal(bs);
		}
		catch (Exception e) {
			throw new EncryptionHelperException(e);
		}
	}
}
import java.text.DecimalFormat;
import java.text.DecimalFormatSymbols;
import java.text.NumberFormat;
import java.util.Locale;
import nl.sense_os.service.commonsense.DefaultSensorRegistrationService;
import nl.sense_os.service.commonsense.SenseApi;
import nl.sense_os.service.commonsense.senddata.BufferTransmitHandler;
import nl.sense_os.service.commonsense.senddata.DataTransmitHandler;
import nl.sense_os.service.commonsense.senddata.FileTransmitHandler;
import nl.sense_os.service.constants.SenseDataTypes;
import nl.sense_os.service.constants.SensePrefs;
import nl.sense_os.service.constants.SensePrefs.Auth;
import nl.sense_os.service.constants.SensePrefs.Main;
import nl.sense_os.service.constants.SensorData.DataPoint;
import nl.sense_os.service.provider.SNTP;
import nl.sense_os.service.storage.LocalStorage;
import nl.sense_os.service.EncryptionHelper;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import android.app.Service;
import android.content.ContentValues;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.os.HandlerThread;
import android.os.IBinder;
import android.os.Message;
import android.util.Log;
/**************************************************************************************************
 * Copyright (C) 2010 Sense Observation Systems, Rotterdam, the Netherlands. All rights reserved. *
 *************************************************************************************************/
package nl.sense_os.service;





/**
 * This class is responsible for handling the sensor data that has been collected by the different
 * sensors. It has two main tasks:
 * <ul>
 * <li>Collect incoming sensor data and add it to the buffer.</li>
 * <li>Periodically transmit all sensor data in the buffer to CommonSense.</li>
 * </ul>
 * Sensors that have sampled a new data point should send it to the MsgHandler by sending an Intent
 * with {@link R.string#action_sense_new_data} that contains the details of the datapoint.<br/>
 * <br/>
 * For example:
 * 
 * <pre>
 * Intent sensorData = new Intent(getString(R.string.action_sense_new_data));
 * sensorData.putExtra(DataPoint.SENSOR_NAME, &quot;sensor name&quot;);
 * sensorData.putExtra(DataPoint.VALUE, &quot;foo&quot;);
 * sensorData.putExtra(DataPoint.DATA_TYPE, SenseDataTypes.FLOAT);
 * sensorData.putExtra(DataPoint.TIMESTAMP, System.currentTimeMillis());
 * startService(sensorData);
 * </pre>
 * 
 * @author Steven Mulder <steven@sense-os.nl>
 */
public class MsgHandler extends Service {

	private static final String TAG = "Sense MsgHandler";
    /**
     * Key for Intent extra that defines the buffer type to send data from. The value should be
     * either {@link #BUFFER_TYPE_FLASH} or {@link #BUFFER_TYPE_MEMORY}.
     */
    public static final String EXTRA_BUFFER_TYPE = "buffer-type";
    public static final int BUFFER_TYPE_FLASH = 1;
    public static final int BUFFER_TYPE_MEMORY = 0;

	private static FileTransmitHandler fileHandler;
	private static DataTransmitHandler dataTransmitHandler;