// Created by plusminus on 12:47:27 - 26.10.2008 package org.androad.ui.sd; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.osmdroid.util.GeoPoint; import org.androad.R; import org.androad.adt.DBPOI; import org.androad.adt.UnitSystem; import org.androad.db.DBManager; import org.androad.db.DataBaseException; import org.androad.preferences.Preferences; import org.androad.sys.ors.adt.GeocodedAddress; import org.androad.sys.ors.adt.ds.ORSPOI; import org.androad.sys.ors.adt.ds.POIType; import org.androad.sys.ors.ds.DSRequester; import org.androad.sys.ors.exceptions.ORSException; import org.androad.sys.ors.lus.LUSRequester; import org.androad.ui.AndNavGPSActivity; import org.androad.ui.common.OnClickOnFocusChangedListenerAdapter; import org.androad.ui.map.OpenStreetDDMap; import org.androad.ui.sd.util.StreetInfoExtractor; import org.androad.util.UserTask; import android.app.ProgressDialog; import android.content.Context; import android.content.DialogInterface; import android.content.Intent; import android.content.DialogInterface.OnCancelListener; import android.media.MediaPlayer; import android.os.Bundle; import android.os.Parcel; import android.os.Parcelable; import android.util.Log; import android.util.TypedValue; import android.view.View; import android.view.ViewGroup; import android.widget.AdapterView; import android.widget.BaseAdapter; import android.widget.LinearLayout; import android.widget.ListView; import android.widget.TextView; import android.widget.Toast; import android.widget.AdapterView.OnItemClickListener; public class SDPOISearchList extends AndNavGPSActivity { // =========================================================== // Constants // =========================================================== public static final String EXTRAS_POISEARCH_MODE = "extras_poisearch_mode"; public static final int EXTRAS_POISEARCH_MODE_ORS_CATEGORY_SEARCH = 0; public static final int EXTRAS_POISEARCH_MODE_GOOGLE_FREEFORM_SEARCH = EXTRAS_POISEARCH_MODE_ORS_CATEGORY_SEARCH + 1; public static final String EXTRAS_POISEARCH_CATEGORY = "EXTRAS_POISEARCH_CATEGORY"; public static final String EXTRAS_POISEARCH_RADIUS = "EXTRAS_POISEARCH_RADIUS_ID"; public static final String EXTRAS_POISEARCH_QUERY = "EXTRAS_POISEARCH_QUERY"; public static final int POISEARCH_RADIUS_GLOBAL = -1; protected static final int CONTEXTMENU_NAVTO_ITEM = 0; protected static final int CONTEXTMENU_CANCEL_ITEM = CONTEXTMENU_NAVTO_ITEM + 1; protected static final int REQUESTCODE_DDMAP = 0x1337; private static final String STATE_RESOLVEITEMS_ID = "state_resolveitems_id"; // =========================================================== // Fields // =========================================================== protected Bundle bundleCreatedWith; protected ListView mFoundPOIList; private UnitSystem mUnitSystem; private ArrayList<POIItem> mResolvedItems = new ArrayList<POIItem>(); private boolean mResolvingFinished = false; // =========================================================== // Constructors // =========================================================== /** Called when the activity is first created. */ @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); Preferences.applySharedSettings(this); this.setContentView(R.layout.sd_poi_searchlist); this.mUnitSystem = Preferences.getUnitSystem(this); /* * Save the Extras Bundle of the Intent this Activity was created with, * because it contains the Information, that will finally be used for a * GeoCode API. */ this.bundleCreatedWith = this.getIntent().getExtras(); this.mFoundPOIList = (ListView) this.findViewById(R.id.list_poi_searchlist); final TextView empty = new TextView(this); empty.setText(R.string.list_empty); this.mFoundPOIList.setEmptyView(empty); this.applyTopMenuButtonListeners(); if(savedInstanceState == null) { resolvePOIItemsAsync(); } } private void resolvePOIItemsAsync() { final int aSearchRadius = this.bundleCreatedWith.getInt(EXTRAS_POISEARCH_RADIUS); final int mode = this.bundleCreatedWith.getInt(EXTRAS_POISEARCH_MODE); switch(mode){ case EXTRAS_POISEARCH_MODE_GOOGLE_FREEFORM_SEARCH: searchFreeFormPOI(this.bundleCreatedWith.getString(EXTRAS_POISEARCH_QUERY), aSearchRadius); initListViewListener(); break; case EXTRAS_POISEARCH_MODE_ORS_CATEGORY_SEARCH: searchCategoryPOI(POIType.fromRawName(this.bundleCreatedWith.getString(EXTRAS_POISEARCH_CATEGORY)), aSearchRadius); initListViewListener(); break; } } protected void initListViewListener() { this.mFoundPOIList.setOnItemClickListener(new OnItemClickListener(){ @Override public void onItemClick(final AdapterView<?> parent, final View v, final int position, final long id) { final POIItem p = (POIItem)parent.getAdapter().getItem(position); final int latE6 = p.getLatitudeE6(); final int lonE6 = p.getLongitudeE6(); final String query = SDPOISearchList.this.bundleCreatedWith.getString(EXTRAS_POISEARCH_QUERY); try { DBManager.addPOIToHistory(SDPOISearchList.this, query, latE6, lonE6); DBManager.addPOIToHistory(SDPOISearchList.this, p.getName(), latE6, lonE6); DBManager.addFavorite(SDPOISearchList.this, query, latE6, lonE6); } catch (final DataBaseException e) { Log.e(DEBUGTAG, "DBError", e); } final int mode = SDPOISearchList.this.bundleCreatedWith.getInt(MODE_SD); switch(mode){ case MODE_SD_DESTINATION: final Intent directIntent = new Intent(SDPOISearchList.this, OpenStreetDDMap.class); final Bundle b = new Bundle(); b.putInt(EXTRAS_MODE, EXTRAS_MODE_DIRECT_LATLNG); b.putInt(EXTRAS_DESTINATION_LATITUDE_ID, p.getLatitudeE6()); b.putInt(EXTRAS_DESTINATION_LONGITUDE_ID, p.getLongitudeE6()); b.putString(EXTRAS_DESTINATION_TITLE, p.getName()); directIntent.putExtras(b); SDPOISearchList.this.startActivityForResult(directIntent, REQUESTCODE_DDMAP); break; case MODE_SD_WAYPOINT: case MODE_SD_RESOLVE: final Intent resultData = new Intent(); SDPOISearchList.this.bundleCreatedWith.putInt(EXTRAS_MODE, EXTRAS_MODE_DIRECT_LATLNG); SDPOISearchList.this.bundleCreatedWith.putInt(EXTRAS_DESTINATION_LATITUDE_ID, p.getLatitudeE6()); SDPOISearchList.this.bundleCreatedWith.putInt(EXTRAS_DESTINATION_LONGITUDE_ID, p.getLongitudeE6()); SDPOISearchList.this.bundleCreatedWith.putString(EXTRAS_DESTINATION_TITLE, p.getName()); resultData.putExtras(SDPOISearchList.this.bundleCreatedWith); SDPOISearchList.this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_SUCCESS, resultData); SDPOISearchList.this.finish(); break; } } }); } // =========================================================== // Getter & Setter // =========================================================== // =========================================================== // Methods from SuperClass/Interfaces // =========================================================== @Override public void onSaveInstanceState(final Bundle out) { if(this.mResolvingFinished) { out.putParcelableArrayList(STATE_RESOLVEITEMS_ID, this.mResolvedItems); } } @Override protected void onRestoreInstanceState(final Bundle in) { final ArrayList<POIItem> restoredItems = in.getParcelableArrayList(STATE_RESOLVEITEMS_ID); if(restoredItems == null){ resolvePOIItemsAsync(); }else{ this.mResolvedItems = restoredItems; final POIListAdapter gla = new POIListAdapter(this); gla.setListItems(this.mResolvedItems); this.mFoundPOIList.setAdapter(gla); this.mResolvingFinished = true; } } @Override protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { switch(resultCode){ case SUBACTIVITY_RESULTCODE_CHAINCLOSE_SUCCESS: this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_SUCCESS, data); this.finish(); break; case SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED: this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED, data); this.finish(); break; } /* Finally call the super()-method. */ super.onActivityResult(requestCode, resultCode, data); } // =========================================================== // Methods // =========================================================== private void searchCategoryPOI(final POIType type, final int aSearchRadius) { final POIListAdapter pla = new POIListAdapter(SDPOISearchList.this); final UserTask<Void, Integer, Void> ut; final ProgressDialog pd = ProgressDialog.show(this, getString(R.string.please_wait_a_moment), getString(R.string.toast_sd_poi_getting_gps_and_poi_search), true, true); ut = new UserTask<Void, Integer, Void>(){ @Override public Void doInBackground(final Void... params) { try{ if(!ensureLocationExists()){ Toast.makeText(SDPOISearchList.this, R.string.location_could_not_be_resolved , Toast.LENGTH_LONG).show(); runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); } }); return null; } final GeoPoint center = SDPOISearchList.super.getCurrentLocationAsGeoPoint(); final DSRequester ds = Preferences.getORSServer(SDPOISearchList.this).DIRECTORYSERVICE; final ArrayList<ORSPOI> orspois = ds.request(SDPOISearchList.this, center, type, aSearchRadius); for (final ORSPOI orspoi : orspois){ String name = orspoi.getName(); if(name.length() == 0){ name = getString(R.string.unknown); } SDPOISearchList.this.mResolvedItems.add(new POIItem(orspoi.getDistance(), name, orspoi.getGeoPoint(), SDPOISearchList.this.mUnitSystem)); } runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); if(SDPOISearchList.this.mResolvedItems.size() == 0){ final String[] distStringParts = SDPOISearchList.this.mUnitSystem.getDistanceString(aSearchRadius, null); final String distString = distStringParts[UnitSystem.DISTSTRINGS_DIST_ID] + distStringParts[UnitSystem.DISTSTRINGS_UNIT_ID]; Toast.makeText(SDPOISearchList.this, getString(R.string.toast_sd_poi_could_not_find, getString(type.READABLENAMERESID), distString), Toast.LENGTH_LONG).show(); }else{ Toast.makeText(SDPOISearchList.this, getString(R.string.whereami_search_places_found) + " " + SDPOISearchList.this.mResolvedItems.size(), Toast.LENGTH_SHORT).show(); } } }); /* Adapt the list to the Adapter. */ pla.setListItems(SDPOISearchList.this.mResolvedItems);/* Orders by name, ascending. */ SDPOISearchList.this.mResolvingFinished = true; } catch (final Exception e) { runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); // TODO GLobal beachten! final String[] distStringParts = SDPOISearchList.this.mUnitSystem.getDistanceString(aSearchRadius, null); final String distString = distStringParts[UnitSystem.DISTSTRINGS_DIST_ID] + distStringParts[UnitSystem.DISTSTRINGS_UNIT_ID]; Toast.makeText(SDPOISearchList.this, getString(R.string.toast_sd_poi_could_not_find, getString(type.READABLENAMERESID), distString), Toast.LENGTH_LONG).show(); } }); // Log.e(DEBUGTAG, "GeocodeError", e); } return null; } @Override public void onPostExecute(final Void result) { /* Adapt the Adapter to the ListView. */ SDPOISearchList.this.mFoundPOIList.setAdapter(pla); try{ pd.dismiss(); }catch(final IllegalArgumentException ia){ // Nothing } } }; pd.setOnCancelListener(new OnCancelListener(){ @Override public void onCancel(final DialogInterface d) { d.dismiss(); if(ut != null) { ut.cancel(true); } } }); ut.execute(); } /** * Searches in a Thread --> Unblocking. * @param query * @param which 0 for GLOBAL , 1 for VISIBLE RECTANGLE! */ private void searchFreeFormPOI(final String query, final int aSearchRadius) { final POIListAdapter pla = new POIListAdapter(SDPOISearchList.this); final UserTask<Void, Integer, Void> ut; final ProgressDialog pd = ProgressDialog.show(this, getString(R.string.please_wait_a_moment), getString(R.string.toast_sd_poi_getting_gps_and_poi_search)); ut = new UserTask<Void, Integer, Void>(){ @Override public Void doInBackground(final Void... params) { try { if(!ensureLocationExists()){ runOnUiThread(new Runnable(){ @Override public void run() { Toast.makeText(SDPOISearchList.this, R.string.location_could_not_be_resolved , Toast.LENGTH_LONG).show(); } }); return null; } final GeoPoint center = SDPOISearchList.super.getCurrentLocationAsGeoPoint(); final List<GeocodedAddress> ret; final int searchdist = aSearchRadius; final LUSRequester lus = Preferences.getORSServer(SDPOISearchList.this).LOCATIONUTILITYSERVICE; ret = lus.requestFreeformAddress(SDPOISearchList.this, null, query); runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); if(ret == null || ret.size() == 0){ Toast.makeText(SDPOISearchList.this, R.string.whereami_search_no_places_found, Toast.LENGTH_SHORT).show(); }else{ for (final GeocodedAddress a : ret){ try{ if(a.getLatitudeE6() != 0 && a.getLongitudeE6() != 0){ final int distanceToCenter = center.distanceTo(a); if(searchdist == SDPOISearchList.POISEARCH_RADIUS_GLOBAL || distanceToCenter < searchdist){ final String description; final String cityInfo = StreetInfoExtractor.constructCityLineInfo(SDPOISearchList.this, a.getMunicipality(), a.getPostalCode(), a.getNationality()); if(cityInfo.length() > 0) { description = cityInfo; } else { description = StreetInfoExtractor.constructStreetLineInfo(SDPOISearchList.this, a.getStreetNameOfficial(), a.getStreetNumber(), a.getNationality()); } SDPOISearchList.this.mResolvedItems.add(new POIItem(distanceToCenter, description, a, SDPOISearchList.this.mUnitSystem)); } } }catch(final Exception e){ } } if(SDPOISearchList.this.mResolvedItems.size() == 0){ Toast.makeText(SDPOISearchList.this, R.string.whereami_search_no_places_found, Toast.LENGTH_SHORT).show(); }else{ Toast.makeText(SDPOISearchList.this, getString(R.string.whereami_search_places_found) + " " + SDPOISearchList.this.mResolvedItems.size(), Toast.LENGTH_SHORT).show(); } } } }); /* Adapt the list to the Adapter. */ pla.setListItems(SDPOISearchList.this.mResolvedItems);/* Orders by name, ascending. */ SDPOISearchList.this.mResolvingFinished = true; } catch (final ORSException e) { runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); Toast.makeText(SDPOISearchList.this, e.getErrors().get(0).toUserString(), Toast.LENGTH_SHORT).show(); } }); // Log.e(DEBUGTAG, "GeocodeError", e); } catch (final Exception e) { runOnUiThread(new Runnable(){ @Override public void run() { pd.dismiss(); Toast.makeText(SDPOISearchList.this, R.string.whereami_search_no_places_found, Toast.LENGTH_SHORT).show(); // TODO Wrong String? } }); // Log.e(DEBUGTAG, "GeocodeError", e); } return null; } @Override public void onPostExecute(final Void result) { /* Adapt the Adapter to the ListView. */ SDPOISearchList.this.mFoundPOIList.setAdapter(pla); try{ pd.dismiss(); }catch(final IllegalArgumentException ia){ // Nothing } } }; pd.setOnCancelListener(new OnCancelListener(){ @Override public void onCancel(final DialogInterface d) { d.dismiss(); if(ut != null) { ut.cancel(true); } } }); ut.execute(); } protected void applyTopMenuButtonListeners() { /* Set Listener for Back-Button. */ new OnClickOnFocusChangedListenerAdapter(this.findViewById(R.id.ibtn_sd_poi_searchlist_back)) { @Override public void onClicked(final View me) { if (SDPOISearchList.super.mMenuVoiceEnabled) { MediaPlayer.create(SDPOISearchList.this, R.raw.close).start(); } /* Back one level. */ SDPOISearchList.this.setResult(SUBACTIVITY_RESULTCODE_UP_ONE_LEVEL); SDPOISearchList.this.finish(); } }; /* Set Listener for Close-Button. */ new OnClickOnFocusChangedListenerAdapter(this.findViewById(R.id.ibtn_sd_poi_searchlist_close)) { @Override public void onClicked(final View me) { if (SDPOISearchList.super.mMenuVoiceEnabled) { MediaPlayer.create(SDPOISearchList.this, R.raw.close).start(); } /* * Set ResultCode that the calling activity knows that we want * to go back to the Base-Menu */ SDPOISearchList.this.setResult(SUBACTIVITY_RESULTCODE_CHAINCLOSE_QUITTED); SDPOISearchList.this.finish(); } }; } private boolean ensureLocationExists() { int tryCount = 30; do{ tryCount--; /* Refresh own GPS position. */ if((SDPOISearchList.super.mMyLocation = SDPOISearchList.this.mLocationManager.getLastKnownLocation(PROVIDER_NAME)) == null){ try { Thread.sleep(1000); } catch (final InterruptedException e) { } } }while(SDPOISearchList.super.mMyLocation == null && tryCount >= 0); return SDPOISearchList.super.mMyLocation != null; } // =========================================================== // Inner and Anonymous Classes // =========================================================== private static class POIItem extends DBPOI implements Comparable<POIItem>, Parcelable { protected final int mDistanceToUserLocation; protected final String mDistanceToUserLocationTextual; protected static UnitSystem mUnitSystem; // WORKAROUND, because we need a reference for unparceling, as this is a static class private POIItem(final int distanceToUserLocation, final String description, final GeoPoint geoPoint, final UnitSystem us) { super(description, geoPoint); mUnitSystem = us; this.mDistanceToUserLocation = distanceToUserLocation; final String[] distStringParts = us.getDistanceString(distanceToUserLocation, null); final String distString = distStringParts[UnitSystem.DISTSTRINGS_DIST_ID] + distStringParts[UnitSystem.DISTSTRINGS_UNIT_ID]; this.mDistanceToUserLocationTextual = distString; } @Override public int compareTo(final POIItem another) { return this.mDistanceToUserLocation - another.mDistanceToUserLocation; } // =========================================================== // Parcelable // =========================================================== public static final Parcelable.Creator<POIItem> CREATOR = new Parcelable.Creator<POIItem>() { public POIItem createFromParcel(final Parcel in) { return readFromParcel(in); } public POIItem[] newArray(final int size) { return new POIItem[size]; } }; @Override public int describeContents() { return 0; } @Override public void writeToParcel(final Parcel out, final int flags) { out.writeInt(this.getLatitudeE6()); out.writeInt(this.getLongitudeE6()); out.writeInt(this.mDistanceToUserLocation); out.writeString(this.mName); out.writeInt(mUnitSystem.ordinal()); } private static POIItem readFromParcel(final Parcel in){ final GeoPoint geoPoint = new GeoPoint(in.readInt(), in.readInt()); final int distanceToUserLocation = in.readInt(); final String description = in.readString(); final int unitSystemOrdinal = in.readInt(); return new POIItem(distanceToUserLocation, description, geoPoint, UnitSystem.values()[unitSystemOrdinal]); } } private class POIListItemView extends LinearLayout{ private final TextView mTVDescription; private final TextView mTVDistance; // private ImageView mIcon; public POIListItemView(final Context context, final POIItem aPOIItem) { super(context); this.setOrientation(HORIZONTAL); this.mTVDistance = new TextView(context); this.mTVDistance.setText(aPOIItem.mDistanceToUserLocationTextual); this.mTVDistance.setTextSize(TypedValue.COMPLEX_UNIT_PX, 16); this.mTVDistance.setPadding(10,0,20,0); addView(this.mTVDistance, new LinearLayout.LayoutParams(90, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); this.mTVDescription = new TextView(context); this.mTVDescription.setText(aPOIItem.getName()); this.mTVDescription.setTextSize(TypedValue.COMPLEX_UNIT_PX, 24); addView(this.mTVDescription, new LinearLayout.LayoutParams(android.view.ViewGroup.LayoutParams.WRAP_CONTENT, android.view.ViewGroup.LayoutParams.WRAP_CONTENT)); } } private class POIListAdapter extends BaseAdapter{ /** Remember our context so we can use it when constructing views. */ private final Context mContext; private List<POIItem> mItems = new ArrayList<POIItem>(); public POIListAdapter(final Context context) { this.mContext = context; } public void addItem(final POIItem it) { this.mItems.add(it); Collections.sort(this.mItems); } @Override public boolean isEmpty() { return this.mItems == null || this.mItems.size() == 0; } public void setListItems(final List<POIItem> lit) { this.mItems = lit; Collections.sort(this.mItems); } /** @return The number of items in the */ public int getCount() { return this.mItems.size(); } public Object getItem(final int position) { return this.mItems.get(position); } @Override public long getItemId(final int position) { return position; } @Override public View getView(final int position, final View convertView, final ViewGroup parent) { POIListItemView btv; if (convertView == null) { btv = new POIListItemView(this.mContext, this.mItems.get(position)); } else { // Reuse/Overwrite the View passed // We are assuming(!) that it is castable! btv = (POIListItemView) convertView; btv.mTVDescription.setText(this.mItems.get(position).getName()); btv.mTVDistance.setText( this.mItems.get(position).mDistanceToUserLocationTextual); } return btv; } } @Override protected void onLocationChanged() { // Nothing } @Override protected void onLocationLost() { } }