/*******************************************************************************
* Copyright 2011 The Regents of the University of California
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
******************************************************************************/
package org.ohmage.triggers.base;
import android.content.Context;
import android.database.Cursor;
import android.location.Location;
import android.location.LocationManager;
import android.text.format.DateUtils;
import org.json.JSONObject;
import org.ohmage.logprobe.Log;
import org.ohmage.logprobe.OhmageAnalytics;
import org.ohmage.logprobe.OhmageAnalytics.TriggerStatus;
import org.ohmage.triggers.notif.NotifDesc;
import org.ohmage.triggers.notif.Notifier;
import java.util.LinkedList;
/*
* The abstract class which must be extended by all the triggers
* implemented in the ..\types\ directory.Each trigger type such
* as time trigger must extend this class and register that concrete
* class with the trigger framework by adding and entry into the class
* TriggerTypeMap.
*
* The framework communicates with the trigger types using the concrete
* class registered in the trigger type map. This class also provides some
* APIs using which the trigger types can communicate with the framework.
*/
public abstract class TriggerBase {
private static final String TAG = "TriggerFramework";
/*
* Function to be called by the trigger types to notify the user
* when a trigger of that types goes off.
*/
public void notifyTrigger(Context context, int trigId) {
Log.v(TAG, "TriggerBase: notifyTrigger(" + trigId + ")");
TriggerDB db = new TriggerDB(context);
db.open();
String rtDesc = db.getRunTimeDescription(trigId);
TriggerRunTimeDesc desc = new TriggerRunTimeDesc();
desc.loadString(rtDesc);
//Save trigger time stamp in the run time description
desc.setTriggerTimeStamp(System.currentTimeMillis());
//Save trigger current loc in the run time description
LocationManager locMan = (LocationManager)
context.getSystemService(Context.LOCATION_SERVICE);
Location loc = locMan.getLastKnownLocation(LocationManager.GPS_PROVIDER);
desc.setTriggerLocation(loc);
//Save the run time desc in the database
db.updateRunTimeDescription(trigId, desc.toString());
OhmageAnalytics.trigger(context, TriggerStatus.TRIGGER, trigId);
//Call the notifier to display the notification
//Pass the notification description corresponding to this trigger
Notifier.notifyNewTrigger(context, trigId, db.getNotifDescription(trigId));
db.close();
}
/*
* Get the ids of all the triggers of this type for a specific campaign
*/
public LinkedList<Integer> getAllTriggerIds(Context context, String campaignUrn) {
Log.v(TAG, "TriggerBase: getAllTriggerIds()");
TriggerDB db = new TriggerDB(context);
db.open();
//Query the triggers of this type
Cursor c = db.getTriggers(campaignUrn, this.getTriggerType());
LinkedList<Integer> ids = new LinkedList<Integer>();
//Populate the list
if(c.moveToFirst()) {
do {
ids.add(c.getInt(
c.getColumnIndexOrThrow(TriggerDB.KEY_ID)));
} while(c.moveToNext());
}
c.close();
db.close();
return ids;
}
/*
* Get the list of ids of all the active triggers for a specific campaign. A trigger
* is active if it has at least one survey associated with it.
*
* Note: This function can be optimized by storing the action
* count separately in the db.
*/
public LinkedList<Integer> getAllActiveTriggerIds(Context context, String campaignUrn) {
Log.v(TAG, "TriggerBase: getAllActiveTriggerIds()");
TriggerDB db = new TriggerDB(context);
db.open();
//Get the triggers of this type
Cursor c = db.getTriggers(campaignUrn, this.getTriggerType());
LinkedList<Integer> ids = new LinkedList<Integer>();
//Iterate through the list of all triggers of this type
if(c.moveToFirst()) {
do {
String actDesc = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_ACTION_DESCRIPT));
TriggerActionDesc desc = new TriggerActionDesc();
if(!desc.loadString(actDesc)) {
continue;
}
//Add the trigger id to the list if there is at least
//one survey associated with this trigger
if(desc.getCount() > 0) {
ids.add(c.getInt(
c.getColumnIndexOrThrow(TriggerDB.KEY_ID)));
}
} while(c.moveToNext());
}
c.close();
db.close();
return ids;
}
/*
* Get the description for a specific trigger
*
* TODO: check if trigId corresponds to a trigger of this type
*/
public String getTrigger(Context context, int trigId) {
Log.v(TAG, "TriggerBase: getTrigger(" + trigId + ")");
TriggerDB db = new TriggerDB(context);
db.open();
String ret = null;
Cursor c = db.getTrigger(trigId);
if(c.moveToFirst()) {
ret = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_DESCRIPT));
}
c.close();
db.close();
return ret;
}
public String getCampaignUrn(Context context, int trigId) {
TriggerDB db = new TriggerDB(context);
db.open();
String campaignUrn = db.getCampaignUrn(trigId);
db.close();
return campaignUrn;
}
/*
* Get the latest time stamp (the time when the trigger went
* off the last time) of a specific trigger.
*
* Returns -1 if there is no time stamp
*
* TODO: check if trigId corresponds to a trigger of this type
*/
public long getTriggerLatestTimeStamp(Context context, int trigId) {
Log.v(TAG, "TriggerBase: getTriggerLatestTimeStamp(" + trigId + ")");
TriggerDB db = new TriggerDB(context);
db.open();
long ret = -1;
Cursor c = db.getTrigger(trigId);
if(c.moveToFirst()) {
String rtDesc = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_RUNTIME_DESCRIPT));
TriggerRunTimeDesc desc = new TriggerRunTimeDesc();
if(desc.loadString(rtDesc)) {
ret = desc.getTriggerTimeStamp();
}
}
c.close();
db.close();
return ret;
}
/*
* Delete a trigger from the db given its id.
* The trigger can be deleted only if its type matches
* this type
*/
public void deleteTrigger(Context context, int trigId) {
Log.v(TAG, "TriggerBase: deleteTrigger(" + trigId + ")");
TriggerDB db = new TriggerDB(context);
db.open();
String campaignUrn = db.getCampaignUrn(trigId);
Cursor c = db.getTrigger(trigId);
if(c.moveToFirst()) {
String trigType = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_TYPE));
if(trigType.equals(this.getTriggerType())) {
OhmageAnalytics.trigger(context, TriggerStatus.DELETE, trigId);
//Stop trigger first
stopTrigger(context, trigId, db.getTriggerDescription(trigId));
//Delete from database
db.deleteTrigger(trigId);
//Now refresh the notification display
Notifier.removeTriggerNotification(context, trigId, campaignUrn);
}
}
c.close();
db.close();
}
/*
* Save a new trigger description to the database.
*/
public void addNewTrigger(Context context, String campaignUrn, String trigDesc, String actDesc) {
Log.v(TAG, "TriggerBase: getTriggerLatestTimeStamp(" + trigDesc + ")");
TriggerDB db = new TriggerDB(context);
db.open();
//Save the trigger desc. Use default desc for notification, action
// and run time
int trigId = (int) db.addTrigger(campaignUrn, this.getTriggerType(), trigDesc,
actDesc,
NotifDesc.getDefaultDesc(context),
TriggerRunTimeDesc.getDefaultDesc());
// String actDesc = db.getActionDescription(trigId);
db.close();
OhmageAnalytics.trigger(context, TriggerStatus.ADD, trigId);
//If the action has a positive number of surveys,
//start the trigger.
TriggerActionDesc desc = new TriggerActionDesc();
if(desc.loadString(actDesc) && desc.getCount() > 0) {
startTrigger(context, trigId, trigDesc);
}
}
/*
* Update the description of an existing trigger
*/
public void updateTrigger(Context context, int trigId, String trigDesc, String actDesc) {
Log.v(TAG, "TriggerBase: getTriggerLatestTimeStamp(" + trigId
+ ", " + trigDesc + ")");
TriggerDB db = new TriggerDB(context);
db.open();
db.updateTriggerDescription(trigId, trigDesc);
// String actDesc = db.getActionDescription(trigId);
db.updateActionDescription(trigId, actDesc);
db.close();
OhmageAnalytics.trigger(context, TriggerStatus.UPDATE, trigId);
//If the action has a positive number of surveys,
//restart the trigger.
TriggerActionDesc desc = new TriggerActionDesc();
if(desc.loadString(actDesc) && desc.getCount() > 0) {
resetTrigger(context, trigId, trigDesc);
}
}
public void updateTrigger(Context context, int trigId, String trigDesc) {
Log.v(TAG, "TriggerBase: getTriggerLatestTimeStamp(" + trigId
+ ", " + trigDesc + ")");
TriggerDB db = new TriggerDB(context);
db.open();
db.updateTriggerDescription(trigId, trigDesc);
String actDesc = db.getActionDescription(trigId);
db.close();
OhmageAnalytics.trigger(context, TriggerStatus.UPDATE, trigId);
//If the action has a positive number of surveys,
//restart the trigger.
TriggerActionDesc desc = new TriggerActionDesc();
if(desc.loadString(actDesc) && desc.getCount() > 0) {
resetTrigger(context, trigId, trigDesc);
}
}
/*
* Utility function to check if the trigger has already gone
* off today. Useful for any time based triggers which are
* supposed to go off only once per day at most.
*/
public boolean hasTriggeredToday(Context context, int trigId) {
Log.v(TAG, "TriggerBase: hasTriggeredToday(" + trigId + ")");
long trigTS = getTriggerLatestTimeStamp(context, trigId);
if(trigTS != TriggerRunTimeDesc.INVALID_TIMESTAMP) {
return DateUtils.isToday(trigTS);
}
return false;
}
/* Abstract functions to be implemented by all the concrete trigger types */
/*
* Get the string which represents the type of this trigger.
* This string must be unique and no other trigger type must
* use it. The list of registered types can be found in the class
* TriggerTypeMap.
*/
public abstract String getTriggerType();
/*
* Get the resource id of the icon which represents this trigger
* type. This icon will be used by the main trigger list.
*/
public abstract int getIcon();
/*
* Get the display name for this trigger type. This name is used to
* create the 'trigger type selector' dialog which is displayed to
* the user when a new trigger is to be created or when the settings
* are to be modified.
*/
public abstract String getTriggerTypeDisplayName(Context context);
/*
* Get the title of a specific trigger description of this type.
* This title will be used in main trigger list when the list item
* corresponding to this description is displayed.
*/
public abstract String getDisplayTitle(Context context, String trigDesc);
/*
* Get the summary of a specific trigger description of this type.
* This summary will be used in main trigger list when the list item
* corresponding to this description is displayed.
*/
public abstract String getDisplaySummary(Context context, String trigDesc);
/*
* Get the preferences for this trigger in JSON format.
* Used while uploading a survey response
*/
public abstract JSONObject getPreferences(Context context);
/*
* Start a specific trigger
*/
public abstract void startTrigger(Context context, int trigId, String trigDesc);
/*
* Reset a specific trigger
*/
public abstract void resetTrigger(Context context, int trigId, String trigDesc);
/*
* Stop a specific trigger
*/
public abstract void stopTrigger(Context context, int trigId, String trigDesc);
/*
* Launch the activity to create a new trigger of this type. The activity
* can save the trigger description to the db using the API addNewTrigger()
* provided by this class.
*/
public abstract void launchTriggerCreateActivity(Context context, String campaingUrn, String[] mActions, String[] mPreselActions, boolean adminMode);
/*
* Launch the activity to edit an existing trigger of this type. The activity
* can save the edited trigger description to the db using the API updateTrigger()
* provided by this class.
*/
public abstract void launchTriggerEditActivity(Context context, int trigId,
String trigDesc, String actDesc, String[] mActions, boolean adminMode);
/*
* Check if this trigger type has its own settings.
*/
public abstract boolean hasSettings();
/*
* Launch the activity to edit the settings if this trigger type
* has its own settings.
*/
public abstract void launchSettingsEditActivity(Context context, boolean adminMode);
/*
* Reset all settings to default
*/
public abstract void resetSettings(Context context);
}