/**
* Copyright (C) 2009 Anders Aagaard <aagaande@gmail.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
*
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package com.neuron.trafikanten.views;
import java.util.ArrayList;
import android.app.ListActivity;
import android.app.SearchManager;
import android.content.Context;
import android.content.Intent;
import android.database.Cursor;
import android.os.Bundle;
import android.text.InputType;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnClickListener;
import android.view.View.OnKeyListener;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.AutoCompleteTextView;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import com.google.android.AnalyticsUtils;
import com.neuron.trafikanten.R;
import com.neuron.trafikanten.dataProviders.IGenericProviderHandler;
import com.neuron.trafikanten.dataProviders.trafikanten.TrafikantenSearch;
import com.neuron.trafikanten.dataSets.StationData;
import com.neuron.trafikanten.db.FavoriteDbAdapter;
import com.neuron.trafikanten.db.HistoryDbAdapter;
import com.neuron.trafikanten.tasks.GenericTask;
import com.neuron.trafikanten.tasks.LocationTask;
import com.neuron.trafikanten.tasks.SearchAddressTask;
import com.neuron.trafikanten.tasks.ShowHelpTask;
import com.neuron.trafikanten.tasks.handlers.ReturnCoordinatesHandler;
import com.neuron.trafikanten.views.map.GenericMap;
public abstract class GenericSelectStationView extends ListActivity {
private static final String TAG = "Trafikanten-SelectStationView";
private final static int ADDFAVORITE_ID = Menu.FIRST;
private final static int REMOVEFAVORITE_ID = Menu.FIRST + 1;
private final static int REMOVEHISTORY_ID = Menu.FIRST + 2;
/*
* Options menu items:
*/
private final static int MYLOCATION_ID = Menu.FIRST;
private final static int MAP_ID = Menu.FIRST + 1;
/* private final static int CONTACT_ID = Menu.FIRST + 2; */
private final static int ADDRESS_ID = Menu.FIRST + 3;
private final static int FAVORITES_ID = Menu.FIRST + 4;
private final static int HELP_ID = Menu.FIRST + 5;
/*
* Database adapter
*/
public FavoriteDbAdapter favoriteDbAdapter;
public HistoryDbAdapter historyDbAdapter;
/*
* if isRealtimeSelector we filter some stations
*/
public boolean isRealtimeSelector;
/*
* Views
*/
private TextView infoText;
/*
* Task tracking
*/
private TrafikantenSearch searchProvider;
private GenericTask activeTask;
/*
* Saved instance state: (list)
*/
public StationListAdapter stationListAdapter;
/*
* Other
*/
private AutoCompleteTextView searchEdit;
/** Called when the activity is first created. */
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
/*
* Setup view
*/
registerForContextMenu(getListView());
infoText = (TextView) findViewById(R.id.infoText);
/*
* Setup adapters, add favorites to list and refresh.
*/
favoriteDbAdapter = new FavoriteDbAdapter(this);
historyDbAdapter = new HistoryDbAdapter(this);
stationListAdapter = new StationListAdapter(this);
if (savedInstanceState == null) {
favoriteDbAdapter.addFavoritesToList(isRealtimeSelector, stationListAdapter.getList());
historyDbAdapter.addHistoryToList(isRealtimeSelector, stationListAdapter.getList());
} else {
final ArrayList<StationData> list = savedInstanceState.getParcelableArrayList(StationListAdapter.KEY_SEARCHSTATIONLIST);
stationListAdapter.setList(list);
}
refresh();
/*
* Setup the search editbox to search on Enter.
*/
searchEdit = (AutoCompleteTextView) findViewById(R.id.search);
//FIXME : This + the search button is .. less than optimal
searchEdit.setOnKeyListener(new OnKeyListener() {
public boolean onKey(View v, int keyCode, KeyEvent event) {
if (event.getAction() != KeyEvent.ACTION_DOWN) {
return false;
}
switch (keyCode) {
case KeyEvent.KEYCODE_ENTER:
case KeyEvent.KEYCODE_DPAD_CENTER:
/*
* Perform search
*/
doSearch();
return true;
}
return false;
}
});
/*
* Setup autocomplete
*/
StationSearchAdapter adapter = new StationSearchAdapter(this);
/*StationSearchAdapter stationSearchAdapter = new StationSearchAdapter();
ArrayAdapter<String> adapter = new ArrayAdapter<String>(this,
android.R.layout.simple_dropdown_item_1line, COUNTRIES);*/
searchEdit.setAdapter(adapter);
searchEdit.setOnItemClickListener(new OnItemClickListener() {
@Override
public void onItemClick(AdapterView<?> adapter, View view, int position,
long stationId) {
searchEdit.getText().clear();
Cursor cursor = (Cursor) adapter.getItemAtPosition(position);
int icon = cursor.getInt(cursor.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_ICON_1));
String stopname = cursor.getString(cursor.getColumnIndexOrThrow(SearchManager.SUGGEST_COLUMN_TEXT_1));
if (icon == android.R.drawable.ic_menu_directions) {
// Icon is station
StationData station = new StationData(stopname, (int)stationId, StationData.TYPE_STATION);
stationSelected(station);
} else {
//FIXME : Critical, clicking icons that are not stations
}
}
});
/*
* Hack : Tweaks for devices with software keyboards, avoid keyboard in program start.
*/
final int inType = searchEdit.getInputType(); // backup the input type
searchEdit.setInputType(InputType.TYPE_NULL); // disable soft input
searchEdit.setOnTouchListener(new OnTouchListener(){
@Override
public boolean onTouch(View v, MotionEvent event) {
searchEdit.setInputType(inType); // restore input type
searchEdit.onTouchEvent(event); // call native handler
return true; // consume touch even
}
});
/*
* Setup our fancy button:
*/
final Button searchButton = (Button) findViewById(R.id.searchButton);
searchButton.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
doSearch();
}
});
setListAdapter(stationListAdapter);
}
private IGenericProviderHandler<StationData> searchHandler = new IGenericProviderHandler<StationData>(){
@Override
public void onExtra(int what, Object obj) {
/* Class has no extra data */
}
@Override
public void onData(StationData data) {
stationListAdapter.addItem(data);
stationListAdapter.notifyDataSetChanged();
}
@Override
public void onPostExecute(Exception exception) {
searchProvider = null;
setProgressBar(false);
if (exception != null) {
Log.w(TAG,"onException " + exception);
if (exception.getClass().getSimpleName().equals("ParseException")) {
Toast.makeText(GenericSelectStationView.this, R.string.trafikantenErrorParse, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(GenericSelectStationView.this, R.string.trafikantenErrorOther, Toast.LENGTH_LONG).show();
}
} else {
refresh();
}
}
@Override
public void onPreExecute() {
Log.i(TAG,"searchProvider started");
stationListAdapter.clear();
stationListAdapter.notifyDataSetChanged();
setProgressBar(true);
}
};
private void doSearch() {
InputMethodManager inputMethodManager = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
inputMethodManager.hideSoftInputFromWindow(searchEdit.getWindowToken(), 0);
if (searchEdit.getText().toString().length() == 0) {
resetView();
} else {
if (searchProvider != null)
searchProvider.kill();
searchProvider = new TrafikantenSearch(this, searchEdit.getText().toString(), isRealtimeSelector, searchHandler);
AnalyticsUtils.getInstance(this).trackEvent("Data", "StationSearch", "String", 0);
infoText.setVisibility(View.GONE);
infoText.setText(getInfoHelpText());
}
}
public void setAdapterLayout(int layout) {
stationListAdapter.setLayout(layout);
}
/*
* Create context menu, context menu = long push on list item.
* @see android.app.Activity#onCreateContextMenu(android.view.ContextMenu, android.view.View, android.view.ContextMenu.ContextMenuInfo)
*/
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
final AdapterView.AdapterContextMenuInfo info = (AdapterView.AdapterContextMenuInfo) menuInfo;
final StationData stationData = stationListAdapter.getList().get(info.position);
if (stationData.isFavorite) {
menu.add(0, REMOVEFAVORITE_ID, 0, R.string.removeFavorite);
} else {
menu.add(0, ADDFAVORITE_ID, 0, R.string.addFavorite);
if (historyDbAdapter.hasStation(stationData.stationId)) {
menu.add(0, REMOVEHISTORY_ID, 0, R.string.removeHistory);
}
}
}
/*
* Select context menu item.
* @see android.app.Activity#onContextItemSelected(android.view.MenuItem)
*/
@Override
public boolean onContextItemSelected(MenuItem item) {
/*
* Get selected item.
*/
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
final StationData station = (StationData) stationListAdapter.getItem(info.position);
switch(item.getItemId()) {
case ADDFAVORITE_ID:
// Fallthrough, we toggle anyway
case REMOVEFAVORITE_ID:
/*
* Toggle favorite.
*/
if (favoriteDbAdapter.toggleFavorite(station)) {
/*
* If we add it to favorite, delete it from history.
*/
historyDbAdapter.delete(station.stationId);
}
refresh();
return true;
case REMOVEHISTORY_ID:
/*
* Remove a station from history
*/
historyDbAdapter.delete(station.stationId);
resetView(); // Need full resetView here, as a refresh only refreshes favorites
return true;
}
return super.onContextItemSelected(item);
}
/*
* Options menu, visible on menu button.
* @see android.app.Activity#onCreateOptionsMenu(android.view.Menu)
*/
@Override
public boolean onCreateOptionsMenu(Menu menu) {
final MenuItem myLocation = menu.add(0, MYLOCATION_ID, 0, R.string.myLocation);
myLocation.setIcon(android.R.drawable.ic_menu_mylocation);
final MenuItem favorites = menu.add(0, FAVORITES_ID, 0, R.string.favorites);
favorites.setIcon(android.R.drawable.ic_menu_myplaces);
/* final MenuItem contact = menu.add(0, CONTACT_ID, 0, R.string.contact);
contact.setIcon(R.drawable.ic_menu_cc); */
final MenuItem address = menu.add(0, ADDRESS_ID, 0, R.string.address);
address.setIcon(R.drawable.ic_menu_directions);
final MenuItem map = menu.add(0, MAP_ID, 0, R.string.map);
map.setIcon(android.R.drawable.ic_menu_mapmode);
final MenuItem help = menu.add(0, HELP_ID, 0, R.string.help);
help.setIcon(android.R.drawable.ic_menu_help);
return true;
}
/*
* Options menu item selected, options menu visible on menu button.
* @see android.app.Activity#onOptionsItemSelected(android.view.MenuItem)
*/
@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch(item.getItemId()) {
case MYLOCATION_ID:
findMyLocationTask();
break;
case MAP_ID:
GenericMap.Show(this, stationListAdapter.getList(), false, 0);
break;
/* case CONTACT_ID:
selectContact();
break; */
case ADDRESS_ID:
searchAddressTask();
break;
case FAVORITES_ID:
AnalyticsUtils.getInstance(this).trackEvent("Navigation", "Home", "Favorites", 0);
resetView();
break;
case HELP_ID:
new ShowHelpTask(this);
break;
default:
Log.e(TAG, "onOptionsItemSelected unknown id " + item.getItemId());
}
return super.onOptionsItemSelected(item);
}
/*
* Deal with contact selection
*/
/*private void selectContact() {
if (searchProvider != null)
searchProvider.kill();
activeTask = new SelectContactTask(this, getReturnCoordinatesHandler());
}*/
/*
* Get a generic handler that returns coordinates
*/
private ReturnCoordinatesHandler getReturnCoordinatesHandler() {
return new ReturnCoordinatesHandler() {
@Override
public void onCanceled() {
setProgressBar(false);
activeTask = null;
}
@Override
public void onError(Exception exception) {
Log.w(TAG,"onException " + exception);
if (exception.getClass().getSimpleName().equals("ParseException")) {
Toast.makeText(GenericSelectStationView.this, R.string.trafikantenErrorParse, Toast.LENGTH_LONG).show();
} else {
Toast.makeText(GenericSelectStationView.this, R.string.trafikantenErrorOther, Toast.LENGTH_LONG).show();
}
setProgressBar(false);
}
@Override
public void onFinished(double latitude, double longitude) {
setProgressBar(false);
activeTask = null;
if (searchProvider != null)
searchProvider.kill();
final boolean filterRealtime = getViewType() == TYPE_REALTIME;
searchProvider = new TrafikantenSearch(GenericSelectStationView.this, latitude, longitude, filterRealtime, searchHandler);
AnalyticsUtils.getInstance(GenericSelectStationView.this).trackEvent("Data", "StationSearch", "Location", 0);
}
@Override
public void OnStartWork() {
setProgressBar(true);
}
};
}
/*
* Deal with a location search
*/
public void findMyLocationTask() {
if (searchProvider != null)
searchProvider.kill();
activeTask = new LocationTask(this, getReturnCoordinatesHandler());
}
/*
* Deal with address search
*/
private void searchAddressTask() {
if (searchProvider != null)
searchProvider.kill();
activeTask = new SearchAddressTask(this, getReturnCoordinatesHandler());
}
/*
* Reset is used by RESET_ID and Settings refresh.
*/
private void resetView() {
/*
* Reset database connection incase dataprovider has changed
*/
favoriteDbAdapter.close();
historyDbAdapter.close();
favoriteDbAdapter = new FavoriteDbAdapter(this);
historyDbAdapter = new HistoryDbAdapter(this);
/*
* Reset view
*/
stationListAdapter.clear();
favoriteDbAdapter.addFavoritesToList(isRealtimeSelector, stationListAdapter.getList());
historyDbAdapter.addHistoryToList(isRealtimeSelector, stationListAdapter.getList());
refresh();
/*
* And searchbox
*/
final EditText searchEdit = (EditText) findViewById(R.id.search);
searchEdit.setText("");
}
/*
* Select list item
* @see android.app.ListActivity#onListItemClick(android.widget.ListView, android.view.View, int, long)
*/
@Override
protected void onListItemClick(ListView l, View v, int position, long id) {
/*
* Take current selected station, and return with it.
*/
final StationData station = (StationData) stationListAdapter.getItem(position);
stationSelected(station);
}
/*
* Custom handlers
*/
public abstract void stationSelected(StationData station);
public abstract int getInfoHelpText();
public abstract void setProgressBar(boolean value);
public static final int TYPE_REALTIME = 1;
public static final int TYPE_ROUTE = 2;
public abstract int getViewType();
public void updateHistory(StationData station) {
if (!station.isFavorite) {
historyDbAdapter.updateHistory(station);
} else {
favoriteDbAdapter.updateUsed(station);
}
}
/*
* Refresh view, this involves checking list against current favorites and setting .isFavorite to render star.
*/
private void refresh() {
boolean dbClosed = !favoriteDbAdapter.isOpen();
if (dbClosed) {
/*
* This can happen as we allow background loading.
*/
favoriteDbAdapter.open();
historyDbAdapter.open();
}
favoriteDbAdapter.refreshFavorites(stationListAdapter.getList());
stationListAdapter.notifyDataSetChanged();
infoText.setVisibility(stationListAdapter.getCount() > 0 ? View.GONE : View.VISIBLE);
if (dbClosed) {
favoriteDbAdapter.close();
historyDbAdapter.close();
}
}
/*
* activityResult handles data passed from map view
* @see android.app.Activity#onActivityResult(int, int, android.content.Intent)
*/
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (resultCode == RESULT_OK) {
/*
* return from map view, will return a single station
*/
if (data.hasExtra(StationData.PARCELABLE)) {
/*
* We got station in return
*/
final StationData station = data.getParcelableExtra(StationData.PARCELABLE);
onResume(); // reopen database
stationSelected(station);
}
}
super.onActivityResult(requestCode, resultCode, data);
}
/*
* This function is used only by route, and return true if multiselect is enabled and the station is selected.
*/
public boolean route_isStationSelected(StationData station) {
return false;
}
@Override
protected void onStop() {
if (searchProvider != null)
searchProvider.kill();
super.onStop();
}
/*
* Save state, commit to databases
* @see android.app.Activity#onPause()
*/
@Override
protected void onPause() {
favoriteDbAdapter.close();
historyDbAdapter.close();
if (activeTask != null) {
activeTask.stop();
}
super.onPause();
}
/*
* Resume state, restart search.
* @see android.app.Activity#onResume()
*/
@Override
protected void onResume() {
super.onResume();
favoriteDbAdapter.open();
historyDbAdapter.open();
}
@Override
protected void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
outState.putParcelableArrayList(StationListAdapter.KEY_SEARCHSTATIONLIST, stationListAdapter.getList());
}
}
class StationListAdapter extends BaseAdapter {
public static final String KEY_SEARCHSTATIONLIST = "searchstationlist";
private LayoutInflater inflater;
private ArrayList<StationData> items = new ArrayList<StationData>();
private int layout = R.layout.selectstation_list;
private GenericSelectStationView parent;
public StationListAdapter(GenericSelectStationView parent) {
this.parent = parent;
inflater = LayoutInflater.from(parent);
}
/*
* Simple functions dealing with adding/setting items.
*/
public void clear() { items.clear(); }
public ArrayList<StationData> getList() { return items; }
public void setList(ArrayList<StationData> items) { this.items = items; }
public void addItem(StationData item) {
if (parent.isRealtimeSelector) {
// if we have a realtime station we add only realtime stops.
if (item.realtimeStop) {
items.add(item);
}
return;
}
items.add(item);
}
/*
* Standard android.widget.Adapter items, self explanatory.
*/
@Override
public int getCount() { return items.size(); }
@Override
public Object getItem(int pos) { return items.get(pos); }
@Override
public long getItemId(int pos) { return pos; }
/*
* Code to support multiselect
*/
public void setLayout(int layout) {
this.layout = layout;
notifyDataSetChanged();
}
/*
* Setup the view
* @see android.widget.Adapter#getView(int, android.view.View, android.view.ViewGroup)
*/
@Override
public View getView(final int pos, View convertView, ViewGroup arg2) {
final StationData station = items.get(pos);
/*
* Setup holder, for performance and readability.
*/
ViewHolder holder;
if (convertView == null || convertView.getId() != layout) {
/*
* New view, inflate and setup holder.
*/
convertView = inflater.inflate(layout, null);
convertView.setId(layout);
holder = new ViewHolder();
holder.star = (ImageView) convertView.findViewById(R.id.star);
holder.stopName = (TextView) convertView.findViewById(R.id.stopname);
holder.address = (TextView) convertView.findViewById(R.id.address);
holder.range = (TextView) convertView.findViewById(R.id.range);
convertView.setTag(holder);
} else {
/*
* Old view found, we can reuse that instead of inflating.
*/
holder = (ViewHolder) convertView.getTag();
}
/*
* Render data to view.
*/
holder.stopName.setText(station.stopName);
/*
* Setup station.extra
*/
if (station.extra != null) {
holder.address.setText(station.extra);
holder.address.setVisibility(View.VISIBLE);
} else {
//holder.address.setText("");
holder.address.setVisibility(View.INVISIBLE);
}
/*
* Setup station.walkingDistance
*/
if (station.walkingDistance > 0) {
holder.range.setText("" + station.walkingDistance + "m");
holder.range.setVisibility(View.VISIBLE);
} else {
holder.range.setText("");
holder.range.setVisibility(View.INVISIBLE);
}
/*
* Setup station.isFavorite
*/
if (station.isFavorite) {
holder.star.setVisibility(View.VISIBLE);
} else {
holder.star.setVisibility(View.GONE);
}
/*
* If we're rendering checkboxes we need checkbox states
*/
if (layout == R.layout.selectstation_list_multiselect) {
final CheckBox stopCheckBox = (CheckBox) convertView.findViewById(R.id.stopname);
stopCheckBox.setOnClickListener(new OnClickListener() {
@Override
public void onClick(View v) {
parent.stationSelected(station);
}
});
stopCheckBox.setChecked(parent.route_isStationSelected(station));
}
return convertView;
}
/*
* Class for caching the view.
*/
static class ViewHolder {
ImageView star;
TextView stopName;
TextView address;
TextView range;
}
};