Atooma SDK allows to create new modules and components by exploiting a pre-defined set of abstract classes, as reported in table below:
Component | Classes |
---|---|
Module | com.atooma.engine.Module |
Value Type | com.atooma.engine.ValueType |
Trigger | com.atooma.engine.IntentBasedTrigger ,
com.atooma.engine.AlarmBasedTrigger ,
com.atooma.engine.Trigger |
Condition Checker | com.atooma.engine.ConditionChecker |
Performer | com.atooma.engine.Performer |
Creating new modules requires to extend the abstract class com.atooma.engine.Module
, including the following protected constructor and set of abstract methods to be implemented:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 | /**
* @param id
* Module identifier, to be unique among all available
* modules within Atooma enabled application.
* @param version
* Module version, to be incremented every time new
* components are added.
*/
protected Module(String id, int version) { ... }
/**
* Allows to declare dependencies of this module
* on other modules
*/
protected abstract void declareDependencies();
/**
* Allows to initialize this module.
*/
protected abstract boolean init();
/**
* Allows to register components
* (e.g. trigger, performer and so on).
*/
protected abstract void registerComponents();
/**
* Allows to deallocate resources when destroying
* this module.
*/
protected abstract void destroy();
|
All the methods above will be described in details in the following sections.
Modules may depend on other modules in two different ways:
CORE.STRING
. This means that module depends on CORE
module version 1. Reason for version 1 is that STRING
value type is available in CORE
module starting from that version.SMS
module. Obviously such module is meaningless in case device doesn’t include proper hardware for GSM communications. Presence of such hardware is determined by TELEPHONY
module. In case TELEPHONY
is not available, SMS
itself is useless. That’s why SMS
depends on TELEPHONY
version 1.All dependencies must be declared within method declareDependencies()
, that must include proper calls to the inherited method declareDependency(String id, int version)
.
For example, in case of SMS
:
1 2 3 4 5 | @Override
protected void declareDependencies() {
declareDependency("CORE", 1);
declareDependency("TELEPHONY", 1);
}
|
Note
When a module depends on another module, the first one will be loaded only if second one is successfully loaded and initialized.
Modules are initialized by invoking method init()
, that must define all operations to be perfomed for having module up and running. Method returns a boolean value for indicating whether initialization was successfully completed or not.
Note
In case a module fails its initialization block, all modules depending on it won’t be initialized too.
For example, during its initialization, TELEPHONY
module checks whether telephony service is available and a SIM card is present within the device or not. If not, the module and all modules depending on it won’t be loaded.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 | @Override
protected boolean init() {
Context context = getContext();
TelephonyManager tm = (TelephonyManager) context
.getSystemService(Context.TELEPHONY_SERVICE);
if (tm == null) {
return false;
}
int simState = tm.getSimState();
if (simState == TelephonyManager.SIM_STATE_ABSENT) {
return false;
}
return true;
}
|
Once module initialization is completed, method registerComponents()
is used for registering all components implemented by the module itself. Different registration methods are available for handling all possible component types:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | /**
* Allows to register a value type.
*/
protected void registerValueType(String id, int sinceVersion, ValueType vt)
/**
* Allows to register a trigger.
*/
protected void registerTrigger(String id, int sinceVersion, Trigger tr)
/**
* Allows to register a condition checker.
*/
protected void registerConditionChecker(String id, int sinceVersion, ConditionChecker cc)
/**
* Allows to register a performer.
*/
protected void registerPerformer(String id, int sinceVersion, Performer pe)
|
All methods above include three parameters:
RECEIVED
as identifier).When Atooma engine is deactivated (e.g. when turning off device) all loaded modules are destroyed, following an order that is opposite to the one used for loading them.
Method destroy()
is aimed to encapsulate logic for releasing specific module resources.
Below is reported a sample structure for SMS
module, including all concepts explained in previous sections.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class _SMS extends Module {
private static final String MODULE_ID = "SMS";
private static final int MODULE_VERSION = 1;
public _SMS() {
super(MODULE_ID, MODULE_VERSION);
}
@Override
protected void declareDependencies() {
declareDependency("CORE", 1);
declareDependency("TELEPHONY", 1);
}
@Override
protected boolean init() {
return true;
}
@Override
protected void registerComponents() {
registerTrigger("INCOMING", 1, new TR_Incoming());
registerPerformer("SEND", 1, new PE_Send());
}
@Override
protected void destroy() {
}
}
|
Initialization is always successful, unless dependencies are not fully satisfied. Two components are declared. First one is a trigger, allowing to listen for incoming message events. Second one is a performer, allowing to send messages.
Modules and components have just one single instance within Atooma engine. This means for example that there is just one instance of _SMS
module class and just one instance of its trigger TR_Incoming
.
Atooma engine works in multi-threading mode and this means that several rules can be simultanously executed. When two or more rules use the same component, the same instance is shared between them.
Basing on this, it’s extremely important to properly handle eventual concurrency and synchronization issues when developing modules and components.
Note
A good practice is to keep inside component classes some data structure for handling information related to specific rules (e.g. a Map with rule id as key).
Warning
Keep always in mind that same external resources can be accessed simultanously in different points even if their management is demanded to a single component.
Value types are components aimed to define, describe and manage data handled by the other components. Some examples are STRING
, NUMBER
, TIMESTAMP
and POSITION
. For each value type it’s important to define a binary representation, as already described in section Atooma binary format. It’s convenient to represent values for a specific type by using dedicated Java objects to be used at runtime. Classes implementing value types are responsible for the conversion between Java objects and their binary representation and vice versa.
In order to implement a value type, class com.atooma.engine.ValueType
must be extended. Implementation of following methods is required:
1 2 3 4 | public Class<?> getValueClass()
public byte[] encode(Object value)
public Object decode(byte[] value) throws Exception
public String getStringRepresentation(Object value)
|
The implementation of the value type must declare which Java class to use for representing values for the managed type. Such class is declared by implementing method:
1 | protected Class<?> getValueClass()
|
For example, CORE.TIMESTAMP
value type, used for representing date and time, internally uses a Long
object (number of mills according to UNIX time notation). Its getValueClass()
method is implemented as follows:
1 2 3 4 | @Override
public Class<?> getValueClass() {
return Long.class;
}
|
Transformation from Java object to binary sequence is implemented through method:
1 | protected byte[] encode(Object value)
|
When invoking it, method receives as argument a Java object of the type defined by the getValueClass()
method. Provided value is never null
.
The opposite transformation is executed by defining method:
1 | protected Object decode(byte[] value) throws Exception
|
Received value is never null
(but the byte array can be empty). It’s good practice to check if received binary sequence is consistent with the value type definition. If not, method should throw an Exception
.
ValueType
class provides to its subclasses some methods for simplifying the encoding / decoding operations for primitive Java types. Methods available for encoding are:
1 2 3 4 5 | protected static byte[] encodeBoolean(boolean value)
protected static byte[] encodeInt(int value)
protected static byte[] encodeLong(long value)
protected static byte[] encodeDouble(double value)
protected static byte[] encodeString(String value)
|
For example, in case of CORE.TIMESTAMP
, the received object will be of Long
type. The binary representation, in such case, will be performed by encoding the value of the received long variable as follows:
1 2 3 4 5 | @Override
protected byte[] encode(Object value) {
long longValue = ((Long) value).longValue();
return encodeLong(longValue);
}
|
All the encoding methods mentioned above can be also used for encoding the atomic information of more complex data structures. An example is about GPS coordinates, that can be encoded as a sequence of two real numbers.
Methods for decoding are reported below:
1 2 3 4 5 | protected static boolean decodeBoolean(byte[] value) throws Exception
protected static int decodeInt(byte[] value) throws Exception
protected static long decodeLong(byte[] value) throws Exception
protected static double decodeDouble(byte[] value) throws Exception
protected static String decodeString(byte[] value) throws Exception
|
For example, in case of CORE.TIMESTAMP
:
1 2 3 4 5 6 7 | @Override
protected Object decode(byte[] value) throws Exception {
if (value.length != 8) {
throw new Exception("Invalid TIMESTAMP binary value");
}
return new Long(decodeLong(value));
}
|
Sometimes it’s important to handle data in form of array. Some examples can be an array of URLs containing a list of favorite web addresses or an array of phone numbers with all references for a contact.
For implementing an array of value types it’s possible to extend the abstract class:
1 | com.atooma.engine.ArrayValueType
|
This class extends ValueType
and requires the implementation of the following abstract methods:
1 2 | protected abstract byte[] encodeItem(Object item)
protected abstract Object decodeItem(byte[] item) throws Exception
|
These methods must implements the procedure for the encoding / decoding of single elements within the entire array (they should be the same used for the value type of the single element of the array).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | class VT_Timestamp extends ValueType {
@Override
public Class<?> getValueClass() {
return Long.class;
}
@Override
public byte[] encode(Object value) {
long longValue = ((Long) value).longValue();
return encodeLong(longValue);
}
@Override
public Object decode(byte[] value) throws Exception {
if (value.length != 8) {
throw new Exception("Invalid TIMESTAMP binary value");
}
return new Long(decodeLong(value));
}
@Override
public String getStringRepresentation(Object value) {
String pattern = getContext().getResources()
.getString(R.string.mod_core_timestamp_pattern);
DateFormat df = new SimpleDateFormat(pattern);
return df.format(new Date((Long) value));
}
}
|
Every value type must be registered to module it belongs to as follows:
1 2 3 4 | @Override
protected void registerComponents() {
registerValueType("TIMESTAMP", 1, new VT_Timestamp());
}
|
Triggers are components responsible for rules activation when a specific event occurs. Some examples are WIFI.ENABLED
or SMS.INCOMING
. Every trigger may require an optional list of input parameters and provide a list of output variables.
In order to create a new trigger it’s possible to extend one specific abstract class among the followings:
com.atooma.engine.IntentBasedTrigger
- Activation of intent-based triggers relies on the reception of a specific intent from Android system.com.atooma.engine.AlarmBasedTrigger
- Activation of alarm-based triggers relies on periodic checks of specific conditions.com.atooma.engine.Trigger
- Activation of generic triggers relies on custom logic not matching with neither of the previous cases.Regardless of trigger type, all triggers are required to implement the following methods:
1 2 | protected void declareParameters()
protected void declareVariables()
|
Additional methods are requested depending on trigger type:
IntentBasedTrigger
1 2 | protected IntentFilter getIntentFilter(String ruleId, Map<String,Object> parameters)
protected void received(String ruleId, Map<String,Object> parameters, Intent intent)
|
AlarmBasedTrigger
1 2 | protected ScheduleInfo getScheduleInfo(String ruleId, Map<String,Object> parameters)
protected void timeout(String ruleId, Map<String,Object> parameters)
|
Trigger
1 2 | protected void invoke(String ruleId, Map<String,Object> parameters)
protected void revoke(String ruleId)
|
Parameters required by triggers must be declared in the implementation of the abstract method:
1 | protected void declareParameters()
|
This method must include a sequence of calls to the inherited method:
1 | protected void declareParameter(String id, String module, String typeId, boolean required)
|
id
is the identifier of the required parameter.module
and typeId
identify value type, indicating respectively the module and the identifier of the referenced value type.required
indicates whether parameter is mandatory or can be null
.Below is reported an example:
1 2 3 4 | @Override
protected void declareParameters() {
declareParameter("CONNECTED", "CORE", "BOOLEAN", true);
}
|
Variables that can be used as output for a trigger must be explicitly declared in the implementation of method:
1 | protected void declareVariables()
|
This method must include a sequence of calls to the inherited method:
1 | protected void declareVariable(String id, String module, String typeId)
|
id
is the identifier of the output variable.module
and typeId
identify value type, indicating respectively the module and the identifier of the referenced value type.Below is reported an example:
1 2 3 4 5 6 | @Override
protected void declareVariables() {
declareVariable("SMS-TEXT", "CORE", "STRING");
declareVariable("SMS-SENDER", "CORE", "STRING");
declareVariable("SMS-TIME", "CORE", "TIMESTAMP");
}
|
When the event monitored by a trigger occurs, the trigger itself must notify the rule execution engine, allowing to proceed with rule next steps (either condition checkers or performers).
The following method must be used for this purpose:
1 | protected void trigger(String ruleId, Map<String, Object> variables)
|
ruleId
is the identifier of the rule to activate.variables
is a map with all the output variables. The key of the map is the variable identifier (according to declarations made in method declareVariables()
), while value must be of the Java type declared by the value type.Note
It’s mandatory for a trigger to produce as output all variables declared in method declareVariables()
.
Activation of intent-based triggers is based on the reception of a specific intent from Android system. Some examples of intent-based triggers are SMS.RECEIVED
and WIFI.CONNECTION-STATE-CHANGED
.
In order to develop an intent-based trigger, the abstract class com.atooma.engine.IntentBasedTrigger
must be implemented. Following methods are requested:
1 2 | protected IntentFilter getIntentFilter(String ruleId, Map<String, Object> parameters)
protected void received(String ruleId, Map<String, Object> parameters, Intent intent)
|
Trigger implementation must generate and return an intent filter when following method is invoked:
1 | protected IntentFilter getIntentFilter(String ruleId, Map<String, Object> parameters)
|
ruleId
is the identifier of the rule to create the intent filter for.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.All intents intercepted by the generated filter will be notified to the trigger through an invocation of the method:
1 | protected void received(String ruleId, Map<String, Object> parameters, Intent intent)
|
ruleId
is the identifier of the rule.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.intent
is the intercepted intent.Of course all values within the received intent must be checked before invoking the trigger()
method.
An example of intent-based trigger is SMS.INCOMING
, that is the trigger intercepting the incoming SMS event. When such event occur, Android system notifies all interested objects throught a broadcast intent called android.provider.Telephony.SMS_RECEIVED
, that also includes details concerned with the message itself (e.g. sender, content and so on).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 | class TR_Incoming extends IntentBasedTrigger {
@Override
protected void declareParameters() {
declareParameter("SENDER", "CONTACTS", "PHONE-NUMBER", false);
declareParameter("TEXT-FILTER", "CORE", "TEXT-FILTER", false);
}
@Override
protected void declareVariables() {
declareVariable("TEXT", "CORE", "STRING");
declareVariable("TIMESTAMP", "CORE", "TIMESTAMP");
declareVariable("SENDER-NAME", "CORE", "STRING");
declareVariable("SENDER-PHONE-NUMBER", "CONTACTS", "PHONE-NUMBER");
}
@Override
protected IntentFilter getIntentFilter(String ruleId, Map<String, Object> parameters) {
return new IntentFilter("android.provider.Telephony.SMS_RECEIVED");
}
@Override
protected void received(String ruleId, Map<String, Object> parameters, Intent intent) {
Bundle extras = intent.getExtras();
Object[] pdus = (Object[]) extras.get("pdus");
if (pdus != null) {
for (Object pdu : pdus) {
SmsMessage sms = SmsMessage.createFromPdu((byte[]) pdu);
// we must inject declared variables into rule execution context
Map<String, Object> variables = new HashMap<String, Object>();
variables.put("TEXT", sms.getMessageBody());
variables.put("TIMESTAMP", sms.getTimestampMillis());
String origin = sms.getDisplayOriginatingAddress();
variables.put("SENDER-NAME", sms.getDisplayOriginatingAddress());
PhoneNumber phoneNumber = null;
if (origin != null) {
phoneNumber = PhoneNumber.resolvePhoneNumber(origin, getContext());
variables.put("SENDER-PHONE-NUMBER", phoneNumber);
}
PhoneNumber senderFilter = (PhoneNumber) parameters.get("SENDER");
TextFilter textFilter = (TextFilter) parameters.get("TEXT-FILTER");
boolean senderMatch = senderFilter == null ||
(phoneNumber != null && phoneNumber.equals(senderFilter));
boolean textMatch = textFilter == null ||
textFilter.filter(sms.getMessageBody());
// triggering rule only if sender and text match
// with provided values
if (senderMatch && textMatch) {
trigger(ruleId, variables);
}
}
}
}
}
|
Activation of alarm-based triggers is based on periodic verification of specific events. A typical example is represented by the integration with web services or third party API, but they can be useful iven in case it’s not possible to exploit any Android Intent. Some examples of intent-based triggers are CORE.ALARM
and FACEBOOK.STATUS_UPDATED
.
In order to develop an alarm-based trigger, the abstract class com.atooma.engine.AlarmBasedTrigger
must be implemented. Following methods are requested:
1 2 | protected ScheduleInfo getScheduleInfo(String ruleId, Map<String, Object> parameters)
protected void onTimeout(String ruleId, Map<String, Object> parameters)
|
Trigger implementation must generate and return all information for defining a proper timer (scheduling) when following method is invoked:
1 | protected ScheduleInfo getScheduleInfo(String ruleId, Map<String, Object> parameters)
|
ruleId
is the identifier of the rule.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.It’s possible to use different constructors for building different types of ScheduleInfo
objects.
1 2 | public ScheduleInfo(boolean repeat, boolean exact, boolean wakeUp,
Long triggerAtTime, Long interval)
|
repeat
indicates whether timer must be reinitialized after first timeout.exact
is used for declaring if timer expiration must be strictly linked with parameters triggerAtTime
and interval
or if it’s possible for Android to optimize it taking care of eventual other contiguous timers.wakeUp
if true forces device to wake up when timer timeout occurs, if false allow to postpone timeout code execution to the next wake up.triggerAtTime
is the time in System.currentTimeMillis()
of the first timeout (if past, trigger will be immediately activated).interval
is the trigger repetition interval in mills. It is ignored if repeat
value is false.Instead of using an explicit constructor it’s also possible to exploit specific factory methods of ScheduleInfo
class for building timers.
1 | public static ScheduleInfo singleExecution(long time)
|
This method creates a ScheduleInfo
representing a timer with single execution at specific time and is equivalent to:
1 | new ScheduleInfo(false, true, true, time, null);
|
While:
1 | public static ScheduleInfo inexactRepeatEvery(long interval)
|
This method creates a ScheduleInfo
representing a timer with immediate execution, to be repeated every interval mills without strict and specific execution time. It’s equivalent to:
1 | new ScheduleInfo(true, false, true, System.currentTimeMillis(), interval);
|
At every timer timeout, trigger method onTimeout()
is invoked. Its implementation includes logic for checking whether trigger()
method must be invoked for activating the rule or not.
An example of alarm-based trigger is CORE.ALARM
, that works exactly like an alarm and doesn’t require any verification in onTimeout()
method.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 | public class TR_Alarm extends AlarmBasedTrigger {
@Override
protected void declareParameters() {
declareParameter("TIME", "CORE", "TIMESTAMP", true);
declareParameter("REPEAT", "CORE", "BOOLEAN", true);
}
@Override
protected void declareVariables() {
// no variable defined
}
@Override
protected ScheduleInfo getScheduleInfo(String ruleId, Map<String, Object> parameters) {
Long time = (Long) parameters.get("TIME");
Boolean repeat = (Boolean) parameters.get("REPEAT");
// repeat interval (24 hours)
// to be ignored if repeat == false
long aDay = 1000l * 60l * 60l * 24l;
// the next deadline
long triggerAtTime = time.longValue();
if (System.currentTimeMillis() > triggerAtTime) {
// if provided time is before current,
// alarm is moved to next day
long difference = System.currentTimeMillis() - triggerAtTime;
int days = (int) Math.ceil(difference / aDay);
triggerAtTime += (aDay * days);
}
return new ScheduleInfo(repeat, true, true, triggerAtTime, aDay);
}
@Override
protected void onTimeout(String ruleId, Map<String, Object> parameters) {
// no condition to be verified
// possible to directly proceed
// with rule triggering
trigger(ruleId, parameters);
}
}
|
In case trigger to be implemented doesn’t match to any of the previous categories, it’s possible to proceed at low level by directly extending the com.atooma.engine.Trigger
class.
Methods to be implemented are reported below:
1 2 | protected void invoke(String ruleId, Map<String, Object> parameters)
protected void revoke(String ruleId)
|
Method:
1 | protected void invoke(String ruleId, Map<String, Object> parameters)
|
Is used for implementing logic deciding when a rule must be activated.
ruleId
is the identifier of the rule.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.As soon as a rule doesn’t want anymore to receive notification about event to be monitored, following method will be invoked:
1 | protected void revoke(String ruleId)
|
ruleId
is the identifier of the rule that asked for event notification stop.Every trigger must be registered to module it belongs to as follows:
1 2 3 4 | @Override
protected void registerComponents() {
registerTrigger("RECEIVED", 1, new TR_Received());
}
|
Condition checkers are used by a module for allowing to check a condition when they are invoked and return a boolean outcome. They may require optional parameters.
In order to create a new condition checker abstract class com.atooma.engine.ConditionChecker
must be extended, including implementation of following methods:
1 2 | protected void declareParameters()
protected boolean invoke(String ruleId, Map<String, Object> parameters)
|
Parameters required by a condition checker must be declared within method:
1 | protected void declareParameters()
|
This method must include a sequence of calls to the inherited method:
1 | protected void declareParameter(String id, String module, String typeId, boolean required)
|
id
is the identifier of the required parameter.module
and typeId
identify value type, indicating respectively the module and the identifier of the referenced value type.required
indicates whether parameter is mandatory or can be null
.Below is reported an example:
1 2 3 4 5 6 | @Override
protected void declareParameters() {
declareParameter("STR1", "CORE", "STRING", true);
declareParameter("STR2", "CORE", "STRING", true);
declareParameter("CASE-SENSITIVE", "CORE", "BOOLEAN", true);
}
|
When a rule requires condition handled by a condition checker to be verified, the following method is invoked:
1 | protected boolean invoke(String ruleId, Map<String, Object> parameters)
|
ruleId
is the identifier of the rule that asked for condition verification.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.Method complete its execution returning a boolean value, true if condition is verified, false otherwise.
WIFI.CONNECTED
condition checker allows to control if WiFi is enabled and connected. It requires an optional parameter SSID
with Type CORE.STRING
, that reports the identifier of the WiFi network to check. In case such parameter is not defined, connection to any network is positively evaluated.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | public class CC_Connected extends ConditionChecker {
@Override
protected void declareParameters() {
declareParameter("SSID", "CORE", "STRING", false);
}
@Override
protected boolean invoke(String ruleId, Map<String, Object> parameters) {
WifiManager wifiManager = (WifiManager) getContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo info = wifiManager.getConnectionInfo();
if (info != null) {
String ssid = (String) parameters.get("SSID");
if (ssid == null || (ssid = ssid.trim()).length() == 0) {
return true;
} else {
WifiManager manager = (WifiManager) getContext()
.getSystemService(Context.WIFI_SERVICE);
WifiInfo wifiInfo = manager.getConnectionInfo();
if (wifiInfo != null) {
String networkSsid = wifiInfo.getSSID();
if (ssid.equalsIgnoreCase(networkSsid)) {
return true;
}
}
}
}
return false;
}
}
|
Every trigger must be registered to module it belongs to as follows:
1 2 3 4 | @Override
protected void registerComponents() {
registerConditionChecker("STR-EQUALS", 1, new CC_StrEquals());
}
|
Even if trigger and condition checkers are classes with own lifecycle, extending different superclasses, some semantic associations can be sometimes defined between them.
When declaring a trigger and a condition checker with same ID, they are considered as related. This means that regardless of which component is choosen, engine may decide to use trigger or condition checker depending on situation.
Making a trigger and a condition checker as related requires in any case some constraints to observe:
Performers are components responsible of executing operations on demand, as requested by rules they are declared in. They may require optional parameters and can produce optional variables in output.
In order to create a new condition checker abstract class com.atooma.engine.Performer
must be extended, including implementation of following methods:
1 2 3 | protected void declareParameters()
protected void declareVariables()
protected Map<String, Object> invoke(String ruleId, Map<String, Object> parameters)
|
Parameters required by a performer must be declared within method:
1 | protected void declareParameters()
|
This method must include a sequence of calls to the inherited method:
1 | protected void declareParameter(String id, String module, String typeId, boolean required)
|
id
is the identifier of the required parameter.module
and typeId
identify value type, indicating respectively the module and the identifier of the referenced value type.required
indicates whether parameter is mandatory or can be null
.Below is reported an example:
1 2 3 4 | @Override
protected void declareParameters() {
declareParameter("TEXT", "CORE", "STRING", true);
}
|
Variables that can be used as output for a performer must be explicitly declared in the implementation of method:
1 | protected void declareVariables()
|
This method must include a sequence of calls to the inherited method:
1 | protected void declareVariable(String id, String module, String typeId)
|
id
is the identifier of the output variable.module
and typeId
identify value type, indicating respectively the module and the identifier of the referenced value type.Below is reported an example:
1 2 3 4 | @Override
protected void declareVariables() {
declareVariable("RESULT", "CORE", "BOOLEAN");
}
|
When a rule requires a performer to execute a set of actions, the following method is invoked:
1 | protected Map<String, Object> invoke(String ruleId, Map<String, Object> parameters)
|
ruleId
is the identifier of the rule that asked for condition verification.parameters
is a map with all the parameters that are input for the rule, according to names and types defined in method declareParameters()
.Method complete its execution returning a map of variables, to be compliant with declarations made in method declareVariables()
.
NOTIFICATION.TOAST
performer shows a toast message to the user. It requires a parameter TEXT
with Type CORE.STRING
, that represents the message to be displayed.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | class PE_Toast extends Performer {
@Override
protected void declareParameters() {
declareParameter("TEXT", "CORE", "STRING", true);
}
@Override
protected void declareVariables() {
// no variables
}
@Override
protected Map<String, Object> invoke(String ruleId, Map<String, Object> parameters) {
final String text = (String) parameters.get("TEXT");
Handler handler = new Handler(Looper.getMainLooper());
handler.post(new Runnable() {
public void run() {
Toast.makeText(getContext(), text, Toast.LENGTH_LONG).show();
}
});
return new HashMap<String, Object>();
}
}
|
Every trigger must be registered to module it belongs to as follows:
1 2 3 4 | @Override
protected void registerComponents() {
registerPerformer("TOAST", 1, new PE_Toast());
}
|