package interdroid.swan.engine;
import interdroid.swan.ExpressionManager;
import interdroid.swan.SensorConfigurationException;
import interdroid.swan.SensorInfo;
import interdroid.swan.SwanException;
import interdroid.swan.crossdevice.Pusher;
import interdroid.swan.crossdevice.Registry;
import interdroid.swan.sensors.Sensor;
import interdroid.swan.sensors.TimeSensor;
import interdroid.swan.swansong.BinaryLogicOperator;
import interdroid.swan.swansong.Comparator;
import interdroid.swan.swansong.ComparatorResult;
import interdroid.swan.swansong.ComparisonExpression;
import interdroid.swan.swansong.ConstantValueExpression;
import interdroid.swan.swansong.Expression;
import interdroid.swan.swansong.HistoryReductionMode;
import interdroid.swan.swansong.LogicExpression;
import interdroid.swan.swansong.MathOperator;
import interdroid.swan.swansong.MathValueExpression;
import interdroid.swan.swansong.Result;
import interdroid.swan.swansong.SensorValueExpression;
import interdroid.swan.swansong.TimestampedValue;
import interdroid.swan.swansong.TriState;
import interdroid.swan.swansong.TriStateExpression;
import interdroid.swan.swansong.UnaryLogicOperator;
import interdroid.swan.swansong.ValueExpression;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.ComponentName;
import android.content.Context;
import android.content.ServiceConnection;
import android.location.Location;
import android.os.Bundle;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
public class EvaluationManager {
private static final String TAG = "EvaluationManager";
// time it takes to start up the remote sensor, this is a bit arbitrary
// because we don't (and cannot) know when the push message arrives
private static final long START_UP_TIME_REMOTE_SENSOR = 60 * 1000;
/** The sensor information. */
private List<SensorInfo> mSensorList = new ArrayList<SensorInfo>();
/** The service connections. */
private final Map<String, ServiceConnection> mConnections = new HashMap<String, ServiceConnection>();
/** The sensors proxies */
private final Map<String, Sensor> mSensors = new HashMap<String, Sensor>();
/** The context (for launching new services). */
private final Context mContext;
private final Map<String, Result> mCachedResults = new HashMap<String, Result>();
public EvaluationManager(Context context) {
mContext = context;
}
public void newRemoteResult(String id, Result result) {
mCachedResults.put(id, result);
}
public void resolveLocation(Expression expression) {
if (!Expression.LOCATION_INFER.equals(expression.getLocation())) {
return;
}
String left = null;
String right = null;
if (expression instanceof LogicExpression) {
resolveLocation(((LogicExpression) expression).getLeft());
left = ((LogicExpression) expression).getLeft().getLocation();
resolveLocation(((LogicExpression) expression).getRight());
right = ((LogicExpression) expression).getRight().getLocation();
} else if (expression instanceof ComparisonExpression) {
resolveLocation(((ComparisonExpression) expression).getLeft());
left = ((ComparisonExpression) expression).getLeft().getLocation();
resolveLocation(((ComparisonExpression) expression).getRight());
right = ((ComparisonExpression) expression).getRight()
.getLocation();
} else if (expression instanceof MathValueExpression) {
resolveLocation(((MathValueExpression) expression).getLeft());
left = ((MathValueExpression) expression).getLeft().getLocation();
resolveLocation(((MathValueExpression) expression).getRight());
right = ((MathValueExpression) expression).getRight().getLocation();
}
if (left.equals(right)) {
expression.setInferredLocation(left);
} else if (left.equals(Expression.LOCATION_INDEPENDENT)) {
expression.setInferredLocation(right);
} else if (right.equals(Expression.LOCATION_INDEPENDENT)) {
expression.setInferredLocation(left);
} else if (left.equals(Expression.LOCATION_SELF)
|| right.equals(Expression.LOCATION_SELF)) {
expression.setInferredLocation(Expression.LOCATION_SELF);
} else {
expression.setInferredLocation(left);
}
}
public void initialize(String id, Expression expression)
throws SensorConfigurationException, SensorSetupFailedException {
// should get the sensors start producing data.
resolveLocation(expression);
String location = expression.getLocation();
if (!location.equals(Expression.LOCATION_SELF)
&& !location.equals(Expression.LOCATION_INDEPENDENT)) {
initializeRemote(id, expression, location);
} else if (expression instanceof LogicExpression) {
initialize(id + Expression.LEFT_SUFFIX,
((LogicExpression) expression).getLeft());
initialize(id + Expression.RIGHT_SUFFIX,
((LogicExpression) expression).getRight());
} else if (expression instanceof ComparisonExpression) {
initialize(id + Expression.LEFT_SUFFIX,
((ComparisonExpression) expression).getLeft());
initialize(id + Expression.RIGHT_SUFFIX,
((ComparisonExpression) expression).getRight());
} else if (expression instanceof MathValueExpression) {
initialize(id + Expression.LEFT_SUFFIX,
((MathValueExpression) expression).getLeft());
initialize(id + Expression.RIGHT_SUFFIX,
((MathValueExpression) expression).getRight());
} else if (expression instanceof SensorValueExpression) {
if (((SensorValueExpression) expression).getEntity().equals("time")) {
return;
}
// do the real work here, bind to the sensor.
bindToSensor(id, (SensorValueExpression) expression, false);
}
}
public void stop(String id, Expression expression) {
// should get the sensors stop producing data.
String location = expression.getLocation();
if (!location.equals(Expression.LOCATION_SELF)
&& !location.equals(Expression.LOCATION_INDEPENDENT)) {
stopRemote(id, expression);
}
if (expression instanceof LogicExpression) {
stop(id + Expression.LEFT_SUFFIX,
((LogicExpression) expression).getLeft());
stop(id + Expression.RIGHT_SUFFIX,
((LogicExpression) expression).getRight());
} else if (expression instanceof ComparisonExpression) {
stop(id + Expression.LEFT_SUFFIX,
((ComparisonExpression) expression).getLeft());
stop(id + Expression.RIGHT_SUFFIX,
((ComparisonExpression) expression).getRight());
} else if (expression instanceof MathValueExpression) {
stop(id + Expression.LEFT_SUFFIX,
((MathValueExpression) expression).getLeft());
stop(id + Expression.RIGHT_SUFFIX,
((MathValueExpression) expression).getRight());
} else if (expression instanceof SensorValueExpression) {
if (((SensorValueExpression) expression).getEntity().equals("time")) {
return;
}
// do the real work here, unbind from the sensor.
unbindFromSensor(id);
}
}
public void destroyAll() {
for (String id : mSensors.keySet()) {
unbindFromSensor(id);
}
}
public void clearCacheFor(String id) {
if (mCachedResults.get(id) != null) {
mCachedResults.get(id).setDeferUntil(0);
}
for (String suffix : Expression.RESERVED_SUFFIXES) {
if (id.endsWith(suffix)) {
clearCacheFor(id.substring(0, id.length() - suffix.length()));
}
}
}
public Result evaluate(String id, Expression expression, long now)
throws SwanException {
if (expression == null) {
throw new RuntimeException("This should not happen! Please debug");
}
if (mCachedResults.containsKey(id)) {
if (mCachedResults.get(id).getDeferUntil() > now) {
return mCachedResults.get(id);
}
}
Result result = null;
// if the location is remote, result is null or undefined
String location = expression.getLocation();
if (!location.equals(Expression.LOCATION_SELF)
&& !location.equals(Expression.LOCATION_INDEPENDENT)) {
if (expression instanceof TriStateExpression) {
if (mCachedResults.containsKey(id)) {
return mCachedResults.get(id);
} else {
result = new Result(now, TriState.UNDEFINED);
}
} else if (expression instanceof ValueExpression) {
// we don't have anything cached, so send an empty result.
result = new Result(new TimestampedValue[] {}, 0);
}
result.setDeferUntil(Long.MAX_VALUE);
result.setDeferUntilGuaranteed(false);
} else if (expression instanceof LogicExpression) {
result = applyLogic(id, (LogicExpression) expression, now);
} else if (expression instanceof ComparisonExpression) {
result = doCompare(id, (ComparisonExpression) expression, now);
} else if (expression instanceof ConstantValueExpression) {
result = ((ConstantValueExpression) expression).getResult();
} else if (expression instanceof MathValueExpression) {
result = doMath(id, (MathValueExpression) expression, now);
} else if (expression instanceof SensorValueExpression) {
if (((SensorValueExpression) expression).getEntity().equals("time")) {
throw new RuntimeException(
"time can only be used in an ComparisonExpression on the left hand");
}
result = getFromSensor(id, (SensorValueExpression) expression, now);
}
if (result != null) {
mCachedResults.put(id, result);
}
return result;
}
private void initializeRemote(String id, Expression expression,
String resolvedLocation) throws SensorSetupFailedException {
// send a push message with 'register' instead of 'initialize',
// disadvantage is that we will only later on get exceptions
String fromRegistrationId = Registry.get(mContext,
Expression.LOCATION_SELF);
if (fromRegistrationId == null) {
throw new SensorSetupFailedException(
"Device not registered with Google Cloud Messaging, unable to use remote sensors.");
}
String toRegistrationId = Registry.get(mContext, resolvedLocation);
if (toRegistrationId == null) {
throw new SensorSetupFailedException(
"No registration id known for location: "
+ resolvedLocation);
}
// resolve all remote locations in the expression with respect to the
// new location.
Pusher.push(fromRegistrationId, toRegistrationId, id,
EvaluationEngineService.ACTION_REGISTER_REMOTE,
toCrossDeviceString(expression, toRegistrationId));
// expression.toCrossDeviceString(mContext,
// expression.getLocation()));
}
private String toCrossDeviceString(Expression expression,
String toRegistrationId) {
String registrationId = Registry
.get(mContext, expression.getLocation());
if (expression instanceof SensorValueExpression) {
String result = ((registrationId.equals(toRegistrationId)) ? Expression.LOCATION_SELF
: registrationId)
+ "@"
+ ((SensorValueExpression) expression).getEntity()
+ ":" + ((SensorValueExpression) expression).getValuePath();
Bundle config = ((SensorValueExpression) expression)
.getConfiguration();
if (config != null && config.size() > 0) {
boolean first = true;
for (String key : config.keySet()) {
result += (first ? "?" : "&") + key + "="
+ config.getString(key);
first = false;
}
}
result += "{"
+ ((SensorValueExpression) expression)
.getHistoryReductionMode().toParseString() + ","
+ ((SensorValueExpression) expression).getHistoryLength()
+ "}";
return result;
} else if (expression instanceof LogicExpression) {
if (((LogicExpression) expression).getRight() == null) {
return ((LogicExpression) expression).getOperator()
+ " "
+ toCrossDeviceString(
((LogicExpression) expression).getLeft(),
toRegistrationId);
}
return "("
+ toCrossDeviceString(
((LogicExpression) expression).getLeft(),
registrationId)
+ " "
+ ((LogicExpression) expression).getOperator()
+ " "
+ toCrossDeviceString(
((LogicExpression) expression).getRight(),
registrationId) + ")";
} else if (expression instanceof ComparisonExpression) {
return "("
+ toCrossDeviceString(
((ComparisonExpression) expression).getLeft(),
registrationId)
+ " "
+ ((ComparisonExpression) expression).getComparator()
.toParseString()
+ " "
+ toCrossDeviceString(
((ComparisonExpression) expression).getRight(),
registrationId) + ")";
} else if (expression instanceof MathValueExpression) {
return "("
+ toCrossDeviceString(
((MathValueExpression) expression).getLeft(),
registrationId)
+ " "
+ ((MathValueExpression) expression).getOperator()
.toParseString()
+ " "
+ toCrossDeviceString(
((MathValueExpression) expression).getRight(),
registrationId) + ")";
} else if (expression instanceof ConstantValueExpression) {
return ((ConstantValueExpression) expression).toParseString();
}
throw new RuntimeException("Unknown expression type: " + expression);
}
private void stopRemote(String id, Expression expression) {
// send a push message with 'unregister'
String toRegistrationId = Registry.get(mContext,
expression.getLocation());
if (toRegistrationId == null) {
// this should not happen, kill swan
throw new RuntimeException(
"No registration id known for location: "
+ expression.getLocation());
}
// resolve all remote locations in the expression with respect to the
// new location.
Pusher.push(null, toRegistrationId, id,
EvaluationEngineService.ACTION_UNREGISTER_REMOTE,
toCrossDeviceString(expression, toRegistrationId));
}
private boolean bindToSensor(final String id,
final SensorValueExpression expression, boolean discover)
throws SensorConfigurationException, SensorSetupFailedException {
if (discover) {
// run discovery
mSensorList.clear();
mSensorList = ExpressionManager.getSensors(mContext);
}
for (SensorInfo sensorInfo : mSensorList) {
if (sensorInfo.getEntity().equals(expression.getEntity())) {
if (sensorInfo.getValuePaths().contains(
expression.getValuePath())) {
if (sensorInfo.acceptsConfiguration(expression
.getConfiguration())) {
ServiceConnection conn = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
// we are disconnected for some reason
Log.d(TAG, "disconnected for id " + id);
}
@Override
public void onServiceConnected(ComponentName name,
IBinder service) {
Sensor sensor = Sensor.Stub
.asInterface(service);
mSensors.put(id, sensor);
try {
sensor.register(id,
expression.getValuePath(),
expression.getConfiguration());
} catch (RemoteException e) {
Log.e(TAG, "Registration failed!", e);
}
}
};
Log.d(TAG,
"binding to sensor: " + sensorInfo.getIntent());
mContext.bindService(sensorInfo.getIntent(), conn,
Context.BIND_AUTO_CREATE);
mConnections.put(id, conn);
return true;
} else {
Log.d(TAG, "Sensor does not accept configuration '"
+ expression.getConfiguration() + "'");
}
} else {
Log.d(TAG, "No valuepath found for valuepath '"
+ expression.getValuePath() + "'");
}
}
}
if (!discover) {
// try again with discovery
if (bindToSensor(id, expression, true)) {
return true;
}
}
Log.d(TAG, "No sensor found for entity '" + expression.getEntity()
+ "'");
// still not found?
throw new SensorSetupFailedException("Failed to bind to service for: "
+ expression);
}
private void unbindFromSensor(final String id) {
ServiceConnection conn = mConnections.remove(id);
Sensor sensor = mSensors.remove(id);
if (sensor != null) {
try {
sensor.unregister(id);
} catch (RemoteException e) {
Log.d(TAG, "Failed to unregister for id: " + id
+ ", this should not happen!", e);
}
} else {
Log.d(TAG, "Cannot unregister for id: " + id
+ ", sensor is null, this should not happen!");
}
if (conn != null) {
mContext.unbindService(conn);
} else {
Log.d(TAG, "Failed to unbind for id: " + id
+ ", connection is null, this should not happen!");
}
}
private boolean leftFirst(String id, LogicExpression expression, long now) {
// For a binary logic operation it is important to make a clever
// decision which of the involved expressions is evaluated first.
// Depending on the result of this evaluation and the logic operator, it
// is possible to short circuit EVALUATION or stop SENSING.
//
// For instance if the first result is TRUE and the operator is OR,
// there is no need to evaluate the second expression. This is an
// EVALUATION optimization, that is, it saves on evaluation time. A good
// strategy would be to start with the expression that is 'cheapest' to
// evaluate or has the highest likelihood to cause short circuiting.
//
// If we short circuit the current EVALUATION, we compute how long we
// can defer the next evaluation
// based on the part that we did evaluate. There is a chance though
// that by evaluating the other part we find out that we can defer new
// evaluation even further.
//
// For example A OR B, where evaluating A is very cheap and often
// results in TRUE make A a good choice to start evaluating, because the
// current evaluation is likely to be cheap and fast. However, it might
// be the case that B also results in TRUE, but is much more suitable
// for turning off sensors. B might look whether the maximum over the
// last hour exceeds a particular limit, while A checks whether the
// average of the last 10 seconds is above a certain threshold. If we
// find a recent sensor value that makes B true, we can conclude that B
// remains true for about an hour, which will make the logic expression
// true for about an hour. Within this hour no evaluation is needed.
// TODO improve, take "time" into account, if a sensor has time, we
// should probably evaluate it first...
// in case we have a unary operator, it doesn't matter at all.
if (expression.getOperator() instanceof UnaryLogicOperator) {
return true;
}
// TODO values below should be replaced by real estimates
float pLeftTrue = 0.5f; // the chance that evaluating the left part
// results in true
float pRightTrue = 0.5f; // the chance that evaluating the right part
// results in true
float leftEvaluationCost = 100;
float rightEvaluationCost = 100;
float leftSenseCost = 200;
float rightSenseCost = 200;
float leftFirstCost, rightFirstCost;
// check which evaluation cost is likely to be cheaper
switch ((BinaryLogicOperator) expression.getOperator()) {
case AND:
leftFirstCost = leftEvaluationCost + pLeftTrue
* rightEvaluationCost;
rightFirstCost = rightEvaluationCost + pRightTrue
* leftEvaluationCost;
return leftFirstCost <= rightFirstCost;
case OR:
leftFirstCost = leftEvaluationCost + (1 - pLeftTrue)
* rightEvaluationCost;
rightFirstCost = rightEvaluationCost + (1 - pRightTrue)
* leftEvaluationCost;
return leftFirstCost <= rightFirstCost;
}
// check which evaluation is likely to cause the other to sleep/defer
switch ((BinaryLogicOperator) expression.getOperator()) {
case AND:
leftFirstCost = leftSenseCost + (1 - pLeftTrue) * rightSenseCost;
rightFirstCost = rightEvaluationCost + pRightTrue
* leftEvaluationCost;
return leftFirstCost <= rightFirstCost;
case OR:
leftFirstCost = leftEvaluationCost + (1 - pLeftTrue)
* rightEvaluationCost;
rightFirstCost = rightEvaluationCost + (1 - pRightTrue)
* leftEvaluationCost;
return leftFirstCost <= rightFirstCost;
}
return true;
}
private Result applyLogic(String id, LogicExpression expression, long now)
throws SwanException {
boolean leftFirst = leftFirst(id, expression, now);
Expression firstExpression = leftFirst ? expression.getLeft()
: expression.getRight();
Expression lastExpression = !leftFirst ? expression.getLeft()
: expression.getRight();
String firstSuffix = leftFirst ? Expression.LEFT_SUFFIX
: Expression.RIGHT_SUFFIX;
String lastSuffix = !leftFirst ? Expression.LEFT_SUFFIX
: Expression.RIGHT_SUFFIX;
Result first = evaluate(id + firstSuffix, firstExpression, now);
if (shortcut(expression, first)) {
// apply the sleep and be ready to last
if (first.isDeferUntilGuaranteed()) {
sleepAndBeReady(id + lastSuffix, lastExpression,
first.getDeferUntil());
}
// put line below in the above if statement if we want to take the
// risk of evaluating the other part of the expression. This can
// potentially lead to a sleep and be ready on the current part of
// the expression.
return first;
}
Result last = evaluate(id + lastSuffix, lastExpression, now);
if (shortcut(expression, last)) {
if (last.isDeferUntilGuaranteed()) {
sleepAndBeReady(id + firstSuffix, firstExpression,
last.getDeferUntil());
}
return last;
}
Result result = new Result(now, expression.getOperator().operate(
first.getTriState(), last.getTriState()));
result.setDeferUntil(Math.min(first.getDeferUntil(),
last.getDeferUntil()));
result.setDeferUntilGuaranteed(first.isDeferUntilGuaranteed()
&& last.isDeferUntilGuaranteed());
return result;
}
@SuppressWarnings("rawtypes")
private Result doCompare(String id, ComparisonExpression expression,
long now) throws SwanException {
Result right = evaluate(id + Expression.RIGHT_SUFFIX,
expression.getRight(), now);
if (expression.getLeft() instanceof SensorValueExpression
&& ((SensorValueExpression) expression.getLeft()).getEntity()
.equals("time")) {
if (right.getValues().length == 0) {
Log.d(TAG, "No data for: " + expression);
Result result = new Result(now, TriState.UNDEFINED);
result.setDeferUntil(Long.MAX_VALUE);
return result;
}
return TimeSensor.determineValue(now,
((SensorValueExpression) expression.getLeft())
.getValuePath(),
((SensorValueExpression) expression.getLeft())
.getConfiguration(), expression.getComparator(),
(Comparable) right.getValues()[0].getValue());
}
Result left = evaluate(id + Expression.LEFT_SUFFIX,
expression.getLeft(), now);
if (left.getValues().length == 0 || right.getValues().length == 0) {
Log.d(TAG, "No data for: " + expression);
Result result = new Result(now, TriState.UNDEFINED);
result.setDeferUntil(Long.MAX_VALUE);
result.setDeferUntilGuaranteed(false);
return result;
}
// in here we should terminate as quickly as possible, but get the
// highest deferUntil, therefore start from recent to old
// assume left and right are sorted with most recent one first
ComparatorResult comparatorResult = new ComparatorResult(now,
expression.getLeft().getHistoryReductionMode(), expression
.getRight().getHistoryReductionMode());
// combination ANY, ANY has a tradeoff. We can terminate evaluation as
// soon as we find a combination that results in true, BUT if we
// continue we might find a longer deferUntil
comparatorResult.startOuterLoop();
int l = 0, r = 0;
for (l = 0; l < left.getValues().length; l++) {
comparatorResult.startInnerLoop();
for (r = 0; r < right.getValues().length; r++) {
if (comparatorResult.innerResult(comparePair(
expression.getComparator(),
left.getValues()[l].getValue(),
right.getValues()[r].getValue()))) {
break;
}
}
if (comparatorResult.outerResult()) {
break;
}
}
// if we don't get a break statement, l and r might be off by one (past
// the last index)
l = Math.min(left.getValues().length - 1, l);
r = Math.min(right.getValues().length - 1, r);
// find out how long this result will remain valid and defer
// evaluation to that moment
DeferUntilResult leftDefer = remainsValidUntil(expression.getLeft(),
left.getValues()[l].getTimestamp(), left.getOldestTimestamp(),
expression.getComparator(), comparatorResult.getTriState(),
true);
DeferUntilResult rightDefer = remainsValidUntil(expression.getRight(),
right.getValues()[r].getTimestamp(),
right.getOldestTimestamp(), expression.getComparator(),
comparatorResult.getTriState(), false);
comparatorResult.setDeferUntilGuaranteed(leftDefer.guaranteed
&& rightDefer.guaranteed);
comparatorResult.setDeferUntil(Math.min(leftDefer.deferUntil,
leftDefer.deferUntil));
return comparatorResult;
}
private class DeferUntilResult {
public long deferUntil;
public boolean guaranteed;
public DeferUntilResult(long deferUntil, boolean guaranteed) {
this.deferUntil = deferUntil;
this.guaranteed = guaranteed;
}
}
private Result doMath(String id, MathValueExpression expression, long now)
throws SwanException {
Result left = evaluate(id + Expression.LEFT_SUFFIX,
expression.getLeft(), now);
Result right = evaluate(id + Expression.RIGHT_SUFFIX,
expression.getRight(), now);
if (left.getValues().length == 0 || right.getValues().length == 0) {
Result result = new Result(left.getValues(),
left.getOldestTimestamp());
return result;
} else if (left.getValues().length == 1
|| right.getValues().length == 1) {
TimestampedValue[] values = new TimestampedValue[left.getValues().length
* right.getValues().length];
int index = 0;
for (int i = 0; i < left.getValues().length; i++) {
for (int j = 0; j < right.getValues().length; j++) {
values[index++] = operate(left.getValues()[i],
expression.getOperator(), right.getValues()[j]);
}
}
Result result = new Result(values, Math.min(
left.getOldestTimestamp(), right.getOldestTimestamp()));
result.setDeferUntil(Math.min(left.getDeferUntil(),
right.getDeferUntil()));
result.setDeferUntilGuaranteed(false);
return result;
} else {
// TODO: we could relax this statement a bit, and allow for
// cross-product
throw new SwanException("Unable to combine two arrays, "
+ "only one of the operands can be an array: "
+ expression.getOperator());
}
}
private Result getFromSensor(String id, SensorValueExpression expression,
long now) {
if (mSensors.get(id) == null) {
Log.d(TAG, "not yet bound for: " + id + ", " + expression);
Result result = new Result(new TimestampedValue[] {}, 0);
// TODO make this a constant (configurable?)
result.setDeferUntil(System.currentTimeMillis() + 300);
result.setDeferUntilGuaranteed(false);
return result;
}
try {
List<TimestampedValue> values = mSensors.get(id).getValues(id, now,
expression.getHistoryLength());
// TODO if values is empty, should we not just defer until forever?
// And can values be null at all?
if (values == null || values.size() == 0) {
Result result = new Result(new TimestampedValue[] {}, 0);
// TODO make this a constant (configurable?)
result.setDeferUntil(now + 1000);
result.setDeferUntilGuaranteed(false);
return result;
}
TimestampedValue[] reduced = TimestampedValue.applyMode(values,
expression.getHistoryReductionMode());
Result result = new Result(reduced, values.get(values.size() - 1)
.getTimestamp());
if (expression.getHistoryLength() == 0 || reduced == null
|| reduced.length == 0) {
// we cannot defer based on values, new values will be retrieved
// when they arrive
result.setDeferUntil(Long.MAX_VALUE);
result.setDeferUntilGuaranteed(false);
} else {
result.setDeferUntil(values.get(values.size() - 1)
.getTimestamp() + expression.getHistoryLength());
result.setDeferUntilGuaranteed(false);
}
return result;
} catch (RemoteException e) {
Log.e(TAG,
"Got remote exception while retrieving values for expression "
+ expression + " with id " + id, e);
}
return null;
}
private DeferUntilResult remainsValidUntil(ValueExpression expression,
long determiningValueTimestamp, long oldestValueTimestamp,
Comparator comparator, TriState triState, boolean left) {
if (expression instanceof MathValueExpression) {
// math value is valid as long both of its children are valid
DeferUntilResult leftResult = remainsValidUntil(
((MathValueExpression) expression).getLeft(),
determiningValueTimestamp, oldestValueTimestamp,
comparator, triState, left);
DeferUntilResult rightResult = remainsValidUntil(
((MathValueExpression) expression).getRight(),
determiningValueTimestamp, oldestValueTimestamp,
comparator, triState, left);
return new DeferUntilResult(Math.min(leftResult.deferUntil,
rightResult.deferUntil), leftResult.guaranteed
&& rightResult.guaranteed);
} else if (expression instanceof ConstantValueExpression) {
return new DeferUntilResult(Long.MAX_VALUE, true);
} else if (expression instanceof SensorValueExpression) {
HistoryReductionMode mode = ((SensorValueExpression) expression)
.getHistoryReductionMode();
long historyLength = ((SensorValueExpression) expression)
.getHistoryLength();
if (historyLength == 0) {
return new DeferUntilResult(Long.MAX_VALUE, false);
}
long deferTime = determiningValueTimestamp + historyLength;
// here we need the big table, see thesis
// symmetric (left or right doesn't matter)
if (comparator == Comparator.EQUALS
|| comparator == Comparator.REGEX_MATCH
|| comparator == Comparator.STRING_CONTAINS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
}
} else if (comparator == Comparator.NOT_EQUALS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
}
}
// assymetric (left or right does matter)
if (left) {
if (comparator == Comparator.GREATER_THAN
|| comparator == Comparator.GREATER_THAN_OR_EQUALS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.MAX
|| mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.MIN
|| mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
}
} else if (comparator == Comparator.LESS_THAN
|| comparator == Comparator.LESS_THAN_OR_EQUALS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.MIN
|| mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.MAX
|| mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
}
}
} else {
if (comparator == Comparator.GREATER_THAN
|| comparator == Comparator.GREATER_THAN_OR_EQUALS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.MIN
|| mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.MAX
|| mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
}
} else if (comparator == Comparator.LESS_THAN
|| comparator == Comparator.LESS_THAN_OR_EQUALS) {
if (triState == TriState.TRUE) {
if (mode == HistoryReductionMode.MAX
|| mode == HistoryReductionMode.ANY) {
return new DeferUntilResult(deferTime, true);
}
} else if (triState == TriState.FALSE) {
if (mode == HistoryReductionMode.MIN
|| mode == HistoryReductionMode.ALL) {
return new DeferUntilResult(deferTime, true);
}
}
}
}
// otherwise we defer based on the oldest timestamp
return new DeferUntilResult(oldestValueTimestamp + historyLength,
false);
}
return new DeferUntilResult(0, false); // should not happen!
}
private void sleepAndBeReady(final String id, final Expression expression,
final long readyTime) {
if (expression instanceof LogicExpression) {
sleepAndBeReady(id + Expression.LEFT_SUFFIX,
((LogicExpression) expression).getLeft(), readyTime);
sleepAndBeReady(id + Expression.RIGHT_SUFFIX,
((LogicExpression) expression).getRight(), readyTime);
} else if (expression instanceof ComparisonExpression) {
sleepAndBeReady(id + Expression.LEFT_SUFFIX,
((ComparisonExpression) expression).getLeft(), readyTime);
sleepAndBeReady(id + Expression.RIGHT_SUFFIX,
((ComparisonExpression) expression).getRight(), readyTime);
} else if (expression instanceof MathValueExpression) {
sleepAndBeReady(id + Expression.LEFT_SUFFIX,
((MathValueExpression) expression).getLeft(), readyTime);
sleepAndBeReady(id + Expression.RIGHT_SUFFIX,
((MathValueExpression) expression).getRight(), readyTime);
} else if (expression instanceof SensorValueExpression) {
String location = expression.getLocation();
// do the real work here, let the sensor stop produce values until
// ready time.
long sensorStartUpTime = 0;
if (!location.equals(Expression.LOCATION_SELF)
&& !location.equals(Expression.LOCATION_INDEPENDENT)) {
sensorStartUpTime = START_UP_TIME_REMOTE_SENSOR;
} else {
try {
mSensors.get(id).getStartUpTime(id);
} catch (RemoteException e) {
Log.d(TAG,
"Got unexpected remote exception while retrieving startup time",
e);
}
}
final long sensorReadyTime = readyTime - sensorStartUpTime;
if (sensorReadyTime < System.currentTimeMillis()) {
return;
}
unbindFromSensor(id);
// this should probably be replaced by an android alarm.
new Thread("sleepAndBeReady-" + id) {
public void run() {
try {
sleep(sensorReadyTime - System.currentTimeMillis());
} catch (InterruptedException e) {
// should not happen
}
try {
bindToSensor(id, (SensorValueExpression) expression,
false);
} catch (SensorConfigurationException e) {
Log.d(TAG, "This should not happen!", e);
} catch (SensorSetupFailedException e) {
Log.d(TAG,
"Failed to re bind after sleep and be ready", e);
}
}
}.start();
}
}
private boolean shortcut(LogicExpression expression, Result first) {
// Can we short circuit and don't evaluate the last expression?
// FALSE && ?? -> FALSE
// TRUE || ?? -> TRUE
// the only drawback of short circuiting is that we could have
// FALSE && FALSE
// TRUE || TRUE
// where the last result has a higher defer until, than the first. But
// the leftFirst() method should prevent this to happen.
if (expression.getOperator() instanceof UnaryLogicOperator) {
// we can always shortcut unary logic operators (there is no last
// expression).
return true;
}
if ((first.getTriState() == TriState.FALSE && expression.getOperator()
.equals(BinaryLogicOperator.AND))
|| ((first.getTriState() == TriState.TRUE) && expression
.getOperator().equals(BinaryLogicOperator.OR))) {
return true;
}
return false;
}
private static Object promote(Object object) {
if (object instanceof Integer) {
return Long.valueOf((Integer) object);
}
if (object instanceof Float) {
return Double.valueOf((Float) object);
}
return object;
}
/**
* Evaluates a leaf item performing the comparison.
*
* @param left
* the left side values
* @param right
* the right side values
* @return Result.FALSE or Result.TRUE
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
public static TriState comparePair(final Comparator comparator,
Object left, Object right) {
TriState result = TriState.FALSE;
// promote types
left = promote(left);
right = promote(right);
switch (comparator) {
case LESS_THAN:
if (((Comparable) left).compareTo(right) < 0) {
result = TriState.TRUE;
}
break;
case LESS_THAN_OR_EQUALS:
if (((Comparable) left).compareTo(right) <= 0) {
result = TriState.TRUE;
}
break;
case GREATER_THAN:
if (((Comparable) left).compareTo(right) > 0) {
result = TriState.TRUE;
}
break;
case GREATER_THAN_OR_EQUALS:
if (((Comparable) left).compareTo(right) >= 0) {
result = TriState.TRUE;
}
break;
case EQUALS:
if (((Comparable) left).compareTo(right) == 0) {
result = TriState.TRUE;
}
break;
case NOT_EQUALS:
if (((Comparable) left).compareTo(right) != 0) {
result = TriState.TRUE;
}
break;
case REGEX_MATCH:
if (((String) left).matches((String) right)) {
result = TriState.TRUE;
}
break;
case STRING_CONTAINS:
if (((String) left).contains((String) right)) {
result = TriState.TRUE;
}
break;
default:
throw new AssertionError("Unknown comparator '" + comparator
+ "'. Should not happen");
}
return result;
}
/**
* Performs the operation on the requested values.
*
* @param left
* the left side
* @param right
* the right side
* @return the timestamped values
* @throws SwanException
* if someting goes wrong
*/
private TimestampedValue operate(final TimestampedValue left,
MathOperator operator, final TimestampedValue right)
throws SwanException {
if (left.getValue() instanceof Double
&& right.getValue() instanceof Double) {
return new TimestampedValue(operateDouble((Double) left.getValue(),
operator, (Double) right.getValue()), left.getTimestamp());
} else if (left.getValue() instanceof Long
&& right.getValue() instanceof Long) {
return new TimestampedValue(operateLong((Long) left.getValue(),
operator, (Long) right.getValue()), left.getTimestamp());
} else if (left.getValue() instanceof String
&& right.getValue() instanceof String) {
return new TimestampedValue(operateString((String) left.getValue(),
operator, (String) right.getValue()), left.getTimestamp());
} else if (left.getValue() instanceof Location
&& right.getValue() instanceof Location) {
return new TimestampedValue(operateLocation(
(Location) left.getValue(), operator,
(Location) right.getValue()), left.getTimestamp());
}
throw new SwanException("Trying to operate on incompatible types: "
+ left.getValue().getClass() + " and "
+ right.getValue().getClass());
}
/**
* Operates on doubles.
*
* @param left
* the left side value
* @param right
* the right side value
* @return the combined value
* @throws SwanException
* if something goes wrong.
*/
private Double operateDouble(final double left, MathOperator operator,
final double right) throws SwanException {
Double ret;
switch (operator) {
case MINUS:
ret = left - right;
break;
case PLUS:
ret = left + right;
break;
case TIMES:
ret = left * right;
break;
case DIVIDE:
ret = left / right;
break;
case MOD:
ret = left % right;
default:
throw new SwanException("Unknown operator: '" + operator
+ "' for type Double");
}
return ret;
}
/**
* Operates on longs.
*
* @param left
* the left side value
* @param right
* the right side value
* @return the combined value
* @throws SwanException
* if something goes wrong.
*/
private Long operateLong(final long left, MathOperator operator,
final long right) throws SwanException {
Long ret;
switch (operator) {
case MINUS:
ret = left - right;
break;
case PLUS:
ret = left + right;
break;
case TIMES:
ret = left * right;
break;
case DIVIDE:
ret = left / right;
break;
case MOD:
ret = left % right;
default:
throw new SwanException("Unknown operator: '" + operator
+ "' for type Long");
}
return ret;
}
/**
* Operates on string.
*
* @param left
* the left side value
* @param right
* the right side value
* @return the combined value
* @throws SwanException
* if something goes wrong.
*/
private String operateString(final String left, MathOperator operator,
final String right) throws SwanException {
String ret;
switch (operator) {
case PLUS:
ret = left + right;
break;
default:
throw new SwanException("Unknown operator: '" + operator
+ "' for type String");
}
return ret;
}
/**
* Operates on locations.
*
* @param left
* the left side value
* @param right
* the right side value
* @return the combined value
* @throws SwanException
* if something goes wrong.
*/
private Float operateLocation(final Location left, MathOperator operator,
final Location right) throws SwanException {
Float ret;
switch (operator) {
case MINUS:
float[] results = new float[3];
Location.distanceBetween(left.getLatitude(), left.getLongitude(),
right.getLatitude(), right.getLongitude(), results);
ret = results[0];
break;
default:
throw new SwanException("Unknown operator: '" + operator
+ "' for type Location");
}
return ret;
}
public Bundle[] activeSensorsAsBundle() {
ArrayList<Bundle> sensors = new ArrayList<Bundle>();
for (String key : mSensors.keySet()) {
try {
boolean dup = false;
for (Bundle b : sensors) {
if (b.getString("name").equals(
mSensors.get(key).getInfo().getString("name"))) {
dup = true;
}
}
if (!dup) {
sensors.add(mSensors.get(key).getInfo());
}
} catch (RemoteException e) {
e.printStackTrace();
}
}
return sensors.toArray(new Bundle[sensors.size()]);
}
}