/*******************************************************************************
* 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.ui;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.View.OnClickListener;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import android.widget.Toast;
import org.ohmage.R;
import org.ohmage.activity.AdminPincodeActivity;
import org.ohmage.logprobe.Analytics;
import org.ohmage.logprobe.Log;
import org.ohmage.logprobe.LogProbe.Status;
import org.ohmage.triggers.base.TriggerActionDesc;
import org.ohmage.triggers.base.TriggerBase;
import org.ohmage.triggers.base.TriggerDB;
import org.ohmage.triggers.base.TriggerTypeMap;
import org.ohmage.triggers.config.TrigUserConfig;
import org.ohmage.triggers.notif.NotifDesc;
import org.ohmage.triggers.notif.NotifEditActivity;
import org.ohmage.triggers.notif.NotifSettingsActivity;
import org.ohmage.triggers.notif.Notifier;
import org.ohmage.triggers.utils.TrigPrefManager;
public class TriggerListActivity extends ListActivity
implements OnClickListener {
private static final String TAG = "TriggerListActivity";
private static final String PREF_FILE_NAME =
TriggerListActivity.class.getName();
public static final String KEY_CAMPAIGN_URN =
TriggerListActivity.class.getName() + ".campain_urn";
public static final String KEY_ACTIONS =
TriggerListActivity.class.getName() + ".actions";
// pass survey names through here to preselect them in the trigger creation page
public static final String KEY_PRESELECTED_ACTIONS =
TriggerListActivity.class.getName() + ".preselected_actions";
public static final String KEY_ADMIN_MODE =
TriggerListActivity.class.getName() + ".admin_mode";
private static final String KEY_SAVE_DIALOG_TRIG_ID =
TriggerListActivity.class.getName() + ".dialog_trig_id";
private static final String KEY_SAVE_SEL_ACTIONS =
TriggerListActivity.class.getName() + ".selected_actions";
private static final String KEY_SAVE_DIALOG_TEXT =
TriggerListActivity.class.getName() + ".dialog_text";
private static final int MENU_ID_DELETE_TRIGGER = Menu.FIRST;
private static final int MENU_ID_NOTIF_SETTINGS = Menu.FIRST + 1;
private static final int MENU_ID_SETTINGS = Menu.FIRST + 2;
private static final int MENU_ID_ADMIN_LOGIN = Menu.FIRST + 3;
private static final int MENU_ID_ADMIN_LOGOFF = Menu.FIRST + 4;
private static final int MENU_ID_RINGTONE_SETTINGS = Menu.FIRST + 5;
private static final int DIALOG_ID_ADD_NEW = 0;
private static final int DIALOG_ID_PREFERENCES = 1;
private static final int DIALOG_ID_ACTION_SEL = 2;
private static final int DIALOG_ID_DELETE = 3;
private static final int REQ_EDIT_NOTIF = 0;
private static final int ADMIN_REQUESTED = 1;
private Cursor mCursor;
private TriggerDB mDb;
private TriggerTypeMap mTrigMap;
private String[] mActions;
private String[] mPreselectedActions; // actions which will be preselected in a new trigger window
private String mCampaignUrn;
private int mDialogTrigId = -1;
private String mDialogText = null;
private boolean[] mActSelected = null;
/**
* Instead of having a shared preference admin mode, we want admin mode to end once the user leaves the activity
*/
private boolean mAdminMode;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mAdminMode = getResources().getBoolean(R.bool.reminder_admin_mode);
setContentView(R.layout.trigger_main_list);
mTrigMap = new TriggerTypeMap();
TextView tv = (TextView) findViewById(R.id.add_new_label);
tv.setText(R.string.triggers_title);
ImageButton bAdd = (ImageButton) findViewById(R.id.button_add_new);
bAdd.setOnClickListener(this);
getListView().setHeaderDividersEnabled(true);
Intent i = getIntent();
if(i.hasExtra(KEY_ACTIONS)) {
mActions = i.getStringArrayExtra(KEY_ACTIONS);
} else {
Log.e(TAG, "TriggerListActivity: Invoked with out passing surveys");
finish();
return;
}
// gather any preselected actions that were specified
// we'll feed these to the trigger create activity later to preselect certain actions
if (i.hasExtra(KEY_PRESELECTED_ACTIONS)) {
mPreselectedActions = i.getStringArrayExtra(KEY_PRESELECTED_ACTIONS);
}
if(i.hasExtra(KEY_CAMPAIGN_URN)) {
mCampaignUrn = i.getStringExtra(KEY_CAMPAIGN_URN);
} else {
Log.e(TAG, "TriggerListActivity: Invoked with out passing campaign urn");
finish();
return;
}
mDb = new TriggerDB(this);
mDb.open();
populateTriggerList();
registerForContextMenu(getListView());
updateGUIWithAdminStatus(isAdminLoggedIn());
//Display message and exit if there are no supported
//trigger types
if(mTrigMap.getAllTriggers().size() == 0) {
Toast.makeText(this, R.string.trigger_nothing_supported,
Toast.LENGTH_SHORT).show();
finish();
}
TrigPrefManager.registerPreferenceFile(this, mCampaignUrn, PREF_FILE_NAME);
TrigPrefManager.registerPreferenceFile(this, "GLOBAL", PREF_FILE_NAME);
}
@Override
protected void onResume() {
super.onResume();
Analytics.activity(this, Status.ON);
}
@Override
protected void onPause() {
super.onPause();
Analytics.activity(this, Status.OFF);
}
@Override
public void onDestroy() {
super.onDestroy();
mCursor.close();
mDb.close();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_SAVE_DIALOG_TRIG_ID, mDialogTrigId);
outState.putBooleanArray(KEY_SAVE_SEL_ACTIONS, mActSelected);
outState.putString(KEY_SAVE_DIALOG_TEXT, mDialogText);
outState.putBoolean(KEY_ADMIN_MODE, mAdminMode);
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
mDialogTrigId = state.getInt(KEY_SAVE_DIALOG_TRIG_ID, -1);
mActSelected = state.getBooleanArray(KEY_SAVE_SEL_ACTIONS);
mDialogText = state.getString(KEY_SAVE_DIALOG_TEXT);
mAdminMode = state.getBoolean(KEY_ADMIN_MODE);
}
private void updateGUIWithAdminStatus(boolean status) {
ImageButton bAdd = (ImageButton) findViewById(R.id.button_add_new);
bAdd.setEnabled(isAdminLoggedIn() || TrigUserConfig.addTrigers);
}
private String getDisplayTitle(String trigType, String trigDesc) {
TriggerBase trig = mTrigMap.getTrigger(trigType);
if(trig == null) {
return null;
}
return trig.getDisplayTitle(this, trigDesc);
}
private int getTrigTypeIcon(String trigType) {
TriggerBase trig = mTrigMap.getTrigger(trigType);
if(trig == null) {
return R.drawable.icon;
}
return trig.getIcon();
}
private String getDisplaySummary(String trigType, String trigDesc) {
TriggerBase trig = mTrigMap.getTrigger(trigType);
if(trig == null) {
return null;
}
return trig.getDisplaySummary(this, trigDesc);
}
private void toggleTrigger(int trigId, boolean enable) {
Cursor c = mDb.getTrigger(trigId);
if(c.moveToFirst()) {
String trigType = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_TYPE));
String trigDesc = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_DESCRIPT));
TriggerBase trig = mTrigMap.getTrigger(trigType);
if(trig != null) {
if(enable) {
trig.startTrigger(this, trigId, trigDesc);
}
else {
trig.stopTrigger(this, trigId, trigDesc);
}
}
}
c.close();
}
private void editTrigger(String trigType, int trigId, String trigDesc, String actDesc) {
TriggerBase trig = mTrigMap.getTrigger(trigType);
if(trig != null) {
trig.launchTriggerEditActivity(this,trigId, trigDesc, actDesc, mActions, isAdminLoggedIn());
}
}
private void deleteTrigger(int trigId) {
TriggerBase trig = mTrigMap.getTrigger(
mDb.getTriggerType(trigId));
trig.deleteTrigger(this, trigId);
}
private void populateTriggerList() {
//The viewbinder class to define each list item
class CategListViewBinder
implements SimpleCursorAdapter.ViewBinder {
@Override
public boolean setViewValue(View view, Cursor c, int colIndex) {
String trigType = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_TYPE));
String trigDesc = c.getString(
c.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_DESCRIPT));
switch(view.getId()) {
case R.id.text1:
String title = getDisplayTitle(trigType, trigDesc);
((TextView) view).setText(title == null ? "" : title);
return true;
case R.id.text2:
String summary = getDisplaySummary(trigType, trigDesc);
((TextView) view).setText(summary == null ? "" : summary);
return true;
case R.id.button_actions_edit: //edit surveys button
int trigId = c.getInt(
c.getColumnIndexOrThrow(TriggerDB.KEY_ID));
String actDesc = c.getString(
c.getColumnIndexOrThrow(
TriggerDB.KEY_TRIG_ACTION_DESCRIPT));
Button bAct = (Button) view;
bAct.setFocusable(false);
TriggerActionDesc desc = new TriggerActionDesc();
desc.loadString(actDesc);
bAct.setText("(" + desc.getCount() + ")");
bAct.setTag(new Integer(trigId));
view.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
mDialogTrigId = (Integer) v.getTag();
mActSelected = null;
removeDialog(DIALOG_ID_ACTION_SEL);
showDialog(DIALOG_ID_ACTION_SEL);
}
});
return true;
case R.id.icon_trigger_type:
ImageView iv = (ImageView) view;
iv.setImageResource(getTrigTypeIcon(trigType));
return true;
}
return false;
}
}
mCursor = mDb.getAllTriggers(mCampaignUrn);
mCursor.moveToFirst();
startManagingCursor(mCursor);
String[] from = new String[] {TriggerDB.KEY_ID,
TriggerDB.KEY_ID, TriggerDB.KEY_ID, TriggerDB.KEY_ID};
int[] to = new int[] {R.id.text1, R.id.text2,
R.id.button_actions_edit, R.id.icon_trigger_type};
SimpleCursorAdapter triggers =
new SimpleCursorAdapter(this, R.layout.trigger_main_list_row,
mCursor, from, to);
triggers.setViewBinder(new CategListViewBinder());
setListAdapter(triggers);
}
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
super.onListItemClick(l, v, position, id);
if(!mCursor.moveToPosition(position)) {
//TODO this should not happen. log
return;
}
String trigDesc = mCursor.getString(
mCursor.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_DESCRIPT));
int trigId = mCursor.getInt(
mCursor.getColumnIndexOrThrow(TriggerDB.KEY_ID));
String trigType = mCursor.getString(
mCursor.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_TYPE));
String actDesc = mCursor.getString(mCursor.getColumnIndexOrThrow(TriggerDB.KEY_TRIG_ACTION_DESCRIPT));
editTrigger(trigType, trigId, trigDesc, actDesc);
}
private boolean isAdminLoggedIn() {
return mAdminMode;
}
private void setAdminMode(boolean enable) {
if(mAdminMode != enable) {
mAdminMode = enable;
updateGUIWithAdminStatus(enable);
}
}
private Dialog createDeleteConfirmDialog(int trigId) {
return new AlertDialog.Builder(this)
.setNegativeButton(R.string.cancel, null)
.setTitle(R.string.trigger_delete_title)
.setMessage(R.string.trigger_delete_text)
.setPositiveButton(R.string.delete,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
deleteTrigger(mDialogTrigId);
mCursor.requery();
}
})
.create();
}
private Dialog createEditActionDialog(int trigId) {
if(mActSelected == null) {
String actDesc = mDb.getActionDescription(trigId);
TriggerActionDesc desc = new TriggerActionDesc();
desc.loadString(actDesc);
mActSelected = new boolean[mActions.length];
for(int i = 0; i < mActSelected.length; i++) {
mActSelected[i] = desc.hasSurvey(mActions[i]);
}
}
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle(R.string.trigger_select_actions)
.setNegativeButton(R.string.cancel, null)
.setView(new ActionSelectorView(getBaseContext(), mActions, mActSelected));
/*
AlertDialog.Builder builder =
new AlertDialog.Builder(this)
.setTitle(R.string.trigger_select_actions)
.setNegativeButton(R.string.cancel, null)
.setMultiChoiceItems(mActions, mActSelected,
new DialogInterface.OnMultiChoiceClickListener() {
@Override
public void onClick(DialogInterface dialog, int which,
boolean isChecked) {
mActSelected[which] = isChecked;
}
});
*/
if(isAdminLoggedIn() || TrigUserConfig.editTriggerActions) {
builder.setPositiveButton(R.string.done,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
TriggerActionDesc desc = new TriggerActionDesc();
for(int i = 0; i < mActSelected.length; i++) {
if(mActSelected[i]) {
desc.addSurvey(mActions[i]);
}
}
dialog.dismiss();
handleActionSelection(mDialogTrigId, desc);
}
});
}
return builder.create();
}
private Dialog createAddNewSelDialog() {
TriggerTypeSelector typeSel = new TriggerTypeSelector(this);
typeSel.setTitle(R.string.trigger_create);
typeSel.setOnClickListener(new TriggerTypeSelector.OnClickListener() {
@Override
public void onClick(String trigType) {
mTrigMap.getTrigger(trigType)
.launchTriggerCreateActivity(TriggerListActivity.this, mCampaignUrn, mActions, mPreselectedActions,
isAdminLoggedIn());
}
});
return typeSel;
}
private Dialog createEditPrefSelDialog() {
TriggerTypeSelector typeSel = new TriggerTypeSelector(this);
typeSel.setTitle(R.string.trigger_preferences);
typeSel.setOnClickListener(new TriggerTypeSelector.OnClickListener() {
@Override
public void onClick(String trigType) {
mTrigMap.getTrigger(trigType)
.launchSettingsEditActivity(TriggerListActivity.this,
isAdminLoggedIn());
}
});
typeSel.setOnListItemChangeListener(
new TriggerTypeSelector.OnListItemChangeListener() {
@Override
public boolean onAddItem(String trigType) {
return mTrigMap.getTrigger(trigType).hasSettings();
}
});
return typeSel;
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case DIALOG_ID_ADD_NEW:
return createAddNewSelDialog();
case DIALOG_ID_PREFERENCES:
return createEditPrefSelDialog();
case DIALOG_ID_ACTION_SEL:
return createEditActionDialog(mDialogTrigId);
case DIALOG_ID_DELETE:
return createDeleteConfirmDialog(mDialogTrigId);
}
return null;
}
@Override
public boolean onPrepareOptionsMenu(Menu menu) {
boolean ret = super.onPrepareOptionsMenu(menu);
menu.removeItem(MENU_ID_ADMIN_LOGOFF);
menu.removeItem(MENU_ID_ADMIN_LOGIN);
menu.removeItem(MENU_ID_NOTIF_SETTINGS);
menu.removeItem(MENU_ID_RINGTONE_SETTINGS);
menu.removeItem(MENU_ID_SETTINGS);
boolean adminMode = isAdminLoggedIn();
if(!getResources().getBoolean(R.bool.reminder_admin_mode)) {
if(!adminMode) {
menu.add(0, MENU_ID_ADMIN_LOGIN, 0, R.string.trigger_menu_admin_turn_on)
.setIcon(R.drawable.ic_menu_login);
}
else {
menu.add(0, MENU_ID_ADMIN_LOGOFF, 0, R.string.trigger_menu_admin_turn_off)
.setIcon(R.drawable.ic_menu_login);
}
}
//Add 'preferences' menu item only if there is at least
//one trigger type registered which has settings
for(TriggerBase trig : mTrigMap.getAllTriggers()) {
if(trig.hasSettings()) {
menu.add(0, MENU_ID_SETTINGS, 0, R.string.trigger_menu_preferences)
.setIcon(android.R.drawable.ic_menu_preferences)
.setEnabled(adminMode || TrigUserConfig.editTriggerSettings);
break;
}
}
menu.add(0, MENU_ID_NOTIF_SETTINGS, 0, R.string.trigger_menu_notifications)
.setIcon(R.drawable.ic_menu_notification)
.setEnabled(adminMode || TrigUserConfig.editNotificationSettings);
menu.add(0, MENU_ID_RINGTONE_SETTINGS, 0, R.string.trigger_menu_ringtone)
.setIcon(R.drawable.ic_menu_ringtone)
.setEnabled(true);
return ret;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case MENU_ID_NOTIF_SETTINGS:
Intent i = new Intent(this, NotifEditActivity.class);
i.putExtra(NotifEditActivity.KEY_NOTIF_CONFIG,
NotifDesc.getGlobalDesc(this));
startActivityForResult(i, REQ_EDIT_NOTIF);
return true;
case MENU_ID_RINGTONE_SETTINGS:
startActivity(new Intent(TriggerListActivity.this, NotifSettingsActivity.class));
return true;
case MENU_ID_SETTINGS:
showDialog(DIALOG_ID_PREFERENCES);
return true;
case MENU_ID_ADMIN_LOGIN:
mDialogText = null;
startActivityForResult(new Intent(this, AdminPincodeActivity.class), ADMIN_REQUESTED);
return true;
case MENU_ID_ADMIN_LOGOFF:
setAdminMode(false);
Toast.makeText(this, R.string.trigger_admin_logged_off, Toast.LENGTH_SHORT)
.show();
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
switch(requestCode) {
case ADMIN_REQUESTED:
setAdminMode(resultCode == RESULT_OK);
case REQ_EDIT_NOTIF:
if(data != null) {
String desc = data.getStringExtra(NotifEditActivity.KEY_NOTIF_CONFIG);
if(desc != null) {
NotifDesc.setGlobalDesc(this, desc);
mDb.updateAllNotificationDescriptions(desc);
//Update any notification if required
Notifier.refreshNotification(this, mCampaignUrn, true);
}
}
break;
default:
super.onActivityResult(requestCode, resultCode, data);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v, ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
menu.add(0, MENU_ID_DELETE_TRIGGER, 0, R.string.trigger_menu_delete)
.setEnabled(isAdminLoggedIn() || TrigUserConfig.removeTrigers);
}
@Override
public boolean onContextItemSelected(MenuItem item) {
int pos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
mCursor.moveToPosition(pos);
int trigId = mCursor.getInt(
mCursor.getColumnIndexOrThrow(TriggerDB.KEY_ID));
switch(item.getItemId()) {
case MENU_ID_DELETE_TRIGGER:
mDialogTrigId = trigId;
showDialog(DIALOG_ID_DELETE);
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
public void handleActionSelection(int trigId, TriggerActionDesc desc) {
String prevActDesc = mDb.getActionDescription(trigId);
TriggerActionDesc prevDesc = new TriggerActionDesc();
prevDesc.loadString(prevActDesc);
mDb.updateActionDescription(trigId, desc.toString());
mCursor.requery();
Notifier.refreshNotification(this, mCampaignUrn, true);
if(desc.getCount() == 0 && prevDesc.getCount() !=0) {
toggleTrigger(trigId, false);
}
if(desc.getCount() != 0 && prevDesc.getCount() == 0) {
toggleTrigger(trigId, true);
}
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.button_add_new) {
showDialog(DIALOG_ID_ADD_NEW);
}
}
}