/*******************************************************************************
* 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.types.location;
import android.app.AlertDialog;
import android.app.Dialog;
import android.app.ListActivity;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
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.view.inputmethod.InputMethodManager;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView;
import org.ohmage.R;
import org.ohmage.db.DbHelper;
import org.ohmage.db.Models.Campaign;
import org.ohmage.logprobe.Analytics;
import org.ohmage.logprobe.LogProbe.Status;
import org.ohmage.triggers.config.TrigUserConfig;
import org.ohmage.triggers.utils.TrigTextInput;
import java.util.HashSet;
import java.util.LinkedList;
/*
* Location triggers settings activity.
* Displays the list of categories (places). The number of locations
* under each category as well the number of surveys associated with
* each place are also displayed. Provides options to manage the
* categories (add, delete, rename and modify surveys)
*/
public class LocTrigSettingsActivity extends ListActivity
implements OnClickListener,
TextWatcher {
private static final String TAG = "LocTrigSettingsActivity";
/* Menu ids */
private static final int MENU_DELETE_CATEG = Menu.FIRST;
private static final int MENU_RENAME_CATEG = Menu.FIRST + 1;
private static final int DIALOG_DELETE = 0;
private static final int DIALOG_RENAME = 1;
private static final String KEY_SAVE_DIALOG_CATEG = "dialog_category";
private static final String KEY_SAVE_DIALOG_TEXT = "dialog_text";
public static final String KEY_ADMIN_MODE = "admin_mode";
//Db instance
private LocTrigDB mDb;
//The list cursor
private Cursor mCursor;
private HashSet<String> mCategNames;
private final LocationTrigger mLocTrigger = new LocationTrigger();
private int mDialogCategId = -1;
private String mDialogText = null;
private boolean mAdminMode = false;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.trigger_loc_settings);
Button bAdd = (Button) findViewById(R.id.trigger_button_add_categ);
bAdd.setOnClickListener(this);
bAdd.setEnabled(false);
EditText et = (EditText) findViewById(R.id.trigger_text_add_categ);
et.addTextChangedListener(this);
mAdminMode = getIntent().getBooleanExtra(KEY_ADMIN_MODE, false);
if(!mAdminMode && !TrigUserConfig.editLocationTriggerPlace) {
et.setEnabled(false);
et.setClickable(false);
et.setFocusable(false);
}
mDb = new LocTrigDB(this);
mDb.open();
mCategNames = new HashSet<String>();
populateCategoryNames();
initializeList();
registerForContextMenu(getListView());
}
@Override
protected void onResume() {
super.onResume();
Analytics.activity(this, Status.ON);
}
@Override
protected void onPause() {
super.onPause();
Analytics.activity(this, Status.OFF);
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putInt(KEY_SAVE_DIALOG_CATEG, mDialogCategId);
if(mDialogText != null && mDialogText.length() != 0) {
outState.putString(KEY_SAVE_DIALOG_TEXT, mDialogText);
}
}
@Override
protected void onRestoreInstanceState(Bundle state) {
super.onRestoreInstanceState(state);
mDialogCategId = state.getInt(KEY_SAVE_DIALOG_CATEG, -1);
mDialogText = state.getString(KEY_SAVE_DIALOG_TEXT);
}
@Override
public void onDestroy() {
mCategNames.clear();
mCursor.close();
mDb.close();
System.gc();
super.onDestroy();
}
private void populateCategoryNames() {
mCategNames.clear();
Cursor c = mDb.getAllCategories();
if(c.moveToFirst()) {
do {
String name = c.getString(
c.getColumnIndexOrThrow(LocTrigDB.KEY_NAME));
mCategNames.add(name.toLowerCase());
} while(c.moveToNext());
}
c.close();
}
private boolean checkIfCategNameValid(String name) {
if(name == null) {
return false;
}
String text = name.trim();
if(text.length() == 0 || mCategNames.contains(text.toLowerCase())) {
return false;
}
return true;
}
private void updateTriggerDescriptions(String oldName, String newName) {
LinkedList<Integer> trigIds = new LinkedList<Integer>();
DbHelper dbHelper = new DbHelper(this);
for (Campaign c : dbHelper.getReadyCampaigns()) {
trigIds.addAll(mLocTrigger.getAllActiveTriggerIds(this, c.mUrn));
}
for(int trigId : trigIds) {
LocTrigDesc desc = new LocTrigDesc();
desc.loadString(mLocTrigger.getTrigger(this, trigId));
if(desc.getLocation().equals(oldName)) {
desc.setLocation(newName);
mLocTrigger.updateTrigger(this, trigId, desc.toString());
}
}
trigIds.clear();
}
private void removeTriggers(String categName) {
LinkedList<Integer> trigIds = new LinkedList<Integer>();
DbHelper dbHelper = new DbHelper(this);
for (Campaign c : dbHelper.getReadyCampaigns()) {
trigIds.addAll(mLocTrigger.getAllActiveTriggerIds(this, c.mUrn));
}
for(int trigId : trigIds) {
LocTrigDesc desc = new LocTrigDesc();
desc.loadString(mLocTrigger.getTrigger(this, trigId));
if(desc.getLocation().equals(categName)) {
mLocTrigger.deleteTrigger(this, trigId);
}
}
trigIds.clear();
}
/* Populate the categories listview */
private void initializeList() {
//The viewbinder class to define each list item
class CategListViewBinder
implements SimpleCursorAdapter.ViewBinder {
@Override
public boolean setViewValue(View view, Cursor c, int colIndex) {
switch(view.getId()) {
case R.id.text2: //locations count
TextView tv = (TextView) view;
Cursor cLocs = mDb.getLocations(c.getInt(colIndex));
int nLocs = cLocs.getCount();
cLocs.close();
String locStr = (nLocs == 1) ? " " +
getString(R.string.location_on_map) : " " +
getString(R.string.locations_on_map);
tv.setText("" + nLocs + locStr);
return true;
}
return false;
}
}
mCursor = mDb.getAllCategories();
mCursor.moveToFirst();
startManagingCursor(mCursor);
String[] from = new String[] {LocTrigDB.KEY_NAME, LocTrigDB.KEY_ID};
int[] to = new int[] {R.id.text1, R.id.text2};
SimpleCursorAdapter categories =
new SimpleCursorAdapter(this, R.layout.trigger_loc_settings_row,
mCursor, from, to);
categories.setViewBinder(new CategListViewBinder());
setListAdapter(categories);
}
private void refreshList() {
mCursor.requery();
populateCategoryNames();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
int builtInStatus = mCursor.getInt(
mCursor.getColumnIndexOrThrow(LocTrigDB.KEY_BUILT_IN));
boolean isBuiltIn = (builtInStatus == 1) ? true : false;
menu.add(0, MENU_DELETE_CATEG, 0, R.string.menu_delete)
.setEnabled(!isBuiltIn &&
(mAdminMode || TrigUserConfig.editLocationTriggerPlace));
menu.add(0, MENU_RENAME_CATEG, 0, R.string.menu_rename)
.setEnabled(!isBuiltIn &&
(mAdminMode || TrigUserConfig.editLocationTriggerPlace));
}
private void deleteCategory(int categId) {
String categName = mDb.getCategoryName(categId);
mDb.removeCategory(categId);
refreshList();
removeTriggers(categName);
Intent i = new Intent(this, LocTrigService.class);
i.setAction(LocTrigService.ACTION_UPDATE_LOCATIONS);
startService(i);
}
private void renameCategory(int trigId, String newName) {
if(newName == null || newName.length() == 0) {
return;
}
String oldName = mDb.getCategoryName(mDialogCategId);
mDb.renameCategory(mDialogCategId, newName);
refreshList();
updateTriggerDescriptions(oldName, newName);
}
@Override
protected Dialog onCreateDialog(int id) {
switch(id) {
case DIALOG_DELETE:
AlertDialog dialog =
new AlertDialog.Builder(this)
.setPositiveButton(R.string.delete,
new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if(which == AlertDialog.BUTTON_POSITIVE) {
deleteCategory(mDialogCategId);
}
}
})
.setNegativeButton(R.string.cancel, null)
.setTitle(getString(R.string.delete_msg, mDb.getCategoryName(mDialogCategId) + "?"))
.setMessage(R.string.trigger_loc_remove_location_text)
.create();
return dialog;
case DIALOG_RENAME:
TrigTextInput ti = new TrigTextInput(this);
ti.setPositiveButtonText(getString(R.string.done));
ti.setNegativeButtonText(getString(R.string.cancel));
String name = mDb.getCategoryName(mDialogCategId);
ti.setTitle(getString(R.string.rename_msg, name));
if(mDialogText != null) {
ti.setText(mDialogText);
}
else {
ti.setText(name);
}
ti.setOnClickListener(new TrigTextInput.onClickListener() {
@Override
public void onClick(TrigTextInput ti, int which) {
if(which == TrigTextInput.BUTTON_POSITIVE) {
renameCategory(mDialogCategId, ti.getText());
}
}
});
ti.setOnTextChangedListener(new TrigTextInput.onTextChangedListener() {
@Override
public boolean onTextChanged(TrigTextInput ti, String text) {
mDialogText = text;
//If the text change to the same name, return true
if(text.trim().equalsIgnoreCase(mDb.getCategoryName(mDialogCategId))) {
return true;
}
return checkIfCategNameValid(text);
}
});
return ti.createDialog();
default:
return null;
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
int pos = ((AdapterContextMenuInfo) item.getMenuInfo()).position;
mCursor.moveToPosition(pos);
int categId = mCursor.getInt(
mCursor.getColumnIndexOrThrow(LocTrigDB.KEY_ID));
switch(item.getItemId()) {
case MENU_DELETE_CATEG:
mDialogCategId = categId;
mDialogText = null;
removeDialog(DIALOG_DELETE);
showDialog(DIALOG_DELETE);
return true;
case MENU_RENAME_CATEG: //Rename category
mDialogCategId = categId;
removeDialog(DIALOG_RENAME);
showDialog(DIALOG_RENAME);
return true;
default:
break;
}
return super.onContextItemSelected(item);
}
@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;
}
//start the maps activity
Intent i = new Intent(this, LocTrigMapsActivity.class);
i.putExtra(LocTrigDB.KEY_ID, (int)id);
startActivity(i);
}
private void addNewCategory() {
EditText et = (EditText) findViewById(R.id.trigger_text_add_categ);
mDb.addCategory(et.getText().toString().trim());
refreshList();
//Hide the onscreen keyboard
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(et.getWindowToken(), 0);
//Clear the edit text
et.setText("");
}
@Override
public void onClick(View v) {
if(v.getId() == R.id.trigger_button_add_categ) {
addNewCategory();
}
}
@Override
public void afterTextChanged(Editable s) {
boolean buttonStatus = true;
if(!checkIfCategNameValid(s.toString())) {
buttonStatus = false;
}
Button bAdd = (Button) findViewById(R.id.trigger_button_add_categ);
bAdd.setEnabled(buttonStatus);
}
@Override
public void beforeTextChanged(CharSequence s, int start, int count,
int after) {
}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {
}
}