package nl.sense_os.platform; import nl.sense_os.service.ISenseServiceCallback; import nl.sense_os.service.R; import nl.sense_os.service.SenseService.SenseBinder; import nl.sense_os.service.SenseServiceStub; import nl.sense_os.service.commonsense.SenseApi; import nl.sense_os.service.commonsense.SensorRegistrator; import nl.sense_os.service.constants.SenseDataTypes; import nl.sense_os.service.constants.SensePrefs; import nl.sense_os.service.constants.SensorData.DataPoint; import nl.sense_os.service.storage.LocalStorage; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; import android.content.SharedPreferences; import android.database.Cursor; import android.net.Uri; import android.os.IBinder; import android.os.RemoteException; import android.util.Log; /** * A proxy class that acts as a high-level interface to the sense Android library. By instantiating * this class you bind (and start if needed) the sense service. You can then use the high level * methods of this class, and/or get the service object to work directly with the sense service. */ public class SensePlatform { /** * Service connection to handle connection with the Sense service. Manages the * <code>service</code> field when the service is connected or disconnected. */ private class SenseServiceConn implements ServiceConnection { private final ServiceConnection mServiceConnection; public SenseServiceConn(ServiceConnection serviceConnection) { mServiceConnection = serviceConnection; } @Override public void onServiceConnected(ComponentName className, IBinder binder) { Log.v(TAG, "Bound to Sense Platform service..."); mSenseService = ((SenseBinder) binder).getService(); mServiceBound = true; // notify the external service connection if (mServiceConnection != null) { mServiceConnection.onServiceConnected(className, binder); } } @Override public void onServiceDisconnected(ComponentName className) { Log.v(TAG, "Sense Platform service disconnected..."); // this is not called when the service is stopped, only when it is suddenly killed! mSenseService = null; mServiceBound = false; // notify the external service connection if (mServiceConnection != null) { mServiceConnection.onServiceDisconnected(className); } } } private static final String TAG = "SensePlatform"; /** Context of the enclosing application */ private final Context mContext; /** Interface for the SenseService. Gets instantiated by {@link #mServiceConnection}. */ private SenseServiceStub mSenseService; /** Keeps track of the service binding state */ private boolean mServiceBound = false; /** Callback for events for the binding with the Sense service */ private final ServiceConnection mServiceConnection; /** * @param context * Context that the Sense service will bind to */ public SensePlatform(Context context) { this(context, null); } /** * @param context * Context that the Sense service will bind to. * @param serviceConnection * ServiceConnection to receive callbacks about the binding with the service. */ public SensePlatform(Context context, ServiceConnection serviceConnection) { mServiceConnection = new SenseServiceConn(serviceConnection); mContext = context; bindToSenseService(); } /** * Convenience method to add a data point without specifying the device UUID * * @param sensorName * Name of the sensor * @param displayName * Display name of the sensor * @param description * Description of the sensor, i.e. CommonSense "device_type" * @param dataType * Data type, e.g. json, string, float, bool * @param value * Data point value * @param timestamp * Data point time stamp * @return true if the data point was sent to the Sense service * @throws IllegalStateException * If the Sense service is not bound yet * @see #addDataPoint(String, String, String, String, String, String, long) */ public boolean addDataPoint(String sensorName, String displayName, String description, String dataType, Object value, long timestamp) throws IllegalStateException { return addDataPoint(sensorName, displayName, description, dataType, null, value, timestamp); } /** * Adds a data point for a sensor at CommonSense. If the sensor does not exist yet, it will be * created. * * @param sensorName * Name of the sensor * @param displayName * Display name of the sensor * @param description * Description of the sensor, i.e. CommonSense "device_type" * @param dataType * Data type, e.g. json, string, float, bool * @param deviceUuid * (Optional) Device UUID, set null to make sure the sensor is connected to the * current device * @param value * Data point value * @param timestamp * Data point time stamp * @return true if the data point was sent to the Sense service * @throws IllegalStateException * If the Sense service is not bound yet */ public boolean addDataPoint(String sensorName, String displayName, String description, String dataType, String deviceUuid, Object value, long timestamp) throws IllegalStateException { checkSenseService(); if (null == deviceUuid) { deviceUuid = SenseApi.getDefaultDeviceUuid(mContext); } // register the sensor SensorRegistrator registrator = new TrivialSensorRegistrator(mContext); registrator.checkSensor(sensorName, displayName, dataType, description, "" + value, null, deviceUuid); // send data point String action = mContext.getString(nl.sense_os.service.R.string.action_sense_new_data); Intent intent = new Intent(action); intent.putExtra(DataPoint.SENSOR_NAME, sensorName); intent.putExtra(DataPoint.DISPLAY_NAME, displayName); intent.putExtra(DataPoint.SENSOR_DESCRIPTION, description); intent.putExtra(DataPoint.DATA_TYPE, dataType); intent.putExtra(DataPoint.DEVICE_UUID, deviceUuid); if (dataType.equals(SenseDataTypes.JSON) || dataType.equals(SenseDataTypes.JSON_TIME_SERIES)) { intent.putExtra(DataPoint.VALUE, value.toString()); } else if (dataType.equals(SenseDataTypes.BOOL)) { intent.putExtra(DataPoint.VALUE, (Boolean) value); } else if (dataType.equals(SenseDataTypes.FLOAT)) { intent.putExtra(DataPoint.VALUE, (Float) value); } else if (dataType.equals(SenseDataTypes.INT)) { intent.putExtra(DataPoint.VALUE, (Integer) value); } else if (dataType.equals(SenseDataTypes.STRING)) { intent.putExtra(DataPoint.VALUE, (String) value); } else { intent.putExtra(DataPoint.VALUE, (String) value); } intent.putExtra(DataPoint.TIMESTAMP, timestamp); ComponentName serviceName = mContext.startService(intent); if (null != serviceName) { return true; } else { Log.w(TAG, "Could not start MsgHandler service!"); return false; } } /** * Binds to the Sense service, creating it if necessary. */ private void bindToSenseService() { // start the service if it was not running already if (!mServiceBound) { Log.v(TAG, "Try to bind to Sense Platform service"); final Intent serviceIntent = new Intent( getContext(), nl.sense_os.service.SenseService.class); boolean bindResult = mContext.bindService(serviceIntent, mServiceConnection, Context.BIND_AUTO_CREATE); Log.v(TAG, "Result: " + bindResult); } else { // already bound } } /** * Check that the sense service is bound. This method is used for public methods to provide a * single check for the sense service. * * @throws IllegalStateException * If the Sense service is not bound yet */ private void checkSenseService() throws IllegalStateException { if (mSenseService == null) { throw new IllegalStateException("Sense service not bound"); } } /** * Closes the service connection to the Sense service and cleans up the binding. */ public void close() { unbindFromSenseService(); } /** * Flush data to CommonSense * * @return true if the flush task was successfully started * @throws IllegalStateException * If the Sense service is not bound yet */ public boolean flushData() throws IllegalStateException { checkSenseService(); Intent flush = new Intent(mContext.getString(R.string.action_sense_send_data)); ComponentName started = mContext.startService(flush); return null != started; } /** * Flush data to Common Sense, return after the flush is completed * * @throws IllegalStateException * If the Sense service is not bound yet */ public void flushDataAndBlock() throws IllegalStateException { checkSenseService(); flushData(); // TODO: block till flush finishes or returns an error } public Context getContext() { return mContext; } /** * Retrieve a number of values of a sensor from CommonSense. * * @param sensorName * The name of the sensor to get data from * @param onlyFromDevice * Whether or not to only look through sensors that are part of this device. Searches * all sensors, including those of this device, if set to NO * @return JSONArray of data points * @throws IllegalStateException * If the Sense service is not bound yet * @throws JSONException * If the response from CommonSense could not be parsed */ public JSONArray getData(String sensorName, boolean onlyFromDevice) throws IllegalStateException, JSONException { return getData(sensorName, onlyFromDevice, 100); } /** * Retrieve a number of values of a sensor from CommonSense. * * @param sensorName * The name of the sensor to get data from * @param onlyFromDevice * Whether or not to only look through sensors that are part of this device. Searches * all sensors, including those of this device, if set to NO * @param limit * Maximum amount of data points. * @return JSONArray of data points * @throws IllegalStateException * If the Sense service is not bound yet * @throws JSONException * If the response from CommonSense could not be parsed */ public JSONArray getData(String sensorName, boolean onlyFromDevice, int limit) throws IllegalStateException, JSONException { checkSenseService(); JSONArray result = new JSONArray(); // select remote path in local storage String localStorage = mContext.getString(R.string.local_storage_authority); Uri uri = Uri.parse("content://" + localStorage + DataPoint.CONTENT_REMOTE_URI_PATH); // get the data result = getValues(sensorName, onlyFromDevice, limit, uri); return result; } /** * Retrieve a number of values of a sensor from the local storage. * * @param sensorName * The name of the sensor to get data from * @param onlyFromDevice * Whether or not to only look through sensors that are part of this device. Searches * all sensors, including those of this device, if set to NO * @return JSONArray of data points * @throws IllegalStateException * If the Sense service is not bound yet * @throws JSONException * If the response from CommonSense could not be parsed */ public JSONArray getLocalData(String sensorName) throws IllegalStateException, JSONException { return getLocalData(sensorName, 100); } /** * Retrieve a number of values of a sensor from the local storage. * * @param sensorName * The name of the sensor to get data from * @param onlyFromDevice * Whether or not to only look through sensors that are part of this device. Searches * all sensors, including those of this device, if set to NO * @param limit * Maximum amount of data points. * @return JSONArray of data points * @throws IllegalStateException * If the Sense service is not bound yet * @throws JSONException * If the response from CommonSense could not be parsed */ public JSONArray getLocalData(String sensorName, int limit) throws IllegalStateException, JSONException { checkSenseService(); JSONArray result = new JSONArray(); // select remote path in local storage String localStorage = mContext.getString(R.string.local_storage_authority); Uri uri = Uri.parse("content://" + localStorage + DataPoint.CONTENT_URI_PATH); // get the data result = getValues(sensorName, true, limit, uri); return result; } /** * @return The intent action for new sensor data. This can be used to subscribe to new data. */ public String getNewDataAction() { return mContext.getString(R.string.action_sense_new_data); } /** * @return The Sense service instance */ public SenseServiceStub getService() { checkSenseService(); return mSenseService; } /** * Gets array of values from the LocalStorage in <code>DESC</code> order. * * @param sensorName * Name of the sensor to get values from. * @param onlyFromDevice * If true this function only looks for sensors attached to this device. * @param limit * Maximum amount of data points. Optional, use null to set the default limit (100). * @param uri * The uri to get data from, can be either local or remote. * @return JSONArray with values for the sensor with the selected name and device * @throws JSONException * @see #getValues(String, boolean, Integer, android.net.Uri, String) */ private JSONArray getValues(String sensorName, boolean onlyFromDevice, int limit, Uri uri) throws JSONException { String orderBy = DataPoint.TIMESTAMP + " DESC"; return getValues(sensorName, onlyFromDevice, limit, uri, orderBy); } /** * Gets array of values from the LocalStorage * * @param sensorName * Name of the sensor to get values from. * @param onlyFromDevice * If true this function only looks for sensors attached to this device. * @param limit * Maximum amount of data points. Optional, use null to set the default limit (100). * @param uri * The uri to get data from, can be either local or remote. * @param sortOrder * The sort order, one of <code>DESC</code> or <code>ASC</code>. * @return JSONArray with values for the sensor with the selected name and device * @throws JSONException */ private JSONArray getValues(String sensorName, boolean onlyFromDevice, Integer limit, Uri uri, String sortOrder) throws JSONException { Cursor cursor = null; JSONArray result = new JSONArray(); String deviceUuid = onlyFromDevice ? SenseApi.getDefaultDeviceUuid(mContext) : null; String[] projection = new String[] { DataPoint.TIMESTAMP, DataPoint.VALUE }; String selection = DataPoint.SENSOR_NAME + " = '" + sensorName + "'"; if (null != deviceUuid) { selection += " AND " + DataPoint.DEVICE_UUID + "='" + deviceUuid + "'"; } String[] selectionArgs = null; // make sure the limit is feasible if (limit < 1) { limit = 100; } try { cursor = LocalStorage.getInstance(mContext).query(uri, projection, selection, selectionArgs, limit, sortOrder); if (null != cursor && cursor.moveToFirst()) { while (!cursor.isAfterLast()) { JSONObject val = new JSONObject(); val.put("date", cursor.getLong(cursor.getColumnIndex(DataPoint.TIMESTAMP))); val.put("value", cursor.getString(cursor.getColumnIndex(DataPoint.VALUE))); result.put(val); cursor.moveToNext(); } } } catch (JSONException je) { throw je; } finally { if (cursor != null) cursor.close(); } return result; } /** * Tries to log in at CommonSense using the supplied username and password. After login, the * service remembers the username and password. * * @param username * Username for login * @param pass * Hashed password for login * @param callback * Interface to receive callback when login is completed * @throws IllegalStateException * If the Sense service is not bound yet * @throws RemoteException */ public void login(String user, String password, ISenseServiceCallback callback) throws IllegalStateException, RemoteException { checkSenseService(); mSenseService.changeLogin(user, password, callback); } /** * Logs out a user, destroying his or her records. */ public void logout() throws IllegalStateException, RemoteException { checkSenseService(); mSenseService.logout(); } /** * Registers a new user at CommonSense and logs in immediately. * * @param username * Username for the new user * @param password * Hashed password String for the new user * @param email * Email address * @param address * Street address (optional, null if not required) * @param zipCode * ZIP code (optional, null if not required) * @param country * Country * @param firstName * First name (optional, null if not required) * @param surname * Surname (optional, null if not required) * @param mobileNumber * Phone number, preferably in E164 format (optional, null if not required) * @param callback * Interface to receive callback when login is completed */ public void registerUser(String username, String password, String email, String address, String zipCode, String country, String firstName, String surname, String mobileNumber, ISenseServiceCallback callback) throws IllegalStateException, RemoteException { checkSenseService(); mSenseService.register(username, password, email, address, zipCode, country, firstName, surname, mobileNumber, callback); } /** * Unbinds from the Sense service, resets {@link #mSenseService} and {@link #mServiceBound}. */ private void unbindFromSenseService() { if (true == mServiceBound && null != mServiceConnection) { Log.v(TAG, "Unbind from Sense Platform service"); mContext.unbindService(mServiceConnection); } else { // already unbound } mSenseService = null; mServiceBound = false; } public void setPreferenceString(SharedPreferences sharedPreferences, String key){ String defValue, retValue; if ( key.compareToIgnoreCase("storage") == 0){ defValue = "Remote Storage"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.Advanced.STORAGE, retValue); } else if ( key.compareToIgnoreCase("battery") == 0){ defValue = "When battery level above threshold"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.Advanced.BATTERY, retValue); } else if ( key.compareToIgnoreCase("battery_threshold") == 0){ defValue = "60"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.Advanced.BATTERY_THRESHOLD, retValue); } else if ( key.compareToIgnoreCase("sync_rate") == 0){ defValue = "ECO"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.SYNC_RATE, retValue); } else if ( key.compareToIgnoreCase("mobile_network") == 0){ defValue = "WiFi"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.Advanced.MOBILE_NETWORK_UPLOAD, retValue); } else if ( key.compareToIgnoreCase("mobile_network2") == 0){ defValue = "WiFi"; retValue = sharedPreferences.getString(key, defValue); mSenseService.setPrefString(SensePrefs.Main.Advanced.MOBILE_NETWORK_DOWNLOAD, retValue); } else { Log.d(TAG, "Unknown key"); return; } } public void setDefaultPreference(){ mSenseService.setPrefString(SensePrefs.Main.Advanced.STORAGE, "Remote storage"); mSenseService.setPrefString(SensePrefs.Main.Advanced.BATTERY, "When battery level above threshold"); mSenseService.setPrefString(SensePrefs.Main.Advanced.BATTERY_THRESHOLD, "60"); mSenseService.setPrefString(SensePrefs.Main.SYNC_RATE, "1"); mSenseService.setPrefString(SensePrefs.Main.Advanced.MOBILE_NETWORK_UPLOAD, "WiFi"); mSenseService.setPrefString(SensePrefs.Main.Advanced.MOBILE_NETWORK_DOWNLOAD, "WiFi"); } }