package org.frasermccrossan.ltc;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import android.Manifest;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.Resources;
import android.location.Criteria;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
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.AdapterView.OnItemSelectedListener;
import android.widget.ArrayAdapter;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleAdapter;
import android.widget.Spinner;
import android.widget.TextView;
import android.widget.Toast;
public class FindStop extends Activity {
TextView findHint;
EditText searchField;
TextView dispSearch;
ListView stopList;
TextView emptyStopListText;
SimpleAdapter stopListAdapter;
List<HashMap<String, String>> stops;
Spinner routeSpinner;
RouteAdapter routeAdapter;
LTCRoute[] routes;
Spinner searchTypeSpinner;
LocationManager myLocationManager;
String locProvider = null;
Location lastLocation;
SearchTask mySearchTask = null;
SharedPreferences prefs;
static final String lastTypePref = "lastType";
int downloadTry;
// entries in R.array.search_types
static final int RECENT_STOPS = 0;
static final int RECENT_ONLY = 1;
static final int CLOSEST_STOPS = 2;
static final long LOCATION_TIME_UPDATE = 15; // seconds between GPS update
static final float LOCATION_DISTANCE_UPDATE = 100; // minimum metres between GPS updates
static final int FORGET_FAVOURITE = 1; // id for context menu item lacking an intent
static final int PERMISSION_REQUEST_FINE_LOCATION = 1;
OnItemClickListener stopListener = new OnItemClickListener() {
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
TextView stopNumberView = (TextView)view.findViewById(R.id.stop_number);
String stopNumber = stopNumberView.getText().toString();
Intent stopTimeIntent = new Intent(FindStop.this, StopTimes.class);
stopTimeIntent.putExtra(BusDb.STOP_NUMBER, stopNumber);
startActivity(stopTimeIntent);
}
};
OnItemSelectedListener searchTypeListener = new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
int selected = searchTypeSpinner.getSelectedItemPosition();
if (selected == CLOSEST_STOPS) {
BusDb db = new BusDb(FindStop.this);
int failReason = 0;
if (locProvider == null) {
failReason = R.string.no_provider;
}
else if (!myLocationManager.isProviderEnabled(locProvider)) {
failReason = R.string.location_disabled;
}
db.close();
if (failReason != 0) {
searchTypeSpinner.setSelection(RECENT_STOPS);
Toast locToast = Toast.makeText(FindStop.this, failReason, Toast.LENGTH_SHORT);
locToast.show();
return;
}
}
else if (selected == RECENT_ONLY) {
// must clear search field when only favourites selected, for consistency
searchField.setText("");
searchField.clearFocus();
InputMethodManager imm = (InputMethodManager)getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(searchField.getWindowToken(), 0);
}
BusDb db = new BusDb(FindStop.this);
setRoutes(db);
db.close();
routeAdapter.notifyDataSetInvalidated();
routeAdapter.notifyDataSetChanged();
setLocationUpdates();
updateStops();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// nothing
}
};
OnItemSelectedListener routeListener = new OnItemSelectedListener() {
@Override
public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) {
updateStops();
}
@Override
public void onNothingSelected(AdapterView<?> parent) {
// nothing
}
};
public class RouteAdapter extends ArrayAdapter<LTCRoute> {
Context c;
RouteAdapter(Context context, int textViewResource, LTCRoute[] routes) {
super(context, textViewResource, routes);
c = context;
}
@Override
public View getDropDownView(int position, View convertView, ViewGroup parent) {
return getCustomView(position, false, android.R.layout.simple_spinner_dropdown_item, parent);
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
return getCustomView(position, true, android.R.layout.simple_spinner_item, parent);
}
public View getCustomView(int position, boolean shortForm, int layoutId, ViewGroup parent) {
LayoutInflater inflater = getLayoutInflater();
View row = inflater.inflate(layoutId, parent, false);
TextView label=(TextView)row.findViewById(android.R.id.text1);
if (routes[position] == null) {
label.setText(shortForm? R.string.all_routes_short : R.string.all_routes_long);
}
else {
label.setText(shortForm? routes[position].getRouteNumber() : routes[position].getNameWithNumber());
}
return row;
}
}
LocationListener locationListener = new LocationListener() {
public void onLocationChanged(Location location) {
// Called when a new location is found by the network location provider.
lastLocation = location;
updateStops();
}
public void onStatusChanged(String provider, int status, Bundle extras) {}
public void onProviderEnabled(String provider) {}
public void onProviderDisabled(String provider) {}
};
void setRoutes(BusDb db) {
boolean onlyFavourites = (searchTypeSpinner.getSelectedItemPosition() == RECENT_ONLY);
LTCRoute[] tmpRoutes = db.getAllRoutes(true, onlyFavourites);
routes = tmpRoutes;
routeAdapter = new RouteAdapter(this, R.layout.route_view, routes);
routeSpinner.setAdapter(routeAdapter);
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.find_stop);
prefs = getPreferences(Context.MODE_PRIVATE);
findHint = (TextView)findViewById(R.id.find_hint);
searchField = (EditText)findViewById(R.id.search);
dispSearch = (TextView)findViewById(R.id.disp_search);
searchField.addTextChangedListener(new TextWatcher() {
public void afterTextChanged(Editable s) {
// switch from favourites-only to just favourites if string is empty
if (s.length() > 0 && searchTypeSpinner.getSelectedItemPosition() == RECENT_ONLY) {
searchTypeSpinner.setSelection(RECENT_STOPS);
}
updateStops();
}
// don't care
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
public void onTextChanged(CharSequence s, int start, int before, int count) {}
});
myLocationManager = (LocationManager) this.getSystemService(Context.LOCATION_SERVICE);
stopList = (ListView)findViewById(R.id.stop_list);
emptyStopListText = (TextView)findViewById(R.id.empty_stop_list);
emptyStopListText.setText(R.string.searching_for_stops);
stopList.setEmptyView(findViewById(R.id.empty_stop_list));
stopList.setOnItemClickListener(stopListener);
stops = new ArrayList<HashMap<String, String>>();
stopListAdapter = new SimpleAdapter(FindStop.this,
stops,
R.layout.stop_list_item,
new String[] { BusDb.STOP_NUMBER, BusDb.STOP_NAME, BusDb.ROUTE_LIST, BusDb.DISTANCE_TEXT },
new int[] { R.id.stop_number, R.id.stop_name, R.id.route_list, R.id.distance });
stopList.setAdapter(stopListAdapter);
searchTypeSpinner = (Spinner)findViewById(R.id.search_type_spinner);
searchTypeSpinner.setOnItemSelectedListener(searchTypeListener);
int lastType = prefs.getInt(lastTypePref, -1);
if (lastType != -1) {
searchTypeSpinner.setSelection(lastType);
}
routeSpinner = (Spinner)findViewById(R.id.route_spinner);
routeSpinner.setOnItemSelectedListener(routeListener);
registerForContextMenu(stopList);
downloadTry = 0;
BusDb db = new BusDb(this);
setRoutes(db);
int updateStatus = db.getUpdateStatus();
db.close();
if (updateStatus != BusDb.UPDATE_NOT_REQUIRED) {
++downloadTry;
if (downloadTry <= 1) {
Intent updateDatabaseIntent = new Intent(FindStop.this, UpdateDatabase.class);
startActivity(updateDatabaseIntent);
}
else if (updateStatus == BusDb.UPDATE_REQUIRED) {
finish();
}
updateStops();
}
// Remove the settings for the various support messages that have existed.
if (prefs.contains("desupportSeen") || prefs.contains(("resupportSeen"))) {
SharedPreferences.Editor edit = prefs.edit();
edit.remove("desupportSeen").remove("resupportSeen").commit();
}
}
@Override
protected void onStart() {
super.onStart();
Criteria criteria = new Criteria();
criteria.setAccuracy(Criteria.ACCURACY_FINE);
locProvider = LocationManager.GPS_PROVIDER;
setLocationUpdates();
}
@Override
protected void onResume() {
super.onResume();
overridePendingTransition(R.anim.slide_in_from_left, R.anim.slide_out_to_right);
}
@Override
protected void onStop() {
if (checkLocationPermission()) {
myLocationManager.removeUpdates(locationListener);
}
if (mySearchTask != null && ! mySearchTask.isCancelled()) {
mySearchTask.cancel(true);
}
super.onStop();
}
@Override
protected void onDestroy() {
SharedPreferences.Editor edit = prefs.edit();
edit.putInt(lastTypePref, searchTypeSpinner.getSelectedItemPosition());
edit.commit();
super.onDestroy();
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.main_menu, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle item selection
switch (item.getItemId()) {
case R.id.about:
startActivity(new Intent(this, About.class));
return true;
case R.id.update_database:
Intent updateDatabaseIntent = new Intent(FindStop.this, UpdateDatabase.class);
startActivity(updateDatabaseIntent);
return true;
case R.id.find_stop_help:
startActivity(new Intent(this, FindStopHelp.class));
return true;
case R.id.share_app:
Resources r = getResources();
Intent sendIntent = new Intent();
sendIntent.setAction(Intent.ACTION_SEND);
sendIntent.putExtra(Intent.EXTRA_TEXT, r.getString(R.string.share_text));
sendIntent.putExtra(Intent.EXTRA_SUBJECT, r.getString(R.string.share_subject));
sendIntent.setType("text/plain");
startActivity(Intent.createChooser(sendIntent, null));
return true;
default:
return super.onOptionsItemSelected(item);
}
}
@Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
AdapterView.AdapterContextMenuInfo listItemInfo = (AdapterView.AdapterContextMenuInfo)menuInfo;
int item = listItemInfo.position;
HashMap<String, String> stop = stops.get(item);
/* this one handled right here by the handler, the rest get intents
* that fall through to the superclass
*/
if (Integer.valueOf(stop.get(BusDb.STOP_USES_COUNT)) > 0) {
menu.add(ContextMenu.NONE, FORGET_FAVOURITE, 2, R.string.forget_favourite);
}
if (stop.get(BusDb.LATITUDE) != null && Float.valueOf(stop.get(BusDb.LATITUDE)) > BusDb.MIN_LATITUDE) {
String query = Uri.encode(String.format("%s@%s,%s",
stop.get(BusDb.STOP_NAME),
stop.get(BusDb.LATITUDE), stop.get(BusDb.LONGITUDE)
));
String geoUri = String.format("geo:%s,%s?q=%s",
stop.get(BusDb.LATITUDE), stop.get(BusDb.LONGITUDE),
query);
Intent mapIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(geoUri));
MenuItem map = menu.add(ContextMenu.NONE, Menu.NONE, 1, R.string.show_map);
map.setIntent(mapIntent);
}
BusDb db = new BusDb(this);
ArrayList<LTCRoute> stopRoutes = db.findStopRoutes(stop.get(BusDb.STOP_NUMBER), null, 0);
db.close();
for (LTCRoute stopRoute : stopRoutes) {
Intent stopTimeIntent = new Intent(FindStop.this, StopTimes.class);
stopTimeIntent.putExtra(BusDb.STOP_NUMBER, stop.get(BusDb.STOP_NUMBER));
stopTimeIntent.putExtra(BusDb.ROUTE_NUMBER, stopRoute.number);
stopTimeIntent.putExtra(BusDb.DIRECTION_NUMBER, stopRoute.direction);
MenuItem check = menu.add(ContextMenu.NONE, Menu.NONE, 4, String.format(getString(R.string.only_check_route), stopRoute.getShortRouteDirection()));
check.setIntent(stopTimeIntent);
}
}
@Override
public boolean onContextItemSelected(MenuItem item) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item.getMenuInfo();
switch (item.getItemId()) {
case FORGET_FAVOURITE:
BusDb db = new BusDb(this);
db.forgetStopUse(Integer.valueOf(stops.get(info.position).get(BusDb.STOP_NUMBER)));
db.close();
updateStops();
return true;
default:
return super.onContextItemSelected(item);
}
}
public void setLocationUpdates()
{
switch (searchTypeSpinner.getSelectedItemPosition()) {
case RECENT_STOPS:
if (checkLocationPermission()) {
myLocationManager.removeUpdates(locationListener);
}
lastLocation = null;
break;
case CLOSEST_STOPS:
if (checkLocationPermission()) {
myLocationManager.requestLocationUpdates(locProvider, LOCATION_TIME_UPDATE * 1000, LOCATION_DISTANCE_UPDATE, locationListener);
lastLocation = myLocationManager.getLastKnownLocation(locProvider);
}
else {
requestLocationPermission();
searchTypeSpinner.setSelection(RECENT_STOPS);
}
break;
default:
// nothing
break;
}
}
public void updateStops() {
if (mySearchTask != null && ! mySearchTask.isCancelled()) {
mySearchTask.cancel(true);
}
mySearchTask = new SearchTask();
mySearchTask.execute(searchField.getText());
}
public boolean checkLocationPermission() {
return ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED;
}
protected void requestLocationPermission() {
ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.ACCESS_FINE_LOCATION}, PERMISSION_REQUEST_FINE_LOCATION);
}
@Override
public void onRequestPermissionsResult(int requestCode, String permissions[], int[] grantResults) {
switch (requestCode) {
case PERMISSION_REQUEST_FINE_LOCATION:
if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
searchTypeSpinner.setSelection(CLOSEST_STOPS);
}
break;
}
}
class SearchTask extends AsyncTask<CharSequence, List<HashMap<String, String>>, Void> {
protected void onPreExecute() {
emptyStopListText.setText(R.string.searching_for_stops);
}
@SuppressWarnings("unchecked")
protected Void doInBackground(CharSequence... strings) {
BusDb db = new BusDb(FindStop.this);
List<HashMap<String, String>> newStops = db.findStops(strings[0],
lastLocation,
strings[0].length() == 0 && searchTypeSpinner.getSelectedItemPosition() == RECENT_ONLY,
(LTCRoute)routeSpinner.getSelectedItem());
if (!isCancelled()) {
publishProgress(newStops);
}
db.addRoutesToStopList(newStops);
if (!isCancelled()) {
// can publish a null since the above update updates all the same objects
publishProgress((List<HashMap<String, String>>) null);
}
db.close();
return null;
}
protected void onProgressUpdate(List<HashMap<String, String>>... newStops) {
if (!isCancelled() && stopListAdapter != null) {
for (List<HashMap<String, String>> newStop: newStops) {
if (newStop != null) {
stops.clear();
stops.addAll(newStop);
}
}
stopListAdapter.notifyDataSetChanged();
emptyStopListText.setText(R.string.no_stops_found);
if (stopListAdapter.getCount() == 0 && searchTypeSpinner.getSelectedItemPosition() == RECENT_ONLY) {
searchTypeSpinner.setSelection(RECENT_STOPS);
Toast.makeText(FindStop.this, R.string.no_favourites, Toast.LENGTH_SHORT).show();
}
}
}
}
}