/** * */ package net.osmand.plus.activities.search; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; import java.util.List; import net.osmand.Algoritms; import net.osmand.LogUtil; import net.osmand.OsmAndFormatter; import net.osmand.data.Amenity; import net.osmand.osm.LatLon; import net.osmand.osm.OpeningHoursParser; import net.osmand.osm.OpeningHoursParser.OpeningHoursRule; import net.osmand.plus.NameFinderPoiFilter; import net.osmand.plus.OsmandSettings; import net.osmand.plus.PoiFilter; import net.osmand.plus.R; import net.osmand.plus.activities.MapActivity; import net.osmand.plus.activities.OsmandApplication; import android.app.AlertDialog; import android.app.ListActivity; import android.content.DialogInterface; import android.content.Intent; import android.content.SharedPreferences; import android.content.pm.ActivityInfo; import android.graphics.Canvas; import android.graphics.Color; import android.graphics.ColorFilter; import android.graphics.Paint; import android.graphics.Path; import android.graphics.Paint.Style; import android.graphics.drawable.Drawable; import android.hardware.Sensor; import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; import android.location.LocationProvider; import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.Message; import android.text.Editable; import android.text.TextWatcher; import android.util.Log; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import android.view.View.OnClickListener; import android.widget.AdapterView; import android.widget.ArrayAdapter; import android.widget.Button; import android.widget.EditText; import android.widget.Filter; import android.widget.ImageView; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; /** * @author Maxim Frolov * */ public class SearchPOIActivity extends ListActivity implements SensorEventListener { public static final String AMENITY_FILTER = "net.osmand.amenity_filter"; //$NON-NLS-1$ public static final String SEARCH_LAT = "net.osmand.am_search_lat"; //$NON-NLS-1$ public static final String SEARCH_LON = "net.osmand.am_search_lon"; //$NON-NLS-1$ private static final int GPS_TIMEOUT_REQUEST = 1000; private static final int GPS_DIST_REQUEST = 5; private static final int MIN_DISTANCE_TO_RESEARCH = 70; private static final int MIN_DISTANCE_TO_UPDATE = 6; private Button searchPOILevel; private Button showOnMap; private PoiFilter filter; private AmenityAdapter amenityAdapter; private TextView searchArea; private EditText searchFilter; private View searchFilterLayout; private boolean searchNearBy = false; private Location location = null; private Location searchedLocation = null; private Float heading = null; private String currentLocationProvider = null; private boolean sensorRegistered = false; private Handler uiHandler; private SharedPreferences settings; @Override public void onCreate(Bundle icicle) { super.onCreate(icicle); setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_PORTRAIT); setContentView(R.layout.searchpoi); Bundle bundle = this.getIntent().getExtras(); String filterId = bundle.getString(AMENITY_FILTER); filter = ((OsmandApplication)getApplication()).getPoiFilters().getFilterById(filterId); uiHandler = new Handler(); searchPOILevel = (Button) findViewById(R.id.SearchPOILevelButton); searchArea = (TextView) findViewById(R.id.SearchAreaText); searchFilter = (EditText) findViewById(R.id.SearchFilter); searchFilterLayout = findViewById(R.id.SearchFilterLayout); showOnMap = (Button) findViewById(R.id.ShowOnMap); settings = OsmandSettings.getPrefs(this); searchPOILevel.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { if(isNameFinderFilter()){ String query = searchFilter.getText().toString(); if(query.length() == 0){ Toast.makeText(SearchPOIActivity.this, R.string.poi_namefinder_query_empty, Toast.LENGTH_LONG).show(); return; } String res = ((NameFinderPoiFilter) filter).searchOnline(location.getLatitude(), location.getLongitude(), query); if(res != null){ Toast.makeText(SearchPOIActivity.this, res, Toast.LENGTH_LONG).show(); } amenityAdapter.setNewModel(((NameFinderPoiFilter) filter).getSearchedAmenities(), ""); showOnMap.setEnabled(amenityAdapter.getCount() > 0); } else { amenityAdapter.setNewModel(filter.searchFurther(location.getLatitude(), location.getLongitude()), searchFilter.getText().toString()); } searchedLocation = location; searchArea.setText(filter.getSearchArea()); searchPOILevel.setEnabled(filter.isSearchFurtherAvailable()); } }); if(isNameFinderFilter()){ searchFilterLayout.setVisibility(View.VISIBLE); } searchFilter.addTextChangedListener(new TextWatcher(){ @Override public void afterTextChanged(Editable s) { if(!isNameFinderFilter()){ amenityAdapter.getFilter().filter(s); } } @Override public void beforeTextChanged(CharSequence s, int start, int count, int after) { } @Override public void onTextChanged(CharSequence s, int start, int before, int count) { } }); if(bundle.containsKey(SEARCH_LAT) && bundle.containsKey(SEARCH_LON)){ location = new Location("internal"); //$NON-NLS-1$ location.setLatitude(bundle.getDouble(SEARCH_LAT)); location.setLongitude(bundle.getDouble(SEARCH_LON)); searchNearBy = false; } else { location = null; searchNearBy = true; } if(isNameFinderFilter()){ showOnMap.setEnabled(false); } else { showOnMap.setEnabled(filter != null); } showOnMap.setOnClickListener(new OnClickListener() { @Override public void onClick(View v) { OsmandSettings.setPoiFilterForMap(SearchPOIActivity.this, filter.getFilterId()); OsmandSettings.setShowPoiOverMap(SearchPOIActivity.this, true); if(/*searchNearBy && */location != null){ OsmandSettings.setMapLocationToShow(SearchPOIActivity.this, location.getLatitude(), location.getLongitude(), 15); } Intent newIntent = new Intent(SearchPOIActivity.this, MapActivity.class); startActivity(newIntent); } }); if (filter != null) { amenityAdapter = new AmenityAdapter(new ArrayList<Amenity>()); if(location == null){ filter.clearPreviousZoom(); } else if(!isNameFinderFilter()) { searchedLocation = location; amenityAdapter.setNewModel(filter.initializeNewSearch(location.getLatitude(), location.getLongitude(), 40), ""); } setListAdapter(amenityAdapter); searchPOILevel.setEnabled(filter.isSearchFurtherAvailable()); searchArea.setText(filter.getSearchArea()); } else { searchPOILevel.setEnabled(false); } // ListActivity has a ListView, which you can get with: ListView lv = getListView(); lv.setOnItemLongClickListener(new AdapterView.OnItemLongClickListener() { @Override public boolean onItemLongClick(AdapterView<?> av, View v, int pos, long id) { final Amenity amenity = ((AmenityAdapter) getListAdapter()).getItem(pos); String format = OsmAndFormatter.getPoiSimpleFormat(amenity, SearchPOIActivity.this, OsmandSettings.usingEnglishNames(settings)); if (amenity.getOpeningHours() != null) { format += " "+getString(R.string.opening_hours) + " : " + amenity.getOpeningHours(); //$NON-NLS-1$ //$NON-NLS-2$ } // Toast.makeText(v.getContext(), format, Toast.LENGTH_LONG).show(); AlertDialog.Builder builder = new AlertDialog.Builder(SearchPOIActivity.this); builder.setTitle(format); builder.setItems(new String[]{getString(R.string.show_poi_on_map), getString(R.string.navigate_to)}, new DialogInterface.OnClickListener(){ @Override public void onClick(DialogInterface dialog, int which) { if(which == 0){ int z = OsmandSettings.getLastKnownMapZoom(settings); String poiSimpleFormat = OsmAndFormatter.getPoiSimpleFormat(amenity, SearchPOIActivity.this, OsmandSettings.usingEnglishNames(settings)); OsmandSettings.setMapLocationToShow(SearchPOIActivity.this, amenity.getLocation().getLatitude(), amenity.getLocation().getLongitude(), Math.max(16, z), getString(R.string.poi)+" : " + poiSimpleFormat); //$NON-NLS-1$ } else if(which == 1){ LatLon l = amenity.getLocation(); OsmandSettings.setPointToNavigate(SearchPOIActivity.this, l.getLatitude(), l.getLongitude()); } if(filter != null){ OsmandSettings.setPoiFilterForMap(SearchPOIActivity.this, filter.getFilterId()); OsmandSettings.setShowPoiOverMap(SearchPOIActivity.this, true); } Intent newIntent = new Intent(SearchPOIActivity.this, MapActivity.class); startActivity(newIntent); } }); builder.show(); return true; } }); } public void setLocation(Location l){ registerUnregisterSensor(l); boolean handled = false; if (l != null && filter != null) { if (location == null) { searchedLocation = l; if (!isNameFinderFilter()) { amenityAdapter.setNewModel(filter.searchAgain(l.getLatitude(), l.getLongitude()), searchFilter.getText().toString()); searchPOILevel.setText(R.string.search_POI_level_btn); } else { searchPOILevel.setText(R.string.search_button); } searchPOILevel.setEnabled(filter.isSearchFurtherAvailable()); searchArea.setText(filter.getSearchArea()); handled = true; } else if (searchedLocation != null && l.distanceTo(searchedLocation) > MIN_DISTANCE_TO_RESEARCH) { amenityAdapter.setNewModel(filter.searchAgain(l.getLatitude(), l.getLongitude()), searchFilter.getText().toString()); handled = true; } else if(location.distanceTo(l) > MIN_DISTANCE_TO_UPDATE){ handled = true; } } else { if(location != null){ searchPOILevel.setText(R.string.search_poi_location); searchPOILevel.setEnabled(false); handled = true; } } if(handled) { location = l; amenityAdapter.notifyDataSetChanged(); } } private boolean isRunningOnEmulator(){ if (Build.DEVICE.equals("generic")) { //$NON-NLS-1$ return true; } return false; } public boolean isNameFinderFilter(){ return filter instanceof NameFinderPoiFilter; } @Override public boolean onCreateOptionsMenu(Menu menu) { boolean m = super.onCreateOptionsMenu(menu); if (!isNameFinderFilter()) { final MenuItem me = menu.add(R.string.show_poi_filter); me.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() { @Override public boolean onMenuItemClick(MenuItem item) { if (searchFilterLayout.getVisibility() == View.GONE) { searchFilterLayout.setVisibility(View.VISIBLE); me.setTitle(R.string.hide_poi_filter); } else { searchFilter.setText(""); //$NON-NLS-1$ searchFilterLayout.setVisibility(View.GONE); me.setTitle(R.string.show_poi_filter); } return true; } }); } return m; } // Working with location listeners private LocationListener networkListener = new LocationListener(){ @Override public void onLocationChanged(Location location) { setLocation(location); } @Override public void onProviderDisabled(String provider) { setLocation(null); } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { if(LocationProvider.OUT_OF_SERVICE == status){ setLocation(null); } } }; private LocationListener gpsListener = new LocationListener(){ @Override public void onLocationChanged(Location location) { setLocation(location); } @Override public void onProviderDisabled(String provider) { setLocation(null); } @Override public void onProviderEnabled(String provider) { } @Override public void onStatusChanged(String provider, int status, Bundle extras) { LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE); // do not change provider for temporarily unavailable (possible bug for htc hero 2.1 ?) if (LocationProvider.OUT_OF_SERVICE == status /*|| LocationProvider.TEMPORARILY_UNAVAILABLE == status*/) { if(LocationProvider.OUT_OF_SERVICE == status){ setLocation(null); } if (!isRunningOnEmulator() && service.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { if (!Algoritms.objectEquals(currentLocationProvider, LocationManager.NETWORK_PROVIDER)) { currentLocationProvider = LocationManager.NETWORK_PROVIDER; service.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, this); } } } else if (LocationProvider.AVAILABLE == status) { if (!Algoritms.objectEquals(currentLocationProvider, LocationManager.GPS_PROVIDER)) { currentLocationProvider = LocationManager.GPS_PROVIDER; service.removeUpdates(networkListener); } } } }; @Override public void onSensorChanged(SensorEvent event) { // Attention : sensor produces a lot of events & can hang the system if(heading != null && Math.abs(heading - event.values[0]) < 4){ // this is very small variation return; } heading = event.values[0]; if(!uiHandler.hasMessages(5)){ Message msg = Message.obtain(uiHandler, new Runnable(){ @Override public void run() { amenityAdapter.notifyDataSetChanged(); } }); msg.what = 5; uiHandler.sendMessageDelayed(msg, 100); } } @Override public void onAccuracyChanged(Sensor sensor, int accuracy) { } @Override protected void onResume() { super.onResume(); if(searchNearBy){ location = null; amenityAdapter.notifyDataSetChanged(); searchPOILevel.setEnabled(false); } else { setLocation(location); } if(searchNearBy && location == null){ searchPOILevel.setText(R.string.search_poi_location); } else if(isNameFinderFilter()){ searchPOILevel.setText(R.string.search_button); } else { searchPOILevel.setText(R.string.search_POI_level_btn); } if (searchNearBy) { LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE); service.requestLocationUpdates(LocationManager.GPS_PROVIDER, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, gpsListener); currentLocationProvider = LocationManager.GPS_PROVIDER; if(!isRunningOnEmulator()){ // try to always ask for network provide it is faster way to find location service.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, GPS_TIMEOUT_REQUEST, GPS_DIST_REQUEST, networkListener); currentLocationProvider = LocationManager.NETWORK_PROVIDER; } } } private void registerUnregisterSensor(Location location){ // show point view only if gps enabled if(location == null){ if(sensorRegistered) { Log.d(LogUtil.TAG, "Disable sensor"); //$NON-NLS-1$ ((SensorManager) getSystemService(SENSOR_SERVICE)).unregisterListener(this); sensorRegistered = false; heading = null; } } else { if(!sensorRegistered){ Log.d(LogUtil.TAG, "Enable sensor"); //$NON-NLS-1$ SensorManager sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE); Sensor s = sensorMgr.getDefaultSensor(Sensor.TYPE_ORIENTATION); if (s != null) { sensorMgr.registerListener(this, s, SensorManager.SENSOR_DELAY_UI); } sensorRegistered = true; } } } @Override protected void onPause() { super.onPause(); if (searchNearBy) { LocationManager service = (LocationManager) getSystemService(LOCATION_SERVICE); service.removeUpdates(gpsListener); service.removeUpdates(networkListener); SensorManager sensorMgr = (SensorManager) getSystemService(SENSOR_SERVICE); sensorMgr.unregisterListener(this); sensorRegistered = false; currentLocationProvider = null; } } public void onListItemClick(ListView parent, View v, int position, long id) { if(filter != null){ OsmandSettings.setPoiFilterForMap(SearchPOIActivity.this, filter.getFilterId()); OsmandSettings.setShowPoiOverMap(SearchPOIActivity.this, true); } int z = OsmandSettings.getLastKnownMapZoom(settings); Amenity amenity = ((AmenityAdapter) getListAdapter()).getItem(position); String poiSimpleFormat = OsmAndFormatter.getPoiSimpleFormat(amenity, this, OsmandSettings.usingEnglishNames(settings)); OsmandSettings.setMapLocationToShow(this, amenity.getLocation().getLatitude(), amenity.getLocation().getLongitude(), Math.max(16, z), getString(R.string.poi)+" : " + poiSimpleFormat); //$NON-NLS-1$ Intent newIntent = new Intent(SearchPOIActivity.this, MapActivity.class); startActivity(newIntent); } class DirectionDrawable extends Drawable { Paint paintRouteDirection; Path path = new Path(); private float angle; private final int width = 24; private final int height = 24; public DirectionDrawable(){ paintRouteDirection = new Paint(); paintRouteDirection.setStyle(Style.FILL_AND_STROKE); paintRouteDirection.setColor(Color.rgb(100, 0, 255)); paintRouteDirection.setAntiAlias(true); int h = 15; int w = 4; float sarrowL = 8; // side of arrow float harrowL = (float) Math.sqrt(2) * sarrowL; // hypotenuse of arrow float hpartArrowL = (float) (harrowL - w) / 2; path.moveTo(width / 2, height - (height - h) / 3); path.rMoveTo(w / 2, 0); path.rLineTo(0, -h); path.rLineTo(hpartArrowL, 0); path.rLineTo(-harrowL / 2, -harrowL / 2); // center path.rLineTo(-harrowL / 2, harrowL / 2); path.rLineTo(hpartArrowL, 0); path.rLineTo(0, h); } public void setOpenedColor(int opened){ if(opened == 0){ paintRouteDirection.setColor(Color.rgb(100, 0, 255)); } else if(opened == -1){ paintRouteDirection.setColor(Color.rgb(150, 150, 150)); } else { paintRouteDirection.setColor(Color.rgb(220, 100, 80)); } } public void setAngle(float angle){ this.angle = angle; } @Override public void draw(Canvas canvas) { canvas.rotate(angle, width/2, height/2); canvas.drawPath(path, paintRouteDirection); } @Override public int getOpacity() { return 0; } @Override public void setAlpha(int alpha) { paintRouteDirection.setAlpha(alpha); } @Override public void setColorFilter(ColorFilter cf) { paintRouteDirection.setColorFilter(cf); } } class AmenityAdapter extends ArrayAdapter<Amenity> { private AmenityFilter filter; private List<Amenity> originalAmenityList; AmenityAdapter(List<Amenity> list) { super(SearchPOIActivity.this, R.layout.searchpoi_list, list); originalAmenityList = new ArrayList<Amenity>(list); this.setNotifyOnChange(false); } public void setNewModel(List<Amenity> amenityList, String filter) { setNotifyOnChange(false); originalAmenityList = new ArrayList<Amenity>(amenityList); clear(); for (Amenity obj : amenityList) { add(obj); } getFilter().filter(filter); setNotifyOnChange(true); this.notifyDataSetChanged(); } public View getView(int position, View convertView, ViewGroup parent) { View row = convertView; if (row == null) { LayoutInflater inflater = getLayoutInflater(); row = inflater.inflate(R.layout.searchpoi_list, parent, false); } float[] mes = null; TextView label = (TextView) row.findViewById(R.id.poi_label); TextView distanceLabel = (TextView) row.findViewById(R.id.poidistance_label); ImageView icon = (ImageView) row.findViewById(R.id.poi_icon); Amenity amenity = getItem(position); if(location != null){ mes = new float[2]; LatLon l = amenity.getLocation(); Location.distanceBetween(l.getLatitude(), l.getLongitude(), location.getLatitude(), location.getLongitude(), mes); } String str = OsmAndFormatter.getPoiStringWithoutType(amenity, OsmandSettings.usingEnglishNames(settings)); label.setText(str); int opened = -1; if (amenity.getOpeningHours() != null) { List<OpeningHoursRule> rs = OpeningHoursParser.parseOpenedHours(amenity.getOpeningHours()); if (rs != null) { Calendar inst = Calendar.getInstance(); inst.setTimeInMillis(System.currentTimeMillis()); boolean work = false; for (OpeningHoursRule p : rs) { if (p.isOpenedForTime(inst)) { work = true; break; } } if (work) { opened = 0; } else { opened = 1; } } } if(location != null){ DirectionDrawable draw = new DirectionDrawable(); float a = heading != null ? heading : 0; draw.setAngle(mes[1] - a + 180); draw.setOpenedColor(opened); icon.setImageDrawable(draw); } else { if(opened == -1){ icon.setImageResource(R.drawable.poi); } else if(opened == 0){ icon.setImageResource(R.drawable.opened_poi); } else { icon.setImageResource(R.drawable.closed_poi); } } if(mes == null){ distanceLabel.setText(""); //$NON-NLS-1$ } else { distanceLabel.setText(" " + OsmAndFormatter.getFormattedDistance((int) mes[0], SearchPOIActivity.this)); //$NON-NLS-1$ } return (row); } @Override public Filter getFilter() { if (filter == null) { filter = new AmenityFilter(); } return filter; } private final class AmenityFilter extends Filter { @Override protected FilterResults performFiltering(CharSequence constraint) { FilterResults results = new FilterResults(); if (constraint == null || constraint.length() == 0) { results.values = originalAmenityList; results.count = originalAmenityList.size(); } else { String lowerCase = constraint.toString() .toLowerCase(); List<Amenity> filter = new ArrayList<Amenity>(); for (Amenity item : originalAmenityList) { String lower = OsmAndFormatter.getPoiStringWithoutType(item, OsmandSettings.usingEnglishNames(settings)).toLowerCase(); if(lower.indexOf(lowerCase) != -1){ filter.add(item); } } results.values = filter; results.count = filter.size(); } return results; } @SuppressWarnings("unchecked") @Override protected void publishResults(CharSequence constraint, FilterResults results) { clear(); for (Amenity item : (Collection<Amenity>) results.values) { add(item); } } } } }