package interdroid.swan;
import interdroid.swan.swansong.Expression;
import interdroid.swan.swansong.TimestampedValue;
import interdroid.swan.swansong.TriState;
import interdroid.swan.swansong.TriStateExpression;
import interdroid.swan.swansong.ValueExpression;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import android.content.BroadcastReceiver;
import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.content.pm.ResolveInfo;
import android.graphics.BitmapFactory;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Parcelable;
import android.util.Log;
public class ExpressionManager {
private static final String TAG = "ExpressionManager";
/**
* Action to be used to register an {@link Expression} with an
* {@link Intent}. Registration should preferably be done with the
* {@link #registerExpression(Context, String, Expression, ExpressionListener)}
* ,
* {@link #registerTriStateExpression(Context, String, TriStateExpression, TriStateExpressionListener)}
* ,
* {@link #registerValueExpression(Context, String, ValueExpression, ValueExpressionListener)}
* convenience methods.
*/
public static final String ACTION_REGISTER = "interdroid.swan.REGISTER";
/**
* Action to be used to unregister an {@link Expression} with an
* {@link Intent}. Unregistration should preferably be done with the
* {@link #unregisterExpression(Context, String)} convenience method.
*/
public static final String ACTION_UNREGISTER = "interdroid.swan.UNREGISTER";
/**
* Action to filter on with a broadcast receiver that indicates the arrival
* of new values for a {@link ValueExpression}. The
* {@link ValueExpressionListener} already listens to these broadcasts and
* forwards them to the listener.
*/
public static final String ACTION_NEW_VALUES = "interdroid.swan.NEW_VALUES";
/**
* Action to filter on with a broadcast receiver that indicates the arrival
* of a new tristate for a {@link TristateExpression}. The
* {@link TristateExpressionListener} already listens to these broadcasts
* and forwards them to the listener.
*/
public static final String ACTION_NEW_TRISTATE = "interdroid.swan.NEW_TRISTATE";
/**
* The extra key that contains the Parcelable[] with the new
* {@link TimestampedValue}s. Cast the array items one by one, rather than
* the entire array, since this will throw a {@link ClassCastException}.
*/
public static final String EXTRA_NEW_VALUES = "values";
/**
* The extra key that contains the new {@link TriState}.
*/
public static final String EXTRA_NEW_TRISTATE = "tristate";
/**
* The extra key that contains the timestamp of when the evaluation
* resulting in the {@link TriState} happened.
*/
public static final String EXTRA_NEW_TRISTATE_TIMESTAMP = "timestamp";
/**
* The extra key that can be used with
* {@link #registerExpression(Context, String, Expression, Intent, Intent, Intent, Intent)}
* intents to indicate what type the intents are. Possible values are
* {@link #INTENT_TYPE_ACTIVITY}, {@link #INTENT_TYPE_BROADCAST} (default),
* {@link #INTENT_TYPE_SERVICE}.
*/
public static final String EXTRA_INTENT_TYPE = "intent_type";
/**
* Extra value for key {@link #EXTRA_INTENT_TYPE} that indicates that the
* intent should be broadcast
*/
public static final String INTENT_TYPE_BROADCAST = "broadcast";
/**
* Extra value for key {@link #EXTRA_INTENT_TYPE} that indicates that the
* intent should start an activity
*/
public static final String INTENT_TYPE_ACTIVITY = "activity";
/**
* Extra value for key {@link #EXTRA_INTENT_TYPE} that indicates that the
* intent should start a service
*/
public static final String INTENT_TYPE_SERVICE = "service";
/**
* Map containing all listeners currently in use, mapped by id of the
* expression
*/
private static Map<String, ExpressionListener> sListeners = new HashMap<String, ExpressionListener>();
/**
* Boolean indicating whether we received a register to intercept broadcasts
* and forward them to the respective listeners
*/
private static boolean sReceiverRegistered = false;
/**
* Broadcast receiver used in case values have to be forwarded to listeners
*/
private static BroadcastReceiver sReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String id = intent.getData().getFragment();
Log.d(TAG, "on receive");
if (sListeners.containsKey(id)) {
if (intent.getAction().equals(ACTION_NEW_VALUES)) {
// do the conversion from Parcelable[] to
// TimestampedValue[], casting doesn't work
Parcelable[] parcelables = (Parcelable[]) intent
.getParcelableArrayExtra(EXTRA_NEW_VALUES);
TimestampedValue[] timestampedValues = new TimestampedValue[parcelables.length];
System.arraycopy(parcelables, 0, timestampedValues, 0,
parcelables.length);
sListeners.get(id).onNewValues(id, timestampedValues);
} else if (intent.getAction().equals(ACTION_NEW_TRISTATE)) {
sListeners
.get(id)
.onNewState(
id,
intent.getLongExtra(
EXTRA_NEW_TRISTATE_TIMESTAMP, 0),
TriState.valueOf(intent
.getStringExtra(EXTRA_NEW_TRISTATE)));
}
} else {
Log.d(TAG, "got spurious broadcast: " + intent.getDataString());
}
}
};
/**
* Returns all the information about the sensors known to SWAN. These are
* sensors both from within the SWAN framework and 3rd party sensors.
*
* @param context
* @return List of information per known sensor.
*/
public static List<SensorInfo> getSensors(Context context) {
List<SensorInfo> result = new ArrayList<SensorInfo>();
Log.d(TAG, "Starting sensor discovery");
PackageManager pm = context.getPackageManager();
Intent queryIntent = new Intent("interdroid.swan.sensor.DISCOVER");
List<ResolveInfo> discoveredSensors = pm.queryIntentActivities(
queryIntent, PackageManager.GET_META_DATA);
Log.d(TAG, "Found " + discoveredSensors.size() + " sensors");
for (ResolveInfo discoveredSensor : discoveredSensors) {
try {
Drawable icon = new BitmapDrawable(
context.getResources(),
BitmapFactory.decodeResource(
pm.getResourcesForApplication(discoveredSensor.activityInfo.packageName),
discoveredSensor.activityInfo.icon));
Log.d(TAG, "\t" + discoveredSensor.activityInfo.name);
result.add(new SensorInfo(new ComponentName(
discoveredSensor.activityInfo.packageName,
discoveredSensor.activityInfo.name),
discoveredSensor.activityInfo.metaData, icon));
} catch (Exception e) {
Log.e(TAG, "Error with discovered sensor: " + discoveredSensor,
e);
}
}
return result;
}
/**
* Returns the information about a sensor with a specific name or throws a
* {@link SwanException} if the sensor is not installed on the device.
*
* @param context
* @param name
* the entity name of the sensor
* @return The sensor information
* @throws SwanException
*/
public static SensorInfo getSensor(Context context, String name)
throws SwanException {
for (SensorInfo sensorInfo : getSensors(context)) {
if (sensorInfo.getEntity().equals(name)) {
return sensorInfo;
}
}
throw new SwanException("Sensor '" + name + "' not installed.");
}
/**
* Registers a {@link TriStateExpression} for evaluation.
*
* @param context
* @param id
* the user provided unique id of the expression. Should not
* contain {@link Expression#SEPARATOR} or end with any of the
* {@link Expression#RESERVED_SUFFIXES}.
* @param expression
* the {@link TriStateExpression} that should be evaluated
* @param listener
* a {@link TriStateExpressionListener} that receives the
* evaluation results. If this parameter is null, it is also
* possible to listen for the results using a
* {@link BroadcastReceiver}. Filter on datascheme
* "swan://<your.package.name>#<your.expression.id>" and action
* {@link #ACTION_NEW_TRISTATE}.
* @throws SwanException
* if id is null or invalid
*/
public static void registerTriStateExpression(final Context context,
final String id, final TriStateExpression expression,
final TriStateExpressionListener listener) throws SwanException {
if (listener == null) {
registerExpression(context, id, expression, null);
} else {
registerExpression(context, id, expression,
new ExpressionListener() {
@Override
public void onNewValues(String id,
TimestampedValue[] newValues) {
// ignore, will not happen
}
@Override
public void onNewState(String id, long timestamp,
TriState newState) {
listener.onNewState(id, timestamp, newState);
}
});
}
}
/**
* Registers a {@link ValueExpression} for evaluation.
*
* @param context
* @param id
* the user provided unique id of the expression. Should not
* contain {@link Expression#SEPARATOR} or end with any of the
* {@link Expression#RESERVED_SUFFIXES}.
* @param expression
* the {@link ValueExpression} that should be evaluated
* @param listener
* a {@link ValueExpressionListener} that receives the evaluation
* results. If this parameter is null, it is also possible to
* listen for the results using a {@link BroadcastReceiver}.
* Filter on datascheme
* "swan://<your.package.name>#<your.expression.id>" and action
* {@link #ACTION_NEW_VALUES}.
* @throws SwanException
* if id is null or invalid
*/
public static void registerValueExpression(Context context, String id,
ValueExpression expression, final ValueExpressionListener listener)
throws SwanException {
if (listener == null) {
registerExpression(context, id, expression, null);
} else {
registerExpression(context, id, expression,
new ExpressionListener() {
@Override
public void onNewValues(String id,
TimestampedValue[] newValues) {
listener.onNewValues(id, newValues);
}
@Override
public void onNewState(String id, long timestamp,
TriState newState) {
// ignore, will not happen
}
});
}
}
/**
* Registers an {@link Expression} for evaluation.
*
* @param context
* @param id
* the user provided unique id of the expression. Should not
* contain {@link Expression#SEPARATOR} or end with any of the
* {@link Expression#RESERVED_SUFFIXES}.
* @param expression
* the {@link ValueExpression} that should be evaluated
* @param listener
* a {@link ValueExpressionListener} that receives the evaluation
* results. If this parameter is null, it is also possible to
* listen for the results using a {@link BroadcastReceiver}.
* Filter on datascheme
* "swan://<your.package.name>#<your.expression.id>" and action
* {@link #ACTION_NEW_VALUES} or {@link #ACTION_NEW_TRISTATE}.
* @throws SwanException
* if id is null or invalid
*/
public static void registerExpression(Context context, String id,
Expression expression, ExpressionListener expressionListener)
throws SwanException {
if (id == null) {
throw new SwanException("Invalid id. Null is not allowed as id");
}
if (id.contains(Expression.SEPARATOR)) {
throw new SwanException("Invalid id: '" + id
+ "' contains reserved separator '" + Expression.SEPARATOR
+ "'");
}
for (String suffix : Expression.RESERVED_SUFFIXES) {
if (id.endsWith(suffix)) {
throw new SwanException("Invalid id. Suffix '" + suffix
+ "' is reserved for internal use.");
}
}
if (sListeners.containsKey(id)) {
throw new SwanException("Listener already registered for id '" + id
+ "'");
} else {
if (expressionListener != null) {
if (sListeners.size() == 0) {
sReceiverRegistered = true;
registerReceiver(context);
}
sListeners.put(id, expressionListener);
}
}
Intent newTriState = new Intent(ACTION_NEW_TRISTATE);
newTriState.setData(Uri.parse("swan://" + context.getPackageName()
+ "#" + id));
Intent newValues = new Intent(ACTION_NEW_VALUES);
newValues.setData(Uri.parse("swan://" + context.getPackageName() + "#"
+ id));
registerExpression(context, id, expression, newTriState, newTriState,
newTriState, newValues);
}
/**
* Registers a {@link TriStateExpression} for evaluation.
*
* @param context
* @param id
* the user provided unique id of the expression. Should not
* contain {@link Expression#SEPARATOR} or end with any of the
* {@link Expression#RESERVED_SUFFIXES}.
* @param expression
* the {@link TriStateExpression} that should be evaluated
* @param onTrue
* Intent that should be fired when state changes to true. By
* default the Intent is used to send a broadcast. Add
* {@link #EXTRA_INTENT_TYPE} with any of the values
* {@link #INTENT_TYPE_ACTIVITY}, {@link #INTENT_TYPE_SERVICE} to
* have Swan launch an activity or service.
* @param onFalse
* Intent that should be fired when state changes to false. By
* default the Intent is used to send a broadcast. Add
* {@link #EXTRA_INTENT_TYPE} with any of the values
* {@link #INTENT_TYPE_ACTIVITY}, {@link #INTENT_TYPE_SERVICE} to
* have Swan launch an activity or service.
* @param onUndefined
* Intent that should be fired when state changes to undefined.
* By default the Intent is used to send a broadcast. Add
* {@link #EXTRA_INTENT_TYPE} with any of the values
* {@link #INTENT_TYPE_ACTIVITY}, {@link #INTENT_TYPE_SERVICE} to
* have Swan launch an activity or service.
*/
public static void registerTriStateExpression(Context context, String id,
TriStateExpression expression, Intent onTrue, Intent onFalse,
Intent onUndefined) {
registerExpression(context, id, expression, onTrue, onFalse,
onUndefined, null);
}
/**
* Registers a {@link ValueExpression} for evaluation.
*
* @param context
* @param id
* the user provided unique id of the expression. Should not
* contain {@link Expression#SEPARATOR} or end with any of the
* {@link Expression#RESERVED_SUFFIXES}.
* @param expression
* the {@link ValueExpression} that should be evaluated
* @param onNewValues
* Intent that should be fired when new values are available. Add
* {@link #EXTRA_INTENT_TYPE} with any of the values
* {@link #INTENT_TYPE_ACTIVITY}, {@link #INTENT_TYPE_SERVICE} to
* have Swan launch an activity or service.
*/
public static void registerValueExpression(Context context, String id,
TriStateExpression expression, Intent onNewValues) {
registerExpression(context, id, expression, null, null, null,
onNewValues);
}
private static void registerExpression(Context context, String id,
Expression expression, Intent onTrue, Intent onFalse,
Intent onUndefined, Intent onNewValues) {
Intent intent = new Intent(ACTION_REGISTER);
intent.putExtra("expressionId", id);
intent.putExtra("expression", expression.toParseString());
intent.putExtra("onTrue", onTrue);
intent.putExtra("onFalse", onFalse);
intent.putExtra("onUndefined", onUndefined);
intent.putExtra("onNewValues", onNewValues);
context.sendBroadcast(intent);
}
/**
* Unregisters a previously registered {@link Expression} for evaluation.
*
* @param context
* @param id
* the id with which the expression was registered.
*/
public static void unregisterExpression(Context context, String id) {
sListeners.remove(id);
if (sListeners.size() == 0 && sReceiverRegistered) {
sReceiverRegistered = false;
unregisterReceiver(context);
}
Intent intent = new Intent(ACTION_UNREGISTER);
intent.putExtra("expressionId", id);
context.sendBroadcast(intent);
}
/**
* registers the broadcast receiver to receive values on behalve of
* listeners and forward them subsequently.
*
* @param context
*/
private static void registerReceiver(Context context) {
IntentFilter intentFilter = new IntentFilter();
intentFilter.addAction(ACTION_NEW_TRISTATE);
intentFilter.addAction(ACTION_NEW_VALUES);
intentFilter.addDataScheme("swan");
intentFilter.addDataAuthority(context.getPackageName(), null);
context.registerReceiver(sReceiver, intentFilter);
}
/**
* unregisters the broadcast receiver. This is executed if no listeners are
* present anymore.
*
* @param context
*/
private static void unregisterReceiver(Context context) {
context.unregisterReceiver(sReceiver);
}
}