package interdroid.swan.sensors;
import interdroid.swan.swansong.TimestampedValue;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.app.Service;
import android.content.Intent;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
/**
* This class is the abstract base for all Sensor services. Sensor implementors
* are advised to use AbstractVdbSensor or AbstractMemorySensor as a basis for
* their sensors instead of using this class directly.
*
* @author nick <palmer@cs.vu.nl>
*
*/
public abstract class AbstractSensorBase extends Service implements
SensorInterface {
private static final String TAG = "AbstractSensorBase";
/**
* The sensor interface.
*/
private final SensorInterface mSensorInterface = this;
private long mStartTime;
// Designed for direct use by subclasses.
/**
* The value paths we support.
*/
protected final String[] VALUE_PATHS = getValuePaths();
/**
* The default configuration.
*/
protected final Bundle mDefaultConfiguration = new Bundle();
/**
* The current configuration of the sensor.
*/
protected final Bundle currentConfiguration = new Bundle();
/**
* The registered configurations for the sensor.
*/
protected final Map<String, Bundle> registeredConfigurations = new HashMap<String, Bundle>();
/**
* The value paths registered as watched.
*/
protected final Map<String, String> registeredValuePaths = new HashMap<String, String>();
/**
* The expression ids for each value path.
*/
protected final Map<String, List<String>> expressionIdsPerValuePath = new HashMap<String, List<String>>();
/**
* Initializes the default configuration for this sensor.
*
* @param defaults
* the bundle to add defaults to
*/
public abstract void initDefaultConfiguration(Bundle defaults);
/**
* Called when the sensor is starting to allow subclasses to handle any
* setup that needs to be done.
*/
protected abstract void init();
@Override
public abstract String[] getValuePaths();
/*
* (non-Javadoc)
*
* @see android.app.Service#onCreate()
*
* Creates the ContextManager and connects to the Swan service.
*/
@Override
public final void onCreate() {
mStartTime = System.currentTimeMillis();
init();
initDefaultConfiguration(mDefaultConfiguration);
onConnected();
}
/** The binder. */
private final Sensor.Stub mBinder = new Sensor.Stub() {
@Override
public void register(final String id, final String valuePath,
final Bundle configuration) throws RemoteException {
// value path exists and id is unique (enforced by evaluation
// engine)
synchronized (mSensorInterface) {
try {
Log.d(TAG, "Registering id: " + id + " value path: "
+ valuePath);
registeredConfigurations.put(id, configuration);
registeredValuePaths.put(id, valuePath);
List<String> ids = expressionIdsPerValuePath.get(valuePath);
if (ids == null) {
ids = new ArrayList<String>();
expressionIdsPerValuePath.put(valuePath, ids);
}
ids.add(id);
printState();
Log.d(TAG, "Registering with implementation.");
mSensorInterface.register(id, valuePath, configuration);
} catch (Exception e) {
Log.e(TAG, "Caught exception while registering.", e);
throw new RemoteException();
}
}
}
@Override
public void unregister(final String id) throws RemoteException {
registeredConfigurations.remove(id);
String valuePath = registeredValuePaths.remove(id);
expressionIdsPerValuePath.get(valuePath).remove(id);
printState();
mSensorInterface.unregister(id);
}
@Override
public List<TimestampedValue> getValues(final String id,
final long now, final long timespan) throws RemoteException {
try {
return mSensorInterface.getValues(id, now, timespan);
} catch (Throwable t) {
t.printStackTrace();
}
return null;
}
@Override
public long getStartUpTime(String id) throws RemoteException {
return mSensorInterface.getStartUpTime(id);
}
@Override
public Bundle getInfo() throws RemoteException {
Bundle info = new Bundle();
info.putString("name", AbstractSensorBase.this.getClass().getSimpleName());
int num = 0;
for (Map.Entry<String, List<String>> entry : expressionIdsPerValuePath
.entrySet()) {
num += entry.getValue().size();
}
info.putInt("registeredids", num);
info.putDouble("sensingRate", getAverageSensingRate());
info.putLong("starttime", getStartTime());
info.putFloat("currentMilliAmpere", getCurrentMilliAmpere());
return info;
}
};
/**
* Debug helper which prints the state for this sensor.
*/
private void printState() {
for (String key : registeredConfigurations.keySet()) {
Log.d(TAG,
"configs: " + key + ": "
+ registeredConfigurations.get(key));
}
for (String key : registeredValuePaths.keySet()) {
Log.d(TAG,
"valuepaths: " + key + ": " + registeredValuePaths.get(key));
}
for (String key : expressionIdsPerValuePath.keySet()) {
Log.d(TAG, "expressionIds: " + key + ": "
+ expressionIdsPerValuePath.get(key));
}
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onBind(android.content.Intent)
*
* returns the sensor interface
*/
@Override
public final IBinder onBind(final Intent arg0) {
return mBinder;
}
/*
* (non-Javadoc)
*
* @see android.app.Service#onDestroy()
*
* Stops the connection to Swan
*/
@Override
public final void onDestroy() {
try {
mSensorInterface.onDestroySensor();
} catch (Exception e) {
Log.e(TAG, "Got exception destroying sensor service", e);
}
super.onDestroy();
}
// =-=-=-=- Utility Functions -=-=-=-=
/**
* Send a notification that data changed for the given id.
*
* @param id
* the id of the value to notify for.
*/
protected final void notifyDataChangedForId(final String... ids) {
Intent notifyIntent = new Intent(ACTION_NOTIFY);
notifyIntent.putExtra("expressionIds", ids);
sendBroadcast(notifyIntent);
}
@Override
public long getStartUpTime(String id) {
return 0;
}
/**
* Send a notification that data for the given value path changed.
*
* @param valuePath
* the value path to notify for.
*/
protected final void notifyDataChanged(final String valuePath) {
List<String> notify = new ArrayList<String>();
synchronized (mSensorInterface) {
// can be null if multiple valuepaths are updated together and not
// for all of them, there's an id registered.
if (expressionIdsPerValuePath.get(valuePath) != null) {
for (String id : expressionIdsPerValuePath.get(valuePath)) {
notify.add(id);
}
}
}
if (notify.size() > 0) {
notifyDataChangedForId(notify.toArray(new String[notify.size()]));
}
}
/**
* Gets all readings from timespan seconds ago until now. Readings are returned in
* reverse order (latest first). This is important for the expression
* engine.
*
* @param now
* the start
* @param timespan
* the end
* @param values
* the values
* @return All readings in the timespan between timespan seconds ago and now
*/
protected static final List<TimestampedValue> getValuesForTimeSpan(
final List<TimestampedValue> values, final long now,
final long timespan){
List<TimestampedValue> result = new ArrayList<TimestampedValue>();
if (timespan == 0) {
if (values != null && values.size() > 0) {
result.add(values.get(values.size()-1)); //item in the last position has the latest timestamp
}
} else {
if (values != null) {
for (int i = values.size()-1; i >= 0 ; i --) {
if ((now - timespan) <= values.get(i).getTimestamp()) {
if (now >= values.get(i).getTimestamp()) //it shouldn't be a future value
result.add(values.get(i));
}
else { //stop when it reaches too outdated values
break;
}
}
}
}
return result;
}
@Override
public double getAverageSensingRate() {
return (double) getReadings()
/ ((System.currentTimeMillis() - mStartTime) / 1000.0);
}
public long getStartTime() {
return mStartTime;
}
public abstract long getReadings();
public float getCurrentMilliAmpere() {
return -1;
}
}