/*******************************************************************************
* 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.notif;
import android.content.Context;
import android.content.SharedPreferences;
import android.database.Cursor;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.ohmage.logprobe.Log;
import org.ohmage.triggers.base.TriggerActionDesc;
import org.ohmage.triggers.base.TriggerBase;
import org.ohmage.triggers.base.TriggerDB;
import org.ohmage.triggers.base.TriggerRunTimeDesc;
import org.ohmage.triggers.base.TriggerTypeMap;
import org.ohmage.triggers.utils.TrigPrefManager;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.Set;
/*
* An interface to the survey list management. This class
* provides functions to keep track of the survey taken
* time stamps, to get the list of active surveys and to
* get various trigger related JSON strings used while
* uploading survey responses.
*
* This class isolates the logic related to surveys from
* the Notifier class thus making the implementation of the
* Notifier generic and independent of the surveys.
*/
public class NotifSurveyAdaptor {
private static final String TAG = "NotifSurveyAdaptor";
/* Keys used in preparation of the JSON strings */
private static final String KEY_ACTIVE_TRIGGERS = "active_triggers";
private static final String KEY_TRIGGER_DESC = "trigger_description";
private static final String KEY_NOTIF_DESC = "notification_description";
private static final String KEY_RUNTIME_DESC = "runtime_description";
private static final String KEY_TRIGGER_TYPE = "trigger_type";
private static final String KEY_TRIGER_PREF = "trigger_preferences";
private static final String KEY_SURVEY_LIST = "survey_list";
private static final String KEY_UNTAKEN_SURVEYS = "surveys_not_taken";
private static final String KEY_CAMPAIGN_URN = "campaign_urn";
/*
* Helper function to prepare the list of active surveys corresponding
* to trigger.
*
* A trigger is active if it has not expired (the notification duration
* has not been reached) after going off the last time.
*
* All surveys associated with an active active trigger are active
* - EXCEPT those which have already been taken by the user within
* the suppression window.
*/
private static HashSet<String> getActiveSurveys(Context context, Cursor trig) {
HashSet<String> actSurveys = new HashSet<String>();
String runTime = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_RUNTIME_DESCRIPT));
String notif = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_NOTIF_DESCRIPT));
String actions = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_ACTION_DESCRIPT));
String campaignUrn = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_CAMPAIGN_URN));
Log.v(TAG, "NotifSurveyAdaptor: Calculating active surveys for trigger");
TriggerRunTimeDesc rtDesc = new TriggerRunTimeDesc();
NotifDesc notifDesc = new NotifDesc();
TriggerActionDesc actDesc = new TriggerActionDesc();
if(!rtDesc.loadString(runTime) ||
!notifDesc.loadString(notif) ||
!actDesc.loadString(actions)) {
Log.e(TAG, "NotifSurveyAdaptor: Descritptor(s) failed to parse");
return actSurveys;
}
if(!rtDesc.hasTriggerTimeStamp()) {
Log.e(TAG, "NotifSurveyAdaptor: Trigger time stamp is invalid");
return actSurveys;
}
long now = System.currentTimeMillis();
long trigTS = rtDesc.getTriggerTimeStamp();
if(trigTS > now) {
Log.e(TAG, "NotifSurveyAdaptor: Trigger time stamp is in the future!");
return actSurveys;
}
//How long it has been since the trigger went off
long elapsedMS = now - trigTS;
long durationMS = notifDesc.getDuration() * 60000;
long suppressMS = notifDesc.getSuppression() * 60000;
if(elapsedMS < durationMS) {
//The trigger has not expired, check each survey
String[] surveys = actDesc.getSurveys();
for(int i = 0; i < surveys.length; i++) {
//Has the survey been taken in within the
//suppression window?
if(IsSurveyTaken(context, campaignUrn, surveys[i],
trigTS - suppressMS)) {
continue;
}
//Add the active survey to the set
actSurveys.add(surveys[i]);
}
}
return actSurveys;
}
/*
* Utility function to check if a survey has been taken
* by the user since a given time. This function checks
* the time stamp of the survey stored in shared preferences.
*/
private static boolean IsSurveyTaken(Context context,
String campaignUrn,
String survey,
long since) {
SharedPreferences pref = context.getSharedPreferences(
NotifSurveyAdaptor.class.getName() + "_" + campaignUrn,
Context.MODE_PRIVATE);
if(!pref.contains(survey)) {
return false;
}
if(pref.getLong(survey, 0) <= since) {
return false;
}
return true;
}
/*
* Get the list of all surveys active at the moment. This
* function creates a set of all active surveys from all
* active triggers.
*
* This function is used by the Notifier to prepare the
* notification item.
*/
public static Set<String> getAllActiveSurveys(Context context, String campaignUrn) {
HashSet<String> actSurveys = new HashSet<String>();
TriggerDB db = new TriggerDB(context);
db.open();
Cursor c = db.getAllTriggers(campaignUrn);
if(c.moveToFirst()) {
do {
actSurveys.addAll(getActiveSurveys(context, c));
} while(c.moveToNext());
}
c.close();
db.close();
return actSurveys;
}
/*
* Get all the active surveys corresponding to a specific trigger.
*/
public static Set<String> getActiveSurveysForTrigger(Context context,
int trigId) {
HashSet<String> actSurveys = new HashSet<String>();
TriggerDB db = new TriggerDB(context);
db.open();
Cursor c = db.getTrigger(trigId);
if(c.moveToFirst()) {
actSurveys.addAll(getActiveSurveys(context, c));
}
c.close();
db.close();
return actSurveys;
}
/*
* Saves the current time stamp against the given survey name.
* This must be called whenever a survey is taken by the user.
*/
public static void recordSurveyTaken(Context context, String campaignUrn, String survey) {
SharedPreferences pref = context.getSharedPreferences(
NotifSurveyAdaptor.class.getName() + "_" + campaignUrn,
Context.MODE_PRIVATE);
SharedPreferences.Editor editor = pref.edit();
editor.putLong(survey, System.currentTimeMillis());
editor.commit();
TrigPrefManager.registerPreferenceFile(context, campaignUrn, NotifSurveyAdaptor.class.getName());
}
/*
* Utility function to add the JSON description of a active
* trigger to JSON array.
*/
private static void addTriggerInfoToArray(Context context,
Cursor trig, JSONArray jArray) {
String rtDesc = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_RUNTIME_DESCRIPT));
TriggerRunTimeDesc desc = new TriggerRunTimeDesc();
desc.loadString(rtDesc);
String notifDesc = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_NOTIF_DESCRIPT));
String trigDesc = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_DESCRIPT));
String trigType = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_TYPE));
String campaignUrn = trig.getString(
trig.getColumnIndexOrThrow(TriggerDB.KEY_CAMPAIGN_URN));
JSONObject jPref = new JSONObject();
TriggerBase trigBase = new TriggerTypeMap().getTrigger(trigType);
if(trigBase != null) {
jPref = trigBase.getPreferences(context);
}
JSONObject jTrigInfo = new JSONObject();
try {
jTrigInfo.put(KEY_TRIGGER_TYPE, trigType);
jTrigInfo.put(KEY_TRIGGER_DESC, new JSONObject(trigDesc));
jTrigInfo.put(KEY_NOTIF_DESC, new JSONObject(notifDesc));
jTrigInfo.put(KEY_RUNTIME_DESC, new JSONObject(desc.toHumanReadableString()));
jTrigInfo.put(KEY_TRIGER_PREF, jPref);
jTrigInfo.put(KEY_CAMPAIGN_URN, campaignUrn);
} catch (JSONException e) {
return;
}
jArray.put(jTrigInfo);
}
/*
* Get the JSON array containing the details of all the triggers
* which have activated a given survey at the moment.
*/
public static JSONArray getActiveTriggerInfo(Context context,
String campaignUrn,
String survey) {
JSONObject jInfo = new JSONObject();
JSONArray jTrigs = new JSONArray();
TriggerDB db = new TriggerDB(context);
db.open();
Cursor c = db.getAllTriggers(campaignUrn);
if(c.moveToFirst()) {
do {
if(getActiveSurveys(context, c).contains(survey)) {
addTriggerInfoToArray(context, c, jTrigs);
}
} while(c.moveToNext());
}
c.close();
db.close();
try {
jInfo.put(KEY_ACTIVE_TRIGGERS, jTrigs);
} catch (JSONException e) {
return null;
}
return jTrigs;
}
/*
* To be called when a trigger expires. This function logs using
* LogProbe, the list of all surveys not taken by the user
* but were activated by the given trigger.
*/
public static void handleExpiredTrigger(Context context, int trigId) {
TriggerDB db = new TriggerDB(context);
db.open();
String sActDesc = db.getActionDescription(trigId);
String sTrigDesc = db.getTriggerDescription(trigId);
String sTrigType = db.getTriggerType(trigId);
String sRTDesc = db.getRunTimeDescription(trigId);
String sCampaignUrn = db.getCampaignUrn(trigId);
db.close();
if(sActDesc == null ||
sTrigDesc == null ||
sTrigType == null ||
sCampaignUrn == null ||
sRTDesc == null) {
return;
}
TriggerActionDesc actDesc = new TriggerActionDesc();
if(!actDesc.loadString(sActDesc)) {
return;
}
TriggerRunTimeDesc rtDesc = new TriggerRunTimeDesc();
if(!rtDesc.loadString(sRTDesc)) {
return;
}
LinkedList<String> untakenList = new LinkedList<String>();
for(String survey: actDesc.getSurveys()) {
if(!IsSurveyTaken(context, sCampaignUrn, survey,
rtDesc.getTriggerTimeStamp())) {
untakenList.add(survey);
}
}
if(untakenList.size() == 0) {
return;
}
JSONArray jSurveyList = new JSONArray();
for(String survey : actDesc.getSurveys()) {
jSurveyList.put(survey);
}
JSONArray jUntakenSurveys = new JSONArray();
for(String unTakenSurvey : untakenList) {
jUntakenSurveys.put(unTakenSurvey);
}
JSONObject jExpired = new JSONObject();
try {
jExpired.put(KEY_TRIGGER_TYPE, sTrigType);
jExpired.put(KEY_TRIGGER_DESC, new JSONObject(sTrigDesc));
jExpired.put(KEY_SURVEY_LIST, jSurveyList);
jExpired.put(KEY_UNTAKEN_SURVEYS, jUntakenSurveys);
jExpired.put(KEY_CAMPAIGN_URN, sCampaignUrn);
} catch (JSONException e) {
return;
}
//Log the info
Log.v(TAG, "Expired trigger has surveys not taken: " + jExpired.toString());
}
}