diff options
| author | Samuel Tardieu <sam@rfc1149.net> | 2011-09-16 14:36:28 +0200 |
|---|---|---|
| committer | Samuel Tardieu <sam@rfc1149.net> | 2011-09-16 14:36:28 +0200 |
| commit | 579ef7a535489d4aa632db11667a3b01deb6cafd (patch) | |
| tree | 55810021c02ac7d80d3a9702ef0b59e4af154b9c /main/src/cgeo | |
| parent | 96ea21fd50334479c262da692038965d0e4d596a (diff) | |
| download | cgeo-579ef7a535489d4aa632db11667a3b01deb6cafd.zip cgeo-579ef7a535489d4aa632db11667a3b01deb6cafd.tar.gz cgeo-579ef7a535489d4aa632db11667a3b01deb6cafd.tar.bz2 | |
Move sources into the main directory
This prepares the inclusion of tests into the same repository.
Diffstat (limited to 'main/src/cgeo')
181 files changed, 36856 insertions, 0 deletions
diff --git a/main/src/cgeo/geocaching/CookieJar.java b/main/src/cgeo/geocaching/CookieJar.java new file mode 100644 index 0000000..dc0a38c --- /dev/null +++ b/main/src/cgeo/geocaching/CookieJar.java @@ -0,0 +1,96 @@ +package cgeo.geocaching; + +import org.apache.commons.lang3.StringUtils; + +import android.content.SharedPreferences; +import android.util.Log; + +import java.net.URLConnection; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +/** + * Handle cookies obtained from web sites. + * + * No other place should touch cookies directly, as we want to make sure + * that the stored information is up-to-date. + * + */ +final public class CookieJar { + + static private boolean cookiesLoaded = false; + final static private HashMap<String, String> cookies = new HashMap<String, String>(); + + static private String cache = null; // Cache information, or null if it has been invalidated + + static private void loadCookiesIfNeeded(final SharedPreferences prefs) { + if (!cookiesLoaded) { + cookies.clear(); + for (final Map.Entry<String, ?> entry : prefs.getAll().entrySet()) { + if (entry.getKey().startsWith("cookie_")) { + cookies.put(entry.getKey().substring(7), (String) entry.getValue()); + } + } + cookiesLoaded = true; + } + } + + static public synchronized void setCookie(final SharedPreferences prefs, final String name, final String value) { + loadCookiesIfNeeded(prefs); + if (!cookies.containsKey(name) || cookies.get(name) != value) { + final SharedPreferences.Editor editor = prefs.edit(); + cookies.put(name, value); + editor.putString(name, value); + cache = null; + editor.commit(); + } + } + + static public synchronized void setCookie(final SharedPreferences prefs, final String headerField) { + final int semiIndex = headerField.indexOf(';'); + final String cookie = semiIndex == -1 ? headerField : headerField.substring(0, semiIndex); + final int equalIndex = headerField.indexOf('='); + if (equalIndex > 0) { + setCookie(prefs, cookie.substring(0, equalIndex), cookie.substring(equalIndex + 1)); + } else { + Log.w(cgSettings.tag, "CookieJar.setCookie: ignoring header " + headerField); + } + } + + static public synchronized void setCookies(final SharedPreferences prefs, final URLConnection uc) { + final Map<String, List<String>> headers = uc.getHeaderFields(); + for (final Map.Entry<String, List<String>> entry : headers.entrySet()) { + if (entry.getKey().equalsIgnoreCase("set-cookie")) { + for (final String field : entry.getValue()) { + setCookie(prefs, field); + } + } + } + } + + static public synchronized String getCookiesAsString(final SharedPreferences prefs) { + if (cache == null) { + loadCookiesIfNeeded(prefs); + final ArrayList<String> built = new ArrayList<String>(); + for (final Map.Entry<String, String> entry : cookies.entrySet()) { + built.add(entry.getKey() + "=" + entry.getValue()); + } + cache = StringUtils.join(built, ';'); + } + return cache; + } + + static public synchronized void deleteCookies(final SharedPreferences prefs) { + loadCookiesIfNeeded(prefs); + final SharedPreferences.Editor editor = prefs.edit(); + for (final String key : cookies.keySet()) { + editor.remove("cookie_" + key); + } + editor.commit(); + cookies.clear(); + cache = ""; + } + +} diff --git a/main/src/cgeo/geocaching/ICache.java b/main/src/cgeo/geocaching/ICache.java new file mode 100644 index 0000000..24dcd7c --- /dev/null +++ b/main/src/cgeo/geocaching/ICache.java @@ -0,0 +1,101 @@ +/** + * + */ +package cgeo.geocaching; + +import cgeo.geocaching.enumerations.CacheSize; + +/** + * Basic interface for caches + * + * @author blafoo + * + */ +public interface ICache { + + /** + * @return Geocode like GCxxxx + */ + public String getGeocode(); + + /** + * @return Tradi, multi etc. + */ + public String getType(); + + /** + * @return Displayed owner, might differ from the real owner + */ + public String getOwner(); + + /** + * @return GC username of the owner + */ + public String getOwnerReal(); + + /** + * @return Micro, small etc. + */ + public CacheSize getSize(); + + /** + * @return Difficulty assessment + */ + public Float getDifficulty(); + + /** + * @return Terrain assessment + */ + public Float getTerrain(); + + /** + * @return Latitude, e.g. N 52° 12.345 + */ + public String getLatitude(); + + /** + * @return Longitude, e.g. E 9° 34.567 + */ + public String getLongitude(); + + /** + * @return true if the cache is disabled, false else + */ + public boolean isDisabled(); + + /** + * @return true if the user is the owner of the cache, false else + */ + public boolean isOwn(); + + /** + * @return true is the cache is archived, false else + */ + public boolean isArchived(); + + /** + * @return true is the cache is a Premium Member cache only, false else + */ + public boolean isMembersOnly(); + + /** + * @return Decrypted hint + */ + public String getHint(); + + /** + * @return Description + */ + public String getDescription(); + + /** + * @return Short Description + */ + public String getShortDescription(); + + /** + * @return Name + */ + public String getName(); + +} diff --git a/main/src/cgeo/geocaching/LogTemplateProvider.java b/main/src/cgeo/geocaching/LogTemplateProvider.java new file mode 100644 index 0000000..4119b37 --- /dev/null +++ b/main/src/cgeo/geocaching/LogTemplateProvider.java @@ -0,0 +1,155 @@ +package cgeo.geocaching; + +import org.apache.commons.lang3.StringUtils; + +import android.util.Log; + +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * provides all the available templates for logging + * + */ +public class LogTemplateProvider { + public static abstract class LogTemplate { + private String template; + private int resourceId; + + protected LogTemplate(String template, int resourceId) { + this.template = template; + this.resourceId = resourceId; + } + + abstract String getValue(cgBase base, boolean offline); + + public int getResourceId() { + return resourceId; + } + + public int getItemId() { + return template.hashCode(); + } + + public String getTemplateString() { + return template; + } + + protected String apply(String input, cgBase base, boolean offline) { + if (input.contains("[" + template + "]")) { + return input.replaceAll("\\[" + template + "\\]", getValue(base, offline)); + } + return input; + } + } + + private static LogTemplate[] templates; + + public static LogTemplate[] getTemplates() { + if (templates == null) { + templates = new LogTemplate[] { + new LogTemplate("DATE", R.string.init_signature_template_date) { + + @Override + String getValue(final cgBase base, final boolean offline) { + return base.formatFullDate(System.currentTimeMillis()); + } + }, + new LogTemplate("TIME", R.string.init_signature_template_time) { + + @Override + String getValue(final cgBase base, final boolean offline) { + return base.formatTime(System.currentTimeMillis()); + } + }, + new LogTemplate("DATETIME", R.string.init_signature_template_datetime) { + + @Override + String getValue(final cgBase base, final boolean offline) { + final long currentTime = System.currentTimeMillis(); + return base.formatFullDate(currentTime) + " " + base.formatTime(currentTime); + } + }, + new LogTemplate("USER", R.string.init_signature_template_user) { + + @Override + String getValue(final cgBase base, final boolean offline) { + return base.getUserName(); + } + }, + new LogTemplate("NUMBER", R.string.init_signature_template_number) { + + @Override + String getValue(final cgBase base, final boolean offline) { + if (offline) { + return ""; + } + String findCount = ""; + final Map<String, String> params = new HashMap<String, String>(); + final String page = base.request(false, "www.geocaching.com", "/email/", "GET", params, false, false, false).getData(); + int current = parseFindCount(page); + + if (current >= 0) { + findCount = String.valueOf(current + 1); + } + return findCount; + } + } + }; + } + return templates; + } + + public static LogTemplate getTemplate(int itemId) { + for (LogTemplate template : getTemplates()) { + if (template.getItemId() == itemId) { + return template; + } + } + return null; + } + + public static String applyTemplates(String signature, cgBase base, boolean offline) { + if (signature == null) { + return ""; + } + String result = signature; + for (LogTemplate template : getTemplates()) { + result = template.apply(result, base, offline); + } + return result; + } + + private static int parseFindCount(String page) { + if (StringUtils.isBlank(page)) { + return -1; + } + + int findCount = -1; + + try { + final Pattern findPattern = Pattern.compile("<strong><img.+?icon_smile.+?title=\"Caches Found\" /> ([,\\d]+)", Pattern.CASE_INSENSITIVE); + final Matcher findMatcher = findPattern.matcher(page); + if (findMatcher.find()) { + if (findMatcher.groupCount() > 0) { + String count = findMatcher.group(1); + + if (count != null) { + if (count.length() == 0) { + findCount = 0; + } else { + findCount = Integer.parseInt(count.replaceAll(",", "")); + } + } + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.parseFindCount: " + e.toString()); + } + + return findCount; + } + +} diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java new file mode 100644 index 0000000..7932363 --- /dev/null +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -0,0 +1,190 @@ +package cgeo.geocaching; + +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.impl.client.DefaultHttpClient; + +import android.app.Activity; +import android.content.Context; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Locale; + +public class StaticMapsProvider { + private static final String MARKERS_URL = "http://cgeo.carnero.cc/_markers/"; + /** + * in my tests the "no image available" image had 5470 bytes, while "street only" maps had at least 20000 bytes + */ + private static final int MIN_MAP_IMAGE_BYTES = 6000; + + private static void downloadMapsInThread(final cgCache cache, String latlonMap, int edge, String waypoints) { + createStorageDirectory(cache); + + downloadMap(cache, 20, "satellite", 1, latlonMap, edge, waypoints); + downloadMap(cache, 18, "satellite", 2, latlonMap, edge, waypoints); + downloadMap(cache, 16, "roadmap", 3, latlonMap, edge, waypoints); + downloadMap(cache, 14, "roadmap", 4, latlonMap, edge, waypoints); + downloadMap(cache, 11, "roadmap", 5, latlonMap, edge, waypoints); + } + + private static void createStorageDirectory(final cgCache cache) { + File dir = new File(cgSettings.getStorage()); + if (dir.exists() == false) { + dir.mkdirs(); + } + dir = new File(getStaticMapsDirectory(cache)); + if (dir.exists() == false) { + dir.mkdirs(); + } + } + + private static String getStaticMapsDirectory(final cgCache cache) { + return cgSettings.getStorage() + cache.geocode; + } + + private static void downloadMap(cgCache cache, int zoom, String mapType, int level, String latlonMap, int edge, String waypoints) { + String mapUrl = "http://maps.google.com/maps/api/staticmap?center=" + latlonMap; + String markerUrl = getMarkerUrl(cache); + + String url = mapUrl + "&zoom=" + zoom + "&size=" + edge + "x" + edge + "&maptype=" + mapType + "&markers=icon%3A" + markerUrl + "%7C" + latlonMap + waypoints + "&sensor=false"; + + final String fileName = getStaticMapsDirectory(cache) + "/map_" + level; + HttpClient client = null; + HttpGet getMethod = null; + HttpResponse httpResponse = null; + HttpEntity entity = null; + BufferedHttpEntity bufferedEntity = null; + + boolean ok = false; + + for (int i = 0; i < 3; i++) { + if (i > 0) + Log.w(cgSettings.tag, "cgMapImg.getDrawable: Failed to download data, retrying. Attempt #" + (i + 1)); + + try { + client = new DefaultHttpClient(); + getMethod = new HttpGet(url); + httpResponse = client.execute(getMethod); + entity = httpResponse.getEntity(); + + // if image is to small, don't download and save, there is no map data for this zoom level + long contentSize = entity.getContentLength(); + if (contentSize > 0 && contentSize <= MIN_MAP_IMAGE_BYTES) { + break; + } + + bufferedEntity = new BufferedHttpEntity(entity); + if (bufferedEntity != null) { + InputStream is = (InputStream) bufferedEntity.getContent(); + FileOutputStream fos = new FileOutputStream(fileName); + + int fileSize = 0; + try { + byte[] buffer = new byte[4096]; + int bytesRead; + while ((bytesRead = is.read(buffer)) != -1) { + fos.write(buffer, 0, bytesRead); + fileSize += bytesRead; + } + fos.flush(); + ok = true; + } catch (IOException e) { + Log.e(cgSettings.tag, "cgMapImg.getDrawable (saving to cache): " + e.toString()); + } finally { + is.close(); + fos.close(); + } + + bufferedEntity = null; + + // delete image if it has no contents + if (ok && fileSize < MIN_MAP_IMAGE_BYTES) { + (new File(fileName)).delete(); + } + } + + if (ok) { + break; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapImg.getDrawable (downloading from web): " + e.toString()); + } + } + } + + public static void downloadMaps(cgCache cache, cgSettings settings, Activity activity) { + if (settings.storeOfflineMaps != 1 || cache.coords == null || StringUtils.isBlank(cache.geocode)) { + return; + } + + final String latlonMap = String.format((Locale) null, "%.6f", cache.coords.getLatitude()) + "," + + String.format((Locale) null, "%.6f", cache.coords.getLongitude()); + final Display display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + final int maxWidth = display.getWidth() - 25; + final int maxHeight = display.getHeight() - 25; + int edge = 0; + if (maxWidth > maxHeight) { + edge = maxWidth; + } else { + edge = maxHeight; + } + + final StringBuilder waypoints = new StringBuilder(); + if (CollectionUtils.isNotEmpty(cache.waypoints)) { + for (cgWaypoint waypoint : cache.waypoints) { + if (waypoint.coords == null) { + continue; + } + + waypoints.append("&markers=icon%3A"); + waypoints.append(MARKERS_URL); + waypoints.append("marker_waypoint_"); + waypoints.append(waypoint.type); + waypoints.append(".png%7C"); + waypoints.append(String.format((Locale) null, "%.6f", waypoint.coords.getLatitude())); + waypoints.append(','); + waypoints.append(String.format((Locale) null, "%.6f", waypoint.coords.getLongitude())); + } + } + + // download map images in separate background thread for higher performance + downloadMaps(cache, latlonMap, edge, waypoints.toString()); + } + + private static void downloadMaps(final cgCache cache, final String latlonMap, final int edge, + final String waypoints) { + Thread staticMapsThread = new Thread("getting static map") { + @Override + public void run() { + downloadMapsInThread(cache, latlonMap, edge, waypoints); + } + }; + staticMapsThread.setPriority(Thread.MIN_PRIORITY); + staticMapsThread.start(); + } + + private static String getMarkerUrl(final cgCache cache) { + String type = "mystery"; + if (cache.found) { + type = cache.type + "_found"; + } else if (cache.disabled) { + type = cache.type + "_disabled"; + } else { + type = cache.type; + } + + return cgBase.urlencode_rfc3986(MARKERS_URL + "marker_cache_" + type + ".png"); + } +} diff --git a/main/src/cgeo/geocaching/UnknownTagsHandler.java b/main/src/cgeo/geocaching/UnknownTagsHandler.java new file mode 100644 index 0000000..6fab349 --- /dev/null +++ b/main/src/cgeo/geocaching/UnknownTagsHandler.java @@ -0,0 +1,33 @@ +package cgeo.geocaching; + +import org.xml.sax.XMLReader; + +import android.text.Editable; +import android.text.Html.TagHandler; +import android.text.Spannable; +import android.text.style.StrikethroughSpan; + +public class UnknownTagsHandler implements TagHandler { + + private static final int UNDEFINED_POSITION = -1; + int strikePos = UNDEFINED_POSITION; + + public void handleTag(boolean opening, String tag, Editable output, + XMLReader xmlReader) { + if (tag.equalsIgnoreCase("strike") || tag.equals("s")) { + handleStrike(opening, output); + } + } + + private void handleStrike(boolean opening, Editable output) { + int length = output.length(); + if (opening) { + strikePos = length; + } else { + if (strikePos > UNDEFINED_POSITION) { + output.setSpan(new StrikethroughSpan(), strikePos, length, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + strikePos = UNDEFINED_POSITION; + } + } + } +} diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java new file mode 100644 index 0000000..358aba3 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -0,0 +1,86 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgeoapplication; + +import android.app.Activity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; + +public abstract class AbstractActivity extends Activity implements IAbstractActivity { + + private String helpTopic; + + protected cgeoapplication app = null; + protected Resources res = null; + protected cgSettings settings = null; + protected cgBase base = null; + protected SharedPreferences prefs = null; + + protected AbstractActivity() { + this(null); + } + + protected AbstractActivity(final String helpTopic) { + this.helpTopic = helpTopic; + } + + final public void goHome(final View view) { + ActivityMixin.goHome(this); + } + + public void goManual(final View view) { + ActivityMixin.goManual(this, helpTopic); + } + + final public void setTitle(final String title) { + ActivityMixin.setTitle(this, title); + } + + final public void showProgress(final boolean show) { + ActivityMixin.showProgress(this, show); + } + + final public void setTheme() { + ActivityMixin.setTheme(this); + } + + public final void showToast(String text) { + ActivityMixin.showToast(this, text); + } + + public final void showShortToast(String text) { + ActivityMixin.showShortToast(this, text); + } + + public final void helpDialog(String title, String message) { + ActivityMixin.helpDialog(this, title, message); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + res = this.getResources(); + app = (cgeoapplication) this.getApplication(); + prefs = getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE); + settings = new cgSettings(this, prefs); + base = new cgBase(app, settings, prefs); + } + + final public cgSettings getSettings() { + return settings; + } + + public void addVisitMenu(Menu menu, cgCache cache) { + ActivityMixin.addVisitMenu(this, menu, cache); + } + +} diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java new file mode 100644 index 0000000..0afa69c --- /dev/null +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -0,0 +1,87 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgeoapplication; + +import android.app.ListActivity; +import android.content.Context; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Menu; +import android.view.View; + +public abstract class AbstractListActivity extends ListActivity implements + IAbstractActivity { + + private String helpTopic; + + protected cgeoapplication app = null; + protected Resources res = null; + protected cgSettings settings = null; + protected cgBase base = null; + protected SharedPreferences prefs = null; + + protected AbstractListActivity() { + this(null); + } + + protected AbstractListActivity(final String helpTopic) { + this.helpTopic = helpTopic; + } + + final public void goHome(View view) { + ActivityMixin.goHome(this); + } + + public void goManual(View view) { + ActivityMixin.goManual(this, helpTopic); + } + + final public void showProgress(final boolean show) { + ActivityMixin.showProgress(this, show); + } + + final public void setTheme() { + ActivityMixin.setTheme(this); + } + + public final void showToast(String text) { + ActivityMixin.showToast(this, text); + } + + public final void showShortToast(String text) { + ActivityMixin.showShortToast(this, text); + } + + public final void helpDialog(String title, String message) { + ActivityMixin.helpDialog(this, title, message); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + res = this.getResources(); + app = (cgeoapplication) this.getApplication(); + prefs = getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE); + settings = new cgSettings(this, prefs); + base = new cgBase(app, settings, prefs); + } + + final public void setTitle(final String title) { + ActivityMixin.setTitle(this, title); + } + + final public cgSettings getSettings() { + return settings; + } + + public void addVisitMenu(Menu menu, cgCache cache) { + ActivityMixin.addVisitMenu(this, menu, cache); + } + +} diff --git a/main/src/cgeo/geocaching/activity/ActivityMixin.java b/main/src/cgeo/geocaching/activity/ActivityMixin.java new file mode 100644 index 0000000..fd60962 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/ActivityMixin.java @@ -0,0 +1,150 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgeo; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Resources; +import android.view.Gravity; +import android.view.Menu; +import android.view.SubMenu; +import android.view.View; +import android.widget.ProgressBar; +import android.widget.TextView; +import android.widget.Toast; + +import gnu.android.app.appmanualclient.AppManualReaderClient; + +import java.util.List; + +public final class ActivityMixin { + private static final int MENU_ICON_LOG_VISIT = android.R.drawable.ic_menu_edit; + + public final static void goHome(final Activity fromActivity) { + final Intent intent = new Intent(fromActivity, cgeo.class); + intent.setFlags(Intent.FLAG_ACTIVITY_CLEAR_TOP); + + fromActivity.startActivity(intent); + fromActivity.finish(); + } + + public final static void goManual(final Context context, final String helpTopic) { + if (StringUtils.isBlank(helpTopic)) { + return; + } + try { + AppManualReaderClient.openManual( + "c-geo", + helpTopic, + context, + "http://manual.cgeo.org/"); + } catch (Exception e) { + // nothing + } + } + + public final static void setTitle(final Activity activity, final String text) { + if (StringUtils.isBlank(text)) { + return; + } + + final TextView title = (TextView) activity.findViewById(R.id.actionbar_title); + if (title != null) { + title.setText(text); + } + } + + public final static void showProgress(final Activity activity, final boolean show) { + if (activity == null) { + return; + } + + final ProgressBar progress = (ProgressBar) activity.findViewById(R.id.actionbar_progress); + if (show) { + progress.setVisibility(View.VISIBLE); + } else { + progress.setVisibility(View.GONE); + } + } + + public final static void setTheme(final Activity activity) { + cgSettings settings = new cgSettings(activity, activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE)); + if (settings.skin == 1) { + activity.setTheme(R.style.light); + } else { + activity.setTheme(R.style.dark); + } + } + + public final static void showToast(final Activity activity, final String text) { + if (StringUtils.isNotBlank(text)) { + Toast toast = Toast.makeText(activity, text, Toast.LENGTH_LONG); + + toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 100); + toast.show(); + } + } + + public final static void showShortToast(final Activity activity, final String text) { + if (StringUtils.isNotBlank(text)) { + Toast toast = Toast.makeText(activity, text, Toast.LENGTH_SHORT); + + toast.setGravity(Gravity.CENTER_HORIZONTAL | Gravity.BOTTOM, 0, 100); + toast.show(); + } + } + + public static final void helpDialog(final Activity activity, final String title, final String message) { + if (StringUtils.isBlank(message)) { + return; + } + + AlertDialog.Builder dialog = new AlertDialog.Builder(activity); + dialog.setTitle(title); + dialog.setMessage(message); + dialog.setCancelable(true); + dialog.setNeutralButton("Ok", new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + } + + protected static void addVisitMenu(IAbstractActivity activity, Menu menu, cgCache cache) { + if (cache == null) { + return; + } + if (!cache.supportsLogging()) { + return; + } + cgSettings settings = activity.getSettings(); + Resources res = ((Activity) activity).getResources(); + if (settings.isLogin()) { + if (settings.getLogOffline()) { + SubMenu logMenu = menu.addSubMenu(1, IAbstractActivity.MENU_LOG_VISIT_OFFLINE, 0, res.getString(R.string.cache_menu_visit_offline)).setIcon(MENU_ICON_LOG_VISIT); + List<Integer> logTypes = cache.getPossibleLogTypes(settings); + for (Integer logType : logTypes) { + String label = cgBase.logTypes2.get(logType); + logMenu.add(1, IAbstractActivity.MENU_LOG_VISIT_OFFLINE + logType, 0, label); + } + logMenu.add(1, IAbstractActivity.MENU_LOG_VISIT, 0, res.getString(R.string.cache_menu_visit)); + } + else { + menu.add(1, IAbstractActivity.MENU_LOG_VISIT, 0, res.getString(R.string.cache_menu_visit)).setIcon(MENU_ICON_LOG_VISIT); + } + } + } + +} diff --git a/main/src/cgeo/geocaching/activity/IAbstractActivity.java b/main/src/cgeo/geocaching/activity/IAbstractActivity.java new file mode 100644 index 0000000..dac97f3 --- /dev/null +++ b/main/src/cgeo/geocaching/activity/IAbstractActivity.java @@ -0,0 +1,36 @@ +package cgeo.geocaching.activity; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; + +import android.view.Menu; +import android.view.View; + +public interface IAbstractActivity { + static final int MENU_LOG_VISIT = 100; + static final int MENU_LOG_VISIT_OFFLINE = 101; + + public void goHome(View view); + + public void goManual(View view); + + public void showProgress(final boolean show); + + public void setTheme(); + + public void showToast(String text); + + public void showShortToast(String text); + + public void helpDialog(String title, String message); + + public void setTitle(final String title); + + /** + * TODO: remove after settings are a singleton + */ + public cgSettings getSettings(); + + void addVisitMenu(Menu menu, cgCache cache); + +} diff --git a/main/src/cgeo/geocaching/apps/AbstractApp.java b/main/src/cgeo/geocaching/apps/AbstractApp.java new file mode 100644 index 0000000..f917563 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/AbstractApp.java @@ -0,0 +1,82 @@ +package cgeo.geocaching.apps; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.utils.CollectionUtils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; + +import java.util.List; + +public abstract class AbstractApp implements App { + + protected String packageName; + + private String intent; + private String name; + + protected AbstractApp(final String name, final String intent, + final String packageName) { + this.name = name; + this.intent = intent; + this.packageName = packageName; + } + + protected AbstractApp(final String name, final String intent) { + this(name, intent, null); + } + + protected Intent getLaunchIntent(Context context) { + if (packageName == null) { + return null; + } + PackageManager packageManager = context.getPackageManager(); + try { + // This can throw an exception where the exception type is only defined on API Level > 3 + // therefore surround with try-catch + Intent intent = packageManager.getLaunchIntentForPackage(packageName); + return intent; + } catch (Exception e) { + return null; + } + } + + public boolean isInstalled(final Context context) { + if (getLaunchIntent(context) != null) { + return true; + } + return isIntentAvailable(context, intent); + } + + private static boolean isIntentAvailable(Context context, String action) { + final Intent intent = new Intent(action); + + return isIntentAvailable(context, intent); + } + + protected static boolean isIntentAvailable(Context context, Intent intent) { + final PackageManager packageManager = context.getPackageManager(); + final List<ResolveInfo> list = packageManager.queryIntentActivities( + intent, PackageManager.MATCH_DEFAULT_ONLY); + + return (CollectionUtils.isNotEmpty(list)); + } + + @Override + public String getName() { + return name; + } + + @Override + public int getId() { + return getName().hashCode(); + } + + protected static cgSettings getSettings(Activity activity) { + return new cgSettings(activity, + activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE)); + } +} diff --git a/main/src/cgeo/geocaching/apps/AbstractAppFactory.java b/main/src/cgeo/geocaching/apps/AbstractAppFactory.java new file mode 100644 index 0000000..bb8b017 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/AbstractAppFactory.java @@ -0,0 +1,16 @@ +package cgeo.geocaching.apps; + +import android.view.MenuItem; + +public abstract class AbstractAppFactory { + + protected static App getAppFromMenuItem(MenuItem item, final App[] availableApps) { + int id = item.getItemId(); + for (App app : availableApps) { + if (app.getId() == id) { + return app; + } + } + return null; + } +} diff --git a/main/src/cgeo/geocaching/apps/AbstractLocusApp.java b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java new file mode 100644 index 0000000..3e79998 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/AbstractLocusApp.java @@ -0,0 +1,272 @@ +package cgeo.geocaching.apps; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.enumerations.WaypointType; + +import menion.android.locus.addon.publiclib.DisplayData; +import menion.android.locus.addon.publiclib.LocusUtils; +import menion.android.locus.addon.publiclib.geoData.Point; +import menion.android.locus.addon.publiclib.geoData.PointGeocachingData; +import menion.android.locus.addon.publiclib.geoData.PointGeocachingDataWaypoint; +import menion.android.locus.addon.publiclib.geoData.PointsData; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.location.Location; + +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.List; + +/** + * for the Locus API: + * + * @see http://forum.asamm.cz/viewtopic.php?f=29&t=767 + */ +public abstract class AbstractLocusApp extends AbstractApp { + private static final String INTENT = Intent.ACTION_VIEW; + private static final SimpleDateFormat ISO8601DATE = new SimpleDateFormat("yyyy-MM-dd'T'"); + + protected AbstractLocusApp(final Resources res) { + super(res.getString(R.string.caches_map_locus), INTENT); + } + + @Override + public boolean isInstalled(Context context) { + return LocusUtils.isLocusAvailable(context); + } + + /** + * Display a list of caches / waypoints in Locus + * + * @param objectsToShow + * which caches/waypoints to show + * @param withCacheWaypoints + * wether to give waypoints of caches to Locus or not + * @param activity + * @author koem + */ + protected void showInLocus(List<? extends Object> objectsToShow, boolean withCacheWaypoints, + Activity activity) { + if (objectsToShow == null) { + return; + } + + int pc = 0; // counter for points + PointsData pd = new PointsData("c:geo"); + for (Object o : objectsToShow) { + // get icon and Point + Point p = null; + if (o instanceof cgCache) { + p = getPoint((cgCache) o, withCacheWaypoints); + } else if (o instanceof cgWaypoint) { + p = getPoint((cgWaypoint) o); + } else { + continue; // no cache, no waypoint => ignore + } + if (p == null) { + continue; + } + + pd.addPoint(p); + ++pc; + } + + if (pc <= 1000) { + DisplayData.sendData(activity, pd, false); + } else { + ArrayList<PointsData> data = new ArrayList<PointsData>(); + data.add(pd); + DisplayData.sendDataCursor(activity, data, + "content://" + LocusDataStorageProvider.class.getCanonicalName().toLowerCase(), + false); + } + } + + /** + * This method constructs a <code>Point</code> for displaying in Locus + * + * @param cache + * @param withWaypoints + * whether to give waypoints to Locus or not + * @return null, when the <code>Point</code> could not be constructed + * @author koem + */ + private static Point getPoint(cgCache cache, boolean withWaypoints) { + if (cache == null || cache.coords == null) { + return null; + } + + // create one simple point with location + Location loc = new Location(cgSettings.tag); + loc.setLatitude(cache.coords.getLatitude()); + loc.setLongitude(cache.coords.getLongitude()); + + Point p = new Point(cache.name, loc); + PointGeocachingData pg = new PointGeocachingData(); + p.setGeocachingData(pg); + + // set data in Locus' cache + pg.cacheID = cache.geocode; + pg.available = !cache.disabled; + pg.archived = cache.archived; + pg.premiumOnly = cache.members; + pg.name = cache.name; + pg.placedBy = cache.owner; + if (cache.hidden != null) { + pg.hidden = ISO8601DATE.format(cache.hidden.getTime()); + } + int locusId = toLocusId(CacheType.FIND_BY_ID.get(cache.type)); + if (locusId != NO_LOCUS_ID) { + pg.type = locusId; + } + locusId = toLocusId(cache.size); + if (locusId != NO_LOCUS_ID) { + pg.container = locusId; + } + if (cache.difficulty != null) { + pg.difficulty = cache.difficulty; + } + if (cache.terrain != null) { + pg.terrain = cache.terrain; + } + pg.found = cache.found; + + if (withWaypoints && cache.waypoints != null) { + pg.waypoints = new ArrayList<PointGeocachingDataWaypoint>(); + for (cgWaypoint waypoint : cache.waypoints) { + if (waypoint == null || waypoint.coords == null) { + continue; + } + PointGeocachingDataWaypoint wp = new PointGeocachingDataWaypoint(); + wp.code = waypoint.geocode; + wp.name = waypoint.name; + String locusWpId = toLocusId(WaypointType.FIND_BY_ID.get(waypoint.type)); + if (locusWpId != null) { + wp.type = locusWpId; + } + wp.lat = waypoint.coords.getLatitude(); + wp.lon = waypoint.coords.getLongitude(); + pg.waypoints.add(wp); + } + } + + // Other properties of caches, not used yet. When there are many caches to be displayed + // in Locus, using these properties can lead to Exceptions in Locus. + // Examination necessary when to display and when not. E. g.: > 200 caches: don't display + // these properties. + + //pg.shortDescription = cache.shortdesc; + //pg.longDescription = cache.description; + //pg.encodedHints = cache.hint; + + return p; + } + + /** + * This method constructs a <code>Point</code> for displaying in Locus + * + * @param waypoint + * @return null, when the <code>Point</code> could not be constructed + * @author koem + */ + private static Point getPoint(cgWaypoint waypoint) { + if (waypoint == null || waypoint.coords == null) { + return null; + } + + // create one simple point with location + Location loc = new Location(cgSettings.tag); + loc.setLatitude(waypoint.coords.getLatitude()); + loc.setLongitude(waypoint.coords.getLongitude()); + + Point p = new Point(waypoint.name, loc); + p.setDescription("<a href=\"http://coord.info/" + waypoint.geocode + "\">" + + waypoint.geocode + "</a>"); + + return p; + } + + private static final int NO_LOCUS_ID = -1; + + private static int toLocusId(final CacheType ct) { + switch (ct) { + case TRADITIONAL: + return PointGeocachingData.CACHE_TYPE_TRADITIONAL; + case MULTI: + return PointGeocachingData.CACHE_TYPE_MULTI; + case MYSTERY: + return PointGeocachingData.CACHE_TYPE_MYSTERY; + case LETTERBOX: + return PointGeocachingData.CACHE_TYPE_LETTERBOX; + case EVENT: + return PointGeocachingData.CACHE_TYPE_EVENT; + case MEGA_EVENT: + return PointGeocachingData.CACHE_TYPE_MEGA_EVENT; + case EARTH: + return PointGeocachingData.CACHE_TYPE_EARTH; + case CITO: + return PointGeocachingData.CACHE_TYPE_CACHE_IN_TRASH_OUT; + case WEBCAM: + return PointGeocachingData.CACHE_TYPE_WEBCAM; + case VIRTUAL: + return PointGeocachingData.CACHE_TYPE_VIRTUAL; + case WHERIGO: + return PointGeocachingData.CACHE_TYPE_WHERIGO; + case PROJECT_APE: + return PointGeocachingData.CACHE_TYPE_PROJECT_APE; + case GPS_EXHIBIT: + return PointGeocachingData.CACHE_TYPE_GPS_ADVENTURE; + default: + return NO_LOCUS_ID; + } + } + + private static int toLocusId(final CacheSize cs) { + switch (cs) { + case MICRO: + return PointGeocachingData.CACHE_SIZE_MICRO; + case SMALL: + return PointGeocachingData.CACHE_SIZE_SMALL; + case REGULAR: + return PointGeocachingData.CACHE_SIZE_REGULAR; + case LARGE: + return PointGeocachingData.CACHE_SIZE_LARGE; + case NOT_CHOSEN: + return PointGeocachingData.CACHE_SIZE_NOT_CHOSEN; + case OTHER: + return PointGeocachingData.CACHE_SIZE_OTHER; + default: + return NO_LOCUS_ID; + } + } + + private static String toLocusId(final WaypointType wt) { + switch (wt) { + case FLAG: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_FINAL; + case OWN: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_STAGES; + case PKG: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_PARKING; + case PUZZLE: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_QUESTION; + case STAGE: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_STAGES; + case TRAILHEAD: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_TRAILHEAD; + case WAYPOINT: + return PointGeocachingData.CACHE_WAYPOINT_TYPE_STAGES; + default: + return null; + } + } + +} diff --git a/main/src/cgeo/geocaching/apps/App.java b/main/src/cgeo/geocaching/apps/App.java new file mode 100644 index 0000000..8241737 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/App.java @@ -0,0 +1,11 @@ +package cgeo.geocaching.apps; + +import android.content.Context; + +public interface App { + public boolean isInstalled(final Context context); + + public String getName(); + + int getId(); +} diff --git a/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java b/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java new file mode 100644 index 0000000..69a5e5b --- /dev/null +++ b/main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java @@ -0,0 +1,71 @@ +package cgeo.geocaching.apps; + +import menion.android.locus.addon.publiclib.geoData.PointsData; +import menion.android.locus.addon.publiclib.utils.DataCursor; +import menion.android.locus.addon.publiclib.utils.DataStorage; + +import android.content.ContentProvider; +import android.content.ContentValues; +import android.database.Cursor; +import android.net.Uri; +import android.os.Parcel; + +import java.util.ArrayList; + +/** + * code provided by menion - developer of Locus + */ +public class LocusDataStorageProvider extends ContentProvider { + + @Override + public Cursor query(Uri aUri, String[] aProjection, String aSelection, + String[] aSelectionArgs, String aSortOrder) { + + DataCursor cursor = new DataCursor(new String[] { "data" }); + + ArrayList<PointsData> data = DataStorage.getData(); + if (data == null || data.size() == 0) { + return cursor; + } + + for (int i = 0; i < data.size(); i++) { + // get byte array + Parcel par = Parcel.obtain(); + data.get(i).writeToParcel(par, 0); + byte[] byteData = par.marshall(); + // add to row + cursor.addRow(new Object[] { byteData }); + } + // data filled to cursor, clear reference to prevent some memory issue + DataStorage.clearData(); + // now finally return filled cursor + return cursor; + } + + @Override + public int delete(Uri uri, String selection, String[] selectionArgs) { + return 0; + } + + @Override + public String getType(Uri uri) { + return null; + } + + @Override + public Uri insert(Uri uri, ContentValues values) { + return null; + } + + @Override + public boolean onCreate() { + return false; + } + + @Override + public int update(Uri uri, ContentValues values, String selection, + String[] selectionArgs) { + return 0; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java b/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java new file mode 100644 index 0000000..8d0d5ae --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java @@ -0,0 +1,34 @@ +package cgeo.geocaching.apps.cache; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.apps.AbstractApp; + +import android.app.Activity; +import android.content.Intent; + +abstract class AbstractGeneralApp extends AbstractApp implements GeneralApp { + + protected AbstractGeneralApp(String name, String packageName) { + super(name, null); + this.packageName = packageName; + } + + @Override + public boolean isEnabled(cgCache cache) { + return true; + } + + @Override + public boolean invoke(Activity activity, cgCache cache) { + if (packageName == null) { + return false; + } + Intent intent = getLaunchIntent(activity); + if (intent != null) { + intent.setFlags(Intent.FLAG_ACTIVITY_REORDER_TO_FRONT); + activity.startActivity(intent); + return true; + } + return false; + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/GccApp.java b/main/src/cgeo/geocaching/apps/cache/GccApp.java new file mode 100644 index 0000000..06ce6c2 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/GccApp.java @@ -0,0 +1,11 @@ +package cgeo.geocaching.apps.cache; + +import cgeo.geocaching.R; + +import android.content.res.Resources; + +class GccApp extends AbstractGeneralApp implements GeneralApp { + GccApp(final Resources res) { + super(res.getString(R.string.cache_menu_gcc), "eisbehr.gcc"); + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/GeneralApp.java b/main/src/cgeo/geocaching/apps/cache/GeneralApp.java new file mode 100644 index 0000000..805d64c --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/GeneralApp.java @@ -0,0 +1,14 @@ +package cgeo.geocaching.apps.cache; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.apps.App; + +import android.app.Activity; + +interface GeneralApp extends App { + + boolean isEnabled(final cgCache cache); + + public boolean invoke(Activity activity, cgCache cache); + +} diff --git a/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java b/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java new file mode 100644 index 0000000..eb581ca --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java @@ -0,0 +1,48 @@ +package cgeo.geocaching.apps.cache; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.apps.AbstractAppFactory; + +import org.apache.commons.lang3.ArrayUtils; + +import android.app.Activity; +import android.content.res.Resources; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +public final class GeneralAppsFactory extends AbstractAppFactory { + private static GeneralApp[] apps = new GeneralApp[] {}; + + private static GeneralApp[] getGeneralApps(Resources res) { + if (ArrayUtils.isEmpty(apps)) { + apps = new GeneralApp[] { new GccApp(res), + new WhereYouGoApp(res) }; + } + return apps; + } + + public static void addMenuItems(Menu menu, Activity activity, + Resources res, cgCache cache) { + for (GeneralApp app : getGeneralApps(res)) { + if (app.isInstalled(activity) && app.isEnabled(cache)) { + menu.add(0, app.getId(), 0, app.getName()); + } + } + } + + public static boolean onMenuItemSelected(final MenuItem item, + Activity activity, cgCache cache) { + GeneralApp app = (GeneralApp) getAppFromMenuItem(item, apps); + if (app != null) { + try { + return app.invoke(activity, cache); + } catch (Exception e) { + Log.e(cgSettings.tag, "GeneralAppsFactory.onMenuItemSelected: " + e.toString()); + } + } + return false; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java new file mode 100644 index 0000000..b12a6d1 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java @@ -0,0 +1,17 @@ +package cgeo.geocaching.apps.cache; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; + +import android.content.res.Resources; + +class WhereYouGoApp extends AbstractGeneralApp implements GeneralApp { + WhereYouGoApp(Resources res) { + super(res.getString(R.string.cache_menu_whereyougo), "menion.android.whereyougo"); + } + + @Override + public boolean isEnabled(cgCache cache) { + return cache != null && cache.type != null && cache.type.equalsIgnoreCase("wherigo"); + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractInternalMap.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractInternalMap.java new file mode 100644 index 0000000..a1ffc89 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractInternalMap.java @@ -0,0 +1,15 @@ +package cgeo.geocaching.apps.cache.navi; + +import android.content.Context; + +abstract class AbstractInternalMap extends AbstractNavigationApp { + + protected AbstractInternalMap(String name, String intent) { + super(name, intent); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java new file mode 100644 index 0000000..3b10f1a --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java @@ -0,0 +1,15 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.apps.AbstractApp; + +abstract class AbstractNavigationApp extends AbstractApp implements NavigationApp { + + protected AbstractNavigationApp(String name, String intent, String packageName) { + super(name, intent, packageName); + } + + protected AbstractNavigationApp(String name, String intent) { + super(name, intent); + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java new file mode 100644 index 0000000..b60a635 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java @@ -0,0 +1,67 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.util.Log; + +import java.util.UUID; + +class GoogleMapsApp extends AbstractNavigationApp implements NavigationApp { + + GoogleMapsApp(final Resources res) { + super(res.getString(R.string.cache_menu_map_ext), null); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } + + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + if (cache == null && waypoint == null && coords == null) { + return false; + } + + try { + if (cache != null && cache.coords != null) { + startActivity(activity, cache.coords); + } else if (waypoint != null && waypoint.coords != null) { + startActivity(activity, waypoint.coords); + } else if (coords != null) { + startActivity(activity, coords); + } + + return true; + } catch (Exception e) { + // nothing + } + + Log.i(cgSettings.tag, "cgBase.runExternalMap: No maps application available."); + + if (res != null) { + ActivityMixin.showToast(activity, res.getString(R.string.err_application_no)); + } + + return false; + } + + private static void startActivity(Activity activity, final Geopoint coords) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("geo:" + coords.getLatitude() + "," + coords.getLongitude()))); + // INFO: q parameter works with Google Maps, but breaks cooperation with all other apps + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java new file mode 100644 index 0000000..2cd2b22 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java @@ -0,0 +1,102 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.util.Log; + +import java.util.UUID; + +class GoogleNavigationApp extends AbstractNavigationApp implements + NavigationApp { + + GoogleNavigationApp(final Resources res) { + super(res.getString(R.string.cache_menu_tbt), null); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } + + @Override + public boolean invoke(final cgGeo geo, final Activity activity, final Resources res, + final cgCache cache, + final UUID searchId, final cgWaypoint waypoint, final Geopoint coords) { + if (activity == null) { + return false; + } + + boolean navigationResult = false; + if (coords != null) { + navigationResult = navigateToCoordinates(geo, activity, coords); + } + else if (waypoint != null) { + navigationResult = navigateToCoordinates(geo, activity, waypoint.coords); + } + else if (cache != null) { + navigationResult = navigateToCoordinates(geo, activity, cache.coords); + } + + if (!navigationResult) { + if (res != null) { + ActivityMixin.showToast(activity, res.getString(R.string.err_navigation_no)); + } + return false; + } + + return true; + } + + private static boolean navigateToCoordinates(cgGeo geo, Activity activity, final Geopoint coords) { + final Geopoint coordsNow = geo == null ? null : geo.coordsNow; + + cgSettings settings = getSettings(activity); + + // Google Navigation + if (settings.useGNavigation == 1) { + try { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("google.navigation:ll=" + coords.getLatitude() + "," + + coords.getLongitude()))); + + return true; + } catch (Exception e) { + // nothing + } + } + + // Google Maps Directions + try { + if (coordsNow != null) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("http://maps.google.com/maps?f=d&saddr=" + + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr=" + + coords.getLatitude() + "," + coords.getLongitude()))); + } else { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri + .parse("http://maps.google.com/maps?f=d&daddr=" + + coords.getLatitude() + "," + coords.getLongitude()))); + } + + return true; + } catch (Exception e) { + // nothing + } + + Log.i(cgSettings.tag, + "cgBase.runNavigation: No navigation application available."); + return false; + } + +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java b/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java new file mode 100644 index 0000000..0aa49db --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java @@ -0,0 +1,47 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.UUID; + +class InternalMap extends AbstractInternalMap implements + NavigationApp { + + InternalMap(Resources res) { + super(res.getString(R.string.cache_menu_map), null); + } + + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + cgSettings settings = getSettings(activity); + Intent mapIntent = new Intent(activity, settings.getMapFactory().getMapClass()); + if (cache != null) { + mapIntent.putExtra("detail", false); + mapIntent.putExtra("geocode", cache.geocode); + } + if (searchId != null) { + mapIntent.putExtra("detail", true); + mapIntent.putExtra("searchid", searchId.toString()); + } + if (waypoint != null) { + mapIntent.putExtra("latitude", waypoint.coords.getLatitude()); + mapIntent.putExtra("longitude", waypoint.coords.getLongitude()); + mapIntent.putExtra("wpttype", waypoint.type); + } + + activity.startActivity(mapIntent); + return true; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java b/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java new file mode 100644 index 0000000..eea80da --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.apps.AbstractLocusApp; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.res.Resources; + +import java.util.ArrayList; +import java.util.UUID; + +class LocusApp extends AbstractLocusApp implements NavigationApp { + + LocusApp(Resources res) { + super(res); + } + + /** + * Show a single cache with waypoints or a single waypoint in Locus. + * This method constructs a list of cache and waypoints only. + * + * @see AbstractLocusApp#showInLocus + * @author koem + */ + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + + if (cache == null && waypoint == null && coords == null) { + return false; + } + + if (isInstalled(activity)) { // TODO: is this if-statement really necessary? + final ArrayList<Object> points = new ArrayList<Object>(); + + // add cache if present + if (cache != null && cache.coords != null) { + points.add(cache); + } + + // add waypoint if present + if (waypoint != null && waypoint.coords != null) { + points.add(waypoint); + } + + this.showInLocus(points, true, activity); + + return true; + } + + return false; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java new file mode 100644 index 0000000..ef0a578 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.apps.App; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.res.Resources; + +import java.util.UUID; + +interface NavigationApp extends App { + public boolean invoke(final cgGeo geo, final Activity activity, + final Resources res, + final cgCache cache, + final UUID searchId, final cgWaypoint waypoint, + final Geopoint coords); +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java new file mode 100644 index 0000000..1a46200 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -0,0 +1,65 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.apps.AbstractAppFactory; +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.ArrayUtils; + +import android.app.Activity; +import android.content.res.Resources; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; + +import java.util.UUID; + +public final class NavigationAppFactory extends AbstractAppFactory { + private static NavigationApp[] apps = new NavigationApp[] {}; + + private static NavigationApp[] getNavigationApps(Resources res) { + if (ArrayUtils.isEmpty(apps)) { + apps = new NavigationApp[] { + // compass + new RadarApp(res), + new InternalMap(res), + new StaticMapApp(res), + new LocusApp(res), + new RMapsApp(res), + new GoogleMapsApp(res), + new GoogleNavigationApp(res), + new StreetviewApp(res), + new OruxMapsApp(res) }; + } + return apps; + } + + public static void addMenuItems(Menu menu, Activity activity, + Resources res) { + for (NavigationApp app : getNavigationApps(res)) { + if (app.isInstalled(activity)) { + menu.add(0, app.getId(), 0, app.getName()); + } + } + } + + public static boolean onMenuItemSelected(final MenuItem item, + final cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint destination) { + NavigationApp app = (NavigationApp) getAppFromMenuItem(item, apps); + if (app != null) { + try { + return app.invoke(geo, activity, res, cache, + searchId, waypoint, destination); + } catch (Exception e) { + Log.e(cgSettings.tag, "NavigationAppFactory.onMenuItemSelected: " + e.toString()); + } + } + return false; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java new file mode 100644 index 0000000..71430ae --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java @@ -0,0 +1,59 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.UUID; + +class OruxMapsApp extends AbstractNavigationApp implements NavigationApp { + + private static final String INTENT = "com.oruxmaps.VIEW_MAP_ONLINE"; + + OruxMapsApp(final Resources res) { + super(res.getString(R.string.cache_menu_oruxmaps), INTENT); + } + + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + if (cache == null && waypoint == null && coords == null) { + return false; + } + + try { + if (isInstalled(activity)) { + Geopoint usedCoords = getCoords(cache, waypoint, coords); + + final Intent intent = new Intent(INTENT); + intent.putExtra("latitude", usedCoords.getLatitude());//latitude, wgs84 datum + intent.putExtra("longitude", usedCoords.getLongitude());//longitude, wgs84 datum + + activity.startActivity(intent); + + return true; + } + } catch (Exception e) { + // nothing + } + + return false; + } + + private static Geopoint getCoords(cgCache cache, cgWaypoint waypoint, Geopoint coords) { + if (cache != null) { + return cache.coords; + } + else if (waypoint != null) { + return waypoint.coords; + } + return coords; + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java new file mode 100644 index 0000000..2d27493 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java @@ -0,0 +1,68 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.ArrayList; +import java.util.Locale; +import java.util.UUID; + +class RMapsApp extends AbstractNavigationApp implements NavigationApp { + + private static final String INTENT = "com.robert.maps.action.SHOW_POINTS"; + + RMapsApp(final Resources res) { + super(res.getString(R.string.cache_menu_rmaps), INTENT); + } + + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + if (cache == null && waypoint == null && coords == null) { + return false; + } + + try { + if (isInstalled(activity)) { + final ArrayList<String> locations = new ArrayList<String>(); + if (cache != null && cache.coords != null) { + locations.add(String.format((Locale) null, "%.6f", + cache.coords.getLatitude()) + + "," + + String.format((Locale) null, "%.6f", + cache.coords.getLongitude()) + + ";" + + cache.geocode + + ";" + cache.name); + } else if (waypoint != null && waypoint.coords != null) { + locations.add(String.format((Locale) null, "%.6f", + waypoint.coords.getLatitude()) + + "," + + String.format((Locale) null, "%.6f", + waypoint.coords.getLongitude()) + + ";" + + waypoint.lookup + + ";" + waypoint.name); + } + + final Intent intent = new Intent(INTENT); + intent.putStringArrayListExtra("locations", locations); + activity.startActivity(intent); + + return true; + } + } catch (Exception e) { + // nothing + } + + return false; + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java new file mode 100644 index 0000000..87e6368 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java @@ -0,0 +1,47 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.UUID; + +class RadarApp extends AbstractNavigationApp implements NavigationApp { + + private static final String INTENT = "com.google.android.radar.SHOW_RADAR"; + private static final String PACKAGE_NAME = "com.eclipsim.gpsstatus2"; + + RadarApp(final Resources res) { + super(res.getString(R.string.cache_menu_radar), INTENT, PACKAGE_NAME); + } + + private static boolean navigateTo(Activity activity, final Geopoint coords) { + if (coords == null) { + return false; + } + Intent radarIntent = new Intent(INTENT); + radarIntent.putExtra("latitude", (float) coords.getLatitude()); + radarIntent.putExtra("longitude", (float) coords.getLongitude()); + activity.startActivity(radarIntent); + return true; + } + + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + if (cache != null) { + return navigateTo(activity, cache.coords); + } + if (waypoint != null) { + return navigateTo(activity, waypoint.coords); + } + return navigateTo(activity, coords); + } +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java new file mode 100644 index 0000000..0967925 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java @@ -0,0 +1,49 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.cgeosmaps; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.UUID; + +class StaticMapApp extends AbstractNavigationApp implements + NavigationApp { + + StaticMapApp(final Resources res) { + super(res.getString(R.string.cache_menu_map_static), null); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } + + @Override + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + + if (cache == null || cache.reason == 0) { + ActivityMixin.showToast(activity, res.getString(R.string.err_detail_no_map_static)); + return true; + } + + if (cache.geocode != null) { + Intent smapsIntent = new Intent(activity, cgeosmaps.class); + smapsIntent.putExtra("geocode", cache.geocode.toUpperCase()); + activity.startActivity(smapsIntent); + return true; + } + return false; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java b/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java new file mode 100644 index 0000000..8dae3c9 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java @@ -0,0 +1,60 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.ActivityNotFoundException; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; + +import java.util.UUID; + +class StreetviewApp extends AbstractNavigationApp implements NavigationApp { + + StreetviewApp(final Resources res) { + super(res.getString(R.string.cache_menu_streetview), null); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } + + public boolean invoke(cgGeo geo, Activity activity, Resources res, + cgCache cache, + final UUID searchId, cgWaypoint waypoint, final Geopoint coords) { + if (cache == null && waypoint == null && coords == null) { + return false; + } + + try { + if (cache != null && cache.coords != null) { + startActivity(activity, cache.coords); + } else if (waypoint != null && waypoint.coords != null) { + startActivity(activity, waypoint.coords); + } else if (coords != null) { + startActivity(activity, coords); + } + + return true; + } catch (ActivityNotFoundException e) { + if (res != null) { + ActivityMixin.showToast(activity, res.getString(R.string.err_application_no)); + } + } + + return false; + } + + private static void startActivity(Activity activity, final Geopoint coords) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("google.streetview:cbll=" + coords.getLatitude() + "," + coords.getLongitude()))); + } +} diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java new file mode 100644 index 0000000..c4d51d2 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.apps.cachelist; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.apps.App; + +import android.app.Activity; +import android.content.res.Resources; + +import java.util.List; +import java.util.UUID; + +interface CacheListApp extends App { + + boolean invoke(final cgGeo geo, final List<cgCache> caches, + final Activity activity, final Resources res, + final UUID searchId); + +} diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java new file mode 100644 index 0000000..5359719 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java @@ -0,0 +1,79 @@ +package cgeo.geocaching.apps.cachelist; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.apps.AbstractAppFactory; + +import org.apache.commons.lang3.ArrayUtils; + +import android.app.Activity; +import android.content.res.Resources; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public final class CacheListAppFactory extends AbstractAppFactory { + private static CacheListApp[] apps = new CacheListApp[] {}; + + private static CacheListApp[] getMultiPointNavigationApps( + Resources res) { + if (ArrayUtils.isEmpty(apps)) { + apps = new CacheListApp[] { + new InternalCacheListMap(res), + new LocusCacheListApp(res) }; + } + return apps; + } + + /** + * @param menu + * @param activity + * @param res + * @return the added menu item (also for a sub menu, then the menu item in the parent menu is returned) + */ + public static MenuItem addMenuItems(Menu menu, + Activity activity, Resources res) { + List<CacheListApp> activeApps = new ArrayList<CacheListApp>(); + for (CacheListApp app : getMultiPointNavigationApps(res)) { + if (app.isInstalled(activity)) { + activeApps.add(app); + } + } + // use a new sub menu, if more than one app is available + if (activeApps.size() > 1) { + SubMenu subMenu = menu.addSubMenu(0, 101, 0, + res.getString(R.string.caches_on_map)).setIcon( + android.R.drawable.ic_menu_mapmode); + for (CacheListApp app : activeApps) { + subMenu.add(0, app.getId(), 0, app.getName()); + } + return subMenu.getItem(); + } else if (activeApps.size() == 1) { + return menu.add(0, activeApps.get(0).getId(), 0, + activeApps.get(0).getName()).setIcon(android.R.drawable.ic_menu_mapmode); + } + return null; + } + + public static boolean onMenuItemSelected(final MenuItem item, + final cgGeo geo, final List<cgCache> caches, final Activity activity, final Resources res, + final UUID searchId) { + CacheListApp app = (CacheListApp) getAppFromMenuItem(item, apps); + if (app != null) { + try { + return app.invoke(geo, caches, activity, res, searchId); + } catch (Exception e) { + Log.e(cgSettings.tag, "CacheListAppFactory.onMenuItemSelected: " + e.toString()); + } + } + return false; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java b/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java new file mode 100644 index 0000000..7eec0f8 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java @@ -0,0 +1,37 @@ +package cgeo.geocaching.apps.cachelist; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.apps.AbstractApp; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; + +import java.util.List; +import java.util.UUID; + +class InternalCacheListMap extends AbstractApp implements CacheListApp { + + InternalCacheListMap(Resources res) { + super(res.getString(R.string.cache_menu_map), null); + } + + @Override + public boolean isInstalled(Context context) { + return true; + } + + @Override + public boolean invoke(cgGeo geo, List<cgCache> caches, Activity activity, Resources res, final UUID searchId) { + Intent mapIntent = new Intent(activity, getSettings(activity).getMapFactory() + .getMapClass()); + mapIntent.putExtra("detail", false); // this is the main difference to the activity for a single point + mapIntent.putExtra("searchid", searchId.toString()); + + activity.startActivity(mapIntent); + return true; + } +} diff --git a/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java new file mode 100644 index 0000000..ed6ac20 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java @@ -0,0 +1,36 @@ +package cgeo.geocaching.apps.cachelist; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.apps.AbstractLocusApp; + +import android.app.Activity; +import android.content.res.Resources; + +import java.util.List; +import java.util.UUID; + +class LocusCacheListApp extends AbstractLocusApp implements CacheListApp { + + LocusCacheListApp(Resources res) { + super(res); + } + + /** + * show caches in Locus + * + * @see AbstractLocusApp#showInLocus + * @author koem + */ + @Override + public boolean invoke(cgGeo geo, List<cgCache> cacheList, Activity activity, Resources res, + final UUID searchId) { + if (cacheList == null || cacheList.isEmpty()) + return false; + + this.showInLocus((List<? extends Object>) cacheList, false, activity); + + return true; + } + +} diff --git a/main/src/cgeo/geocaching/cgBase.java b/main/src/cgeo/geocaching/cgBase.java new file mode 100644 index 0000000..93d3fd0 --- /dev/null +++ b/main/src/cgeo/geocaching/cgBase.java @@ -0,0 +1,4809 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.enumerations.WaypointType; +import cgeo.geocaching.files.LocParser; +import cgeo.geocaching.geopoint.DistanceParser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.text.Spannable; +import android.text.format.DateUtils; +import android.text.style.StrikethroughSpan; +import android.util.Log; +import android.widget.EditText; + +import java.io.BufferedReader; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.math.BigInteger; +import java.net.HttpURLConnection; +import java.net.InetAddress; +import java.net.NetworkInterface; +import java.net.SocketException; +import java.net.URL; +import java.net.URLConnection; +import java.net.URLDecoder; +import java.net.URLEncoder; +import java.security.MessageDigest; +import java.security.cert.CertificateException; +import java.security.cert.X509Certificate; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Collections; +import java.util.Date; +import java.util.Enumeration; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; +import java.util.zip.GZIPInputStream; +import java.util.zip.Inflater; +import java.util.zip.InflaterInputStream; + +import javax.crypto.Mac; +import javax.crypto.spec.SecretKeySpec; +import javax.net.ssl.HostnameVerifier; +import javax.net.ssl.HttpsURLConnection; +import javax.net.ssl.SSLContext; +import javax.net.ssl.SSLSession; +import javax.net.ssl.TrustManager; +import javax.net.ssl.X509TrustManager; + +public class cgBase { + + private final static Pattern patternGeocode = Pattern.compile("<meta name=\"og:url\" content=\"[^\"]+/(GC[0-9A-Z]+)\"[^>]*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternCacheId = Pattern.compile("/seek/log\\.aspx\\?ID=(\\d+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternCacheGuid = Pattern.compile(Pattern.quote("&wid=") + "([0-9a-z\\-]+)" + Pattern.quote("&"), Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternType = Pattern.compile("<img src=\"[^\"]*/WptTypes/\\d+\\.gif\" alt=\"([^\"]+)\" (title=\"[^\"]*\" )?width=\"32\" height=\"32\"[^>]*>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + private final static Pattern patternName = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_CacheName\">([^<]+)<\\/span>[^<]*<\\/h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternSize = Pattern.compile("<div class=\"CacheSize[^\"]*\">[^<]*<p[^>]*>[^S]*Size[^:]*:[^<]*<span[^>]*>[^<]*<img src=\"[^\"]*/icons/container/[a-z_]+\\.gif\" alt=\"Size: ([^\"]+)\"[^>]*>[^<]*<small>[^<]*</small>[^<]*</span>[^<]*</p>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternDifficulty = Pattern.compile("<span id=\"ctl00_ContentBody_uxLegendScale\"[^>]*>[^<]*<img src=\"[^\"]*/images/stars/stars([0-9_]+)\\.gif\" alt=\"[^\"]+\"[^>]*>[^<]*</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternTerrain = Pattern.compile("<span id=\"ctl00_ContentBody_Localize6\"[^>]*>[^<]*<img src=\"[^\"]*/images/stars/stars([0-9_]+)\\.gif\" alt=\"[^\"]+\"[^>]*>[^<]*</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternOwner = Pattern.compile("<span class=\"minorCacheDetails\">\\W*An?(\\W*Event)?\\W*cache\\W*by[^<]*<a href=\"[^\"]+\">([^<]+)</a>[^<]*</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternOwnerReal = Pattern.compile("<a id=\"ctl00_ContentBody_uxFindLinksHiddenByThisUser\" href=\"[^\"]*/seek/nearest\\.aspx\\?u=*([^\"]+)\">[^<]+</a>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternHidden = Pattern.compile("<span[^>]*>\\W*Hidden[\\s:]*([^<]+)</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternHiddenEvent = Pattern.compile("<span[^>]*>\\W*Event\\W*Date[^:]*:([^<]*)</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternFavourite = Pattern.compile("<a id=\"uxFavContainerLink\"[^>]*>[^<]*<div[^<]*<span class=\"favorite-value\">[^\\d]*([0-9]+)[^\\d^<]*</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + private final static Pattern patternFound = Pattern.compile("<p>[^<]*<a id=\"ctl00_ContentBody_hlFoundItLog\"[^<]*<img src=\".*/images/stockholm/16x16/check\\.gif\"[^>]*>[^<]*</a>[^<]*</p>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternFoundAlternative = Pattern.compile("<div class=\"StatusInformationWidget FavoriteWidget\"", Pattern.CASE_INSENSITIVE); + private final static Pattern patternLatLon = Pattern.compile("<span id=\"ctl00_ContentBody_LatLon\"[^>]*>(<b>)?([^<]*)(<\\/b>)?<\\/span>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternLocation = Pattern.compile("<span id=\"ctl00_ContentBody_Location\"[^>]*>In ([^<]*)", Pattern.CASE_INSENSITIVE); + private final static Pattern patternHint = Pattern.compile("<p>([^<]*<strong>)?\\W*Additional Hints([^<]*<\\/strong>)?[^\\(]*\\(<a[^>]+>Encrypt</a>\\)[^<]*<\\/p>[^<]*<div id=\"div_hint\"[^>]*>(.*)</div>[^<]*<div id=[\\'|\"]dk[\\'|\"]", Pattern.CASE_INSENSITIVE); + private final static Pattern patternPersonalNote = Pattern.compile("<p id=\"cache_note\"[^>]*>([^<]*)</p>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternDescShort = Pattern.compile("<div class=\"UserSuppliedContent\">[^<]*<span id=\"ctl00_ContentBody_ShortDescription\"[^>]*>((?:(?!</span>[^\\w^<]*</div>).)*)</span>[^\\w^<]*</div>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternDesc = Pattern.compile("<span id=\"ctl00_ContentBody_LongDescription\"[^>]*>" + "(.*)</span>[^<]*</div>[^<]*<p>[^<]*</p>[^<]*<p>[^<]*<strong>\\W*Additional Hints</strong>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternCountLogs = Pattern.compile("<span id=\"ctl00_ContentBody_lblFindCounts\"><p(.+?)<\\/p><\\/span>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternCountLog = Pattern.compile("src=\"\\/images\\/icons\\/(.+?).gif\"[^>]+> (\\d+)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternAttributes = Pattern.compile("<h3 class=\"WidgetHeader\">[^<]*<img[^>]+>\\W*Attributes[^<]*</h3>[^<]*<div class=\"WidgetBody\">(([^<]*<img src=\"[^\"]+\" alt=\"[^\"]+\"[^>]*>)+)[^<]*<p", Pattern.CASE_INSENSITIVE); + private final static Pattern patternAttributesInside = Pattern.compile("[^<]*<img src=\"([^\"]+)\" alt=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternSpoilers = Pattern.compile("<p class=\"NoPrint\">\\s*((<a href=\"([^\"]+)\"[^>]*>\\s*<img[^>]+><span>([^<]+)</span></a><br\\s*/>)*)\\s*</p>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private final static Pattern patternSpoilersInside = Pattern.compile("[^<]*<a href=\"([^\"]+)\"[^>]*>[^<]*<img[^>]+>[^<]*<span>([^>]+)</span>[^<]*</a>[^<]*<br[^>]*>(([^<]*)(<br[^<]*>)+)?", Pattern.CASE_INSENSITIVE); + private final static Pattern patternInventory = Pattern.compile("<span id=\"ctl00_ContentBody_uxTravelBugList_uxInventoryLabel\">\\W*Inventory[^<]*</span>[^<]*</h3>[^<]*<div class=\"WidgetBody\">([^<]*<ul>(([^<]*<li>[^<]*<a href=\"[^\"]+\"[^>]*>[^<]*<img src=\"[^\"]+\"[^>]*>[^<]*<span>[^<]+<\\/span>[^<]*<\\/a>[^<]*<\\/li>)+)[^<]*<\\/ul>)?", Pattern.CASE_INSENSITIVE); + private final static Pattern patternInventoryInside = Pattern.compile("[^<]*<li>[^<]*<a href=\"[a-z0-9\\-\\_\\.\\?\\/\\:\\@]*\\/track\\/details\\.aspx\\?guid=([0-9a-z\\-]+)[^\"]*\"[^>]*>[^<]*<img src=\"[^\"]+\"[^>]*>[^<]*<span>([^<]+)<\\/span>[^<]*<\\/a>[^<]*<\\/li>", Pattern.CASE_INSENSITIVE); + private final static Pattern patternOnWatchlist = Pattern.compile("<img\\s*src=\"\\/images\\/stockholm\\/16x16\\/icon_stop_watchlist.gif\"", Pattern.CASE_INSENSITIVE); + + private final static Pattern PATTERN_TRACKABLE_TrackableId = Pattern.compile("<a id=\"ctl00_ContentBody_LogLink\" title=\"[^\"]*\" href=\".*log\\.aspx\\?wid=([a-z0-9\\-]+)\"[^>]*>[^<]*</a>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Geocode = Pattern.compile("<span id=\"ctl00_ContentBody_BugDetails_BugTBNum\" String=\"[^\"]*\">Use[^<]*<strong>(TB[0-9a-z]+)[^<]*</strong> to reference this item.[^<]*</span>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Name = Pattern.compile("<h2>([^<]*<img[^>]*>)?[^<]*<span id=\"ctl00_ContentBody_lbHeading\">([^<]+)</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Owner = Pattern.compile("<dt>\\W*Owner:[^<]*</dt>[^<]*<dd>[^<]*<a id=\"ctl00_ContentBody_BugDetails_BugOwner\" title=\"[^\"]*\" href=\"[^\"]*/profile/\\?guid=([a-z0-9\\-]+)\">([^<]+)<\\/a>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Released = Pattern.compile("<dt>\\W*Released:[^<]*</dt>[^<]*<dd>[^<]*<span id=\"ctl00_ContentBody_BugDetails_BugReleaseDate\">([^<]+)<\\/span>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Origin = Pattern.compile("<dt>\\W*Origin:[^<]*</dt>[^<]*<dd>[^<]*<span id=\"ctl00_ContentBody_BugDetails_BugOrigin\">([^<]+)<\\/span>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_SpottedCache = Pattern.compile("<dt>\\W*Recently Spotted:[^<]*</dt>[^<]*<dd>[^<]*<a id=\"ctl00_ContentBody_BugDetails_BugLocation\" title=\"[^\"]*\" href=\"[^\"]*/seek/cache_details.aspx\\?guid=([a-z0-9\\-]+)\">In ([^<]+)</a>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_SpottedUser = Pattern.compile("<dt>\\W*Recently Spotted:[^<]*</dt>[^<]*<dd>[^<]*<a id=\"ctl00_ContentBody_BugDetails_BugLocation\" href=\"[^\"]*/profile/\\?guid=([a-z0-9\\-]+)\">In the hands of ([^<]+).</a>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_SpottedUnknown = Pattern.compile("<dt>\\W*Recently Spotted:[^<]*</dt>[^<]*<dd>[^<]*<a id=\"ctl00_ContentBody_BugDetails_BugLocation\">Unknown Location[^<]*</a>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_SpottedOwner = Pattern.compile("<dt>\\W*Recently Spotted:[^<]*</dt>[^<]*<dd>[^<]*<a id=\"ctl00_ContentBody_BugDetails_BugLocation\">In the hands of the owner[^<]*</a>[^<]*</dd>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Goal = Pattern.compile("<h3>\\W*Current GOAL[^<]*</h3>[^<]*<p[^>]*>(.*)</p>[^<]*<h3>\\W*About This Item[^<]*</h3>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_DetailsImage = Pattern.compile("<h3>\\W*About This Item[^<]*</h3>([^<]*<p>([^<]*<img id=\"ctl00_ContentBody_BugDetails_BugImage\" class=\"[^\"]+\" src=\"([^\"]+)\"[^>]*>)?[^<]*</p>)?[^<]*<p[^>]*>(.*)</p>[^<]*<div id=\"ctl00_ContentBody_BugDetails_uxAbuseReport\">", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Icon = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Type = Pattern.compile("<img id=\"ctl00_ContentBody_BugTypeImage\" class=\"TravelBugHeaderIcon\" src=\"[^\"]+\" alt=\"([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Distance = Pattern.compile("<h4[^>]*\\W*Tracking History \\(([0-9\\.,]+(km|mi))[^\\)]*\\)", Pattern.CASE_INSENSITIVE); + private final static Pattern PATTERN_TRACKABLE_Log = Pattern.compile("<tr class=\"Data.+?src=\"/images/icons/([^\\.]+)\\.gif[^>]+> ([^<]+)</td>.+?guid.+?>([^<]+)</a>.+?(?:guid=([^\"]+)\">([^<]+)</a>.+?)?<td colspan=\"4\">(.+?)(?:<ul.+?ul>)?\\s*</td>\\s*</tr>", Pattern.CASE_INSENSITIVE); + + public final static Map<String, String> cacheTypes = new HashMap<String, String>(); + public final static Map<String, String> cacheTypesInv = new HashMap<String, String>(); + public final static Map<String, String> cacheIDs = new HashMap<String, String>(); + public final static Map<String, String> cacheIDsChoices = new HashMap<String, String>(); + public final static Map<CacheSize, String> cacheSizesInv = new HashMap<CacheSize, String>(); + public final static Map<String, String> waypointTypes = new HashMap<String, String>(); + public final static Map<String, Integer> logTypes = new HashMap<String, Integer>(); + public final static Map<String, Integer> logTypes0 = new HashMap<String, Integer>(); + public final static Map<Integer, String> logTypes1 = new HashMap<Integer, String>(); + public final static Map<Integer, String> logTypes2 = new HashMap<Integer, String>(); + public final static Map<Integer, String> logTypesTrackable = new HashMap<Integer, String>(); + public final static Map<Integer, String> logTypesTrackableAction = new HashMap<Integer, String>(); + public final static Map<Integer, String> errorRetrieve = new HashMap<Integer, String>(); + public final static Map<String, SimpleDateFormat> gcCustomDateFormats; + static { + final String[] formats = new String[] { + "MM/dd/yyyy", + "yyyy-MM-dd", + "yyyy/MM/dd", + "dd/MMM/yyyy", + "MMM/dd/yyyy", + "dd MMM yy", + "dd/MM/yyyy" + }; + + Map<String, SimpleDateFormat> map = new HashMap<String, SimpleDateFormat>(); + + for (String format : formats) + { + map.put(format, new SimpleDateFormat(format, Locale.ENGLISH)); + } + + gcCustomDateFormats = Collections.unmodifiableMap(map); + } + public final static SimpleDateFormat dateTbIn1 = new SimpleDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 + public final static SimpleDateFormat dateTbIn2 = new SimpleDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009 + public final static SimpleDateFormat dateSqlIn = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); // 2010-07-25 14:44:01 + private Resources res = null; + private static final String passMatch = "[/\\?&]*[Pp]ass(word)?=[^&^#^$]+"; + private static final Pattern patternLoggedIn = Pattern.compile("<span class=\"Success\">You are logged in as[^<]*<strong[^>]*>([^<]+)</strong>[^<]*</span>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern patternLogged2In = Pattern.compile("<strong>\\W*Hello,[^<]*<a[^>]+>([^<]+)</a>[^<]*</strong>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern patternViewstateFieldCount = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern patternViewstates = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern patternIsPremium = Pattern.compile("<span id=\"ctl00_litPMLevel\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + private static final Pattern patternUserToken = Pattern.compile("userToken\\s*=\\s*'([^']+)'"); + public static final float miles2km = 1.609344f; + public static final float feet2km = 0.0003048f; + public static final float yards2km = 0.0009144f; + public static final double deg2rad = Math.PI / 180; + public static final double rad2deg = 180 / Math.PI; + public static final float erad = 6371.0f; + private cgeoapplication app = null; + private cgSettings settings = null; + private SharedPreferences prefs = null; + public String version = null; + private String idBrowser = "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4"; + Context context = null; + final private static Map<String, Integer> gcIcons = new HashMap<String, Integer>(); + final private static Map<String, Integer> wpIcons = new HashMap<String, Integer>(); + + public static final int LOG_FOUND_IT = 2; + public static final int LOG_DIDNT_FIND_IT = 3; + public static final int LOG_NOTE = 4; + public static final int LOG_PUBLISH_LISTING = 1003; // unknown ID; used number doesn't match any GC.com's ID + public static final int LOG_ENABLE_LISTING = 23; + public static final int LOG_ARCHIVE = 5; + public static final int LOG_TEMP_DISABLE_LISTING = 22; + public static final int LOG_NEEDS_ARCHIVE = 7; + public static final int LOG_WILL_ATTEND = 9; + public static final int LOG_ATTENDED = 10; + public static final int LOG_RETRIEVED_IT = 13; + public static final int LOG_PLACED_IT = 14; + public static final int LOG_GRABBED_IT = 19; + public static final int LOG_NEEDS_MAINTENANCE = 45; + public static final int LOG_OWNER_MAINTENANCE = 46; + public static final int LOG_UPDATE_COORDINATES = 47; + public static final int LOG_DISCOVERED_IT = 48; + public static final int LOG_POST_REVIEWER_NOTE = 18; + public static final int LOG_VISIT = 1001; // unknown ID; used number doesn't match any GC.com's ID + public static final int LOG_WEBCAM_PHOTO_TAKEN = 11; + public static final int LOG_ANNOUNCEMENT = 74; + + public cgBase(cgeoapplication appIn, cgSettings settingsIn, SharedPreferences prefsIn) { + context = appIn.getBaseContext(); + res = appIn.getBaseContext().getResources(); + + // setup cache type mappings + + final String CACHETYPE_ALL_GUID = "9a79e6ce-3344-409c-bbe9-496530baf758"; + + cacheIDs.put("all", CACHETYPE_ALL_GUID); + cacheIDsChoices.put(res.getString(R.string.all), CACHETYPE_ALL_GUID); + + for (CacheType ct : CacheType.values()) { + String l10n = res.getString(ct.stringId); + cacheTypes.put(ct.pattern, ct.id); + cacheTypesInv.put(ct.id, l10n); + cacheIDs.put(ct.id, ct.guid); + cacheIDsChoices.put(l10n, ct.guid); + } + + for (CacheSize cs : CacheSize.values()) { + cacheSizesInv.put(cs, res.getString(cs.stringId)); + } + + // waypoint types + waypointTypes.put("flag", res.getString(WaypointType.FLAG.stringId)); + waypointTypes.put("stage", res.getString(WaypointType.STAGE.stringId)); + waypointTypes.put("puzzle", res.getString(WaypointType.PUZZLE.stringId)); + waypointTypes.put("pkg", res.getString(WaypointType.PKG.stringId)); + waypointTypes.put("trailhead", res.getString(WaypointType.TRAILHEAD.stringId)); + waypointTypes.put("waypoint", res.getString(WaypointType.WAYPOINT.stringId)); + + // log types + logTypes.put("icon_smile", LOG_FOUND_IT); + logTypes.put("icon_sad", LOG_DIDNT_FIND_IT); + logTypes.put("icon_note", LOG_NOTE); + logTypes.put("icon_greenlight", LOG_PUBLISH_LISTING); + logTypes.put("icon_enabled", LOG_ENABLE_LISTING); + logTypes.put("traffic_cone", LOG_ARCHIVE); + logTypes.put("icon_disabled", LOG_TEMP_DISABLE_LISTING); + logTypes.put("icon_remove", LOG_NEEDS_ARCHIVE); + logTypes.put("icon_rsvp", LOG_WILL_ATTEND); + logTypes.put("icon_attended", LOG_ATTENDED); + logTypes.put("picked_up", LOG_RETRIEVED_IT); + logTypes.put("dropped_off", LOG_PLACED_IT); + logTypes.put("transfer", LOG_GRABBED_IT); + logTypes.put("icon_needsmaint", LOG_NEEDS_MAINTENANCE); + logTypes.put("icon_maint", LOG_OWNER_MAINTENANCE); + logTypes.put("coord_update", LOG_UPDATE_COORDINATES); + logTypes.put("icon_discovered", LOG_DISCOVERED_IT); + logTypes.put("big_smile", LOG_POST_REVIEWER_NOTE); + logTypes.put("icon_visited", LOG_VISIT); // unknown ID; used number doesn't match any GC.com's ID + logTypes.put("icon_camera", LOG_WEBCAM_PHOTO_TAKEN); // unknown ID; used number doesn't match any GC.com's ID + logTypes.put("icon_announcement", LOG_ANNOUNCEMENT); // unknown ID; used number doesn't match any GC.com's ID + + logTypes0.put("found it", LOG_FOUND_IT); + logTypes0.put("didn't find it", LOG_DIDNT_FIND_IT); + logTypes0.put("write note", LOG_NOTE); + logTypes0.put("publish listing", LOG_PUBLISH_LISTING); + logTypes0.put("enable listing", LOG_ENABLE_LISTING); + logTypes0.put("archive", LOG_ARCHIVE); + logTypes0.put("temporarily disable listing", LOG_TEMP_DISABLE_LISTING); + logTypes0.put("needs archived", LOG_NEEDS_ARCHIVE); + logTypes0.put("will attend", LOG_WILL_ATTEND); + logTypes0.put("attended", LOG_ATTENDED); + logTypes0.put("retrieved it", LOG_RETRIEVED_IT); + logTypes0.put("placed it", LOG_PLACED_IT); + logTypes0.put("grabbed it", LOG_GRABBED_IT); + logTypes0.put("needs maintenance", LOG_NEEDS_MAINTENANCE); + logTypes0.put("owner maintenance", LOG_OWNER_MAINTENANCE); + logTypes0.put("update coordinates", LOG_UPDATE_COORDINATES); + logTypes0.put("discovered it", LOG_DISCOVERED_IT); + logTypes0.put("post reviewer note", LOG_POST_REVIEWER_NOTE); + logTypes0.put("visit", LOG_VISIT); // unknown ID; used number doesn't match any GC.com's ID + logTypes0.put("webcam photo taken", LOG_WEBCAM_PHOTO_TAKEN); // unknown ID; used number doesn't match any GC.com's ID + logTypes0.put("announcement", LOG_ANNOUNCEMENT); // unknown ID; used number doesn't match any GC.com's ID + + logTypes1.put(LOG_FOUND_IT, res.getString(R.string.log_found)); + logTypes1.put(LOG_DIDNT_FIND_IT, res.getString(R.string.log_dnf)); + logTypes1.put(LOG_NOTE, res.getString(R.string.log_note)); + logTypes1.put(LOG_PUBLISH_LISTING, res.getString(R.string.log_published)); + logTypes1.put(LOG_ENABLE_LISTING, res.getString(R.string.log_enabled)); + logTypes1.put(LOG_ARCHIVE, res.getString(R.string.log_archived)); + logTypes1.put(LOG_TEMP_DISABLE_LISTING, res.getString(R.string.log_disabled)); + logTypes1.put(LOG_NEEDS_ARCHIVE, res.getString(R.string.log_needs_archived)); + logTypes1.put(LOG_WILL_ATTEND, res.getString(R.string.log_attend)); + logTypes1.put(LOG_ATTENDED, res.getString(R.string.log_attended)); + logTypes1.put(LOG_RETRIEVED_IT, res.getString(R.string.log_retrieved)); + logTypes1.put(LOG_PLACED_IT, res.getString(R.string.log_placed)); + logTypes1.put(LOG_GRABBED_IT, res.getString(R.string.log_grabbed)); + logTypes1.put(LOG_NEEDS_MAINTENANCE, res.getString(R.string.log_maintenance_needed)); + logTypes1.put(LOG_OWNER_MAINTENANCE, res.getString(R.string.log_maintained)); + logTypes1.put(LOG_UPDATE_COORDINATES, res.getString(R.string.log_update)); + logTypes1.put(LOG_DISCOVERED_IT, res.getString(R.string.log_discovered)); + logTypes1.put(LOG_POST_REVIEWER_NOTE, res.getString(R.string.log_reviewed)); + logTypes1.put(LOG_VISIT, res.getString(R.string.log_taken)); + logTypes1.put(LOG_WEBCAM_PHOTO_TAKEN, res.getString(R.string.log_webcam)); + logTypes1.put(LOG_ANNOUNCEMENT, res.getString(R.string.log_announcement)); + + logTypes2.put(LOG_FOUND_IT, res.getString(R.string.log_found)); // traditional, multi, unknown, earth, wherigo, virtual, letterbox + logTypes2.put(LOG_DIDNT_FIND_IT, res.getString(R.string.log_dnf)); // traditional, multi, unknown, earth, wherigo, virtual, letterbox, webcam + logTypes2.put(LOG_NOTE, res.getString(R.string.log_note)); // traditional, multi, unknown, earth, wherigo, virtual, event, letterbox, webcam, trackable + logTypes2.put(LOG_PUBLISH_LISTING, res.getString(R.string.log_published)); // X + logTypes2.put(LOG_ENABLE_LISTING, res.getString(R.string.log_enabled)); // owner + logTypes2.put(LOG_ARCHIVE, res.getString(R.string.log_archived)); // traditional, multi, unknown, earth, event, wherigo, virtual, letterbox, webcam + logTypes2.put(LOG_TEMP_DISABLE_LISTING, res.getString(R.string.log_disabled)); // owner + logTypes2.put(LOG_NEEDS_ARCHIVE, res.getString(R.string.log_needs_archived)); // traditional, multi, unknown, earth, event, wherigo, virtual, letterbox, webcam + logTypes2.put(LOG_WILL_ATTEND, res.getString(R.string.log_attend)); // event + logTypes2.put(LOG_ATTENDED, res.getString(R.string.log_attended)); // event + logTypes2.put(LOG_WEBCAM_PHOTO_TAKEN, res.getString(R.string.log_webcam)); // webcam + logTypes2.put(LOG_RETRIEVED_IT, res.getString(R.string.log_retrieved)); //trackable + logTypes2.put(LOG_GRABBED_IT, res.getString(R.string.log_grabbed)); //trackable + logTypes2.put(LOG_NEEDS_MAINTENANCE, res.getString(R.string.log_maintenance_needed)); // traditional, unknown, multi, wherigo, virtual, letterbox, webcam + logTypes2.put(LOG_OWNER_MAINTENANCE, res.getString(R.string.log_maintained)); // owner + logTypes2.put(LOG_DISCOVERED_IT, res.getString(R.string.log_discovered)); //trackable + logTypes2.put(LOG_POST_REVIEWER_NOTE, res.getString(R.string.log_reviewed)); // X + logTypes2.put(LOG_ANNOUNCEMENT, res.getString(R.string.log_announcement)); // X + + // trackables for logs + logTypesTrackable.put(0, res.getString(R.string.log_tb_nothing)); // do nothing + logTypesTrackable.put(1, res.getString(R.string.log_tb_visit)); // visit cache + logTypesTrackable.put(2, res.getString(R.string.log_tb_drop)); // drop here + logTypesTrackableAction.put(0, ""); // do nothing + logTypesTrackableAction.put(1, "_Visited"); // visit cache + logTypesTrackableAction.put(2, "_DroppedOff"); // drop here + + // retrieving errors (because of ____ ) + errorRetrieve.put(1, res.getString(R.string.err_none)); + errorRetrieve.put(0, res.getString(R.string.err_start)); + errorRetrieve.put(-1, res.getString(R.string.err_parse)); + errorRetrieve.put(-2, res.getString(R.string.err_server)); + errorRetrieve.put(-3, res.getString(R.string.err_login)); + errorRetrieve.put(-4, res.getString(R.string.err_unknown)); + errorRetrieve.put(-5, res.getString(R.string.err_comm)); + errorRetrieve.put(-6, res.getString(R.string.err_wrong)); + errorRetrieve.put(-7, res.getString(R.string.err_license)); + + // init + app = appIn; + settings = settingsIn; + prefs = prefsIn; + + try { + PackageManager manager = app.getPackageManager(); + PackageInfo info = manager.getPackageInfo(app.getPackageName(), 0); + version = info.versionName; + } catch (Exception e) { + // nothing + } + + if (settings.asBrowser == 1) { + final long rndBrowser = Math.round(Math.random() * 6); + if (rndBrowser == 0) { + idBrowser = "Mozilla/5.0 (Windows; U; Windows NT 6.1; en-US) AppleWebKit/533.1 (KHTML, like Gecko) Chrome/5.0.322.2 Safari/533.1"; + } else if (rndBrowser == 1) { + idBrowser = "Mozilla/4.0 (compatible; MSIE 8.0; Windows NT 6.1; WOW64; Trident/4.0; SLCC2; .NET CLR 2.0.50727; .NET CLR 3.5.30729; .NET CLR 3.0.30729; Media Center PC 6.0; MDDC)"; + } else if (rndBrowser == 2) { + idBrowser = "Mozilla/5.0 (Windows; U; Windows NT 5.1; en-US; rv:1.9.2.3) Gecko/20100401 Firefox/3.6.3"; + } else if (rndBrowser == 3) { + idBrowser = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-us) AppleWebKit/531.21.8 (KHTML, like Gecko) Version/4.0.4 Safari/531.21.10"; + } else if (rndBrowser == 4) { + idBrowser = "Mozilla/5.0 (iPod; U; CPU iPhone OS 2_2_1 like Mac OS X; en-us) AppleWebKit/525.18.1 (KHTML, like Gecko) Version/3.1.1 Mobile/5H11a Safari/525.20"; + } else if (rndBrowser == 5) { + idBrowser = "Mozilla/5.0 (Linux; U; Android 1.1; en-gb; dream) AppleWebKit/525.10+ (KHTML, like Gecko) Version/3.0.4 Mobile Safari/523.12.2"; + } else if (rndBrowser == 6) { + idBrowser = "Mozilla/5.0 (X11; U; Linux i686; en-US) AppleWebKit/533.4 (KHTML, like Gecko) Chrome/5.0.375.86 Safari/533.4"; + } else { + idBrowser = "Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10_6_2; en-US) AppleWebKit/532.9 (KHTML, like Gecko) Chrome/5.0.307.11 Safari/532.9"; + } + } + } + + /** + * read all viewstates from page + * + * @return String[] with all view states + */ + public static String[] getViewstates(String page) { + // Get the number of viewstates. + // If there is only one viewstate, __VIEWSTATEFIELDCOUNT is not present + int count = 1; + final Matcher matcherViewstateCount = patternViewstateFieldCount.matcher(page); + if (matcherViewstateCount.find()) + count = Integer.parseInt(matcherViewstateCount.group(1)); + + String[] viewstates = new String[count]; + + // Get the viewstates + int no; + final Matcher matcherViewstates = patternViewstates.matcher(page); + while (matcherViewstates.find()) { + String sno = matcherViewstates.group(1); // number of viewstate + if ("".equals(sno)) + no = 0; + else + no = Integer.parseInt(sno); + viewstates[no] = matcherViewstates.group(2); + } + + if (viewstates.length == 1 && viewstates[0] == null) + // no viewstates were present + return null; + else + return viewstates; + } + + /** + * put viewstates into request parameters + */ + private static void setViewstates(String[] viewstates, Map<String, String> params) { + if (ArrayUtils.isEmpty(viewstates)) + return; + params.put("__VIEWSTATE", viewstates[0]); + if (viewstates.length > 1) { + for (int i = 1; i < viewstates.length; i++) + params.put("__VIEWSTATE" + i, viewstates[i]); + params.put("__VIEWSTATEFIELDCOUNT", viewstates.length + ""); + } + } + + /** + * transfers the viewstates variables from a page (response) to parameters + * (next request) + */ + public static void transferViewstates(String page, Map<String, String> params) { + setViewstates(getViewstates(page), params); + } + + /** + * checks if an Array of Strings is empty or not. Empty means: + * - Array is null + * - or all elements are null or empty strings + */ + public static boolean isEmpty(String[] a) { + if (a == null) + return true; + + for (String s : a) { + if (StringUtils.isNotEmpty(s)) { + return false; + } + } + return true; + } + + public class loginThread extends Thread { + + @Override + public void run() { + login(); + } + } + + public int login() { + final String host = "www.geocaching.com"; + final String path = "/login/default.aspx"; + cgResponse loginResponse = null; + String loginData = null; + + String[] viewstates = null; + + final Map<String, String> loginStart = settings.getLogin(); + + if (loginStart == null) { + return -3; // no login information stored + } + + loginResponse = request(true, host, path, "GET", new HashMap<String, String>(), false, false, false); + loginData = loginResponse.getData(); + if (StringUtils.isNotBlank(loginData)) { + if (checkLogin(loginData)) { + Log.i(cgSettings.tag, "Already logged in Geocaching.com as " + loginStart.get("username")); + + switchToEnglish(viewstates); + + return 1; // logged in + } + + viewstates = getViewstates(loginData); + + if (isEmpty(viewstates)) { + Log.e(cgSettings.tag, "cgeoBase.login: Failed to find viewstates"); + return -1; // no viewstates + } + } else { + Log.e(cgSettings.tag, "cgeoBase.login: Failed to retrieve login page (1st)"); + return -2; // no loginpage + } + + final Map<String, String> login = settings.getLogin(); + final Map<String, String> params = new HashMap<String, String>(); + + if (login == null || StringUtils.isEmpty(login.get("username")) || StringUtils.isEmpty(login.get("password"))) { + Log.e(cgSettings.tag, "cgeoBase.login: No login information stored"); + return -3; + } + + CookieJar.deleteCookies(prefs); + + params.put("__EVENTTARGET", ""); + params.put("__EVENTARGUMENT", ""); + setViewstates(viewstates, params); + params.put("ctl00$SiteContent$tbUsername", login.get("username")); + params.put("ctl00$SiteContent$tbPassword", login.get("password")); + params.put("ctl00$SiteContent$cbRememberMe", "on"); + params.put("ctl00$SiteContent$btnSignIn", "Login"); + + loginResponse = request(true, host, path, "POST", params, false, false, false); + loginData = loginResponse.getData(); + + if (StringUtils.isNotBlank(loginData)) { + if (checkLogin(loginData)) { + Log.i(cgSettings.tag, "Successfully logged in Geocaching.com as " + login.get("username")); + + switchToEnglish(getViewstates(loginData)); + + return 1; // logged in + } else { + if (loginData.indexOf("Your username/password combination does not match.") != -1) { + Log.i(cgSettings.tag, "Failed to log in Geocaching.com as " + login.get("username") + " because of wrong username/password"); + + return -6; // wrong login + } else { + Log.i(cgSettings.tag, "Failed to log in Geocaching.com as " + login.get("username") + " for some unknown reason"); + + return -4; // can't login + } + } + } else { + Log.e(cgSettings.tag, "cgeoBase.login: Failed to retrieve login page (2nd)"); + + return -5; // no login page + } + } + + public static Boolean isPremium(String page) + { + if (checkLogin(page)) { + final Matcher matcherIsPremium = patternIsPremium.matcher(page); + return matcherIsPremium.find(); + } else + return false; + } + + public static Boolean checkLogin(String page) { + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.checkLogin: No page given"); + return false; + } + + // on every page + final Matcher matcherLogged2In = patternLogged2In.matcher(page); + if (matcherLogged2In.find()) { + return true; + } + + // after login + final Matcher matcherLoggedIn = patternLoggedIn.matcher(page); + if (matcherLoggedIn.find()) { + return true; + } + + return false; + } + + public String switchToEnglish(String[] viewstates) { + final String host = "www.geocaching.com"; + final String path = "/default.aspx"; + final Map<String, String> params = new HashMap<String, String>(); + + setViewstates(viewstates, params); + params.put("__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem"); // switch to english + params.put("__EVENTARGUMENT", ""); + + return request(false, host, path, "POST", params, false, false, false).getData(); + } + + public cgCacheWrap parseSearch(cgSearchThread thread, String url, String page, boolean showCaptcha) { + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.parseSearch: No page given"); + return null; + } + + final cgCacheWrap caches = new cgCacheWrap(); + final List<String> cids = new ArrayList<String>(); + final List<String> guids = new ArrayList<String>(); + String recaptchaChallenge = null; + String recaptchaText = null; + + caches.url = url; + + final Pattern patternCacheType = Pattern.compile("<td class=\"Merge\">[^<]*<a href=\"[^\"]*/seek/cache_details\\.aspx\\?guid=[^\"]+\"[^>]+>[^<]*<img src=\"[^\"]*/images/wpttypes/[^\\.]+\\.gif\" alt=\"([^\"]+)\" title=\"[^\"]+\"[^>]*>[^<]*</a>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + final Pattern patternGuidAndDisabled = Pattern.compile("<img src=\"[^\"]*/images/wpttypes/[^>]*>[^<]*</a></td><td class=\"Merge\">[^<]*<a href=\"[^\"]*/seek/cache_details\\.aspx\\?guid=([a-z0-9\\-]+)\" class=\"lnk([^\"]*)\">([^<]*<span>)?([^<]*)(</span>[^<]*)?</a>[^<]+<br />([^<]*)<span[^>]+>([^<]*)</span>([^<]*<img[^>]+>)?[^<]*<br />[^<]*</td>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + final Pattern patternTbs = Pattern.compile("<a id=\"ctl00_ContentBody_dlResults_ctl[0-9]+_uxTravelBugList\" class=\"tblist\" data-tbcount=\"([0-9]+)\" data-id=\"[^\"]*\"[^>]*>(.*)</a>", Pattern.CASE_INSENSITIVE); + final Pattern patternTbsInside = Pattern.compile("(<img src=\"[^\"]+\" alt=\"([^\"]+)\" title=\"[^\"]*\" />[^<]*)", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + final Pattern patternDirection = Pattern.compile("<img id=\"ctl00_ContentBody_dlResults_ctl[0-9]+_uxDistanceAndHeading\" title=\"[^\"]*\" src=\"[^\"]*/seek/CacheDir\\.ashx\\?k=([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + final Pattern patternCode = Pattern.compile("\\|\\W*(GC[a-z0-9]+)[^\\|]*\\|", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + final Pattern patternId = Pattern.compile("name=\"CID\"[^v]*value=\"([0-9]+)\"", Pattern.CASE_INSENSITIVE); + final Pattern patternFavourite = Pattern.compile("<span id=\"ctl00_ContentBody_dlResults_ctl[0-9]+_uxFavoritesValue\" title=\"[^\"]*\" class=\"favorite-rank\">([0-9]+)</span>", Pattern.CASE_INSENSITIVE); + final Pattern patternTotalCnt = Pattern.compile("<td class=\"PageBuilderWidget\"><span>Total Records[^<]*<b>(\\d+)<\\/b>", Pattern.CASE_INSENSITIVE); + final Pattern patternRecaptcha = Pattern.compile("<script[^>]*src=\"[^\"]*/recaptcha/api/challenge\\?k=([^\"]+)\"[^>]*>", Pattern.CASE_INSENSITIVE); + final Pattern patternRecaptchaChallenge = Pattern.compile("challenge : '([^']+)'", Pattern.CASE_INSENSITIVE); + + caches.viewstates = getViewstates(page); + + // recaptcha + if (showCaptcha) { + try { + String recaptchaJsParam = null; + final Matcher matcherRecaptcha = patternRecaptcha.matcher(page); + while (matcherRecaptcha.find()) { + if (matcherRecaptcha.groupCount() > 0) { + recaptchaJsParam = matcherRecaptcha.group(1); + } + } + + if (recaptchaJsParam != null) { + final String recaptchaJs = request(false, "www.google.com", "/recaptcha/api/challenge", "GET", "k=" + urlencode_rfc3986(recaptchaJsParam.trim()), 0, true).getData(); + + if (StringUtils.isNotBlank(recaptchaJs)) { + final Matcher matcherRecaptchaChallenge = patternRecaptchaChallenge.matcher(recaptchaJs); + while (matcherRecaptchaChallenge.find()) { + if (matcherRecaptchaChallenge.groupCount() > 0) { + recaptchaChallenge = matcherRecaptchaChallenge.group(1).trim(); + } + } + } + } + } catch (Exception e) { + // failed to parse recaptcha challenge + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse recaptcha challenge"); + } + + if (thread != null && StringUtils.isNotBlank(recaptchaChallenge)) { + thread.setChallenge(recaptchaChallenge); + thread.notifyNeed(); + } + } + + if (page.indexOf("SearchResultsTable") < 0) { + // there are no results. aborting here avoids a wrong error log in the next parsing step + return caches; + } + + int startPos = page.indexOf("<div id=\"ctl00_ContentBody_ResultsPanel\""); + if (startPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseSearch: ID \"ctl00_ContentBody_dlResults\" not found on page"); + return null; + } + + page = page.substring(startPos); // cut on <table + + startPos = page.indexOf(">"); + int endPos = page.indexOf("ctl00_ContentBody_UnitTxt"); + if (startPos == -1 || endPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseSearch: ID \"ctl00_ContentBody_UnitTxt\" not found on page"); + return null; + } + + page = page.substring(startPos + 1, endPos - startPos + 1); // cut between <table> and </table> + + final String[] rows = page.split("<tr class="); + final int rows_count = rows.length; + + for (int z = 1; z < rows_count; z++) { + cgCache cache = new cgCache(); + String row = rows[z]; + + // check for cache type presence + if (row.indexOf("images/wpttypes") == -1) { + continue; + } + + try { + final Matcher matcherGuidAndDisabled = patternGuidAndDisabled.matcher(row); + + while (matcherGuidAndDisabled.find()) { + if (matcherGuidAndDisabled.groupCount() > 0) { + guids.add(matcherGuidAndDisabled.group(1)); + + cache.guid = matcherGuidAndDisabled.group(1); + if (matcherGuidAndDisabled.group(4) != null) { + cache.name = Html.fromHtml(matcherGuidAndDisabled.group(4).trim()).toString(); + } + if (matcherGuidAndDisabled.group(6) != null) { + cache.location = Html.fromHtml(matcherGuidAndDisabled.group(6).trim()).toString(); + } + + final String attr = matcherGuidAndDisabled.group(2); + if (attr != null) { + if (attr.contains("Strike")) { + cache.disabled = true; + } else { + cache.disabled = false; + } + + if (attr.contains("OldWarning")) { + cache.archived = true; + } else { + cache.archived = false; + } + } + } + } + } catch (Exception e) { + // failed to parse GUID and/or Disabled + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse GUID and/or Disabled data"); + } + + if (settings.excludeDisabled == 1 && (cache.disabled || cache.archived)) { + // skip disabled and archived caches + cache = null; + continue; + } + + String inventoryPre = null; + + // GC* code + try { + final Matcher matcherCode = patternCode.matcher(row); + while (matcherCode.find()) { + if (matcherCode.groupCount() > 0) { + cache.geocode = matcherCode.group(1).toUpperCase(); + } + } + } catch (Exception e) { + // failed to parse code + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache code"); + } + + // cache type + try { + final Matcher matcherCacheType = patternCacheType.matcher(row); + while (matcherCacheType.find()) { + if (matcherCacheType.groupCount() > 0) { + cache.type = cacheTypes.get(matcherCacheType.group(1).toLowerCase()); + } + } + } catch (Exception e) { + // failed to parse type + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache type"); + } + + // cache direction - image + if (settings.getLoadDirImg()) + { + try { + final Matcher matcherDirection = patternDirection.matcher(row); + while (matcherDirection.find()) { + if (matcherDirection.groupCount() > 0) { + cache.directionImg = matcherDirection.group(1); + } + } + } catch (Exception e) { + // failed to parse direction image + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache direction image"); + } + } + + // cache inventory + try { + final Matcher matcherTbs = patternTbs.matcher(row); + while (matcherTbs.find()) { + if (matcherTbs.groupCount() > 0) { + cache.inventoryItems = Integer.parseInt(matcherTbs.group(1)); + inventoryPre = matcherTbs.group(2); + } + } + } catch (Exception e) { + // failed to parse inventory + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache inventory (1)"); + } + + if (StringUtils.isNotBlank(inventoryPre)) { + try { + final Matcher matcherTbsInside = patternTbsInside.matcher(inventoryPre); + while (matcherTbsInside.find()) { + if (matcherTbsInside.groupCount() == 2 && matcherTbsInside.group(2) != null) { + final String inventoryItem = matcherTbsInside.group(2).toLowerCase(); + if (inventoryItem.equals("premium member only cache")) { + continue; + } else { + if (cache.inventoryItems <= 0) { + cache.inventoryItems = 1; + } + } + } + } + } catch (Exception e) { + // failed to parse cache inventory info + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache inventory info"); + } + } + + // premium cache + if (row.indexOf("/images/small_profile.gif") != -1) { + cache.members = true; + } else { + cache.members = false; + } + + // found it + if (row.indexOf("/images/icons/icon_smile") != -1) { + cache.found = true; + } else { + cache.found = false; + } + + // own it + if (row.indexOf("/images/silk/star.png") != -1) { + cache.own = true; + } else { + cache.own = false; + } + + // id + try { + final Matcher matcherId = patternId.matcher(row); + while (matcherId.find()) { + if (matcherId.groupCount() > 0) { + cache.cacheId = matcherId.group(1); + cids.add(cache.cacheId); + } + } + } catch (Exception e) { + // failed to parse cache id + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache id"); + } + + // favourite count + try { + final Matcher matcherFavourite = patternFavourite.matcher(row); + while (matcherFavourite.find()) { + if (matcherFavourite.groupCount() > 0) { + cache.favouriteCnt = Integer.parseInt(matcherFavourite.group(1)); + } + } + } catch (Exception e) { + // failed to parse favourite count + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse favourite count"); + } + + if (cache.nameSp == null) { + cache.nameSp = (new Spannable.Factory()).newSpannable(cache.name); + if (cache.disabled || cache.archived) { // strike + cache.nameSp.setSpan(new StrikethroughSpan(), 0, cache.nameSp.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + caches.cacheList.add(cache); + } + + // total caches found + try { + final Matcher matcherTotalCnt = patternTotalCnt.matcher(page); + while (matcherTotalCnt.find()) { + if (matcherTotalCnt.groupCount() > 0) { + if (matcherTotalCnt.group(1) != null) { + caches.totalCnt = Integer.valueOf(matcherTotalCnt.group(1)); + } + } + } + } catch (Exception e) { + // failed to parse cache count + Log.w(cgSettings.tag, "cgeoBase.parseSearch: Failed to parse cache count"); + } + + if (thread != null && recaptchaChallenge != null) { + if (thread.getText() == null) { + thread.waitForUser(); + } + + recaptchaText = thread.getText(); + } + + if (cids.size() > 0 && (recaptchaChallenge == null || (recaptchaChallenge != null && StringUtils.isNotBlank(recaptchaText)))) { + Log.i(cgSettings.tag, "Trying to get .loc for " + cids.size() + " caches"); + + try { + // get coordinates for parsed caches + final String host = "www.geocaching.com"; + final String path = "/seek/nearest.aspx"; + final StringBuilder params = new StringBuilder(); + params.append("__EVENTTARGET=&__EVENTARGUMENT="); + if (ArrayUtils.isNotEmpty(caches.viewstates)) { + params.append("&__VIEWSTATE="); + params.append(urlencode_rfc3986(caches.viewstates[0])); + if (caches.viewstates.length > 1) { + for (int i = 1; i < caches.viewstates.length; i++) { + params.append("&__VIEWSTATE" + i + "="); + params.append(urlencode_rfc3986(caches.viewstates[i])); + } + params.append("&__VIEWSTATEFIELDCOUNT=" + caches.viewstates.length); + } + } + for (String cid : cids) { + params.append("&CID="); + params.append(urlencode_rfc3986(cid)); + } + + if (recaptchaChallenge != null && StringUtils.isNotBlank(recaptchaText)) { + params.append("&recaptcha_challenge_field="); + params.append(urlencode_rfc3986(recaptchaChallenge)); + params.append("&recaptcha_response_field="); + params.append(urlencode_rfc3986(recaptchaText)); + } + params.append("&ctl00%24ContentBody%24uxDownloadLoc=Download+Waypoints"); + + final String coordinates = request(false, host, path, "POST", params.toString(), 0, true).getData(); + + if (StringUtils.isNotBlank(coordinates)) { + if (coordinates.indexOf("You have not agreed to the license agreement. The license agreement is required before you can start downloading GPX or LOC files from Geocaching.com") > -1) { + Log.i(cgSettings.tag, "User has not agreed to the license agreement. Can\'t download .loc file."); + + caches.error = errorRetrieve.get(-7); + + return caches; + } + } + + LocParser.parseLoc(caches, coordinates); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.parseSearch.CIDs: " + e.toString()); + } + } + + // get direction images + if (settings.getLoadDirImg()) + { + for (cgCache oneCache : caches.cacheList) { + if (oneCache.coords == null && oneCache.directionImg != null) { + cgDirectionImg.getDrawable(oneCache.geocode, oneCache.directionImg); + } + } + } + + // get ratings + if (guids.size() > 0) { + Log.i(cgSettings.tag, "Trying to get ratings for " + cids.size() + " caches"); + + try { + final Map<String, cgRating> ratings = getRating(guids, null); + + if (CollectionUtils.isNotEmpty(ratings)) { + // save found cache coordinates + for (cgCache oneCache : caches.cacheList) { + if (ratings.containsKey(oneCache.guid)) { + cgRating thisRating = ratings.get(oneCache.guid); + + oneCache.rating = thisRating.rating; + oneCache.votes = thisRating.votes; + oneCache.myVote = thisRating.myVote; + } + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.parseSearch.GCvote: " + e.toString()); + } + } + + return caches; + } + + public static cgCacheWrap parseMapJSON(String url, String data) { + if (StringUtils.isEmpty(data)) { + Log.e(cgSettings.tag, "cgeoBase.parseMapJSON: No page given"); + return null; + } + + final cgCacheWrap caches = new cgCacheWrap(); + caches.url = url; + + try { + final JSONObject yoDawg = new JSONObject(data); + final String json = yoDawg.getString("d"); + + if (StringUtils.isBlank(json)) { + Log.e(cgSettings.tag, "cgeoBase.parseMapJSON: No JSON inside JSON"); + return null; + } + + final JSONObject dataJSON = new JSONObject(json); + final JSONObject extra = dataJSON.getJSONObject("cs"); + if (extra != null && extra.length() > 0) { + int count = extra.getInt("count"); + + if (count > 0 && extra.has("cc")) { + final JSONArray cachesData = extra.getJSONArray("cc"); + if (cachesData != null && cachesData.length() > 0) { + JSONObject oneCache = null; + for (int i = 0; i < count; i++) { + oneCache = cachesData.getJSONObject(i); + if (oneCache == null) { + break; + } + + final cgCache cacheToAdd = new cgCache(); + cacheToAdd.reliableLatLon = false; + cacheToAdd.geocode = oneCache.getString("gc"); + cacheToAdd.coords = new Geopoint(oneCache.getDouble("lat"), oneCache.getDouble("lon")); + cacheToAdd.name = oneCache.getString("nn"); + cacheToAdd.found = oneCache.getBoolean("f"); + cacheToAdd.own = oneCache.getBoolean("o"); + cacheToAdd.disabled = !oneCache.getBoolean("ia"); + int ctid = oneCache.getInt("ctid"); + if (ctid == 2) { + cacheToAdd.type = "traditional"; + } else if (ctid == 3) { + cacheToAdd.type = "multi"; + } else if (ctid == 4) { + cacheToAdd.type = "virtual"; + } else if (ctid == 5) { + cacheToAdd.type = "letterbox"; + } else if (ctid == 6) { + cacheToAdd.type = "event"; + } else if (ctid == 8) { + cacheToAdd.type = "mystery"; + } else if (ctid == 11) { + cacheToAdd.type = "webcam"; + } else if (ctid == 13) { + cacheToAdd.type = "cito"; + } else if (ctid == 137) { + cacheToAdd.type = "earth"; + } else if (ctid == 453) { + cacheToAdd.type = "mega"; + } else if (ctid == 1858) { + cacheToAdd.type = "wherigo"; + } else if (ctid == 3653) { + cacheToAdd.type = "lost"; + } + + caches.cacheList.add(cacheToAdd); + } + } + } else { + Log.w(cgSettings.tag, "There are no caches in viewport"); + } + caches.totalCnt = caches.cacheList.size(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.parseMapJSON: " + e.toString()); + } + + return caches; + } + + public cgCacheWrap parseCache(String page, int reason) { + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: No page given"); + return null; + } + + final cgCacheWrap caches = new cgCacheWrap(); + final cgCache cache = new cgCache(); + + if (page.indexOf("Cache is Unpublished") > -1) { + caches.error = "cache was unpublished"; + return caches; + } + + if (page.indexOf("Sorry, the owner of this listing has made it viewable to Premium Members only.") != -1) { + caches.error = "requested cache is for premium members only"; + return caches; + } + + if (page.indexOf("has chosen to make this cache listing visible to Premium Members only.") != -1) { + caches.error = "requested cache is for premium members only"; + return caches; + } + + if (page.indexOf("<li>This cache is temporarily unavailable.") != -1) { + cache.disabled = true; + } else { + cache.disabled = false; + } + + if (page.indexOf("<li>This cache has been archived,") != -1) { + cache.archived = true; + } else { + cache.archived = false; + } + + if (page.indexOf("<p class=\"Warning\">This is a Premium Member Only cache.</p>") != -1) { + cache.members = true; + } else { + cache.members = false; + } + + cache.reason = reason; + + // cache geocode + try { + final Matcher matcherGeocode = patternGeocode.matcher(page); + if (matcherGeocode.find() && matcherGeocode.groupCount() > 0) { + cache.geocode = getMatch(matcherGeocode.group(1)); + } + } catch (Exception e) { + // failed to parse cache geocode + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache geocode"); + } + + // cache id + try { + final Matcher matcherCacheId = patternCacheId.matcher(page); + if (matcherCacheId.find() && matcherCacheId.groupCount() > 0) { + cache.cacheId = getMatch(matcherCacheId.group(1)); + } + } catch (Exception e) { + // failed to parse cache id + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache id"); + } + + // cache guid + try { + final Matcher matcherCacheGuid = patternCacheGuid.matcher(page); + if (matcherCacheGuid.find() && matcherCacheGuid.groupCount() > 0) { + cache.guid = getMatch(matcherCacheGuid.group(1)); + } + } catch (Exception e) { + // failed to parse cache guid + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache guid"); + } + + // name + try { + final Matcher matcherName = patternName.matcher(page); + if (matcherName.find() && matcherName.groupCount() > 0) { + cache.name = Html.fromHtml(matcherName.group(1)).toString(); + } + } catch (Exception e) { + // failed to parse cache name + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache name"); + } + + // owner real name + try { + final Matcher matcherOwnerReal = patternOwnerReal.matcher(page); + if (matcherOwnerReal.find() && matcherOwnerReal.groupCount() > 0) { + cache.ownerReal = URLDecoder.decode(matcherOwnerReal.group(1)); + } + } catch (Exception e) { + // failed to parse owner real name + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache owner real name"); + } + + final String username = settings.getUsername(); + if (cache.ownerReal != null && username != null && cache.ownerReal.equalsIgnoreCase(username)) { + cache.own = true; + } + + int pos = -1; + String tableInside = page; + + pos = tableInside.indexOf("id=\"cacheDetails\""); + if (pos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: ID \"cacheDetails\" not found on page"); + return null; + } + + tableInside = tableInside.substring(pos); + + pos = tableInside.indexOf("<div class=\"CacheInformationTable\""); + if (pos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: ID \"CacheInformationTable\" not found on page"); + return null; + } + + tableInside = tableInside.substring(0, pos); + + if (StringUtils.isNotBlank(tableInside)) { + // cache terrain + try { + final Matcher matcherTerrain = patternTerrain.matcher(tableInside); + if (matcherTerrain.find() && matcherTerrain.groupCount() > 0) { + cache.terrain = new Float(Pattern.compile("_").matcher(matcherTerrain.group(1)).replaceAll(".")); + } + } catch (Exception e) { + // failed to parse terrain + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache terrain"); + } + + // cache difficulty + try { + final Matcher matcherDifficulty = patternDifficulty.matcher(tableInside); + if (matcherDifficulty.find() && matcherDifficulty.groupCount() > 0) { + cache.difficulty = new Float(Pattern.compile("_").matcher(matcherDifficulty.group(1)).replaceAll(".")); + } + } catch (Exception e) { + // failed to parse difficulty + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache difficulty"); + } + + // owner + try { + final Matcher matcherOwner = patternOwner.matcher(tableInside); + if (matcherOwner.find() && matcherOwner.groupCount() > 0) { + cache.owner = Html.fromHtml(matcherOwner.group(2)).toString(); + } + } catch (Exception e) { + // failed to parse owner + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache owner"); + } + + // hidden + try { + final Matcher matcherHidden = patternHidden.matcher(tableInside); + if (matcherHidden.find() && matcherHidden.groupCount() > 0) { + cache.hidden = parseGcCustomDate(matcherHidden.group(1)); + } + } catch (ParseException e) { + // failed to parse cache hidden date + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache hidden date"); + } + + if (cache.hidden == null) { + // event date + try { + final Matcher matcherHiddenEvent = patternHiddenEvent.matcher(tableInside); + if (matcherHiddenEvent.find() && matcherHiddenEvent.groupCount() > 0) { + cache.hidden = parseGcCustomDate(matcherHiddenEvent.group(1)); + } + } catch (ParseException e) { + // failed to parse cache event date + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache event date"); + } + } + + // favourite + try { + final Matcher matcherFavourite = patternFavourite.matcher(tableInside); + if (matcherFavourite.find() && matcherFavourite.groupCount() > 0) { + cache.favouriteCnt = Integer.parseInt(matcherFavourite.group(1)); + } + } catch (Exception e) { + // failed to parse favourite count + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse favourite count"); + } + + // cache size + try { + final Matcher matcherSize = patternSize.matcher(tableInside); + if (matcherSize.find() && matcherSize.groupCount() > 0) { + cache.size = CacheSize.FIND_BY_ID.get(getMatch(matcherSize.group(1)).toLowerCase()); + } + } catch (Exception e) { + // failed to parse size + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache size"); + } + } + + // cache found + cache.found = patternFound.matcher(page).find() || patternFoundAlternative.matcher(page).find(); + + // cache type + try { + final Matcher matcherType = patternType.matcher(page); + if (matcherType.find() && matcherType.groupCount() > 0) { + cache.type = cacheTypes.get(matcherType.group(1).toLowerCase()); + } + } catch (Exception e) { + // failed to parse type + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache type"); + } + + // on watchlist + try { + final Matcher matcher = patternOnWatchlist.matcher(page); + cache.onWatchlist = matcher.find(); + } catch (Exception e) { + // failed to parse watchlist state + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse watchlist state"); + } + + // latitude and logitude + try { + final Matcher matcherLatLon = patternLatLon.matcher(page); + if (matcherLatLon.find() && matcherLatLon.groupCount() > 0) { + cache.latlon = getMatch(matcherLatLon.group(2)); // first is <b> + + Map<String, Object> tmp = cgBase.parseLatlon(cache.latlon); + if (tmp.size() > 0) { + cache.coords = new Geopoint((Double) tmp.get("latitude"), (Double) tmp.get("longitude")); + cache.latitudeString = (String) tmp.get("latitudeString"); + cache.longitudeString = (String) tmp.get("longitudeString"); + cache.reliableLatLon = true; + } + tmp = null; + } + } catch (Exception e) { + // failed to parse latitude and/or longitude + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache coordinates"); + } + + // cache location + try { + final Matcher matcherLocation = patternLocation.matcher(page); + if (matcherLocation.find() && matcherLocation.groupCount() > 0) { + cache.location = getMatch(matcherLocation.group(1)); + } + } catch (Exception e) { + // failed to parse location + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache location"); + } + + // cache hint + try { + final Matcher matcherHint = patternHint.matcher(page); + if (matcherHint.find() && matcherHint.groupCount() > 2 && matcherHint.group(3) != null) { + // replace linebreak and paragraph tags + String hint = Pattern.compile("<(br|p)[^>]*>").matcher(matcherHint.group(3)).replaceAll("\n"); + if (hint != null) { + cache.hint = hint.replaceAll(Pattern.quote("</p>"), "").trim(); + } + } + } catch (Exception e) { + // failed to parse hint + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache hint"); + } + + checkFields(cache); + /* + * // short info debug + * Log.d(cgSettings.tag, "gc-code: " + cache.geocode); + * Log.d(cgSettings.tag, "id: " + cache.cacheid); + * Log.d(cgSettings.tag, "guid: " + cache.guid); + * Log.d(cgSettings.tag, "name: " + cache.name); + * Log.d(cgSettings.tag, "terrain: " + cache.terrain); + * Log.d(cgSettings.tag, "difficulty: " + cache.difficulty); + * Log.d(cgSettings.tag, "owner: " + cache.owner); + * Log.d(cgSettings.tag, "owner (real): " + cache.ownerReal); + * Log.d(cgSettings.tag, "hidden: " + dateOutShort.format(cache.hidden)); + * Log.d(cgSettings.tag, "favorite: " + cache.favouriteCnt); + * Log.d(cgSettings.tag, "size: " + cache.size); + * if (cache.found) { + * Log.d(cgSettings.tag, "found!"); + * } else { + * Log.d(cgSettings.tag, "not found"); + * } + * Log.d(cgSettings.tag, "type: " + cache.type); + * Log.d(cgSettings.tag, "latitude: " + String.format("%.6f", cache.latitude)); + * Log.d(cgSettings.tag, "longitude: " + String.format("%.6f", cache.longitude)); + * Log.d(cgSettings.tag, "location: " + cache.location); + * Log.d(cgSettings.tag, "hint: " + cache.hint); + */ + + // cache personal note + try { + final Matcher matcherPersonalNote = patternPersonalNote.matcher(page); + if (matcherPersonalNote.find() && matcherPersonalNote.groupCount() > 0) { + cache.personalNote = getMatch(matcherPersonalNote.group(1)); + } + } catch (Exception e) { + // failed to parse cache personal note + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache personal note"); + } + + // cache short description + try { + final Matcher matcherDescShort = patternDescShort.matcher(page); + if (matcherDescShort.find() && matcherDescShort.groupCount() > 0) { + cache.shortdesc = getMatch(matcherDescShort.group(1)); + } + } catch (Exception e) { + // failed to parse short description + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache short description"); + } + + // cache description + try { + final Matcher matcherDesc = patternDesc.matcher(page); + if (matcherDesc.find() && matcherDesc.groupCount() > 0) { + cache.description = getMatch(matcherDesc.group(1)); + } + } catch (Exception e) { + // failed to parse short description + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache description"); + } + + // cache attributes + try { + final Matcher matcherAttributes = patternAttributes.matcher(page); + if (matcherAttributes.find() && matcherAttributes.groupCount() > 0) { + final String attributesPre = matcherAttributes.group(1); + final Matcher matcherAttributesInside = patternAttributesInside.matcher(attributesPre); + + while (matcherAttributesInside.find()) { + if (matcherAttributesInside.groupCount() > 1 && matcherAttributesInside.group(2).equalsIgnoreCase("blank") != true) { + if (cache.attributes == null) { + cache.attributes = new ArrayList<String>(); + } + // by default, use the tooltip of the attribute + String attribute = matcherAttributesInside.group(2).toLowerCase(); + + // if the image name can be recognized, use the image name as attribute + String imageName = matcherAttributesInside.group(1).trim(); + if (imageName.length() > 0) { + int start = imageName.lastIndexOf('/'); + int end = imageName.lastIndexOf('.'); + if (start >= 0 && end >= 0) { + attribute = imageName.substring(start + 1, end).replace('-', '_').toLowerCase(); + } + } + cache.attributes.add(attribute); + } + } + } + } catch (Exception e) { + // failed to parse cache attributes + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache attributes"); + } + + // cache spoilers + try { + final Matcher matcherSpoilers = patternSpoilers.matcher(page); + if (matcherSpoilers.find() && matcherSpoilers.groupCount() > 0) { + final String spoilersPre = matcherSpoilers.group(1); + final Matcher matcherSpoilersInside = patternSpoilersInside.matcher(spoilersPre); + + while (matcherSpoilersInside.find()) { + if (matcherSpoilersInside.groupCount() > 0) { + final cgImage spoiler = new cgImage(); + spoiler.url = matcherSpoilersInside.group(1); + + if (matcherSpoilersInside.group(2) != null) { + spoiler.title = matcherSpoilersInside.group(2); + } + if (matcherSpoilersInside.group(4) != null) { + spoiler.description = matcherSpoilersInside.group(4); + } + + if (cache.spoilers == null) { + cache.spoilers = new ArrayList<cgImage>(); + } + cache.spoilers.add(spoiler); + } + } + } + } catch (Exception e) { + // failed to parse cache spoilers + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache spoilers"); + } + + // cache inventory + try { + cache.inventoryItems = 0; + + final Matcher matcherInventory = patternInventory.matcher(page); + if (matcherInventory.find()) { + if (cache.inventory == null) { + cache.inventory = new ArrayList<cgTrackable>(); + } + + if (matcherInventory.groupCount() > 1) { + final String inventoryPre = matcherInventory.group(2); + + if (StringUtils.isNotBlank(inventoryPre)) { + final Matcher matcherInventoryInside = patternInventoryInside.matcher(inventoryPre); + + while (matcherInventoryInside.find()) { + if (matcherInventoryInside.groupCount() > 0) { + final cgTrackable inventoryItem = new cgTrackable(); + inventoryItem.guid = matcherInventoryInside.group(1); + inventoryItem.name = matcherInventoryInside.group(2); + + cache.inventory.add(inventoryItem); + cache.inventoryItems++; + } + } + } + } + } + } catch (Exception e) { + // failed to parse cache inventory + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache inventory (2)"); + } + + // cache logs counts + try + { + final Matcher matcherLogCounts = patternCountLogs.matcher(page); + + if (matcherLogCounts.find()) + { + final Matcher matcherLog = patternCountLog.matcher(matcherLogCounts.group(1)); + + while (matcherLog.find()) + { + String typeStr = matcherLog.group(1); + String countStr = matcherLog.group(2); + + if (StringUtils.isNotBlank(typeStr) + && logTypes.containsKey(typeStr.toLowerCase()) + && StringUtils.isNotBlank(countStr)) + { + cache.logCounts.put(logTypes.get(typeStr.toLowerCase()), Integer.parseInt(countStr)); + } + } + } + } catch (Exception e) + { + // failed to parse logs + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache log count"); + } + + // cache logs + loadLogsFromDetails(page, cache); + + int wpBegin = 0; + int wpEnd = 0; + + wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); + if (wpBegin != -1) { // parse waypoints + final Pattern patternWpType = Pattern.compile("\\/wpttypes\\/sm\\/(.+)\\.jpg", Pattern.CASE_INSENSITIVE); + final Pattern patternWpPrefixOrLookupOrLatlon = Pattern.compile(">([^<]*<[^>]+>)?([^<]+)(<[^>]+>[^<]*)?<\\/td>", Pattern.CASE_INSENSITIVE); + final Pattern patternWpName = Pattern.compile(">[^<]*<a[^>]+>([^<]*)<\\/a>", Pattern.CASE_INSENSITIVE); + final Pattern patternWpNote = Pattern.compile("colspan=\"6\">(.*)<\\/td>", Pattern.CASE_INSENSITIVE); + + String wpList = page.substring(wpBegin); + + wpEnd = wpList.indexOf("</p>"); + if (wpEnd > -1 && wpEnd <= wpList.length()) { + wpList = wpList.substring(0, wpEnd); + } + + if (wpList.indexOf("No additional waypoints to display.") == -1) { + wpEnd = wpList.indexOf("</table>"); + wpList = wpList.substring(0, wpEnd); + + wpBegin = wpList.indexOf("<tbody>"); + wpEnd = wpList.indexOf("</tbody>"); + if (wpBegin >= 0 && wpEnd >= 0 && wpEnd <= wpList.length()) { + wpList = wpList.substring(wpBegin + 7, wpEnd); + } + + final String[] wpItems = wpList.split("<tr"); + + String[] wp; + for (int j = 1; j < wpItems.length; j++) { + final cgWaypoint waypoint = new cgWaypoint(); + + wp = wpItems[j].split("<td"); + + // waypoint type + try { + final Matcher matcherWpType = patternWpType.matcher(wp[3]); + if (matcherWpType.find() && matcherWpType.groupCount() > 0) { + waypoint.type = matcherWpType.group(1).trim(); + } + } catch (Exception e) { + // failed to parse type + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint type"); + } + + // waypoint prefix + try { + final Matcher matcherWpPrefix = patternWpPrefixOrLookupOrLatlon.matcher(wp[4]); + if (matcherWpPrefix.find() && matcherWpPrefix.groupCount() > 1) { + waypoint.prefix = matcherWpPrefix.group(2).trim(); + } + } catch (Exception e) { + // failed to parse prefix + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint prefix"); + } + + // waypoint lookup + try { + final Matcher matcherWpLookup = patternWpPrefixOrLookupOrLatlon.matcher(wp[5]); + if (matcherWpLookup.find() && matcherWpLookup.groupCount() > 1) { + waypoint.lookup = matcherWpLookup.group(2).trim(); + } + } catch (Exception e) { + // failed to parse lookup + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint lookup"); + } + + // waypoint name + try { + final Matcher matcherWpName = patternWpName.matcher(wp[6]); + while (matcherWpName.find()) { + if (matcherWpName.groupCount() > 0) { + waypoint.name = matcherWpName.group(1); + if (StringUtils.isNotBlank(waypoint.name)) { + waypoint.name = waypoint.name.trim(); + } + } + if (matcherWpName.find() && matcherWpName.groupCount() > 0) { + waypoint.name = matcherWpName.group(1).trim(); + } + } + } catch (Exception e) { + // failed to parse name + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint name"); + } + + // waypoint latitude and logitude + try { + final Matcher matcherWpLatLon = patternWpPrefixOrLookupOrLatlon.matcher(wp[7]); + if (matcherWpLatLon.find() && matcherWpLatLon.groupCount() > 1) { + waypoint.latlon = Html.fromHtml(matcherWpLatLon.group(2)).toString(); + + final Map<String, Object> tmp = cgBase.parseLatlon(waypoint.latlon); + if (tmp.size() > 0) { + waypoint.coords = new Geopoint((Double) tmp.get("latitude"), (Double) tmp.get("longitude")); + waypoint.latitudeString = (String) tmp.get("latitudeString"); + waypoint.longitudeString = (String) tmp.get("longitudeString"); + } + } + } catch (Exception e) { + // failed to parse latitude and/or longitude + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint coordinates"); + } + + j++; + if (wpItems.length > j) { + wp = wpItems[j].split("<td"); + } + + // waypoint note + try { + final Matcher matcherWpNote = patternWpNote.matcher(wp[3]); + if (matcherWpNote.find() && matcherWpNote.groupCount() > 0) { + waypoint.note = matcherWpNote.group(1).trim(); + } + } catch (Exception e) { + // failed to parse note + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse waypoint note"); + } + + if (cache.waypoints == null) { + cache.waypoints = new ArrayList<cgWaypoint>(); + } + cache.waypoints.add(waypoint); + } + } + } + + if (cache.coords != null) { + cache.elevation = getElevation(cache.coords); + } + + final cgRating rating = getRating(cache.guid, cache.geocode); + if (rating != null) { + cache.rating = rating.rating; + cache.votes = rating.votes; + cache.myVote = rating.myVote; + } + + cache.updated = System.currentTimeMillis(); + cache.detailedUpdate = System.currentTimeMillis(); + cache.detailed = true; + caches.cacheList.add(cache); + + return caches; + } + + /** + * Load logs from a cache details page. + * + * @param page + * the text of the details page + * @param cache + * the cache object to put the logs in + */ + private void loadLogsFromDetails(final String page, final cgCache cache) { + final Matcher userTokenMatcher = patternUserToken.matcher(page); + if (!userTokenMatcher.find()) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: unable to extract userToken"); + return; + } + + final String userToken = userTokenMatcher.group(1); + final HashMap<String, String> params = new HashMap<String, String>(); + params.put("tkn", userToken); + params.put("idx", "1"); + params.put("num", "35"); + params.put("sp", "0"); + params.put("sf", "0"); + params.put("decrypt", "1"); + final cgResponse response = request(false, "www.geocaching.com", "/seek/geocache.logbook", "GET", + params, false, false, false); + if (response.getStatusCode() != 200) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: error " + response.getStatusCode() + " when requesting log information"); + return; + } + + try { + final JSONObject resp = new JSONObject(response.getData()); + if (!resp.getString("status").equals("success")) { + Log.e(cgSettings.tag, "cgeoBase.parseCache: status is " + resp.getString("status")); + return; + } + + final JSONArray data = resp.getJSONArray("data"); + + for (int index = 0; index < data.length(); index++) { + final JSONObject entry = data.getJSONObject(index); + final cgLog logDone = new cgLog(); + + // FIXME: use the "LogType" field instead of the "LogTypeImage" one. + final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); + final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); + if (logTypes.containsKey(logIconName)) { + logDone.type = logTypes.get(logIconName); + } else { + logDone.type = logTypes.get("icon_note"); + } + + try { + logDone.date = parseGcCustomDate(entry.getString("Visited")).getTime(); + } catch (ParseException e) { + Log.e(cgSettings.tag, "Failed to parse log date."); + } + + logDone.author = entry.getString("UserName"); + logDone.found = entry.getInt("GeocacheFindCount"); + logDone.log = entry.getString("LogText"); + + final JSONArray images = entry.getJSONArray("Images"); + for (int i = 0; i < images.length(); i++) { + final JSONObject image = images.getJSONObject(i); + final cgImage logImage = new cgImage(); + logImage.url = "http://img.geocaching.com/cache/log/" + image.getString("FileName"); + logImage.title = image.getString("Name"); + if (logDone.logImages == null) { + logDone.logImages = new ArrayList<cgImage>(); + } + logDone.logImages.add(logImage); + } + + if (null == cache.logs) { + cache.logs = new ArrayList<cgLog>(); + } + cache.logs.add(logDone); + } + } catch (JSONException e) { + // failed to parse logs + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache logs", e); + } + } + + private static void checkFields(cgCache cache) { + if (StringUtils.isBlank(cache.geocode)) { + Log.w(cgSettings.tag, "geo code not parsed correctly"); + } + if (StringUtils.isBlank(cache.name)) { + Log.w(cgSettings.tag, "name not parsed correctly"); + } + if (StringUtils.isBlank(cache.guid)) { + Log.w(cgSettings.tag, "guid not parsed correctly"); + } + if (cache.terrain == null || cache.terrain == 0.0) { + Log.w(cgSettings.tag, "terrain not parsed correctly"); + } + if (cache.difficulty == null || cache.difficulty == 0.0) { + Log.w(cgSettings.tag, "difficulty not parsed correctly"); + } + if (StringUtils.isBlank(cache.owner)) { + Log.w(cgSettings.tag, "owner not parsed correctly"); + } + if (StringUtils.isBlank(cache.ownerReal)) { + Log.w(cgSettings.tag, "owner real not parsed correctly"); + } + if (cache.hidden == null) { + Log.w(cgSettings.tag, "hidden not parsed correctly"); + } + if (cache.favouriteCnt == null) { + Log.w(cgSettings.tag, "favoriteCount not parsed correctly"); + } + if (cache.size == null) { + Log.w(cgSettings.tag, "size not parsed correctly"); + } + if (StringUtils.isBlank(cache.type)) { + Log.w(cgSettings.tag, "type not parsed correctly"); + } + if (cache.coords == null) { + Log.w(cgSettings.tag, "coordinates not parsed correctly"); + } + if (StringUtils.isBlank(cache.location)) { + Log.w(cgSettings.tag, "location not parsed correctly"); + } + } + + private static String getMatch(String match) { + // creating a new String via String constructor is necessary here!! + return new String(match.trim()); + // Java copies the whole page String, when matching with regular expressions + // later this would block the garbage collector, as we only need tiny parts of the page + // see http://developer.android.com/reference/java/lang/String.html#backing_array + + // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler! + } + + public Date parseGcCustomDate(String input) + throws ParseException + { + if (StringUtils.isBlank(input)) + { + throw new ParseException("Input is null", 0); + } + + input = input.trim(); + + if (null != settings + //&& null != settings.getGcCustomDate() + && gcCustomDateFormats.containsKey(settings.getGcCustomDate())) + { + try + { + return gcCustomDateFormats.get(settings.getGcCustomDate()).parse(input); + } catch (ParseException e) { + } + } + + for (SimpleDateFormat format : gcCustomDateFormats.values()) + { + try + { + return format.parse(input); + } catch (ParseException e) { + } + } + + throw new ParseException("No matching pattern", 0); + } + + public void detectGcCustomDate() + { + final String host = "www.geocaching.com"; + final String path = "/account/ManagePreferences.aspx"; + + final String result = request(false, host, path, "GET", null, false, false, false).getData(); + + final Pattern pattern = Pattern.compile("<option selected=\"selected\" value=\"([ /Mdy-]+)\">", Pattern.CASE_INSENSITIVE); + final Matcher matcher = pattern.matcher(result); + + if (matcher.find()) + { + settings.setGcCustomDate(matcher.group(1)); + } + } + + public cgRating getRating(String guid, String geocode) { + List<String> guids = null; + List<String> geocodes = null; + + if (StringUtils.isNotBlank(guid)) { + guids = new ArrayList<String>(); + guids.add(guid); + } else if (StringUtils.isNotBlank(geocode)) { + geocodes = new ArrayList<String>(); + geocodes.add(geocode); + } else { + return null; + } + + final Map<String, cgRating> ratings = getRating(guids, geocodes); + if (ratings != null) { + for (Entry<String, cgRating> entry : ratings.entrySet()) { + return entry.getValue(); + } + } + + return null; + } + + public Map<String, cgRating> getRating(List<String> guids, List<String> geocodes) { + if (guids == null && geocodes == null) { + return null; + } + + final Map<String, cgRating> ratings = new HashMap<String, cgRating>(); + + try { + final Map<String, String> params = new HashMap<String, String>(); + if (settings.isLogin()) { + final Map<String, String> login = settings.getGCvoteLogin(); + if (login != null) { + params.put("userName", login.get("username")); + params.put("password", login.get("password")); + } + } + if (CollectionUtils.isNotEmpty(guids)) { + params.put("cacheIds", implode(",", guids.toArray())); + } else { + params.put("waypoints", implode(",", geocodes.toArray())); + } + params.put("version", "cgeo"); + final String votes = request(false, "gcvote.com", "/getVotes.php", "GET", params, false, false, false).getData(); + if (votes == null) { + return null; + } + + final Pattern patternLogIn = Pattern.compile("loggedIn='([^']+)'", Pattern.CASE_INSENSITIVE); + final Pattern patternGuid = Pattern.compile("cacheId='([^']+)'", Pattern.CASE_INSENSITIVE); + final Pattern patternRating = Pattern.compile("voteAvg='([0-9\\.]+)'", Pattern.CASE_INSENSITIVE); + final Pattern patternVotes = Pattern.compile("voteCnt='([0-9]+)'", Pattern.CASE_INSENSITIVE); + final Pattern patternVote = Pattern.compile("voteUser='([0-9\\.]+)'", Pattern.CASE_INSENSITIVE); + + String voteData = null; + final Pattern patternVoteElement = Pattern.compile("<vote ([^>]+)>", Pattern.CASE_INSENSITIVE); + final Matcher matcherVoteElement = patternVoteElement.matcher(votes); + while (matcherVoteElement.find()) { + if (matcherVoteElement.groupCount() > 0) { + voteData = matcherVoteElement.group(1); + } + + if (voteData == null) { + continue; + } + + String guid = null; + cgRating rating = new cgRating(); + boolean loggedIn = false; + + try { + final Matcher matcherGuid = patternGuid.matcher(voteData); + if (matcherGuid.find()) { + if (matcherGuid.groupCount() > 0) { + guid = (String) matcherGuid.group(1); + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getRating: Failed to parse guid"); + } + + try { + final Matcher matcherLoggedIn = patternLogIn.matcher(votes); + if (matcherLoggedIn.find()) { + if (matcherLoggedIn.groupCount() > 0) { + if (matcherLoggedIn.group(1).equalsIgnoreCase("true")) { + loggedIn = true; + } + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getRating: Failed to parse loggedIn"); + } + + try { + final Matcher matcherRating = patternRating.matcher(voteData); + if (matcherRating.find()) { + if (matcherRating.groupCount() > 0) { + rating.rating = Float.parseFloat(matcherRating.group(1)); + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getRating: Failed to parse rating"); + } + + try { + final Matcher matcherVotes = patternVotes.matcher(voteData); + if (matcherVotes.find()) { + if (matcherVotes.groupCount() > 0) { + rating.votes = Integer.parseInt(matcherVotes.group(1)); + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getRating: Failed to parse vote count"); + } + + if (loggedIn) { + try { + final Matcher matcherVote = patternVote.matcher(voteData); + if (matcherVote.find()) { + if (matcherVote.groupCount() > 0) { + rating.myVote = Float.parseFloat(matcherVote.group(1)); + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getRating: Failed to parse user's vote"); + } + } + + if (StringUtils.isNotBlank(guid)) { + ratings.put(guid, rating); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.getRating: " + e.toString()); + } + + return ratings; + } + + public cgTrackable parseTrackable(String page) { + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.parseTrackable: No page given"); + return null; + } + + final cgTrackable trackable = new cgTrackable(); + + // trackable geocode + try { + final Matcher matcherGeocode = PATTERN_TRACKABLE_Geocode.matcher(page); + if (matcherGeocode.find() && matcherGeocode.groupCount() > 0) { + trackable.geocode = matcherGeocode.group(1).toUpperCase(); + } + } catch (Exception e) { + // failed to parse trackable geocode + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable geocode"); + } + + // trackable id + try { + final Matcher matcherTrackableId = PATTERN_TRACKABLE_TrackableId.matcher(page); + if (matcherTrackableId.find() && matcherTrackableId.groupCount() > 0) { + trackable.guid = matcherTrackableId.group(1); + } + } catch (Exception e) { + // failed to parse trackable id + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable id"); + } + + // trackable icon + try { + final Matcher matcherTrackableIcon = PATTERN_TRACKABLE_Icon.matcher(page); + if (matcherTrackableIcon.find() && matcherTrackableIcon.groupCount() > 0) { + trackable.iconUrl = matcherTrackableIcon.group(1); + } + } catch (Exception e) { + // failed to parse trackable icon + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable icon"); + } + + // trackable name + try { + final Matcher matcherName = PATTERN_TRACKABLE_Name.matcher(page); + if (matcherName.find() && matcherName.groupCount() > 1) { + trackable.name = matcherName.group(2); + } + } catch (Exception e) { + // failed to parse trackable name + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable name"); + } + + // trackable type + if (StringUtils.isNotBlank(trackable.name)) { + try { + final Matcher matcherType = PATTERN_TRACKABLE_Type.matcher(page); + if (matcherType.find() && matcherType.groupCount() > 0) { + trackable.type = matcherType.group(1); + } + } catch (Exception e) { + // failed to parse trackable type + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable type"); + } + } + + // trackable owner name + try { + final Matcher matcherOwner = PATTERN_TRACKABLE_Owner.matcher(page); + if (matcherOwner.find() && matcherOwner.groupCount() > 0) { + trackable.ownerGuid = matcherOwner.group(1); + trackable.owner = matcherOwner.group(2); + } + } catch (Exception e) { + // failed to parse trackable owner name + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable owner name"); + } + + // trackable origin + try { + final Matcher matcherOrigin = PATTERN_TRACKABLE_Origin.matcher(page); + if (matcherOrigin.find() && matcherOrigin.groupCount() > 0) { + trackable.origin = matcherOrigin.group(1); + } + } catch (Exception e) { + // failed to parse trackable origin + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable origin"); + } + + // trackable spotted + try { + final Matcher matcherSpottedCache = PATTERN_TRACKABLE_SpottedCache.matcher(page); + if (matcherSpottedCache.find() && matcherSpottedCache.groupCount() > 0) { + trackable.spottedGuid = matcherSpottedCache.group(1); + trackable.spottedName = matcherSpottedCache.group(2); + trackable.spottedType = cgTrackable.SPOTTED_CACHE; + } + + final Matcher matcherSpottedUser = PATTERN_TRACKABLE_SpottedUser.matcher(page); + if (matcherSpottedUser.find() && matcherSpottedUser.groupCount() > 0) { + trackable.spottedGuid = matcherSpottedUser.group(1); + trackable.spottedName = matcherSpottedUser.group(2); + trackable.spottedType = cgTrackable.SPOTTED_USER; + } + + final Matcher matcherSpottedUnknown = PATTERN_TRACKABLE_SpottedUnknown.matcher(page); + if (matcherSpottedUnknown.find()) { + trackable.spottedType = cgTrackable.SPOTTED_UNKNOWN; + } + + final Matcher matcherSpottedOwner = PATTERN_TRACKABLE_SpottedOwner.matcher(page); + if (matcherSpottedOwner.find()) { + trackable.spottedType = cgTrackable.SPOTTED_OWNER; + } + } catch (Exception e) { + // failed to parse trackable last known place + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable last known place"); + } + + // released + try { + final Matcher matcherReleased = PATTERN_TRACKABLE_Released.matcher(page); + if (matcherReleased.find() && matcherReleased.groupCount() > 0 && matcherReleased.group(1) != null) { + try { + if (trackable.released == null) { + trackable.released = dateTbIn1.parse(matcherReleased.group(1)); + } + } catch (Exception e) { + // + } + + try { + if (trackable.released == null) { + trackable.released = dateTbIn2.parse(matcherReleased.group(1)); + } + } catch (Exception e) { + // + } + } + } catch (Exception e) { + // failed to parse trackable released date + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable released date"); + } + + // trackable distance + try { + final Matcher matcherDistance = PATTERN_TRACKABLE_Distance.matcher(page); + if (matcherDistance.find() && matcherDistance.groupCount() > 0) { + try { + trackable.distance = DistanceParser.parseDistance(matcherDistance.group(1), settings.units); + } catch (NumberFormatException e) { + trackable.distance = null; + throw e; + } + } + } catch (Exception e) { + // failed to parse trackable distance + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable distance"); + } + + // trackable goal + try { + final Matcher matcherGoal = PATTERN_TRACKABLE_Goal.matcher(page); + if (matcherGoal.find() && matcherGoal.groupCount() > 0) { + trackable.goal = matcherGoal.group(1); + } + } catch (Exception e) { + // failed to parse trackable goal + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable goal"); + } + + // trackable details & image + try { + final Matcher matcherDetailsImage = PATTERN_TRACKABLE_DetailsImage.matcher(page); + if (matcherDetailsImage.find() && matcherDetailsImage.groupCount() > 0) { + final String image = matcherDetailsImage.group(3); + final String details = matcherDetailsImage.group(4); + + if (image != null) { + trackable.image = image; + } + if (details != null) { + trackable.details = details; + } + } + } catch (Exception e) { + // failed to parse trackable details & image + Log.w(cgSettings.tag, "cgeoBase.parseTrackable: Failed to parse trackable details & image"); + } + + // trackable logs + try + { + final Matcher matcherLogs = PATTERN_TRACKABLE_Log.matcher(page); + /* + * 1. Type (img) + * 2. Date + * 3. Author + * 4. Cache-GUID + * 5. Cache-name + * 6. Logtext + */ + while (matcherLogs.find()) + { + final cgLog logDone = new cgLog(); + + if (logTypes.containsKey(matcherLogs.group(1).toLowerCase())) + { + logDone.type = logTypes.get(matcherLogs.group(1).toLowerCase()); + } + else + { + logDone.type = logTypes.get("icon_note"); + } + + logDone.author = Html.fromHtml(matcherLogs.group(3)).toString(); + + try + { + logDone.date = parseGcCustomDate(matcherLogs.group(2)).getTime(); + } catch (ParseException e) { + } + + logDone.log = matcherLogs.group(6).trim(); + + if (matcherLogs.group(4) != null && matcherLogs.group(5) != null) + { + logDone.cacheGuid = matcherLogs.group(4); + logDone.cacheName = matcherLogs.group(5); + } + + trackable.logs.add(logDone); + } + } catch (Exception e) { + // failed to parse logs + Log.w(cgSettings.tag, "cgeoBase.parseCache: Failed to parse cache logs"); + } + + app.saveTrackable(trackable); + + return trackable; + } + + public static List<Integer> parseTypes(String page) { + if (StringUtils.isEmpty(page)) { + return null; + } + + final List<Integer> types = new ArrayList<Integer>(); + + final Pattern typeBoxPattern = Pattern.compile("<select name=\"ctl00\\$ContentBody\\$LogBookPanel1\\$ddLogType\" id=\"ctl00_ContentBody_LogBookPanel1_ddLogType\"[^>]*>" + + "(([^<]*<option[^>]*>[^<]+</option>)+)[^<]*</select>", Pattern.CASE_INSENSITIVE); + final Matcher typeBoxMatcher = typeBoxPattern.matcher(page); + String typesText = null; + if (typeBoxMatcher.find()) { + if (typeBoxMatcher.groupCount() > 0) { + typesText = typeBoxMatcher.group(1); + } + } + + if (typesText != null) { + final Pattern typePattern = Pattern.compile("<option( selected=\"selected\")? value=\"(\\d+)\">[^<]+</option>", Pattern.CASE_INSENSITIVE); + final Matcher typeMatcher = typePattern.matcher(typesText); + while (typeMatcher.find()) { + if (typeMatcher.groupCount() > 1) { + final int type = Integer.parseInt(typeMatcher.group(2)); + + if (type > 0) { + types.add(type); + } + } + } + } + + return types; + } + + public static List<cgTrackableLog> parseTrackableLog(String page) { + if (StringUtils.isEmpty(page)) { + return null; + } + + final List<cgTrackableLog> trackables = new ArrayList<cgTrackableLog>(); + + int startPos = -1; + int endPos = -1; + + startPos = page.indexOf("<table id=\"tblTravelBugs\""); + if (startPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseTrackableLog: ID \"tblTravelBugs\" not found on page"); + return null; + } + + page = page.substring(startPos); // cut on <table + + endPos = page.indexOf("</table>"); + if (endPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseTrackableLog: end of ID \"tblTravelBugs\" not found on page"); + return null; + } + + page = page.substring(0, endPos); // cut on </table> + + startPos = page.indexOf("<tbody>"); + if (startPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseTrackableLog: tbody not found on page"); + return null; + } + + page = page.substring(startPos); // cut on <tbody> + + endPos = page.indexOf("</tbody>"); + if (endPos == -1) { + Log.e(cgSettings.tag, "cgeoBase.parseTrackableLog: end of tbody not found on page"); + return null; + } + + page = page.substring(0, endPos); // cut on </tbody> + + final Pattern trackablePattern = Pattern.compile("<tr id=\"ctl00_ContentBody_LogBookPanel1_uxTrackables_repTravelBugs_ctl[0-9]+_row\"[^>]*>" + + "[^<]*<td>[^<]*<a href=\"[^\"]+\">([A-Z0-9]+)</a>[^<]*</td>[^<]*<td>([^<]+)</td>[^<]*<td>" + + "[^<]*<select name=\"ctl00\\$ContentBody\\$LogBookPanel1\\$uxTrackables\\$repTravelBugs\\$ctl([0-9]+)\\$ddlAction\"[^>]*>" + + "([^<]*<option value=\"([0-9]+)(_[a-z]+)?\">[^<]+</option>)+" + + "[^<]*</select>[^<]*</td>[^<]*</tr>", Pattern.CASE_INSENSITIVE); + final Matcher trackableMatcher = trackablePattern.matcher(page); + while (trackableMatcher.find()) { + if (trackableMatcher.groupCount() > 0) { + final cgTrackableLog trackable = new cgTrackableLog(); + + if (trackableMatcher.group(1) != null) { + trackable.trackCode = trackableMatcher.group(1); + } else { + continue; + } + if (trackableMatcher.group(2) != null) { + trackable.name = Html.fromHtml(trackableMatcher.group(2)).toString(); + } else { + continue; + } + if (trackableMatcher.group(3) != null) { + trackable.ctl = Integer.valueOf(trackableMatcher.group(3)); + } else { + continue; + } + if (trackableMatcher.group(5) != null) { + trackable.id = Integer.valueOf(trackableMatcher.group(5)); + } else { + continue; + } + + Log.i(cgSettings.tag, "Trackable in inventory (#" + trackable.ctl + "/" + trackable.id + "): " + trackable.trackCode + " - " + trackable.name); + + trackables.add(trackable); + } + } + + return trackables; + } + + public static String stripParagraphs(String text) { + if (StringUtils.isBlank(text)) { + return ""; + } + + final Pattern patternP = Pattern.compile("(<p>|</p>|<br \\/>|<br>)", Pattern.CASE_INSENSITIVE); + final Pattern patternP2 = Pattern.compile("([ ]+)", Pattern.CASE_INSENSITIVE); + final Matcher matcherP = patternP.matcher(text); + final Matcher matcherP2 = patternP2.matcher(text); + + matcherP.replaceAll(" "); + matcherP2.replaceAll(" "); + + return text.trim(); + } + + public static String stripTags(String text) { + if (StringUtils.isBlank(text)) { + return ""; + } + + final Pattern patternP = Pattern.compile("(<[^>]+>)", Pattern.CASE_INSENSITIVE); + final Matcher matcherP = patternP.matcher(text); + + matcherP.replaceAll(" "); + + return text.trim(); + } + + public String getHumanDistance(Float distance) { + if (distance == null) { + return "?"; + } + + if (settings.units == cgSettings.unitsImperial) { + distance /= miles2km; + if (distance > 100) { + return String.format(Locale.getDefault(), "%.0f", Double.valueOf(Math.round(distance))) + " mi"; + } else if (distance > 0.5) { + return String.format(Locale.getDefault(), "%.1f", Double.valueOf(Math.round(distance * 10.0) / 10.0)) + " mi"; + } else if (distance > 0.1) { + return String.format(Locale.getDefault(), "%.2f", Double.valueOf(Math.round(distance * 100.0) / 100.0)) + " mi"; + } else if (distance > 0.05) { + return String.format(Locale.getDefault(), "%.0f", Double.valueOf(Math.round(distance * 5280.0))) + " ft"; + } else if (distance > 0.01) { + return String.format(Locale.getDefault(), "%.1f", Double.valueOf(Math.round(distance * 5280 * 10.0) / 10.0)) + " ft"; + } else { + return String.format(Locale.getDefault(), "%.2f", Double.valueOf(Math.round(distance * 5280 * 100.0) / 100.0)) + " ft"; + } + } else { + if (distance > 100) { + return String.format(Locale.getDefault(), "%.0f", Double.valueOf(Math.round(distance))) + " km"; + } else if (distance > 10) { + return String.format(Locale.getDefault(), "%.1f", Double.valueOf(Math.round(distance * 10.0) / 10.0)) + " km"; + } else if (distance > 1) { + return String.format(Locale.getDefault(), "%.2f", Double.valueOf(Math.round(distance * 100.0) / 100.0)) + " km"; + } else if (distance > 0.1) { + return String.format(Locale.getDefault(), "%.0f", Double.valueOf(Math.round(distance * 1000.0))) + " m"; + } else if (distance > 0.01) { + return String.format(Locale.getDefault(), "%.1f", Double.valueOf(Math.round(distance * 1000.0 * 10.0) / 10.0)) + " m"; + } else { + return String.format(Locale.getDefault(), "%.2f", Double.valueOf(Math.round(distance * 1000.0 * 100.0) / 100.0)) + " m"; + } + } + } + + public String getHumanSpeed(float speed) { + double kph = speed * 3.6; + String unit = "km/h"; + + if (this.settings.units == cgSettings.unitsImperial) { + kph /= miles2km; + unit = "mph"; + } + + if (kph < 10.0) { + return String.format(Locale.getDefault(), "%.1f", Double.valueOf((Math.round(kph * 10.0) / 10.0))) + " " + unit; + } else { + return String.format(Locale.getDefault(), "%.0f", Double.valueOf(Math.round(kph))) + " " + unit; + } + } + + public static Map<String, Object> parseLatlon(String latlon) { + final Map<String, Object> result = new HashMap<String, Object>(); + final Pattern patternLatlon = Pattern.compile("([NS])[^\\d]*(\\d+)[^°]*° (\\d+)\\.(\\d+) ([WE])[^\\d]*(\\d+)[^°]*° (\\d+)\\.(\\d+)", Pattern.CASE_INSENSITIVE); + final Matcher matcherLatlon = patternLatlon.matcher(latlon); + + while (matcherLatlon.find()) { + if (matcherLatlon.groupCount() > 0) { + result.put("latitudeString", (String) (matcherLatlon.group(1) + " " + matcherLatlon.group(2) + "° " + matcherLatlon.group(3) + "." + matcherLatlon.group(4))); + result.put("longitudeString", (String) (matcherLatlon.group(5) + " " + matcherLatlon.group(6) + "° " + matcherLatlon.group(7) + "." + matcherLatlon.group(8))); + int latNegative = -1; + int lonNegative = -1; + if (matcherLatlon.group(1).equalsIgnoreCase("N")) { + latNegative = 1; + } + if (matcherLatlon.group(5).equalsIgnoreCase("E")) { + lonNegative = 1; + } + result.put("latitude", Double.valueOf(latNegative * (Float.valueOf(matcherLatlon.group(2)) + Float.valueOf(matcherLatlon.group(3) + "." + matcherLatlon.group(4)) / 60))); + result.put("longitude", Double.valueOf(lonNegative * (Float.valueOf(matcherLatlon.group(6)) + Float.valueOf(matcherLatlon.group(7) + "." + matcherLatlon.group(8)) / 60))); + } else { + Log.w(cgSettings.tag, "cgBase.parseLatlon: Failed to parse coordinates."); + } + } + + return result; + } + + private static String formatCoordinate(final Double coordIn, final boolean degrees, final String direction, final String digitsFormat) { + if (coordIn == null) { + return ""; + } + StringBuilder formatted = new StringBuilder(direction); + + double coordAbs = Math.abs(coordIn); + Locale locale = Locale.getDefault(); + double floor = Math.floor(coordAbs); + + formatted.append(String.format(locale, digitsFormat, floor)); + + if (degrees) { + formatted.append("° "); + } else { + formatted.append(" "); + } + formatted.append(String.format(locale, "%06.3f", ((coordAbs - floor) * 60))); + + return formatted.toString(); + } + + public static String formatLatitude(final Double coord, final boolean degrees) { + return formatCoordinate(coord, degrees, (coord >= 0) ? "N " : "S ", "%02.0f"); + } + + public static String formatLongitude(final Double coord, final boolean degrees) { + return formatCoordinate(coord, degrees, (coord >= 0) ? "E " : "W ", "%03.0f"); + } + + public static String formatCoords(final Geopoint coords, final boolean degrees) { + return formatLatitude(coords.getLatitude(), degrees) + " | " + formatLongitude(coords.getLongitude(), degrees); + } + + public UUID searchByNextPage(cgSearchThread thread, final UUID searchId, int reason, boolean showCaptcha) { + final String[] viewstates = app.getViewstates(searchId); + + String url = app.getUrl(searchId); + + if (StringUtils.isBlank(url)) { + Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No url found"); + return searchId; + } + + if (isEmpty(viewstates)) { + Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No viewstate given"); + return searchId; + } + + String host = "www.geocaching.com"; + String path = "/"; + final String method = "POST"; + + int dash = -1; + if (url.indexOf("http://") > -1) { + url = url.substring(7); + } + + dash = url.indexOf("/"); + if (dash > -1) { + host = url.substring(0, dash); + url = url.substring(dash); + } else { + host = url; + url = ""; + } + + dash = url.indexOf("?"); + if (dash > -1) { + path = url.substring(0, dash); + } else { + path = url; + } + + final Map<String, String> params = new HashMap<String, String>(); + setViewstates(viewstates, params); + params.put("__EVENTTARGET", "ctl00$ContentBody$pgrBottom$ctl08"); + params.put("__EVENTARGUMENT", ""); + + String page = request(false, host, path, method, params, false, false, true).getData(); + if (checkLogin(page) == false) { + int loginState = login(); + if (loginState == 1) { + page = request(false, host, path, method, params, false, false, true).getData(); + } else if (loginState == -3) { + Log.i(cgSettings.tag, "Working as guest."); + } else { + app.setError(searchId, errorRetrieve.get(loginState)); + Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: Can not log in geocaching"); + return searchId; + } + } + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No data from server"); + return searchId; + } + + final cgCacheWrap caches = parseSearch(thread, url, page, showCaptcha); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + Log.e(cgSettings.tag, "cgeoBase.searchByNextPage: No cache parsed"); + return searchId; + } + + // save to application + app.setError(searchId, caches.error); + app.setViewstates(searchId, caches.viewstates); + + final List<cgCache> cacheList = new ArrayList<cgCache>(); + for (cgCache cache : caches.cacheList) { + app.addGeocode(searchId, cache.geocode); + cacheList.add(cache); + } + + app.addSearch(searchId, cacheList, true, reason); + + return searchId; + } + + public UUID searchByGeocode(Map<String, String> parameters, int reason, boolean forceReload) { + final cgSearch search = new cgSearch(); + String geocode = parameters.get("geocode"); + String guid = parameters.get("guid"); + + if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid)) { + Log.e(cgSettings.tag, "cgeoBase.searchByGeocode: No geocode nor guid given"); + return null; + } + + if (forceReload == false && reason == 0 && (app.isOffline(geocode, guid) || app.isThere(geocode, guid, true, true))) { + if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { + geocode = app.getGeocode(guid); + } + + List<cgCache> cacheList = new ArrayList<cgCache>(); + cacheList.add(app.getCacheByGeocode(geocode, true, true, true, true, true, true)); + search.addGeocode(geocode); + + app.addSearch(search, cacheList, false, reason); + + cacheList.clear(); + cacheList = null; + + return search.getCurrentId(); + } + + final String host = "www.geocaching.com"; + final String path = "/seek/cache_details.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (StringUtils.isNotBlank(geocode)) { + params.put("wp", geocode); + } else if (StringUtils.isNotBlank(guid)) { + params.put("guid", guid); + } + + String page = requestLogged(false, host, path, method, params, false, false, false); + + if (StringUtils.isEmpty(page)) { + if (app.isThere(geocode, guid, true, false)) { + if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { + Log.i(cgSettings.tag, "Loading old cache from cache."); + + geocode = app.getGeocode(guid); + } + + final List<cgCache> cacheList = new ArrayList<cgCache>(); + cacheList.add(app.getCacheByGeocode(geocode)); + search.addGeocode(geocode); + search.error = null; + + app.addSearch(search, cacheList, false, reason); + + cacheList.clear(); + + return search.getCurrentId(); + } + + Log.e(cgSettings.tag, "cgeoBase.searchByGeocode: No data from server"); + return null; + } + + final cgCacheWrap caches = parseCache(page, reason); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + if (caches != null && StringUtils.isNotBlank(caches.error)) { + search.error = caches.error; + } + if (caches != null && StringUtils.isNotBlank(caches.url)) { + search.url = caches.url; + } + + app.addSearch(search, null, true, reason); + + Log.e(cgSettings.tag, "cgeoBase.searchByGeocode: No cache parsed"); + return null; + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByGeocode: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, 0, 0, null); + + app.addSearch(search, cacheList, true, reason); + + page = null; + cacheList.clear(); + + return search.getCurrentId(); + } + + public UUID searchByOffline(Map<String, Object> parameters) { + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByOffline: No application found"); + return null; + } + + Geopoint coords = null; + String cachetype = null; + Integer list = 1; + + if (parameters.containsKey("latitude") && parameters.containsKey("longitude")) { + coords = new Geopoint((Double) parameters.get("latitude"), (Double) parameters.get("longitude")); + } + + if (parameters.containsKey("cachetype")) { + cachetype = (String) parameters.get("cachetype"); + } + + if (parameters.containsKey("list")) { + list = (Integer) parameters.get("list"); + } + + final cgSearch search = app.getBatchOfStoredCaches(true, coords, cachetype, list); + search.totalCnt = app.getAllStoredCachesCount(true, cachetype, list); + + return search.getCurrentId(); + } + + public UUID searchByHistory(Map<String, Object> parameters) { + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByHistory: No application found"); + return null; + } + + String cachetype = null; + + if (parameters.containsKey("cachetype")) { + cachetype = (String) parameters.get("cachetype"); + } + + final cgSearch search = app.getHistoryOfCaches(true, cachetype); + search.totalCnt = app.getAllHistoricCachesCount(true, cachetype); + + return search.getCurrentId(); + } + + public UUID searchByCoords(cgSearchThread thread, Map<String, String> parameters, int reason, boolean showCaptcha) { + final cgSearch search = new cgSearch(); + final String latitude = parameters.get("latitude"); + final String longitude = parameters.get("longitude"); + String cacheType = parameters.get("cachetype"); + + if (StringUtils.isBlank(latitude)) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No latitude given"); + return null; + } + + if (StringUtils.isBlank(longitude)) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No longitude given"); + return null; + } + + if (StringUtils.isBlank(cacheType)) { + cacheType = null; + } + + final String host = "www.geocaching.com"; + final String path = "/seek/nearest.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (cacheType != null && cacheIDs.containsKey(cacheType)) { + params.put("tx", cacheIDs.get(cacheType)); + } else { + params.put("tx", cacheIDs.get("all")); + } + params.put("lat", latitude); + params.put("lng", longitude); + + final String url = "http://" + host + path + "?" + prepareParameters(params, false, true); + String page = requestLogged(false, host, path, method, params, false, false, true); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No data from server"); + return null; + } + + final cgCacheWrap caches = parseSearch(thread, url, page, showCaptcha); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No cache parsed"); + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, settings.excludeDisabled, 0, null); + + app.addSearch(search, cacheList, true, reason); + + return search.getCurrentId(); + } + + public UUID searchByKeyword(cgSearchThread thread, Map<String, String> parameters, int reason, boolean showCaptcha) { + final cgSearch search = new cgSearch(); + final String keyword = parameters.get("keyword"); + String cacheType = parameters.get("cachetype"); + + if (StringUtils.isBlank(keyword)) { + Log.e(cgSettings.tag, "cgeoBase.searchByKeyword: No keyword given"); + return null; + } + + if (StringUtils.isBlank(cacheType)) { + cacheType = null; + } + + final String host = "www.geocaching.com"; + final String path = "/seek/nearest.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (cacheType != null && cacheIDs.containsKey(cacheType)) { + params.put("tx", cacheIDs.get(cacheType)); + } else { + params.put("tx", cacheIDs.get("all")); + } + params.put("key", keyword); + + final String url = "http://" + host + path + "?" + prepareParameters(params, false, true); + String page = requestLogged(false, host, path, method, params, false, false, true); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByKeyword: No data from server"); + return null; + } + + final cgCacheWrap caches = parseSearch(thread, url, page, showCaptcha); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + Log.e(cgSettings.tag, "cgeoBase.searchByKeyword: No cache parsed"); + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, settings.excludeDisabled, 0, null); + + app.addSearch(search, cacheList, true, reason); + + return search.getCurrentId(); + } + + public UUID searchByUsername(cgSearchThread thread, Map<String, String> parameters, int reason, boolean showCaptcha) { + final cgSearch search = new cgSearch(); + final String userName = parameters.get("username"); + String cacheType = parameters.get("cachetype"); + + if (StringUtils.isBlank(userName)) { + Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No user name given"); + return null; + } + + if (StringUtils.isBlank(cacheType)) { + cacheType = null; + } + + final String host = "www.geocaching.com"; + final String path = "/seek/nearest.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (cacheType != null && cacheIDs.containsKey(cacheType)) { + params.put("tx", cacheIDs.get(cacheType)); + } else { + params.put("tx", cacheIDs.get("all")); + } + params.put("ul", userName); + + boolean my = false; + if (userName.equalsIgnoreCase(settings.getLogin().get("username"))) { + my = true; + Log.i(cgSettings.tag, "cgBase.searchByUsername: Overriding users choice, downloading all caches."); + } + + final String url = "http://" + host + path + "?" + prepareParameters(params, my, true); + String page = requestLogged(false, host, path, method, params, false, my, true); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No data from server"); + return null; + } + + final cgCacheWrap caches = parseSearch(thread, url, page, showCaptcha); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No cache parsed"); + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByUsername: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, settings.excludeDisabled, 0, null); + + app.addSearch(search, cacheList, true, reason); + + return search.getCurrentId(); + } + + public UUID searchByOwner(cgSearchThread thread, Map<String, String> parameters, int reason, boolean showCaptcha) { + final cgSearch search = new cgSearch(); + final String userName = parameters.get("username"); + String cacheType = parameters.get("cachetype"); + + if (StringUtils.isBlank(userName)) { + Log.e(cgSettings.tag, "cgeoBase.searchByOwner: No user name given"); + return null; + } + + if (StringUtils.isBlank(cacheType)) { + cacheType = null; + } + + final String host = "www.geocaching.com"; + final String path = "/seek/nearest.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (cacheType != null && cacheIDs.containsKey(cacheType)) { + params.put("tx", cacheIDs.get(cacheType)); + } else { + params.put("tx", cacheIDs.get("all")); + } + params.put("u", userName); + + final String url = "http://" + host + path + "?" + prepareParameters(params, false, true); + String page = requestLogged(false, host, path, method, params, false, false, true); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByOwner: No data from server"); + return null; + } + + final cgCacheWrap caches = parseSearch(thread, url, page, showCaptcha); + if (caches == null || caches.cacheList == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByOwner: No cache parsed"); + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByCoords: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, settings.excludeDisabled, 0, null); + + app.addSearch(search, cacheList, true, reason); + + return search.getCurrentId(); + } + + public UUID searchByViewport(Map<String, String> parameters, int reason) { + final cgSearch search = new cgSearch(); + final String latMin = parameters.get("latitude-min"); + final String latMax = parameters.get("latitude-max"); + final String lonMin = parameters.get("longitude-min"); + final String lonMax = parameters.get("longitude-max"); + + String usertoken = null; + if (parameters.get("usertoken") != null) { + usertoken = parameters.get("usertoken"); + } else { + usertoken = ""; + } + + String page = null; + + if (StringUtils.isBlank(latMin) || StringUtils.isBlank(latMax) || StringUtils.isBlank(lonMin) || StringUtils.isBlank(lonMax)) { + Log.e(cgSettings.tag, "cgeoBase.searchByViewport: Not enough parameters to recognize viewport"); + return null; + } + + final String host = "www.geocaching.com"; + final String path = "/map/default.aspx/MapAction"; + + String params = "{\"dto\":{\"data\":{\"c\":1,\"m\":\"\",\"d\":\"" + latMax + "|" + latMin + "|" + lonMax + "|" + lonMin + "\"},\"ut\":\"" + usertoken + "\"}}"; + + final String url = "http://" + host + path + "?" + params; + page = requestJSONgc(host, path, params); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchByViewport: No data from server"); + return null; + } + + final cgCacheWrap caches = parseMapJSON(url, page); + if (caches == null || caches.cacheList == null || caches.cacheList.isEmpty()) { + Log.e(cgSettings.tag, "cgeoBase.searchByViewport: No cache parsed"); + } + + if (app == null) { + Log.e(cgSettings.tag, "cgeoBase.searchByViewport: No application found"); + return null; + } + + List<cgCache> cacheList = processSearchResults(search, caches, settings.excludeDisabled, settings.excludeMine, settings.cacheType); + + app.addSearch(search, cacheList, true, reason); + + return search.getCurrentId(); + } + + public List<cgUser> getGeocachersInViewport(String username, Double latMin, Double latMax, Double lonMin, Double lonMax) { + final List<cgUser> users = new ArrayList<cgUser>(); + + if (username == null) { + return users; + } + if (latMin == null || latMax == null || lonMin == null || lonMax == null) { + return users; + } + + final String host = "api.go4cache.com"; + final String path = "/get.php"; + final String method = "POST"; + final Map<String, String> params = new HashMap<String, String>(); + + params.put("u", username); + params.put("ltm", String.format((Locale) null, "%.6f", latMin)); + params.put("ltx", String.format((Locale) null, "%.6f", latMax)); + params.put("lnm", String.format((Locale) null, "%.6f", lonMin)); + params.put("lnx", String.format((Locale) null, "%.6f", lonMax)); + + final String data = request(false, host, path, method, params, false, false, false).getData(); + + if (StringUtils.isBlank(data)) { + Log.e(cgSettings.tag, "cgeoBase.getGeocachersInViewport: No data from server"); + return null; + } + + try { + final JSONObject dataJSON = new JSONObject(data); + + final JSONArray usersData = dataJSON.getJSONArray("users"); + if (usersData != null && usersData.length() > 0) { + int count = usersData.length(); + JSONObject oneUser = null; + for (int i = 0; i < count; i++) { + final cgUser user = new cgUser(); + oneUser = usersData.getJSONObject(i); + if (oneUser != null) { + final String located = oneUser.getString("located"); + if (located != null) { + user.located = dateSqlIn.parse(located); + } else { + user.located = new Date(); + } + user.username = oneUser.getString("user"); + user.coords = new Geopoint(oneUser.getDouble("latitude"), oneUser.getDouble("longitude")); + user.action = oneUser.getString("action"); + user.client = oneUser.getString("client"); + + if (user.coords != null) { + users.add(user); + } + } + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.getGeocachersInViewport: " + e.toString()); + } + + return users; + } + + public List<cgCache> processSearchResults(cgSearch search, cgCacheWrap caches, int excludeDisabled, int excludeMine, String cacheType) { + List<cgCache> cacheList = new ArrayList<cgCache>(); + if (caches != null) { + if (StringUtils.isNotBlank(caches.error)) { + search.error = caches.error; + } + if (StringUtils.isNotBlank(caches.url)) { + search.url = caches.url; + } + search.viewstates = caches.viewstates; + search.totalCnt = caches.totalCnt; + + if (CollectionUtils.isNotEmpty(caches.cacheList)) { + for (cgCache cache : caches.cacheList) { + if ((excludeDisabled == 0 || (excludeDisabled == 1 && cache.disabled == false)) + && (excludeMine == 0 || (excludeMine == 1 && cache.own == false)) + && (excludeMine == 0 || (excludeMine == 1 && cache.found == false)) + && (cacheType == null || (cacheType.equals(cache.type)))) { + search.addGeocode(cache.geocode); + cacheList.add(cache); + } + } + } + } + return cacheList; + } + + public cgTrackable searchTrackable(Map<String, String> parameters) { + final String geocode = parameters.get("geocode"); + final String guid = parameters.get("guid"); + final String id = parameters.get("id"); + cgTrackable trackable = new cgTrackable(); + + if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid) && StringUtils.isBlank(id)) { + Log.e(cgSettings.tag, "cgeoBase.searchTrackable: No geocode nor guid nor id given"); + return null; + } + + final String host = "www.geocaching.com"; + final String path = "/track/details.aspx"; + final String method = "GET"; + final Map<String, String> params = new HashMap<String, String>(); + if (StringUtils.isNotBlank(geocode)) { + params.put("tracker", geocode); + } else if (StringUtils.isNotBlank(guid)) { + params.put("guid", guid); + } else if (StringUtils.isNotBlank(id)) { + params.put("id", id); + } + + String page = requestLogged(false, host, path, method, params, false, false, false); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.searchTrackable: No data from server"); + return trackable; + } + + trackable = parseTrackable(page); + if (trackable == null) { + Log.e(cgSettings.tag, "cgeoBase.searchTrackable: No trackable parsed"); + return trackable; + } + + return trackable; + } + + public int postLog(cgeoapplication app, String geocode, String cacheid, String[] viewstates, + int logType, int year, int month, int day, String log, List<cgTrackableLog> trackables) { + if (isEmpty(viewstates)) { + Log.e(cgSettings.tag, "cgeoBase.postLog: No viewstate given"); + return 1000; + } + + if (logTypes2.containsKey(logType) == false) { + Log.e(cgSettings.tag, "cgeoBase.postLog: Unknown logtype"); + return 1000; + } + + if (StringUtils.isBlank(log)) { + Log.e(cgSettings.tag, "cgeoBase.postLog: No log text given"); + return 1001; + } + + // fix log (non-Latin characters converted to HTML entities) + final int logLen = log.length(); + final StringBuilder logUpdated = new StringBuilder(); + + for (int i = 0; i < logLen; i++) { + char c = log.charAt(i); + + if (c > 300) { + logUpdated.append("&#"); + logUpdated.append(Integer.toString((int) c)); + logUpdated.append(';'); + } else { + logUpdated.append(c); + } + } + log = logUpdated.toString(); + + log = log.replace("\n", "\r\n"); // windows' eol + + if (trackables != null) { + Log.i(cgSettings.tag, "Trying to post log for cache #" + cacheid + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + log + "; trackables: " + trackables.size()); + } else { + Log.i(cgSettings.tag, "Trying to post log for cache #" + cacheid + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + log + "; trackables: 0"); + } + + final String host = "www.geocaching.com"; + final String path = "/seek/log.aspx?ID=" + cacheid; + final String method = "POST"; + final Map<String, String> params = new HashMap<String, String>(); + + setViewstates(viewstates, params); + params.put("__EVENTTARGET", ""); + params.put("__EVENTARGUMENT", ""); + params.put("__LASTFOCUS", ""); + params.put("ctl00$ContentBody$LogBookPanel1$ddLogType", Integer.toString(logType)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", String.format("%02d", month) + "/" + String.format("%02d", day) + "/" + String.format("%04d", year)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Month", Integer.toString(month)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Day", Integer.toString(day)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Year", Integer.toString(year)); + params.put("ctl00$ContentBody$LogBookPanel1$uxLogInfo", log); + params.put("ctl00$ContentBody$LogBookPanel1$LogButton", "Submit Log Entry"); + params.put("ctl00$ContentBody$uxVistOtherListingGC", ""); + if (trackables != null && trackables.isEmpty() == false) { // we have some trackables to proceed + final StringBuilder hdnSelected = new StringBuilder(); + + for (cgTrackableLog tb : trackables) { + final String action = Integer.toString(tb.id) + logTypesTrackableAction.get(tb.action); + + if (tb.action > 0) { + hdnSelected.append(action); + hdnSelected.append(','); + } + } + + params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$hdnSelectedActions", hdnSelected.toString()); // selected trackables + params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$hdnCurrentFilter", ""); + } + + String page = request(false, host, path, method, params, false, false, false).getData(); + if (checkLogin(page) == false) { + int loginState = login(); + if (loginState == 1) { + page = request(false, host, path, method, params, false, false, false).getData(); + } else { + Log.e(cgSettings.tag, "cgeoBase.postLog: Can not log in geocaching (error: " + loginState + ")"); + return loginState; + } + } + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.postLog: No data from server"); + return 1002; + } + + // maintenance, archived needs to be confirmed + final Pattern pattern = Pattern.compile("<span id=\"ctl00_ContentBody_LogBookPanel1_lbConfirm\"[^>]*>([^<]*<font[^>]*>)?([^<]+)(</font>[^<]*)?</span>", Pattern.CASE_INSENSITIVE); + final Matcher matcher = pattern.matcher(page); + + try { + if (matcher.find() && matcher.groupCount() > 0) { + final String[] viewstatesConfirm = getViewstates(page); + + if (isEmpty(viewstatesConfirm)) { + Log.e(cgSettings.tag, "cgeoBase.postLog: No viewstate for confirm log"); + return 1000; + } + + params.clear(); + setViewstates(viewstatesConfirm, params); + params.put("__EVENTTARGET", ""); + params.put("__EVENTARGUMENT", ""); + params.put("__LASTFOCUS", ""); + params.put("ctl00$ContentBody$LogBookPanel1$btnConfirm", "Yes"); + params.put("ctl00$ContentBody$LogBookPanel1$uxLogInfo", log); + params.put("ctl00$ContentBody$uxVistOtherListingGC", ""); + if (trackables != null && trackables.isEmpty() == false) { // we have some trackables to proceed + final StringBuilder hdnSelected = new StringBuilder(); + + for (cgTrackableLog tb : trackables) { + String ctl = null; + final String action = Integer.toString(tb.id) + logTypesTrackableAction.get(tb.action); + + if (tb.ctl < 10) { + ctl = "0" + Integer.toString(tb.ctl); + } else { + ctl = Integer.toString(tb.ctl); + } + + params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$repTravelBugs$ctl" + ctl + "$ddlAction", action); + if (tb.action > 0) { + hdnSelected.append(action); + hdnSelected.append(','); + } + } + + params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$hdnSelectedActions", hdnSelected.toString()); // selected trackables + params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$hdnCurrentFilter", ""); + } + + page = request(false, host, path, method, params, false, false, false).getData(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.postLog.confim: " + e.toString()); + } + + try { + final Pattern patternOk = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_lbHeading\"[^>]*>[^<]*</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + final Matcher matcherOk = patternOk.matcher(page); + if (matcherOk.find()) { + Log.i(cgSettings.tag, "Log successfully posted to cache #" + cacheid); + + if (app != null && geocode != null) { + app.saveVisitDate(geocode); + } + + return 1; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.postLog.check: " + e.toString()); + } + + Log.e(cgSettings.tag, "cgeoBase.postLog: Failed to post log because of unknown error"); + return 1000; + } + + public int postLogTrackable(String tbid, String trackingCode, String[] viewstates, + int logType, int year, int month, int day, String log) { + if (isEmpty(viewstates)) { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No viewstate given"); + return 1000; + } + + if (logTypes2.containsKey(logType) == false) { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: Unknown logtype"); + return 1000; + } + + if (StringUtils.isBlank(log)) { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No log text given"); + return 1001; + } + + Log.i(cgSettings.tag, "Trying to post log for trackable #" + trackingCode + " - action: " + logType + "; date: " + year + "." + month + "." + day + ", log: " + log); + + log = log.replace("\n", "\r\n"); // windows' eol + + final Calendar currentDate = Calendar.getInstance(); + final String host = "www.geocaching.com"; + final String path = "/track/log.aspx?wid=" + tbid; + final String method = "POST"; + final Map<String, String> params = new HashMap<String, String>(); + + setViewstates(viewstates, params); + params.put("__EVENTTARGET", ""); + params.put("__EVENTARGUMENT", ""); + params.put("__LASTFOCUS", ""); + params.put("ctl00$ContentBody$LogBookPanel1$ddLogType", Integer.toString(logType)); + params.put("ctl00$ContentBody$LogBookPanel1$tbCode", trackingCode); + if (currentDate.get(Calendar.YEAR) == year && (currentDate.get(Calendar.MONTH) + 1) == month && currentDate.get(Calendar.DATE) == day) { + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", ""); + } else { + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", Integer.toString(month) + "/" + Integer.toString(day) + "/" + Integer.toString(year)); + } + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Day", Integer.toString(day)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Month", Integer.toString(month)); + params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged$Year", Integer.toString(year)); + params.put("ctl00$ContentBody$LogBookPanel1$uxLogInfo", log); + params.put("ctl00$ContentBody$LogBookPanel1$LogButton", "Submit Log Entry"); + params.put("ctl00$ContentBody$uxVistOtherListingGC", ""); + + String page = request(false, host, path, method, params, false, false, false).getData(); + if (checkLogin(page) == false) { + int loginState = login(); + if (loginState == 1) { + page = request(false, host, path, method, params, false, false, false).getData(); + } else { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: Can not log in geocaching (error: " + loginState + ")"); + return loginState; + } + } + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: No data from server"); + return 1002; + } + + try { + final Pattern patternOk = Pattern.compile("<div id=[\"|']ctl00_ContentBody_LogBookPanel1_ViewLogPanel[\"|']>", Pattern.CASE_INSENSITIVE); + final Matcher matcherOk = patternOk.matcher(page); + if (matcherOk.find()) { + Log.i(cgSettings.tag, "Log successfully posted to trackable #" + trackingCode); + return 1; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable.check: " + e.toString()); + } + + Log.e(cgSettings.tag, "cgeoBase.postLogTrackable: Failed to post log because of unknown error"); + return 1000; + } + + /** + * Adds the cache to the watchlist of the user. + * + * @param cache + * the cache to add + * @return -1: error occured + */ + public int addToWatchlist(cgCache cache) { + String page = requestLogged(false, "www.geocaching.com", "/my/watchlist.aspx?w=" + cache.cacheId, + "POST", null, false, false, false); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgBase.addToWatchlist: No data from server"); + return -1; // error + } + + boolean guidOnPage = cache.isGuidContainedInPage(page); + if (guidOnPage) { + Log.i(cgSettings.tag, "cgBase.addToWatchlist: cache is on watchlist"); + cache.onWatchlist = true; + } else { + Log.e(cgSettings.tag, "cgBase.addToWatchlist: cache is not on watchlist"); + } + return guidOnPage ? 1 : -1; // on watchlist (=added) / else: error + } + + /** + * Removes the cache from the watchlist + * + * @param cache + * the cache to remove + * @return -1: error occured + */ + public int removeFromWatchlist(cgCache cache) { + String host = "www.geocaching.com"; + String path = "/my/watchlist.aspx?ds=1&action=rem&id=" + cache.cacheId; + String method = "POST"; + + String page = requestLogged(false, host, path, method, null, false, false, false); + + if (StringUtils.isBlank(page)) { + Log.e(cgSettings.tag, "cgBase.removeFromWatchlist: No data from server"); + return -1; // error + } + + // removing cache from list needs approval by hitting "Yes" button + final Map<String, String> params = new HashMap<String, String>(); + transferViewstates(page, params); + params.put("__EVENTTARGET", ""); + params.put("__EVENTARGUMENT", ""); + params.put("ctl00$ContentBody$btnYes", "Yes"); + + page = request(false, host, path, method, params, false, false, false).getData(); + boolean guidOnPage = cache.isGuidContainedInPage(page); + if (!guidOnPage) { + Log.i(cgSettings.tag, "cgBase.removeFromWatchlist: cache removed from watchlist"); + cache.onWatchlist = false; + } else { + Log.e(cgSettings.tag, "cgBase.removeFromWatchlist: cache not removed from watchlist"); + } + return guidOnPage ? -1 : 0; // on watchlist (=error) / not on watchlist + } + + final public static HostnameVerifier doNotVerify = new HostnameVerifier() { + + public boolean verify(String hostname, SSLSession session) { + return true; + } + }; + + public static void trustAllHosts() { + TrustManager[] trustAllCerts = new TrustManager[] { + new X509TrustManager() { + + public java.security.cert.X509Certificate[] getAcceptedIssuers() { + return new java.security.cert.X509Certificate[] {}; + } + + public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + + public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { + } + } + }; + + try { + SSLContext sc = SSLContext.getInstance("TLS"); + sc.init(null, trustAllCerts, new java.security.SecureRandom()); + HttpsURLConnection.setDefaultSSLSocketFactory(sc.getSocketFactory()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.trustAllHosts: " + e.toString()); + } + } + + public static void postTweetCache(cgeoapplication app, cgSettings settings, String geocode) { + final cgCache cache = app.getCacheByGeocode(geocode); + String name = cache.name; + if (name.length() > 84) { + name = name.substring(0, 81) + "..."; + } + final String status = "I found " + name + " (http://coord.info/" + cache.geocode.toUpperCase() + ")! #cgeo #geocaching"; // 56 chars + cache name + + postTweet(app, settings, status, null); + } + + public static void postTweetTrackable(cgeoapplication app, cgSettings settings, String geocode) { + final cgTrackable trackable = app.getTrackableByGeocode(geocode); + String name = trackable.name; + if (name.length() > 82) { + name = name.substring(0, 79) + "..."; + } + final String status = "I touched " + name + " (http://coord.info/" + trackable.geocode.toUpperCase() + ")! #cgeo #geocaching"; // 58 chars + trackable name + + postTweet(app, settings, status, null); + } + + public static void postTweet(cgeoapplication app, cgSettings settings, String status, final Geopoint coords) { + if (app == null) { + return; + } + if (settings == null || StringUtils.isBlank(settings.tokenPublic) || StringUtils.isBlank(settings.tokenSecret)) { + return; + } + + try { + Map<String, String> parameters = new HashMap<String, String>(); + + parameters.put("status", status); + if (coords != null) { + parameters.put("lat", String.format("%.6f", coords.getLatitude())); + parameters.put("long", String.format("%.6f", coords.getLongitude())); + parameters.put("display_coordinates", "true"); + } + + final String paramsDone = cgOAuth.signOAuth("api.twitter.com", "/1/statuses/update.json", "POST", false, parameters, settings.tokenPublic, settings.tokenSecret); + + HttpURLConnection connection = null; + try { + final StringBuffer buffer = new StringBuffer(); + final URL u = new URL("http://api.twitter.com/1/statuses/update.json"); + final URLConnection uc = u.openConnection(); + + uc.setRequestProperty("Host", "api.twitter.com"); + + connection = (HttpURLConnection) uc; + connection.setReadTimeout(30000); + connection.setRequestMethod("POST"); + HttpURLConnection.setFollowRedirects(true); + connection.setDoInput(true); + connection.setDoOutput(true); + + final OutputStream out = connection.getOutputStream(); + final OutputStreamWriter wr = new OutputStreamWriter(out); + wr.write(paramsDone); + wr.flush(); + wr.close(); + + Log.i(cgSettings.tag, "Twitter.com: " + connection.getResponseCode() + " " + connection.getResponseMessage()); + + InputStream ins; + final String encoding = connection.getContentEncoding(); + + if (encoding != null && encoding.equalsIgnoreCase("gzip")) { + ins = new GZIPInputStream(connection.getInputStream()); + } else if (encoding != null && encoding.equalsIgnoreCase("deflate")) { + ins = new InflaterInputStream(connection.getInputStream(), new Inflater(true)); + } else { + ins = connection.getInputStream(); + } + + final InputStreamReader inr = new InputStreamReader(ins); + final BufferedReader br = new BufferedReader(inr); + + readIntoBuffer(br, buffer); + + br.close(); + ins.close(); + inr.close(); + connection.disconnect(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgBase.postTweet.IO: " + connection.getResponseCode() + ": " + connection.getResponseMessage() + " ~ " + e.toString()); + + final InputStream ins = connection.getErrorStream(); + final StringBuffer buffer = new StringBuffer(); + final InputStreamReader inr = new InputStreamReader(ins); + final BufferedReader br = new BufferedReader(inr); + + readIntoBuffer(br, buffer); + + br.close(); + ins.close(); + inr.close(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.postTweet.inner: " + e.toString()); + } + + connection.disconnect(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.postTweet: " + e.toString()); + } + } + + private static void readIntoBuffer(BufferedReader br, StringBuffer buffer) throws IOException { + int bufferSize = 1024 * 16; + char[] bytes = new char[bufferSize]; + int bytesRead; + while ((bytesRead = br.read(bytes)) > 0) { + if (bytesRead == bufferSize) { + buffer.append(bytes); + } + else { + buffer.append(bytes, 0, bytesRead); + } + } + } + + public static String getLocalIpAddress() { + try { + for (Enumeration<NetworkInterface> en = NetworkInterface.getNetworkInterfaces(); en.hasMoreElements();) { + NetworkInterface intf = en.nextElement(); + for (Enumeration<InetAddress> enumIpAddr = intf.getInetAddresses(); enumIpAddr.hasMoreElements();) { + InetAddress inetAddress = enumIpAddr.nextElement(); + if (!inetAddress.isLoopbackAddress()) { + return inetAddress.getHostAddress(); + } + } + } + } catch (SocketException e) { + // nothing + } + + return null; + } + + public static String implode(String delim, Object[] array) { + StringBuilder out = new StringBuilder(); + + try { + for (int i = 0; i < array.length; i++) { + if (i != 0) { + out.append(delim); + } + out.append(array[i].toString()); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.implode: " + e.toString()); + } + return out.toString(); + } + + public static String urlencode_rfc3986(String text) { + final String encoded = URLEncoder.encode(text).replace("+", "%20").replaceAll("%7E", "~"); + + return encoded; + } + + public String prepareParameters(Map<String, String> params, boolean my, boolean addF) { + String paramsDone = null; + + if (my != true && settings.excludeMine > 0) { + if (params == null) { + params = new HashMap<String, String>(); + } + if (addF) { + params.put("f", "1"); + } + + Log.i(cgSettings.tag, "Skipping caches found or hidden by user."); + } + + if (params != null) { + Set<Map.Entry<String, String>> entrySet = params.entrySet(); + List<String> paramsEncoded = new ArrayList<String>(); + + for (Map.Entry<String, String> entry : entrySet) + { + String key = entry.getKey(); + String value = entry.getValue(); + + if (key.charAt(0) == '^') { + key = ""; + } + if (value == null) { + value = ""; + } + + paramsEncoded.add(key + "=" + urlencode_rfc3986(value)); + } + + paramsDone = implode("&", paramsEncoded.toArray()); + } else { + paramsDone = ""; + } + + return paramsDone; + } + + public String[] requestViewstates(boolean secure, String host, String path, String method, Map<String, String> params, boolean xContentType, boolean my) { + final cgResponse response = request(secure, host, path, method, params, xContentType, my, false); + + return getViewstates(response.getData()); + } + + public String requestLogged(boolean secure, String host, String path, String method, Map<String, String> params, boolean xContentType, boolean my, boolean addF) { + cgResponse response = request(secure, host, path, method, params, xContentType, my, addF); + String data = response.getData(); + + if (checkLogin(data) == false) { + int loginState = login(); + if (loginState == 1) { + response = request(secure, host, path, method, params, xContentType, my, addF); + data = response.getData(); + } else { + Log.i(cgSettings.tag, "Working as guest."); + } + } + + return data; + } + + public cgResponse request(boolean secure, String host, String path, String method, Map<String, String> params, boolean xContentType, boolean my, boolean addF) { + // prepare parameters + final String paramsDone = prepareParameters(params, my, addF); + + return request(secure, host, path, method, paramsDone, 0, xContentType); + } + + public cgResponse request(boolean secure, String host, String path, String method, Map<String, String> params, int requestId, boolean xContentType, boolean my, boolean addF) { + // prepare parameters + final String paramsDone = prepareParameters(params, my, addF); + + return request(secure, host, path, method, paramsDone, requestId, xContentType); + } + + public cgResponse request(boolean secure, String host, String path, String method, String params, int requestId, Boolean xContentType) { + URL u = null; + int httpCode = -1; + String httpMessage = null; + String httpLocation = null; + + if (requestId == 0) { + requestId = (int) (Math.random() * 1000); + } + + if (method == null || (method.equalsIgnoreCase("GET") == false && method.equalsIgnoreCase("POST") == false)) { + method = "POST"; + } else { + method = method.toUpperCase(); + } + + // https + String scheme = "http://"; + if (secure) { + scheme = "https://"; + } + + String cookiesDone = CookieJar.getCookiesAsString(prefs); + + URLConnection uc = null; + HttpURLConnection connection = null; + Integer timeout = 30000; + StringBuffer buffer = null; + + for (int i = 0; i < 5; i++) { + if (i > 0) { + Log.w(cgSettings.tag, "Failed to download data, retrying. Attempt #" + (i + 1)); + } + + buffer = new StringBuffer(); + timeout = 30000 + (i * 10000); + + try { + if (method.equals("GET")) { + // GET + u = new URL(scheme + host + path + "?" + params); + uc = u.openConnection(); + + uc.setRequestProperty("Host", host); + uc.setRequestProperty("Cookie", cookiesDone); + if (xContentType) { + uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + } + + if (settings.asBrowser == 1) { + uc.setRequestProperty("Accept", "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + // uc.setRequestProperty("Accept-Encoding", "gzip"); // not supported via cellular network + uc.setRequestProperty("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7"); + uc.setRequestProperty("Accept-Language", "en-US"); + uc.setRequestProperty("User-Agent", idBrowser); + uc.setRequestProperty("Connection", "keep-alive"); + uc.setRequestProperty("Keep-Alive", "300"); + } + + connection = (HttpURLConnection) uc; + connection.setReadTimeout(timeout); + connection.setRequestMethod(method); + HttpURLConnection.setFollowRedirects(false); + connection.setDoInput(true); + connection.setDoOutput(false); + } else { + // POST + u = new URL(scheme + host + path); + uc = u.openConnection(); + + uc.setRequestProperty("Host", host); + uc.setRequestProperty("Cookie", cookiesDone); + if (xContentType) { + uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + } + + if (settings.asBrowser == 1) { + uc.setRequestProperty("Accept", "application/xml,application/xhtml+xml,text/html;q=0.9,text/plain;q=0.8,image/png,*/*;q=0.5"); + // uc.setRequestProperty("Accept-Encoding", "gzip"); // not supported via cellular network + uc.setRequestProperty("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7"); + uc.setRequestProperty("Accept-Language", "en-US"); + uc.setRequestProperty("User-Agent", idBrowser); + uc.setRequestProperty("Connection", "keep-alive"); + uc.setRequestProperty("Keep-Alive", "300"); + } + + connection = (HttpURLConnection) uc; + connection.setReadTimeout(timeout); + connection.setRequestMethod(method); + HttpURLConnection.setFollowRedirects(false); + connection.setDoInput(true); + connection.setDoOutput(true); + + final OutputStream out = connection.getOutputStream(); + final OutputStreamWriter wr = new OutputStreamWriter(out); + wr.write(params); + wr.flush(); + wr.close(); + } + + CookieJar.setCookies(prefs, uc); + + InputStream ins = getInputstreamFromConnection(connection); + final InputStreamReader inr = new InputStreamReader(ins); + final BufferedReader br = new BufferedReader(inr, 16 * 1024); + + readIntoBuffer(br, buffer); + + httpCode = connection.getResponseCode(); + httpMessage = connection.getResponseMessage(); + httpLocation = uc.getHeaderField("Location"); + + final String paramsLog = params.replaceAll(passMatch, "password=***"); + Log.i(cgSettings.tag + "|" + requestId, "[" + method + " " + (int) (params.length() / 1024) + "k | " + httpCode + " | " + (int) (buffer.length() / 1024) + "k] Downloaded " + scheme + host + path + "?" + paramsLog); + + connection.disconnect(); + br.close(); + ins.close(); + inr.close(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgeoBase.request.IOException: " + e.toString()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.request: " + e.toString()); + } + + if (buffer.length() > 0) { + break; + } + } + + cgResponse response = new cgResponse(); + + try { + if (httpCode == 302 && httpLocation != null) { + final Uri newLocation = Uri.parse(httpLocation); + if (newLocation.isRelative()) { + response = request(secure, host, path, "GET", new HashMap<String, String>(), requestId, false, false, false); + } else { + boolean secureRedir = false; + if (newLocation.getScheme().equals("https")) { + secureRedir = true; + } + response = request(secureRedir, newLocation.getHost(), newLocation.getPath(), "GET", new HashMap<String, String>(), requestId, false, false, false); + } + } else { + if (StringUtils.isNotEmpty(buffer)) { + replaceWhitespace(buffer); + String data = buffer.toString(); + buffer = null; + + if (data != null) { + response.setData(data); + } else { + response.setData(""); + } + response.setStatusCode(httpCode); + response.setStatusMessage(httpMessage); + response.setUrl(u.toString()); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.page: " + e.toString()); + } + + return response; + } + + /** + * Replace the characters \n, \r and \t with a space + * + * @param buffer + * The data + */ + public static void replaceWhitespace(final StringBuffer buffer) { + final int length = buffer.length(); + final char[] chars = new char[length]; + buffer.getChars(0, length, chars, 0); + int resultSize = 0; + boolean lastWasWhitespace = false; + for (char c : chars) { + if (c == ' ' || c == '\n' || c == '\r' || c == '\t') { + if (!lastWasWhitespace) { + chars[resultSize++] = ' '; + } + lastWasWhitespace = true; + } else { + chars[resultSize++] = c; + lastWasWhitespace = false; + } + } + buffer.setLength(0); + buffer.append(chars); + } + + public String requestJSONgc(String host, String path, String params) { + int httpCode = -1; + String httpLocation = null; + + final String cookiesDone = CookieJar.getCookiesAsString(prefs); + + URLConnection uc = null; + HttpURLConnection connection = null; + Integer timeout = 30000; + final StringBuffer buffer = new StringBuffer(); + + for (int i = 0; i < 3; i++) { + if (i > 0) { + Log.w(cgSettings.tag, "Failed to download data, retrying. Attempt #" + (i + 1)); + } + + buffer.delete(0, buffer.length()); + timeout = 30000 + (i * 15000); + + try { + // POST + final URL u = new URL("http://" + host + path); + uc = u.openConnection(); + + uc.setRequestProperty("Host", host); + uc.setRequestProperty("Cookie", cookiesDone); + uc.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + uc.setRequestProperty("X-Requested-With", "XMLHttpRequest"); + uc.setRequestProperty("Accept", "application/json, text/javascript, */*; q=0.01"); + uc.setRequestProperty("Referer", host + "/" + path); + + if (settings.asBrowser == 1) { + uc.setRequestProperty("Accept-Charset", "utf-8, iso-8859-1, utf-16, *;q=0.7"); + uc.setRequestProperty("Accept-Language", "en-US"); + uc.setRequestProperty("User-Agent", idBrowser); + uc.setRequestProperty("Connection", "keep-alive"); + uc.setRequestProperty("Keep-Alive", "300"); + } + + connection = (HttpURLConnection) uc; + connection.setReadTimeout(timeout); + connection.setRequestMethod("POST"); + HttpURLConnection.setFollowRedirects(false); // TODO: Fix these (FilCab) + connection.setDoInput(true); + connection.setDoOutput(true); + + final OutputStream out = connection.getOutputStream(); + final OutputStreamWriter wr = new OutputStreamWriter(out); + wr.write(params); + wr.flush(); + wr.close(); + + CookieJar.setCookies(prefs, uc); + + InputStream ins = getInputstreamFromConnection(connection); + final InputStreamReader inr = new InputStreamReader(ins); + final BufferedReader br = new BufferedReader(inr); + + readIntoBuffer(br, buffer); + + httpCode = connection.getResponseCode(); + httpLocation = uc.getHeaderField("Location"); + + final String paramsLog = params.replaceAll(passMatch, "password=***"); + Log.i(cgSettings.tag + " | JSON", "[POST " + (int) (params.length() / 1024) + "k | " + httpCode + " | " + (int) (buffer.length() / 1024) + "k] Downloaded " + "http://" + host + path + "?" + paramsLog); + + connection.disconnect(); + br.close(); + ins.close(); + inr.close(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgeoBase.requestJSONgc.IOException: " + e.toString()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.requestJSONgc: " + e.toString()); + } + + if (buffer != null && buffer.length() > 0) { + break; + } + } + + String page = null; + if (httpCode == 302 && httpLocation != null) { + final Uri newLocation = Uri.parse(httpLocation); + if (newLocation.isRelative()) { + page = requestJSONgc(host, path, params); + } else { + page = requestJSONgc(newLocation.getHost(), newLocation.getPath(), params); + } + } else { + replaceWhitespace(buffer); + page = buffer.toString(); + } + + if (page != null) { + return page; + } else { + return ""; + } + } + + private static InputStream getInputstreamFromConnection(HttpURLConnection connection) throws IOException { + final String encoding = connection.getContentEncoding(); + InputStream ins; + + if (encoding != null && encoding.equalsIgnoreCase("gzip")) { + ins = new GZIPInputStream(connection.getInputStream()); + } else if (encoding != null && encoding.equalsIgnoreCase("deflate")) { + ins = new InflaterInputStream(connection.getInputStream(), new Inflater(true)); + } else { + ins = connection.getInputStream(); + } + return ins; + } + + public static String requestJSON(String host, String path, String params) { + return requestJSON("http://", host, path, "GET", params); + } + + public static String requestJSON(String scheme, String host, String path, String method, String params) { + int httpCode = -1; + //String httpLocation = null; + + if (method == null) { + method = "GET"; + } else { + method = method.toUpperCase(); + } + + boolean methodPost = false; + if (method.equalsIgnoreCase("POST")) { + methodPost = true; + } + + URLConnection uc = null; + HttpURLConnection connection = null; + Integer timeout = 30000; + final StringBuffer buffer = new StringBuffer(); + + for (int i = 0; i < 3; i++) { + if (i > 0) { + Log.w(cgSettings.tag, "Failed to download data, retrying. Attempt #" + (i + 1)); + } + + buffer.delete(0, buffer.length()); + timeout = 30000 + (i * 15000); + + try { + try { + URL u = null; + if (methodPost) { + u = new URL(scheme + host + path); + } else { + u = new URL(scheme + host + path + "?" + params); + } + + if (u.getProtocol().toLowerCase().equals("https")) { + trustAllHosts(); + HttpsURLConnection https = (HttpsURLConnection) u.openConnection(); + https.setHostnameVerifier(doNotVerify); + uc = https; + } else { + uc = (HttpURLConnection) u.openConnection(); + } + + uc.setRequestProperty("Host", host); + uc.setRequestProperty("Accept", "application/json, text/javascript, */*; q=0.01"); + if (methodPost) { + uc.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); + uc.setRequestProperty("Content-Length", Integer.toString(params.length())); + uc.setRequestProperty("X-HTTP-Method-Override", "GET"); + } else { + uc.setRequestProperty("Content-Type", "application/json; charset=UTF-8"); + } + uc.setRequestProperty("X-Requested-With", "XMLHttpRequest"); + + connection = (HttpURLConnection) uc; + connection.setReadTimeout(timeout); + connection.setRequestMethod(method); + HttpURLConnection.setFollowRedirects(false); // TODO: Fix these (FilCab) + connection.setDoInput(true); + if (methodPost) { + connection.setDoOutput(true); + + final OutputStream out = connection.getOutputStream(); + final OutputStreamWriter wr = new OutputStreamWriter(out); + wr.write(params); + wr.flush(); + wr.close(); + } else { + connection.setDoOutput(false); + } + + InputStream ins = getInputstreamFromConnection(connection); + final InputStreamReader inr = new InputStreamReader(ins); + final BufferedReader br = new BufferedReader(inr, 1024); + + readIntoBuffer(br, buffer); + + httpCode = connection.getResponseCode(); + + final String paramsLog = params.replaceAll(passMatch, "password=***"); + Log.i(cgSettings.tag + " | JSON", "[POST " + (int) (params.length() / 1024) + "k | " + httpCode + " | " + (int) (buffer.length() / 1024) + "k] Downloaded " + "http://" + host + path + "?" + paramsLog); + + connection.disconnect(); + br.close(); + ins.close(); + inr.close(); + } catch (IOException e) { + httpCode = connection.getResponseCode(); + + Log.e(cgSettings.tag, "cgeoBase.requestJSON.IOException: " + httpCode + ": " + connection.getResponseMessage() + " ~ " + e.toString()); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoBase.requestJSON: " + e.toString()); + } + + if (StringUtils.isNotBlank(buffer)) { + break; + } + + if (httpCode == 403) { + // we're not allowed to download content, so let's move + break; + } + } + + String page = null; + //This is reported as beeing deadCode (httpLocation is always null) + //2011-08-09 - 302 is redirect so something should probably be done + /* + * if (httpCode == 302 && httpLocation != null) { + * final Uri newLocation = Uri.parse(httpLocation); + * if (newLocation.isRelative()) { + * page = requestJSONgc(host, path, params); + * } else { + * page = requestJSONgc(newLocation.getHost(), newLocation.getPath(), params); + * } + * } else { + */ + replaceWhitespace(buffer); + page = buffer.toString(); + //} + + if (page != null) { + return page; + } else { + return ""; + } + } + + public static String rot13(String text) { + final StringBuilder result = new StringBuilder(); + // plaintext flag (do not convert) + boolean plaintext = false; + + int length = text.length(); + for (int index = 0; index < length; index++) { + int c = text.charAt(index); + if (c == '[') { + plaintext = true; + } else if (c == ']') { + plaintext = false; + } else if (!plaintext) { + int capitalized = c & 32; + c &= ~capitalized; + c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) + | capitalized; + } + result.append((char) c); + } + return result.toString(); + } + + public static String md5(String text) { + String hashed = ""; + + try { + MessageDigest digest = MessageDigest.getInstance("MD5"); + digest.update(text.getBytes(), 0, text.length()); + hashed = new BigInteger(1, digest.digest()).toString(16); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.md5: " + e.toString()); + } + + return hashed; + } + + public static String sha1(String text) { + String hashed = ""; + + try { + MessageDigest digest = MessageDigest.getInstance("SHA-1"); + digest.update(text.getBytes(), 0, text.length()); + hashed = new BigInteger(1, digest.digest()).toString(16); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.sha1: " + e.toString()); + } + + return hashed; + } + + public static byte[] hashHmac(String text, String salt) { + byte[] macBytes = {}; + + try { + SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(), "HmacSHA1"); + Mac mac = Mac.getInstance("HmacSHA1"); + mac.init(secretKeySpec); + macBytes = mac.doFinal(text.getBytes()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.hashHmac: " + e.toString()); + } + + return macBytes; + } + + public static boolean deleteDirectory(File path) { + if (path.exists()) { + File[] files = path.listFiles(); + + for (File file : files) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } + } + + return (path.delete()); + } + + public void storeCache(cgeoapplication app, Activity activity, cgCache cache, String geocode, int listId, Handler handler) { + try { + // get cache details, they may not yet be complete + if (cache != null) { + // only reload the cache, if it was already stored or has not all details (by checking the description) + if (cache.reason > 0 || StringUtils.isBlank(cache.description)) { + final Map<String, String> params = new HashMap<String, String>(); + params.put("geocode", cache.geocode); + final UUID searchId = searchByGeocode(params, listId, false); + cache = app.getCache(searchId); + } + } else if (StringUtils.isNotBlank(geocode)) { + final Map<String, String> params = new HashMap<String, String>(); + params.put("geocode", geocode); + final UUID searchId = searchByGeocode(params, listId, false); + cache = app.getCache(searchId); + } + + if (cache == null) { + if (handler != null) { + handler.sendMessage(new Message()); + } + + return; + } + + final cgHtmlImg imgGetter = new cgHtmlImg(activity, cache.geocode, false, listId, true); + + // store images from description + if (StringUtils.isNotBlank(cache.description)) { + Html.fromHtml(cache.description, imgGetter, null); + } + + // store spoilers + if (CollectionUtils.isNotEmpty(cache.spoilers)) { + for (cgImage oneSpoiler : cache.spoilers) { + imgGetter.getDrawable(oneSpoiler.url); + } + } + + // store images from logs + if (settings.storelogimages) { + for (cgLog log : cache.logs) { + if (CollectionUtils.isNotEmpty(log.logImages)) { + for (cgImage oneLogImg : log.logImages) { + imgGetter.getDrawable(oneLogImg.url); + } + } + } + } + + // store map previews + StaticMapsProvider.downloadMaps(cache, settings, activity); + + app.markStored(cache.geocode, listId); + app.removeCacheFromCache(cache.geocode); + + if (handler != null) { + handler.sendMessage(new Message()); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.storeCache: " + e.toString()); + } + } + + public static void dropCache(cgeoapplication app, Activity activity, cgCache cache, Handler handler) { + try { + app.markDropped(cache.geocode); + app.removeCacheFromCache(cache.geocode); + + handler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.dropCache: " + e.toString()); + } + } + + public static boolean isInViewPort(int centerLat1, int centerLon1, int centerLat2, int centerLon2, int spanLat1, int spanLon1, int spanLat2, int spanLon2) { + try { + // expects coordinates in E6 format + final int left1 = centerLat1 - (spanLat1 / 2); + final int right1 = centerLat1 + (spanLat1 / 2); + final int top1 = centerLon1 + (spanLon1 / 2); + final int bottom1 = centerLon1 - (spanLon1 / 2); + + final int left2 = centerLat2 - (spanLat2 / 2); + final int right2 = centerLat2 + (spanLat2 / 2); + final int top2 = centerLon2 + (spanLon2 / 2); + final int bottom2 = centerLon2 - (spanLon2 / 2); + + if (left2 <= left1) { + return false; + } + if (right2 >= right1) { + return false; + } + if (top2 >= top1) { + return false; + } + if (bottom2 <= bottom1) { + return false; + } + + return true; + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.isInViewPort: " + e.toString()); + return false; + } + } + + public static boolean isCacheInViewPort(int centerLat, int centerLon, int spanLat, int spanLon, final Geopoint cacheCoords) { + if (cacheCoords == null) { + return false; + } + + // viewport is defined by center, span and some (10%) reserve on every side + int minLat = centerLat - (spanLat / 2) - (spanLat / 10); + int maxLat = centerLat + (spanLat / 2) + (spanLat / 10); + int minLon = centerLon - (spanLon / 2) - (spanLon / 10); + int maxLon = centerLon + (spanLon / 2) + (spanLon / 10); + final int cLat = cacheCoords.getLatitudeE6(); + final int cLon = cacheCoords.getLongitudeE6(); + int mid = 0; + + if (maxLat < minLat) { + mid = minLat; + minLat = maxLat; + maxLat = mid; + } + + if (maxLon < minLon) { + mid = minLon; + minLon = maxLon; + maxLon = mid; + } + + boolean latOk = false; + boolean lonOk = false; + + if (cLat >= minLat && cLat <= maxLat) { + latOk = true; + } + if (cLon >= minLon && cLon <= maxLon) { + lonOk = true; + } + + if (latOk && lonOk) { + return true; + } else { + return false; + } + } + + private static char[] base64map1 = new char[64]; + + static { + int i = 0; + for (char c = 'A'; c <= 'Z'; c++) { + base64map1[i++] = c; + } + for (char c = 'a'; c <= 'z'; c++) { + base64map1[i++] = c; + } + for (char c = '0'; c <= '9'; c++) { + base64map1[i++] = c; + } + base64map1[i++] = '+'; + base64map1[i++] = '/'; + } + private static byte[] base64map2 = new byte[128]; + + static { + for (int i = 0; i < base64map2.length; i++) { + base64map2[i] = -1; + } + for (int i = 0; i < 64; i++) { + base64map2[base64map1[i]] = (byte) i; + } + } + + public static String base64Encode(byte[] in) { + int iLen = in.length; + int oDataLen = (iLen * 4 + 2) / 3; // output length without padding + int oLen = ((iLen + 2) / 3) * 4; // output length including padding + char[] out = new char[oLen]; + int ip = 0; + int op = 0; + + while (ip < iLen) { + int i0 = in[ip++] & 0xff; + int i1 = ip < iLen ? in[ip++] & 0xff : 0; + int i2 = ip < iLen ? in[ip++] & 0xff : 0; + int o0 = i0 >>> 2; + int o1 = ((i0 & 3) << 4) | (i1 >>> 4); + int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); + int o3 = i2 & 0x3F; + out[op++] = base64map1[o0]; + out[op++] = base64map1[o1]; + out[op] = op < oDataLen ? base64map1[o2] : '='; + op++; + out[op] = op < oDataLen ? base64map1[o3] : '='; + op++; + } + + return new String(out); + } + + public static byte[] base64Decode(String text) { + char[] in = text.toCharArray(); + + int iLen = in.length; + if (iLen % 4 != 0) { + throw new IllegalArgumentException("Length of Base64 encoded input string is not a multiple of 4."); + } + while (iLen > 0 && in[iLen - 1] == '=') { + iLen--; + } + int oLen = (iLen * 3) / 4; + byte[] out = new byte[oLen]; + int ip = 0; + int op = 0; + while (ip < iLen) { + int i0 = in[ip++]; + int i1 = in[ip++]; + int i2 = ip < iLen ? in[ip++] : 'A'; + int i3 = ip < iLen ? in[ip++] : 'A'; + if (i0 > 127 || i1 > 127 || i2 > 127 || i3 > 127) { + throw new IllegalArgumentException("Illegal character in Base64 encoded data."); + } + int b0 = base64map2[i0]; + int b1 = base64map2[i1]; + int b2 = base64map2[i2]; + int b3 = base64map2[i3]; + if (b0 < 0 || b1 < 0 || b2 < 0 || b3 < 0) { + throw new IllegalArgumentException("Illegal character in Base64 encoded data."); + } + int o0 = (b0 << 2) | (b1 >>> 4); + int o1 = ((b1 & 0xf) << 4) | (b2 >>> 2); + int o2 = ((b2 & 3) << 6) | b3; + out[op++] = (byte) o0; + if (op < oLen) { + out[op++] = (byte) o1; + } + if (op < oLen) { + out[op++] = (byte) o2; + } + } + return out; + } + + public static int getCacheIcon(final String type) { + fillIconsMap(); + Integer iconId = gcIcons.get("type_" + type); + if (iconId != null) { + return iconId; + } + // fallback to traditional if some icon type is not correct + return gcIcons.get("type_traditional"); + } + + public static int getMarkerIcon(final boolean cache, final String type, final boolean own, final boolean found, final boolean disabled) { + fillIconsMap(); + + if (wpIcons.isEmpty()) { + wpIcons.put("waypoint", R.drawable.marker_waypoint_waypoint); + wpIcons.put("flag", R.drawable.marker_waypoint_flag); + wpIcons.put("pkg", R.drawable.marker_waypoint_pkg); + wpIcons.put("puzzle", R.drawable.marker_waypoint_puzzle); + wpIcons.put("stage", R.drawable.marker_waypoint_stage); + wpIcons.put("trailhead", R.drawable.marker_waypoint_trailhead); + } + + int icon = -1; + String iconTxt = null; + + if (cache) { + if (StringUtils.isNotBlank(type)) { + if (own) { + iconTxt = type + "-own"; + } else if (found) { + iconTxt = type + "-found"; + } else if (disabled) { + iconTxt = type + "-disabled"; + } else { + iconTxt = type; + } + } else { + iconTxt = "traditional"; + } + + if (gcIcons.containsKey(iconTxt)) { + icon = gcIcons.get(iconTxt); + } else { + icon = gcIcons.get("traditional"); + } + } else { + if (StringUtils.isNotBlank(type)) { + iconTxt = type; + } else { + iconTxt = "waypoint"; + } + + if (wpIcons.containsKey(iconTxt)) { + icon = wpIcons.get(iconTxt); + } else { + icon = wpIcons.get("waypoint"); + } + } + + return icon; + } + + private static void fillIconsMap() { + if (gcIcons.isEmpty()) { + gcIcons.put("type_ape", R.drawable.type_ape); + gcIcons.put("type_cito", R.drawable.type_cito); + gcIcons.put("type_earth", R.drawable.type_earth); + gcIcons.put("type_event", R.drawable.type_event); + gcIcons.put("type_letterbox", R.drawable.type_letterbox); + gcIcons.put("type_locationless", R.drawable.type_locationless); + gcIcons.put("type_mega", R.drawable.type_mega); + gcIcons.put("type_multi", R.drawable.type_multi); + gcIcons.put("type_traditional", R.drawable.type_traditional); + gcIcons.put("type_virtual", R.drawable.type_virtual); + gcIcons.put("type_webcam", R.drawable.type_webcam); + gcIcons.put("type_wherigo", R.drawable.type_wherigo); + gcIcons.put("type_mystery", R.drawable.type_mystery); + gcIcons.put("type_gchq", R.drawable.type_hq); + // default markers + gcIcons.put("ape", R.drawable.marker_cache_ape); + gcIcons.put("cito", R.drawable.marker_cache_cito); + gcIcons.put("earth", R.drawable.marker_cache_earth); + gcIcons.put("event", R.drawable.marker_cache_event); + gcIcons.put("letterbox", R.drawable.marker_cache_letterbox); + gcIcons.put("locationless", R.drawable.marker_cache_locationless); + gcIcons.put("mega", R.drawable.marker_cache_mega); + gcIcons.put("multi", R.drawable.marker_cache_multi); + gcIcons.put("traditional", R.drawable.marker_cache_traditional); + gcIcons.put("virtual", R.drawable.marker_cache_virtual); + gcIcons.put("webcam", R.drawable.marker_cache_webcam); + gcIcons.put("wherigo", R.drawable.marker_cache_wherigo); + gcIcons.put("mystery", R.drawable.marker_cache_mystery); + gcIcons.put("gchq", R.drawable.marker_cache_gchq); + // own cache markers + gcIcons.put("ape-own", R.drawable.marker_cache_ape_own); + gcIcons.put("cito-own", R.drawable.marker_cache_cito_own); + gcIcons.put("earth-own", R.drawable.marker_cache_earth_own); + gcIcons.put("event-own", R.drawable.marker_cache_event_own); + gcIcons.put("letterbox-own", R.drawable.marker_cache_letterbox_own); + gcIcons.put("locationless-own", R.drawable.marker_cache_locationless_own); + gcIcons.put("mega-own", R.drawable.marker_cache_mega_own); + gcIcons.put("multi-own", R.drawable.marker_cache_multi_own); + gcIcons.put("traditional-own", R.drawable.marker_cache_traditional_own); + gcIcons.put("virtual-own", R.drawable.marker_cache_virtual_own); + gcIcons.put("webcam-own", R.drawable.marker_cache_webcam_own); + gcIcons.put("wherigo-own", R.drawable.marker_cache_wherigo_own); + gcIcons.put("mystery-own", R.drawable.marker_cache_mystery_own); + gcIcons.put("gchq-own", R.drawable.marker_cache_gchq_own); + // found cache markers + gcIcons.put("ape-found", R.drawable.marker_cache_ape_found); + gcIcons.put("cito-found", R.drawable.marker_cache_cito_found); + gcIcons.put("earth-found", R.drawable.marker_cache_earth_found); + gcIcons.put("event-found", R.drawable.marker_cache_event_found); + gcIcons.put("letterbox-found", R.drawable.marker_cache_letterbox_found); + gcIcons.put("locationless-found", R.drawable.marker_cache_locationless_found); + gcIcons.put("mega-found", R.drawable.marker_cache_mega_found); + gcIcons.put("multi-found", R.drawable.marker_cache_multi_found); + gcIcons.put("traditional-found", R.drawable.marker_cache_traditional_found); + gcIcons.put("virtual-found", R.drawable.marker_cache_virtual_found); + gcIcons.put("webcam-found", R.drawable.marker_cache_webcam_found); + gcIcons.put("wherigo-found", R.drawable.marker_cache_wherigo_found); + gcIcons.put("mystery-found", R.drawable.marker_cache_mystery_found); + gcIcons.put("gchq-found", R.drawable.marker_cache_gchq_found); + // disabled cache markers + gcIcons.put("ape-disabled", R.drawable.marker_cache_ape_disabled); + gcIcons.put("cito-disabled", R.drawable.marker_cache_cito_disabled); + gcIcons.put("earth-disabled", R.drawable.marker_cache_earth_disabled); + gcIcons.put("event-disabled", R.drawable.marker_cache_event_disabled); + gcIcons.put("letterbox-disabled", R.drawable.marker_cache_letterbox_disabled); + gcIcons.put("locationless-disabled", R.drawable.marker_cache_locationless_disabled); + gcIcons.put("mega-disabled", R.drawable.marker_cache_mega_disabled); + gcIcons.put("multi-disabled", R.drawable.marker_cache_multi_disabled); + gcIcons.put("traditional-disabled", R.drawable.marker_cache_traditional_disabled); + gcIcons.put("virtual-disabled", R.drawable.marker_cache_virtual_disabled); + gcIcons.put("webcam-disabled", R.drawable.marker_cache_webcam_disabled); + gcIcons.put("wherigo-disabled", R.drawable.marker_cache_wherigo_disabled); + gcIcons.put("mystery-disabled", R.drawable.marker_cache_mystery_disabled); + gcIcons.put("gchq-disabled", R.drawable.marker_cache_gchq_disabled); + } + } + + public static boolean runNavigation(Activity activity, Resources res, cgSettings settings, final Geopoint coords) { + return runNavigation(activity, res, settings, coords, null); + } + + public static boolean runNavigation(Activity activity, Resources res, cgSettings settings, final Geopoint coords, final Geopoint coordsNow) { + if (activity == null) { + return false; + } + if (settings == null) { + return false; + } + + // Google Navigation + if (settings.useGNavigation == 1) { + try { + activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("google.navigation:ll=" + coords.getLatitude() + "," + coords.getLongitude()))); + + return true; + } catch (Exception e) { + // nothing + } + } + + // Google Maps Directions + try { + if (coordsNow != null) { + activity.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("http://maps.google.com/maps?f=d&saddr=" + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + + "&daddr=" + coords.getLatitude() + "," + coords.getLongitude()))); + } else { + activity.startActivity(new Intent(Intent.ACTION_VIEW, + Uri.parse("http://maps.google.com/maps?f=d&daddr=" + coords.getLatitude() + "," + coords.getLongitude()))); + } + + return true; + } catch (Exception e) { + // nothing + } + + Log.i(cgSettings.tag, "cgBase.runNavigation: No navigation application available."); + + if (res != null) { + ActivityMixin.showToast(activity, res.getString(R.string.err_navigation_no)); + } + + return false; + } + + public String getMapUserToken(Handler noTokenHandler) { + final cgResponse response = request(false, "www.geocaching.com", "/map/default.aspx", "GET", "", 0, false); + final String data = response.getData(); + String usertoken = null; + + if (StringUtils.isNotBlank(data)) { + final Pattern pattern = Pattern.compile("var userToken[^=]*=[^']*'([^']+)';", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); + + final Matcher matcher = pattern.matcher(data); + while (matcher.find()) { + if (matcher.groupCount() > 0) { + usertoken = matcher.group(1); + } + } + } + + if (noTokenHandler != null && StringUtils.isBlank(usertoken)) { + noTokenHandler.sendEmptyMessage(0); + } + + return usertoken; + } + + public static Double getElevation(final Geopoint coords) { + try { + final String host = "maps.googleapis.com"; + final String path = "/maps/api/elevation/json"; + final String params = "sensor=false&locations=" + + String.format((Locale) null, "%.6f", coords.getLatitude()) + "," + + String.format((Locale) null, "%.6f", coords.getLongitude()); + + final String data = requestJSON(host, path, params); + + if (StringUtils.isBlank(data)) { + return null; + } + + JSONObject response = new JSONObject(data); + String status = response.getString("status"); + + if (status == null || status.equalsIgnoreCase("OK") == false) { + return null; + } + + if (response.has("results")) { + JSONArray results = response.getJSONArray("results"); + JSONObject result = results.getJSONObject(0); + return result.getDouble("elevation"); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgBase.getElevation: " + e.toString()); + } + + return null; + } + + /** + * Generate a time string according to system-wide settings (locale, 12/24 hour) + * such as "13:24". + * + * @param date + * milliseconds since the epoch + * @return the formatted string + */ + public String formatTime(long date) { + return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME); + } + + /** + * Generate a date string according to system-wide settings (locale, date format) + * such as "20 December" or "20 December 2010". The year will only be included when necessary. + * + * @param date + * milliseconds since the epoch + * @return the formatted string + */ + public String formatDate(long date) { + return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE); + } + + /** + * Generate a date string according to system-wide settings (locale, date format) + * such as "20 December 2010". The year will always be included, making it suitable + * to generate long-lived log entries. + * + * @param date + * milliseconds since the epoch + * @return the formatted string + */ + public String formatFullDate(long date) { + return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_SHOW_YEAR); + } + + /** + * Generate a numeric date string according to system-wide settings (locale, date format) + * such as "10/20/2010". + * + * @param date + * milliseconds since the epoch + * @return the formatted string + */ + public String formatShortDate(long date) { + return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE + | DateUtils.FORMAT_NUMERIC_DATE); + } + + /** + * Generate a numeric date and time string according to system-wide settings (locale, + * date format) such as "7 sept. à 12:35". + * + * @param context + * a Context + * @param date + * milliseconds since the epoch + * @return the formatted string + */ + public static String formatShortDateTime(Context context, long date) { + return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL); + } + + /** + * TODO This method is only needed until the settings are a singleton + * + * @return + */ + public String getUserName() { + return settings.getUsername(); + } + + /** + * insert text into the EditText at the current cursor position + * + * @param editText + * @param insertText + * @param addSpace + * add a space character, if there is no whitespace before the current cursor position + */ + static void insertAtPosition(final EditText editText, String insertText, final boolean addSpace) { + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + int start = Math.min(selectionStart, selectionEnd); + int end = Math.max(selectionStart, selectionEnd); + + String content = editText.getText().toString(); + if (start > 0 && !Character.isWhitespace(content.charAt(start - 1))) { + insertText = " " + insertText; + } + + editText.getText().replace(start, end, insertText); + int newCursor = start + insertText.length(); + editText.setSelection(newCursor, newCursor); + } +} diff --git a/main/src/cgeo/geocaching/cgCache.java b/main/src/cgeo/geocaching/cgCache.java new file mode 100644 index 0000000..68aaf49 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCache.java @@ -0,0 +1,461 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.IAbstractActivity; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.text.Spannable; +import android.util.Log; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Internal c:geo representation of a "cache" + */ +public class cgCache implements ICache { + + public Long updated = null; + public Long detailedUpdate = null; + public Long visitedDate = null; + public Integer reason = 0; + public Boolean detailed = false; + /** + * Code of the cache like GCABCD + */ + public String geocode = ""; + public String cacheId = ""; + public String guid = ""; + public String type = ""; + public String name = ""; + public Spannable nameSp = null; + public String owner = ""; + public String ownerReal = ""; + public Date hidden = null; + public String hint = ""; + public CacheSize size = null; + public Float difficulty = Float.valueOf(0); + public Float terrain = Float.valueOf(0); + public Float direction = null; + public Float distance = null; + public String latlon = ""; + public String latitudeString = ""; + public String longitudeString = ""; + public String location = ""; + public Geopoint coords = null; + public boolean reliableLatLon = false; + public Double elevation = null; + public String personalNote = null; + public String shortdesc = ""; + public String description = ""; + public boolean disabled = false; + public boolean archived = false; + public boolean members = false; + public boolean found = false; + public boolean favourite = false; + public boolean own = false; + public Integer favouriteCnt = null; + public Float rating = null; + public Integer votes = null; + public Float myVote = null; + public int inventoryItems = 0; + public boolean onWatchlist = false; + public List<String> attributes = null; + public List<cgWaypoint> waypoints = null; + public List<cgImage> spoilers = null; + public List<cgLog> logs = null; + public List<cgTrackable> inventory = null; + public Map<Integer, Integer> logCounts = new HashMap<Integer, Integer>(); + public boolean logOffline = false; + // temporary values + public boolean statusChecked = false; + public boolean statusCheckedView = false; + public String directionImg = null; + + /** + * Gather missing information from another cache object. + * + * @param other + * the other version, or null if non-existent + */ + public void gatherMissingFrom(final cgCache other) { + if (other == null) { + return; + } + + updated = System.currentTimeMillis(); + if (detailed == false && other.detailed) { + detailed = true; + detailedUpdate = updated; + } + + if (visitedDate == null || visitedDate == 0) { + visitedDate = other.visitedDate; + } + if (reason == null || reason == 0) { + reason = other.reason; + } + if (StringUtils.isBlank(geocode)) { + geocode = other.geocode; + } + if (StringUtils.isBlank(cacheId)) { + cacheId = other.cacheId; + } + if (StringUtils.isBlank(guid)) { + guid = other.guid; + } + if (StringUtils.isBlank(type)) { + type = other.type; + } + if (StringUtils.isBlank(name)) { + name = other.name; + } + if (StringUtils.isBlank(nameSp)) { + nameSp = other.nameSp; + } + if (StringUtils.isBlank(owner)) { + owner = other.owner; + } + if (StringUtils.isBlank(ownerReal)) { + ownerReal = other.ownerReal; + } + if (hidden == null) { + hidden = other.hidden; + } + if (StringUtils.isBlank(hint)) { + hint = other.hint; + } + if (size == null) { + size = other.size; + } + if (difficulty == null || difficulty == 0) { + difficulty = other.difficulty; + } + if (terrain == null || terrain == 0) { + terrain = other.terrain; + } + if (direction == null) { + direction = other.direction; + } + if (distance == null) { + distance = other.distance; + } + if (StringUtils.isBlank(latlon)) { + latlon = other.latlon; + } + if (StringUtils.isBlank(latitudeString)) { + latitudeString = other.latitudeString; + } + if (StringUtils.isBlank(longitudeString)) { + longitudeString = other.longitudeString; + } + if (StringUtils.isBlank(location)) { + location = other.location; + } + if (coords == null) { + coords = other.coords; + } + if (elevation == null) { + elevation = other.elevation; + } + if (StringUtils.isNotBlank(personalNote)) { + personalNote = other.personalNote; + } + if (StringUtils.isBlank(shortdesc)) { + shortdesc = other.shortdesc; + } + if (StringUtils.isBlank(description)) { + description = other.description; + } + if (favouriteCnt == null) { + favouriteCnt = other.favouriteCnt; + } + if (rating == null) { + rating = other.rating; + } + if (votes == null) { + votes = other.votes; + } + if (myVote == null) { + myVote = other.myVote; + } + if (attributes == null) { + attributes = other.attributes; + } + if (waypoints == null) { + waypoints = other.waypoints; + } + cgWaypoint.mergeWayPoints(waypoints, other.waypoints); + if (spoilers == null) { + spoilers = other.spoilers; + } + if (inventory == null) { + // If inventoryItems is 0, it can mean both + // "don't know" or "0 items". Since we cannot distinguish + // them here, only populate inventoryItems from + // old data when we have to do it for inventory. + inventory = other.inventory; + inventoryItems = other.inventoryItems; + } + if (logs == null || logs.isEmpty()) { // keep last known logs if none + logs = other.logs; + } + } + + public boolean hasTrackables() { + return inventoryItems > 0; + } + + public boolean canBeAddedToCalendar() { + // is event type? + if (!isEventCache()) { + return false; + } + // has event date set? + if (hidden == null) { + return false; + } + // is in future? + Date today = new Date(); + today.setHours(0); + today.setMinutes(0); + today.setSeconds(0); + if (hidden.compareTo(today) <= 0) { + return false; + } + return true; + } + + /** + * checks if a page contains the guid of a cache + * + * @param cache + * the cache to look for + * @param page + * the page to search in + * + * @return true: page contains guid of cache, false: otherwise + */ + boolean isGuidContainedInPage(final String page) { + // check if the guid of the cache is anywhere in the page + if (StringUtils.isBlank(guid)) { + return false; + } + Pattern patternOk = Pattern.compile(guid, Pattern.CASE_INSENSITIVE); + Matcher matcherOk = patternOk.matcher(page); + if (matcherOk.find()) { + Log.i(cgSettings.tag, "cgCache.isGuidContainedInPage: guid '" + guid + "' found"); + return true; + } else { + Log.i(cgSettings.tag, "cgCache.isGuidContainedInPage: guid '" + guid + "' not found"); + return false; + } + } + + public boolean isEventCache() { + return "event".equalsIgnoreCase(type) || "mega".equalsIgnoreCase(type) || "cito".equalsIgnoreCase(type); + } + + public boolean logVisit(IAbstractActivity fromActivity) { + if (StringUtils.isBlank(cacheId)) { + fromActivity.showToast(((Activity) fromActivity).getResources().getString(R.string.err_cannot_log_visit)); + return true; + } + Intent logVisitIntent = new Intent((Activity) fromActivity, cgeovisit.class); + logVisitIntent.putExtra(cgeovisit.EXTRAS_ID, cacheId); + logVisitIntent.putExtra(cgeovisit.EXTRAS_GEOCODE, geocode.toUpperCase()); + logVisitIntent.putExtra(cgeovisit.EXTRAS_FOUND, found); + + ((Activity) fromActivity).startActivity(logVisitIntent); + + return true; + } + + public boolean logOffline(final IAbstractActivity fromActivity, final int logType, final cgSettings settings, final cgBase base) { + String log = ""; + if (StringUtils.isNotBlank(settings.getSignature()) + && settings.signatureAutoinsert) { + log = LogTemplateProvider.applyTemplates(settings.getSignature(), base, true); + } + logOffline(fromActivity, log, Calendar.getInstance(), logType); + return true; + } + + void logOffline(final IAbstractActivity fromActivity, final String log, Calendar date, final int logType) { + if (logType <= 0) { + return; + } + cgeoapplication app = (cgeoapplication) ((Activity) fromActivity).getApplication(); + final boolean status = app.saveLogOffline(geocode, date.getTime(), logType, log); + + Resources res = ((Activity) fromActivity).getResources(); + if (status) { + fromActivity.showToast(res.getString(R.string.info_log_saved)); + app.saveVisitDate(geocode); + } else { + fromActivity.showToast(res.getString(R.string.err_log_post_failed)); + } + } + + public List<Integer> getPossibleLogTypes(cgSettings settings) { + boolean isOwner = owner != null && owner.equalsIgnoreCase(settings.getUsername()); + List<Integer> types = new ArrayList<Integer>(); + if ("event".equals(type) || "mega".equals(type) || "cito".equals(type) || "lostfound".equals(type)) { + types.add(cgBase.LOG_WILL_ATTEND); + types.add(cgBase.LOG_NOTE); + types.add(cgBase.LOG_ATTENDED); + types.add(cgBase.LOG_NEEDS_ARCHIVE); + if (isOwner) { + types.add(cgBase.LOG_ANNOUNCEMENT); + } + } else if ("webcam".equals(type)) { + types.add(cgBase.LOG_WEBCAM_PHOTO_TAKEN); + types.add(cgBase.LOG_DIDNT_FIND_IT); + types.add(cgBase.LOG_NOTE); + types.add(cgBase.LOG_NEEDS_ARCHIVE); + types.add(cgBase.LOG_NEEDS_MAINTENANCE); + } else { + types.add(cgBase.LOG_FOUND_IT); + types.add(cgBase.LOG_DIDNT_FIND_IT); + types.add(cgBase.LOG_NOTE); + types.add(cgBase.LOG_NEEDS_ARCHIVE); + types.add(cgBase.LOG_NEEDS_MAINTENANCE); + } + if (isOwner) { + types.add(cgBase.LOG_OWNER_MAINTENANCE); + types.add(cgBase.LOG_TEMP_DISABLE_LISTING); + types.add(cgBase.LOG_ENABLE_LISTING); + types.add(cgBase.LOG_ARCHIVE); + types.remove(Integer.valueOf(cgBase.LOG_UPDATE_COORDINATES)); + } + return types; + } + + public void openInBrowser(Activity fromActivity) { + fromActivity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getCacheUrl()))); + } + + private String getCacheUrl() { + return getConnector().getCacheUrl(this); + } + + private IConnector getConnector() { + return ConnectorFactory.getConnector(this); + } + + public boolean canOpenInBrowser() { + return getCacheUrl() != null; + } + + public boolean supportsRefresh() { + return getConnector().supportsRefreshCache(this); + } + + public boolean supportsWatchList() { + return getConnector().supportsWatchList(); + } + + public boolean supportsLogging() { + return getConnector().supportsLogging(); + } + + @Override + public Float getDifficulty() { + return difficulty; + } + + @Override + public String getGeocode() { + return geocode; + } + + @Override + public String getLatitude() { + return latitudeString; + } + + @Override + public String getLongitude() { + return longitudeString; + } + + @Override + public String getOwner() { + return owner; + } + + @Override + public CacheSize getSize() { + return size; + } + + @Override + public Float getTerrain() { + return terrain; + } + + @Override + public String getType() { + return type; + } + + @Override + public boolean isArchived() { + return archived; + } + + @Override + public boolean isDisabled() { + return disabled; + } + + @Override + public boolean isMembersOnly() { + return members; + } + + @Override + public boolean isOwn() { + return own; + } + + @Override + public String getOwnerReal() { + return ownerReal; + } + + @Override + public String getHint() { + return hint; + } + + @Override + public String getDescription() { + return description; + } + + @Override + public String getShortDescription() { + return shortdesc; + } + + @Override + public String getName() { + return name; + } + +} diff --git a/main/src/cgeo/geocaching/cgCacheListAdapter.java b/main/src/cgeo/geocaching/cgCacheListAdapter.java new file mode 100644 index 0000000..98b5e5b --- /dev/null +++ b/main/src/cgeo/geocaching/cgCacheListAdapter.java @@ -0,0 +1,912 @@ +package cgeo.geocaching; + +import cgeo.geocaching.filter.cgFilter; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.sorting.CacheComparator; +import cgeo.geocaching.sorting.DistanceComparator; +import cgeo.geocaching.sorting.VisitComparator; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Intent; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.Drawable; +import android.text.Spannable; +import android.text.style.StrikethroughSpan; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.GestureDetector; +import android.view.LayoutInflater; +import android.view.MotionEvent; +import android.view.View; +import android.view.ViewGroup; +import android.view.animation.AccelerateDecelerateInterpolator; +import android.view.animation.AlphaAnimation; +import android.view.animation.Animation; +import android.view.animation.AnimationSet; +import android.view.animation.TranslateAnimation; +import android.widget.ArrayAdapter; +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class cgCacheListAdapter extends ArrayAdapter<cgCache> { + + private Resources res = null; + private List<cgCache> list = null; + private cgSettings settings = null; + private cgCacheView holder = null; + private LayoutInflater inflater = null; + private Activity activity = null; + private cgBase base = null; + private CacheComparator statComparator = null; + private boolean historic = false; + private Geopoint coords = null; + private float azimuth = 0; + private long lastSort = 0L; + private boolean sort = true; + private int checked = 0; + private boolean selectMode = false; + private static Map<String, Drawable> gcIconDrawables = new HashMap<String, Drawable>(); + private List<cgCompassMini> compasses = new ArrayList<cgCompassMini>(); + private List<cgDistanceView> distances = new ArrayList<cgDistanceView>(); + private int[] ratingBcgs = new int[3]; + private float pixelDensity = 1f; + private static final int SWIPE_MIN_DISTANCE = 60; + private static final int SWIPE_MAX_OFF_PATH = 100; + private static final int SWIPE_DISTANCE = 80; + private static final float SWIPE_OPACITY = 0.5f; + private cgFilter currentFilter = null; + private List<cgCache> originalList = null; + + public cgCacheListAdapter(Activity activityIn, cgSettings settingsIn, List<cgCache> listIn, cgBase baseIn) { + super(activityIn, 0, listIn); + + res = activityIn.getResources(); + activity = activityIn; + settings = settingsIn; + list = listIn; + base = baseIn; + + DisplayMetrics metrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + pixelDensity = metrics.density; + + if (gcIconDrawables == null || gcIconDrawables.isEmpty()) { + for (String cacheType : cgBase.cacheTypesInv.keySet()) { + gcIconDrawables.put(cacheType, (Drawable) activity.getResources().getDrawable(cgBase.getCacheIcon(cacheType))); + } + } + + if (settings.skin == 0) { + ratingBcgs[0] = R.drawable.favourite_background_red_dark; + ratingBcgs[1] = R.drawable.favourite_background_orange_dark; + ratingBcgs[2] = R.drawable.favourite_background_green_dark; + } else { + ratingBcgs[0] = R.drawable.favourite_background_red_light; + ratingBcgs[1] = R.drawable.favourite_background_orange_light; + ratingBcgs[2] = R.drawable.favourite_background_green_light; + } + } + + public void setComparator(CacheComparator comparator) { + statComparator = comparator; + + forceSort(coords); + } + + /** + * Called when a new page of caches was loaded. + */ + public void reFilter() { + if (currentFilter != null) { + // Back up the list again + originalList = new ArrayList<cgCache>(list); + + currentFilter.filter(list); + } + } + + /** + * Called after a user action on the filter menu. + */ + public void setFilter(cgFilter filter) { + // Backup current caches list if it isn't backed up yet + if (originalList == null) { + originalList = new ArrayList<cgCache>(list); + } + + // If there is already a filter in place, this is a request to change or clear the filter, so we have to + // replace the original cache list + if (currentFilter != null) { + list.clear(); + list.addAll(originalList); + } + + // Do the filtering or clear it + if (filter != null) { + filter.filter(list); + } + currentFilter = filter; + + notifyDataSetChanged(); + } + + public void clearFilter() { + if (originalList != null) { + list.clear(); + list.addAll(originalList); + + currentFilter = null; + } + + notifyDataSetChanged(); + } + + public boolean isFilter() { + if (currentFilter != null) { + return true; + } else { + return false; + } + } + + public String getFilterName() { + return currentFilter.getFilterName(); + } + + public void setHistoric(boolean historicIn) { + historic = historicIn; + + if (historic) { + statComparator = new VisitComparator(); + } else { + statComparator = null; + } + } + + public int getChecked() { + return checked; + } + + public boolean setSelectMode(boolean status, boolean clear) { + selectMode = status; + + if (selectMode == false && clear) { + for (cgCache cache : list) { + cache.statusChecked = false; + cache.statusCheckedView = false; + } + checked = 0; + } else if (selectMode) { + for (cgCache cache : list) { + cache.statusCheckedView = false; + } + } + checkChecked(0); + + notifyDataSetChanged(); + + return selectMode; + } + + public boolean getSelectMode() { + return selectMode; + } + + public void switchSelectMode() { + selectMode = !selectMode; + + if (selectMode == false) { + for (cgCache cache : list) { + cache.statusChecked = false; + cache.statusCheckedView = false; + } + checked = 0; + } else if (selectMode) { + for (cgCache cache : list) { + cache.statusCheckedView = false; + } + } + checkChecked(0); + + notifyDataSetChanged(); + } + + public void invertSelection() { + int check = 0; + + for (cgCache cache : list) { + if (cache.statusChecked) { + cache.statusChecked = false; + cache.statusCheckedView = false; + } else { + cache.statusChecked = true; + cache.statusCheckedView = true; + + check++; + } + } + checkChecked(check); + + notifyDataSetChanged(); + } + + public void forceSort(final Geopoint coordsIn) { + if (list == null || list.isEmpty()) { + return; + } + if (sort == false) { + return; + } + + try { + if (statComparator != null) { + Collections.sort((List<cgCache>) list, statComparator); + } else { + if (coordsIn == null) { + return; + } + + final DistanceComparator dstComparator = new DistanceComparator(coordsIn); + Collections.sort((List<cgCache>) list, dstComparator); + } + notifyDataSetChanged(); + } catch (Exception e) { + Log.w(cgSettings.tag, "cgCacheListAdapter.setActualCoordinates: failed to sort caches in list"); + } + } + + public void setActualCoordinates(final Geopoint coordsIn) { + if (coordsIn == null) { + return; + } + + coords = coordsIn; + + if (list != null && list.isEmpty() == false && (System.currentTimeMillis() - lastSort) > 1000 && sort) { + try { + if (statComparator != null) { + Collections.sort((List<cgCache>) list, statComparator); + } else { + final DistanceComparator dstComparator = new DistanceComparator(coordsIn); + Collections.sort((List<cgCache>) list, dstComparator); + } + notifyDataSetChanged(); + } catch (Exception e) { + Log.w(cgSettings.tag, "cgCacheListAdapter.setActualCoordinates: failed to sort caches in list"); + } + + lastSort = System.currentTimeMillis(); + } + + if (CollectionUtils.isNotEmpty(distances)) { + for (cgDistanceView distance : distances) { + distance.update(coordsIn); + } + } + + if (CollectionUtils.isNotEmpty(compasses)) { + for (cgCompassMini compass : compasses) { + compass.updateCoords(coordsIn); + } + } + } + + public void setActualHeading(Float directionNow) { + if (directionNow == null) { + return; + } + + azimuth = directionNow; + + if (CollectionUtils.isNotEmpty(compasses)) { + for (cgCompassMini compass : compasses) { + compass.updateAzimuth(azimuth); + } + } + } + + /** + * clear all check marks + * + * @return + */ + public boolean resetChecks() { + if (list.isEmpty()) { + return false; + } + if (checked <= 0) { + return false; + } + + boolean status = getSelectMode(); + int cleared = 0; + for (cgCache cache : list) { + if (cache.statusChecked) { + cache.statusChecked = false; + + checkChecked(-1); + cleared++; + } + } + setSelectMode(false, false); + notifyDataSetChanged(); + + if (cleared > 0 || status) { + return true; + } else { + return false; + } + } + + @Override + public View getView(int position, View rowView, ViewGroup parent) { + if (inflater == null) { + inflater = ((Activity) getContext()).getLayoutInflater(); + } + + if (position > getCount()) { + Log.w(cgSettings.tag, "cgCacheListAdapter.getView: Attempt to access missing item #" + position); + return null; + } + + cgCache cache = getItem(position); + + if (rowView == null) { + rowView = (View) inflater.inflate(R.layout.cache, null); + + holder = new cgCacheView(); + holder.oneCache = (RelativeLayout) rowView.findViewById(R.id.one_cache); + holder.checkbox = (CheckBox) rowView.findViewById(R.id.checkbox); + holder.oneInfo = (RelativeLayout) rowView.findViewById(R.id.one_info); + holder.oneCheckbox = (RelativeLayout) rowView.findViewById(R.id.one_checkbox); + holder.logStatusMark = (ImageView) rowView.findViewById(R.id.log_status_mark); + holder.oneCache = (RelativeLayout) rowView.findViewById(R.id.one_cache); + holder.text = (TextView) rowView.findViewById(R.id.text); + holder.directionLayout = (RelativeLayout) rowView.findViewById(R.id.direction_layout); + holder.distance = (cgDistanceView) rowView.findViewById(R.id.distance); + holder.direction = (cgCompassMini) rowView.findViewById(R.id.direction); + holder.dirImgLayout = (RelativeLayout) rowView.findViewById(R.id.dirimg_layout); + holder.dirImg = (ImageView) rowView.findViewById(R.id.dirimg); + holder.inventory = (RelativeLayout) rowView.findViewById(R.id.inventory); + holder.favourite = (TextView) rowView.findViewById(R.id.favourite); + holder.info = (TextView) rowView.findViewById(R.id.info); + + rowView.setTag(holder); + } else { + holder = (cgCacheView) rowView.getTag(); + } + + if (cache.own) { + if (settings.skin == 1) { + holder.oneInfo.setBackgroundResource(R.color.owncache_background_light); + holder.oneCheckbox.setBackgroundResource(R.color.owncache_background_light); + } else { + holder.oneInfo.setBackgroundResource(R.color.owncache_background_dark); + holder.oneCheckbox.setBackgroundResource(R.color.owncache_background_dark); + } + } else { + if (settings.skin == 1) { + holder.oneInfo.setBackgroundResource(R.color.background_light); + holder.oneCheckbox.setBackgroundResource(R.color.background_light); + } else { + holder.oneInfo.setBackgroundResource(R.color.background_dark); + holder.oneCheckbox.setBackgroundResource(R.color.background_dark); + } + } + + final touchListener touchLst = new touchListener(cache.geocode, cache.name, cache); + rowView.setOnClickListener(touchLst); + rowView.setOnLongClickListener(touchLst); + rowView.setOnTouchListener(touchLst); + rowView.setLongClickable(true); + + if (selectMode) { + if (cache.statusCheckedView) { + moveRight(holder, cache, true); // move fast when already slided + } else { + moveRight(holder, cache, false); + } + } else if (cache.statusChecked) { + holder.checkbox.setChecked(true); + if (cache.statusCheckedView) { + moveRight(holder, cache, true); // move fast when already slided + } else { + moveRight(holder, cache, false); + } + } else { + holder.checkbox.setChecked(false); + if (cache.statusCheckedView == false) { + holder.oneInfo.clearAnimation(); + } else { + moveLeft(holder, cache, false); + } + } + + holder.checkbox.setOnClickListener(new checkBoxListener(cache)); + + if (distances.contains(holder.distance) == false) { + distances.add(holder.distance); + } + holder.distance.setContent(base, cache.coords); + if (compasses.contains(holder.direction) == false) { + compasses.add(holder.direction); + } + holder.direction.setContent(cache.coords); + + if (cache.found && cache.logOffline) { + holder.logStatusMark.setImageResource(R.drawable.mark_green_red); + holder.logStatusMark.setVisibility(View.VISIBLE); + } else if (cache.found) { + holder.logStatusMark.setImageResource(R.drawable.mark_green); + holder.logStatusMark.setVisibility(View.VISIBLE); + } else if (cache.logOffline) { + holder.logStatusMark.setImageResource(R.drawable.mark_red); + holder.logStatusMark.setVisibility(View.VISIBLE); + } else { + holder.logStatusMark.setVisibility(View.GONE); + } + + if (cache.nameSp == null) { + cache.nameSp = (new Spannable.Factory()).newSpannable(cache.name); + if (cache.disabled || cache.archived) { // strike + cache.nameSp.setSpan(new StrikethroughSpan(), 0, cache.nameSp.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + } + + holder.text.setText(cache.nameSp, TextView.BufferType.SPANNABLE); + if (gcIconDrawables.containsKey(cache.type)) { // cache icon + holder.text.setCompoundDrawablesWithIntrinsicBounds(gcIconDrawables.get(cache.type), null, null, null); + } else { // unknown cache type, "mystery" icon + holder.text.setCompoundDrawablesWithIntrinsicBounds(gcIconDrawables.get("mystery"), null, null, null); + } + + if (holder.inventory.getChildCount() > 0) { + holder.inventory.removeAllViews(); + } + + ImageView tbIcon = null; + if (cache.inventoryItems > 0) { + tbIcon = (ImageView) inflater.inflate(R.layout.trackable_icon, null); + tbIcon.setImageResource(R.drawable.trackable_all); + + holder.inventory.addView(tbIcon); + holder.inventory.setVisibility(View.VISIBLE); + } else { + holder.inventory.setVisibility(View.GONE); + } + + boolean setDiDi = false; + if (cache.coords != null) { + holder.direction.setVisibility(View.VISIBLE); + holder.direction.updateAzimuth(azimuth); + if (coords != null) { + holder.distance.update(coords); + holder.direction.updateCoords(coords); + } + setDiDi = true; + } else { + if (cache.distance != null) { + holder.distance.setDistance(cache.distance); + setDiDi = true; + } + if (cache.direction != null) { + holder.direction.setVisibility(View.VISIBLE); + holder.direction.updateAzimuth(azimuth); + holder.direction.updateHeading(cache.direction); + setDiDi = true; + } + } + + if (setDiDi) { + holder.directionLayout.setVisibility(View.VISIBLE); + holder.dirImgLayout.setVisibility(View.GONE); + } else { + holder.directionLayout.setVisibility(View.GONE); + holder.distance.clear(); + + Bitmap dirImgPre = null; + Bitmap dirImg = null; + try { + dirImgPre = BitmapFactory.decodeFile(cgSettings.getStorage() + cache.geocode + "/direction.png"); + dirImg = dirImgPre.copy(Bitmap.Config.ARGB_8888, true); + + dirImgPre.recycle(); + dirImgPre = null; + } catch (Exception e) { + // nothing + } + + if (dirImg != null) { + if (settings.skin == 0) { + int length = dirImg.getWidth() * dirImg.getHeight(); + int[] pixels = new int[length]; + dirImg.getPixels(pixels, 0, dirImg.getWidth(), 0, 0, dirImg.getWidth(), dirImg.getHeight()); + for (int i = 0; i < length; i++) { + if (pixels[i] == 0xff000000) { // replace black with white + pixels[i] = 0xffffffff; + } + } + dirImg.setPixels(pixels, 0, dirImg.getWidth(), 0, 0, dirImg.getWidth(), dirImg.getHeight()); + } + + holder.dirImg.setImageBitmap(dirImg); + holder.dirImgLayout.setVisibility(View.VISIBLE); + } else { + holder.dirImg.setImageBitmap(null); + holder.dirImgLayout.setVisibility(View.GONE); + } + } + + if (cache.favouriteCnt != null) { + holder.favourite.setText(String.format("%d", cache.favouriteCnt)); + } else { + holder.favourite.setText("---"); + } + + int favoriteBack; + // set default background, neither vote nor rating may be available + if (settings.skin == 1) { + favoriteBack = R.drawable.favourite_background_light; + } else { + favoriteBack = R.drawable.favourite_background_dark; + } + if (cache.myVote != null && cache.myVote > 0) { + if (cache.myVote >= 4) { + favoriteBack = ratingBcgs[2]; + } else if (cache.myVote >= 3) { + favoriteBack = ratingBcgs[1]; + } else if (cache.myVote > 0) { + favoriteBack = ratingBcgs[0]; + } + } else if (cache.rating != null && cache.rating > 0) { + if (cache.rating >= 3.5) { + favoriteBack = ratingBcgs[2]; + } else if (cache.rating >= 2.1) { + favoriteBack = ratingBcgs[1]; + } else if (cache.rating > 0.0) { + favoriteBack = ratingBcgs[0]; + } + } + holder.favourite.setBackgroundResource(favoriteBack); + + StringBuilder cacheInfo = new StringBuilder(); + if (historic && cache.visitedDate != null) { + cacheInfo.append(base.formatTime(cache.visitedDate)); + cacheInfo.append("; "); + cacheInfo.append(base.formatDate(cache.visitedDate)); + } else { + if (StringUtils.isNotBlank(cache.geocode)) { + cacheInfo.append(cache.geocode); + } + if (cache.size != null) { + if (cacheInfo.length() > 0) { + cacheInfo.append(" | "); + } + cacheInfo.append(res.getString(cache.size.stringId)); + } + if ((cache.difficulty != null && cache.difficulty > 0f) || (cache.terrain != null && cache.terrain > 0f) || (cache.rating != null && cache.rating > 0f)) { + if (cacheInfo.length() > 0 && ((cache.difficulty != null && cache.difficulty > 0f) || (cache.terrain != null && cache.terrain > 0f))) { + cacheInfo.append(" |"); + } + + if (cache.difficulty != null && cache.difficulty > 0f) { + cacheInfo.append(" D:"); + cacheInfo.append(String.format(Locale.getDefault(), "%.1f", cache.difficulty)); + } + if (cache.terrain != null && cache.terrain > 0f) { + cacheInfo.append(" T:"); + cacheInfo.append(String.format(Locale.getDefault(), "%.1f", cache.terrain)); + } + } + if (cache.members) { + if (cacheInfo.length() > 0) { + cacheInfo.append(" | "); + } + cacheInfo.append(res.getString(R.string.cache_premium)); + } + if (cache.reason != null && cache.reason == 1) { + if (cacheInfo.length() > 0) { + cacheInfo.append(" | "); + } + cacheInfo.append(res.getString(R.string.cache_offline)); + } + } + holder.info.setText(cacheInfo.toString()); + + return rowView; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + + checked = 0; + for (cgCache cache : list) { + if (cache.statusChecked) { + checked++; + } + } + + distances.clear(); + compasses.clear(); + } + + private class checkBoxListener implements View.OnClickListener { + + private cgCache cache = null; + + public checkBoxListener(cgCache cacheIn) { + cache = cacheIn; + } + + public void onClick(View view) { + final boolean checkNow = ((CheckBox) view).isChecked(); + + if (checkNow) { + cache.statusChecked = true; + checked++; + } else { + cache.statusChecked = false; + checked--; + } + } + } + + private class touchListener implements View.OnLongClickListener, View.OnClickListener, View.OnTouchListener { + + private String geocode = null; + private String name = null; + private cgCache cache = null; + private boolean touch = true; + private GestureDetector gestureDetector = null; + + public touchListener(String geocodeIn, String nameIn, cgCache cacheIn) { + geocode = geocodeIn; + name = nameIn; + cache = cacheIn; + + final detectGesture dGesture = new detectGesture(holder, cache); + gestureDetector = new GestureDetector(dGesture); + } + + // tap on item + public void onClick(View view) { + if (touch == false) { + touch = true; + + return; + } + + if (getSelectMode() || getChecked() > 0) { + return; + } + + // load cache details + Intent cachesIntent = new Intent(getContext(), cgeodetail.class); + cachesIntent.putExtra("geocode", geocode); + cachesIntent.putExtra("name", name); + getContext().startActivity(cachesIntent); + } + + // long tap on item + public boolean onLongClick(View view) { + if (touch == false) { + touch = true; + + return true; + } + + return view.showContextMenu(); + } + + // swipe on item + public boolean onTouch(View view, MotionEvent event) { + if (gestureDetector.onTouchEvent(event)) { + touch = false; + + return true; + } + + return false; + } + } + + class detectGesture extends GestureDetector.SimpleOnGestureListener { + + private cgCacheView holder = null; + private cgCache cache = null; + + public detectGesture(cgCacheView holderIn, cgCache cacheIn) { + holder = holderIn; + cache = cacheIn; + } + + @Override + public boolean onFling(MotionEvent e1, MotionEvent e2, float velocityX, float velocityY) { + try { + if (getSelectMode()) { + return false; + } + + if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) { + return false; + } + + if ((e2.getX() - e1.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { + // left to right swipe + if (cache.statusChecked) { + return true; + } + + if (holder != null && holder.oneInfo != null) { + checkChecked(+1); + holder.checkbox.setChecked(true); + cache.statusChecked = true; + moveRight(holder, cache, false); + } + + return true; + } else if ((e1.getX() - e2.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { + // right to left swipe + if (cache.statusChecked == false) { + return true; + } + + if (holder != null && holder.oneInfo != null) { + if (getSelectMode()) { + setSelectMode(false, false); + } + + checkChecked(-1); + holder.checkbox.setChecked(false); + cache.statusChecked = false; + moveLeft(holder, cache, false); + } + + return true; + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgCacheListAdapter.detectGesture.onFling: " + e.toString()); + } + + return false; + } + } + + private void checkChecked(int cnt) { + // check how many caches are selected, if any block sorting of list + boolean statusChecked = false; + boolean statusSort = false; + checked += cnt; + + if (checked > 0) { + statusChecked = false; + } else { + statusChecked = true; + } + + if (getSelectMode()) { + statusSort = false; + } else { + statusSort = true; + } + + if (statusChecked == false || statusSort == false) { + sort = false; + } else { + sort = true; + } + + if (sort) { + forceSort(coords); + } + } + + private void moveRight(cgCacheView holder, cgCache cache, boolean force) { + if (cache == null) { + return; + } + + try { + holder.checkbox.setChecked(cache.statusChecked); + + // slide cache info + Animation showCheckbox = new TranslateAnimation(0, (int) (SWIPE_DISTANCE * pixelDensity), 0, 0); + showCheckbox.setRepeatCount(0); + if (force) { + showCheckbox.setDuration(0); + } else { + showCheckbox.setDuration(400); + } + showCheckbox.setFillEnabled(true); + showCheckbox.setFillAfter(true); + showCheckbox.setInterpolator(new AccelerateDecelerateInterpolator()); + + // dim cache info + Animation dimInfo = new AlphaAnimation(1.0f, SWIPE_OPACITY); + dimInfo.setRepeatCount(0); + if (force) { + dimInfo.setDuration(0); + } else { + dimInfo.setDuration(400); + } + dimInfo.setFillEnabled(true); + dimInfo.setFillAfter(true); + dimInfo.setInterpolator(new AccelerateDecelerateInterpolator()); + + // animation set (container) + AnimationSet selectAnimation = new AnimationSet(true); + selectAnimation.setFillEnabled(true); + selectAnimation.setFillAfter(true); + + selectAnimation.addAnimation(showCheckbox); + selectAnimation.addAnimation(dimInfo); + + holder.oneInfo.startAnimation(selectAnimation); + cache.statusCheckedView = true; + } catch (Exception e) { + // nothing + } + } + + private void moveLeft(cgCacheView holder, cgCache cache, boolean force) { + if (cache == null) { + return; + } + + try { + holder.checkbox.setChecked(cache.statusChecked); + + // slide cache info + Animation hideCheckbox = new TranslateAnimation((int) (SWIPE_DISTANCE * pixelDensity), 0, 0, 0); + hideCheckbox.setRepeatCount(0); + if (force) { + hideCheckbox.setDuration(0); + } else { + hideCheckbox.setDuration(400); + } + hideCheckbox.setFillEnabled(true); + hideCheckbox.setFillAfter(true); + hideCheckbox.setInterpolator(new AccelerateDecelerateInterpolator()); + + // brighten cache info + Animation brightenInfo = new AlphaAnimation(SWIPE_OPACITY, 1.0f); + brightenInfo.setRepeatCount(0); + if (force) { + brightenInfo.setDuration(0); + } else { + brightenInfo.setDuration(400); + } + brightenInfo.setFillEnabled(true); + brightenInfo.setFillAfter(true); + brightenInfo.setInterpolator(new AccelerateDecelerateInterpolator()); + + // animation set (container) + AnimationSet selectAnimation = new AnimationSet(true); + selectAnimation.setFillEnabled(true); + selectAnimation.setFillAfter(true); + + selectAnimation.addAnimation(hideCheckbox); + selectAnimation.addAnimation(brightenInfo); + + holder.oneInfo.startAnimation(selectAnimation); + cache.statusCheckedView = false; + } catch (Exception e) { + // nothing + } + } +} diff --git a/main/src/cgeo/geocaching/cgCacheView.java b/main/src/cgeo/geocaching/cgCacheView.java new file mode 100644 index 0000000..6b3b433 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCacheView.java @@ -0,0 +1,28 @@ +package cgeo.geocaching; + +import android.widget.CheckBox; +import android.widget.ImageView; +import android.widget.RelativeLayout; +import android.widget.TextView; + +public class cgCacheView { + // layouts & views + public RelativeLayout oneCache; + public RelativeLayout oneInfo; + public RelativeLayout oneCheckbox; + public CheckBox checkbox; + public ImageView logStatusMark; + public TextView text; + public TextView favourite; + public TextView info; + public RelativeLayout inventory; + public RelativeLayout directionLayout; + public cgDistanceView distance; + public cgCompassMini direction; + public RelativeLayout dirImgLayout; + public ImageView dirImg; + + // status + public float startX = -1; + public float prevX = -1; +} diff --git a/main/src/cgeo/geocaching/cgCacheWrap.java b/main/src/cgeo/geocaching/cgCacheWrap.java new file mode 100644 index 0000000..e8668b5 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCacheWrap.java @@ -0,0 +1,15 @@ +package cgeo.geocaching; + +import java.util.ArrayList; +import java.util.List; + +/** + * List of caches + */ +public class cgCacheWrap { + public String error = null; + public String url = ""; + public String[] viewstates = null; + public int totalCnt = 0; + public List<cgCache> cacheList = new ArrayList<cgCache>(); +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgCompass.java b/main/src/cgeo/geocaching/cgCompass.java new file mode 100644 index 0000000..e6b9447 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCompass.java @@ -0,0 +1,271 @@ +package cgeo.geocaching; + +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.os.Handler; +import android.os.Message; +import android.util.AttributeSet; +import android.util.Log; +import android.view.View; + +public class cgCompass extends View { + + private changeThread watchdog = null; + private volatile boolean wantStop = false; + private Context context = null; + private Bitmap compassUnderlay = null; + private Bitmap compassRose = null; + private Bitmap compassArrow = null; + private Bitmap compassOverlay = null; + private double azimuth = 0.0; + private double heading = 0.0; + private double cacheHeading = 0.0; + private double northHeading = 0.0; + private PaintFlagsDrawFilter setfil = null; + private PaintFlagsDrawFilter remfil = null; + private int compassUnderlayWidth = 0; + private int compassUnderlayHeight = 0; + private int compassRoseWidth = 0; + private int compassRoseHeight = 0; + private int compassArrowWidth = 0; + private int compassArrowHeight = 0; + private int compassOverlayWidth = 0; + private int compassOverlayHeight = 0; + private boolean initialDisplay; + private Handler changeHandler = new Handler() { + + @Override + public void handleMessage(Message message) { + try { + invalidate(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgCompass.changeHandler: " + e.toString()); + } + } + }; + + public cgCompass(Context contextIn) { + super(contextIn); + context = contextIn; + } + + public cgCompass(Context contextIn, AttributeSet attrs) { + super(contextIn, attrs); + context = contextIn; + } + + @Override + public void onAttachedToWindow() { + compassUnderlay = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_underlay); + compassRose = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_rose); + compassArrow = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_arrow); + compassOverlay = BitmapFactory.decodeResource(context.getResources(), R.drawable.compass_overlay); + + compassUnderlayWidth = compassUnderlay.getWidth(); + compassUnderlayHeight = compassUnderlay.getWidth(); + compassRoseWidth = compassRose.getWidth(); + compassRoseHeight = compassRose.getWidth(); + compassArrowWidth = compassArrow.getWidth(); + compassArrowHeight = compassArrow.getWidth(); + compassOverlayWidth = compassOverlay.getWidth(); + compassOverlayHeight = compassOverlay.getWidth(); + + setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); + remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); + + initialDisplay = true; + wantStop = false; + + watchdog = new changeThread(); + watchdog.start(); + } + + @Override + public void onDetachedFromWindow() { + wantStop = true; + + if (compassUnderlay != null) { + compassUnderlay.recycle(); + } + + if (compassRose != null) { + compassRose.recycle(); + } + + if (compassArrow != null) { + compassArrow.recycle(); + } + + if (compassOverlay != null) { + compassOverlay.recycle(); + } + } + + protected synchronized void updateNorth(double northHeadingIn, double cacheHeadingIn) { + if (initialDisplay) { + // We will force the compass to move brutally if this is the first + // update since it is visible. + azimuth = northHeadingIn; + heading = cacheHeadingIn; + initialDisplay = false; + } + northHeading = northHeadingIn; + cacheHeading = cacheHeadingIn; + } + + /** + * Compute the new value, moving by small increments. + * + * @param goal + * the goal to reach + * @param actual + * the actual value + * @return the new value + */ + static protected double smoothUpdate(double goal, double actual) { + double diff = goal - actual; + final boolean largeDiff = Math.abs(diff) > 5; + + double offset = 0.0; + + if (diff < 0.0) { + diff = diff + 360.0; + } else if (diff >= 360.0) { + diff = diff - 360.0; + } + + // If the difference is smaller than 1 degree, do nothing as it + // causes the arrow to vibrate. + if (diff > 1.0 && diff <= 180.0) { + offset = largeDiff ? 2.0 : 1.0; + } else if (diff > 180.0 && diff < 359.0) { + offset = largeDiff ? -2.0 : -1.0; + } + + return actual + offset; + } + + private class changeThread extends Thread { + + @Override + public void run() { + while (wantStop == false) { + try { + sleep(50); + } catch (Exception e) { + // nothing + } + + synchronized (cgCompass.this) { + azimuth = smoothUpdate(northHeading, azimuth); + heading = smoothUpdate(cacheHeading, heading); + } + + changeHandler.sendMessage(new Message()); + } + } + } + + @Override + protected void onDraw(Canvas canvas) { + double currentAzimuth; + double currentHeading; + + synchronized (this) { + currentAzimuth = azimuth; + currentHeading = heading; + } + + double azimuthTemp = currentAzimuth; + double azimuthRelative = azimuthTemp - currentHeading; + if (azimuthRelative < 0) { + azimuthRelative = azimuthRelative + 360; + } else if (azimuthRelative >= 360) { + azimuthRelative = azimuthRelative - 360; + } + + // compass margins + int canvasCenterX = (compassRoseWidth / 2) + ((getWidth() - compassRoseWidth) / 2); + int canvasCenterY = (compassRoseHeight / 2) + ((getHeight() - compassRoseHeight) / 2); + + int marginLeftTemp = 0; + int marginTopTemp = 0; + + super.onDraw(canvas); + + canvas.save(); + canvas.setDrawFilter(setfil); + + marginLeftTemp = (getWidth() - compassUnderlayWidth) / 2; + marginTopTemp = (getHeight() - compassUnderlayHeight) / 2; + + canvas.drawBitmap(compassUnderlay, marginLeftTemp, marginTopTemp, null); + + marginLeftTemp = (getWidth() - compassRoseWidth) / 2; + marginTopTemp = (getHeight() - compassRoseHeight) / 2; + + canvas.rotate((float) -azimuthTemp, canvasCenterX, canvasCenterY); + canvas.drawBitmap(compassRose, marginLeftTemp, marginTopTemp, null); + canvas.rotate((float) azimuthTemp, canvasCenterX, canvasCenterY); + + marginLeftTemp = (getWidth() - compassArrowWidth) / 2; + marginTopTemp = (getHeight() - compassArrowHeight) / 2; + + canvas.rotate((float) -azimuthRelative, canvasCenterX, canvasCenterY); + canvas.drawBitmap(compassArrow, marginLeftTemp, marginTopTemp, null); + canvas.rotate((float) azimuthRelative, canvasCenterX, canvasCenterY); + + marginLeftTemp = (getWidth() - compassOverlayWidth) / 2; + marginTopTemp = (getHeight() - compassOverlayHeight) / 2; + + canvas.drawBitmap(compassOverlay, marginLeftTemp, marginTopTemp, null); + + canvas.setDrawFilter(remfil); + canvas.restore(); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = compassArrow.getWidth() + getPaddingLeft() + getPaddingRight(); + + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = compassArrow.getHeight() + getPaddingTop() + getPaddingBottom(); + + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + + return result; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgCompassMini.java b/main/src/cgeo/geocaching/cgCompassMini.java new file mode 100644 index 0000000..2a9f550 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCompassMini.java @@ -0,0 +1,172 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import android.content.Context; +import android.content.res.TypedArray; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.PaintFlagsDrawFilter; +import android.util.AttributeSet; +import android.view.View; + +public class cgCompassMini extends View { + private int arrowSkin = R.drawable.compass_arrow_mini_white; + private Context context = null; + private Geopoint cacheCoords = null; + private Bitmap compassArrow = null; + private float azimuth = 0; + private float heading = 0; + private PaintFlagsDrawFilter setfil = null; + private PaintFlagsDrawFilter remfil = null; + + public cgCompassMini(Context contextIn) { + super(contextIn); + context = contextIn; + } + + public cgCompassMini(Context contextIn, AttributeSet attrs) { + super(contextIn, attrs); + context = contextIn; + + TypedArray attributes = context.obtainStyledAttributes(attrs, R.styleable.cgCompassMini); + int usedSkin = attributes.getInt(R.styleable.cgCompassMini_skin, 0); + if (usedSkin == 1) { + arrowSkin = R.drawable.compass_arrow_mini_black; + } else { + arrowSkin = R.drawable.compass_arrow_mini_white; + } + } + + @Override + public void onAttachedToWindow() { + compassArrow = BitmapFactory.decodeResource(context.getResources(), arrowSkin); + + setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); + remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); + } + + @Override + public void onDetachedFromWindow() { + if (compassArrow != null) { + compassArrow.recycle(); + compassArrow = null; + } + } + + public void setContent(final Geopoint cacheCoordsIn) { + cacheCoords = cacheCoordsIn; + } + + protected void updateAzimuth(float azimuthIn) { + azimuth = azimuthIn; + + updateDirection(); + } + + protected void updateHeading(float headingIn) { + heading = headingIn; + + updateDirection(); + } + + protected void updateCoords(final Geopoint coordsIn) { + if (coordsIn == null || cacheCoords == null) { + return; + } + + heading = coordsIn.bearingTo(cacheCoords); + + updateDirection(); + } + + protected void updateDirection() { + if (compassArrow == null || compassArrow.isRecycled()) { + return; + } + + // compass margins + int compassRoseWidth = compassArrow.getWidth(); + int compassRoseHeight = compassArrow.getWidth(); + int marginLeft = (getWidth() - compassRoseWidth) / 2; + int marginTop = (getHeight() - compassRoseHeight) / 2; + + invalidate(marginLeft, marginTop, (marginLeft + compassRoseWidth), (marginTop + compassRoseHeight)); + } + + @Override + protected void onDraw(Canvas canvas) { + super.onDraw(canvas); + + float azimuthRelative = azimuth - heading; + if (azimuthRelative < 0) { + azimuthRelative = azimuthRelative + 360; + } else if (azimuthRelative >= 360) { + azimuthRelative = azimuthRelative - 360; + } + + // compass margins + canvas.setDrawFilter(setfil); + + int marginLeft = 0; + int marginTop = 0; + + int compassArrowWidth = compassArrow.getWidth(); + int compassArrowHeight = compassArrow.getWidth(); + + int canvasCenterX = (compassArrowWidth / 2) + ((getWidth() - compassArrowWidth) / 2); + int canvasCenterY = (compassArrowHeight / 2) + ((getHeight() - compassArrowHeight) / 2); + + marginLeft = (getWidth() - compassArrowWidth) / 2; + marginTop = (getHeight() - compassArrowHeight) / 2; + + canvas.rotate(-azimuthRelative, canvasCenterX, canvasCenterY); + canvas.drawBitmap(compassArrow, marginLeft, marginTop, null); + canvas.rotate(azimuthRelative, canvasCenterX, canvasCenterY); + + canvas.setDrawFilter(remfil); + } + + @Override + protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { + setMeasuredDimension(measureWidth(widthMeasureSpec), measureHeight(heightMeasureSpec)); + } + + private int measureWidth(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = 21 + getPaddingLeft() + getPaddingRight(); + + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + + return result; + } + + private int measureHeight(int measureSpec) { + int result = 0; + int specMode = MeasureSpec.getMode(measureSpec); + int specSize = MeasureSpec.getSize(measureSpec); + + if (specMode == MeasureSpec.EXACTLY) { + result = specSize; + } else { + result = 21 + getPaddingTop() + getPaddingBottom(); + + if (specMode == MeasureSpec.AT_MOST) { + result = Math.min(result, specSize); + } + } + + return result; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgCoord.java b/main/src/cgeo/geocaching/cgCoord.java new file mode 100644 index 0000000..a8e6349 --- /dev/null +++ b/main/src/cgeo/geocaching/cgCoord.java @@ -0,0 +1,46 @@ +package cgeo.geocaching; + +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.geopoint.Geopoint; + +public class cgCoord { + + public Integer id = null; + public String geocode = ""; + public String type = "cache"; + public String typeSpec = "traditional"; + public String name = ""; + public boolean found = false; + public boolean disabled = false; + public Geopoint coords = new Geopoint(0, 0); + public Float difficulty = null; + public Float terrain = null; + public CacheSize size = null; + + public cgCoord() { + } + + public cgCoord(cgCache cache) { + disabled = cache.disabled; + found = cache.found; + geocode = cache.geocode; + coords = cache.coords; + name = cache.name; + type = "cache"; + typeSpec = cache.type; + difficulty = cache.difficulty; + terrain = cache.terrain; + size = cache.size; + } + + public cgCoord(cgWaypoint waypoint) { + id = waypoint.id; + disabled = false; + found = false; + geocode = ""; + coords = waypoint.coords; + name = waypoint.name; + type = "waypoint"; + typeSpec = waypoint.type; + } +} diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java new file mode 100644 index 0000000..c24d0ff --- /dev/null +++ b/main/src/cgeo/geocaching/cgData.java @@ -0,0 +1,3272 @@ +package cgeo.geocaching; + +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Geopoint.MalformedCoordinateException; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.content.ContentValues; +import android.content.Context; +import android.content.res.Resources; +import android.database.Cursor; +import android.database.DatabaseUtils.InsertHelper; +import android.database.sqlite.SQLiteDatabase; +import android.database.sqlite.SQLiteOpenHelper; +import android.database.sqlite.SQLiteStatement; +import android.os.Environment; +import android.util.Log; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.io.OutputStream; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.LinkedList; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; +import java.util.Set; + +public class cgData { + + /** The list of fields needed for mapping. */ + private static final String[] CACHE_COLUMNS = new String[] { + "_id", "updated", "reason", "detailed", "detailedupdate", "visiteddate", "geocode", "cacheid", "guid", "type", "name", "own", "owner", "owner_real", "hidden", "hint", "size", + "difficulty", "distance", "direction", "terrain", "latlon", "latitude_string", "longitude_string", "location", "latitude", "longitude", "elevation", "shortdesc", + "description", "favourite_cnt", "rating", "votes", "myvote", "disabled", "archived", "members", "found", "favourite", "inventorycoins", "inventorytags", + "inventoryunknown", "onWatchlist", "personal_note", "reliable_latlon" + }; + public cgCacheWrap caches; + private Context context = null; + private String path = null; + private cgDbHelper dbHelper = null; + private SQLiteDatabase databaseRO = null; + private SQLiteDatabase databaseRW = null; + private static final int dbVersion = 57; + private static final String dbName = "data"; + private static final String dbTableCaches = "cg_caches"; + private static final String dbTableLists = "cg_lists"; + private static final String dbTableAttributes = "cg_attributes"; + private static final String dbTableWaypoints = "cg_waypoints"; + private static final String dbTableSpoilers = "cg_spoilers"; + private static final String dbTableLogs = "cg_logs"; + private static final String dbTableLogCount = "cg_logCount"; + private static final String dbTableLogImages = "cg_logImages"; + private static final String dbTableLogsOffline = "cg_logs_offline"; + private static final String dbTableTrackables = "cg_trackables"; + private static final String dbTableSearchDestionationHistory = "cg_search_destination_history"; + private static final String dbCreateCaches = "" + + "create table " + dbTableCaches + " (" + + "_id integer primary key autoincrement, " + + "updated long not null, " + + "detailed integer not null default 0, " + + "detailedupdate long, " + + "visiteddate long, " + + "geocode text unique not null, " + + "reason integer not null default 0, " // cached, favourite... + + "cacheid text, " + + "guid text, " + + "type text, " + + "name text, " + + "own integer not null default 0, " + + "owner text, " + + "owner_real text, " + + "hidden long, " + + "hint text, " + + "size text, " + + "difficulty float, " + + "terrain float, " + + "latlon text, " + + "latitude_string text, " + + "longitude_string text, " + + "location text, " + + "direction double, " + + "distance double, " + + "latitude double, " + + "longitude double, " + + "reliable_latlon integer, " + + "elevation double, " + + "personal_note text, " + + "shortdesc text, " + + "description text, " + + "favourite_cnt integer, " + + "rating float, " + + "votes integer, " + + "myvote float, " + + "disabled integer not null default 0, " + + "archived integer not null default 0, " + + "members integer not null default 0, " + + "found integer not null default 0, " + + "favourite integer not null default 0, " + + "inventorycoins integer default 0, " + + "inventorytags integer default 0, " + + "inventoryunknown integer default 0, " + + "onWatchlist integer default 0 " + + "); "; + private static final String dbCreateLists = "" + + "create table " + dbTableLists + " (" + + "_id integer primary key autoincrement, " + + "title text not null, " + + "updated long not null, " + + "latitude double, " + + "longitude double " + + "); "; + private static final String dbCreateAttributes = "" + + "create table " + dbTableAttributes + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "attribute text " + + "); "; + private final static int ATTRIBUTES_GEOCODE = 2; + private final static int ATTRIBUTES_UPDATED = 3; + private final static int ATTRIBUTES_ATTRIBUTE = 4; + + private static final String dbCreateWaypoints = "" + + "create table " + dbTableWaypoints + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "type text not null default 'waypoint', " + + "prefix text, " + + "lookup text, " + + "name text, " + + "latlon text, " + + "latitude_string text, " + + "longitude_string text, " + + "latitude double, " + + "longitude double, " + + "note text " + + "); "; + private static final String dbCreateSpoilers = "" + + "create table " + dbTableSpoilers + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "url text, " + + "title text, " + + "description text " + + "); "; + private static final String dbCreateLogs = "" + + "create table " + dbTableLogs + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "type integer not null default 4, " + + "author text, " + + "log text, " + + "date long, " + + "found integer not null default 0 " + + "); "; + private final static int LOGS_GEOCODE = 2; + private final static int LOGS_UPDATED = 3; + private final static int LOGS_TYPE = 4; + private final static int LOGS_AUTHOR = 5; + private final static int LOGS_LOG = 6; + private final static int LOGS_DATE = 7; + private final static int LOGS_FOUND = 8; + + private static final String dbCreateLogCount = "" + + "create table " + dbTableLogCount + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "type integer not null default 4, " + + "count integer not null default 0 " + + "); "; + private static final String dbCreateLogImages = "" + + "create table " + dbTableLogImages + " (" + + "_id integer primary key autoincrement, " + + "log_id integer not null, " + + "title text not null, " + + "url text not null" + + "); "; + private static final String dbCreateLogsOffline = "" + + "create table " + dbTableLogsOffline + " (" + + "_id integer primary key autoincrement, " + + "geocode text not null, " + + "updated long not null, " // date of save + + "type integer not null default 4, " + + "log text, " + + "date long " + + "); "; + private static final String dbCreateTrackables = "" + + "create table " + dbTableTrackables + " (" + + "_id integer primary key autoincrement, " + + "updated long not null, " // date of save + + "tbcode text not null, " + + "guid text, " + + "title text, " + + "owner text, " + + "released long, " + + "goal text, " + + "description text, " + + "geocode text " + + "); "; + + private static final String dbCreateSearchDestinationHistory = "" + + "create table " + dbTableSearchDestionationHistory + " (" + + "_id integer primary key autoincrement, " + + "date long not null, " + + "latitude double, " + + "longitude double " + + "); "; + + public boolean initialized = false; + + public cgData(Context contextIn) { + context = contextIn; + } + + public synchronized void init() { + if (databaseRW == null || databaseRW.isOpen() == false) { + try { + if (dbHelper == null) { + dbHelper = new cgDbHelper(context); + } + databaseRW = dbHelper.getWritableDatabase(); + + if (databaseRW != null && databaseRW.isOpen()) { + Log.i(cgSettings.tag, "Connection to RW database established."); + } else { + Log.e(cgSettings.tag, "Failed to open connection to RW database."); + } + + if (databaseRW != null && databaseRW.inTransaction()) { + databaseRW.endTransaction(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.openDb.RW: " + e.toString()); + } + } + + if (databaseRO == null || !databaseRO.isOpen()) { + try { + if (dbHelper == null) { + dbHelper = new cgDbHelper(context); + } + databaseRO = dbHelper.getReadableDatabase(); + + if (databaseRO.needUpgrade(dbVersion)) { + databaseRO = null; + } + + if (databaseRO != null && databaseRO.isOpen()) { + Log.i(cgSettings.tag, "Connection to RO database established."); + } else { + Log.e(cgSettings.tag, "Failed to open connection to RO database."); + } + + if (databaseRO != null && databaseRO.inTransaction()) { + databaseRO.endTransaction(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.openDb.RO: " + e.toString()); + } + } + + initialized = true; + } + + public void closeDb() { + if (databaseRO != null) { + path = databaseRO.getPath(); + + if (databaseRO.inTransaction()) { + databaseRO.endTransaction(); + } + + databaseRO.close(); + databaseRO = null; + SQLiteDatabase.releaseMemory(); + + Log.d(cgSettings.tag, "Closing RO database"); + } + + if (databaseRW != null) { + path = databaseRW.getPath(); + + if (databaseRW.inTransaction()) { + databaseRW.endTransaction(); + } + + databaseRW.close(); + databaseRW = null; + SQLiteDatabase.releaseMemory(); + + Log.d(cgSettings.tag, "Closing RW database"); + } + + if (dbHelper != null) { + dbHelper.close(); + dbHelper = null; + } + } + + public String backupDatabase() { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) == false) { + Log.w(cgSettings.tag, "Database wasn't backed up: no external memory"); + + return null; + } + + closeDb(); + + boolean backupDone = false; + final String directoryImg = cgSettings.cache; + final String directoryTarget = Environment.getExternalStorageDirectory() + "/" + directoryImg + "/"; + final String fileTarget = directoryTarget + "cgeo.sqlite"; + final String fileSource = path; + + File directoryTargetFile = new File(directoryTarget); + if (directoryTargetFile.exists() == false) { + directoryTargetFile.mkdir(); + } + + InputStream input = null; + OutputStream output = null; + try { + input = new FileInputStream(fileSource); + output = new FileOutputStream(fileTarget); + } catch (FileNotFoundException e) { + Log.e(cgSettings.tag, "Database wasn't backed up, could not open file: " + e.toString()); + } + + byte[] buffer = new byte[1024]; + int length; + if ((input != null) && (output != null)) { + try { + while ((length = input.read(buffer)) > 0) { + output.write(buffer, 0, length); + } + output.flush(); + backupDone = true; + } catch (IOException e) { + Log.e(cgSettings.tag, "Database wasn't backed up, could not read/write file: " + e.toString()); + } + } + + try { + if (output != null) { + output.close(); + } + if (input != null) { + input.close(); + } + } catch (IOException e) { + Log.e(cgSettings.tag, "Database wasn't backed up, could not close file: " + e.toString()); + } + + if (backupDone) { + Log.i(cgSettings.tag, "Database was copied to " + fileTarget); + } + + init(); + + return backupDone ? fileTarget : null; + } + + public static File isRestoreFile() { + final String directoryImg = cgSettings.cache; + final String fileSource = Environment.getExternalStorageDirectory() + "/" + directoryImg + "/cgeo.sqlite"; + + File fileSourceFile = new File(fileSource); + if (fileSourceFile.exists()) { + return fileSourceFile; + } else { + return null; + } + } + + public boolean restoreDatabase() { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED) == false) { + Log.w(cgSettings.tag, "Database wasn't restored: no external memory"); + + return false; + } + + closeDb(); + + boolean restoreDone = false; + + final String directoryImg = cgSettings.cache; + final String fileSource = Environment.getExternalStorageDirectory() + "/" + directoryImg + "/cgeo.sqlite"; + final String fileTarget = path; + + File fileSourceFile = new File(fileSource); + if (fileSourceFile.exists() == false) { + Log.w(cgSettings.tag, "Database backup was not found"); + + init(); + + return restoreDone; + } + + InputStream input = null; + OutputStream output = null; + try { + input = new FileInputStream(fileSource); + output = new FileOutputStream(fileTarget); + } catch (FileNotFoundException e) { + Log.e(cgSettings.tag, "Database wasn't restored, could not open file: " + e.toString()); + } + + byte[] buffer = new byte[1024]; + int length; + if ((input != null) && (output != null)) { + try { + while ((length = input.read(buffer)) > 0) { + output.write(buffer, 0, length); + } + output.flush(); + restoreDone = true; + } catch (IOException e) { + Log.e(cgSettings.tag, "Database wasn't restored, could not read/write file: " + e.toString()); + } + } + + try { + if (output != null) { + output.close(); + } + if (input != null) { + input.close(); + } + } catch (IOException e) { + Log.e(cgSettings.tag, "Database wasn't restored, could not close file: " + e.toString()); + } + + if (restoreDone) { + Log.i(cgSettings.tag, "Database was restored"); + } + + init(); + + return restoreDone; + } + + private static class cgDbHelper extends SQLiteOpenHelper { + + cgDbHelper(Context context) { + super(context, dbName, null, dbVersion); + } + + @Override + public void onCreate(SQLiteDatabase db) { + db.execSQL(dbCreateCaches); + db.execSQL(dbCreateLists); + db.execSQL(dbCreateAttributes); + db.execSQL(dbCreateWaypoints); + db.execSQL(dbCreateSpoilers); + db.execSQL(dbCreateLogs); + db.execSQL(dbCreateLogCount); + db.execSQL(dbCreateLogImages); + db.execSQL(dbCreateLogsOffline); + db.execSQL(dbCreateTrackables); + db.execSQL(dbCreateSearchDestinationHistory); + + db.execSQL("create index if not exists in_caches_geo on " + dbTableCaches + " (geocode)"); + db.execSQL("create index if not exists in_caches_guid on " + dbTableCaches + " (guid)"); + db.execSQL("create index if not exists in_caches_reason on " + dbTableCaches + " (reason)"); + db.execSQL("create index if not exists in_caches_detailed on " + dbTableCaches + " (detailed)"); + db.execSQL("create index if not exists in_caches_type on " + dbTableCaches + " (type)"); + db.execSQL("create index if not exists in_caches_visit_detail on " + dbTableCaches + " (visiteddate, detailedupdate)"); + db.execSQL("create index if not exists in_attr_geo on " + dbTableAttributes + " (geocode)"); + db.execSQL("create index if not exists in_wpts_geo on " + dbTableWaypoints + " (geocode)"); + db.execSQL("create index if not exists in_wpts_geo_type on " + dbTableWaypoints + " (geocode, type)"); + db.execSQL("create index if not exists in_spoil_geo on " + dbTableSpoilers + " (geocode)"); + db.execSQL("create index if not exists in_logs_geo on " + dbTableLogs + " (geocode)"); + db.execSQL("create index if not exists in_logcount_geo on " + dbTableLogCount + " (geocode)"); + db.execSQL("create index if not exists in_logsoff_geo on " + dbTableLogsOffline + " (geocode)"); + db.execSQL("create index if not exists in_trck_geo on " + dbTableTrackables + " (geocode)"); + } + + @Override + public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { + Log.i(cgSettings.tag, "Upgrade database from ver. " + oldVersion + " to ver. " + newVersion + ": start"); + + try { + if (db.isReadOnly()) { + return; + } + + db.beginTransaction(); + + if (oldVersion <= 0) { // new table + dropDatabase(db); + onCreate(db); + + Log.i(cgSettings.tag, "Database structure created."); + } + + if (oldVersion > 0) { + db.execSQL("delete from " + dbTableCaches + " where reason = 0"); + + if (oldVersion < 34) { // upgrade to 34 + try { + db.execSQL("create index if not exists in_a on " + dbTableCaches + " (geocode)"); + db.execSQL("create index if not exists in_b on " + dbTableCaches + " (guid)"); + db.execSQL("create index if not exists in_c on " + dbTableCaches + " (reason)"); + db.execSQL("create index if not exists in_d on " + dbTableCaches + " (detailed)"); + db.execSQL("create index if not exists in_e on " + dbTableCaches + " (type)"); + db.execSQL("create index if not exists in_a on " + dbTableAttributes + " (geocode)"); + db.execSQL("create index if not exists in_a on " + dbTableWaypoints + " (geocode)"); + db.execSQL("create index if not exists in_b on " + dbTableWaypoints + " (geocode, type)"); + db.execSQL("create index if not exists in_a on " + dbTableSpoilers + " (geocode)"); + db.execSQL("create index if not exists in_a on " + dbTableLogs + " (geocode)"); + db.execSQL("create index if not exists in_a on " + dbTableTrackables + " (geocode)"); + + Log.i(cgSettings.tag, "Indexes added."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 34: " + e.toString()); + } + } + + if (oldVersion < 37) { // upgrade to 37 + try { + db.execSQL("alter table " + dbTableCaches + " add column direction text"); + db.execSQL("alter table " + dbTableCaches + " add column distance double"); + + Log.i(cgSettings.tag, "Columns direction and distance added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 37: " + e.toString()); + } + } + + if (oldVersion < 38) { // upgrade to 38 + try { + db.execSQL("drop table " + dbTableLogs); + db.execSQL(dbCreateLogs); + + Log.i(cgSettings.tag, "Changed type column in " + dbTableLogs + " to integer."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 38: " + e.toString()); + } + } + + if (oldVersion < 39) { // upgrade to 39 + try { + db.execSQL(dbCreateLists); + + Log.i(cgSettings.tag, "Created lists table."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 39: " + e.toString()); + } + } + + if (oldVersion < 40) { // upgrade to 40 + try { + db.execSQL("drop table " + dbTableTrackables); + db.execSQL(dbCreateTrackables); + + Log.i(cgSettings.tag, "Changed type of geocode column in trackables table."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 40: " + e.toString()); + } + } + + if (oldVersion < 41) { // upgrade to 41 + try { + db.execSQL("alter table " + dbTableCaches + " add column rating float"); + db.execSQL("alter table " + dbTableCaches + " add column votes integer"); + db.execSQL("alter table " + dbTableCaches + " add column vote integer"); + + Log.i(cgSettings.tag, "Added columns for GCvote."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 41: " + e.toString()); + } + } + + if (oldVersion < 42) { // upgrade to 42 + try { + db.execSQL(dbCreateLogsOffline); + + Log.i(cgSettings.tag, "Added table for offline logs"); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 42: " + e.toString()); + } + } + + if (oldVersion < 43) { // upgrade to 43 + try { + final String dbTableCachesTemp = dbTableCaches + "_temp"; + final String dbCreateCachesTemp = "" + + "create temporary table " + dbTableCachesTemp + " (" + + "_id integer primary key autoincrement, " + + "updated long not null, " + + "detailed integer not null default 0, " + + "detailedupdate long, " + + "geocode text unique not null, " + + "reason integer not null default 0, " // cached, favourite... + + "cacheid text, " + + "guid text, " + + "type text, " + + "name text, " + + "owner text, " + + "hidden long, " + + "hint text, " + + "size text, " + + "difficulty float, " + + "terrain float, " + + "latlon text, " + + "latitude_string text, " + + "longitude_string text, " + + "location text, " + + "distance double, " + + "latitude double, " + + "longitude double, " + + "shortdesc text, " + + "description text, " + + "rating float, " + + "votes integer, " + + "vote integer, " + + "disabled integer not null default 0, " + + "archived integer not null default 0, " + + "members integer not null default 0, " + + "found integer not null default 0, " + + "favourite integer not null default 0, " + + "inventorycoins integer default 0, " + + "inventorytags integer default 0, " + + "inventoryunknown integer default 0 " + + "); "; + final String dbCreateCachesNew = "" + + "create table " + dbTableCaches + " (" + + "_id integer primary key autoincrement, " + + "updated long not null, " + + "detailed integer not null default 0, " + + "detailedupdate long, " + + "geocode text unique not null, " + + "reason integer not null default 0, " // cached, favourite... + + "cacheid text, " + + "guid text, " + + "type text, " + + "name text, " + + "owner text, " + + "hidden long, " + + "hint text, " + + "size text, " + + "difficulty float, " + + "terrain float, " + + "latlon text, " + + "latitude_string text, " + + "longitude_string text, " + + "location text, " + + "direction double, " + + "distance double, " + + "latitude double, " + + "longitude double, " + + "shortdesc text, " + + "description text, " + + "rating float, " + + "votes integer, " + + "vote integer, " + + "disabled integer not null default 0, " + + "archived integer not null default 0, " + + "members integer not null default 0, " + + "found integer not null default 0, " + + "favourite integer not null default 0, " + + "inventorycoins integer default 0, " + + "inventorytags integer default 0, " + + "inventoryunknown integer default 0 " + + "); "; + + db.beginTransaction(); + db.execSQL(dbCreateCachesTemp); + db.execSQL("insert into " + dbTableCachesTemp + " select _id, updated, detailed, detailedupdate, geocode, reason, cacheid, guid, type, name, owner, hidden, hint, size, difficulty, terrain, latlon, latitude_string, longitude_string, location, distance, latitude, longitude, shortdesc, description, rating, votes, vote, disabled, archived, members, found, favourite, inventorycoins, inventorytags, inventoryunknown from " + dbTableCaches); + db.execSQL("drop table " + dbTableCaches); + db.execSQL(dbCreateCachesNew); + db.execSQL("insert into " + dbTableCaches + " select _id, updated, detailed, detailedupdate, geocode, reason, cacheid, guid, type, name, owner, hidden, hint, size, difficulty, terrain, latlon, latitude_string, longitude_string, location, null, distance, latitude, longitude, shortdesc, description, rating, votes, vote, disabled, archived, members, found, favourite, inventorycoins, inventorytags, inventoryunknown from " + dbTableCachesTemp); + db.execSQL("drop table " + dbTableCachesTemp); + db.setTransactionSuccessful(); + + Log.i(cgSettings.tag, "Changed direction column"); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 43: " + e.toString()); + } finally { + db.endTransaction(); + } + } + + if (oldVersion < 44) { // upgrade to 44 + try { + db.execSQL("alter table " + dbTableCaches + " add column favourite_cnt integer"); + + Log.i(cgSettings.tag, "Column favourite_cnt added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 44: " + e.toString()); + } + } + + if (oldVersion < 45) { // upgrade to 45 + try { + db.execSQL("alter table " + dbTableCaches + " add column owner_real text"); + + Log.i(cgSettings.tag, "Column owner_real added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 45: " + e.toString()); + } + } + + if (oldVersion < 46) { // upgrade to 46 + try { + db.execSQL("alter table " + dbTableCaches + " add column visiteddate long"); + db.execSQL("create index if not exists in_f on " + dbTableCaches + " (visiteddate, detailedupdate)"); + + Log.i(cgSettings.tag, "Added column for date of visit."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 46: " + e.toString()); + } + } + if (oldVersion < 47) { // upgrade to 47 + try { + db.execSQL("alter table " + dbTableCaches + " add column own integer not null default 0"); + + Log.i(cgSettings.tag, "Added column own."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 47: " + e.toString()); + } + } + + if (oldVersion < 48) { // upgrade to 48 + try { + db.execSQL("alter table " + dbTableCaches + " add column elevation double"); + + Log.i(cgSettings.tag, "Column elevation added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 48: " + e.toString()); + } + } + + if (oldVersion < 49) { // upgrade to 49 + try { + db.execSQL(dbCreateLogCount); + + Log.i(cgSettings.tag, "Created table " + dbTableLogCount + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 49: " + e.toString()); + } + } + + if (oldVersion < 50) { // upgrade to 50 + try { + db.execSQL("alter table " + dbTableCaches + " add column myvote float"); + + Log.i(cgSettings.tag, "Added float column for votes to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 50: " + e.toString()); + } + } + + if (oldVersion < 51) { // upgrade to 51 + try { + db.execSQL("alter table " + dbTableCaches + " add column reliable_latlon integer"); + + Log.i(cgSettings.tag, "Column reliable_latlon added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 51: " + e.toString()); + } + } + + if (oldVersion < 52) { // upgrade to 52 + try { + db.execSQL(dbCreateSearchDestinationHistory); + + Log.i(cgSettings.tag, "Added table " + dbTableSearchDestionationHistory + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 52", e); + } + } + + if (oldVersion < 53) { // upgrade to 53 + try { + db.execSQL("alter table " + dbTableCaches + " add column onWatchlist integer"); + + Log.i(cgSettings.tag, "Column onWatchlist added to " + dbTableCaches + "."); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 53", e); + } + } + + if (oldVersion < 54) { // update to 54 + try { + db.execSQL(dbCreateLogImages); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 54: " + e.toString()); + + } + } + + if (oldVersion < 55) { // update to 55 + try { + db.execSQL("alter table " + dbTableCaches + " add column personal_note text"); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 55: " + e.toString()); + + } + } + + // make all internal attribute names lowercase + // @see issue #299 + if (oldVersion < 56) { // update to 56 + try { + db.execSQL("update " + dbTableAttributes + " set attribute = " + + "lower(attribute) where attribute like \"%_yes\" " + + "or attribute like \"%_no\""); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 56: " + e.toString()); + } + } + + // Create missing indices. See issue #435 + if (oldVersion < 57) { // update to 57 + try { + db.execSQL("drop index in_a"); + db.execSQL("drop index in_b"); + db.execSQL("drop index in_c"); + db.execSQL("drop index in_d"); + db.execSQL("drop index in_e"); + db.execSQL("drop index in_f"); + db.execSQL("create index if not exists in_caches_geo on " + dbTableCaches + " (geocode)"); + db.execSQL("create index if not exists in_caches_guid on " + dbTableCaches + " (guid)"); + db.execSQL("create index if not exists in_caches_reason on " + dbTableCaches + " (reason)"); + db.execSQL("create index if not exists in_caches_detailed on " + dbTableCaches + " (detailed)"); + db.execSQL("create index if not exists in_caches_type on " + dbTableCaches + " (type)"); + db.execSQL("create index if not exists in_caches_visit_detail on " + dbTableCaches + " (visiteddate, detailedupdate)"); + db.execSQL("create index if not exists in_attr_geo on " + dbTableAttributes + " (geocode)"); + db.execSQL("create index if not exists in_wpts_geo on " + dbTableWaypoints + " (geocode)"); + db.execSQL("create index if not exists in_wpts_geo_type on " + dbTableWaypoints + " (geocode, type)"); + db.execSQL("create index if not exists in_spoil_geo on " + dbTableSpoilers + " (geocode)"); + db.execSQL("create index if not exists in_logs_geo on " + dbTableLogs + " (geocode)"); + db.execSQL("create index if not exists in_logcount_geo on " + dbTableLogCount + " (geocode)"); + db.execSQL("create index if not exists in_logsoff_geo on " + dbTableLogsOffline + " (geocode)"); + db.execSQL("create index if not exists in_trck_geo on " + dbTableTrackables + " (geocode)"); + } catch (Exception e) { + Log.e(cgSettings.tag, "Failed to upgrade to ver. 57: " + e.toString()); + } + } + } + + db.setTransactionSuccessful(); + } finally { + db.endTransaction(); + } + + Log.i(cgSettings.tag, "Upgrade database from ver. " + oldVersion + " to ver. " + newVersion + ": completed"); + } + } + + private static void dropDatabase(SQLiteDatabase db) { + db.execSQL("drop table if exists " + dbTableCaches); + db.execSQL("drop table if exists " + dbTableAttributes); + db.execSQL("drop table if exists " + dbTableWaypoints); + db.execSQL("drop table if exists " + dbTableSpoilers); + db.execSQL("drop table if exists " + dbTableLogs); + db.execSQL("drop table if exists " + dbTableLogCount); + db.execSQL("drop table if exists " + dbTableLogsOffline); + db.execSQL("drop table if exists " + dbTableTrackables); + } + + public String[] allDetailedThere() { + init(); + + Cursor cursor = null; + List<String> list = new ArrayList<String>(); + + try { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + "(detailed = 1 and detailedupdate > " + (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000)) + ") or reason > 0", + null, + null, + null, + "detailedupdate desc", + "100"); + + if (cursor != null) { + int index = 0; + + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + index = cursor.getColumnIndex("geocode"); + + do { + list.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } else { + if (cursor != null) { + cursor.close(); + } + + return null; + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.allDetailedThere: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + return list.toArray(new String[list.size()]); + } + + public boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { + init(); + + Cursor cursor = null; + + int cnt = 0; + long dataUpdated = 0; + long dataDetailedUpdate = 0; + int dataDetailed = 0; + + try { + if (StringUtils.isNotBlank(geocode)) { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "detailed", "detailedupdate", "updated" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "1"); + } else if (StringUtils.isNotBlank(guid)) { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "detailed", "detailedupdate", "updated" }, + "guid = \"" + guid + "\"", + null, + null, + null, + null, + "1"); + } else { + return false; + } + + if (cursor != null) { + int index = 0; + cnt = cursor.getCount(); + + if (cnt > 0) { + cursor.moveToFirst(); + + index = cursor.getColumnIndex("updated"); + dataUpdated = (long) cursor.getLong(index); + index = cursor.getColumnIndex("detailedupdate"); + dataDetailedUpdate = (long) cursor.getLong(index); + index = cursor.getColumnIndex("detailed"); + dataDetailed = (int) cursor.getInt(index); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.isThere: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + if (cnt > 0) { + if (detailed && dataDetailed == 0) { + // we want details, but these are not stored + return false; + } + + if (checkTime && detailed && dataDetailedUpdate < (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000))) { + // we want to check time for detailed cache, but data are older than 3 hours + return false; + } + + if (checkTime && detailed == false && dataUpdated < (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000))) { + // we want to check time for short cache, but data are older than 3 hours + return false; + } + + // we have some cache + return true; + } + + // we have no such cache stored in cache + return false; + } + + public boolean isOffline(String geocode, String guid) { + init(); + + Cursor cursor = null; + long reason = 0; + + try { + if (StringUtils.isNotBlank(geocode)) { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "reason" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "1"); + } else if (StringUtils.isNotBlank(guid)) { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "reason" }, + "guid = \"" + guid + "\"", + null, + null, + null, + null, + "1"); + } else { + return false; + } + + if (cursor != null) { + final int cnt = cursor.getCount(); + int index = 0; + + if (cnt > 0) { + cursor.moveToFirst(); + + index = cursor.getColumnIndex("reason"); + reason = (long) cursor.getLong(index); + } + + cursor.close(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.isOffline: " + e.toString()); + } + + if (reason >= 1) { + return true; + } else { + return false; + } + } + + public String getGeocodeForGuid(String guid) { + if (StringUtils.isBlank(guid)) { + return null; + } + + init(); + + Cursor cursor = null; + String geocode = null; + + try { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + "guid = \"" + guid + "\"", + null, + null, + null, + null, + "1"); + + if (cursor != null) { + int index = 0; + + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + + index = cursor.getColumnIndex("geocode"); + geocode = (String) cursor.getString(index); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getGeocodeForGuid: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + return geocode; + } + + public String getCacheidForGeocode(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + Cursor cursor = null; + String cacheid = null; + + try { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "cacheid" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "1"); + + if (cursor != null) { + int index = 0; + + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + + index = cursor.getColumnIndex("cacheid"); + cacheid = (String) cursor.getString(index); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getCacheidForGeocode: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + return cacheid; + } + + public boolean saveCache(cgCache cache) { + //LeeB - writing to the DB is slow + if (cache == null) { + return false; + } + + ContentValues values = new ContentValues(); + + if (cache.updated == null) { + values.put("updated", System.currentTimeMillis()); + } else { + values.put("updated", cache.updated); + } + values.put("reason", cache.reason); + values.put("detailed", cache.detailed ? 1 : 0); + values.put("detailedupdate", cache.detailedUpdate); + values.put("visiteddate", cache.visitedDate); + values.put("geocode", cache.geocode); + values.put("cacheid", cache.cacheId); + values.put("guid", cache.guid); + values.put("type", cache.type); + values.put("name", cache.name); + values.put("own", cache.own ? 1 : 0); + values.put("owner", cache.owner); + values.put("owner_real", cache.ownerReal); + if (cache.hidden == null) { + values.put("hidden", 0); + } else { + values.put("hidden", cache.hidden.getTime()); + } + values.put("hint", cache.hint); + values.put("size", cache.size == null ? "" : cache.size.id); + values.put("difficulty", cache.difficulty); + values.put("terrain", cache.terrain); + values.put("latlon", cache.latlon); + values.put("latitude_string", cache.latitudeString); + values.put("longitude_string", cache.longitudeString); + values.put("location", cache.location); + values.put("distance", cache.distance); + values.put("direction", cache.direction); + putCoords(values, cache.coords); + values.put("reliable_latlon", cache.reliableLatLon ? 1 : 0); + values.put("elevation", cache.elevation); + values.put("shortdesc", cache.shortdesc); + values.put("personal_note", cache.personalNote); + values.put("description", cache.description); + values.put("favourite_cnt", cache.favouriteCnt); + values.put("rating", cache.rating); + values.put("votes", cache.votes); + values.put("myvote", cache.myVote); + values.put("disabled", cache.disabled ? 1 : 0); + values.put("archived", cache.archived ? 1 : 0); + values.put("members", cache.members ? 1 : 0); + values.put("found", cache.found ? 1 : 0); + values.put("favourite", cache.favourite ? 1 : 0); + values.put("inventoryunknown", cache.inventoryItems); + values.put("onWatchlist", cache.onWatchlist ? 1 : 0); + + boolean statusOk = true; + + if (cache.attributes != null) { + if (!saveAttributes(cache.geocode, cache.attributes)) { + statusOk = false; + } + } + + if (cache.waypoints != null) { + if (!saveWaypoints(cache.geocode, cache.waypoints, true)) { + statusOk = false; + } + } + + if (cache.spoilers != null) { + if (!saveSpoilers(cache.geocode, cache.spoilers)) { + statusOk = false; + } + } + + if (cache.logs != null) { + if (!saveLogs(cache.geocode, cache.logs)) { + statusOk = false; + } + } + + if (cache.logCounts != null && cache.logCounts.isEmpty() == false) { + if (!saveLogCount(cache.geocode, cache.logCounts)) { + statusOk = false; + } + } + + if (cache.inventory != null) { + if (!saveInventory(cache.geocode, cache.inventory)) { + statusOk = false; + } + } + + if (statusOk == false) { + cache.detailed = false; + cache.detailedUpdate = 0L; + } + + init(); + + //try to update record else insert fresh.. + try { + int rows = databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { cache.geocode }); + if (rows > 0) { + values = null; + return true; + } + } catch (Exception e) { + // nothing + } + + try { + long id = databaseRW.insert(dbTableCaches, null, values); + if (id > 0) { + values = null; + return true; + } + } catch (Exception e) { + // nothing + } + + values = null; + + return false; + } + + public boolean saveAttributes(String geocode, List<String> attributes) { + init(); + + if (StringUtils.isBlank(geocode) || attributes == null) { + return false; + } + + databaseRW.beginTransaction(); + try { + databaseRW.delete(dbTableAttributes, "geocode = ?", new String[] { geocode }); + + if (!attributes.isEmpty()) { + + InsertHelper helper = new InsertHelper(databaseRW, dbTableAttributes); + long timeStamp = System.currentTimeMillis(); + + for (String attribute : attributes) { + helper.prepareForInsert(); + + helper.bind(ATTRIBUTES_GEOCODE, geocode); + helper.bind(ATTRIBUTES_UPDATED, timeStamp); + helper.bind(ATTRIBUTES_ATTRIBUTE, attribute); + + helper.execute(); + } + helper.close(); + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return true; + } + + /** + * Persists the given <code>destination</code> into the database. + * + * @param destinations + * @return <code>true</code> if the given destination was successfully + * persisted <code>false</code> otherwise. + */ + public boolean saveSearchedDestination(cgDestination destination) { + boolean success = true; + + if (destination == null) { + success = false; + } else { + init(); + + databaseRW.beginTransaction(); + + try { + ContentValues values = new ContentValues(); + values.put("date", destination.getDate()); + putCoords(values, destination.getCoords()); + + long id = databaseRW.insert(dbTableSearchDestionationHistory, null, values); + destination.setId(id); + databaseRW.setTransactionSuccessful(); + } catch (Exception e) { + success = false; + Log.e(cgSettings.tag, "Updating searchedDestinations db failed", e); + } finally { + databaseRW.endTransaction(); + } + } + + return success; + } + + public boolean saveWaypoints(String geocode, List<cgWaypoint> waypoints, boolean drop) { + init(); + + if (StringUtils.isBlank(geocode) || waypoints == null) { + return false; + } + + boolean ok = false; + databaseRW.beginTransaction(); + try { + if (drop) { + databaseRW.delete(dbTableWaypoints, "geocode = ? and type <> ?", new String[] { geocode, "own" }); + } + + if (!waypoints.isEmpty()) { + ContentValues values = new ContentValues(); + long timeStamp = System.currentTimeMillis(); + for (cgWaypoint oneWaypoint : waypoints) { + if (oneWaypoint.isUserDefined()) { + continue; + } + + values.clear(); + values.put("geocode", geocode); + values.put("updated", timeStamp); + values.put("type", oneWaypoint.type); + values.put("prefix", oneWaypoint.prefix); + values.put("lookup", oneWaypoint.lookup); + values.put("name", oneWaypoint.name); + values.put("latlon", oneWaypoint.latlon); + values.put("latitude_string", oneWaypoint.latitudeString); + values.put("longitude_string", oneWaypoint.longitudeString); + putCoords(values, oneWaypoint.coords); + values.put("note", oneWaypoint.note); + + databaseRW.insert(dbTableWaypoints, null, values); + } + } + + databaseRW.setTransactionSuccessful(); + ok = true; + } finally { + databaseRW.endTransaction(); + } + + return ok; + } + + /** + * Save coordinates into a ContentValues + * + * @param values + * a ContentValues to save coordinates in + * @param oneWaypoint + * coordinates to save, or null to save empty coordinates + */ + private static void putCoords(final ContentValues values, final Geopoint coords) { + values.put("latitude", coords == null ? null : coords.getLatitude()); + values.put("longitude", coords == null ? null : coords.getLongitude()); + } + + /** + * Retrieve coordinates from a Cursor + * + * @param cursor + * a Cursor representing a row in the database + * @param indexLat + * index of the latitude column + * @param indexLon + * index of the longitude column + * @return the coordinates, or null if latitude or longitude is null or the coordinates are invalid + */ + private static Geopoint getCoords(final Cursor cursor, final int indexLat, final int indexLon) { + if (cursor.isNull(indexLat) || cursor.isNull(indexLon)) { + return null; + } + + try { + return new Geopoint(cursor.getDouble(indexLat), cursor.getDouble(indexLon)); + } catch (MalformedCoordinateException e) { + // TODO: check whether the exception should be returned to the caller instead, + // as it might want to remove an invalid geopoint from the database. + Log.e(cgSettings.tag, "cannot parse geopoint from database: " + e.getMessage()); + return null; + } + } + + /** + * Retrieve coordinates from a Cursor + * + * @param cursor + * a Cursor representing a row in the database + * @return the coordinates, or null if latitude or longitude is null or the coordinates are invalid + */ + private static Geopoint getCoords(final Cursor cursor) { + final int indexLat = cursor.getColumnIndex("latitude"); + final int indexLon = cursor.getColumnIndex("longitude"); + return getCoords(cursor, indexLat, indexLon); + } + + public boolean saveOwnWaypoint(int id, String geocode, cgWaypoint waypoint) { + init(); + + if ((StringUtils.isBlank(geocode) && id <= 0) || waypoint == null) { + return false; + } + + boolean ok = false; + databaseRW.beginTransaction(); + try { + ContentValues values = new ContentValues(); + values.put("geocode", geocode); + values.put("updated", System.currentTimeMillis()); + values.put("type", waypoint.type); + values.put("prefix", waypoint.prefix); + values.put("lookup", waypoint.lookup); + values.put("name", waypoint.name); + values.put("latlon", waypoint.latlon); + values.put("latitude_string", waypoint.latitudeString); + values.put("longitude_string", waypoint.longitudeString); + putCoords(values, waypoint.coords); + values.put("note", waypoint.note); + + if (id <= 0) { + databaseRW.insert(dbTableWaypoints, null, values); + ok = true; + } else { + final int rows = databaseRW.update(dbTableWaypoints, values, "_id = " + id, null); + if (rows > 0) { + ok = true; + } else { + ok = false; + } + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return ok; + } + + public boolean deleteWaypoint(int id) { + init(); + + if (id == 0) { + return false; + } + + int deleted = databaseRW.delete(dbTableWaypoints, "_id = " + id, null); + + if (deleted > 0) { + return true; + } + + return false; + } + + public boolean saveSpoilers(String geocode, List<cgImage> spoilers) { + init(); + + if (StringUtils.isBlank(geocode) || spoilers == null) { + return false; + } + + databaseRW.beginTransaction(); + try { + databaseRW.delete(dbTableSpoilers, "geocode = ?", new String[] { geocode }); + + if (!spoilers.isEmpty()) { + ContentValues values = new ContentValues(); + long timeStamp = System.currentTimeMillis(); + for (cgImage oneSpoiler : spoilers) { + values.clear(); + values.put("geocode", geocode); + values.put("updated", timeStamp); + values.put("url", oneSpoiler.url); + values.put("title", oneSpoiler.title); + values.put("description", oneSpoiler.description); + + databaseRW.insert(dbTableSpoilers, null, values); + } + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return true; + } + + public boolean saveLogs(String geocode, List<cgLog> logs) { + return saveLogs(geocode, logs, true); + } + + public boolean saveLogs(String geocode, List<cgLog> logs, boolean drop) { + init(); + + if (StringUtils.isBlank(geocode) || logs == null) { + return false; + } + + databaseRW.beginTransaction(); + try { + if (drop) { + // TODO delete logimages referring these logs + databaseRW.delete(dbTableLogs, "geocode = ?", new String[] { geocode }); + } + + if (!logs.isEmpty()) { + InsertHelper helper = new InsertHelper(databaseRW, dbTableLogs); + long timeStamp = System.currentTimeMillis(); + for (cgLog log : logs) { + helper.prepareForInsert(); + + helper.bind(LOGS_GEOCODE, geocode); + helper.bind(LOGS_UPDATED, timeStamp); + helper.bind(LOGS_TYPE, log.type); + helper.bind(LOGS_AUTHOR, log.author); + helper.bind(LOGS_LOG, log.log); + helper.bind(LOGS_DATE, log.date); + helper.bind(LOGS_FOUND, log.found); + + long log_id = helper.execute(); + + if (CollectionUtils.isNotEmpty(log.logImages)) { + ContentValues values = new ContentValues(); + for (cgImage img : log.logImages) { + values.clear(); + values.put("log_id", log_id); + values.put("title", img.title); + values.put("url", img.url); + databaseRW.insert(dbTableLogImages, null, values); + } + } + } + helper.close(); + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return true; + } + + public boolean saveLogCount(String geocode, Map<Integer, Integer> logCounts) { + return saveLogCount(geocode, logCounts, true); + } + + public boolean saveLogCount(String geocode, Map<Integer, Integer> logCounts, boolean drop) { + init(); + + if (StringUtils.isBlank(geocode) || CollectionUtils.isEmpty(logCounts)) { + return false; + } + + databaseRW.beginTransaction(); + try { + if (drop) { + databaseRW.delete(dbTableLogCount, "geocode = ?", new String[] { geocode }); + } + + ContentValues values = new ContentValues(); + + Set<Entry<Integer, Integer>> logCountsItems = logCounts.entrySet(); + long timeStamp = System.currentTimeMillis(); + for (Entry<Integer, Integer> pair : logCountsItems) { + values.clear(); + values.put("geocode", geocode); + values.put("updated", timeStamp); + values.put("type", pair.getKey().intValue()); + values.put("count", pair.getValue().intValue()); + + databaseRW.insert(dbTableLogCount, null, values); + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return true; + } + + public boolean saveInventory(String geocode, List<cgTrackable> trackables) { + init(); + + if (trackables == null) { + return false; + } + + databaseRW.beginTransaction(); + try { + if (geocode != null) { + databaseRW.delete(dbTableTrackables, "geocode = ?", new String[] { geocode }); + } + + if (!trackables.isEmpty()) { + ContentValues values = new ContentValues(); + long timeStamp = System.currentTimeMillis(); + for (cgTrackable oneTrackable : trackables) { + values.clear(); + if (geocode != null) { + values.put("geocode", geocode); + } + values.put("updated", timeStamp); + values.put("tbcode", oneTrackable.geocode); + values.put("guid", oneTrackable.guid); + values.put("title", oneTrackable.name); + values.put("owner", oneTrackable.owner); + if (oneTrackable.released != null) { + values.put("released", oneTrackable.released.getTime()); + } else { + values.put("released", 0L); + } + values.put("goal", oneTrackable.goal); + values.put("description", oneTrackable.details); + + databaseRW.insert(dbTableTrackables, null, values); + + saveLogs(oneTrackable.geocode, oneTrackable.logs); + } + } + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return true; + } + + public List<Object> getBounds(Object[] geocodes) { + init(); + + Cursor cursor = null; + + final List<Object> viewport = new ArrayList<Object>(); + + try { + final StringBuilder where = new StringBuilder(); + + if (geocodes != null && geocodes.length > 0) { + StringBuilder all = new StringBuilder(); + for (Object one : geocodes) { + if (all.length() > 0) { + all.append(", "); + } + all.append('"'); + all.append((String) one); + all.append('"'); + } + + if (where.length() > 0) { + where.append(" and "); + } + where.append("geocode in ("); + where.append(all); + where.append(')'); + } + + cursor = databaseRO.query( + dbTableCaches, + new String[] { "count(_id) as cnt", "min(latitude) as latMin", "max(latitude) as latMax", "min(longitude) as lonMin", "max(longitude) as lonMax" }, + where.toString(), + null, + null, + null, + null, + null); + + if (cursor != null) { + int cnt = cursor.getCount(); + + if (cnt > 0) { + cursor.moveToFirst(); + + viewport.add((Integer) cursor.getInt(cursor.getColumnIndex("cnt"))); + viewport.add((Double) cursor.getDouble(cursor.getColumnIndex("latMin"))); + viewport.add((Double) cursor.getDouble(cursor.getColumnIndex("latMax"))); + viewport.add((Double) cursor.getDouble(cursor.getColumnIndex("lonMin"))); + viewport.add((Double) cursor.getDouble(cursor.getColumnIndex("lonMax"))); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getBounds: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + return viewport; + } + + public cgCache loadCache(String geocode, String guid) { + return loadCache(geocode, guid, false, true, false, false, false, false); + } + + /** + * Loads a single Cache. + * + * @param geocode + * The Geocode GCXXXX + * @param guid + * @param loadA + * @param loadW + * @param loadS + * @param loadL + * @param loadI + * @param loadO + * @return the loaded cache + */ + + public cgCache loadCache(String geocode, String guid, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) { + Object[] geocodes = new Object[1]; + Object[] guids = new Object[1]; + + if (StringUtils.isNotBlank(geocode)) { + geocodes[0] = geocode; + } else { + geocodes = null; + } + + if (StringUtils.isNotBlank(guid)) { + guids[0] = guid; + } else { + guids = null; + } + + List<cgCache> caches = loadCaches(geocodes, null, null, null, null, null, loadA, loadW, loadS, loadL, loadI, loadO); + if (caches != null && caches.isEmpty() == false) { + return caches.get(0); + } + + return null; + } + + public List<cgCache> loadCaches(Object[] geocodes, Object[] guids) { + return loadCaches(geocodes, guids, null, null, null, null, false, true, false, false, false, false); + } + + public List<cgCache> loadCaches(Object[] geocodes, Object[] guids, boolean lite) { + if (lite) { + return loadCaches(geocodes, guids, null, null, null, null, false, true, false, false, false, false); + } else { + return loadCaches(geocodes, guids, null, null, null, null, true, true, true, true, true, true); + } + } + + public List<cgCache> loadCaches(Object[] geocodes, Object[] guids, Long centerLat, Long centerLon, Long spanLat, Long spanLon, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) { + init(); + // Using more than one of the parametersets results in overly comlex wheres + if (((geocodes != null && geocodes.length > 0) && (guids != null && guids.length > 0))) { + throw new IllegalArgumentException("Please use only one parameter"); + } + if (((geocodes != null && geocodes.length > 0) || (guids != null && guids.length > 0)) + && centerLat != null + && centerLon != null + && spanLat != null + && spanLon != null) { + throw new IllegalArgumentException("Please use only one parameter"); + } + StringBuilder where = new StringBuilder(); + Cursor cursor = null; + List<cgCache> caches = new ArrayList<cgCache>(); + + try { + if (geocodes != null && geocodes.length > 0) { + StringBuilder all = new StringBuilder(); + for (Object one : geocodes) { + if (all.length() > 0) { + all.append(", "); + } + all.append('"'); + all.append((String) one); + all.append('"'); + } + + if (where.length() > 0) { + where.append(" and "); + } + where.append("geocode in ("); + where.append(all); + where.append(')'); + } else if (guids != null && guids.length > 0) { + StringBuilder all = new StringBuilder(); + for (Object one : guids) { + if (all.length() > 0) { + all.append(", "); + } + all.append('"'); + all.append((String) one); + all.append('"'); + } + + if (where.length() > 0) { + where.append(" and "); + } + where.append("guid in ("); + where.append(all); + where.append(')'); + } else { + return caches; + } + + // viewport limitation + if (centerLat != null && centerLon != null && spanLat != null && spanLon != null) { + double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); + double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); + double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); + double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); + double llCache; + + if (latMin > latMax) { + llCache = latMax; + latMax = latMin; + latMin = llCache; + } + if (lonMin > lonMax) { + llCache = lonMax; + lonMax = lonMin; + lonMin = llCache; + } + + if (where.length() > 0) { + where.append(" and "); + } + where.append("(latitude >= "); + where.append(String.format((Locale) null, "%.6f", latMin)); + where.append(" and latitude <= "); + where.append(String.format((Locale) null, "%.6f", latMax)); + where.append(" and longitude >= "); + where.append(String.format((Locale) null, "%.6f", lonMin)); + where.append(" and longitude <= "); + where.append(String.format((Locale) null, "%.6f", lonMax)); + where.append(')'); + } + cursor = databaseRO.query( + dbTableCaches, + CACHE_COLUMNS, + where.toString(), + null, + null, + null, + null, + null); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + + do { + //Extracted Method + cgCache cache = createCacheFromDatabaseContent(cursor); + + if (loadA) { + List<String> attributes = loadAttributes(cache.geocode); + if (attributes != null && attributes.isEmpty() == false) { + if (cache.attributes == null) { + cache.attributes = new ArrayList<String>(); + } else { + cache.attributes.clear(); + } + cache.attributes.addAll(attributes); + } + } + + if (loadW) { + List<cgWaypoint> waypoints = loadWaypoints(cache.geocode); + if (waypoints != null && waypoints.isEmpty() == false) { + if (cache.waypoints == null) { + cache.waypoints = new ArrayList<cgWaypoint>(); + } else { + cache.waypoints.clear(); + } + cache.waypoints.addAll(waypoints); + } + } + + if (loadS) { + List<cgImage> spoilers = loadSpoilers(cache.geocode); + if (spoilers != null && spoilers.isEmpty() == false) { + if (cache.spoilers == null) { + cache.spoilers = new ArrayList<cgImage>(); + } else { + cache.spoilers.clear(); + } + cache.spoilers.addAll(spoilers); + } + } + + if (loadL) { + List<cgLog> logs = loadLogs(cache.geocode); + if (logs != null && logs.isEmpty() == false) { + if (cache.logs == null) { + cache.logs = new ArrayList<cgLog>(); + } else { + cache.logs.clear(); + } + cache.logs.addAll(logs); + } + Map<Integer, Integer> logCounts = loadLogCounts(cache.geocode); + if (logCounts != null && logCounts.isEmpty() == false) { + cache.logCounts.clear(); + cache.logCounts.putAll(logCounts); + } + } + + if (loadI) { + List<cgTrackable> inventory = loadInventory(cache.geocode); + if (inventory != null && inventory.isEmpty() == false) { + if (cache.inventory == null) { + cache.inventory = new ArrayList<cgTrackable>(); + } else { + cache.inventory.clear(); + } + cache.inventory.addAll(inventory); + } + } + + if (loadO) { + cache.logOffline = hasLogOffline(cache.geocode); + } + + caches.add(cache); + } while (cursor.moveToNext()); + } else { + if (cursor != null) { + cursor.close(); + } + + return null; + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.loadCaches: " + e.toString()); + } + + if (cursor != null) { + cursor.close(); + } + + return caches; + } + + /** + * maps a Cache from the cursor. Doesn't next. + * + * @param cursor + * @return + */ + + private static cgCache createCacheFromDatabaseContent(Cursor cursor) { + int index; + cgCache cache = new cgCache(); + + cache.updated = (long) cursor.getLong(cursor.getColumnIndex("updated")); + cache.reason = (int) cursor.getInt(cursor.getColumnIndex("reason")); + cache.detailed = cursor.getInt(cursor.getColumnIndex("detailed")) == 1; + cache.detailedUpdate = (Long) cursor.getLong(cursor.getColumnIndex("detailedupdate")); + cache.visitedDate = (Long) cursor.getLong(cursor.getColumnIndex("visiteddate")); + cache.geocode = (String) cursor.getString(cursor.getColumnIndex("geocode")); + cache.cacheId = (String) cursor.getString(cursor.getColumnIndex("cacheid")); + cache.guid = (String) cursor.getString(cursor.getColumnIndex("guid")); + cache.type = (String) cursor.getString(cursor.getColumnIndex("type")); + cache.name = (String) cursor.getString(cursor.getColumnIndex("name")); + cache.own = cursor.getInt(cursor.getColumnIndex("own")) == 1; + cache.owner = (String) cursor.getString(cursor.getColumnIndex("owner")); + cache.ownerReal = (String) cursor.getString(cursor.getColumnIndex("owner_real")); + cache.hidden = new Date((long) cursor.getLong(cursor.getColumnIndex("hidden"))); + cache.hint = (String) cursor.getString(cursor.getColumnIndex("hint")); + cache.size = CacheSize.FIND_BY_ID.get((String) cursor.getString(cursor.getColumnIndex("size"))); + cache.difficulty = (Float) cursor.getFloat(cursor.getColumnIndex("difficulty")); + index = cursor.getColumnIndex("direction"); + if (cursor.isNull(index)) { + cache.direction = null; + } else { + cache.direction = cursor.getFloat(index); + } + index = cursor.getColumnIndex("distance"); + if (cursor.isNull(index)) { + cache.distance = null; + } else { + cache.distance = cursor.getFloat(index); + } + cache.terrain = (Float) cursor.getFloat(cursor.getColumnIndex("terrain")); + cache.latlon = (String) cursor.getString(cursor.getColumnIndex("latlon")); + cache.latitudeString = (String) cursor.getString(cursor.getColumnIndex("latitude_string")); + cache.longitudeString = (String) cursor.getString(cursor.getColumnIndex("longitude_string")); + cache.location = (String) cursor.getString(cursor.getColumnIndex("location")); + cache.coords = getCoords(cursor); + index = cursor.getColumnIndex("elevation"); + if (cursor.isNull(index)) { + cache.elevation = null; + } else { + cache.elevation = (Double) cursor.getDouble(index); + } + cache.personalNote = (String) cursor.getString(cursor.getColumnIndex("personal_note")); + cache.shortdesc = (String) cursor.getString(cursor.getColumnIndex("shortdesc")); + cache.description = (String) cursor.getString(cursor.getColumnIndex("description")); + cache.favouriteCnt = (Integer) cursor.getInt(cursor.getColumnIndex("favourite_cnt")); + cache.rating = (Float) cursor.getFloat(cursor.getColumnIndex("rating")); + cache.votes = (Integer) cursor.getInt(cursor.getColumnIndex("votes")); + cache.myVote = (Float) cursor.getFloat(cursor.getColumnIndex("myvote")); + cache.disabled = cursor.getLong(cursor.getColumnIndex("disabled")) == 1l; + cache.archived = cursor.getLong(cursor.getColumnIndex("archived")) == 1l; + cache.members = cursor.getLong(cursor.getColumnIndex("members")) == 1l; + cache.found = cursor.getLong(cursor.getColumnIndex("found")) == 1l; + cache.favourite = cursor.getLong(cursor.getColumnIndex("favourite")) == 1l; + cache.inventoryItems = (Integer) cursor.getInt(cursor.getColumnIndex("inventoryunknown")); + cache.onWatchlist = cursor.getLong(cursor.getColumnIndex("onWatchlist")) == 1l; + cache.reliableLatLon = cursor.getInt(cursor.getColumnIndex("reliable_latlon")) > 0; + return cache; + } + + public List<String> loadAttributes(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + ArrayList<String> attributes = new ArrayList<String>(); + + Cursor cursor = databaseRO.query( + dbTableAttributes, + new String[] { "_id", "attribute" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "100"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("attribute"); + + do { + attributes.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return attributes; + } + + public cgWaypoint loadWaypoint(Integer id) { + if (id == null || id == 0) { + return null; + } + + init(); + + cgWaypoint waypoint = new cgWaypoint(); + + Cursor cursor = databaseRO.query( + dbTableWaypoints, + new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude_string", "longitude_string", "latitude", "longitude", "note" }, + "_id = " + id, + null, + null, + null, + null, + "1"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + + waypoint = createWaypointFromDatabaseContent(cursor); + } + + if (cursor != null) { + cursor.close(); + } + + return waypoint; + } + + public List<cgWaypoint> loadWaypoints(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + List<cgWaypoint> waypoints = new ArrayList<cgWaypoint>(); + + Cursor cursor = databaseRO.query( + dbTableWaypoints, + new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude_string", "longitude_string", "latitude", "longitude", "note" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "100"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + + do { + + cgWaypoint waypoint = createWaypointFromDatabaseContent(cursor); + + waypoints.add(waypoint); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return waypoints; + } + + private static cgWaypoint createWaypointFromDatabaseContent(Cursor cursor) { + cgWaypoint waypoint = new cgWaypoint(); + waypoint.id = (int) cursor.getInt(cursor.getColumnIndex("_id")); + waypoint.geocode = (String) cursor.getString(cursor.getColumnIndex("geocode")); + waypoint.type = (String) cursor.getString(cursor.getColumnIndex("type")); + waypoint.prefix = (String) cursor.getString(cursor.getColumnIndex("prefix")); + waypoint.lookup = (String) cursor.getString(cursor.getColumnIndex("lookup")); + waypoint.name = (String) cursor.getString(cursor.getColumnIndex("name")); + waypoint.latlon = (String) cursor.getString(cursor.getColumnIndex("latlon")); + waypoint.latitudeString = (String) cursor.getString(cursor.getColumnIndex("latitude_string")); + waypoint.longitudeString = (String) cursor.getString(cursor.getColumnIndex("longitude_string")); + waypoint.coords = getCoords(cursor); + waypoint.note = (String) cursor.getString(cursor.getColumnIndex("note")); + + return waypoint; + } + + public List<cgImage> loadSpoilers(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + List<cgImage> spoilers = new ArrayList<cgImage>(); + + Cursor cursor = databaseRO.query( + dbTableSpoilers, + new String[] { "_id", "url", "title", "description" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "100"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + int indexUrl = cursor.getColumnIndex("url"); + int indexTitle = cursor.getColumnIndex("title"); + int indexDescription = cursor.getColumnIndex("description"); + + do { + cgImage spoiler = new cgImage(); + spoiler.url = (String) cursor.getString(indexUrl); + spoiler.title = (String) cursor.getString(indexTitle); + spoiler.description = (String) cursor.getString(indexDescription); + + spoilers.add(spoiler); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return spoilers; + } + + /** + * Loads the history of previously entered destinations from + * the database. If no destinations exist, an {@link Collections#emptyList()} will be returned. + * + * @return A list of previously entered destinations or an empty list. + */ + public List<cgDestination> loadHistoryOfSearchedLocations() { + init(); + + Cursor cursor = databaseRO.query(dbTableSearchDestionationHistory, + new String[] { "_id", "date", "latitude", "longitude" }, null, + null, null, null, "date desc", "100"); + + final List<cgDestination> destinations = new LinkedList<cgDestination>(); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + int indexId = cursor.getColumnIndex("_id"); + int indexDate = cursor.getColumnIndex("date"); + int indexLatitude = cursor.getColumnIndex("latitude"); + int indexLongitude = cursor.getColumnIndex("longitude"); + + do { + final cgDestination dest = new cgDestination(); + dest.setId((long) cursor.getLong(indexId)); + dest.setDate((long) cursor.getLong(indexDate)); + dest.setCoords(getCoords(cursor, indexLatitude, indexLongitude)); + + // If coordinates are non-existent or invalid, do not consider + // this point. + if (dest.getCoords() != null) { + destinations.add(dest); + } + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return destinations; + } + + public boolean clearSearchedDestinations() { + boolean success = true; + init(); + databaseRW.beginTransaction(); + + try { + databaseRW.delete(dbTableSearchDestionationHistory, null, null); + databaseRW.setTransactionSuccessful(); + } catch (Exception e) { + success = false; + Log.e(cgSettings.tag, "Unable to clear searched destinations", e); + } finally { + databaseRW.endTransaction(); + } + + return success; + } + + public List<cgLog> loadLogs(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + List<cgLog> logs = new ArrayList<cgLog>(); + + Cursor cursor = databaseRO.rawQuery( + "SELECT cg_logs._id as cg_logs_id, type, author, log, date, found, " + dbTableLogImages + "._id as cg_logImages_id, log_id, title, url FROM " + + dbTableLogs + " LEFT OUTER JOIN " + dbTableLogImages + + " ON ( cg_logs._id = log_id ) WHERE geocode = ? ORDER BY date desc, cg_logs._id asc", new String[] { geocode }); + + if (cursor != null && cursor.getCount() > 0) { + cgLog log = null; + int indexLogsId = cursor.getColumnIndex("cg_logs_id"); + int indexType = cursor.getColumnIndex("type"); + int indexAuthor = cursor.getColumnIndex("author"); + int indexLog = cursor.getColumnIndex("log"); + int indexDate = cursor.getColumnIndex("date"); + int indexFound = cursor.getColumnIndex("found"); + int indexLogImagesId = cursor.getColumnIndex("cg_logImages_id"); + int indexTitle = cursor.getColumnIndex("title"); + int indexUrl = cursor.getColumnIndex("url"); + while (cursor.moveToNext() && logs.size() < 100) { + if (log == null || log.id != cursor.getInt(indexLogsId)) { + log = new cgLog(); + log.id = (int) cursor.getInt(indexLogsId); + log.type = (int) cursor.getInt(indexType); + log.author = (String) cursor.getString(indexAuthor); + log.log = (String) cursor.getString(indexLog); + log.date = (long) cursor.getLong(indexDate); + log.found = (int) cursor.getInt(indexFound); + logs.add(log); + } + if (!cursor.isNull(indexLogImagesId)) { + final cgImage log_img = new cgImage(); + log_img.title = (String) cursor.getString(indexTitle); + if (log_img.title == null) { + log_img.title = ""; + } + log_img.url = (String) cursor.getString(indexUrl); + if (log_img.url == null) { + log_img.url = ""; + } + if (log.logImages == null) { + log.logImages = new ArrayList<cgImage>(); + } + log.logImages.add(log_img); + } + } + } + + if (cursor != null) { + cursor.close(); + } + + return logs; + } + + public Map<Integer, Integer> loadLogCounts(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + Map<Integer, Integer> logCounts = new HashMap<Integer, Integer>(); + + Cursor cursor = databaseRO.query( + dbTableLogCount, + new String[] { "_id", "type", "count" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + null, + "100"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + int indexType = cursor.getColumnIndex("type"); + int indexCount = cursor.getColumnIndex("count"); + + do { + Integer type = cursor.getInt(indexType); + Integer count = cursor.getInt(indexCount); + + logCounts.put(type, count); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return logCounts; + } + + public List<cgTrackable> loadInventory(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + List<cgTrackable> trackables = new ArrayList<cgTrackable>(); + + Cursor cursor = databaseRO.query( + dbTableTrackables, + new String[] { "_id", "updated", "tbcode", "guid", "title", "owner", "released", "goal", "description" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + "title COLLATE NOCASE ASC", + "100"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + + do { + cgTrackable trackable = createTrackableFromDatabaseContent(cursor); + + trackables.add(trackable); + } while (cursor.moveToNext()); + } + + if (cursor != null) { + cursor.close(); + } + + return trackables; + } + + public cgTrackable loadTrackable(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + cgTrackable trackable = new cgTrackable(); + + Cursor cursor = databaseRO.query( + dbTableTrackables, + new String[] { "_id", "updated", "tbcode", "guid", "title", "owner", "released", "goal", "description" }, + "tbcode = \"" + geocode + "\"", + null, + null, + null, + null, + "1"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + trackable = createTrackableFromDatabaseContent(cursor); + } + + if (cursor != null) { + cursor.close(); + } + + return trackable; + } + + private cgTrackable createTrackableFromDatabaseContent(Cursor cursor) { + cgTrackable trackable = new cgTrackable(); + trackable.geocode = (String) cursor.getString(cursor.getColumnIndex("tbcode")); + trackable.guid = (String) cursor.getString(cursor.getColumnIndex("guid")); + trackable.name = (String) cursor.getString(cursor.getColumnIndex("title")); + trackable.owner = (String) cursor.getString(cursor.getColumnIndex("owner")); + String releasedPre = cursor.getString(cursor.getColumnIndex("released")); + if (releasedPre != null && Long.getLong(releasedPre) != null) { + trackable.released = new Date(Long.getLong(releasedPre)); + } else { + trackable.released = null; + } + trackable.goal = (String) cursor.getString(cursor.getColumnIndex("goal")); + trackable.details = (String) cursor.getString(cursor.getColumnIndex("description")); + trackable.logs = loadLogs(trackable.geocode); + return trackable; + } + + public int getAllStoredCachesCount(boolean detailedOnly, String cachetype, Integer list) { + String listSql = null; + String listSqlW = null; + if (list == null) { + listSql = " where reason >= 1"; + listSqlW = " and reason >= 1"; + } else if (list >= 1) { + listSql = " where reason = " + list; + listSqlW = " and reason = " + list; + } else { + return 0; + } + + int count = 0; + try { + String sql = "select count(_id) from " + dbTableCaches; // this default is not used, but we like to have variables initialized + if (detailedOnly == false) { + if (cachetype == null) { + sql = "select count(_id) from " + dbTableCaches + listSql; + } else { + sql = "select count(_id) from " + dbTableCaches + " where type = \"" + cachetype + "\"" + listSqlW; + } + } else { + if (cachetype == null) { + sql = "select count(_id) from " + dbTableCaches + " where detailed = 1" + listSqlW; + } else { + sql = "select count(_id) from " + dbTableCaches + " where detailed = 1 and type = \"" + cachetype + "\"" + listSqlW; + } + } + SQLiteStatement compiledStmnt = databaseRO.compileStatement(sql); + count = (int) compiledStmnt.simpleQueryForLong(); + compiledStmnt.close(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.loadAllStoredCachesCount: " + e.toString()); + } + + return count; + } + + public int getAllHistoricCachesCount(boolean detailedOnly, String cachetype) { + init(); + + int count = 0; + + try { + SQLiteStatement sqlCount = databaseRO.compileStatement("select count(_id) from " + dbTableCaches + " where visiteddate > 0"); + count = (int) sqlCount.simpleQueryForLong(); + sqlCount.close(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getAllHistoricCachesCount: " + e.toString()); + } + + return count; + } + + public List<String> loadBatchOfStoredGeocodes(boolean detailedOnly, final Geopoint coords, String cachetype, int list) { + init(); + + if (list < 1) { + list = 1; + } + + List<String> geocodes = new ArrayList<String>(); + + StringBuilder specifySql = new StringBuilder(); + + specifySql.append("reason = "); + specifySql.append(list); + + if (detailedOnly) { + specifySql.append(" and detailed = 1 "); + } + + if (cachetype != null) { + specifySql.append(" and type = \""); + specifySql.append(cachetype); + specifySql.append('"'); + } + + try { + Cursor cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode", "(abs(latitude-" + String.format((Locale) null, "%.6f", coords.getLatitude()) + + ") + abs(longitude-" + String.format((Locale) null, "%.6f", coords.getLongitude()) + ")) as dif" }, + specifySql.toString(), + null, + null, + null, + "dif", + null); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } else { + cursor.close(); + return null; + } + + cursor.close(); + } + + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.loadBatchOfStoredGeocodes: " + e.toString()); + } + + return geocodes; + } + + public List<String> loadBatchOfHistoricGeocodes(boolean detailedOnly, String cachetype) { + init(); + + List<String> geocodes = new ArrayList<String>(); + + StringBuilder specifySql = new StringBuilder(); + specifySql.append("visiteddate > 0"); + + if (detailedOnly) { + specifySql.append(" and detailed = 1"); + } + if (cachetype != null) { + specifySql.append(" and type = \""); + specifySql.append(cachetype); + specifySql.append('"'); + } + + try { + Cursor cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + specifySql.toString(), + null, + null, + null, + "visiteddate", + null); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } else { + cursor.close(); + return null; + } + + cursor.close(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.loadBatchOfHistoricGeocodes: " + e.toString()); + } + + return geocodes; + } + + public List<String> getCachedInViewport(Long centerLat, Long centerLon, Long spanLat, Long spanLon, String cachetype) { + return getInViewport(false, centerLat, centerLon, spanLat, spanLon, cachetype); + } + + public List<String> getStoredInViewport(Long centerLat, Long centerLon, Long spanLat, Long spanLon, String cachetype) { + return getInViewport(true, centerLat, centerLon, spanLat, spanLon, cachetype); + } + + public List<String> getInViewport(boolean stored, Long centerLat, Long centerLon, Long spanLat, Long spanLon, String cachetype) { + if (centerLat == null || centerLon == null || spanLat == null || spanLon == null) { + return null; + } + + init(); + + List<String> geocodes = new ArrayList<String>(); + + // viewport limitation + double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); + double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); + double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); + double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); + double llCache; + + if (latMin > latMax) { + llCache = latMax; + latMax = latMin; + latMin = llCache; + } + if (lonMin > lonMax) { + llCache = lonMax; + lonMax = lonMin; + lonMin = llCache; + } + + StringBuilder where = new StringBuilder(); + where.append("latitude >= "); + where.append(String.format((Locale) null, "%.6f", latMin)); + where.append(" and latitude <= "); + where.append(String.format((Locale) null, "%.6f", latMax)); + where.append(" and longitude >= "); + where.append(String.format((Locale) null, "%.6f", lonMin)); + where.append(" and longitude <= "); + where.append(String.format((Locale) null, "%.6f", lonMax)); + + // cachetype limitation + if (cachetype != null) { + where.append(" and type = \""); + where.append(cachetype); + where.append('"'); + } + + // offline caches only + if (stored) { + where.append(" and reason >= 1"); + } + + try { + Cursor cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + where.toString(), + null, + null, + null, + null, + "500"); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } else { + cursor.close(); + return null; + } + + cursor.close(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getOfflineInViewport: " + e.toString()); + } + + return geocodes; + } + + public List<String> getOfflineAll(String cachetype) { + init(); + + List<String> geocodes = new ArrayList<String>(); + + StringBuilder where = new StringBuilder(); + + // cachetype limitation + if (cachetype != null) { + where.append(cachetype); + where.append('"'); + } + + // offline caches only + if (where.length() > 0) { + where.append(" and "); + } + where.append("reason >= 1"); + + try { + Cursor cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + where.toString(), + null, + null, + null, + null, + "5000"); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add((String) cursor.getString(index)); + } while (cursor.moveToNext()); + } else { + cursor.close(); + return null; + } + + cursor.close(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.getOfflineAll: " + e.toString()); + } + + return geocodes; + } + + public void markStored(String geocode, int listId) { + if (StringUtils.isBlank(geocode)) { + return; + } + + init(); + + if (listId <= 0) { + listId = 1; + } + + ContentValues values = new ContentValues(); + values.put("reason", listId); + databaseRW.update(dbTableCaches, values, "geocode = ? and reason < 1", new String[] { geocode }); + } + + public boolean markDropped(String geocode) { + if (StringUtils.isBlank(geocode)) { + return false; + } + + init(); + + try { + ContentValues values = new ContentValues(); + values.put("reason", 0); + int rows = databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { geocode }); + + if (rows > 0) { + return true; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.markDropped: " + e.toString()); + } + + return false; + } + + public boolean markFound(String geocode) { + if (StringUtils.isBlank(geocode)) { + return false; + } + + init(); + + try { + ContentValues values = new ContentValues(); + values.put("found", 1); + int rows = databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { geocode }); + + if (rows > 0) { + return true; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.markFound: " + e.toString()); + } + + return false; + } + + public void clean() { + clean(false); + } + + public void clean(boolean more) { + init(); + + Log.d(cgSettings.tag, "Database clean: started"); + + Cursor cursor = null; + List<String> geocodes = new ArrayList<String>(); + + try { + if (more) { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + "reason = 0", + null, + null, + null, + null, + null); + } else { + cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + "reason = 0 and detailed < " + (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000)) + " and detailedupdate < " + (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000)) + " and visiteddate < " + (System.currentTimeMillis() - (3 * 24 * 60 * 60 * 1000)), + null, + null, + null, + null, + null); + } + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add("\"" + (String) cursor.getString(index) + "\""); + } while (cursor.moveToNext()); + } + + cursor.close(); + } + + final int size = geocodes.size(); + if (size > 0) { + Log.d(cgSettings.tag, "Database clean: removing " + size + " geocaches"); + + String geocodeList = cgBase.implode(", ", geocodes.toArray()); + databaseRW.execSQL("delete from " + dbTableCaches + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableAttributes + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableSpoilers + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogs + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogCount + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogsOffline + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableWaypoints + " where geocode in (" + geocodeList + ") and type <> \"own\""); + databaseRW.execSQL("delete from " + dbTableTrackables + " where geocode in (" + geocodeList + ")"); + + geocodes.clear(); + } + + databaseRW.execSQL("delete from " + dbTableCaches + " where geocode = \"\""); + + if (Log.isLoggable(cgSettings.tag, Log.DEBUG)) { + final SQLiteStatement countSql = databaseRO.compileStatement("select count(_id) from " + dbTableCaches + " where reason = 0"); + final int count = (int) countSql.simpleQueryForLong(); + countSql.close(); + Log.d(cgSettings.tag, "Database clean: " + count + " cached geocaches remaining"); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgData.clean: " + e.toString()); + } + + Log.d(cgSettings.tag, "Database clean: finished"); + } + + public void dropStored(int listId) { + init(); + + List<String> geocodes = new ArrayList<String>(); + + try { + Cursor cursor = databaseRO.query( + dbTableCaches, + new String[] { "_id", "geocode" }, + "reason = " + listId, + null, + null, + null, + null, + null); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int index = cursor.getColumnIndex("geocode"); + + do { + geocodes.add("\"" + (String) cursor.getString(index) + "\""); + } while (cursor.moveToNext()); + } else { + cursor.close(); + return; + } + + cursor.close(); + } + + if (CollectionUtils.isNotEmpty(geocodes)) { + String geocodeList = cgBase.implode(", ", geocodes.toArray()); + databaseRW.execSQL("delete from " + dbTableCaches + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableAttributes + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableSpoilers + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogs + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogCount + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableLogsOffline + " where geocode in (" + geocodeList + ")"); + databaseRW.execSQL("delete from " + dbTableWaypoints + " where geocode in (" + geocodeList + ") and type <> \"own\""); + databaseRW.execSQL("delete from " + dbTableTrackables + " where geocode in (" + geocodeList + ")"); + + geocodes.clear(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.dropStored: " + e.toString()); + } + } + + public boolean saveLogOffline(String geocode, Date date, int type, String log) { + if (StringUtils.isBlank(geocode)) { + return false; + } + if (type <= 0 && StringUtils.isBlank(log)) { + return false; + } + + boolean status = false; + + ContentValues values = new ContentValues(); + values.put("geocode", geocode); + values.put("updated", System.currentTimeMillis()); + values.put("type", type); + values.put("log", log); + values.put("date", date.getTime()); + + try { + if (hasLogOffline(geocode)) { + final int rows = databaseRW.update(dbTableLogsOffline, values, "geocode = ?", new String[] { geocode }); + + if (rows > 0) { + status = true; + } + } else { + final long id = databaseRW.insert(dbTableLogsOffline, null, values); + + if (id > 0) { + status = true; + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.saveLogOffline: " + e.toString()); + } + + return status; + } + + public cgLog loadLogOffline(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + init(); + + cgLog log = null; + + Cursor cursor = databaseRO.query( + dbTableLogsOffline, + new String[] { "_id", "type", "log", "date" }, + "geocode = \"" + geocode + "\"", + null, + null, + null, + "_id desc", + "1"); + + if (cursor != null && cursor.getCount() > 0) { + cursor.moveToFirst(); + + log = new cgLog(); + log.id = (int) cursor.getInt(cursor.getColumnIndex("_id")); + log.type = (int) cursor.getInt(cursor.getColumnIndex("type")); + log.log = (String) cursor.getString(cursor.getColumnIndex("log")); + log.date = (long) cursor.getLong(cursor.getColumnIndex("date")); + } + + if (cursor != null) { + cursor.close(); + } + + return log; + } + + public void clearLogOffline(String geocode) { + if (StringUtils.isBlank(geocode)) { + return; + } + + init(); + + databaseRW.delete(dbTableLogsOffline, "geocode = ?", new String[] { geocode }); + } + + public boolean hasLogOffline(String geocode) { + if (StringUtils.isBlank(geocode)) { + return false; + } + + int count = 0; + init(); + try { + final SQLiteStatement countSql = databaseRO.compileStatement("select count(_id) from " + dbTableLogsOffline + " where geocode = \"" + geocode.toUpperCase() + "\""); + count = (int) countSql.simpleQueryForLong(); + + countSql.close(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.hasLogOffline: " + e.toString()); + } + + return count > 0; + } + + public void saveVisitDate(String geocode) { + if (StringUtils.isBlank(geocode)) { + return; + } + + ContentValues values = new ContentValues(); + values.put("visiteddate", System.currentTimeMillis()); + + try { + databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { geocode }); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.saveVisitDate: " + e.toString()); + } + } + + public void clearVisitDate(String geocode) { + if (StringUtils.isBlank(geocode)) { + return; + } + + ContentValues values = new ContentValues(); + values.put("visiteddate", 0); + + try { + databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { geocode }); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.clearVisitDate: " + e.toString()); + } + } + + public List<cgList> getLists(Resources res) { + init(); + + List<cgList> lists = new ArrayList<cgList>(); + + lists.add(new cgList(true, 1, res.getString(R.string.list_inbox))); + // lists.add(new cgList(true, 2, res.getString(R.string.list_wpt))); + + ArrayList<cgList> storedLists = readLists(null, "title COLLATE NOCASE ASC"); + lists.addAll(storedLists); + + return lists; + } + + private ArrayList<cgList> readLists(String selection, String sorting) { + ArrayList<cgList> result = new ArrayList<cgList>(); + try { + Cursor cursor = databaseRO.query( + dbTableLists, + new String[] { "_id", "title", "updated", "latitude", "longitude" }, + selection, + null, + null, + null, + sorting); + + if (cursor != null) { + if (cursor.getCount() > 0) { + cursor.moveToFirst(); + int indexId = cursor.getColumnIndex("_id"); + int indexTitle = cursor.getColumnIndex("title"); + int indexUpdated = cursor.getColumnIndex("updated"); + int indexLatitude = cursor.getColumnIndex("latitude"); + int indexLongitude = cursor.getColumnIndex("longitude"); + + do { + cgList list = new cgList(false); + + list.id = ((int) cursor.getInt(indexId)) + 10; + list.title = (String) cursor.getString(indexTitle); + list.updated = (Long) cursor.getLong(indexUpdated); + list.coords = getCoords(cursor, indexLatitude, indexLongitude); + + result.add(list); + } while (cursor.moveToNext()); + } + + cursor.close(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgData.readLists: " + e.toString()); + } + return result; + } + + public cgList getList(int id, Resources res) { + if (id == 1) { + return new cgList(true, 1, res.getString(R.string.list_inbox)); + } else if (id == 2) { + return new cgList(true, 2, res.getString(R.string.list_wpt)); + } else if (id >= 10) { + init(); + + ArrayList<cgList> lists = readLists("_id = " + (id - 10), null); + if (!lists.isEmpty()) { + return lists.get(0); + } + } + + return null; + } + + public int createList(String name) { + int id = -1; + if (StringUtils.isBlank(name)) { + return id; + } + + init(); + + databaseRW.beginTransaction(); + try { + ContentValues values = new ContentValues(); + values.put("title", name); + values.put("updated", System.currentTimeMillis()); + + id = (int) databaseRW.insert(dbTableLists, null, values); + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + if (id < 0) { + return -1; + } else { + return (id + 10); + } + } + + public boolean removeList(int id) { + boolean status = false; + if (id < 10) { + return status; + } + + init(); + + databaseRW.beginTransaction(); + try { + int cnt = databaseRW.delete(dbTableLists, "_id = " + (id - 10), null); + + if (cnt > 0) { + ContentValues values = new ContentValues(); + values.put("reason", 1); + databaseRW.update(dbTableCaches, values, "reason = " + id, null); + + status = true; + } + + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + + return status; + } + + public void moveToList(String geocode, int listId) { + if (StringUtils.isBlank(geocode) || listId <= 0) { + return; + } + + databaseRW.beginTransaction(); + try { + ContentValues values = new ContentValues(); + values.put("reason", listId); + databaseRW.update(dbTableCaches, values, "geocode = ?", new String[] { geocode }); + + databaseRW.setTransactionSuccessful(); + } finally { + databaseRW.endTransaction(); + } + } + + public synchronized boolean status() { + if (databaseRO == null || databaseRW == null || initialized == false) { + return false; + } + + return true; + } + + public boolean removeSearchedDestination(cgDestination destination) { + boolean success = true; + if (destination == null) { + success = false; + } else { + init(); + + databaseRW.beginTransaction(); + + try { + databaseRW.delete(dbTableSearchDestionationHistory, "_id = " + destination.getId(), null); + databaseRW.setTransactionSuccessful(); + } catch (Exception e) { + Log.e(cgSettings.tag, "Unable to remove searched destination", e); + success = false; + } finally { + databaseRW.endTransaction(); + } + } + + return success; + } +} diff --git a/main/src/cgeo/geocaching/cgDestination.java b/main/src/cgeo/geocaching/cgDestination.java new file mode 100644 index 0000000..96beec7 --- /dev/null +++ b/main/src/cgeo/geocaching/cgDestination.java @@ -0,0 +1,72 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +public class cgDestination { + + private long id; + + private long date; + + private Geopoint coords; + + public cgDestination() { + } + + public cgDestination(long id, long date, final Geopoint coords) { + super(); + this.id = id; + this.date = date; + this.coords = coords; + } + + public long getDate() { + return date; + } + + public void setDate(long date) { + this.date = date; + } + + public Geopoint getCoords() { + return coords; + } + + public void setCoords(final Geopoint coords) { + this.coords = coords; + } + + @Override + public int hashCode() { + final int prime = 31; + int result = 1; + long temp; + temp = Double.doubleToLongBits(coords.getLatitude()); + result = prime * result + (int) (temp ^ (temp >>> 32)); + temp = Double.doubleToLongBits(coords.getLongitude()); + result = prime * result + (int) (temp ^ (temp >>> 32)); + return result; + } + + @Override + public boolean equals(Object obj) { + if (this == obj) + return true; + if (obj == null) + return false; + if (!(obj instanceof cgDestination)) { + return false; + } + cgDestination other = (cgDestination) obj; + return coords.isEqualTo(other.coords); + } + + public long getId() { + return id; + } + + public void setId(long id) { + this.id = id; + } + +} diff --git a/main/src/cgeo/geocaching/cgDirection.java b/main/src/cgeo/geocaching/cgDirection.java new file mode 100644 index 0000000..e786edf --- /dev/null +++ b/main/src/cgeo/geocaching/cgDirection.java @@ -0,0 +1,75 @@ +package cgeo.geocaching; + +import cgeo.geocaching.compatibility.Compatibility; + +import android.app.Activity; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class cgDirection { + private cgDirection dir = null; + private Context context = null; + private SensorManager sensorManager = null; + private cgeoSensorListener sensorListener = null; + private cgUpdateDir dirUpdate = null; + + public Float directionNow = null; + + public cgDirection(Context contextIn, cgUpdateDir dirUpdateIn) { + context = contextIn; + dirUpdate = dirUpdateIn; + sensorListener = new cgeoSensorListener(); + } + + public void initDir() { + dir = this; + + if (sensorManager == null) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + } + sensorManager.registerListener(sensorListener, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL); + } + + public void closeDir() { + if (sensorManager != null && sensorListener != null) { + sensorManager.unregisterListener(sensorListener); + } + } + + public void replaceUpdate(cgUpdateDir dirUpdateIn) { + dirUpdate = dirUpdateIn; + + if (dirUpdate != null && directionNow != null) { + dirUpdate.updateDir(dir); + } + } + + private class cgeoSensorListener implements SensorEventListener { + @Override + public void onAccuracyChanged(Sensor sensor, int accuracy) { + /* + * There is a bug in Android, which appearently causes this method to be called every + * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging + * this event leads to the log being flooded with multiple entries _per second_, + * which I experienced when running cgeo in a building (with GPS and network being + * unreliable). + * + * See for example https://code.google.com/p/android/issues/detail?id=14792 + */ + + //Log.i(cgSettings.tag, "Compass' accuracy is low (" + accuracy + ")"); + } + + @Override + public void onSensorChanged(SensorEvent event) { + directionNow = Compatibility.getDirectionNow(event.values[0], (Activity) context); + + if (dirUpdate != null && directionNow != null) { + dirUpdate.updateDir(dir); + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgDirectionImg.java b/main/src/cgeo/geocaching/cgDirectionImg.java new file mode 100644 index 0000000..1aa7795 --- /dev/null +++ b/main/src/cgeo/geocaching/cgDirectionImg.java @@ -0,0 +1,95 @@ +package cgeo.geocaching; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.impl.client.DefaultHttpClient; + +import android.util.Log; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; + +public class cgDirectionImg { + + public static void getDrawable(String geocode, String code) { + String dirName; + String fileName; + + if (StringUtils.isBlank(geocode) || StringUtils.isBlank(code)) { + return; + } + + if (StringUtils.isNotBlank(geocode)) { + dirName = cgSettings.getStorage() + geocode + "/"; + fileName = cgSettings.getStorage() + geocode + "/direction.png"; + } else { + return; + } + + File dir = null; + dir = new File(cgSettings.getStorage()); + if (dir.exists() == false) { + dir.mkdirs(); + } + dir = new File(dirName); + if (dir.exists() == false) { + dir.mkdirs(); + } + dir = null; + + HttpClient client = null; + HttpGet getMethod = null; + HttpResponse httpResponse = null; + HttpEntity entity = null; + BufferedHttpEntity bufferedEntity = null; + + boolean ok = false; + + for (int i = 0; i < 3; i++) { + if (i > 0) + Log.w(cgSettings.tag, "cgDirectionImg.getDrawable: Failed to download data, retrying. Attempt #" + (i + 1)); + + try { + client = new DefaultHttpClient(); + getMethod = new HttpGet("http://www.geocaching.com/ImgGen/seek/CacheDir.ashx?k=" + code); + httpResponse = client.execute(getMethod); + entity = httpResponse.getEntity(); + bufferedEntity = new BufferedHttpEntity(entity); + + Log.i(cgSettings.tag, "[" + entity.getContentLength() + "B] Downloading direction image " + code); + + if (bufferedEntity != null) { + InputStream is = (InputStream) bufferedEntity.getContent(); + FileOutputStream fos = new FileOutputStream(fileName); + + try { + byte[] buffer = new byte[4096]; + int l; + while ((l = is.read(buffer)) != -1) { + fos.write(buffer, 0, l); + } + ok = true; + fos.flush(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgDirectionImg.getDrawable (saving to cache): " + e.toString()); + } finally { + is.close(); + fos.close(); + } + } + + if (ok) { + break; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgDirectionImg.getDrawable (downloading from web): " + e.toString()); + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgDistanceView.java b/main/src/cgeo/geocaching/cgDistanceView.java new file mode 100644 index 0000000..ebdfe78 --- /dev/null +++ b/main/src/cgeo/geocaching/cgDistanceView.java @@ -0,0 +1,44 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import android.content.Context; +import android.util.AttributeSet; +import android.widget.TextView; + +public class cgDistanceView extends TextView { + private cgBase base = null; + private Geopoint cacheCoords = null; + + public cgDistanceView(Context context) { + super(context); + } + + public cgDistanceView(Context context, AttributeSet attrs) { + super(context, attrs); + } + + public cgDistanceView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + } + + public void setContent(cgBase baseIn, final Geopoint cacheCoordsIn) { + base = baseIn; + cacheCoords = cacheCoordsIn; + } + + public void update(final Geopoint coords) { + if (cacheCoords == null || coords == null || base == null) { + return; + } + setText(base.getHumanDistance(coords.distanceTo(cacheCoords))); + } + + public void setDistance(Float distance) { + setText("~" + base.getHumanDistance(distance)); + } + + public void clear() { + setText(null); + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgGPXListAdapter.java b/main/src/cgeo/geocaching/cgGPXListAdapter.java new file mode 100644 index 0000000..df1933c --- /dev/null +++ b/main/src/cgeo/geocaching/cgGPXListAdapter.java @@ -0,0 +1,75 @@ +package cgeo.geocaching; + +import android.app.Activity; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.io.File; +import java.util.List; + +public class cgGPXListAdapter extends ArrayAdapter<File> { + private cgGPXView holder = null; + private cgeogpxes parent = null; + private LayoutInflater inflater = null; + + public cgGPXListAdapter(cgeogpxes parentIn, cgSettings settingsIn, List<File> listIn) { + super(parentIn, 0, listIn); + + parent = parentIn; + } + + @Override + public View getView(int position, View rowView, ViewGroup parent) { + if (inflater == null) + inflater = ((Activity) getContext()).getLayoutInflater(); + + if (position > getCount()) { + Log.w(cgSettings.tag, "cgGPXListAdapter.getView: Attempt to access missing item #" + position); + return null; + } + + File file = getItem(position); + + if (rowView == null) { + rowView = (View) inflater.inflate(R.layout.gpx_item, null); + + holder = new cgGPXView(); + holder.filepath = (TextView) rowView.findViewById(R.id.filepath); + holder.filename = (TextView) rowView.findViewById(R.id.filename); + + rowView.setTag(holder); + } else { + holder = (cgGPXView) rowView.getTag(); + } + + final touchListener touchLst = new touchListener(file); + rowView.setOnClickListener(touchLst); + + holder.filepath.setText(file.getParent()); + holder.filename.setText(file.getName()); + + return rowView; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + } + + private class touchListener implements View.OnClickListener { + private File file = null; + + public touchListener(File fileIn) { + file = fileIn; + } + + // tap on item + public void onClick(View view) { + parent.loadGPX(file); + } + } +} diff --git a/main/src/cgeo/geocaching/cgGPXView.java b/main/src/cgeo/geocaching/cgGPXView.java new file mode 100644 index 0000000..a731f70 --- /dev/null +++ b/main/src/cgeo/geocaching/cgGPXView.java @@ -0,0 +1,9 @@ +package cgeo.geocaching; + +import android.widget.TextView; + +public class cgGPXView { + // layouts & views + public TextView filepath; + public TextView filename; +} diff --git a/main/src/cgeo/geocaching/cgGeo.java b/main/src/cgeo/geocaching/cgGeo.java new file mode 100644 index 0000000..633ab2f --- /dev/null +++ b/main/src/cgeo/geocaching/cgGeo.java @@ -0,0 +1,441 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; + +import android.content.Context; +import android.content.SharedPreferences; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.Location; +import android.location.LocationListener; +import android.location.LocationManager; +import android.os.Bundle; +import android.util.Log; + +import java.util.HashMap; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +public class cgGeo { + + private Context context = null; + private cgeoapplication app = null; + private LocationManager geoManager = null; + private cgUpdateLoc geoUpdate = null; + private cgBase base = null; + private cgSettings settings = null; + private SharedPreferences prefs = null; + private cgeoGeoListener geoNetListener = null; + private cgeoGeoListener geoGpsListener = null; + private cgeoGpsStatusListener geoGpsStatusListener = null; + private Integer time = 0; + private Integer distance = 0; + private Location locGps = null; + private Location locNet = null; + private long locGpsLast = 0L; + private boolean g4cRunning = false; + private Geopoint lastGo4cacheCoords = null; + public Location location = null; + public int gps = -1; + public Geopoint coordsNow = null; + public Geopoint coordsBefore = null; + public Double altitudeNow = null; + public Float bearingNow = null; + public Float speedNow = null; + public Float accuracyNow = null; + public Integer satellitesVisible = null; + public Integer satellitesFixed = null; + public double distanceNow = 0d; + + public cgGeo(Context contextIn, cgeoapplication appIn, cgUpdateLoc geoUpdateIn, cgBase baseIn, cgSettings settingsIn, int timeIn, int distanceIn) { + context = contextIn; + app = appIn; + geoUpdate = geoUpdateIn; + base = baseIn; + settings = settingsIn; + time = timeIn; + distance = distanceIn; + + if (prefs == null) { + prefs = context.getSharedPreferences(cgSettings.preferences, 0); + } + distanceNow = prefs.getFloat("dst", 0f); + if (Double.isNaN(distanceNow)) { + distanceNow = 0d; + } + if (distanceNow == 0f) { + final SharedPreferences.Editor prefsEdit = context.getSharedPreferences(cgSettings.preferences, 0).edit(); + if (prefsEdit != null) { + prefsEdit.putLong("dst-since", System.currentTimeMillis()); + prefsEdit.commit(); + } + } + + geoNetListener = new cgeoGeoListener(); + geoNetListener.setProvider(LocationManager.NETWORK_PROVIDER); + + geoGpsListener = new cgeoGeoListener(); + geoGpsListener.setProvider(LocationManager.GPS_PROVIDER); + + geoGpsStatusListener = new cgeoGpsStatusListener(); + } + + public void initGeo() { + location = null; + gps = -1; + coordsNow = null; + altitudeNow = null; + bearingNow = null; + speedNow = null; + accuracyNow = null; + satellitesVisible = 0; + satellitesFixed = 0; + + if (geoManager == null) { + geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + lastLoc(); + + geoNetListener.setProvider(LocationManager.NETWORK_PROVIDER); + geoGpsListener.setProvider(LocationManager.GPS_PROVIDER); + geoManager.addGpsStatusListener(geoGpsStatusListener); + + try { + geoManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, time, distance, geoNetListener); + } catch (Exception e) { + Log.e(cgSettings.tag, "There is no NETWORK location provider"); + } + + try { + geoManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, time, distance, geoGpsListener); + } catch (Exception e) { + Log.e(cgSettings.tag, "There is no GPS location provider"); + } + } + + public void closeGeo() { + if (geoManager != null && geoNetListener != null) { + geoManager.removeUpdates(geoNetListener); + } + if (geoManager != null && geoGpsListener != null) { + geoManager.removeUpdates(geoGpsListener); + } + if (geoManager != null) { + geoManager.removeGpsStatusListener(geoGpsStatusListener); + } + + final SharedPreferences.Editor prefsEdit = context.getSharedPreferences(cgSettings.preferences, 0).edit(); + if (prefsEdit != null && Double.isNaN(distanceNow) == false) { + prefsEdit.putFloat("dst", (float) distanceNow); + prefsEdit.commit(); + } + } + + public void replaceUpdate(cgUpdateLoc geoUpdateIn) { + geoUpdate = geoUpdateIn; + + if (geoUpdate != null) { + geoUpdate.updateLoc(this); + } + } + + public class cgeoGeoListener implements LocationListener { + + public String active = null; + + @Override + public void onStatusChanged(String provider, int status, Bundle extras) { + // nothing + } + + @Override + public void onLocationChanged(Location location) { + if (location.getProvider().equals(LocationManager.GPS_PROVIDER)) { + locGps = location; + locGpsLast = System.currentTimeMillis(); + } else if (location.getProvider().equals(LocationManager.NETWORK_PROVIDER)) { + locNet = location; + } + + selectBest(location.getProvider()); + } + + @Override + public void onProviderDisabled(String provider) { + if (provider.equals(LocationManager.NETWORK_PROVIDER)) { + if (geoManager != null && geoNetListener != null) { + geoManager.removeUpdates(geoNetListener); + } + } else if (provider.equals(LocationManager.GPS_PROVIDER)) { + if (geoManager != null && geoGpsListener != null) { + geoManager.removeUpdates(geoGpsListener); + } + } + } + + @Override + public void onProviderEnabled(String provider) { + if (provider.equals(LocationManager.NETWORK_PROVIDER)) { + if (geoNetListener == null) { + geoNetListener = new cgeoGeoListener(); + } + geoNetListener.setProvider(LocationManager.NETWORK_PROVIDER); + } else if (provider.equals(LocationManager.GPS_PROVIDER)) { + if (geoGpsListener == null) { + geoGpsListener = new cgeoGeoListener(); + } + geoGpsListener.setProvider(LocationManager.GPS_PROVIDER); + } + } + + public void setProvider(String provider) { + if (provider.equals(LocationManager.GPS_PROVIDER)) { + if (geoManager != null && geoManager.isProviderEnabled(LocationManager.GPS_PROVIDER)) { + active = provider; + } else { + active = null; + } + } else if (provider.equals(LocationManager.NETWORK_PROVIDER)) { + if (geoManager != null && geoManager.isProviderEnabled(LocationManager.NETWORK_PROVIDER)) { + active = provider; + } else { + active = null; + } + } + } + } + + public class cgeoGpsStatusListener implements GpsStatus.Listener { + + @Override + public void onGpsStatusChanged(int event) { + if (event == GpsStatus.GPS_EVENT_SATELLITE_STATUS) { + GpsStatus status = geoManager.getGpsStatus(null); + Iterator<GpsSatellite> statusIterator = status.getSatellites().iterator(); + + int satellites = 0; + int fixed = 0; + + while (statusIterator.hasNext()) { + GpsSatellite sat = statusIterator.next(); + if (sat.usedInFix()) { + fixed++; + } + satellites++; + + /* + * satellite signal strength + * if (sat.usedInFix()) { + * Log.d(cgSettings.tag, "Sat #" + satellites + ": " + sat.getSnr() + " FIX"); + * } else { + * Log.d(cgSettings.tag, "Sat #" + satellites + ": " + sat.getSnr()); + * } + */ + } + + boolean changed = false; + if (satellitesVisible == null || satellites != satellitesVisible) { + satellitesVisible = satellites; + changed = true; + } + if (satellitesFixed == null || fixed != satellitesFixed) { + satellitesFixed = fixed; + changed = true; + } + + if (changed) { + selectBest(null); + } + } + } + } + + private void selectBest(String initProvider) { + if (locNet == null && locGps != null) { // we have only GPS + assign(locGps); + return; + } + + if (locNet != null && locGps == null) { // we have only NET + assign(locNet); + return; + } + + if (satellitesFixed > 0) { // GPS seems to be fixed + assign(locGps); + return; + } + + if (initProvider != null && initProvider.equals(LocationManager.GPS_PROVIDER)) { // we have new location from GPS + assign(locGps); + return; + } + + if (locGpsLast > (System.currentTimeMillis() - 30 * 1000)) { // GPS was working in last 30 seconds + assign(locGps); + return; + } + + assign(locNet); // nothing else, using NET + } + + private void assign(final Geopoint coords) { + if (coords == null) { + return; + } + + gps = -1; + coordsNow = coords; + altitudeNow = null; + bearingNow = 0f; + speedNow = 0f; + accuracyNow = 999f; + + if (geoUpdate != null) { + geoUpdate.updateLoc(this); + } + } + + private void assign(Location loc) { + if (loc == null) { + gps = -1; + return; + } + + location = loc; + + String provider = location.getProvider(); + if (provider.equals(LocationManager.GPS_PROVIDER)) { + gps = 1; + } else if (provider.equals(LocationManager.NETWORK_PROVIDER)) { + gps = 0; + } else if (provider.equals("last")) { + gps = -1; + } + + coordsNow = new Geopoint(location.getLatitude(), location.getLongitude()); + app.setLastLoc(coordsNow); + + if (location.hasAltitude() && gps != -1) { + altitudeNow = location.getAltitude() + settings.altCorrection; + } else { + altitudeNow = null; + } + if (location.hasBearing() && gps != -1) { + bearingNow = location.getBearing(); + } else { + bearingNow = 0f; + } + if (location.hasSpeed() && gps != -1) { + speedNow = location.getSpeed(); + } else { + speedNow = 0f; + } + if (location.hasAccuracy() && gps != -1) { + accuracyNow = location.getAccuracy(); + } else { + accuracyNow = 999f; + } + + if (gps == 1) { + // save travelled distance only when location is from GPS + if (coordsBefore != null && coordsNow != null) { + final float dst = coordsBefore.distanceTo(coordsNow); + + if (dst > 0.005) { + distanceNow += dst; + + coordsBefore = coordsNow; + } + } else if (coordsBefore == null) { // values aren't initialized + coordsBefore = coordsNow; + } + } + + if (geoUpdate != null) { + geoUpdate.updateLoc(this); + } + + if (gps > -1) { + (new publishLoc()).start(); + } + } + + private class publishLoc extends Thread { + + private publishLoc() { + setPriority(Thread.MIN_PRIORITY); + } + + @Override + public void run() { + if (g4cRunning) { + return; + } + + if (settings.publicLoc == 1 && (lastGo4cacheCoords == null || coordsNow.distanceTo(lastGo4cacheCoords) > 0.75)) { + g4cRunning = true; + + final String host = "api.go4cache.com"; + final String path = "/"; + final String method = "POST"; + String action = null; + if (app != null) { + action = app.getAction(); + } else { + action = ""; + } + + final String username = settings.getUsername(); + if (username != null) { + final Map<String, String> params = new HashMap<String, String>(); + final String latStr = String.format((Locale) null, "%.6f", coordsNow.getLatitude()); + final String lonStr = String.format((Locale) null, "%.6f", coordsNow.getLongitude()); + params.put("u", username); + params.put("lt", latStr); + params.put("ln", lonStr); + params.put("a", action); + params.put("s", (cgBase.sha1(username + "|" + latStr + "|" + lonStr + "|" + action + "|" + cgBase.md5("carnero: developing your dreams"))).toLowerCase()); + if (base.version != null) { + params.put("v", base.version); + } + final String res = base.request(false, host, path, method, params, false, false, false).getData(); + + if (StringUtils.isNotBlank(res)) { + lastGo4cacheCoords = coordsNow; + } + } + } + + g4cRunning = false; + } + } + + public void lastLoc() { + assign(app.getLastCoords()); + + Location lastGps = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + + if (lastGps != null) { + lastGps.setProvider("last"); + assign(lastGps); + + Log.i(cgSettings.tag, "Using last location from GPS"); + return; + } + + Location lastGsm = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + + if (lastGsm != null) { + lastGsm.setProvider("last"); + assign(lastGsm); + + Log.i(cgSettings.tag, "Using last location from NETWORK"); + return; + } + } +} diff --git a/main/src/cgeo/geocaching/cgHtmlImg.java b/main/src/cgeo/geocaching/cgHtmlImg.java new file mode 100644 index 0000000..303fb0e --- /dev/null +++ b/main/src/cgeo/geocaching/cgHtmlImg.java @@ -0,0 +1,295 @@ +package cgeo.geocaching; + +import org.apache.commons.lang3.StringUtils; +import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; +import org.apache.http.client.HttpClient; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.entity.BufferedHttpEntity; +import org.apache.http.impl.client.DefaultHttpClient; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.text.Html; +import android.util.Log; +import android.view.Display; +import android.view.WindowManager; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Date; + +public class cgHtmlImg implements Html.ImageGetter { + + private Activity activity = null; + private String geocode = null; + private boolean placement = true; + private int reason = 0; + private boolean onlySave = false; + private boolean save = true; + private BitmapFactory.Options bfOptions = new BitmapFactory.Options(); + private Display display = null; + private int maxWidth = 0; + private int maxHeight = 0; + private double ratio = 1.0d; + private int width = 0; + private int height = 0; + + public cgHtmlImg(Activity activityIn, String geocodeIn, boolean placementIn, int reasonIn, boolean onlySaveIn) { + this(activityIn, geocodeIn, placementIn, reasonIn, onlySaveIn, true); + } + + public cgHtmlImg(Activity activityIn, String geocodeIn, boolean placementIn, int reasonIn, boolean onlySaveIn, boolean saveIn) { + activity = activityIn; + geocode = geocodeIn; + placement = placementIn; + reason = reasonIn; + onlySave = onlySaveIn; + save = saveIn; + + bfOptions.inTempStorage = new byte[16 * 1024]; + + display = ((WindowManager) activity.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + maxWidth = display.getWidth() - 25; + maxHeight = display.getHeight() - 25; + } + + @Override + public BitmapDrawable getDrawable(String url) { + Bitmap imagePre = null; + String dirName = null; + String fileName = null; + String fileNameSec = null; + + if (StringUtils.isBlank(url)) { + return null; + } + + final String[] urlParts = url.split("\\."); + String urlExt = null; + if (urlParts.length > 1) { + urlExt = "." + urlParts[(urlParts.length - 1)]; + if (urlExt.length() > 5) { + urlExt = ""; + } + } else { + urlExt = ""; + } + + if (StringUtils.isNotBlank(geocode)) { + dirName = cgSettings.getStorage() + geocode + "/"; + fileName = cgSettings.getStorage() + geocode + "/" + cgBase.md5(url) + urlExt; + fileNameSec = cgSettings.getStorageSec() + geocode + "/" + cgBase.md5(url) + urlExt; + } else { + dirName = cgSettings.getStorage() + "_others/"; + fileName = cgSettings.getStorage() + "_others/" + cgBase.md5(url) + urlExt; + fileNameSec = cgSettings.getStorageSec() + "_others/" + cgBase.md5(url) + urlExt; + } + + File dir = null; + dir = new File(cgSettings.getStorage()); + if (dir.exists() == false) { + dir.mkdirs(); + } + dir = new File(dirName); + if (dir.exists() == false) { + dir.mkdirs(); + } + dir = null; + + // load image from cache + if (onlySave == false) { + try { + final Date now = new Date(); + + final File file = new File(fileName); + if (file.exists()) { + final long imageSize = file.length(); + + // large images will be downscaled on input to save memory + if (imageSize > (6 * 1024 * 1024)) { + bfOptions.inSampleSize = 48; + } else if (imageSize > (4 * 1024 * 1024)) { + bfOptions.inSampleSize = 16; + } else if (imageSize > (2 * 1024 * 1024)) { + bfOptions.inSampleSize = 10; + } else if (imageSize > (1 * 1024 * 1024)) { + bfOptions.inSampleSize = 6; + } else if (imageSize > (0.5 * 1024 * 1024)) { + bfOptions.inSampleSize = 2; + } + + if (reason > 0 || file.lastModified() > (now.getTime() - (24 * 60 * 60 * 1000))) { + imagePre = BitmapFactory.decodeFile(fileName, bfOptions); + } + } + + if (imagePre == null) { + final File fileSec = new File(fileNameSec); + if (fileSec.exists()) { + final long imageSize = fileSec.length(); + + // large images will be downscaled on input to save memory + if (imageSize > (6 * 1024 * 1024)) { + bfOptions.inSampleSize = 48; + } else if (imageSize > (4 * 1024 * 1024)) { + bfOptions.inSampleSize = 16; + } else if (imageSize > (2 * 1024 * 1024)) { + bfOptions.inSampleSize = 10; + } else if (imageSize > (1 * 1024 * 1024)) { + bfOptions.inSampleSize = 6; + } else if (imageSize > (0.5 * 1024 * 1024)) { + bfOptions.inSampleSize = 2; + } + + if (reason > 0 || file.lastModified() > (now.getTime() - (24 * 60 * 60 * 1000))) { + imagePre = BitmapFactory.decodeFile(fileNameSec, bfOptions); + } + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgHtmlImg.getDrawable (reading cache): " + e.toString()); + } + } + + // download image and save it to the cache + if ((imagePre == null && reason == 0) || onlySave) { + Uri uri = null; + HttpClient client = null; + HttpGet getMethod = null; + HttpResponse httpResponse = null; + HttpEntity entity = null; + BufferedHttpEntity bufferedEntity = null; + + try { + // check if uri is absolute or not, if not attach geocaching.com hostname and scheme + uri = Uri.parse(url); + + if (uri.isAbsolute() == false) { + url = "http://www.geocaching.com" + url; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgHtmlImg.getDrawable (parse URL): " + e.toString()); + } + + if (uri != null) { + for (int i = 0; i < 2; i++) { + if (i > 0) { + Log.w(cgSettings.tag, "cgHtmlImg.getDrawable: Failed to download data, retrying. Attempt #" + (i + 1)); + } + + try { + client = new DefaultHttpClient(); + getMethod = new HttpGet(url); + httpResponse = client.execute(getMethod); + entity = httpResponse.getEntity(); + bufferedEntity = new BufferedHttpEntity(entity); + + final long imageSize = bufferedEntity.getContentLength(); + + // large images will be downscaled on input to save memory + if (imageSize > (6 * 1024 * 1024)) { + bfOptions.inSampleSize = 48; + } else if (imageSize > (4 * 1024 * 1024)) { + bfOptions.inSampleSize = 16; + } else if (imageSize > (2 * 1024 * 1024)) { + bfOptions.inSampleSize = 10; + } else if (imageSize > (1 * 1024 * 1024)) { + bfOptions.inSampleSize = 6; + } else if (imageSize > (0.5 * 1024 * 1024)) { + bfOptions.inSampleSize = 2; + } + + if (bufferedEntity != null) { + imagePre = BitmapFactory.decodeStream(bufferedEntity.getContent(), null, bfOptions); + } + if (imagePre != null) { + break; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgHtmlImg.getDrawable (downloading from web): " + e.toString()); + } + } + } + + if (save) { + try { + // save to memory/SD cache + if (bufferedEntity != null) { + final InputStream is = (InputStream) bufferedEntity.getContent(); + final FileOutputStream fos = new FileOutputStream(fileName); + try { + final byte[] buffer = new byte[4096]; + int l; + while ((l = is.read(buffer)) != -1) { + fos.write(buffer, 0, l); + } + fos.flush(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgHtmlImg.getDrawable (saving to cache): " + e.toString()); + } finally { + is.close(); + fos.close(); + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgHtmlImg.getDrawable (saving to cache): " + e.toString()); + } + } + + entity = null; + bufferedEntity = null; + } + + if (onlySave) { + return null; + } + + // get image and return + if (imagePre == null) { + Log.d(cgSettings.tag, "cgHtmlImg.getDrawable: Failed to obtain image"); + + if (placement == false) { + imagePre = BitmapFactory.decodeResource(activity.getResources(), R.drawable.image_no_placement); + } else { + imagePre = BitmapFactory.decodeResource(activity.getResources(), R.drawable.image_not_loaded); + } + } + + final int imgWidth = imagePre.getWidth(); + final int imgHeight = imagePre.getHeight(); + + if (imgWidth > maxWidth || imgHeight > maxHeight) { + if ((maxWidth / imgWidth) > (maxHeight / imgHeight)) { + ratio = (double) maxHeight / (double) imgHeight; + } else { + ratio = (double) maxWidth / (double) imgWidth; + } + + width = (int) Math.ceil(imgWidth * ratio); + height = (int) Math.ceil(imgHeight * ratio); + + try { + imagePre = Bitmap.createScaledBitmap(imagePre, width, height, true); + } catch (Exception e) { + Log.d(cgSettings.tag, "cgHtmlImg.getDrawable: Failed to scale image"); + return null; + } + } else { + width = imgWidth; + height = imgHeight; + } + + final BitmapDrawable image = new BitmapDrawable(imagePre); + image.setBounds(new Rect(0, 0, width, height)); + + return image; + } +} diff --git a/main/src/cgeo/geocaching/cgImage.java b/main/src/cgeo/geocaching/cgImage.java new file mode 100644 index 0000000..b5874d6 --- /dev/null +++ b/main/src/cgeo/geocaching/cgImage.java @@ -0,0 +1,7 @@ +package cgeo.geocaching; + +public class cgImage { + public String url = ""; + public String title = ""; + public String description = ""; +} diff --git a/main/src/cgeo/geocaching/cgList.java b/main/src/cgeo/geocaching/cgList.java new file mode 100644 index 0000000..162af76 --- /dev/null +++ b/main/src/cgeo/geocaching/cgList.java @@ -0,0 +1,21 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +public class cgList { + public boolean def = false; + public int id = 0; + public String title = null; + public Long updated = null; + public Geopoint coords = null; + + public cgList(boolean defIn) { + def = defIn; + } + + public cgList(boolean defIn, int idIn, String titleIn) { + def = defIn; + id = idIn; + title = titleIn; + } +} diff --git a/main/src/cgeo/geocaching/cgLog.java b/main/src/cgeo/geocaching/cgLog.java new file mode 100644 index 0000000..7e81897 --- /dev/null +++ b/main/src/cgeo/geocaching/cgLog.java @@ -0,0 +1,15 @@ +package cgeo.geocaching; + +import java.util.List; + +public class cgLog { + public int id = 0; + public int type = 4; // note + public String author = ""; + public String log = ""; + public long date = 0; + public int found = -1; + public List<cgImage> logImages = null; + public String cacheName = ""; // used for trackables + public String cacheGuid = ""; // used for trackables +} diff --git a/main/src/cgeo/geocaching/cgLogForm.java b/main/src/cgeo/geocaching/cgLogForm.java new file mode 100644 index 0000000..12f894e --- /dev/null +++ b/main/src/cgeo/geocaching/cgLogForm.java @@ -0,0 +1,15 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import java.util.Calendar; + +public class cgLogForm extends AbstractActivity { + public cgLogForm(String helpTopic) { + super(helpTopic); + } + + public void setDate(Calendar dateIn) { + // to be overwritten + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgMapfileListAdapter.java b/main/src/cgeo/geocaching/cgMapfileListAdapter.java new file mode 100644 index 0000000..c0f8f5b --- /dev/null +++ b/main/src/cgeo/geocaching/cgMapfileListAdapter.java @@ -0,0 +1,91 @@ +package cgeo.geocaching; + +import android.app.Activity; +import android.graphics.Typeface; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.ArrayAdapter; +import android.widget.TextView; + +import java.io.File; +import java.util.List; + +public class cgMapfileListAdapter extends ArrayAdapter<File> { + + private cgSelectMapfile parentView; + private LayoutInflater inflater; + private MapfileView holder; + + public cgMapfileListAdapter(cgSelectMapfile parentIn, List<File> listIn) { + super(parentIn, 0, listIn); + + parentView = parentIn; + } + + @Override + public View getView(int position, View rowView, ViewGroup parent) { + if (inflater == null) + inflater = ((Activity) getContext()).getLayoutInflater(); + + if (position > getCount()) { + Log.w(cgSettings.tag, "cgGPXListAdapter.getView: Attempt to access missing item #" + position); + return null; + } + + File file = getItem(position); + + if (rowView == null) { + rowView = (View) inflater.inflate(R.layout.mapfile_item, null); + + holder = new MapfileView(); + holder.filepath = (TextView) rowView.findViewById(R.id.mapfilepath); + holder.filename = (TextView) rowView.findViewById(R.id.mapfilename); + + rowView.setTag(holder); + } else { + holder = (MapfileView) rowView.getTag(); + } + + File current = new File(parentView.getCurrentMapfile()); + + if (file.equals(current)) { + holder.filename.setTypeface(holder.filename.getTypeface(), Typeface.BOLD); + } else { + holder.filename.setTypeface(holder.filename.getTypeface(), Typeface.NORMAL); + } + + final touchListener touchLst = new touchListener(file); + rowView.setOnClickListener(touchLst); + + holder.filepath.setText(file.getParent()); + holder.filename.setText(file.getName()); + + return rowView; + } + + @Override + public void notifyDataSetChanged() { + super.notifyDataSetChanged(); + } + + private class touchListener implements View.OnClickListener { + private File file = null; + + public touchListener(File fileIn) { + file = fileIn; + } + + // tap on item + public void onClick(View view) { + parentView.setMapfile(file.toString()); + parentView.close(); + } + } + + private static class MapfileView { + public TextView filepath; + public TextView filename; + } +} diff --git a/main/src/cgeo/geocaching/cgOAuth.java b/main/src/cgeo/geocaching/cgOAuth.java new file mode 100644 index 0000000..29b82d3 --- /dev/null +++ b/main/src/cgeo/geocaching/cgOAuth.java @@ -0,0 +1,58 @@ +package cgeo.geocaching; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Date; +import java.util.List; +import java.util.Map; + +public class cgOAuth { + public static String signOAuth(String host, String path, String method, boolean https, Map<String, String> params, String token, String tokenSecret) { + String paramsDone = ""; + if (method.equalsIgnoreCase("GET") == false && method.equalsIgnoreCase("POST") == false) { + method = "POST"; + } else { + method = method.toUpperCase(); + } + + if (token == null) + token = ""; + if (tokenSecret == null) + tokenSecret = ""; + + long currentTime = new Date().getTime(); // miliseconds + currentTime = currentTime / 1000; // seconds + currentTime = (long) Math.floor(currentTime); + + params.put("oauth_consumer_key", cgSettings.keyConsumerPublic); + params.put("oauth_nonce", cgBase.md5(Long.toString(System.currentTimeMillis()))); + params.put("oauth_signature_method", "HMAC-SHA1"); + params.put("oauth_timestamp", Long.toString(currentTime)); + params.put("oauth_token", token); + params.put("oauth_version", "1.0"); + + String[] keys = new String[params.keySet().size()]; + params.keySet().toArray(keys); + Arrays.sort(keys); + + List<String> paramsEncoded = new ArrayList<String>(); + for (String key : keys) { + String value = params.get(key); + paramsEncoded.add(key + "=" + cgBase.urlencode_rfc3986(value)); + } + + String keysPacked; + String requestPacked; + + keysPacked = cgSettings.keyConsumerSecret + "&" + tokenSecret; // both even if empty some of them! + if (https) + requestPacked = method + "&" + cgBase.urlencode_rfc3986("https://" + host + path) + "&" + cgBase.urlencode_rfc3986(cgBase.implode("&", paramsEncoded.toArray())); + else + requestPacked = method + "&" + cgBase.urlencode_rfc3986("http://" + host + path) + "&" + cgBase.urlencode_rfc3986(cgBase.implode("&", paramsEncoded.toArray())); + paramsEncoded.add("oauth_signature=" + cgBase.urlencode_rfc3986(cgBase.base64Encode(cgBase.hashHmac(requestPacked, keysPacked)))); + + paramsDone = cgBase.implode("&", paramsEncoded.toArray()); + + return paramsDone; + } +} diff --git a/main/src/cgeo/geocaching/cgRating.java b/main/src/cgeo/geocaching/cgRating.java new file mode 100644 index 0000000..f0c7a76 --- /dev/null +++ b/main/src/cgeo/geocaching/cgRating.java @@ -0,0 +1,7 @@ +package cgeo.geocaching; + +public class cgRating { + public Float rating = null; + public Integer votes = null; + public Float myVote = null; +} diff --git a/main/src/cgeo/geocaching/cgResponse.java b/main/src/cgeo/geocaching/cgResponse.java new file mode 100644 index 0000000..3ac7770 --- /dev/null +++ b/main/src/cgeo/geocaching/cgResponse.java @@ -0,0 +1,40 @@ +package cgeo.geocaching; + +public class cgResponse { + private String url; + private int statusCode; + private String statusMessage; + private String data; + + public void setUrl(String url) { + this.url = url; + } + + public String getUrl() { + return url; + } + + public void setStatusCode(int code) { + statusCode = code; + } + + public int getStatusCode() { + return statusCode; + } + + public void setStatusMessage(String message) { + statusMessage = message; + } + + public String getStatusMessage() { + return statusMessage; + } + + public void setData(String data) { + this.data = data; + } + + public String getData() { + return data; + } +} diff --git a/main/src/cgeo/geocaching/cgSearch.java b/main/src/cgeo/geocaching/cgSearch.java new file mode 100644 index 0000000..75c5572 --- /dev/null +++ b/main/src/cgeo/geocaching/cgSearch.java @@ -0,0 +1,39 @@ +package cgeo.geocaching; + +import java.util.ArrayList; +import java.util.List; +import java.util.UUID; + +public class cgSearch { + private UUID id; + private List<String> geocodes = new ArrayList<String>(); + + public String error = null; + public String url = ""; + public String[] viewstates = null; + public int totalCnt = 0; + + public cgSearch() { + id = UUID.randomUUID(); + } + + public UUID getCurrentId() { + return id; + } + + public List<String> getGeocodes() { + return geocodes; + } + + public int getCount() { + return geocodes.size(); + } + + public void addGeocode(String geocode) { + if (geocodes == null) { + geocodes = new ArrayList<String>(); + } + + geocodes.add(geocode); + } +} diff --git a/main/src/cgeo/geocaching/cgSearchHandler.java b/main/src/cgeo/geocaching/cgSearchHandler.java new file mode 100644 index 0000000..4fc6200 --- /dev/null +++ b/main/src/cgeo/geocaching/cgSearchHandler.java @@ -0,0 +1,105 @@ +package cgeo.geocaching; + +import android.app.Activity; +import android.app.AlertDialog; +import android.content.DialogInterface; +import android.content.res.Resources; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.EditText; +import android.widget.ImageView; + +import java.io.IOException; +import java.io.InputStream; +import java.net.HttpURLConnection; +import java.net.URL; + +public class cgSearchHandler extends Handler { + private Activity activity = null; + private Resources res = null; + private cgSearchThread recaptchaThread = null; + private ImageView imageView = null; + private Bitmap img = null; + + private Handler imgHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + try { + if (img != null && imageView != null) { + imageView.setImageBitmap(img); + } + } catch (Exception e) { + // nothing + } + } + }; + + public cgSearchHandler(Activity activityIn, Resources resIn, cgSearchThread recaptchaThreadIn) { + activity = activityIn; + res = resIn; + recaptchaThread = recaptchaThreadIn; + } + + @Override + public void handleMessage(Message msg) { + try { + if (msg.what == 1) { + final AlertDialog.Builder dlg = new AlertDialog.Builder(activity); + final LayoutInflater inflater = activity.getLayoutInflater(); + final View view = inflater.inflate(R.layout.recaptcha_dialog, null); + + imageView = (ImageView) view.findViewById(R.id.image); + + (new getCaptcha(new URL("http://www.google.com/recaptcha/api/image?c=" + recaptchaThread.getChallenge()))).start(); + + dlg.setTitle(res.getString(R.string.caches_recaptcha_title)); + dlg.setView(view); + dlg.setNeutralButton(res.getString(R.string.caches_recaptcha_continue), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + final String text = ((EditText) view.findViewById(R.id.text)).getText().toString(); + + recaptchaThread.setText(text); + + dialog.cancel(); + } + }); + + dlg.create().show(); + } + } catch (Exception e) { + // nothing + } + } + + private class getCaptcha extends Thread { + private URL uri = null; + + public getCaptcha(URL uriIn) { + uri = uriIn; + } + + @Override + public void run() { + try { + HttpURLConnection connection = (HttpURLConnection) uri.openConnection(); + connection.setDoInput(true); + connection.connect(); + + InputStream is = connection.getInputStream(); + + img = BitmapFactory.decodeStream(is); + + is.close(); + + imgHandler.sendEmptyMessage(0); + } catch (IOException e) { + Log.e(cgSettings.tag, "Failed to download reCAPTCHA image"); + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgSearchThread.java b/main/src/cgeo/geocaching/cgSearchThread.java new file mode 100644 index 0000000..732275c --- /dev/null +++ b/main/src/cgeo/geocaching/cgSearchThread.java @@ -0,0 +1,46 @@ +package cgeo.geocaching; + +import android.os.Handler; +import android.util.Log; + +public class cgSearchThread extends Thread { + private Handler recaptchaHandler = null; + private String recaptchaChallenge = null; + private String recaptchaText = null; + + public void setRecaptchaHandler(Handler recaptchaHandlerIn) { + recaptchaHandler = recaptchaHandlerIn; + } + + public void notifyNeed() { + if (recaptchaHandler != null) { + recaptchaHandler.sendEmptyMessage(1); + } + } + + public synchronized void waitForUser() { + try { + wait(); + } catch (InterruptedException e) { + Log.w(cgSettings.tag, "searchThread is not waiting for user..."); + } + } + + public void setChallenge(String challenge) { + recaptchaChallenge = challenge; + } + + public String getChallenge() { + return recaptchaChallenge; + } + + public synchronized void setText(String text) { + recaptchaText = text; + + notify(); + } + + public synchronized String getText() { + return recaptchaText; + } +} diff --git a/main/src/cgeo/geocaching/cgSelectMapfile.java b/main/src/cgeo/geocaching/cgSelectMapfile.java new file mode 100644 index 0000000..921987c --- /dev/null +++ b/main/src/cgeo/geocaching/cgSelectMapfile.java @@ -0,0 +1,61 @@ +package cgeo.geocaching; + +import cgeo.geocaching.files.FileList; + +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; + +import java.io.File; +import java.util.List; + +public class cgSelectMapfile extends FileList<cgMapfileListAdapter> { + + public cgSelectMapfile() { + super("map"); + } + + String mapFile; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + mapFile = getSettings().getMapFile(); + } + + public void close() { + + Intent intent = new Intent(); + intent.putExtra("mapfile", mapFile); + + setResult(RESULT_OK, intent); + + finish(); + } + + @Override + protected cgMapfileListAdapter getAdapter(List<File> files) { + return new cgMapfileListAdapter(this, files); + } + + @Override + protected String[] getBaseFolders() { + String base = Environment.getExternalStorageDirectory().toString(); + return new String[] { base + "/mfmaps", base + "/Locus/mapsVector" }; + } + + @Override + protected void setTitle() { + setTitle(res.getString(R.string.map_file_select_title)); + } + + public String getCurrentMapfile() { + return mapFile; + } + + public void setMapfile(String newMapfile) { + mapFile = newMapfile; + } + +} diff --git a/main/src/cgeo/geocaching/cgSettings.java b/main/src/cgeo/geocaching/cgSettings.java new file mode 100644 index 0000000..9fd4555 --- /dev/null +++ b/main/src/cgeo/geocaching/cgSettings.java @@ -0,0 +1,610 @@ +package cgeo.geocaching; + +import cgeo.geocaching.googlemaps.googleMapFactory; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapsforge.mfMapFactory; + +import org.apache.commons.lang3.StringUtils; +import org.mapsforge.android.maps.MapDatabase; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.SharedPreferences.Editor; +import android.content.res.Configuration; +import android.os.Environment; + +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; + +/** + * General c:geo preferences/settings set by the user + */ +public class cgSettings { + + private static final String KEY_WEB_DEVICE_CODE = "webDeviceCode"; + private static final String KEY_WEBDEVICE_NAME = "webDeviceName"; + private static final String KEY_MAP_LIVE = "maplive"; + private static final String KEY_MAP_SOURCE = "mapsource"; + private static final String KEY_USE_TWITTER = "twitter"; + private static final String KEY_SHOW_ADDRESS = "showaddress"; + private static final String KEY_SHOW_CAPTCHA = "showcaptcha"; + private static final String KEY_MAP_TRAIL = "maptrail"; + private static final String KEY_LAST_MAP_ZOOM = "mapzoom"; + private static final String KEY_LIVE_LIST = "livelist"; + private static final String KEY_METRIC_UNITS = "units"; + private static final String KEY_SKIN = "skin"; + private static final String KEY_LAST_USED_LIST = "lastlist"; + private static final String KEY_CACHE_TYPE = "cachetype"; + private static final String KEY_INITIALIZED = "initialized"; + private static final String KEY_TWITTER_TOKEN_SECRET = "tokensecret"; + private static final String KEY_TWITTER_TOKEN_PUBLIC = "tokenpublic"; + private static final String KEY_VERSION = "version"; + private static final String KEY_LOAD_DESCRIPTION = "autoloaddesc"; + private static final String KEY_USE_ENGLISH = "useenglish"; + private static final String KEY_AS_BROWSER = "asbrowser"; + private static final String KEY_USE_COMPASS = "usecompass"; + private static final String KEY_AUTO_VISIT_TRACKABLES = "trackautovisit"; + private static final String KEY_AUTO_INSERT_SIGNATURE = "sigautoinsert"; + private static final String KEY_ALTITUDE_CORRECTION = "altcorrection"; + private static final String KEY_USE_GOOGLE_NAVIGATION = "usegnav"; + private static final String KEY_STORE_LOG_IMAGES = "logimages"; + private static final String KEY_EXCLUDE_DISABLED = "excludedisabled"; + private static final String KEY_EXCLUDE_OWN = "excludemine"; + private static final String KEY_MAPFILE = "mfmapfile"; + private static final String KEY_SIGNATURE = "signature"; + private static final String KEY_GCVOTE_PASSWORD = "pass-vote"; + private static final String KEY_PASSWORD = "password"; + private static final String KEY_USERNAME = "username"; + private static final String KEY_COORD_INPUT_FORMAT = "coordinputformat"; + private static final String KEY_LOG_OFFLINE = "log_offline"; + private static final String KEY_LOAD_DIRECTION_IMG = "loaddirectionimg"; + private static final String KEY_GC_CUSTOM_DATE = "gccustomdate"; + + private interface PrefRunnable { + void edit(final Editor edit); + } + + public enum mapSourceEnum { + googleMap, + googleSat, + mapsforgeMapnik, + mapsforgeOsmarender, + mapsforgeCycle, + mapsforgeOffline; + + static mapSourceEnum fromInt(int id) { + mapSourceEnum[] values = mapSourceEnum.values(); + if (id >= 0 && id < values.length) { + return values[id]; + } else { + return googleMap; + } + } + + public boolean isGoogleMapSource() { + if (googleMap == this || googleSat == this) { + return true; + } + + return false; + } + } + + public enum coordInputFormatEnum { + Plain, + Deg, + Min, + Sec; + + static coordInputFormatEnum fromInt(int id) { + coordInputFormatEnum[] values = coordInputFormatEnum.values(); + if (id >= 0 && id < values.length) { + return values[id]; + } else { + return Min; + } + } + } + + // constants + public final static int unitsMetric = 1; + public final static int unitsImperial = 2; + public final static String cache = ".cgeo"; + + // twitter api keys + public final static String keyConsumerPublic = "RFafPiNi3xRhcS1TPE3wTw"; + public final static String keyConsumerSecret = "7iDJprNPI9hzRwWhpzycSr9SPZMFrdVdsxD2OauI9k"; + + // version + public int version = 0; + + // skin + public int skin = 0; + + // settings + public int helper = 0; + public int initialized = 0; + public int autoLoadDesc = 0; + public int units = unitsMetric; + public int livelist = 1; + public int mapzoom = 14; + public int maplive = 1; + public int maptrail = 1; + public boolean useEnglish = false; + public boolean showCaptcha = false; + public int excludeMine = 0; + public int excludeDisabled = 0; + public int storeOfflineMaps = 0; + public boolean storelogimages = false; + public int asBrowser = 1; + public int useCompass = 1; + public int useGNavigation = 1; + public int showAddress = 1; + public int publicLoc = 0; + public int twitter = 0; + public int altCorrection = 0; + public String cacheType = null; + public String tokenPublic = null; + public String tokenSecret = null; + public String webDeviceName = null; + public String webDeviceCode = null; + public boolean trackableAutovisit = false; + public boolean signatureAutoinsert = false; + + // usable values + public static final String tag = "cgeo"; + + /** Name of the preferences file */ + public static final String preferences = "cgeo.pref"; + + // private variables + private Context context = null; + private SharedPreferences prefs = null; + private String username = null; + private String password = null; + private coordInputFormatEnum coordInput = coordInputFormatEnum.Plain; + + // maps + public MapFactory mapFactory = null; + public mapSourceEnum mapSourceUsed = mapSourceEnum.googleMap; + public mapSourceEnum mapSource = mapSourceEnum.googleMap; + private String mapFile = null; + private boolean mapFileValid = false; + + public cgSettings(Context contextIn, SharedPreferences prefsIn) { + context = contextIn; + prefs = prefsIn; + + load(); + } + + public void load() { + version = prefs.getInt(KEY_VERSION, 0); + + initialized = prefs.getInt(KEY_INITIALIZED, 0); + helper = prefs.getInt("helper", 0); + + skin = prefs.getInt(KEY_SKIN, 0); + + autoLoadDesc = prefs.getInt(KEY_LOAD_DESCRIPTION, 0); + units = prefs.getInt(KEY_METRIC_UNITS, 1); + livelist = prefs.getInt(KEY_LIVE_LIST, 1); + maplive = prefs.getInt(KEY_MAP_LIVE, 1); + mapzoom = prefs.getInt(KEY_LAST_MAP_ZOOM, 14); + maptrail = prefs.getInt(KEY_MAP_TRAIL, 1); + useEnglish = prefs.getBoolean(KEY_USE_ENGLISH, false); + showCaptcha = prefs.getBoolean(KEY_SHOW_CAPTCHA, false); + excludeMine = prefs.getInt(KEY_EXCLUDE_OWN, 0); + excludeDisabled = prefs.getInt(KEY_EXCLUDE_DISABLED, 0); + storeOfflineMaps = prefs.getInt("offlinemaps", 1); + storelogimages = prefs.getBoolean(KEY_STORE_LOG_IMAGES, false); + asBrowser = prefs.getInt(KEY_AS_BROWSER, 1); + useCompass = prefs.getInt(KEY_USE_COMPASS, 1); + useGNavigation = prefs.getInt(KEY_USE_GOOGLE_NAVIGATION, 1); + showAddress = prefs.getInt(KEY_SHOW_ADDRESS, 1); + publicLoc = prefs.getInt("publicloc", 0); + twitter = prefs.getInt(KEY_USE_TWITTER, 0); + altCorrection = prefs.getInt(KEY_ALTITUDE_CORRECTION, 0); + cacheType = prefs.getString(KEY_CACHE_TYPE, null); + tokenPublic = prefs.getString(KEY_TWITTER_TOKEN_PUBLIC, null); + tokenSecret = prefs.getString(KEY_TWITTER_TOKEN_SECRET, null); + mapFile = prefs.getString(KEY_MAPFILE, null); + mapFileValid = checkMapfile(mapFile); + mapSource = mapSourceEnum.fromInt(prefs.getInt(KEY_MAP_SOURCE, 0)); + webDeviceName = prefs.getString(KEY_WEBDEVICE_NAME, null); + webDeviceCode = prefs.getString(KEY_WEB_DEVICE_CODE, null); + trackableAutovisit = prefs.getBoolean(KEY_AUTO_VISIT_TRACKABLES, false); + signatureAutoinsert = prefs.getBoolean(KEY_AUTO_INSERT_SIGNATURE, false); + coordInput = coordInputFormatEnum.fromInt(prefs.getInt(KEY_COORD_INPUT_FORMAT, 0)); + + setLanguage(useEnglish); + } + + public void setSkin(int skinIn) { + if (skin == 1) { + skin = 1; + } else { + skin = 0; + } + } + + public void setLanguage(boolean useEnglish) { + Locale locale = Locale.getDefault(); + if (useEnglish) { + locale = new Locale("en"); + } + Configuration config = new Configuration(); + config.locale = locale; + context.getResources().updateConfiguration(config, + context.getResources().getDisplayMetrics()); + } + + public void reloadTwitterTokens() { + tokenPublic = prefs.getString(KEY_TWITTER_TOKEN_PUBLIC, null); + tokenSecret = prefs.getString(KEY_TWITTER_TOKEN_SECRET, null); + } + + public void reloadCacheType() { + cacheType = prefs.getString(KEY_CACHE_TYPE, null); + } + + public static String getStorage() { + return getStorageSpecific(null)[0]; + } + + public static String getStorageSec() { + return getStorageSpecific(null)[1]; + } + + public static String[] getStorageSpecific(Boolean hidden) { + String external = Environment.getExternalStorageDirectory() + "/" + cache + "/"; + String data = Environment.getDataDirectory() + "/data/cgeo.geocaching/" + cache + "/"; + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + return new String[] { external, data }; + } else { + return new String[] { data, external }; + } + } + + public boolean isLogin() { + final String preUsername = prefs.getString(KEY_USERNAME, null); + final String prePassword = prefs.getString(KEY_PASSWORD, null); + + if (StringUtils.isBlank(preUsername) || StringUtils.isBlank(prePassword)) { + return false; + } else { + return true; + } + } + + public Map<String, String> getLogin() { + final Map<String, String> login = new HashMap<String, String>(); + + if (username == null || password == null) { + final String preUsername = prefs.getString(KEY_USERNAME, null); + final String prePassword = prefs.getString(KEY_PASSWORD, null); + + if (initialized == 0 && (preUsername == null || prePassword == null)) { + Intent initIntent = new Intent(context, cgeoinit.class); + context.startActivity(initIntent); + + final SharedPreferences.Editor prefsEdit = prefs.edit(); + prefsEdit.putInt(KEY_INITIALIZED, 1); + prefsEdit.commit(); + + initialized = 1; + + return null; + } else if (initialized == 1 && (preUsername == null || prePassword == null)) { + return null; + } + + login.put(KEY_USERNAME, preUsername); + login.put(KEY_PASSWORD, prePassword); + + username = preUsername; + password = prePassword; + } else { + login.put(KEY_USERNAME, username); + login.put(KEY_PASSWORD, password); + } + + return login; + } + + public String getUsername() { + if (username == null) { + return prefs.getString(KEY_USERNAME, null); + } else { + return username; + } + } + + public boolean setLogin(final String username, final String password) { + this.username = username; + this.password = password; + return editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { + // erase username and password + edit.remove(KEY_USERNAME); + edit.remove(KEY_PASSWORD); + } else { + // save username and password + edit.putString(KEY_USERNAME, username); + edit.putString(KEY_PASSWORD, password); + } + } + }); + } + + public boolean isGCvoteLogin() { + final String preUsername = prefs.getString(KEY_USERNAME, null); + final String prePassword = prefs.getString(KEY_GCVOTE_PASSWORD, null); + + if (StringUtils.isBlank(preUsername) || StringUtils.isBlank(prePassword)) { + return false; + } else { + return true; + } + } + + public boolean setGCvoteLogin(final String password) { + return editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + if (StringUtils.isBlank(password)) { + // erase password + edit.remove(KEY_GCVOTE_PASSWORD); + } else { + // save password + edit.putString(KEY_GCVOTE_PASSWORD, password); + } + } + }); + } + + public Map<String, String> getGCvoteLogin() { + final Map<String, String> login = new HashMap<String, String>(); + + if (username == null || password == null) { + final String preUsername = prefs.getString(KEY_USERNAME, null); + final String prePassword = prefs.getString(KEY_GCVOTE_PASSWORD, null); + + if (preUsername == null || prePassword == null) { + return null; + } + + login.put(KEY_USERNAME, preUsername); + login.put(KEY_PASSWORD, prePassword); + + username = preUsername; + } else { + login.put(KEY_USERNAME, username); + login.put(KEY_PASSWORD, password); + } + + return login; + } + + public boolean setSignature(final String signature) { + return editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + if (StringUtils.isBlank(signature)) { + // erase signature + edit.remove(KEY_SIGNATURE); + } else { + // save signature + edit.putString(KEY_SIGNATURE, signature); + } + } + }); + } + + public String getSignature() { + return prefs.getString(KEY_SIGNATURE, null); + } + + public boolean setAltCorrection(final int altitude) { + altCorrection = altitude; + return editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_ALTITUDE_CORRECTION, altitude); + } + }); + } + + public String setCacheType(final String cacheTypeIn) { + editSettings(new PrefRunnable() { + @Override + public void edit(Editor edit) { + edit.putString(KEY_CACHE_TYPE, cacheTypeIn); + } + }); + + cacheType = cacheTypeIn; + + return cacheType; + } + + public void liveMapEnable() { + if (editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_MAP_LIVE, 1); + } + })) { + maplive = 1; + } + } + + public void liveMapDisable() { + if (editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_MAP_LIVE, 0); + } + })) { + maplive = 0; + } + } + + public int getLastList() { + int listId = prefs.getInt(KEY_LAST_USED_LIST, -1); + + return listId; + } + + public void saveLastList(final int listId) { + editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_LAST_USED_LIST, listId); + } + }); + } + + public void setWebNameCode(final String name, final String code) { + if (editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + + edit.putString(KEY_WEBDEVICE_NAME, name); + edit.putString(KEY_WEB_DEVICE_CODE, code); + } + })) { + webDeviceCode = code; + webDeviceName = name; + } + } + + public MapFactory getMapFactory() { + if (mapSource.isGoogleMapSource()) { + if (!mapSourceUsed.isGoogleMapSource() || mapFactory == null) { + mapFactory = new googleMapFactory(); + mapSourceUsed = mapSource; + } + } else if (!mapSource.isGoogleMapSource()) { + if (mapSourceUsed.isGoogleMapSource() || mapFactory == null) { + mapFactory = new mfMapFactory(); + mapSourceUsed = mapSource; + } + } + + return mapFactory; + } + + public String getMapFile() { + return mapFile; + } + + public boolean setMapFile(final String mapFileIn) { + boolean commitResult = editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putString(KEY_MAPFILE, mapFileIn); + } + }); + + mapFile = mapFileIn; + mapFileValid = checkMapfile(mapFile); + + return commitResult; + } + + public boolean hasValidMapFile() { + return mapFileValid; + } + + private static boolean checkMapfile(String mapFileIn) { + if (null == mapFileIn) { + return false; + } + return MapDatabase.isValidMapFile(mapFileIn); + } + + public coordInputFormatEnum getCoordInputFormat() { + return coordInput; + } + + public boolean setCoordInputFormat(coordInputFormatEnum format) { + coordInput = format; + boolean commitResult = editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putInt(KEY_COORD_INPUT_FORMAT, coordInput.ordinal()); + } + }); + return commitResult; + } + + /** + * edit some settings without knowing how to get the settings editor or how to commit + * + * @param runnable + * @return commit result + */ + private boolean editSettings(final PrefRunnable runnable) { + final SharedPreferences.Editor prefsEdit = prefs.edit(); + runnable.edit(prefsEdit); + return prefsEdit.commit(); + } + + void setLogOffline(final boolean offline) { + editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putBoolean(KEY_LOG_OFFLINE, offline); + } + }); + } + + public boolean getLogOffline() { + return prefs.getBoolean(KEY_LOG_OFFLINE, false); + } + + void setLoadDirImg(final boolean value) { + editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putBoolean(KEY_LOAD_DIRECTION_IMG, value); + } + }); + } + + public boolean getLoadDirImg() { + return prefs.getBoolean(KEY_LOAD_DIRECTION_IMG, true); + } + + void setGcCustomDate(final String format) { + editSettings(new PrefRunnable() { + + @Override + public void edit(Editor edit) { + edit.putString(KEY_GC_CUSTOM_DATE, format); + } + }); + } + + public String getGcCustomDate() { + return prefs.getString(KEY_GC_CUSTOM_DATE, null); + } +} diff --git a/main/src/cgeo/geocaching/cgTrackable.java b/main/src/cgeo/geocaching/cgTrackable.java new file mode 100644 index 0000000..a8e4621 --- /dev/null +++ b/main/src/cgeo/geocaching/cgTrackable.java @@ -0,0 +1,37 @@ +package cgeo.geocaching; + +import android.text.Spannable; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; + +public class cgTrackable { + static final public int SPOTTED_UNSET = 0; + static final public int SPOTTED_CACHE = 1; + static final public int SPOTTED_USER = 2; + static final public int SPOTTED_UNKNOWN = 3; + static final public int SPOTTED_OWNER = 4; + + public int errorRetrieve = 0; + public String error = ""; + public String guid = ""; + public String geocode = ""; + public String iconUrl = ""; + public String name = ""; + public String nameString = null; + public Spannable nameSp = null; + public String type = null; + public Date released = null; + public Float distance = null; + public String origin = null; + public String owner = null; + public String ownerGuid = null; + public String spottedName = null; + public int spottedType = SPOTTED_UNSET; + public String spottedGuid = null; + public String goal = null; + public String details = null; + public String image = null; + public List<cgLog> logs = new ArrayList<cgLog>(); +} diff --git a/main/src/cgeo/geocaching/cgTrackableLog.java b/main/src/cgeo/geocaching/cgTrackableLog.java new file mode 100644 index 0000000..a5bae6d --- /dev/null +++ b/main/src/cgeo/geocaching/cgTrackableLog.java @@ -0,0 +1,9 @@ +package cgeo.geocaching; + +public class cgTrackableLog { + public int ctl = -1; + public int id = -1; + public String trackCode = null; + public String name = null; + public int action = 0; // base.logTrackablesAction - no action +} diff --git a/main/src/cgeo/geocaching/cgUpdateDir.java b/main/src/cgeo/geocaching/cgUpdateDir.java new file mode 100644 index 0000000..dd09a55 --- /dev/null +++ b/main/src/cgeo/geocaching/cgUpdateDir.java @@ -0,0 +1,7 @@ +package cgeo.geocaching; + +public class cgUpdateDir { + public void updateDir(cgDirection dir) { + // to be overriden + } +} diff --git a/main/src/cgeo/geocaching/cgUpdateLoc.java b/main/src/cgeo/geocaching/cgUpdateLoc.java new file mode 100644 index 0000000..a341081 --- /dev/null +++ b/main/src/cgeo/geocaching/cgUpdateLoc.java @@ -0,0 +1,7 @@ +package cgeo.geocaching; + +public class cgUpdateLoc { + public void updateLoc(cgGeo geo) { + // to be overriden + } +} diff --git a/main/src/cgeo/geocaching/cgUser.java b/main/src/cgeo/geocaching/cgUser.java new file mode 100644 index 0000000..2249133 --- /dev/null +++ b/main/src/cgeo/geocaching/cgUser.java @@ -0,0 +1,13 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import java.util.Date; + +public class cgUser { + public Date located = null; + public String username = null; + public Geopoint coords = null; + public String action = null; + public String client = null; +} diff --git a/main/src/cgeo/geocaching/cgWaypoint.java b/main/src/cgeo/geocaching/cgWaypoint.java new file mode 100644 index 0000000..c0dd719 --- /dev/null +++ b/main/src/cgeo/geocaching/cgWaypoint.java @@ -0,0 +1,97 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; + +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.widget.TextView; + +import java.util.List; + +public class cgWaypoint { + public Integer id = 0; + public String geocode = "geocode"; + public String type = "waypoint"; + public String prefix = ""; + public String lookup = ""; + public String name = ""; + public String latlon = ""; + public String latitudeString = ""; + public String longitudeString = ""; + public Geopoint coords = null; + public String note = ""; + + public void setIcon(Resources res, cgBase base, TextView nameView) { + int iconId = R.drawable.waypoint_waypoint; + if (type != null) { + int specialId = res.getIdentifier("waypoint_" + type, "drawable", base.context.getPackageName()); + if (specialId > 0) { + iconId = specialId; + } + } + nameView.setCompoundDrawablesWithIntrinsicBounds((Drawable) res.getDrawable(iconId), null, null, null); + } + + public void merge(final cgWaypoint old) { + if (StringUtils.isBlank(prefix)) { + prefix = old.prefix; + } + if (StringUtils.isBlank(lookup)) { + lookup = old.lookup; + } + if (StringUtils.isBlank(name)) { + name = old.name; + } + if (StringUtils.isBlank(latlon)) { + latlon = old.latlon; + } + if (StringUtils.isBlank(latitudeString)) { + latitudeString = old.latitudeString; + } + if (StringUtils.isBlank(longitudeString)) { + longitudeString = old.longitudeString; + } + if (coords == null) { + coords = old.coords; + } + if (StringUtils.isBlank(note)) { + note = old.note; + } + if (note != null && old.note != null) { + if (old.note.length() > note.length()) { + note = old.note; + } + } + } + + public static void mergeWayPoints(List<cgWaypoint> newPoints, + List<cgWaypoint> oldPoints) { + // copy user modified details of the waypoints + if (newPoints != null && oldPoints != null) { + for (cgWaypoint old : oldPoints) { + boolean merged = false; + if (old != null && old.name != null && old.name.length() > 0) { + for (cgWaypoint waypoint : newPoints) { + if (waypoint != null && waypoint.name != null) { + if (old.name.equalsIgnoreCase(waypoint.name)) { + waypoint.merge(old); + merged = true; + break; + } + } + } + } + // user added waypoints should also be in the new list + if (!merged) { + newPoints.add(old); + } + } + } + } + + public boolean isUserDefined() { + return type != null && type.equalsIgnoreCase("own"); + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/cgeo.java new file mode 100644 index 0000000..3870e83 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeo.java @@ -0,0 +1,756 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.content.pm.ResolveInfo; +import android.content.res.Configuration; +import android.location.Address; +import android.location.Geocoder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.RelativeLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.Map.Entry; + +public class cgeo extends AbstractActivity { + + private static final String SCAN_INTENT = "com.google.zxing.client.android.SCAN"; + private static final int MENU_ABOUT = 0; + private static final int MENU_HELPERS = 1; + private static final int MENU_SETTINGS = 2; + private static final int MENU_HISTORY = 3; + private static final int MENU_SCAN = 4; + private static final int SCAN_REQUEST_CODE = 1; + private static final int MENU_OPEN_LIST = 100; + + private Context context = null; + private Integer version = null; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private TextView navType = null; + private TextView navAccuracy = null; + private TextView navSatellites = null; + private TextView navLocation = null; + private TextView filterTitle = null; + private TextView countBubble = null; + private boolean cleanupRunning = false; + private int countBubbleCnt = 0; + private Geopoint addCoords = null; + private List<Address> addresses = null; + private boolean addressObtaining = false; + private boolean initialized = false; + private Handler countBubbleHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (countBubble == null) { + countBubble = (TextView) findViewById(R.id.offline_count); + } + + if (countBubbleCnt == 0) { + countBubble.setVisibility(View.GONE); + } else { + countBubble.setText(Integer.toString(countBubbleCnt)); + countBubble.bringToFront(); + countBubble.setVisibility(View.VISIBLE); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeo.countBubbleHander: " + e.toString()); + } + } + }; + private Handler obtainAddressHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (CollectionUtils.isNotEmpty(addresses)) { + final Address address = addresses.get(0); + final StringBuilder addText = new StringBuilder(); + + if (address.getCountryName() != null) { + addText.append(address.getCountryName()); + } + if (address.getLocality() != null) { + if (addText.length() > 0) { + addText.append(", "); + } + addText.append(address.getLocality()); + } else if (address.getAdminArea() != null) { + if (addText.length() > 0) { + addText.append(", "); + } + addText.append(address.getAdminArea()); + } + + addCoords = geo.coordsNow; + + if (navLocation == null) { + navLocation = (TextView) findViewById(R.id.nav_location); + } + + navLocation.setText(addText.toString()); + } + } catch (Exception e) { + // nothing + } + + addresses = null; + } + }; + + private Handler firstLoginHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + int reason = msg.what; + + if (reason < 0) { //LoginFailed + showToast(res.getString(R.string.err_login_failed_toast)); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeo.fisrtLoginHander: " + e.toString()); + } + } + }; + + public cgeo() { + super("c:geo-main-screen"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + context = this; + app.setAction(null); + + app.cleanGeo(); + app.cleanDir(); + + setContentView(R.layout.main); + setDefaultKeyMode(DEFAULT_KEYS_SEARCH_LOCAL); // type to search + + try { + PackageManager manager = this.getPackageManager(); + PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0); + + version = info.versionCode; + + Log.i(cgSettings.tag, "Starting " + info.packageName + " " + info.versionCode + " a.k.a " + info.versionName + "..."); + + info = null; + manager = null; + } catch (Exception e) { + Log.i(cgSettings.tag, "No info."); + } + + try { + if (settings.helper == 0) { + RelativeLayout helper = (RelativeLayout) findViewById(R.id.helper); + if (helper != null) { + helper.setVisibility(View.VISIBLE); + helper.setClickable(true); + helper.setOnClickListener(new View.OnClickListener() { + + public void onClick(View view) { + ActivityMixin.goManual(context, "c:geo-intro"); + view.setVisibility(View.GONE); + } + }); + + final SharedPreferences.Editor edit = getSharedPreferences(cgSettings.preferences, 0).edit(); + edit.putInt("helper", 1); + edit.commit(); + } + } + } catch (Exception e) { + // nothing + } + + init(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + init(); + } + + @Override + public void onDestroy() { + initialized = false; + app.showLoginToast = true; + + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + initialized = false; + + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + initialized = false; + + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_SETTINGS, 0, res.getString(R.string.menu_settings)).setIcon(android.R.drawable.ic_menu_preferences); + menu.add(0, MENU_HISTORY, 0, res.getString(R.string.menu_history)).setIcon(android.R.drawable.ic_menu_recent_history); + menu.add(0, MENU_HELPERS, 0, res.getString(R.string.menu_helpers)).setIcon(R.drawable.ic_menu_shopping); + menu.add(0, MENU_SCAN, 0, res.getString(R.string.menu_scan)).setIcon(R.drawable.ic_menu_barcode); + menu.add(0, MENU_ABOUT, 0, res.getString(R.string.menu_about)).setIcon(android.R.drawable.ic_menu_info_details); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + MenuItem item = menu.findItem(MENU_SCAN); + if (item != null) { + item.setEnabled(isIntentAvailable(this, SCAN_INTENT)); + } + return true; + } + + private static boolean isIntentAvailable(Context context, String intent) { + final PackageManager packageManager = context.getPackageManager(); + final List<ResolveInfo> list = packageManager.queryIntentActivities( + new Intent(intent), PackageManager.MATCH_DEFAULT_ONLY); + + return list.size() > 0; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int id = item.getItemId(); + switch (id) { + case MENU_ABOUT: + showAbout(null); + return true; + case MENU_HELPERS: + context.startActivity(new Intent(context, cgeohelpers.class)); + return true; + case MENU_SETTINGS: + context.startActivity(new Intent(context, cgeoinit.class)); + return true; + case MENU_HISTORY: + final Intent cachesIntent = new Intent(context, cgeocaches.class); + cachesIntent.putExtra("type", "history"); + context.startActivity(cachesIntent); + return true; + case MENU_SCAN: + Intent intent = new Intent(SCAN_INTENT); + intent.putExtra("SCAN_MODE", "QR_CODE_MODE"); + startActivityForResult(intent, SCAN_REQUEST_CODE); + return true; + default: + break; + } + + return false; + } + + public void onActivityResult(int requestCode, int resultCode, Intent intent) { + if (requestCode == SCAN_REQUEST_CODE) { + if (resultCode == RESULT_OK) { + String scan = intent.getStringExtra("SCAN_RESULT"); + if (StringUtils.isBlank(scan)) { + return; + } + String host = "http://coord.info/"; + if (scan.toLowerCase().startsWith(host)) { + String geocode = scan.substring(host.length()).trim(); + cgeodetail.startActivity(this, geocode); + } + else { + showToast(res.getString(R.string.unknown_scan)); + } + } else if (resultCode == RESULT_CANCELED) { + // do nothing + } + } + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, ContextMenu.ContextMenuInfo menuInfo) { + super.onCreateContextMenu(menu, v, menuInfo); + + // context menu for offline button + if (v.getId() == R.id.search_offline) { + List<cgList> cacheLists = app.getLists(); + int listCount = cacheLists.size(); + menu.setHeaderTitle(res.getString(R.string.list_title)); + for (int i = 0; i < listCount; i++) { + cgList list = cacheLists.get(i); + menu.add(Menu.NONE, MENU_OPEN_LIST + list.id, Menu.NONE, list.title); + } + return; + } + + // standard context menu + menu.setHeaderTitle(res.getString(R.string.menu_filter)); + + //first add the most used types + menu.add(1, 0, 0, res.getString(R.string.all_types)); + menu.add(1, 1, 0, res.getString(R.string.traditional)); + menu.add(1, 2, 0, res.getString(R.string.multi)); + menu.add(1, 3, 0, res.getString(R.string.mystery)); + + // then add all other cache types sorted alphabetically + Map<String, String> allTypes = new HashMap<String, String>(cgBase.cacheTypesInv); + allTypes.remove("traditional"); + allTypes.remove("multi"); + allTypes.remove("mystery"); + List<String> sorted = new ArrayList<String>(allTypes.values()); + Collections.sort(sorted); + for (String choice : sorted) { + menu.add(1, menu.size(), 0, choice); + } + + // mark current filter as checked + menu.setGroupCheckable(1, true, true); + boolean foundItem = false; + int itemCount = menu.size(); + if (settings.cacheType != null) { + String typeTitle = cgBase.cacheTypesInv.get(settings.cacheType); + if (typeTitle != null) { + for (int i = 0; i < itemCount; i++) { + if (menu.getItem(i).getTitle().equals(typeTitle)) { + menu.getItem(i).setChecked(true); + foundItem = true; + break; + } + } + } + } + if (!foundItem) { + menu.getItem(0).setChecked(true); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int id = item.getItemId(); + + if (id == 0) { + settings.setCacheType(null); + setFilterTitle(); + + return true; + } else if (id > MENU_OPEN_LIST) { + int listId = id - MENU_OPEN_LIST; + settings.saveLastList(listId); + cgeocaches.startActivityOffline(context); + return true; + } else if (id > 0) { + String itemTitle = item.getTitle().toString(); + String choice = null; + for (Entry<String, String> entry : cgBase.cacheTypesInv.entrySet()) { + if (entry.getValue().equalsIgnoreCase(itemTitle)) { + choice = entry.getKey(); + break; + } + } + if (choice == null) { + settings.setCacheType(null); + } else { + settings.setCacheType(choice); + } + setFilterTitle(); + + return true; + } + + return false; + } + + private void setFilterTitle() { + if (filterTitle == null) { + filterTitle = (TextView) findViewById(R.id.filter_button_title); + } + if (settings.cacheType != null) { + filterTitle.setText(cgBase.cacheTypesInv.get(settings.cacheType)); + } else { + filterTitle.setText(res.getString(R.string.all)); + } + } + + private void init() { + if (initialized) { + return; + } + + initialized = true; + + settings.getLogin(); + settings.reloadCacheType(); + + if (app.firstRun) { + (new firstLogin()).start(); + } + + (new countBubbleUpdate()).start(); + (new cleanDatabase()).start(); + + if (settings.cacheType != null && cgBase.cacheTypesInv.containsKey(settings.cacheType) == false) { + settings.setCacheType(null); + } + + if (geo == null) { + geo = app.startGeo(context, geoUpdate, base, settings, 0, 0); + } + + navType = (TextView) findViewById(R.id.nav_type); + navAccuracy = (TextView) findViewById(R.id.nav_accuracy); + navLocation = (TextView) findViewById(R.id.nav_location); + + final View findOnMap = findViewById(R.id.map); + findOnMap.setClickable(true); + findOnMap.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cgeoFindOnMap(v); + } + }); + + final View findByOffline = findViewById(R.id.search_offline); + findByOffline.setClickable(true); + findByOffline.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cgeoFindByOffline(v); + } + }); + registerForContextMenu(findByOffline); + + (new countBubbleUpdate()).start(); + + final View advanced = findViewById(R.id.advanced_button); + advanced.setClickable(true); + advanced.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cgeoSearch(v); + } + }); + + final View any = findViewById(R.id.any_button); + any.setClickable(true); + any.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cgeoPoint(v); + } + }); + + final View filter = findViewById(R.id.filter_button); + filter.setClickable(true); + registerForContextMenu(filter); + filter.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + openContextMenu(v); + } + }); + + setFilterTitle(); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + if (navType == null || navLocation == null || navAccuracy == null) { + navType = (TextView) findViewById(R.id.nav_type); + navAccuracy = (TextView) findViewById(R.id.nav_accuracy); + navSatellites = (TextView) findViewById(R.id.nav_satellites); + navLocation = (TextView) findViewById(R.id.nav_location); + } + + if (geo.coordsNow != null) { + View findNearest = findViewById(R.id.nearest); + findNearest.setClickable(true); + findNearest.setOnClickListener(new OnClickListener() { + public void onClick(View v) { + cgeoFindNearest(v); + } + }); + findNearest.setBackgroundResource(R.drawable.main_nearby); + + String satellites = null; + if (geo.satellitesVisible != null && geo.satellitesFixed != null && geo.satellitesFixed > 0) { + satellites = res.getString(R.string.loc_sat) + ": " + geo.satellitesFixed + "/" + geo.satellitesVisible; + } else if (geo.satellitesVisible != null) { + satellites = res.getString(R.string.loc_sat) + ": 0/" + geo.satellitesVisible; + } else { + satellites = ""; + } + navSatellites.setText(satellites); + + if (geo.gps == -1) { + navType.setText(res.getString(R.string.loc_last)); + } else if (geo.gps == 0) { + navType.setText(res.getString(R.string.loc_net)); + } else { + navType.setText(res.getString(R.string.loc_gps)); + } + + if (geo.accuracyNow != null) { + if (settings.units == cgSettings.unitsImperial) { + navAccuracy.setText("±" + String.format(Locale.getDefault(), "%.0f", (geo.accuracyNow * 3.2808399)) + " ft"); + } else { + navAccuracy.setText("±" + String.format(Locale.getDefault(), "%.0f", geo.accuracyNow) + " m"); + } + } else { + navAccuracy.setText(null); + } + + if (settings.showAddress == 1) { + if (addCoords == null) { + navLocation.setText(res.getString(R.string.loc_no_addr)); + } + if (addCoords == null || (geo.coordsNow.distanceTo(addCoords) > 0.5 && addressObtaining == false)) { + (new obtainAddress()).start(); + } + } else { + if (geo.altitudeNow != null) { + String humanAlt; + if (settings.units == cgSettings.unitsImperial) { + humanAlt = String.format("%.0f", (geo.altitudeNow * 3.2808399)) + " ft"; + } else { + humanAlt = String.format("%.0f", geo.altitudeNow) + " m"; + } + navLocation.setText(cgBase.formatCoords(geo.coordsNow, true) + " | " + humanAlt); + } else { + navLocation.setText(cgBase.formatCoords(geo.coordsNow, true)); + } + } + } else { + View findNearest = findViewById(R.id.nearest); + findNearest.setFocusable(false); + findNearest.setClickable(false); + findNearest.setOnClickListener(null); + findNearest.setBackgroundResource(R.drawable.main_nearby_disabled); + + navType.setText(null); + navAccuracy.setText(null); + navLocation.setText(res.getString(R.string.loc_trying)); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + public void cgeoFindOnMap(View v) { + findViewById(R.id.map).setPressed(true); + context.startActivity(new Intent(context, settings.getMapFactory().getMapClass())); + } + + public void cgeoFindNearest(View v) { + if (geo == null) { + return; + } + + findViewById(R.id.nearest).setPressed(true); + final Intent cachesIntent = new Intent(context, cgeocaches.class); + cachesIntent.putExtra("type", "nearest"); + cachesIntent.putExtra("latitude", geo.coordsNow.getLatitude()); + cachesIntent.putExtra("longitude", geo.coordsNow.getLongitude()); + cachesIntent.putExtra("cachetype", settings.cacheType); + context.startActivity(cachesIntent); + } + + public void cgeoFindByOffline(View v) { + findViewById(R.id.search_offline).setPressed(true); + final Intent cachesIntent = new Intent(context, cgeocaches.class); + cachesIntent.putExtra("type", "offline"); + context.startActivity(cachesIntent); + } + + public void cgeoSearch(View v) { + findViewById(R.id.advanced_button).setPressed(true); + context.startActivity(new Intent(context, cgeoadvsearch.class)); + } + + public void cgeoPoint(View v) { + findViewById(R.id.any_button).setPressed(true); + context.startActivity(new Intent(context, cgeopoint.class)); + } + + public void cgeoFilter(View v) { + findViewById(R.id.filter_button).setPressed(true); + findViewById(R.id.filter_button).performClick(); + } + + private class countBubbleUpdate extends Thread { + + @Override + public void run() { + if (app == null) { + return; + } + + int checks = 0; + while (app.storageStatus() == false) { + try { + wait(500); + checks++; + } catch (Exception e) { + // nothing; + } + + if (checks > 10) { + return; + } + } + + countBubbleCnt = app.getAllStoredCachesCount(true, null, null); + + countBubbleHandler.sendEmptyMessage(0); + } + } + + private class cleanDatabase extends Thread { + + @Override + public void run() { + if (app == null) { + return; + } + if (cleanupRunning) { + return; + } + + boolean more = false; + if (version != settings.version) { + Log.i(cgSettings.tag, "Initializing hard cleanup - version changed from " + settings.version + " to " + version + "."); + + more = true; + } + + cleanupRunning = true; + app.cleanDatabase(more); + cleanupRunning = false; + + if (version != null && version > 0) { + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("version", version); + edit.commit(); + } + } + } + + private class firstLogin extends Thread { + + @Override + public void run() { + if (app == null) { + return; + } + + int status = base.login(); + + if (status == 1) { + app.firstRun = false; + } + + if (app.showLoginToast) { + firstLoginHandler.sendEmptyMessage(status); + app.showLoginToast = false; + } + } + } + + private class obtainAddress extends Thread { + + public obtainAddress() { + setPriority(Thread.MIN_PRIORITY); + } + + @Override + public void run() { + if (geo == null) { + return; + } + if (addressObtaining) { + return; + } + addressObtaining = true; + + try { + Geocoder geocoder = new Geocoder(context, Locale.getDefault()); + + addresses = geocoder.getFromLocation(geo.coordsNow.getLatitude(), geo.coordsNow.getLongitude(), 1); + } catch (Exception e) { + Log.i(cgSettings.tag, "Failed to obtain address"); + } + + obtainAddressHandler.sendEmptyMessage(0); + + addressObtaining = false; + } + } + + public void showAbout(View view) { + context.startActivity(new Intent(context, cgeoabout.class)); + } + + public void goSearch(View view) { + onSearchRequested(); + } +} diff --git a/main/src/cgeo/geocaching/cgeoabout.java b/main/src/cgeo/geocaching/cgeoabout.java new file mode 100644 index 0000000..1c09614 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoabout.java @@ -0,0 +1,90 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import android.content.Intent; +import android.content.pm.PackageInfo; +import android.content.pm.PackageManager; +import android.net.Uri; +import android.os.Bundle; +import android.text.method.LinkMovementMethod; +import android.util.Log; +import android.view.View; +import android.widget.TextView; + +public class cgeoabout extends AbstractActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.about); + setTitle(res.getString(R.string.about)); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + private void init() { + try { + PackageManager manager = this.getPackageManager(); + PackageInfo info = manager.getPackageInfo(this.getPackageName(), 0); + + setTitle(res.getString(R.string.about) + " (ver. " + info.versionName + ")"); + + manager = null; + + ((TextView) findViewById(R.id.contributors)).setMovementMethod(LinkMovementMethod.getInstance()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoabout.init: Failed to obtain package version."); + } + } + + public void donateMore(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FMLNN8GXZKJEE"))); + //activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=N2FKGNCPPRUVE"))); + //activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2Z69QWLRCBE9N&lc=US&item_name=c%3ageo¤cy_code=EUR&amount=15&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"))); + } + + public void donateLess(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=FMLNN8GXZKJEE"))); + //activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=4PRD9CX4Y8XR6"))); + //activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.paypal.com/cgi-bin/webscr?cmd=_donations&business=2Z69QWLRCBE9N&lc=US&item_name=c%3ageo¤cy_code=EUR&amount=7&bn=PP%2dDonationsBF%3abtn_donateCC_LG%2egif%3aNonHosted"))); + } + + public void author(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://carnero.cc/"))); + } + + public void support(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("mailto:support@cgeo.org"))); + } + + public void website(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.cgeo.org/"))); + } + + public void facebook(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.facebook.com/pages/cgeo/297269860090"))); + } + + public void twitter(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://twitter.com/android_gc"))); + } + + public void nutshellmanual(View view) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.cgeo.org/"))); + } +} diff --git a/main/src/cgeo/geocaching/cgeoaddresses.java b/main/src/cgeo/geocaching/cgeoaddresses.java new file mode 100644 index 0000000..0261349 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoaddresses.java @@ -0,0 +1,178 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.location.Address; +import android.location.Geocoder; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class cgeoaddresses extends AbstractActivity { + private final List<Address> addresses = new ArrayList<Address>(); + private String keyword = null; + private LayoutInflater inflater = null; + private LinearLayout addList = null; + private ProgressDialog waitDialog = null; + private Handler loadPlacesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (addList == null) { + addList = (LinearLayout) findViewById(R.id.address_list); + } + + if (addresses.isEmpty()) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + showToast(res.getString(R.string.err_search_address_no_match)); + + finish(); + return; + } else { + LinearLayout oneAddPre = null; + for (Address address : addresses) { + oneAddPre = (LinearLayout) inflater.inflate(R.layout.address_button, null); + + Button oneAdd = (Button) oneAddPre.findViewById(R.id.button); + int index = 0; + StringBuilder allAdd = new StringBuilder(); + StringBuilder allAddLine = new StringBuilder(); + + while (address.getAddressLine(index) != null) { + if (allAdd.length() > 0) { + allAdd.append('\n'); + } + if (allAddLine.length() > 0) { + allAddLine.append("; "); + } + + allAdd.append(address.getAddressLine(index)); + allAddLine.append(address.getAddressLine(index)); + + index++; + } + + oneAdd.setText(allAdd.toString()); + oneAdd.setLines(allAdd.toString().split("\n").length); + oneAdd.setClickable(true); + oneAdd.setOnClickListener(new buttonListener(address.getLatitude(), address.getLongitude(), allAddLine.toString())); + addList.addView(oneAddPre); + } + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + Log.e(cgSettings.tag, "cgeoaddresses.loadCachesHandler: " + e.toString()); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + inflater = getLayoutInflater(); + + setTheme(); + setContentView(R.layout.addresses); + setTitle(res.getString(R.string.search_address_result)); + + // get parameters + Bundle extras = getIntent().getExtras(); + + // try to get data from extras + if (extras != null) { + keyword = extras.getString("keyword"); + } + + if (keyword == null) { + showToast(res.getString(R.string.err_search_address_forgot)); + finish(); + return; + } + + waitDialog = ProgressDialog.show(this, res.getString(R.string.search_address_started), keyword, true); + waitDialog.setCancelable(true); + + (new loadPlaces()).start(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onDestroy() { + super.onDestroy(); + } + + private class loadPlaces extends Thread { + + @Override + public void run() { + Geocoder geocoder = new Geocoder(cgeoaddresses.this, Locale.getDefault()); + try { + List<Address> knownLocations = geocoder.getFromLocationName(keyword, 20); + + addresses.clear(); + for (Address address : knownLocations) { + addresses.add(address); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoaddresses.loadPlaces.run: " + e.toString()); + } + + loadPlacesHandler.sendMessage(new Message()); + } + } + + private class buttonListener implements View.OnClickListener { + + private Double latitude = null; + private Double longitude = null; + private String address = null; + + public buttonListener(Double latitudeIn, Double longitudeIn, String addressIn) { + latitude = latitudeIn; + longitude = longitudeIn; + address = addressIn; + } + + public void onClick(View arg0) { + Intent addressIntent = new Intent(cgeoaddresses.this, cgeocaches.class); + addressIntent.putExtra("type", "address"); + addressIntent.putExtra("latitude", (Double) latitude); + addressIntent.putExtra("longitude", (Double) longitude); + addressIntent.putExtra("address", (String) address); + addressIntent.putExtra("cachetype", settings.cacheType); + startActivity(addressIntent); + + finish(); + return; + } + } +} diff --git a/main/src/cgeo/geocaching/cgeoadvsearch.java b/main/src/cgeo/geocaching/cgeoadvsearch.java new file mode 100644 index 0000000..9e70a3a --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoadvsearch.java @@ -0,0 +1,499 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.GeopointParser; + +import org.apache.commons.lang3.StringUtils; + +import android.app.SearchManager; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.view.KeyEvent; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.view.View.OnClickListener; +import android.view.inputmethod.EditorInfo; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; +import android.widget.TextView; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class cgeoadvsearch extends AbstractActivity { + + private static final int MENU_SEARCH_OWN_CACHES = 1; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private EditText latEdit = null; + private EditText lonEdit = null; + private String[] geocodesInCache = null; + + public cgeoadvsearch() { + super("c:geo-search"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + app.setAction(null); + + // search query + Intent intent = getIntent(); + if (Intent.ACTION_SEARCH.equals(intent.getAction())) { + final String query = intent.getStringExtra(SearchManager.QUERY); + final boolean found = instantSearch(query); + + if (found) { + finish(); + + return; + } + } + + setTheme(); + setContentView(R.layout.search); + setTitle(res.getString(R.string.search)); + + init(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + init(); + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + private boolean instantSearch(String query) { + boolean found = false; + + final Pattern gcCode = Pattern.compile("^GC[0-9A-Z]+$", Pattern.CASE_INSENSITIVE); + final Pattern tbCode = Pattern.compile("^TB[0-9A-Z]+$", Pattern.CASE_INSENSITIVE); + final Matcher gcCodeM = gcCode.matcher(query); + final Matcher tbCodeM = tbCode.matcher(query); + + try { + if (gcCodeM.find()) { // GC-code + final Intent cachesIntent = new Intent(this, cgeodetail.class); + cachesIntent.putExtra("geocode", query.trim().toUpperCase()); + startActivity(cachesIntent); + + found = true; + } else if (tbCodeM.find()) { // TB-code + final Intent trackablesIntent = new Intent(this, cgeotrackable.class); + trackablesIntent.putExtra("geocode", query.trim().toUpperCase()); + startActivity(trackablesIntent); + + found = true; + } else { // keyword (fallback) + final Intent cachesIntent = new Intent(this, cgeocaches.class); + cachesIntent.putExtra("type", "keyword"); + cachesIntent.putExtra("keyword", query); + cachesIntent.putExtra("cachetype", settings.cacheType); + startActivity(cachesIntent); + + found = true; + } + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeoadvsearch.instantSearch: " + e.toString()); + } + + return found; + } + + private void init() { + settings.getLogin(); + settings.reloadCacheType(); + + if (settings.cacheType != null && cgBase.cacheTypesInv.containsKey(settings.cacheType) == false) { + settings.setCacheType(null); + } + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + ((Button) findViewById(R.id.buttonLatitude)).setOnClickListener(new findByCoordsAction()); + ((Button) findViewById(R.id.buttonLongitude)).setOnClickListener(new findByCoordsAction()); + + final Button findByCoords = (Button) findViewById(R.id.search_coordinates); + findByCoords.setOnClickListener(new findByCoordsListener()); + + ((EditText) findViewById(R.id.address)).setOnEditorActionListener(new findByAddressAction()); + + final Button findByAddress = (Button) findViewById(R.id.search_address); + findByAddress.setOnClickListener(new findByAddressListener()); + + final AutoCompleteTextView geocodeEdit = (AutoCompleteTextView) findViewById(R.id.geocode); + geocodeEdit.setOnEditorActionListener(new findByGeocodeAction()); + geocodesInCache = app.geocodesInCache(); + if (geocodesInCache != null) { + final ArrayAdapter<String> geocodesAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, geocodesInCache); + geocodeEdit.setAdapter(geocodesAdapter); + } + + final Button displayByGeocode = (Button) findViewById(R.id.display_geocode); + displayByGeocode.setOnClickListener(new findByGeocodeListener()); + + ((EditText) findViewById(R.id.keyword)).setOnEditorActionListener(new findByKeywordAction()); + + final Button findByKeyword = (Button) findViewById(R.id.search_keyword); + findByKeyword.setOnClickListener(new findByKeywordListener()); + + ((EditText) findViewById(R.id.username)).setOnEditorActionListener(new findByUsernameAction()); + + final Button findByUserName = (Button) findViewById(R.id.search_username); + findByUserName.setOnClickListener(new findByUsernameListener()); + + ((EditText) findViewById(R.id.owner)).setOnEditorActionListener(new findByOwnerAction()); + + final Button findByOwner = (Button) findViewById(R.id.search_owner); + findByOwner.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View arg0) { + findByOwnerFn(); + } + }); + + EditText trackable = (EditText) findViewById(R.id.trackable); + trackable.setOnEditorActionListener(new findTrackableAction()); + + final Button displayTrackable = (Button) findViewById(R.id.display_trackable); + displayTrackable.setOnClickListener(new findTrackableListener()); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + if (latEdit == null) { + latEdit = (EditText) findViewById(R.id.latitude); + } + if (lonEdit == null) { + lonEdit = (EditText) findViewById(R.id.longitude); + } + + if (geo.coordsNow != null) { + latEdit.setHint(cgBase.formatLatitude(geo.coordsNow.getLatitude(), false)); + lonEdit.setHint(cgBase.formatLongitude(geo.coordsNow.getLongitude(), false)); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class findByCoordsAction implements OnClickListener { + + @Override + public void onClick(View arg0) { + cgeocoords coordsDialog = new cgeocoords(cgeoadvsearch.this, settings, null, geo); + coordsDialog.setCancelable(true); + coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + @Override + public void update(Geopoint gp) { + ((Button) findViewById(R.id.buttonLatitude)).setText(cgBase.formatLatitude(gp.getLatitude(), true)); + ((Button) findViewById(R.id.buttonLongitude)).setText(cgBase.formatLongitude(gp.getLongitude(), true)); + } + }); + coordsDialog.show(); + } + } + + private class findByCoordsListener implements View.OnClickListener { + + public void onClick(View arg0) { + findByCoordsFn(); + } + } + + private void findByCoordsFn() { + final Button latView = (Button) findViewById(R.id.buttonLatitude); + final Button lonView = (Button) findViewById(R.id.buttonLongitude); + final String latText = latView.getText().toString(); + final String lonText = lonView.getText().toString(); + + if (StringUtils.isEmpty(latText) || StringUtils.isEmpty(lonText)) { + if (geo.coordsNow != null) { + latView.setText(cgBase.formatLatitude(geo.coordsNow.getLatitude(), true)); + lonView.setText(cgBase.formatLongitude(geo.coordsNow.getLongitude(), true)); + } + } else { + try { + final Intent cachesIntent = new Intent(this, cgeocaches.class); + cachesIntent.putExtra("latitude", GeopointParser.parseLatitude(latText)); + cachesIntent.putExtra("longitude", GeopointParser.parseLongitude(lonText)); + cachesIntent.putExtra("type", "coordinate"); + cachesIntent.putExtra("cachetype", settings.cacheType); + startActivity(cachesIntent); + } catch (GeopointParser.ParseException e) { + showToast(res.getString(e.resource)); + } + } + } + + private class findByKeywordAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findByKeywordFn(); + return true; + } + + return false; + } + } + + private class findByKeywordListener implements View.OnClickListener { + + public void onClick(View arg0) { + findByKeywordFn(); + } + } + + private void findByKeywordFn() { + // find caches by coordinates + String keyText = ((EditText) findViewById(R.id.keyword)).getText().toString(); + + if (StringUtils.isBlank(keyText)) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_keyword)); + return; + } + + final Intent cachesIntent = new Intent(this, cgeocaches.class); + cachesIntent.putExtra("type", "keyword"); + cachesIntent.putExtra("keyword", keyText); + cachesIntent.putExtra("cachetype", settings.cacheType); + startActivity(cachesIntent); + } + + private class findByAddressAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findByAddressFn(); + return true; + } + + return false; + } + } + + private class findByAddressListener implements View.OnClickListener { + + public void onClick(View arg0) { + findByAddressFn(); + } + } + + private void findByAddressFn() { + final String addText = ((EditText) findViewById(R.id.address)).getText().toString(); + + if (StringUtils.isBlank(addText)) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_address)); + return; + } + + final Intent addressesIntent = new Intent(this, cgeoaddresses.class); + addressesIntent.putExtra("keyword", addText); + startActivity(addressesIntent); + } + + private class findByUsernameAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findByUsernameFn(); + return true; + } + + return false; + } + } + + private class findByUsernameListener implements View.OnClickListener { + + public void onClick(View arg0) { + findByUsernameFn(); + } + } + + public void findByUsernameFn() { + final String usernameText = ((EditText) findViewById(R.id.username)).getText().toString(); + + if (StringUtils.isBlank(usernameText)) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user)); + return; + } + + final Intent cachesIntent = new Intent(this, cgeocaches.class); + cachesIntent.putExtra("type", "username"); + cachesIntent.putExtra("username", usernameText); + cachesIntent.putExtra("cachetype", settings.cacheType); + startActivity(cachesIntent); + } + + private class findByOwnerAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findByOwnerFn(); + return true; + } + + return false; + } + } + + private void findByOwnerFn() { + findByOwnerFn(((EditText) findViewById(R.id.owner)).getText().toString()); + } + + private void findByOwnerFn(String userName) { + final String usernameText = StringUtils.trimToEmpty(userName); + + if (StringUtils.isBlank(usernameText)) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_user)); + return; + } + + final Intent cachesIntent = new Intent(this, cgeocaches.class); + cachesIntent.putExtra("type", "owner"); + cachesIntent.putExtra("username", usernameText); + cachesIntent.putExtra("cachetype", settings.cacheType); + startActivity(cachesIntent); + } + + private class findByGeocodeAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findByGeocodeFn(); + return true; + } + + return false; + } + } + + private class findByGeocodeListener implements View.OnClickListener { + + public void onClick(View arg0) { + findByGeocodeFn(); + } + } + + private void findByGeocodeFn() { + final String geocodeText = ((EditText) findViewById(R.id.geocode)).getText().toString(); + + if (StringUtils.isBlank(geocodeText) || geocodeText.equalsIgnoreCase("GC")) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_gccode)); + return; + } + + cgeodetail.startActivity(this, geocodeText); + } + + private class findTrackableAction implements TextView.OnEditorActionListener { + + @Override + public boolean onEditorAction(TextView view, int action, KeyEvent event) { + if (action == EditorInfo.IME_ACTION_GO) { + findTrackableFn(); + return true; + } + + return false; + } + } + + private class findTrackableListener implements View.OnClickListener { + + public void onClick(View arg0) { + findTrackableFn(); + } + } + + private void findTrackableFn() { + final String trackableText = ((EditText) findViewById(R.id.trackable)).getText().toString(); + + if (StringUtils.isBlank(trackableText) || trackableText.equalsIgnoreCase("TB")) { + helpDialog(res.getString(R.string.warn_search_help_title), res.getString(R.string.warn_search_help_tb)); + return; + } + + final Intent trackablesIntent = new Intent(this, cgeotrackable.class); + trackablesIntent.putExtra("geocode", trackableText.toUpperCase()); + startActivity(trackablesIntent); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_SEARCH_OWN_CACHES, 0, res.getString(R.string.search_own_caches)).setIcon(android.R.drawable.ic_menu_myplaces); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == MENU_SEARCH_OWN_CACHES) { + findByOwnerFn(settings.getUsername()); + return true; + } + return super.onOptionsItemSelected(item); + } +} diff --git a/main/src/cgeo/geocaching/cgeoapplication.java b/main/src/cgeo/geocaching/cgeoapplication.java new file mode 100644 index 0000000..d7571b2 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoapplication.java @@ -0,0 +1,842 @@ +package cgeo.geocaching; + +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Application; +import android.content.Context; +import android.util.Log; + +import java.io.File; +import java.util.ArrayList; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +public class cgeoapplication extends Application { + + private cgData storage = null; + private String action = null; + private Geopoint lastCoords = null; + private cgGeo geo = null; + private boolean geoInUse = false; + private cgDirection dir = null; + private boolean dirInUse = false; + final private Map<UUID, cgSearch> searches = new HashMap<UUID, cgSearch>(); // information about searches + final private Map<String, cgCache> cachesCache = new HashMap<String, cgCache>(); // caching caches into memory + public boolean firstRun = true; // c:geo is just launched + public boolean showLoginToast = true; //login toast shown just once. + public boolean warnedLanguage = false; // user was warned about different language settings on geocaching.com + private boolean databaseCleaned = false; // database was cleaned + + public cgeoapplication() { + if (storage == null) { + storage = new cgData(this); + } + } + + @Override + public void onLowMemory() { + Log.i(cgSettings.tag, "Cleaning applications cache."); + + cachesCache.clear(); + } + + @Override + public void onTerminate() { + Log.d(cgSettings.tag, "Terminating c:geo..."); + + if (geo != null) { + geo.closeGeo(); + geo = null; + } + + if (dir != null) { + dir.closeDir(); + dir = null; + } + + if (storage != null) { + storage.clean(); + storage.closeDb(); + storage = null; + } + + super.onTerminate(); + } + + public String backupDatabase() { + return storage.backupDatabase(); + } + + public static File isRestoreFile() { + return cgData.isRestoreFile(); + } + + public boolean restoreDatabase() { + return storage.restoreDatabase(); + } + + public void cleanGeo() { + if (geo != null) { + geo.closeGeo(); + geo = null; + } + } + + public void cleanDir() { + if (dir != null) { + dir.closeDir(); + dir = null; + } + } + + public boolean storageStatus() { + return storage.status(); + } + + public cgGeo startGeo(Context context, cgUpdateLoc geoUpdate, cgBase base, cgSettings settings, int time, int distance) { + if (geo == null) { + geo = new cgGeo(context, this, geoUpdate, base, settings, time, distance); + geo.initGeo(); + + Log.i(cgSettings.tag, "Location service started"); + } + + geo.replaceUpdate(geoUpdate); + geoInUse = true; + + return geo; + } + + public cgGeo removeGeo() { + if (geo != null) { + geo.replaceUpdate(null); + } + geoInUse = false; + + (new removeGeoThread()).start(); + + return null; + } + + private class removeGeoThread extends Thread { + + @Override + public void run() { + try { + sleep(2500); + } catch (Exception e) { + // nothing + } + + if (geoInUse == false && geo != null) { + geo.closeGeo(); + geo = null; + + Log.i(cgSettings.tag, "Location service stopped"); + } + } + } + + public cgDirection startDir(Context context, cgUpdateDir dirUpdate) { + if (dir == null) { + dir = new cgDirection(context, dirUpdate); + dir.initDir(); + + Log.i(cgSettings.tag, "Direction service started"); + } + + dir.replaceUpdate(dirUpdate); + dirInUse = true; + + return dir; + } + + public cgDirection removeDir() { + if (dir != null) { + dir.replaceUpdate(null); + } + dirInUse = false; + + (new removeDirThread()).start(); + + return null; + } + + private class removeDirThread extends Thread { + + @Override + public void run() { + try { + sleep(2500); + } catch (Exception e) { + // nothing + } + + if (dirInUse == false && dir != null) { + dir.closeDir(); + dir = null; + + Log.i(cgSettings.tag, "Direction service stopped"); + } + } + } + + public void cleanDatabase(boolean more) { + if (databaseCleaned) { + return; + } + + if (storage == null) { + storage = new cgData(this); + } + storage.clean(more); + databaseCleaned = true; + } + + public Boolean isThere(String geocode, String guid, boolean detailed, boolean checkTime) { + if (storage == null) { + storage = new cgData(this); + } + return storage.isThere(geocode, guid, detailed, checkTime); + } + + public Boolean isOffline(String geocode, String guid) { + if (storage == null) { + storage = new cgData(this); + } + return storage.isOffline(geocode, guid); + } + + public String getGeocode(String guid) { + if (storage == null) { + storage = new cgData(this); + } + return storage.getGeocodeForGuid(guid); + } + + public String getCacheid(String geocode) { + if (storage == null) { + storage = new cgData(this); + } + return storage.getCacheidForGeocode(geocode); + } + + public String getError(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + return searches.get(searchId).error; + } + + public boolean setError(final UUID searchId, String error) { + if (searchId == null || searches.containsKey(searchId) == false) { + return false; + } + + searches.get(searchId).error = error; + + return true; + } + + public String getUrl(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + return searches.get(searchId).url; + } + + public boolean setUrl(final UUID searchId, String url) { + if (searchId == null || searches.containsKey(searchId) == false) { + return false; + } + + searches.get(searchId).url = url; + + return true; + } + + public String[] getViewstates(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + return searches.get(searchId).viewstates; + } + + public boolean setViewstates(final UUID searchId, String[] viewstates) { + if (cgBase.isEmpty(viewstates)) { + return false; + } + if (searchId == null || searches.containsKey(searchId) == false) { + return false; + } + + searches.get(searchId).viewstates = viewstates; + + return true; + } + + public Integer getTotal(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + return searches.get(searchId).totalCnt; + } + + public Integer getCount(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return 0; + } + + return searches.get(searchId).getCount(); + } + + public Integer getNotOfflineCount(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return 0; + } + + int count = 0; + List<String> geocodes = searches.get(searchId).getGeocodes(); + if (geocodes != null) { + for (String geocode : geocodes) { + if (isOffline(geocode, null) == false) { + count++; + } + } + } + + return count; + } + + public cgCache getCacheByGeocode(String geocode) { + return getCacheByGeocode(geocode, false, true, false, false, false, false); + } + + public cgCache getCacheByGeocode(String geocode, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + cgCache cache = null; + if (cachesCache.containsKey(geocode)) { + cache = cachesCache.get(geocode); + } else { + if (storage == null) { + storage = new cgData(this); + } + cache = storage.loadCache(geocode, null, loadA, loadW, loadS, loadL, loadI, loadO); + + if (cache != null && cache.detailed && loadA && loadW && loadS && loadL && loadI) { + putCacheInCache(cache); + } + } + + return cache; + } + + public cgTrackable getTrackableByGeocode(String geocode) { + if (StringUtils.isBlank(geocode)) { + return null; + } + + cgTrackable trackable = null; + trackable = storage.loadTrackable(geocode); + + return trackable; + } + + public void removeCacheFromCache(String geocode) { + if (geocode != null && cachesCache.containsKey(geocode)) { + cachesCache.remove(geocode); + } + } + + public void putCacheInCache(cgCache cache) { + if (cache == null || cache.geocode == null) { + return; + } + + if (cachesCache.containsKey(cache.geocode)) { + cachesCache.remove(cache.geocode); + } + + cachesCache.put(cache.geocode, cache); + } + + public String[] geocodesInCache() { + if (storage == null) { + storage = new cgData(this); + } + + return storage.allDetailedThere(); + } + + public cgWaypoint getWaypointById(Integer id) { + if (id == null || id == 0) { + return null; + } + + if (storage == null) { + storage = new cgData(this); + } + return storage.loadWaypoint(id); + } + + public List<Object> getBounds(String geocode) { + if (geocode == null) { + return null; + } + + List<String> geocodeList = new ArrayList<String>(); + geocodeList.add(geocode); + + return getBounds(geocodeList); + } + + public List<Object> getBounds(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + if (storage == null) { + storage = new cgData(this); + } + + final cgSearch search = searches.get(searchId); + final List<String> geocodeList = search.getGeocodes(); + + return getBounds(geocodeList); + } + + public List<Object> getBounds(List<String> geocodes) { + if (geocodes == null || geocodes.isEmpty()) { + return null; + } + + if (storage == null) { + storage = new cgData(this); + } + + return storage.getBounds(geocodes.toArray()); + } + + public cgCache getCache(final UUID searchId) { + if (searchId == null || searches.containsKey(searchId) == false) { + return null; + } + + cgSearch search = searches.get(searchId); + List<String> geocodeList = search.getGeocodes(); + + return getCacheByGeocode(geocodeList.get(0), true, true, true, true, true, true); + } + + public List<cgCache> getCaches(final UUID searchId) { + return getCaches(searchId, null, null, null, null, false, true, false, false, false, true); + } + + public List<cgCache> getCaches(final UUID searchId, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) { + return getCaches(searchId, null, null, null, null, loadA, loadW, loadS, loadL, loadI, loadO); + } + + public List<cgCache> getCaches(final UUID searchId, Long centerLat, Long centerLon, Long spanLat, Long spanLon) { + return getCaches(searchId, centerLat, centerLon, spanLat, spanLon, false, true, false, false, false, true); + } + + public List<cgCache> getCaches(final UUID searchId, Long centerLat, Long centerLon, Long spanLat, Long spanLon, boolean loadA, boolean loadW, boolean loadS, boolean loadL, boolean loadI, boolean loadO) { + if (searchId == null || searches.containsKey(searchId) == false) { + List<cgCache> cachesOut = new ArrayList<cgCache>(); + + final List<cgCache> cachesPre = storage.loadCaches(null, null, centerLat, centerLon, spanLat, spanLon, loadA, loadW, loadS, loadL, loadI, loadO); + + if (cachesPre != null) { + cachesOut.addAll(cachesPre); + } + + return cachesOut; + } + + List<cgCache> cachesOut = new ArrayList<cgCache>(); + + cgSearch search = searches.get(searchId); + List<String> geocodeList = search.getGeocodes(); + + if (storage == null) { + storage = new cgData(this); + } + + // The list of geocodes is sufficient. more parameters generate an overly complex select. + final List<cgCache> cachesPre = storage.loadCaches(geocodeList.toArray(), null, null, null, null, null, loadA, loadW, loadS, loadL, loadI, loadO); + if (cachesPre != null) { + cachesOut.addAll(cachesPre); + } + + return cachesOut; + } + + public cgSearch getBatchOfStoredCaches(boolean detailedOnly, final Geopoint coords, String cachetype, int list) { + if (storage == null) { + storage = new cgData(this); + } + cgSearch search = new cgSearch(); + + List<String> geocodes = storage.loadBatchOfStoredGeocodes(detailedOnly, coords, cachetype, list); + if (geocodes != null && geocodes.isEmpty() == false) { + for (String gccode : geocodes) { + search.addGeocode(gccode); + } + } + searches.put(search.getCurrentId(), search); + + return search; + } + + public List<cgDestination> getHistoryOfSearchedLocations() { + if (storage == null) { + storage = new cgData(this); + } + + return storage.loadHistoryOfSearchedLocations(); + } + + public cgSearch getHistoryOfCaches(boolean detailedOnly, String cachetype) { + if (storage == null) { + storage = new cgData(this); + } + cgSearch search = new cgSearch(); + + List<String> geocodes = storage.loadBatchOfHistoricGeocodes(detailedOnly, cachetype); + if (geocodes != null && geocodes.isEmpty() == false) { + for (String gccode : geocodes) { + search.addGeocode(gccode); + } + } + searches.put(search.getCurrentId(), search); + + return search; + } + + public UUID getCachedInViewport(Long centerLat, Long centerLon, Long spanLat, Long spanLon, String cachetype) { + if (storage == null) { + storage = new cgData(this); + } + cgSearch search = new cgSearch(); + + List<String> geocodes = storage.getCachedInViewport(centerLat, centerLon, spanLat, spanLon, cachetype); + if (geocodes != null && geocodes.isEmpty() == false) { + for (String gccode : geocodes) { + search.addGeocode(gccode); + } + } + searches.put(search.getCurrentId(), search); + + return search.getCurrentId(); + } + + public UUID getStoredInViewport(Long centerLat, Long centerLon, Long spanLat, Long spanLon, String cachetype) { + if (storage == null) { + storage = new cgData(this); + } + cgSearch search = new cgSearch(); + + List<String> geocodes = storage.getStoredInViewport(centerLat, centerLon, spanLat, spanLon, cachetype); + if (geocodes != null && geocodes.isEmpty() == false) { + for (String gccode : geocodes) { + search.addGeocode(gccode); + } + } + searches.put(search.getCurrentId(), search); + + return search.getCurrentId(); + } + + public UUID getOfflineAll(String cachetype) { + if (storage == null) { + storage = new cgData(this); + } + cgSearch search = new cgSearch(); + + List<String> geocodes = storage.getOfflineAll(cachetype); + if (geocodes != null && geocodes.isEmpty() == false) { + for (String gccode : geocodes) { + search.addGeocode(gccode); + } + } + searches.put(search.getCurrentId(), search); + + return search.getCurrentId(); + } + + public int getAllStoredCachesCount(boolean detailedOnly, String cachetype, Integer list) { + if (storage == null) { + storage = new cgData(this); + } + + return storage.getAllStoredCachesCount(detailedOnly, cachetype, list); + } + + public int getAllHistoricCachesCount(boolean detailedOnly, String cachetype) { + if (storage == null) { + storage = new cgData(this); + } + + return storage.getAllHistoricCachesCount(detailedOnly, cachetype); + } + + public void markStored(String geocode, int listId) { + if (storage == null) { + storage = new cgData(this); + } + storage.markStored(geocode, listId); + } + + public boolean markDropped(String geocode) { + if (storage == null) { + storage = new cgData(this); + } + return storage.markDropped(geocode); + } + + public boolean markFound(String geocode) { + if (storage == null) { + storage = new cgData(this); + } + return storage.markFound(geocode); + } + + public boolean clearSearchedDestinations() { + if (storage == null) { + storage = new cgData(this); + } + + return storage.clearSearchedDestinations(); + } + + public boolean saveSearchedDestination(cgDestination destination) { + if (storage == null) { + storage = new cgData(this); + } + + return storage.saveSearchedDestination(destination); + } + + public boolean saveWaypoints(String geocode, List<cgWaypoint> waypoints, boolean drop) { + if (storage == null) { + storage = new cgData(this); + } + return storage.saveWaypoints(geocode, waypoints, drop); + } + + public boolean saveOwnWaypoint(int id, String geocode, cgWaypoint waypoint) { + if (storage == null) { + storage = new cgData(this); + } + return storage.saveOwnWaypoint(id, geocode, waypoint); + } + + public boolean deleteWaypoint(int id) { + if (storage == null) { + storage = new cgData(this); + } + return storage.deleteWaypoint(id); + } + + public boolean saveTrackable(cgTrackable trackable) { + if (storage == null) { + storage = new cgData(this); + } + + final List<cgTrackable> list = new ArrayList<cgTrackable>(); + list.add(trackable); + + return storage.saveInventory("---", list); + } + + public void addGeocode(final UUID searchId, String geocode) { + if (this.searches.containsKey(searchId) == false || StringUtils.isBlank(geocode)) { + return; + } + + this.searches.get(searchId).addGeocode(geocode); + } + + public UUID addSearch(final UUID searchId, List<cgCache> cacheList, Boolean newItem, int reason) { + if (this.searches.containsKey(searchId) == false) { + return null; + } + + cgSearch search = this.searches.get(searchId); + + return addSearch(search, cacheList, newItem, reason); + } + + public UUID addSearch(final cgSearch search, final List<cgCache> cacheList, final boolean newItem, final int reason) { + if (cacheList == null || cacheList.size() == 0) { + return null; + } + + final UUID searchId = search.getCurrentId(); + searches.put(searchId, search); + + if (storage == null) { + storage = new cgData(this); + } + if (newItem) { + // save only newly downloaded data + for (final cgCache cache : cacheList) { + cache.reason = reason; + storeWithMerge(cache, false); + } + } + + return searchId; + } + + public boolean addCacheToSearch(cgSearch search, cgCache cache) { + if (search == null || cache == null) { + return false; + } + + final UUID searchId = search.getCurrentId(); + + if (searches.containsKey(searchId) == false) { + searches.put(searchId, search); + } + + final boolean status = storeWithMerge(cache, cache.reason >= 1); + + if (status) { + search.addGeocode(cache.geocode); + } + + return status; + } + + /** + * Checks if Cache is already in Database and if so does a merge. + * + * @param cache + * the cache to be saved + * @param override + * override the check and persist the new state. + * @return true if the cache has been saved correctly + */ + + private boolean storeWithMerge(final cgCache cache, final boolean override) { + if (!override) { + final cgCache oldCache = storage.loadCache(cache.geocode, cache.guid, + true, true, true, true, true, true); + cache.gatherMissingFrom(oldCache); + } + return storage.saveCache(cache); + } + + public void dropStored(int listId) { + if (storage == null) { + storage = new cgData(this); + } + storage.dropStored(listId); + } + + public List<cgTrackable> loadInventory(String geocode) { + return storage.loadInventory(geocode); + } + + public Map<Integer, Integer> loadLogCounts(String geocode) { + return storage.loadLogCounts(geocode); + } + + public List<cgImage> loadSpoilers(String geocode) { + return storage.loadSpoilers(geocode); + } + + public cgWaypoint loadWaypoint(int id) { + return storage.loadWaypoint(id); + } + + public void setAction(String act) { + action = act; + } + + public String getAction() { + if (action == null) { + return ""; + } + return action; + } + + public boolean addLog(String geocode, cgLog log) { + if (StringUtils.isBlank(geocode)) { + return false; + } + if (log == null) { + return false; + } + + List<cgLog> list = new ArrayList<cgLog>(); + list.add(log); + + return storage.saveLogs(geocode, list, false); + } + + public void setLastLoc(final Geopoint coords) { + lastCoords = coords; + } + + public Geopoint getLastCoords() { + return lastCoords; + } + + public boolean saveLogOffline(String geocode, Date date, int logtype, String log) { + return storage.saveLogOffline(geocode, date, logtype, log); + } + + public cgLog loadLogOffline(String geocode) { + return storage.loadLogOffline(geocode); + } + + public void clearLogOffline(String geocode) { + storage.clearLogOffline(geocode); + } + + public void saveVisitDate(String geocode) { + storage.saveVisitDate(geocode); + } + + public void clearVisitDate(String geocode) { + storage.clearVisitDate(geocode); + } + + public List<cgList> getLists() { + return storage.getLists(getResources()); + } + + public cgList getList(int id) { + return storage.getList(id, getResources()); + } + + public int createList(String title) { + return storage.createList(title); + } + + public boolean removeList(int id) { + return storage.removeList(id); + } + + public boolean removeSearchedDestinations(cgDestination destination) { + return storage.removeSearchedDestination(destination); + } + + public void moveToList(String geocode, int listId) { + storage.moveToList(geocode, listId); + } +} diff --git a/main/src/cgeo/geocaching/cgeoauth.java b/main/src/cgeo/geocaching/cgeoauth.java new file mode 100644 index 0000000..124ff7c --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoauth.java @@ -0,0 +1,392 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.View; +import android.widget.Button; +import android.widget.EditText; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.net.URL; +import java.net.URLConnection; +import java.util.HashMap; +import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.net.ssl.HttpsURLConnection; + +public class cgeoauth extends AbstractActivity { + private String OAtoken = null; + private String OAtokenSecret = null; + private final Pattern paramsPattern1 = Pattern.compile("oauth_token=([a-zA-Z0-9\\-\\_\\.]+)"); + private final Pattern paramsPattern2 = Pattern.compile("oauth_token_secret=([a-zA-Z0-9\\-\\_\\.]+)"); + private Button startButton = null; + private EditText pinEntry = null; + private Button pinEntryButton = null; + private ProgressDialog requestTokenDialog = null; + private ProgressDialog changeTokensDialog = null; + private Handler requestTokenHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (requestTokenDialog != null && requestTokenDialog.isShowing()) { + requestTokenDialog.dismiss(); + } + + startButton.setOnClickListener(new startListener()); + startButton.setEnabled(true); + + if (msg.what == 1) { + startButton.setText(res.getString(R.string.auth_again)); + + pinEntry.setVisibility(View.VISIBLE); + pinEntryButton.setVisibility(View.VISIBLE); + pinEntryButton.setOnClickListener(new confirmPINListener()); + } else { + showToast(res.getString(R.string.err_auth_initialize)); + startButton.setText(res.getString(R.string.auth_start)); + } + } + }; + private Handler changeTokensHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (changeTokensDialog != null && changeTokensDialog.isShowing()) { + changeTokensDialog.dismiss(); + } + + pinEntryButton.setOnClickListener(new confirmPINListener()); + pinEntryButton.setEnabled(true); + + if (msg.what == 1) { + showToast(res.getString(R.string.auth_dialog_completed)); + + pinEntryButton.setVisibility(View.GONE); + + finish(); + } else { + showToast(res.getString(R.string.err_auth_process)); + + pinEntry.setVisibility(View.GONE); + pinEntryButton.setVisibility(View.GONE); + startButton.setText(res.getString(R.string.auth_start)); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + app.setAction("setting up"); + + setTheme(); + setContentView(R.layout.auth); + setTitle(res.getString(R.string.auth_twitter)); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + private void init() { + startButton = (Button) findViewById(R.id.start); + pinEntry = (EditText) findViewById(R.id.pin); + pinEntryButton = (Button) findViewById(R.id.pin_button); + + OAtoken = prefs.getString("temp-token-public", null); + OAtokenSecret = prefs.getString("temp-token-secret", null); + + startButton.setEnabled(true); + startButton.setOnClickListener(new startListener()); + + if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { + // start authorization process + startButton.setText(res.getString(R.string.auth_start)); + } else { + // already have temporary tokens, continue from pin + startButton.setText(res.getString(R.string.auth_again)); + + pinEntry.setVisibility(View.VISIBLE); + pinEntryButton.setVisibility(View.VISIBLE); + pinEntryButton.setOnClickListener(new confirmPINListener()); + } + } + + private void requestToken() { + final String host = "twitter.com"; + final String pathRequest = "/oauth/request_token"; + final String pathAuthorize = "/oauth/authorize"; + final String method = "GET"; + + int status = 0; + try { + String lineOne = null; + HttpsURLConnection connection = null; + + try { + final StringBuilder sb = new StringBuilder(); + final String params = cgOAuth.signOAuth(host, pathRequest, method, true, new HashMap<String, String>(), null, null); + + int code = -1; + int retries = 0; + + do { + // base.trustAllHosts(); + Log.d(cgSettings.tag, "https://" + host + pathRequest + "?" + params); + final URL u = new URL("https://" + host + pathRequest + "?" + params); + final URLConnection uc = u.openConnection(); + connection = (HttpsURLConnection) uc; + + // connection.setHostnameVerifier(base.doNotVerify); + connection.setReadTimeout(30000); + connection.setRequestMethod(method); + HttpsURLConnection.setFollowRedirects(true); + connection.setDoInput(true); + connection.setDoOutput(false); + + final InputStream in = connection.getInputStream(); + final InputStreamReader ins = new InputStreamReader(in); + final BufferedReader br = new BufferedReader(ins, 16 * 1024); + + while ((lineOne = br.readLine()) != null) { + sb.append(lineOne); + sb.append('\n'); + } + + code = connection.getResponseCode(); + retries++; + + Log.i(cgSettings.tag, host + ": " + connection.getResponseCode() + " " + connection.getResponseMessage()); + + br.close(); + in.close(); + ins.close(); + } while (code == -1 && retries < 5); + + final String line = sb.toString(); + + if (StringUtils.isNotBlank(line)) { + final Matcher paramsMatcher1 = paramsPattern1.matcher(line); + if (paramsMatcher1.find() && paramsMatcher1.groupCount() > 0) { + OAtoken = paramsMatcher1.group(1); + } + final Matcher paramsMatcher2 = paramsPattern2.matcher(line); + if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { + OAtokenSecret = paramsMatcher2.group(1); + } + + if (StringUtils.isNotBlank(OAtoken) && StringUtils.isNotBlank(OAtokenSecret)) { + final SharedPreferences.Editor prefsEdit = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefsEdit.putString("temp-token-public", OAtoken); + prefsEdit.putString("temp-token-secret", OAtokenSecret); + prefsEdit.commit(); + + try { + final Map<String, String> paramsPre = new HashMap<String, String>(); + paramsPre.put("oauth_callback", "oob"); + + final String paramsBrowser = cgOAuth.signOAuth(host, pathAuthorize, "GET", true, paramsPre, OAtoken, OAtokenSecret); + + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("https://" + host + pathAuthorize + "?" + paramsBrowser))); + + status = 1; + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoauth.requestToken(2): " + e.toString()); + } + } + } + } catch (IOException eio) { + Log.e(cgSettings.tag, "cgeoauth.requestToken(IO): " + eio.toString() + " ~ " + connection.getResponseCode() + ": " + connection.getResponseMessage()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoauth.requestToken(1): " + e.toString()); + } finally { + if (connection != null) { + connection.disconnect(); + } + } + } catch (Exception e2) { + Log.e(cgSettings.tag, "cgeoauth.requestToken(3): " + e2.toString()); + } + + requestTokenHandler.sendEmptyMessage(status); + } + + private void changeToken() { + final String host = "twitter.com"; + final String path = "/oauth/access_token"; + final String method = "POST"; + + int status = 0; + String lineOne = null; + + try { + final Map<String, String> paramsPre = new HashMap<String, String>(); + paramsPre.put("oauth_verifier", pinEntry.getText().toString()); + + int code = -1; + int retries = 0; + + final String params = cgOAuth.signOAuth(host, path, method, true, paramsPre, OAtoken, OAtokenSecret); + final StringBuilder sb = new StringBuilder(); + do { + // base.trustAllHosts(); + final URL u = new URL("https://" + host + path); + final URLConnection uc = u.openConnection(); + final HttpsURLConnection connection = (HttpsURLConnection) uc; + + // connection.setHostnameVerifier(base.doNotVerify); + connection.setReadTimeout(30000); + connection.setRequestMethod(method); + HttpsURLConnection.setFollowRedirects(true); + connection.setDoOutput(true); + connection.setDoInput(true); + + final OutputStream out = connection.getOutputStream(); + final OutputStreamWriter wr = new OutputStreamWriter(out); + + wr.write(params); + wr.flush(); + wr.close(); + out.close(); + + final InputStream in = connection.getInputStream(); + final InputStreamReader ins = new InputStreamReader(in); + final BufferedReader br = new BufferedReader(ins, 16 * 1024); + + while ((lineOne = br.readLine()) != null) { + sb.append(lineOne); + sb.append('\n'); + } + + code = connection.getResponseCode(); + retries++; + + Log.i(cgSettings.tag, host + ": " + connection.getResponseCode() + " " + connection.getResponseMessage()); + + br.close(); + ins.close(); + in.close(); + connection.disconnect(); + } while (code == -1 && retries < 5); + + final String line = sb.toString(); + + OAtoken = ""; + OAtokenSecret = ""; + + final Matcher paramsMatcher1 = paramsPattern1.matcher(line); + if (paramsMatcher1.find() && paramsMatcher1.groupCount() > 0) { + OAtoken = paramsMatcher1.group(1); + } + final Matcher paramsMatcher2 = paramsPattern2.matcher(line); + if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { + OAtokenSecret = paramsMatcher2.group(1); + } + + if (StringUtils.isBlank(OAtoken) && StringUtils.isBlank(OAtokenSecret)) { + OAtoken = ""; + OAtokenSecret = ""; + + final SharedPreferences.Editor prefs = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefs.putString("tokenpublic", null); + prefs.putString("tokensecret", null); + prefs.putInt("twitter", 0); + prefs.commit(); + } else { + final SharedPreferences.Editor prefs = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefs.remove("temp-token-public"); + prefs.remove("temp-token-secret"); + prefs.putString("tokenpublic", OAtoken); + prefs.putString("tokensecret", OAtokenSecret); + prefs.putInt("twitter", 1); + prefs.commit(); + + status = 1; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoauth.changeToken: " + e.toString()); + } + + changeTokensHandler.sendEmptyMessage(status); + } + + private class startListener implements View.OnClickListener { + + public void onClick(View arg0) { + if (requestTokenDialog == null) { + requestTokenDialog = new ProgressDialog(cgeoauth.this); + requestTokenDialog.setCancelable(false); + requestTokenDialog.setMessage(res.getString(R.string.auth_dialog_wait)); + } + requestTokenDialog.show(); + startButton.setEnabled(false); + startButton.setOnTouchListener(null); + startButton.setOnClickListener(null); + + final SharedPreferences.Editor prefs = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefs.putString("temp-token-public", null); + prefs.putString("temp-token-secret", null); + prefs.commit(); + + (new Thread() { + + @Override + public void run() { + requestToken(); + } + }).start(); + } + } + + private class confirmPINListener implements View.OnClickListener { + + public void onClick(View arg0) { + if (((EditText) findViewById(R.id.pin)).getText().toString().length() == 0) { + helpDialog(res.getString(R.string.auth_dialog_pin_title), res.getString(R.string.auth_dialog_pin_message)); + return; + } + + if (changeTokensDialog == null) { + changeTokensDialog = new ProgressDialog(cgeoauth.this); + changeTokensDialog.setCancelable(false); + changeTokensDialog.setMessage(res.getString(R.string.auth_dialog_wait)); + } + changeTokensDialog.show(); + pinEntryButton.setEnabled(false); + pinEntryButton.setOnTouchListener(null); + pinEntryButton.setOnClickListener(null); + + (new Thread() { + + @Override + public void run() { + changeToken(); + } + }).start(); + } + } +} diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java new file mode 100644 index 0000000..4d864ca --- /dev/null +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -0,0 +1,2642 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.AbstractListActivity; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.apps.cachelist.CacheListAppFactory; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.filter.cgFilter; +import cgeo.geocaching.filter.cgFilterBySize; +import cgeo.geocaching.filter.cgFilterByTrackables; +import cgeo.geocaching.filter.cgFilterByType; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.sorting.CacheComparator; +import cgeo.geocaching.sorting.DateComparator; +import cgeo.geocaching.sorting.DifficultyComparator; +import cgeo.geocaching.sorting.FindsComparator; +import cgeo.geocaching.sorting.GeocodeComparator; +import cgeo.geocaching.sorting.InventoryComparator; +import cgeo.geocaching.sorting.NameComparator; +import cgeo.geocaching.sorting.PopularityComparator; +import cgeo.geocaching.sorting.RatingComparator; +import cgeo.geocaching.sorting.SizeComparator; +import cgeo.geocaching.sorting.StateComparator; +import cgeo.geocaching.sorting.TerrainComparator; +import cgeo.geocaching.sorting.VoteComparator; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.WindowManager; +import android.view.ContextMenu.ContextMenuInfo; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.AdapterView.AdapterContextMenuInfo; + +import java.io.File; +import java.io.FileOutputStream; +import java.io.IOException; +import java.io.OutputStream; +import java.io.OutputStreamWriter; +import java.io.Writer; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class cgeocaches extends AbstractListActivity { + + private static final int MAX_LIST_ITEMS = 1000; + private static final String EXTRAS_LIST_TYPE = "type"; + private static final int MENU_COMPASS = 1; + private static final int MENU_REFRESH_STORED = 2; + private static final int MENU_CACHE_DETAILS = 4; + private static final int MENU_DROP_CACHES = 5; + private static final int MENU_IMPORT_GPX = 6; + private static final int MENU_CREATE_LIST = 7; + private static final int MENU_DROP_LIST = 8; + private static final int MENU_INVERT_SELECTION = 9; + private static final int MENU_SORT_DISTANCE = 10; + private static final int MENU_SORT_DIFFICULTY = 11; + private static final int MENU_SORT_TERRAIN = 12; + private static final int MENU_SORT_SIZE = 13; + private static final int MENU_SORT_FAVORITES = 14; + private static final int MENU_SORT_NAME = 15; + private static final int MENU_SORT_GEOCODE = 16; + private static final int MENU_SWITCH_LIST = 17; + private static final int MENU_SORT_RATING = 18; + private static final int MENU_SORT_VOTE = 19; + private static final int MENU_SORT_INVENTORY = 20; + private static final int MENU_IMPORT_WEB = 21; + private static final int MENU_EXPORT_NOTES = 22; + private static final int MENU_REMOVE_FROM_HISTORY = 23; + private static final int MENU_DROP_CACHE = 24; + private static final int MENU_MOVE_TO_LIST = 25; + private static final int MENU_FILTER_CLEAR = 26; + private static final int MENU_FILTER_TRACKABLES = 27; + private static final int SUBMENU_FILTER_SIZE = 28; + private static final int SUBMENU_FILTER_TYPE = 29; + private static final int MENU_FILTER_TYPE_GPS = 30; + private static final int MENU_FILTER_TYPE_GCHQ = 31; + private static final int MENU_FILTER_TYPE_APE = 32; + private static final int MENU_FILTER_TYPE_LOSTFOUND = 33; + private static final int MENU_FILTER_TYPE_WHERIGO = 34; + private static final int MENU_FILTER_TYPE_VIRTUAL = 35; + private static final int MENU_FILTER_TYPE_WEBCAM = 36; + private static final int MENU_FILTER_TYPE_CITO = 37; + private static final int MENU_FILTER_TYPE_EARTH = 38; + private static final int MENU_FILTER_TYPE_MEGA = 39; + private static final int MENU_FILTER_TYPE_EVENT = 40; + private static final int MENU_FILTER_TYPE_LETTERBOX = 41; + private static final int MENU_FILTER_TYPE_MYSTERY = 42; + private static final int MENU_FILTER_TYPE_MULTI = 43; + private static final int MENU_FILTER_TYPE_TRADITIONAL = 44; + private static final int MENU_FILTER_SIZE_NOT_CHOSEN = 45; + private static final int MENU_FILTER_SIZE_VIRTUAL = 46; + private static final int MENU_FILTER_SIZE_OTHER = 47; + private static final int MENU_FILTER_SIZE_LARGE = 48; + private static final int MENU_FILTER_SIZE_REGULAR = 49; + private static final int MENU_FILTER_SIZE_SMALL = 50; + private static final int MENU_FILTER_SIZE_MICRO = 51; + private static final int MENU_SWITCH_SELECT_MODE = 52; + private static final int SUBMENU_SHOW_MAP = 54; + private static final int SUBMENU_MANAGE_LISTS = 55; + private static final int SUBMENU_MANAGE_OFFLINE = 56; + private static final int SUBMENU_SORT = 57; + private static final int SUBMENU_FILTER = 58; + private static final int SUBMENU_IMPORT = 59; + private static final int SUBMENU_MANAGE_HISTORY = 60; + private static final int MENU_SORT_DATE = 61; + private static final int MENU_SORT_FINDS = 62; + private static final int MENU_SORT_STATE = 63; + + private static final int CONTEXT_MENU_MOVE_TO_LIST = 1000; + private static final int MENU_MOVE_SELECTED_OR_ALL_TO_LIST = 1200; + + private String action = null; + private String type = null; + private Geopoint coords = null; + private String cachetype = null; + private String keyword = null; + private String address = null; + private String username = null; + private UUID searchId = null; + private List<cgCache> cacheList = new ArrayList<cgCache>(); + private cgCacheListAdapter adapter = null; + private LayoutInflater inflater = null; + private View listFooter = null; + private TextView listFooterText = null; + private ProgressDialog waitDialog = null; + private Float northHeading = 0f; + private cgGeo geo = null; + private cgDirection dir = null; + private cgUpdateLoc geoUpdate = new update(); + private cgUpdateDir dirUpdate = new UpdateDirection(); + private String title = ""; + private int detailTotal = 0; + private int detailProgress = 0; + private long detailProgressTime = 0L; + private geocachesLoadDetails threadD = null; + private geocachesLoadFromWeb threadW = null; + private geocachesDropDetails threadR = null; + private geocachesExportFieldNotes threadF = null; + private geocachesRemoveFromHistory threadH = null; + private int listId = 0; + private List<cgList> lists = null; + private String selectedFilter = null; + private GeocodeComparator gcComparator = new GeocodeComparator(); + private Handler loadCachesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (searchId != null) { + setTitle(title + " [" + app.getCount(searchId) + "]"); + cacheList.clear(); + + final List<cgCache> cacheListTmp = app.getCaches(searchId); + if (CollectionUtils.isNotEmpty(cacheListTmp)) { + cacheList.addAll(cacheListTmp); + cacheListTmp.clear(); + + Collections.sort((List<cgCache>) cacheList, gcComparator); + } + } else { + setTitle(title); + } + + setAdapter(); + + if (cacheList == null) { + showToast(res.getString(R.string.err_list_load_fail)); + setMoreCaches(false); + } else { + final Integer count = app.getTotal(searchId); + + if (count != null && count > 0) { + if (cacheList.size() < app.getTotal(searchId) && cacheList.size() < MAX_LIST_ITEMS) { + setMoreCaches(true); + } else { + setMoreCaches(false); + } + } else { + setMoreCaches(false); + } + } + + if (cacheList != null && app.getError(searchId) != null && app.getError(searchId).equalsIgnoreCase(cgBase.errorRetrieve.get(-7))) { + AlertDialog.Builder dialog = new AlertDialog.Builder(cgeocaches.this); + dialog.setTitle(res.getString(R.string.license)); + dialog.setMessage(res.getString(R.string.err_license)); + dialog.setCancelable(true); + dialog.setNegativeButton(res.getString(R.string.license_dismiss), new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + CookieJar.deleteCookies(prefs); + dialog.cancel(); + } + }); + dialog.setPositiveButton(res.getString(R.string.license_show), new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + CookieJar.deleteCookies(prefs); + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0"))); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + } else if (app != null && StringUtils.isNotBlank(app.getError(searchId))) { + showToast(res.getString(R.string.err_download_fail) + app.getError(searchId) + "."); + + hideLoading(); + showProgress(false); + + finish(); + return; + } + + if (geo != null && geo.coordsNow != null) { + adapter.setActualCoordinates(geo.coordsNow); + adapter.setActualHeading(northHeading); + } + } catch (Exception e) { + showToast(res.getString(R.string.err_detail_cache_find_any)); + Log.e(cgSettings.tag, "cgeocaches.loadCachesHandler: " + e.toString()); + + hideLoading(); + showProgress(false); + + finish(); + return; + } + + try { + hideLoading(); + showProgress(false); + } catch (Exception e2) { + Log.e(cgSettings.tag, "cgeocaches.loadCachesHandler.2: " + e2.toString()); + } + + if (adapter != null) { + adapter.setSelectMode(false, true); + } + } + }; + private Handler loadNextPageHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (searchId != null) { + setTitle(title + " [" + app.getCount(searchId) + "]"); + cacheList.clear(); + + final List<cgCache> cacheListTmp = app.getCaches(searchId); + if (CollectionUtils.isNotEmpty(cacheListTmp)) { + cacheList.addAll(cacheListTmp); + cacheListTmp.clear(); + Collections.sort((List<cgCache>) cacheList, gcComparator); + } + if (adapter != null) { + adapter.reFilter(); + } + } else { + setTitle(title); + } + + setAdapter(); + + if (cacheList == null) { + showToast(res.getString(R.string.err_list_load_fail)); + setMoreCaches(false); + } else { + final Integer count = app.getTotal(searchId); + if (count != null && count > 0) { + if (cacheList.size() < app.getTotal(searchId) && cacheList.size() < MAX_LIST_ITEMS) { + setMoreCaches(true); + } else { + setMoreCaches(false); + } + } else { + setMoreCaches(false); + } + } + + if (StringUtils.isNotBlank(app.getError(searchId))) { + showToast(res.getString(R.string.err_download_fail) + app.getError(searchId) + "."); + + listFooter.setOnClickListener(new moreCachesListener()); + hideLoading(); + showProgress(false); + + finish(); + return; + } + + if (geo != null && geo.coordsNow != null) { + adapter.setActualCoordinates(geo.coordsNow); + adapter.setActualHeading(northHeading); + } + } catch (Exception e) { + showToast(res.getString(R.string.err_detail_cache_find_next)); + Log.e(cgSettings.tag, "cgeocaches.loadNextPageHandler: " + e.toString()); + } + + listFooter.setOnClickListener(new moreCachesListener()); + + hideLoading(); + showProgress(false); + + if (adapter != null) { + adapter.setSelectMode(false, true); + } + } + }; + private Handler loadDetailsHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + setAdapter(); + + if (msg.what > -1) { + if (waitDialog != null) { + cacheList.get(msg.what).statusChecked = false; + + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + + int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); + int minutesRemaining = (int) ((detailTotal - detailProgress) * secondsElapsed / ((detailProgress > 0) ? detailProgress : 1) / 60); + + waitDialog.setProgress(detailProgress); + if (minutesRemaining < 1) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); + } else if (minutesRemaining == 1) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minutesRemaining + " " + res.getString(R.string.caches_eta_min)); + } else { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + minutesRemaining + " " + res.getString(R.string.caches_eta_mins)); + } + } + } else { + if (cacheList != null && searchId != null) { + final List<cgCache> cacheListTmp = app.getCaches(searchId); + if (CollectionUtils.isNotEmpty(cacheListTmp)) { + cacheList.clear(); + cacheList.addAll(cacheListTmp); + cacheListTmp.clear(); + Collections.sort((List<cgCache>) cacheList, gcComparator); + } + } + + if (geo != null && geo.coordsNow != null) { + adapter.setActualCoordinates(geo.coordsNow); + adapter.setActualHeading(northHeading); + } + + showProgress(false); + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + + if (geo == null) { + geo = app.startGeo(cgeocaches.this, geoUpdate, base, settings, 0, 0); + } + if (settings.livelist == 1 && settings.useCompass == 1 && dir == null) { + dir = app.startDir(cgeocaches.this, dirUpdate); + } + } + } + }; + private Handler downloadFromWebHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + setAdapter(); + + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + + if (msg.what == 0) { //no caches + waitDialog.setMessage(res.getString(R.string.web_import_waiting)); + } else if (msg.what == 1) { //cache downloading + waitDialog.setMessage(res.getString(R.string.web_downloading) + " " + (String) msg.obj + "..."); + } else if (msg.what == 2) { //Cache downloaded + waitDialog.setMessage(res.getString(R.string.web_downloaded) + " " + (String) msg.obj + "."); + refreshCurrentList(); + } else if (msg.what == -2) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + showToast(res.getString(R.string.sendToCgeo_download_fail)); + finish(); + return; + } else if (msg.what == -3) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + showToast(res.getString(R.string.sendToCgeo_no_registration)); + finish(); + return; + } else { + if (adapter != null) { + adapter.setSelectMode(false, true); + } + + cacheList.clear(); + + final List<cgCache> cacheListTmp = app.getCaches(searchId); + if (CollectionUtils.isNotEmpty(cacheListTmp)) { + cacheList.addAll(cacheListTmp); + cacheListTmp.clear(); + + Collections.sort((List<cgCache>) cacheList, gcComparator); + } + + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + getWindow().clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + } + } + }; + private Handler dropDetailsHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (adapter != null) { + adapter.setSelectMode(false, true); + } + + refreshCurrentList(); + + cacheList.clear(); + + final List<cgCache> cacheListTmp = app.getCaches(searchId); + if (CollectionUtils.isNotEmpty(cacheListTmp)) { + cacheList.addAll(cacheListTmp); + cacheListTmp.clear(); + + Collections.sort((List<cgCache>) cacheList, gcComparator); + } + + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + } + }; + private Handler removeFromHistoryHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + setAdapter(); + + if (msg.what > -1) { + cacheList.get(msg.what).statusChecked = false; + } else { + if (adapter != null) { + adapter.setSelectMode(false, true); + } + + // TODO: Reload cacheList + + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + } + } + }; + private Handler exportFieldNotesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) + { + setAdapter(); + + if (msg.what > -1) + { + cacheList.get(msg.what).statusChecked = false; + waitDialog.setProgress(detailProgress); + } + else if (-2 == msg.what) + { + showToast(res.getString(R.string.info_fieldnotes_exported_to) + ": " + msg.obj.toString()); + } + else if (-3 == msg.what) + { + showToast(res.getString(R.string.err_fieldnotes_export_failed)); + } + else + { + if (adapter != null) + { + adapter.setSelectMode(false, true); + } + + if (waitDialog != null) + { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + } + } + }; + private ContextMenuInfo lastMenuInfo; + /** + * the navigation menu item for the cache list (not the context menu!), or <code>null</code> + */ + private MenuItem navigationMenu; + /** + * flag indicating whether we shall show the move to list context menu + */ + private boolean contextMenuMoveToList = false; + + /** + * flag indicating whether we shall show the filter context menu + */ + private boolean contextMenuShowFilter = false; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + app.setAction(action); + + setTheme(); + setContentView(R.layout.caches); + setTitle("caches"); + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + type = extras.getString(EXTRAS_LIST_TYPE); + coords = new Geopoint(extras.getDouble("latitude"), extras.getDouble("longitude")); + cachetype = extras.getString("cachetype"); + keyword = extras.getString("keyword"); + address = extras.getString("address"); + username = extras.getString("username"); + } + + init(); + + Thread threadPure; + cgSearchThread thread; + + if (type.equals("offline")) { + listId = settings.getLastList(); + if (listId <= 0) { + listId = 1; + title = res.getString(R.string.caches_stored); + } else { + final cgList list = app.getList(listId); + title = list.title; + } + + setTitle(title); + showProgress(true); + setLoadingCaches(); + + threadPure = new geocachesLoadByOffline(loadCachesHandler, coords, listId); + threadPure.start(); + } else if (type.equals("history")) { + if (adapter != null) { + adapter.setHistoric(true); + } + + title = res.getString(R.string.caches_history); + setTitle(title); + showProgress(true); + setLoadingCaches(); + + threadPure = new geocachesLoadByHistory(loadCachesHandler); + threadPure.start(); + } else if (type.equals("nearest")) { + action = "pending"; + title = res.getString(R.string.caches_nearby); + setTitle(title); + showProgress(true); + setLoadingCaches(); + + thread = new geocachesLoadByCoords(loadCachesHandler, coords, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else if (type.equals("coordinate")) { + action = "planning"; + title = cgBase.formatCoords(coords, true); + setTitle(title); + showProgress(true); + setLoadingCaches(); + + thread = new geocachesLoadByCoords(loadCachesHandler, coords, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else if (type.equals("keyword")) { + title = keyword; + setTitle(title); + showProgress(true); + setLoadingCaches(); + + thread = new geocachesLoadByKeyword(loadCachesHandler, keyword, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else if (type.equals("address")) { + action = "planning"; + if (StringUtils.isNotBlank(address)) { + title = address; + setTitle(title); + showProgress(true); + setLoadingCaches(); + } else { + title = cgBase.formatCoords(coords, true); + setTitle(title); + showProgress(true); + setLoadingCaches(); + } + + thread = new geocachesLoadByCoords(loadCachesHandler, coords, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else if (type.equals("username")) { + title = username; + setTitle(title); + showProgress(true); + setLoadingCaches(); + + thread = new geocachesLoadByUserName(loadCachesHandler, username, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else if (type.equals("owner")) { + title = username; + setTitle(title); + showProgress(true); + setLoadingCaches(); + + thread = new geocachesLoadByOwner(loadCachesHandler, username, cachetype); + thread.setRecaptchaHandler(new cgSearchHandler(this, res, thread)); + thread.start(); + } else { + title = "caches"; + setTitle(title); + Log.e(cgSettings.tag, "cgeocaches.onCreate: No action or unknown action specified"); + } + + prepareFilterBar(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + init(); + + if (adapter != null && geo != null && geo.coordsNow != null) { + adapter.setActualCoordinates(geo.coordsNow); + adapter.setActualHeading(northHeading); + } + + if (adapter != null) { + adapter.setSelectMode(false, true); + if (geo != null && geo.coordsNow != null) { + adapter.forceSort(geo.coordsNow); + } + } + + if (loadCachesHandler != null && searchId != null) { + loadCachesHandler.sendEmptyMessage(0); + } + } + + @Override + public void onDestroy() { + if (adapter != null) { + adapter = null; + } + + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + SubMenu subMenuFilter = menu.addSubMenu(0, SUBMENU_FILTER, 0, res.getString(R.string.caches_filter)).setIcon(R.drawable.ic_menu_filter); + subMenuFilter.setHeaderTitle(res.getString(R.string.caches_filter_title)); + if (settings.cacheType == null) { + subMenuFilter.add(0, SUBMENU_FILTER_TYPE, 0, res.getString(R.string.caches_filter_type)); + } + subMenuFilter.add(0, SUBMENU_FILTER_SIZE, 0, res.getString(R.string.caches_filter_size)); + subMenuFilter.add(0, MENU_FILTER_TRACKABLES, 0, res.getString(R.string.caches_filter_track)); + subMenuFilter.add(0, MENU_FILTER_CLEAR, 0, res.getString(R.string.caches_filter_clear)); + + SubMenu subMenuSort = menu.addSubMenu(0, SUBMENU_SORT, 0, res.getString(R.string.caches_sort)).setIcon(android.R.drawable.ic_menu_sort_alphabetically); + subMenuSort.setHeaderTitle(res.getString(R.string.caches_sort_title)); + + // sort the context menu labels alphabetically for easier reading + Map<String, Integer> comparators = new HashMap<String, Integer>(); + comparators.put(res.getString(R.string.caches_sort_distance), MENU_SORT_DISTANCE); + comparators.put(res.getString(R.string.caches_sort_difficulty), MENU_SORT_DIFFICULTY); + comparators.put(res.getString(R.string.caches_sort_terrain), MENU_SORT_TERRAIN); + comparators.put(res.getString(R.string.caches_sort_size), MENU_SORT_SIZE); + comparators.put(res.getString(R.string.caches_sort_favorites), MENU_SORT_FAVORITES); + comparators.put(res.getString(R.string.caches_sort_name), MENU_SORT_NAME); + comparators.put(res.getString(R.string.caches_sort_gccode), MENU_SORT_GEOCODE); + comparators.put(res.getString(R.string.caches_sort_rating), MENU_SORT_RATING); + comparators.put(res.getString(R.string.caches_sort_vote), MENU_SORT_VOTE); + comparators.put(res.getString(R.string.caches_sort_inventory), MENU_SORT_INVENTORY); + comparators.put(res.getString(R.string.caches_sort_date), MENU_SORT_DATE); + comparators.put(res.getString(R.string.caches_sort_finds), MENU_SORT_FINDS); + comparators.put(res.getString(R.string.caches_sort_state), MENU_SORT_STATE); + + List<String> sortedLabels = new ArrayList<String>(comparators.keySet()); + Collections.sort(sortedLabels); + for (String label : sortedLabels) { + Integer id = comparators.get(label); + subMenuSort.add(1, id, 0, label).setCheckable(true).setChecked(id == MENU_SORT_DISTANCE); + } + + subMenuSort.setGroupCheckable(1, true, true); + + menu.add(0, MENU_SWITCH_SELECT_MODE, 0, res.getString(R.string.caches_select_mode)).setIcon(android.R.drawable.ic_menu_agenda); + menu.add(0, MENU_INVERT_SELECTION, 0, res.getString(R.string.caches_select_invert)).setIcon(R.drawable.ic_menu_mark); + if (type.equals("offline")) { + SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_OFFLINE, 0, res.getString(R.string.caches_manage)).setIcon(android.R.drawable.ic_menu_save); + subMenu.add(0, MENU_DROP_CACHES, 0, res.getString(R.string.caches_drop_all)); // delete saved caches + subMenu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.cache_offline_refresh)); // download details for all caches + subMenu.add(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list)); + subMenu.add(0, MENU_EXPORT_NOTES, 0, res.getString(R.string.cache_export_fieldnote)); // export field notes + if (settings.webDeviceCode == null) + { + menu.add(0, MENU_IMPORT_GPX, 0, res.getString(R.string.gpx_import_title)).setIcon(android.R.drawable.ic_menu_upload); // import gpx file + } else { + SubMenu subMenuImport = menu.addSubMenu(0, SUBMENU_IMPORT, 0, res.getString(R.string.import_title)).setIcon(android.R.drawable.ic_menu_upload); // import + subMenuImport.add(1, MENU_IMPORT_GPX, 0, res.getString(R.string.gpx_import_title)).setCheckable(false).setChecked(false); + subMenuImport.add(1, MENU_IMPORT_WEB, 0, res.getString(R.string.web_import_title)).setCheckable(false).setChecked(false); + } + } else { + if (type.equals("history")) + { + SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_HISTORY, 0, res.getString(R.string.caches_manage)).setIcon(android.R.drawable.ic_menu_save); + subMenu.add(0, MENU_REMOVE_FROM_HISTORY, 0, res.getString(R.string.cache_clear_history)); // remove from history + subMenu.add(0, MENU_EXPORT_NOTES, 0, res.getString(R.string.cache_export_fieldnote)); // export field notes + } + menu.add(0, MENU_REFRESH_STORED, 0, res.getString(R.string.caches_store_offline)).setIcon(android.R.drawable.ic_menu_set_as); // download details for all caches + } + + navigationMenu = CacheListAppFactory.addMenuItems(menu, this, res); + + if (type.equals("offline")) { + SubMenu subMenu = menu.addSubMenu(0, SUBMENU_MANAGE_LISTS, 0, res.getString(R.string.list_menu)).setIcon(android.R.drawable.ic_menu_more); + subMenu.add(0, MENU_CREATE_LIST, 0, res.getString(R.string.list_menu_create)); + subMenu.add(0, MENU_DROP_LIST, 0, res.getString(R.string.list_menu_drop)); + subMenu.add(0, MENU_SWITCH_LIST, 0, res.getString(R.string.list_menu_change)); + } + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + try { + if (adapter != null && adapter.getSelectMode()) { + menu.findItem(MENU_SWITCH_SELECT_MODE).setTitle(res.getString(R.string.caches_select_mode_exit)) + .setIcon(R.drawable.ic_menu_clear_playlist); + menu.findItem(MENU_INVERT_SELECTION).setVisible(true); + } else { + menu.findItem(MENU_SWITCH_SELECT_MODE).setTitle(res.getString(R.string.caches_select_mode)) + .setIcon(android.R.drawable.ic_menu_agenda); + menu.findItem(MENU_INVERT_SELECTION).setVisible(false); + } + + boolean hasSelection = adapter != null && adapter.getChecked() > 0; + if (type != null && type.equals("offline")) { // only offline list + if (hasSelection) { + menu.findItem(MENU_DROP_CACHES).setTitle(res.getString(R.string.caches_drop_selected) + " (" + adapter.getChecked() + ")"); + } else { + menu.findItem(MENU_DROP_CACHES).setTitle(res.getString(R.string.caches_drop_all)); + } + + if (hasSelection) { + menu.findItem(MENU_REFRESH_STORED).setTitle(res.getString(R.string.caches_refresh_selected) + " (" + adapter.getChecked() + ")"); + } else { + menu.findItem(MENU_REFRESH_STORED).setTitle(res.getString(R.string.caches_refresh_all)); + } + + if (hasSelection) { + menu.findItem(MENU_MOVE_TO_LIST).setTitle(res.getString(R.string.caches_move_selected) + " (" + adapter.getChecked() + ")"); + } else { + menu.findItem(MENU_MOVE_TO_LIST).setTitle(res.getString(R.string.caches_move_all)); + } + } else { // search and history list (all other than offline) + if (hasSelection) { + menu.findItem(MENU_REFRESH_STORED).setTitle(res.getString(R.string.caches_store_selected) + " (" + adapter.getChecked() + ")"); + } else { + menu.findItem(MENU_REFRESH_STORED).setTitle(res.getString(R.string.caches_store_offline)); + } + } + + // Hide menus if cache-list is empty + int[] hideIfEmptyList = new int[] { MENU_SWITCH_SELECT_MODE, + SUBMENU_MANAGE_OFFLINE, + SUBMENU_MANAGE_HISTORY, + SUBMENU_SHOW_MAP, + SUBMENU_SORT, + MENU_REFRESH_STORED }; + + boolean menuEnabled = cacheList.size() > 0; + for (int itemId : hideIfEmptyList) + { + MenuItem item = menu.findItem(itemId); + if (null != item) + { + item.setEnabled(menuEnabled); + } + } + + if (navigationMenu != null) { + navigationMenu.setEnabled(menuEnabled); + } + + MenuItem item = menu.findItem(MENU_DROP_LIST); + if (item != null) { + item.setVisible(listId != 1); + } + + boolean multipleLists = app.getLists().size() >= 2; + item = menu.findItem(MENU_SWITCH_LIST); + if (item != null) { + item.setVisible(multipleLists); + } + item = menu.findItem(MENU_MOVE_TO_LIST); + if (item != null) { + item.setVisible(multipleLists); + } + + item = menu.findItem(MENU_REMOVE_FROM_HISTORY); + if (null != item) { + if (hasSelection) { + item.setTitle(res.getString(R.string.cache_remove_from_history) + " (" + adapter.getChecked() + ")"); + } else { + item.setTitle(res.getString(R.string.cache_clear_history)); + } + } + + item = menu.findItem(MENU_EXPORT_NOTES); + if (null != item) { + if (hasSelection) { + item.setTitle(res.getString(R.string.cache_export_fieldnote) + " (" + adapter.getChecked() + ")"); + } else { + item.setTitle(res.getString(R.string.cache_export_fieldnote)); + } + } + + // Hide Field Notes export if there are no caches with logs + item = menu.findItem(MENU_EXPORT_NOTES); + if (null != item) { + item.setEnabled(false); + for (cgCache cache : cacheList) + { + if (cache.logOffline) + { + item.setEnabled(true); + break; + } + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.onPrepareOptionsMenu: " + e.toString()); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + switch (itemId) { + case MENU_SWITCH_SELECT_MODE: + if (adapter != null) { + adapter.switchSelectMode(); + } + return true; + case MENU_REFRESH_STORED: + refreshStored(); + return true; + case MENU_DROP_CACHES: + dropStored(); + return false; + case MENU_IMPORT_GPX: + importGpx(); + return false; + case MENU_CREATE_LIST: + createList(); + return false; + case MENU_DROP_LIST: + removeList(); + return false; + case MENU_INVERT_SELECTION: + if (adapter != null) { + adapter.invertSelection(); + } + return false; + case MENU_SORT_DISTANCE: + setComparator(item, null); + return false; + case MENU_SORT_DIFFICULTY: + setComparator(item, new DifficultyComparator()); + return false; + case MENU_SORT_TERRAIN: + setComparator(item, new TerrainComparator()); + return false; + case MENU_SORT_SIZE: + setComparator(item, new SizeComparator()); + return false; + case MENU_SORT_FAVORITES: + setComparator(item, new PopularityComparator()); + return false; + case MENU_SORT_NAME: + setComparator(item, new NameComparator()); + return false; + case MENU_SORT_GEOCODE: + setComparator(item, new GeocodeComparator()); + return false; + case MENU_SWITCH_LIST: + selectList(null); + return false; + case MENU_SORT_RATING: + setComparator(item, new RatingComparator()); + return false; + case MENU_SORT_VOTE: + setComparator(item, new VoteComparator()); + return false; + case MENU_SORT_INVENTORY: + setComparator(item, new InventoryComparator()); + return false; + case MENU_SORT_DATE: + setComparator(item, new DateComparator()); + return true; + case MENU_SORT_FINDS: + setComparator(item, new FindsComparator(app)); + return true; + case MENU_SORT_STATE: + setComparator(item, new StateComparator()); + return true; + case SUBMENU_FILTER_TYPE: + selectedFilter = res.getString(R.string.caches_filter_type); + contextMenuShowFilter = true; + openContextMenu(getListView()); + contextMenuShowFilter = false; + return false; + case SUBMENU_FILTER_SIZE: + selectedFilter = res.getString(R.string.caches_filter_size); + contextMenuShowFilter = true; + openContextMenu(getListView()); + contextMenuShowFilter = false; + return false; + case MENU_FILTER_TRACKABLES: + setFilter(new cgFilterByTrackables(res.getString(R.string.caches_filter_track))); + return false; + case MENU_FILTER_CLEAR: + if (adapter != null) { + setFilter(null); + } + return false; + case MENU_IMPORT_WEB: + importWeb(); + return false; + case MENU_EXPORT_NOTES: + exportFieldNotes(); + return false; + case MENU_REMOVE_FROM_HISTORY: + removeFromHistoryCheck(); + return false; + case MENU_MOVE_TO_LIST: + contextMenuMoveToList = true; + openContextMenu(getListView()); + contextMenuMoveToList = false; + return false; + } + + return CacheListAppFactory.onMenuItemSelected(item, geo, cacheList, this, res, searchId); + } + + private void setComparator(MenuItem item, + CacheComparator comparator) { + if (adapter != null) { + adapter.setComparator(comparator); + } + item.setChecked(true); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + super.onCreateContextMenu(menu, view, info); + + if (adapter == null) { + return; + } + + if (contextMenuMoveToList) { + createFakeContextMenuMoveToList(menu); + return; + } + + AdapterContextMenuInfo adapterInfo = null; + try { + adapterInfo = (AdapterContextMenuInfo) info; + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeocaches.onCreateContextMenu: " + e.toString()); + } + + if ((adapterInfo == null || adapterInfo.position < 0 || contextMenuShowFilter) && selectedFilter != null) { + // Context menu opened by selecting an option on the filter submenu + + if (selectedFilter.equals(res.getString(R.string.caches_filter_size))) { + menu.setHeaderTitle(res.getString(R.string.caches_filter_size_title)); + menu.add(0, MENU_FILTER_SIZE_MICRO, 0, res.getString(CacheSize.MICRO.stringId)); + menu.add(0, MENU_FILTER_SIZE_SMALL, 0, res.getString(CacheSize.SMALL.stringId)); + menu.add(0, MENU_FILTER_SIZE_REGULAR, 0, res.getString(CacheSize.REGULAR.stringId)); + menu.add(0, MENU_FILTER_SIZE_LARGE, 0, res.getString(CacheSize.LARGE.stringId)); + menu.add(0, MENU_FILTER_SIZE_OTHER, 0, res.getString(CacheSize.OTHER.stringId)); + menu.add(0, MENU_FILTER_SIZE_VIRTUAL, 0, res.getString(CacheSize.VIRTUAL.stringId)); + menu.add(0, MENU_FILTER_SIZE_NOT_CHOSEN, 0, res.getString(CacheSize.NOT_CHOSEN.stringId)); + } else if (selectedFilter.equals(res.getString(R.string.caches_filter_type))) { + menu.setHeaderTitle(res.getString(R.string.caches_filter_type_title)); + menu.add(0, MENU_FILTER_TYPE_TRADITIONAL, 0, res.getString(R.string.caches_filter_type_traditional)); + menu.add(0, MENU_FILTER_TYPE_MULTI, 0, res.getString(R.string.caches_filter_type_multi)); + menu.add(0, MENU_FILTER_TYPE_MYSTERY, 0, res.getString(R.string.caches_filter_type_mystery)); + menu.add(0, MENU_FILTER_TYPE_LETTERBOX, 0, res.getString(R.string.caches_filter_type_letterbox)); + menu.add(0, MENU_FILTER_TYPE_EVENT, 0, res.getString(R.string.caches_filter_type_event)); + menu.add(0, MENU_FILTER_TYPE_MEGA, 0, res.getString(R.string.caches_filter_type_mega)); + menu.add(0, MENU_FILTER_TYPE_EARTH, 0, res.getString(R.string.caches_filter_type_earth)); + menu.add(0, MENU_FILTER_TYPE_CITO, 0, res.getString(R.string.caches_filter_type_cito)); + menu.add(0, MENU_FILTER_TYPE_WEBCAM, 0, res.getString(R.string.caches_filter_type_webcam)); + menu.add(0, MENU_FILTER_TYPE_VIRTUAL, 0, res.getString(R.string.caches_filter_type_virtual)); + menu.add(0, MENU_FILTER_TYPE_WHERIGO, 0, res.getString(R.string.caches_filter_type_wherigo)); + menu.add(0, MENU_FILTER_TYPE_LOSTFOUND, 0, res.getString(R.string.caches_filter_type_lostfound)); + menu.add(0, MENU_FILTER_TYPE_APE, 0, res.getString(R.string.caches_filter_type_ape)); + menu.add(0, MENU_FILTER_TYPE_GCHQ, 0, res.getString(R.string.caches_filter_type_gchq)); + menu.add(0, MENU_FILTER_TYPE_GPS, 0, res.getString(R.string.caches_filter_type_gps)); + } + } else { + if (adapterInfo.position >= adapter.getCount()) { + return; + } + final cgCache cache = adapter.getItem(adapterInfo.position); + + if (StringUtils.isNotBlank(cache.name)) { + menu.setHeaderTitle(cache.name); + } else { + menu.setHeaderTitle(cache.geocode); + } + + if (cache.coords != null) { + menu.add(0, MENU_COMPASS, 0, res.getString(R.string.cache_menu_compass)); + SubMenu subMenu = menu.addSubMenu(1, 0, 0, res.getString(R.string.cache_menu_navigate)).setIcon(android.R.drawable.ic_menu_more); + NavigationAppFactory.addMenuItems(subMenu, this, res); + addVisitMenu(menu, cache); + // String label = settings.getLogOffline()? res.getString(R.string.cache_menu_visit_offline) : res.getString(R.string.cache_menu_visit); + // menu.add(0, MENU_LOG_VISIT, 0, label); + menu.add(0, MENU_CACHE_DETAILS, 0, res.getString(R.string.cache_menu_details)); + } + if (cache.reason >= 1) { + menu.add(0, MENU_DROP_CACHE, 0, res.getString(R.string.cache_offline_drop)); + List<cgList> cacheLists = app.getLists(); + int listCount = cacheLists.size(); + if (listCount > 1) { + SubMenu submenu = menu.addSubMenu(0, MENU_MOVE_TO_LIST, 0, res.getString(R.string.cache_menu_move_list)); + for (int i = 0; i < listCount; i++) { + cgList list = cacheLists.get(i); + submenu.add(Menu.NONE, CONTEXT_MENU_MOVE_TO_LIST + list.id, Menu.NONE, list.title); + } + } + } + } + } + + private void createFakeContextMenuMoveToList(ContextMenu menu) { + List<cgList> cacheLists = app.getLists(); + int listCount = cacheLists.size(); + menu.setHeaderTitle(res.getString(R.string.cache_menu_move_list)); + for (int i = 0; i < listCount; i++) { + cgList list = cacheLists.get(i); + menu.add(Menu.NONE, MENU_MOVE_SELECTED_OR_ALL_TO_LIST + list.id, Menu.NONE, list.title); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int id = item.getItemId(); + ContextMenu.ContextMenuInfo info = item.getMenuInfo(); + + // restore menu info for sub menu items, see + // https://code.google.com/p/android/issues/detail?id=7139 + if (info == null) { + info = lastMenuInfo; + lastMenuInfo = null; + } + + AdapterContextMenuInfo adapterInfo = null; + try { + adapterInfo = (AdapterContextMenuInfo) info; + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeocaches.onContextItemSelected: " + e.toString()); + } + + // the context menu may be invoked for the cache or for the filter list + int touchedPos = -1; + cgCache cache = null; + if (adapterInfo != null) { + touchedPos = adapterInfo.position; + if (touchedPos < adapter.getCount()) { + cache = adapter.getItem(touchedPos); + } + } + + if (id == MENU_COMPASS) { + Intent navigateIntent = new Intent(this, cgeonavigate.class); + navigateIntent.putExtra("latitude", cache.coords.getLatitude()); + navigateIntent.putExtra("longitude", cache.coords.getLongitude()); + navigateIntent.putExtra("geocode", cache.geocode.toUpperCase()); + navigateIntent.putExtra("name", cache.name); + + startActivity(navigateIntent); + + return true; + } else if (id == MENU_LOG_VISIT) { + return cache.logVisit(this); + } else if (id == MENU_CACHE_DETAILS) { + Intent cachesIntent = new Intent(this, cgeodetail.class); + cachesIntent.putExtra("geocode", cache.geocode.toUpperCase()); + cachesIntent.putExtra("name", cache.name); + startActivity(cachesIntent); + + return true; + } + else if (id == MENU_FILTER_SIZE_MICRO) { + return setFilter(new cgFilterBySize(CacheSize.MICRO)); + } else if (id == MENU_FILTER_SIZE_SMALL) { + return setFilter(new cgFilterBySize(CacheSize.SMALL)); + } else if (id == MENU_FILTER_SIZE_REGULAR) { + return setFilter(new cgFilterBySize(CacheSize.REGULAR)); + } else if (id == MENU_FILTER_SIZE_LARGE) { + return setFilter(new cgFilterBySize(CacheSize.LARGE)); + } else if (id == MENU_FILTER_SIZE_OTHER) { + return setFilter(new cgFilterBySize(CacheSize.OTHER)); + } else if (id == MENU_FILTER_SIZE_VIRTUAL) { + return setFilter(new cgFilterBySize(CacheSize.VIRTUAL)); + } else if (id == MENU_FILTER_SIZE_NOT_CHOSEN) { + return setFilter(new cgFilterBySize(CacheSize.NOT_CHOSEN)); + } else if (id == MENU_FILTER_TYPE_TRADITIONAL) { + return setFilter(new cgFilterByType("traditional")); + } else if (id == MENU_FILTER_TYPE_MULTI) { + return setFilter(new cgFilterByType("multi")); + } else if (id == MENU_FILTER_TYPE_MYSTERY) { + return setFilter(new cgFilterByType("mystery")); + } else if (id == MENU_FILTER_TYPE_LETTERBOX) { + return setFilter(new cgFilterByType("letterbox")); + } else if (id == MENU_FILTER_TYPE_EVENT) { + return setFilter(new cgFilterByType("event")); + } else if (id == MENU_FILTER_TYPE_MEGA) { + return setFilter(new cgFilterByType("mega")); + } else if (id == MENU_FILTER_TYPE_EARTH) { + return setFilter(new cgFilterByType("earth")); + } else if (id == MENU_FILTER_TYPE_CITO) { + return setFilter(new cgFilterByType("cito")); + } else if (id == MENU_FILTER_TYPE_WEBCAM) { + return setFilter(new cgFilterByType("webcam")); + } else if (id == MENU_FILTER_TYPE_VIRTUAL) { + return setFilter(new cgFilterByType("virtual")); + } else if (id == MENU_FILTER_TYPE_WHERIGO) { + return setFilter(new cgFilterByType("wherigo")); + } else if (id == MENU_FILTER_TYPE_LOSTFOUND) { + return setFilter(new cgFilterByType("lostfound")); + } else if (id == MENU_FILTER_TYPE_APE) { + return setFilter(new cgFilterByType("ape")); + } else if (id == MENU_FILTER_TYPE_GCHQ) { + return setFilter(new cgFilterByType("gchq")); + } else if (id == MENU_FILTER_TYPE_GPS) { + return setFilter(new cgFilterByType("gps")); + } else if (id == MENU_DROP_CACHE) { + cgBase.dropCache(app, this, cache, new Handler() { + @Override + public void handleMessage(Message msg) { + refreshCurrentList(); + } + }); + return true; + } else if (id >= CONTEXT_MENU_MOVE_TO_LIST && id < CONTEXT_MENU_MOVE_TO_LIST + 100) { + int newListId = id - CONTEXT_MENU_MOVE_TO_LIST; + if (cache != null) { + app.moveToList(cache.geocode, newListId); + } + adapter.resetChecks(); + + refreshCurrentList(); + return true; + } else if (id >= MENU_MOVE_SELECTED_OR_ALL_TO_LIST && id < MENU_MOVE_SELECTED_OR_ALL_TO_LIST + 100) { + int newListId = id - MENU_MOVE_SELECTED_OR_ALL_TO_LIST; + boolean moveAll = adapter.getChecked() == 0; + final List<cgCache> cacheListTemp = new ArrayList<cgCache>(cacheList); + for (cgCache c : cacheListTemp) { + if (moveAll || c.statusChecked) { + app.moveToList(c.geocode, newListId); + } + } + adapter.resetChecks(); + + refreshCurrentList(); + return true; + } + + // we must remember the menu info for the sub menu, there is a bug + // in Android: + // https://code.google.com/p/android/issues/detail?id=7139 + lastMenuInfo = info; + + if (cache != null) { + // create a searchId for a single cache (as if in details view) + Map<String, String> params = new HashMap<String, String>(); + params.put("geocode", cache.geocode); + final UUID singleSearchId = base.searchByGeocode(params, 0, false); + + if (NavigationAppFactory.onMenuItemSelected(item, geo, this, + res, cache, singleSearchId, null, null)) { + return true; + } + + int logType = id - MENU_LOG_VISIT_OFFLINE; + cache.logOffline(this, logType, settings, base); + } + return true; + } + + private boolean setFilter(cgFilter filter) { + if (adapter != null) { + adapter.setFilter(filter); + prepareFilterBar(); + return true; + } + return false; + } + + @Override + public boolean onKeyDown(int keyCode, KeyEvent event) { + if (keyCode == KeyEvent.KEYCODE_BACK) { + if (adapter != null) { + if (adapter.resetChecks()) { + return true; + } else if (adapter.getSelectMode()) { + adapter.setSelectMode(false, true); + return true; + } + } + } + return super.onKeyDown(keyCode, event); + } + + private void setAdapter() { + if (listFooter == null) { + if (inflater == null) { + inflater = getLayoutInflater(); + } + listFooter = inflater.inflate(R.layout.caches_footer, null); + + listFooter.setClickable(true); + listFooter.setOnClickListener(new moreCachesListener()); + } + if (listFooterText == null) { + listFooterText = (TextView) listFooter.findViewById(R.id.more_caches); + } + + if (adapter == null) { + final ListView list = getListView(); + + registerForContextMenu(list); + list.setLongClickable(true); + list.addFooterView(listFooter); + + adapter = new cgCacheListAdapter(this, settings, cacheList, base); + setListAdapter(adapter); + } else { + adapter.notifyDataSetChanged(); + } + adapter.reFilter(); + + if (geo != null) { + adapter.setActualCoordinates(geo.coordsNow); + } + if (dir != null) { + adapter.setActualHeading(dir.directionNow); + } + } + + private void setLoadingCaches() { + if (listFooter == null) { + return; + } + if (listFooterText == null) { + return; + } + + listFooterText.setText(res.getString(R.string.caches_more_caches_loading)); + listFooter.setClickable(false); + listFooter.setOnClickListener(null); + } + + private void setMoreCaches(boolean more) { + if (listFooter == null) { + return; + } + if (listFooterText == null) { + return; + } + + if (more == false) { + if (CollectionUtils.isEmpty(cacheList)) { + listFooterText.setText(res.getString(R.string.caches_no_cache)); + } else { + listFooterText.setText(res.getString(R.string.caches_more_caches_no)); + } + listFooter.setClickable(false); + listFooter.setOnClickListener(null); + } else { + listFooterText.setText(res.getString(R.string.caches_more_caches)); + listFooter.setClickable(true); + listFooter.setOnClickListener(new moreCachesListener()); + } + } + + private void init() { + // sensor & geolocation manager + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + if (settings.livelist == 1 && settings.useCompass == 1 && dir == null) { + dir = app.startDir(this, dirUpdate); + } + + if (cacheList != null) { + setTitle(title); + } + + if (CollectionUtils.isNotEmpty(cacheList)) { + final Integer count = app.getTotal(searchId); + if (count != null && count > 0) { + setTitle(title); + if (cacheList.size() < app.getTotal(searchId) && cacheList.size() < MAX_LIST_ITEMS) { + setMoreCaches(true); + } else { + setMoreCaches(false); + } + } else { + setTitle(title); + setMoreCaches(false); + } + } else { + setTitle(title); + } + + setAdapter(); + + if (geo != null) { + geoUpdate.updateLoc(geo); + } + if (dir != null) { + dirUpdate.updateDir(dir); + } + } + + private void importGpx() { + cgeogpxes.startSubActivity(this, listId); + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + refreshCurrentList(); + } + + public void refreshStored() { + if (adapter != null && adapter.getChecked() > 0) { + // there are some checked caches + detailTotal = adapter.getChecked(); + } else { + // no checked caches, download everything (when already stored - refresh them) + detailTotal = cacheList.size(); + } + detailProgress = 0; + + showProgress(false); + waitDialog = new ProgressDialog(this); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) { + try { + if (threadD != null) { + threadD.kill(); + } + + if (geo == null) { + geo = app.startGeo(cgeocaches.this, geoUpdate, base, settings, 0, 0); + } + if (settings.livelist == 1 && settings.useCompass == 1 && dir == null) { + dir = app.startDir(cgeocaches.this, dirUpdate); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.onOptionsItemSelected.onCancel: " + e.toString()); + } + } + }); + + waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + int etaTime = (int) ((detailTotal * 25) / 60); + if (etaTime < 1) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); + } else if (etaTime == 1) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + etaTime + " " + res.getString(R.string.caches_eta_min)); + } else { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + etaTime + " " + res.getString(R.string.caches_eta_mins)); + } + waitDialog.setCancelable(true); + waitDialog.setMax(detailTotal); + waitDialog.show(); + + detailProgressTime = System.currentTimeMillis(); + + threadD = new geocachesLoadDetails(loadDetailsHandler, listId); + threadD.start(); + } + + public void removeFromHistoryCheck() + { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setCancelable(true); + dialog.setTitle(res.getString(R.string.caches_removing_from_history)); + dialog.setMessage((adapter != null && adapter.getChecked() > 0) ? res.getString(R.string.cache_remove_from_history) + : res.getString(R.string.cache_clear_history)); + dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + removeFromHistory(); + dialog.cancel(); + } + }); + dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + } + + public void removeFromHistory() + { + if (adapter != null && adapter.getChecked() > 0) + { + // there are some checked caches + detailTotal = adapter.getChecked(); + } + else + { + // no checked caches, remove all + detailTotal = cacheList.size(); + } + detailProgress = 0; + + showProgress(false); + waitDialog = new ProgressDialog(this); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) + { + try + { + if (threadH != null) + { + threadH.kill(); + } + } catch (Exception e) + { + Log.e(cgSettings.tag, "cgeocaches.removeFromHistory.onCancel: " + e.toString()); + } + } + }); + + waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + waitDialog.setMessage(res.getString(R.string.caches_removing_from_history)); + + waitDialog.setCancelable(true); + waitDialog.setMax(detailTotal); + waitDialog.show(); + + threadH = new geocachesRemoveFromHistory(removeFromHistoryHandler); + threadH.start(); + } + + public void exportFieldNotes() + { + if (adapter != null && adapter.getChecked() > 0) + { + // there are some checked caches + detailTotal = adapter.getChecked(); + } + else + { + // no checked caches, export all + detailTotal = cacheList.size(); + } + detailProgress = 0; + + showProgress(false); + waitDialog = new ProgressDialog(this); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) + { + try + { + if (threadF != null) + { + threadF.kill(); + } + } catch (Exception e) + { + Log.e(cgSettings.tag, "cgeocaches.exportFieldNotes.onCancel: " + e.toString()); + } + } + }); + + waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + waitDialog.setMessage(res.getString(R.string.caches_exporting_fieldnote)); + + waitDialog.setCancelable(true); + waitDialog.setMax(detailTotal); + waitDialog.show(); + + threadF = new geocachesExportFieldNotes(exportFieldNotesHandler); + threadF.start(); + } + + public void importWeb() { + detailProgress = 0; + + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + showProgress(false); + waitDialog = new ProgressDialog(this); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) { + try { + if (threadW != null) { + threadW.kill(); + } + + if (geo == null) { + geo = app.startGeo(cgeocaches.this, geoUpdate, base, settings, 0, 0); + } + if (settings.livelist == 1 && settings.useCompass == 1 && dir == null) { + dir = app.startDir(cgeocaches.this, dirUpdate); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.importWeb.onCancel: " + e.toString()); + } + } + }); + + waitDialog.setProgressStyle(ProgressDialog.STYLE_SPINNER); + waitDialog.setMessage(res.getString(R.string.web_import_waiting)); + waitDialog.setCancelable(true); + waitDialog.show(); + + threadW = new geocachesLoadFromWeb(downloadFromWebHandler, listId); + threadW.start(); + } + + public void dropStored() { + AlertDialog.Builder dialog = new AlertDialog.Builder(this); + dialog.setCancelable(true); + dialog.setTitle(res.getString(R.string.caches_drop_stored)); + + if (adapter != null && adapter.getChecked() > 0) { + dialog.setMessage(res.getString(R.string.caches_drop_selected_ask)); + dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dropSelected(); + dialog.cancel(); + } + }); + } else { + dialog.setMessage(res.getString(R.string.caches_drop_all_ask)); + dialog.setPositiveButton(getString(android.R.string.yes), new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dropSelected(); + dialog.cancel(); + } + }); + } + dialog.setNegativeButton(getString(android.R.string.no), new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + } + + public void dropSelected() { + waitDialog = new ProgressDialog(this); + waitDialog.setMessage(res.getString(R.string.caches_drop_progress)); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) { + try { + if (threadR != null) { + threadR.kill(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.onOptionsItemSelected.onCancel: " + e.toString()); + } + } + }); + + waitDialog.setCancelable(true); + waitDialog.show(); + + threadR = new geocachesDropDetails(dropDetailsHandler); + threadR.start(); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + if (adapter == null) { + return; + } + + try { + if (cacheList != null && geo.coordsNow != null) { + adapter.setActualCoordinates(geo.coordsNow); + } + + if (settings.useCompass == 0 || (geo.speedNow != null && geo.speedNow > 5)) { // use GPS when speed is higher than 18 km/h + if (settings.useCompass == 0) { + if (geo.bearingNow != null) { + adapter.setActualHeading(geo.bearingNow); + } else { + adapter.setActualHeading(0f); + } + } + if (northHeading != null) { + adapter.setActualHeading(northHeading); + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class UpdateDirection extends cgUpdateDir { + + @Override + public void updateDir(cgDirection dir) { + if (settings.livelist == 0) { + return; + } + if (dir == null || dir.directionNow == null) { + return; + } + + northHeading = dir.directionNow; + if (northHeading != null && adapter != null && (geo == null || geo.speedNow == null || geo.speedNow <= 5)) { // use compass when speed is lower than 18 km/h) { + adapter.setActualHeading(northHeading); + } + } + } + + private class geocachesLoadByOffline extends Thread { + + private Handler handler = null; + private Geopoint coords = null; + private int listId = 1; + + public geocachesLoadByOffline(Handler handlerIn, final Geopoint coordsIn, int listIdIn) { + handler = handlerIn; + coords = coordsIn; + listId = listIdIn; + } + + @Override + public void run() { + Map<String, Object> params = new HashMap<String, Object>(); + if (coords != null) { + params.put("latitude", coords.getLatitude()); + params.put("longitude", coords.getLongitude()); + params.put("cachetype", settings.cacheType); + params.put("list", listId); + } + + searchId = base.searchByOffline(params); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadByHistory extends Thread { + + private Handler handler = null; + + public geocachesLoadByHistory(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + Map<String, Object> params = new HashMap<String, Object>(); + if (coords != null) { + params.put("cachetype", settings.cacheType); + } + + searchId = base.searchByHistory(params); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadNextPage extends cgSearchThread { + + private Handler handler = null; + + public geocachesLoadNextPage(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + searchId = base.searchByNextPage(this, searchId, 0, settings.showCaptcha); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadByCoords extends cgSearchThread { + + private Handler handler = null; + private Geopoint coords = null; + private String cachetype = null; + + public geocachesLoadByCoords(Handler handlerIn, final Geopoint coordsIn, String cachetypeIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + coords = coordsIn; + cachetype = cachetypeIn; + + if (coords == null) { + showToast(res.getString(R.string.warn_no_coordinates)); + + finish(); + return; + } + } + + @Override + public void run() { + Map<String, String> params = new HashMap<String, String>(); + params.put("latitude", String.format((Locale) null, "%.6f", coords.getLatitude())); + params.put("longitude", String.format((Locale) null, "%.6f", coords.getLongitude())); + params.put("cachetype", cachetype); + + searchId = base.searchByCoords(this, params, 0, settings.showCaptcha); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadByKeyword extends cgSearchThread { + + private Handler handler = null; + private String keyword = null; + private String cachetype = null; + + public geocachesLoadByKeyword(Handler handlerIn, String keywordIn, String cachetypeIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + keyword = keywordIn; + cachetype = cachetypeIn; + + if (keyword == null) { + showToast(res.getString(R.string.warn_no_keyword)); + + finish(); + return; + } + } + + @Override + public void run() { + Map<String, String> params = new HashMap<String, String>(); + params.put("keyword", keyword); + params.put("cachetype", cachetype); + + searchId = base.searchByKeyword(this, params, 0, settings.showCaptcha); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadByUserName extends cgSearchThread { + + private Handler handler = null; + private String username = null; + private String cachetype = null; + + public geocachesLoadByUserName(Handler handlerIn, String usernameIn, String cachetypeIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + username = usernameIn; + cachetype = cachetypeIn; + + if (StringUtils.isBlank(username)) { + showToast(res.getString(R.string.warn_no_username)); + + finish(); + return; + } + } + + @Override + public void run() { + Map<String, String> params = new HashMap<String, String>(); + params.put("username", username); + params.put("cachetype", cachetype); + + searchId = base.searchByUsername(this, params, 0, settings.showCaptcha); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadByOwner extends cgSearchThread { + + private Handler handler = null; + private String username = null; + private String cachetype = null; + + public geocachesLoadByOwner(Handler handlerIn, String usernameIn, String cachetypeIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + username = usernameIn; + cachetype = cachetypeIn; + + if (StringUtils.isBlank(username)) { + showToast(res.getString(R.string.warn_no_username)); + + finish(); + return; + } + } + + @Override + public void run() { + Map<String, String> params = new HashMap<String, String>(); + params.put("username", username); + params.put("cachetype", cachetype); + + searchId = base.searchByOwner(this, params, 0, settings.showCaptcha); + + handler.sendMessage(new Message()); + } + } + + private class geocachesLoadDetails extends Thread { + + private Handler handler = null; + private int reason = 1; + private volatile boolean needToStop = false; + private int checked = 0; + private long last = 0L; + + public geocachesLoadDetails(Handler handlerIn, int reasonIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + reason = reasonIn; + + if (adapter != null) { + checked = adapter.getChecked(); + } + } + + public void kill() { + needToStop = true; + } + + @Override + public void run() { + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + final List<cgCache> cacheListTemp = new ArrayList<cgCache>(cacheList); + for (cgCache cache : cacheListTemp) { + if (checked > 0 && cache.statusChecked == false) { + handler.sendEmptyMessage(0); + + yield(); + continue; + } + + try { + if (needToStop) { + Log.i(cgSettings.tag, "Stopped storing process."); + break; + } + + if ((System.currentTimeMillis() - last) < 1500) { + try { + int delay = 1000 + ((Double) (Math.random() * 1000)).intValue() - (int) (System.currentTimeMillis() - last); + if (delay < 0) { + delay = 500; + } + + Log.i(cgSettings.tag, "Waiting for next cache " + delay + " ms"); + sleep(delay); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesLoadDetails.sleep: " + e.toString()); + } + } + + if (needToStop) { + Log.i(cgSettings.tag, "Stopped storing process."); + break; + } + + detailProgress++; + base.storeCache(app, cgeocaches.this, cache, null, reason, null); + + handler.sendEmptyMessage(cacheList.indexOf(cache)); + + yield(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesLoadDetails: " + e.toString()); + } + + last = System.currentTimeMillis(); + } + cacheListTemp.clear(); + + handler.sendEmptyMessage(-1); + } + } + + private class geocachesLoadFromWeb extends Thread { + + private Handler handler = null; + private int reason = 1; + private volatile boolean needToStop = false; + + public geocachesLoadFromWeb(Handler handlerIn, int reasonIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + reason = reasonIn; + } + + public void kill() { + needToStop = true; + } + + @Override + public void run() { + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + int delay = -1; + int times = 0; + + while (times < 3 * 60 / 5) // maximum: 3 minutes, every 5 seconds + { + if (needToStop) + { + handler.sendEmptyMessage(-1); + break; + } + + //download new code + String deviceCode = settings.webDeviceCode; + if (deviceCode == null) { + deviceCode = ""; + } + cgResponse responseFromWeb = base.request(false, "send2.cgeo.org", "/read.html", "GET", "code=" + cgBase.urlencode_rfc3986(deviceCode), 0, true); + + if (responseFromWeb.getStatusCode() == 200) { + if (responseFromWeb.getData().length() > 2) { + + String GCcode = responseFromWeb.getData(); + + delay = 1; + Message mes = new Message(); + mes.what = 1; + mes.obj = GCcode; + handler.sendMessage(mes); + yield(); + + base.storeCache(app, cgeocaches.this, null, GCcode, + reason, null); + + Message mes1 = new Message(); + mes1.what = 2; + mes1.obj = GCcode; + handler.sendMessage(mes1); + yield(); + } else if ("RG".equals(responseFromWeb.getData())) { + //Server returned RG (registration) and this device no longer registered. + settings.setWebNameCode(null, null); + needToStop = true; + handler.sendEmptyMessage(-3); + return; + } else { + delay = 0; + handler.sendEmptyMessage(0); + yield(); + } + } + if (responseFromWeb.getStatusCode() != 200) { + needToStop = true; + handler.sendEmptyMessage(-2); + return; + } + + try { + yield(); + if (delay == 0) + { + sleep(5000); //No caches 5s + times++; + } else { + sleep(500); //Cache was loaded 0.5s + times = 0; + } + } catch (InterruptedException e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesLoadFromWeb.sleep: " + e.toString()); + } + } + handler.sendEmptyMessage(-1); + } + } + + private class geocachesDropDetails extends Thread { + + private Handler handler = null; + private volatile boolean needToStop = false; + private int checked = 0; + + public geocachesDropDetails(Handler handlerIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + + if (adapter != null) { + checked = adapter.getChecked(); + } + } + + public void kill() { + needToStop = true; + } + + @Override + public void run() { + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + final List<cgCache> cacheListTemp = new ArrayList<cgCache>(cacheList); + for (cgCache cache : cacheListTemp) { + if (checked > 0 && cache.statusChecked == false) { + continue; + } + + try { + if (needToStop) { + Log.i(cgSettings.tag, "Stopped dropping process."); + break; + } + + app.markDropped(cache.geocode); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesDropDetails: " + e.toString()); + } + } + cacheListTemp.clear(); + + handler.sendEmptyMessage(-1); + } + } + + private class geocachesRemoveFromHistory extends Thread { + + private Handler handler = null; + private volatile boolean needToStop = false; + private int checked = 0; + + public geocachesRemoveFromHistory(Handler handlerIn) { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + + if (adapter != null) { + checked = adapter.getChecked(); + } + } + + public void kill() { + needToStop = true; + } + + @Override + public void run() { + for (cgCache cache : cacheList) { + if (checked > 0 && cache.statusChecked == false) { + handler.sendEmptyMessage(0); + + yield(); + continue; + } + + try { + if (needToStop) { + Log.i(cgSettings.tag, "Stopped removing process."); + break; + } + + app.clearVisitDate(cache.geocode); + + handler.sendEmptyMessage(cacheList.indexOf(cache)); + + yield(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesRemoveFromHistory: " + e.toString()); + } + } + + handler.sendEmptyMessage(-1); + } + } + + private class geocachesExportFieldNotes extends Thread + { + private Handler handler = null; + private volatile boolean needToStop = false; + private int checked = 0; + + public geocachesExportFieldNotes(Handler handlerIn) + { + setPriority(Thread.MIN_PRIORITY); + + handler = handlerIn; + + if (adapter != null) + { + checked = adapter.getChecked(); + } + } + + public void kill() + { + needToStop = true; + } + + @Override + public void run() + { + SimpleDateFormat fieldNoteDateFormat = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); + StringBuffer fieldNoteBuffer = new StringBuffer(500); + + // We need our own HashMap because cgBase.LogTypes1 will give us localized and maybe + // different strings than gc.com expects in the field note + // We only need such logtypes that are possible to log via c:geo + Map<Integer, String> logTypes = new HashMap<Integer, String>(); + logTypes.put(cgBase.LOG_FOUND_IT, "Found it"); + logTypes.put(cgBase.LOG_DIDNT_FIND_IT, "Didn't find it"); + logTypes.put(cgBase.LOG_NOTE, "Write Note"); + logTypes.put(cgBase.LOG_NEEDS_ARCHIVE, "Needs archived"); + logTypes.put(cgBase.LOG_NEEDS_MAINTENANCE, "Needs Maintenance"); + logTypes.put(cgBase.LOG_WILL_ATTEND, "Will Attend"); + logTypes.put(cgBase.LOG_ATTENDED, "Attended"); + logTypes.put(cgBase.LOG_WEBCAM_PHOTO_TAKEN, "Webcam Photo Taken"); + + for (cgCache cache : cacheList) { + if (checked > 0 && cache.statusChecked == false) { + handler.sendEmptyMessage(0); + + yield(); + continue; + } + + try { + if (needToStop) + { + Log.i(cgSettings.tag, "Stopped exporting process."); + break; + } + + if (cache.logOffline) + { + cgLog log = app.loadLogOffline(cache.geocode); + + if (null != logTypes.get(log.type)) + { + fieldNoteBuffer.append(cache.geocode) + .append(',') + .append(fieldNoteDateFormat.format(new Date(log.date))) + .append(',') + .append(logTypes.get(log.type)) + .append(",\"") + .append(log.log.replaceAll("\"", "'")) + .append("\"\n"); + } + } + + detailProgress++; + + handler.sendEmptyMessage(cacheList.indexOf(cache)); + + yield(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesExportFieldNotes: " + e.toString()); + } + } + + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) + { + File exportLocation = new File(Environment.getExternalStorageDirectory().getAbsolutePath() + "/field-notes"); + exportLocation.mkdirs(); + + SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss"); + File exportFile = new File(exportLocation + "/" + fileNameDateFormat.format(new Date()) + ".txt"); + + OutputStream os = null; + Writer fw = null; + try + { + os = new FileOutputStream(exportFile); + fw = new OutputStreamWriter(os, "ISO-8859-1"); // TODO: gc.com doesn't support UTF-8 + fw.write(fieldNoteBuffer.toString()); + + Message.obtain(handler, -2, exportFile).sendToTarget(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesExportFieldNotes: " + e.toString()); + handler.sendEmptyMessage(-3); + } finally + { + if (fw != null) + { + try { + fw.close(); + } catch (IOException e) { + Log.e(cgSettings.tag, "cgeocaches.geocachesExportFieldNotes: " + e.toString()); + } + } + } + } + + handler.sendEmptyMessage(-1); + } + } + + private class moreCachesListener implements View.OnClickListener { + + @Override + public void onClick(View arg0) { + showProgress(true); + setLoadingCaches(); + listFooter.setOnClickListener(null); + + geocachesLoadNextPage thread; + thread = new geocachesLoadNextPage(loadNextPageHandler); + thread.setRecaptchaHandler(new cgSearchHandler(cgeocaches.this, res, thread)); + thread.start(); + } + } + + private void hideLoading() { + final ListView list = getListView(); + final RelativeLayout loading = (RelativeLayout) findViewById(R.id.loading); + + if (list.getVisibility() == View.GONE) { + list.setVisibility(View.VISIBLE); + loading.setVisibility(View.GONE); + } + } + + public void selectList(View view) { + if (type.equals("offline") == false) { + return; + } + + lists = app.getLists(); + + if (lists == null) { + return; + } + + final List<CharSequence> listsTitle = new ArrayList<CharSequence>(); + for (cgList list : lists) { + listsTitle.add(list.title); + } + + final CharSequence[] items = new CharSequence[listsTitle.size()]; + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(res.getString(R.string.list_title)); + builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialogInterface, int item) { + switchListById(lists.get(item).id); + + return; + } + }); + builder.create().show(); + } + + public void switchListById(int id) { + cgList list = null; + + if (id >= 0) { + list = app.getList(id); + } else { + return; + } + + if (list == null) { + return; + } + + listId = list.id; + title = list.title; + + settings.saveLastList(listId); + + showProgress(true); + setLoadingCaches(); + + (new moveCachesToList(listId, new MoveHandler())).start(); + } + + private class MoveHandler extends Handler { + @Override + public void handleMessage(Message msg) { + Thread threadPure = new geocachesLoadByOffline(loadCachesHandler, coords, msg.what); + threadPure.start(); + } + } + + private class moveCachesToList extends Thread { + int listId = -1; + Handler handler = null; + + public moveCachesToList(int listIdIn, Handler handlerIn) { + listId = listIdIn; + handler = handlerIn; + } + + @Override + public void run() { + int checked = adapter.getChecked(); + if (checked > 0) { + final List<cgCache> cacheListTemp = new ArrayList<cgCache>(cacheList); + for (cgCache cache : cacheListTemp) { + if (cache.statusChecked) { + app.moveToList(cache.geocode, listId); + } + } + } + + handler.sendEmptyMessage(listId); + } + } + + private void createList() { + final AlertDialog.Builder alert = new AlertDialog.Builder(this); + final View view = inflater.inflate(R.layout.list_create_dialog, null); + final EditText input = (EditText) view.findViewById(R.id.text); + + alert.setTitle(R.string.list_dialog_create_title); + alert.setView(view); + alert.setPositiveButton(R.string.list_dialog_create, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + String value = input.getText().toString(); + // remove whitespaces added by autocompletion of Android keyboard + if (value != null) { + value = value.trim(); + } + + if (StringUtils.isNotBlank(value)) { + int newId = app.createList(value); + + if (newId >= 10) { + showToast(res.getString(R.string.list_dialog_create_ok)); + } else { + showToast(res.getString(R.string.list_dialog_create_err)); + } + } + } + }); + alert.setNegativeButton(res.getString(R.string.list_dialog_cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dialog.dismiss(); + } + }); + + alert.show(); + } + + private void removeListInternal() { + boolean status = app.removeList(listId); + + if (status) { + showToast(res.getString(R.string.list_dialog_remove_ok)); + switchListById(1); + } else { + showToast(res.getString(R.string.list_dialog_remove_err)); + } + } + + private void removeList() { + // if there are no caches on this list, don't bother the user with questions. + // there is no harm in deleting the list, he could recreate it easily + if (cacheList != null && cacheList.isEmpty()) { + removeListInternal(); + return; + } + + // ask him, if there are caches on the list + final AlertDialog.Builder alert = new AlertDialog.Builder(this); + + alert.setTitle(R.string.list_dialog_remove_title); + alert.setMessage(R.string.list_dialog_remove_description); + alert.setPositiveButton(R.string.list_dialog_remove, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + removeListInternal(); + } + }); + alert.setNegativeButton(res.getString(R.string.list_dialog_cancel), new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int whichButton) { + dialog.dismiss(); + } + }); + + alert.show(); + } + + public void goMap(View view) { + if (searchId == null || CollectionUtils.isEmpty(cacheList)) { + showToast(res.getString(R.string.warn_no_cache_coord)); + + return; + } + + Intent mapIntent = new Intent(this, settings.getMapFactory().getMapClass()); + mapIntent.putExtra("detail", false); + mapIntent.putExtra("searchid", searchId.toString()); + + startActivity(mapIntent); + } + + public void goManual(View view) { + if (type != null && type.equals("offline")) { + ActivityMixin.goManual(this, "c:geo-stored"); + } else if (type != null && type.equals("history")) { + ActivityMixin.goManual(this, "c:geo-history"); + } else { + ActivityMixin.goManual(this, "c:geo-nearby"); + } + } + + private void refreshCurrentList() { + switchListById(listId); + } + + public static void startActivityOffline(final Context context) { + final Intent cachesIntent = new Intent(context, cgeocaches.class); + cachesIntent.putExtra(EXTRAS_LIST_TYPE, "offline"); + context.startActivity(cachesIntent); + } + + public static void startActivityCachesAround(final AbstractActivity context, final Geopoint coords) { + cgeocaches cachesActivity = new cgeocaches(); + + Intent cachesIntent = new Intent(context, cachesActivity.getClass()); + cachesIntent.putExtra("type", "coordinate"); + cachesIntent.putExtra("latitude", coords.getLatitude()); + cachesIntent.putExtra("longitude", coords.getLongitude()); + cachesIntent.putExtra("cachetype", context.getSettings().cacheType); + + context.startActivity(cachesIntent); + } + + public static void startActivityCacheOwner(final AbstractActivity context, final String userName) { + final Intent cachesIntent = new Intent(context, cgeocaches.class); + + cachesIntent.putExtra("type", "owner"); + cachesIntent.putExtra("username", userName); + cachesIntent.putExtra("cachetype", context.getSettings().cacheType); + + context.startActivity(cachesIntent); + } + + public static void startActivityCacheUser(final AbstractActivity context, final String userName) { + final Intent cachesIntent = new Intent(context, cgeocaches.class); + + cachesIntent.putExtra("type", "username"); + cachesIntent.putExtra("username", userName); + cachesIntent.putExtra("cachetype", context.getSettings().cacheType); + + context.startActivity(cachesIntent); + } + + private void prepareFilterBar() { + TextView filterTextView = (TextView) findViewById(R.id.filter_text); + View filterBar = findViewById(R.id.filter_bar); + String cacheType = "", filter = ""; + + if (settings.cacheType != null || adapter.isFilter()) { + if (settings.cacheType != null) { + cacheType = cgBase.cacheTypesInv.get(settings.cacheType); + } + if (adapter.isFilter()) { + filter = adapter.getFilterName(); + } + + if (settings.cacheType != null && adapter.isFilter()) { + filter = ", " + filter; + } + + filterTextView.setText(cacheType + filter); + filterBar.setVisibility(View.VISIBLE); + } + else { + filterBar.setVisibility(View.GONE); + } + } +} diff --git a/main/src/cgeo/geocaching/cgeocoords.java b/main/src/cgeo/geocaching/cgeocoords.java new file mode 100644 index 0000000..ec56a4a --- /dev/null +++ b/main/src/cgeo/geocaching/cgeocoords.java @@ -0,0 +1,494 @@ +package cgeo.geocaching; + +import cgeo.geocaching.cgSettings.coordInputFormatEnum; +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.Geopoint.MalformedCoordinateException; +import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.geopoint.GeopointParser.ParseException; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Dialog; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +public class cgeocoords extends Dialog { + + private AbstractActivity context = null; + private cgSettings settings = null; + private cgGeo geo = null; + private Geopoint gp = null; + + private EditText eLat, eLon; + private Button bLat, bLon; + private EditText eLatDeg, eLatMin, eLatSec, eLatSub; + private EditText eLonDeg, eLonMin, eLonSec, eLonSub; + private TextView tLatSep1, tLatSep2, tLatSep3; + private TextView tLonSep1, tLonSep2, tLonSep3; + + private Spinner spinner; + + CoordinateUpdate cuListener; + + coordInputFormatEnum currentFormat = null; + + public cgeocoords(final AbstractActivity contextIn, cgSettings settingsIn, final Geopoint gpIn, final cgGeo geoIn) { + super(contextIn); + context = contextIn; + settings = settingsIn; + geo = geoIn; + + if (gpIn != null) { + gp = gpIn; + } else if (geo != null && geo.coordsNow != null) { + gp = geo.coordsNow; + } + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); + } catch (Exception e) { + // nothing + } + + setContentView(R.layout.coords); + + spinner = (Spinner) findViewById(R.id.spinnerCoordinateFormats); + ArrayAdapter<CharSequence> adapter = + ArrayAdapter.createFromResource(context, + R.array.waypoint_coordinate_formats, + android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + spinner.setAdapter(adapter); + spinner.setSelection(settings.getCoordInputFormat().ordinal()); + spinner.setOnItemSelectedListener(new CoordinateFormatListener()); + + bLat = (Button) findViewById(R.id.ButtonLat); + eLat = (EditText) findViewById(R.id.latitude); + eLatDeg = (EditText) findViewById(R.id.EditTextLatDeg); + eLatMin = (EditText) findViewById(R.id.EditTextLatMin); + eLatSec = (EditText) findViewById(R.id.EditTextLatSec); + eLatSub = (EditText) findViewById(R.id.EditTextLatSecFrac); + tLatSep1 = (TextView) findViewById(R.id.LatSeparator1); + tLatSep2 = (TextView) findViewById(R.id.LatSeparator2); + tLatSep3 = (TextView) findViewById(R.id.LatSeparator3); + + bLon = (Button) findViewById(R.id.ButtonLon); + eLon = (EditText) findViewById(R.id.longitude); + eLonDeg = (EditText) findViewById(R.id.EditTextLonDeg); + eLonMin = (EditText) findViewById(R.id.EditTextLonMin); + eLonSec = (EditText) findViewById(R.id.EditTextLonSec); + eLonSub = (EditText) findViewById(R.id.EditTextLonSecFrac); + tLonSep1 = (TextView) findViewById(R.id.LonSeparator1); + tLonSep2 = (TextView) findViewById(R.id.LonSeparator2); + tLonSep3 = (TextView) findViewById(R.id.LonSeparator3); + + eLatDeg.addTextChangedListener(new TextChanged(eLatDeg)); + eLatMin.addTextChangedListener(new TextChanged(eLatMin)); + eLatSec.addTextChangedListener(new TextChanged(eLatSec)); + eLatSub.addTextChangedListener(new TextChanged(eLatSub)); + eLonDeg.addTextChangedListener(new TextChanged(eLonDeg)); + eLonMin.addTextChangedListener(new TextChanged(eLonMin)); + eLonSec.addTextChangedListener(new TextChanged(eLonSec)); + eLonSub.addTextChangedListener(new TextChanged(eLonSub)); + + bLat.setOnClickListener(new ButtonClickListener()); + bLon.setOnClickListener(new ButtonClickListener()); + + Button buttonCurrent = (Button) findViewById(R.id.current); + buttonCurrent.setOnClickListener(new CurrentListener()); + Button buttonDone = (Button) findViewById(R.id.done); + buttonDone.setOnClickListener(new InputDoneListener()); + } + + private void updateGUI() { + if (gp == null) + return; + Double lat = 0.0; + if (gp.getLatitude() < 0) { + bLat.setText("S"); + } else { + bLat.setText("N"); + } + + lat = Math.abs(gp.getLatitude()); + + Double lon = 0.0; + if (gp.getLongitude() < 0) { + bLon.setText("W"); + } else { + bLon.setText("E"); + } + + lon = Math.abs(gp.getLongitude()); + + int latDeg = (int) Math.floor(lat); + int latDegFrac = (int) Math.round((lat - latDeg) * 100000); + + int latMin = (int) Math.floor((lat - latDeg) * 60); + int latMinFrac = (int) Math.round(((lat - latDeg) * 60 - latMin) * 1000); + + int latSec = (int) Math.floor(((lat - latDeg) * 60 - latMin) * 60); + int latSecFrac = (int) Math.round((((lat - latDeg) * 60 - latMin) * 60 - latSec) * 1000); + + int lonDeg = (int) Math.floor(lon); + int lonDegFrac = (int) Math.round((lon - lonDeg) * 100000); + + int lonMin = (int) Math.floor((lon - lonDeg) * 60); + int lonMinFrac = (int) Math.round(((lon - lonDeg) * 60 - lonMin) * 1000); + + int lonSec = (int) Math.floor(((lon - lonDeg) * 60 - lonMin) * 60); + int lonSecFrac = (int) Math.round((((lon - lonDeg) * 60 - lonMin) * 60 - lonSec) * 1000); + + switch (currentFormat) { + case Plain: + findViewById(R.id.coordTable).setVisibility(View.GONE); + eLat.setVisibility(View.VISIBLE); + eLon.setVisibility(View.VISIBLE); + eLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + eLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); + break; + case Deg: // DDD.DDDDD° + findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + eLat.setVisibility(View.GONE); + eLon.setVisibility(View.GONE); + eLatSec.setVisibility(View.GONE); + eLonSec.setVisibility(View.GONE); + tLatSep3.setVisibility(View.GONE); + tLonSep3.setVisibility(View.GONE); + eLatSub.setVisibility(View.GONE); + eLonSub.setVisibility(View.GONE); + + tLatSep1.setText("."); + tLonSep1.setText("."); + tLatSep2.setText("°"); + tLonSep2.setText("°"); + + eLatDeg.setText(addZeros(latDeg, 2) + Integer.toString(latDeg)); + eLatMin.setText(addZeros(latDegFrac, 5) + Integer.toString(latDegFrac)); + eLonDeg.setText(addZeros(latDeg, 3) + Integer.toString(lonDeg)); + eLonMin.setText(addZeros(lonDegFrac, 5) + Integer.toString(lonDegFrac)); + break; + case Min: // DDD° MM.MMM + findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + eLat.setVisibility(View.GONE); + eLon.setVisibility(View.GONE); + eLatSec.setVisibility(View.VISIBLE); + eLonSec.setVisibility(View.VISIBLE); + tLatSep3.setVisibility(View.VISIBLE); + tLonSep3.setVisibility(View.VISIBLE); + eLatSub.setVisibility(View.GONE); + eLonSub.setVisibility(View.GONE); + + tLatSep1.setText("°"); + tLonSep1.setText("°"); + tLatSep2.setText("."); + tLonSep2.setText("."); + tLatSep3.setText("'"); + tLonSep3.setText("'"); + + eLatDeg.setText(addZeros(latDeg, 2) + Integer.toString(latDeg)); + eLatMin.setText(addZeros(latMin, 2) + Integer.toString(latMin)); + eLatSec.setText(addZeros(latMinFrac, 3) + Integer.toString(latMinFrac)); + eLonDeg.setText(addZeros(lonDeg, 3) + Integer.toString(lonDeg)); + eLonMin.setText(addZeros(lonMin, 2) + Integer.toString(lonMin)); + eLonSec.setText(addZeros(lonMinFrac, 3) + Integer.toString(lonMinFrac)); + break; + case Sec: // DDD° MM SS.SSS + findViewById(R.id.coordTable).setVisibility(View.VISIBLE); + eLat.setVisibility(View.GONE); + eLon.setVisibility(View.GONE); + eLatSec.setVisibility(View.VISIBLE); + eLonSec.setVisibility(View.VISIBLE); + tLatSep3.setVisibility(View.VISIBLE); + tLonSep3.setVisibility(View.VISIBLE); + eLatSub.setVisibility(View.VISIBLE); + eLonSub.setVisibility(View.VISIBLE); + + tLatSep1.setText("°"); + tLonSep1.setText("°"); + tLatSep2.setText("'"); + tLonSep2.setText("'"); + tLatSep3.setText("."); + tLonSep3.setText("."); + + eLatDeg.setText(addZeros(latDeg, 2) + Integer.toString(latDeg)); + eLatMin.setText(addZeros(latMin, 2) + Integer.toString(latMin)); + eLatSec.setText(addZeros(latSec, 2) + Integer.toString(latSec)); + eLatSub.setText(addZeros(latSecFrac, 3) + Integer.toString(latSecFrac)); + eLonDeg.setText(addZeros(lonDeg, 3) + Integer.toString(lonDeg)); + eLonMin.setText(addZeros(lonMin, 2) + Integer.toString(lonMin)); + eLonSec.setText(addZeros(lonSec, 2) + Integer.toString(lonSec)); + eLonSub.setText(addZeros(lonSecFrac, 3) + Integer.toString(lonSecFrac)); + break; + } + } + + private static String addZeros(int value, int len) { + StringBuilder zeros = new StringBuilder(); + if (value == 0) { + value = 1; + } + double wantedLength = Math.pow(10, len - 1); + while (value < wantedLength) { + zeros.append('0'); + value *= 10; + } + return zeros.toString(); + } + + private class ButtonClickListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + Button e = (Button) v; + CharSequence text = e.getText(); + if (StringUtils.isBlank(text)) { + return; + } + switch (text.charAt(0)) { + case 'N': + e.setText("S"); + break; + case 'S': + e.setText("N"); + break; + case 'E': + e.setText("W"); + break; + case 'W': + e.setText("E"); + break; + } + calc(true); + } + } + + private class TextChanged implements TextWatcher { + + private EditText editText; + + public TextChanged(EditText editText) { + this.editText = editText; + } + + @Override + public void afterTextChanged(Editable s) { + /* + * Max lengths, depending on currentFormat + * + * formatPlain = disabled + * DEG MIN SEC SUB + * formatDeg 2/3 5 - - + * formatMin 2/3 2 3 - + * formatSec 2/3 2 2 3 + */ + + if (currentFormat == coordInputFormatEnum.Plain) + return; + + int maxLength = 2; + if (editText == eLonDeg || editText == eLatSub || editText == eLonSub) { + maxLength = 3; + } + if ((editText == eLatMin || editText == eLonMin) && currentFormat == coordInputFormatEnum.Deg) { + maxLength = 5; + } + if ((editText == eLatSec || editText == eLonSec) && currentFormat == coordInputFormatEnum.Min) { + maxLength = 3; + } + + if (s.length() == maxLength) { + if (editText == eLatDeg) + eLatMin.requestFocus(); + else if (editText == eLatMin) + if (eLatSec.getVisibility() == View.GONE) + eLonDeg.requestFocus(); + else + eLatSec.requestFocus(); + else if (editText == eLatSec) + if (eLatSub.getVisibility() == View.GONE) + eLonDeg.requestFocus(); + else + eLatSub.requestFocus(); + else if (editText == eLatSub) + eLonDeg.requestFocus(); + else if (editText == eLonDeg) + eLonMin.requestFocus(); + else if (editText == eLonMin) + if (eLonSec.getVisibility() == View.GONE) + eLatDeg.requestFocus(); + else + eLonSec.requestFocus(); + else if (editText == eLonSec) + if (eLonSub.getVisibility() == View.GONE) + eLatDeg.requestFocus(); + else + eLonSub.requestFocus(); + else if (editText == eLonSub) + eLatDeg.requestFocus(); + } + } + + @Override + public void beforeTextChanged(CharSequence s, int start, int count, int after) { + } + + @Override + public void onTextChanged(CharSequence s, int start, int before, int count) { + } + + } + + private boolean calc(final boolean signalError) { + if (currentFormat == coordInputFormatEnum.Plain) { + try { + gp = new Geopoint(eLat.getText().toString() + " " + eLon.getText().toString()); + } catch (ParseException e) { + if (signalError) { + context.showToast(context.getResources().getString(R.string.err_parse_lat_lon)); + } + return false; + } catch (MalformedCoordinateException e) { + if (signalError) { + context.showToast(context.getResources().getString(R.string.err_invalid_lat_lon)); + } + return false; + } + return true; + } + + int latDeg = 0, latMin = 0, latSec = 0; + int lonDeg = 0, lonMin = 0, lonSec = 0; + Double latDegFrac = 0.0, latMinFrac = 0.0, latSecFrac = 0.0; + Double lonDegFrac = 0.0, lonMinFrac = 0.0, lonSecFrac = 0.0; + + try { + latDeg = Integer.parseInt(eLatDeg.getText().toString()); + lonDeg = Integer.parseInt(eLonDeg.getText().toString()); + latDegFrac = Double.parseDouble("0." + eLatMin.getText().toString()); + lonDegFrac = Double.parseDouble("0." + eLonMin.getText().toString()); + latMin = Integer.parseInt(eLatMin.getText().toString()); + lonMin = Integer.parseInt(eLonMin.getText().toString()); + latMinFrac = Double.parseDouble("0." + eLatSec.getText().toString()); + lonMinFrac = Double.parseDouble("0." + eLonSec.getText().toString()); + latSec = Integer.parseInt(eLatSec.getText().toString()); + lonSec = Integer.parseInt(eLonSec.getText().toString()); + latSecFrac = Double.parseDouble("0." + eLatSub.getText().toString()); + lonSecFrac = Double.parseDouble("0." + eLonSub.getText().toString()); + + } catch (NumberFormatException e) { + } + + double latitude = 0.0; + double longitude = 0.0; + + switch (currentFormat) { + case Deg: + latitude = latDeg + latDegFrac; + longitude = lonDeg + lonDegFrac; + break; + case Min: + latitude = latDeg + latMin / 60.0 + latMinFrac / 60.0; + longitude = lonDeg + lonMin / 60.0 + lonMinFrac / 60.0; + break; + case Sec: + latitude = latDeg + latMin / 60.0 + latSec / 60.0 / 60.0 + latSecFrac / 60.0 / 60.0; + longitude = lonDeg + lonMin / 60.0 + lonSec / 60.0 / 60.0 + lonSecFrac / 60.0 / 60.0; + break; + } + latitude *= (bLat.getText().toString().equalsIgnoreCase("S") ? -1 : 1); + longitude *= (bLon.getText().toString().equalsIgnoreCase("W") ? -1 : 1); + + try { + gp = new Geopoint(latitude, longitude); + } catch (MalformedCoordinateException e) { + if (signalError) { + context.showToast(context.getResources().getString(R.string.err_invalid_lat_lon)); + } + return false; + } + return true; + } + + private class CoordinateFormatListener implements OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView<?> parent, View view, int pos, long id) { + // Ignore first call, which comes from onCreate() + if (currentFormat != null) { + + // Start new format with an acceptable value: either the current one + // entered by the user, else our current coordinates, else (0,0). + if (!calc(false)) { + if (geo != null && geo.coordsNow != null) { + gp = geo.coordsNow; + } else { + gp = new Geopoint(0, 0); + } + } + } + + currentFormat = coordInputFormatEnum.fromInt(pos); + settings.setCoordInputFormat(currentFormat); + updateGUI(); + } + + @Override + public void onNothingSelected(AdapterView<?> arg0) { + } + + } + + private class CurrentListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + if (geo == null || geo.coordsNow == null) { + context.showToast(context.getResources().getString(R.string.err_point_unknown_position)); + return; + } + + gp = geo.coordsNow; + updateGUI(); + } + } + + private class InputDoneListener implements View.OnClickListener { + + @Override + public void onClick(View v) { + if (!calc(true)) + return; + if (gp != null) + cuListener.update(gp); + dismiss(); + } + } + + public void setOnCoordinateUpdate(CoordinateUpdate cu) { + cuListener = cu; + } + + public interface CoordinateUpdate { + public void update(final Geopoint gp); + } + +} diff --git a/main/src/cgeo/geocaching/cgeodate.java b/main/src/cgeo/geocaching/cgeodate.java new file mode 100644 index 0000000..85ddeac --- /dev/null +++ b/main/src/cgeo/geocaching/cgeodate.java @@ -0,0 +1,53 @@ +package cgeo.geocaching; + +import android.app.Activity; +import android.app.Dialog; +import android.os.Bundle; +import android.view.ViewGroup.LayoutParams; +import android.view.Window; +import android.widget.DatePicker; + +import java.util.Calendar; + +public class cgeodate extends Dialog { + + private cgLogForm parent = null; + private Calendar date = Calendar.getInstance(); + + public cgeodate(Activity contextIn, cgLogForm parentIn, Calendar dateIn) { + super(contextIn); + + // init + date = dateIn; + parent = parentIn; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + try { + requestWindowFeature(Window.FEATURE_NO_TITLE); + getWindow().setLayout(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT); + } catch (Exception e) { + // nothing + } + + setContentView(R.layout.date); + + DatePicker picker = (DatePicker) findViewById(R.id.picker); + picker.init(date.get(Calendar.YEAR), date.get(Calendar.MONTH), date.get(Calendar.DATE), new pickerListener()); + } + + public class pickerListener implements DatePicker.OnDateChangedListener { + + @Override + public void onDateChanged(DatePicker picker, int year, int month, int day) { + if (parent != null) { + date.set(year, month, day); + + parent.setDate(date); + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgeodetail.java b/main/src/cgeo/geocaching/cgeodetail.java new file mode 100644 index 0000000..8ae91cf --- /dev/null +++ b/main/src/cgeo/geocaching/cgeodetail.java @@ -0,0 +1,2128 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.apps.cache.GeneralAppsFactory; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.compatibility.Compatibility; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.ContentValues; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.res.Configuration; +import android.database.Cursor; +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.text.Spannable; +import android.text.SpannableStringBuilder; +import android.text.Spanned; +import android.text.method.LinkMovementMethod; +import android.text.style.StrikethroughSpan; +import android.util.DisplayMetrics; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Display; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.ViewGroup; +import android.view.ViewParent; +import android.view.WindowManager; +import android.view.ViewGroup.LayoutParams; +import android.widget.Button; +import android.widget.FrameLayout; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.net.URLEncoder; +import java.util.ArrayList; +import java.util.Collections; +import java.util.Comparator; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; +import java.util.Map.Entry; + +/** + * Activity to display all details of a cache like owner, difficulty, description etc. + * + */ +public class cgeodetail extends AbstractActivity { + + public cgeodetail() { + super("c:geo-cache-details"); + } + + public UUID searchId = null; + public cgCache cache = null; + public String geocode = null; + public String name = null; + public String guid = null; + private LayoutInflater inflater = null; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private float pixelRatio = 1; + private TextView cacheDistance = null; + private String contextMenuUser = null; + private ProgressDialog waitDialog = null; + private ProgressDialog descDialog = null; + private Spanned longDesc = null; + private Boolean longDescDisplayed = false; + private loadCache threadCache = null; + private loadLongDesc threadLongDesc = null; + private Thread storeThread = null; + private Thread refreshThread = null; + private ProgressDialog storeDialog = null; + private ProgressDialog refreshDialog = null; + private ProgressDialog dropDialog = null; + private ProgressDialog watchlistDialog = null; // progress dialog for watchlist add/remove + private Thread watchlistThread = null; // thread for watchlist add/remove + private Map<Integer, String> calendars = new HashMap<Integer, String>(); + + private ViewGroup attributeIconsLayout; // layout for attribute icons + private ViewGroup attributeDescriptionsLayout; // layout for attribute descriptions + private boolean attributesShowAsIcons = true; // default: show icons + /** + * <code>noAttributeImagesFound</code> This will be the case if the cache was imported with an older version of + * c:geo. + * These older versions parsed the attribute description from the tooltip in the web + * page and put them into the DB. No icons can be matched for these. + */ + private boolean noAttributeIconsFound = false; + private int attributeBoxMaxWidth; + /** + * differentiate between whether we are starting the activity for a cache or if we return to the activity from + * another activity that we started in front + */ + private boolean disableResumeSetView = false; + + private Handler storeCacheHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + storeThread = null; + + try { + cache = app.getCache(searchId); // reload cache details + } catch (Exception e) { + showToast(res.getString(R.string.err_store_failed)); + + Log.e(cgSettings.tag, "cgeodetail.storeCacheHandler: " + e.toString()); + } + + setView(); + } + }; + + private Handler refreshCacheHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + refreshThread = null; + + try { + cache = app.getCache(searchId); // reload cache details + } catch (Exception e) { + showToast(res.getString(R.string.err_refresh_failed)); + + Log.e(cgSettings.tag, "cgeodetail.refreshCacheHandler: " + e.toString()); + } + + setView(); + } + }; + + private Handler dropCacheHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + try { + cache = app.getCache(searchId); // reload cache details + } catch (Exception e) { + showToast(res.getString(R.string.err_drop_failed)); + + Log.e(cgSettings.tag, "cgeodetail.dropCacheHandler: " + e.toString()); + } + + setView(); + } + }; + + private Handler loadCacheHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (searchId == null) { + showToast(res.getString(R.string.err_dwld_details_failed)); + + finish(); + return; + } + + if (app.getError(searchId) != null) { + showToast(res.getString(R.string.err_dwld_details_failed_reason) + " " + app.getError(searchId) + "."); + + finish(); + return; + } + + setView(); + + if (settings.autoLoadDesc == 1) { + try { + loadLongDesc(); + } catch (Exception e) { + // activity is not visible + } + } + + (new loadMapPreview(loadMapPreviewHandler)).start(); + } + }; + + final Handler loadMapPreviewHandler = new Handler() { + @Override + public void handleMessage(Message message) { + BitmapDrawable image = (BitmapDrawable) message.obj; + if (image == null) { + return; + } + ScrollView scroll = (ScrollView) findViewById(R.id.details_list_box); + final ImageView view = (ImageView) findViewById(R.id.map_preview); + if (view == null) { + return; + } + Bitmap bitmap = image.getBitmap(); + if (bitmap == null || bitmap.getWidth() <= 10) { + return; + } + view.setImageDrawable(image); + + if (scroll.getScrollY() == 0) { + scroll.scrollTo(0, (int) (80 * pixelRatio)); + } + view.setVisibility(View.VISIBLE); + view.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + try { + registerForContextMenu(view); + openContextMenu(view); + } catch (Exception e) { + // nothing + } + } + }); + } + }; + + private Handler loadDescriptionHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + parseLongDescription(); + + if (longDesc != null) { + ((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE); + TextView descView = (TextView) findViewById(R.id.description); + if (cache.description.length() > 0) { + descView.setVisibility(View.VISIBLE); + descView.setText(longDesc, TextView.BufferType.SPANNABLE); + descView.setMovementMethod(LinkMovementMethod.getInstance()); + } + else { + descView.setVisibility(View.GONE); + } + + Button showDesc = (Button) findViewById(R.id.show_description); + showDesc.setVisibility(View.GONE); + showDesc.setOnTouchListener(null); + showDesc.setOnClickListener(null); + } else { + showToast(res.getString(R.string.err_load_descr_failed)); + } + + if (descDialog != null && descDialog.isShowing()) { + descDialog.dismiss(); + } + + longDescDisplayed = true; + } + }; + + /** + * shows/hides buttons, sets text in watchlist box + */ + private void updateWatchlistBox() { + LinearLayout layout = (LinearLayout) findViewById(R.id.watchlist_box); + boolean supportsWatchList = cache.supportsWatchList(); + layout.setVisibility(supportsWatchList ? View.VISIBLE : View.GONE); + if (!supportsWatchList) { + return; + } + Button buttonAdd = (Button) findViewById(R.id.add_to_watchlist); + Button buttonRemove = (Button) findViewById(R.id.remove_from_watchlist); + TextView text = (TextView) findViewById(R.id.watchlist_text); + + if (cache.onWatchlist) { + buttonAdd.setVisibility(View.GONE); + buttonRemove.setVisibility(View.VISIBLE); + text.setText(R.string.cache_watchlist_on); + } else { + buttonAdd.setVisibility(View.VISIBLE); + buttonRemove.setVisibility(View.GONE); + text.setText(R.string.cache_watchlist_not_on); + } + } + + /** + * Handler, called when watchlist add or remove is done + */ + private Handler WatchlistHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + watchlistThread = null; + if (watchlistDialog != null) + watchlistDialog.dismiss(); + if (msg.what == -1) { + showToast(res.getString(R.string.err_watchlist_failed)); + } else { + updateWatchlistBox(); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.detail); + setTitle(res.getString(R.string.cache)); + + init(); + + // get parameters + final Bundle extras = getIntent().getExtras(); + final Uri uri = getIntent().getData(); + + // try to get data from extras + if (geocode == null && extras != null) { + geocode = extras.getString("geocode"); + name = extras.getString("name"); + guid = extras.getString("guid"); + } + + // try to get data from URI + if (geocode == null && guid == null && uri != null) { + String uriHost = uri.getHost().toLowerCase(); + String uriPath = uri.getPath().toLowerCase(); + String uriQuery = uri.getQuery(); + + if (uriQuery != null) { + Log.i(cgSettings.tag, "Opening URI: " + uriHost + uriPath + "?" + uriQuery); + } else { + Log.i(cgSettings.tag, "Opening URI: " + uriHost + uriPath); + } + + if (uriHost.contains("geocaching.com")) { + geocode = uri.getQueryParameter("wp"); + guid = uri.getQueryParameter("guid"); + + if (StringUtils.isNotBlank(geocode)) { + geocode = geocode.toUpperCase(); + guid = null; + } else if (StringUtils.isNotBlank(guid)) { + geocode = null; + guid = guid.toLowerCase(); + } else { + showToast(res.getString(R.string.err_detail_open)); + finish(); + return; + } + } else if (uriHost.contains("coord.info")) { + if (uriPath != null && uriPath.startsWith("/gc")) { + geocode = uriPath.substring(1).toUpperCase(); + } else { + showToast(res.getString(R.string.err_detail_open)); + finish(); + return; + } + } + } + + // no given data + if (geocode == null && guid == null) { + showToast(res.getString(R.string.err_detail_cache)); + finish(); + return; + } + + app.setAction(geocode); + + try { + if (StringUtils.isNotBlank(name)) { + waitDialog = ProgressDialog.show(this, name, res.getString(R.string.cache_dialog_loading_details), true); + } else if (StringUtils.isNotBlank(geocode)) { + waitDialog = ProgressDialog.show(this, geocode.toUpperCase(), res.getString(R.string.cache_dialog_loading_details), true); + } else { + waitDialog = ProgressDialog.show(this, res.getString(R.string.cache), res.getString(R.string.cache_dialog_loading_details), true); + } + waitDialog.setCancelable(true); + } catch (Exception e) { + // nothing, we lost the window + } + + disableResumeSetView = true; + threadCache = new loadCache(loadCacheHandler); + threadCache.start(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + setView(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + if (!disableResumeSetView) { + setView(); + } + disableResumeSetView = false; + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + super.onCreateContextMenu(menu, view, info); + final int viewId = view.getId(); + + if (viewId == R.id.author || viewId == R.id.value) { + if (viewId == R.id.author) { // Author of a log entry + contextMenuUser = ((TextView) view).getText().toString(); + } else if (viewId == R.id.value) { // The owner of the cache + if (StringUtils.isNotBlank(cache.ownerReal)) { + contextMenuUser = cache.ownerReal; + } else { + contextMenuUser = cache.owner; + } + } + + menu.setHeaderTitle(res.getString(R.string.user_menu_title) + " " + contextMenuUser); + menu.add(viewId, 1, 0, res.getString(R.string.user_menu_view_hidden)); + menu.add(viewId, 2, 0, res.getString(R.string.user_menu_view_found)); + menu.add(viewId, 3, 0, res.getString(R.string.user_menu_open_browser)); + } + if (viewId == R.id.map_preview) { + menu.setHeaderTitle(res.getString(R.string.cache_menu_navigate)); + addNavigationMenuItems(menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int group = item.getGroupId(); + + if (group == R.id.author || group == R.id.value) { + final int id = item.getItemId(); + + if (id == 1) { + cgeocaches.startActivityCacheOwner(this, contextMenuUser); + return true; + } else if (id == 2) { + cgeocaches.startActivityCacheUser(this, contextMenuUser); + return true; + } else if (id == 3) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + URLEncoder.encode(contextMenuUser)))); + + return true; + } + } + else { + return onOptionsItemSelected(item); + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (cache != null && cache.coords != null) { + menu.add(0, 2, 0, res.getString(R.string.cache_menu_compass)).setIcon(android.R.drawable.ic_menu_compass); // compass + SubMenu subMenu = menu.addSubMenu(1, 0, 0, res.getString(R.string.cache_menu_navigate)).setIcon(android.R.drawable.ic_menu_mapmode); + addNavigationMenuItems(subMenu); + } + + if (cache != null && cache.canBeAddedToCalendar()) { + menu.add(1, 11, 0, res.getString(R.string.cache_menu_event)).setIcon(android.R.drawable.ic_menu_agenda); // add event to calendar + } + addVisitMenu(menu, cache); + + if (cache != null && CollectionUtils.isNotEmpty(cache.spoilers)) { + menu.add(1, 5, 0, res.getString(R.string.cache_menu_spoilers)).setIcon(android.R.drawable.ic_menu_gallery); // spoiler images + } + + if (cache != null && cache.coords != null) { + menu.add(0, 10, 0, res.getString(R.string.cache_menu_around)).setIcon(android.R.drawable.ic_menu_rotate); // caches around + } + + if (cache != null && cache.canOpenInBrowser()) { + menu.add(1, 7, 0, res.getString(R.string.cache_menu_browser)).setIcon(R.drawable.ic_menu_globe); // browser + } + menu.add(0, 12, 0, res.getString(R.string.cache_menu_share)).setIcon(android.R.drawable.ic_menu_share); // share cache + + return true; + } + + private void addNavigationMenuItems(final Menu menu) { + NavigationAppFactory.addMenuItems(menu, this, res); + GeneralAppsFactory.addMenuItems(menu, this, res, cache); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int menuItem = item.getItemId(); + + // no menu selected, but a new sub menu shown + if (menuItem == 0) { + return false; + } + + if (menuItem == 2) { + navigateTo(); + return true; + } else if (menuItem == MENU_LOG_VISIT) { + logVisit(); + return true; + } else if (menuItem == 5) { + showSpoilers(); + return true; + } else if (menuItem == 7) { + cache.openInBrowser(this); + return true; + } else if (menuItem == 10) { + cachesAround(); + return true; + } else if (menuItem == 11) { + addToCalendar(); + return true; + } else if (menuItem == 12) { + shareCache(); + return true; + } + if (NavigationAppFactory.onMenuItemSelected(item, geo, this, res, cache, searchId, null, null)) { + return true; + } + if (GeneralAppsFactory.onMenuItemSelected(item, this, cache)) { + return true; + } + + int logType = menuItem - MENU_LOG_VISIT_OFFLINE; + cache.logOffline(this, logType, settings, base); + return true; + } + + private void init() { + final DisplayMetrics dm = getResources().getDisplayMetrics(); + pixelRatio = dm.density; + + if (inflater == null) { + inflater = getLayoutInflater(); + } + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + if (searchId != null) { + cache = app.getCache(searchId); + if (cache != null && cache.geocode != null) { + geocode = cache.geocode; + } + } + + if (StringUtils.isNotBlank(geocode)) { + app.setAction(geocode); + } + } + + private void setView() { + RelativeLayout itemLayout; + TextView itemName; + TextView itemValue; + + if (searchId == null) { + return; + } + + cache = app.getCache(searchId); + + if (cache == null) { + if (waitDialog != null && waitDialog.isShowing()) + waitDialog.dismiss(); + + if (StringUtils.isNotBlank(geocode)) { + showToast(res.getString(R.string.err_detail_cache_find) + " " + geocode + "."); + } else { + geocode = null; + showToast(res.getString(R.string.err_detail_cache_find_some)); + } + + finish(); + return; + } + + try { + + if (geocode == null && StringUtils.isNotBlank(cache.geocode)) { + geocode = cache.geocode; + } + + if (guid == null && StringUtils.isNotBlank(cache.guid)) { + guid = cache.guid; + } + + setTitle(cache.geocode.toUpperCase()); + + inflater = getLayoutInflater(); + + ScrollView scroll = (ScrollView) findViewById(R.id.details_list_box); + scroll.setVisibility(View.VISIBLE); + + LinearLayout detailsList = (LinearLayout) findViewById(R.id.details_list); + detailsList.removeAllViews(); + + // actionbar icon, default myster< + ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds((Drawable) getResources().getDrawable(cgBase.getCacheIcon(cache.type)), null, null, null); + + // cache name (full name) + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_name)); + Spannable span = (new Spannable.Factory()).newSpannable(Html.fromHtml(cache.name).toString()); + if (cache.disabled || cache.archived) { // strike + span.setSpan(new StrikethroughSpan(), 0, span.toString().length(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE); + } + itemValue.setText(span); + detailsList.addView(itemLayout); + + // cache type + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_type)); + + String size = ""; + if (cache.size != null) { + // don't show "not chosen" for events, that should be the normal case + if (!(cache.isEventCache() && cache.size == CacheSize.NOT_CHOSEN)) { + size = " (" + res.getString(cache.size.stringId) + ")"; + } + } + + if (cgBase.cacheTypesInv.containsKey(cache.type)) { // cache icon + itemValue.setText(cgBase.cacheTypesInv.get(cache.type) + size); + } else { + itemValue.setText(cgBase.cacheTypesInv.get("mystery") + size); + } + detailsList.addView(itemLayout); + + // gc-code + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_geocode)); + itemValue.setText(cache.geocode.toUpperCase()); + detailsList.addView(itemLayout); + + // cache state + if (cache.logOffline || cache.archived || cache.disabled || cache.members || cache.found) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_status)); + + StringBuilder state = new StringBuilder(); + if (cache.logOffline) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_offline_log)); + } + if (cache.found) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_found)); + } + if (cache.archived) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_archived)); + } + if (cache.disabled) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_disabled)); + } + if (cache.members) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_premium)); + } + + itemValue.setText(state.toString()); + detailsList.addView(itemLayout); + + state = null; + } + + // distance + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_distance)); + if (cache.distance != null) { + itemValue.setText("~" + base.getHumanDistance(cache.distance)); + } else { + itemValue.setText("--"); + } + detailsList.addView(itemLayout); + cacheDistance = itemValue; + + // difficulty + if (cache.difficulty != null && cache.difficulty > 0) { + addStarRating(detailsList, res.getString(R.string.cache_difficulty), cache.difficulty); + } + + // terrain + if (cache.terrain != null && cache.terrain > 0) { + addStarRating(detailsList, res.getString(R.string.cache_terrain), cache.terrain); + } + + // rating + if (cache.rating != null && cache.rating > 0) { + itemLayout = addStarRating(detailsList, res.getString(R.string.cache_rating), cache.rating); + if (cache.votes != null) { + final TextView itemAddition = (TextView) itemLayout.findViewById(R.id.addition); + itemAddition.setText("(" + cache.votes + ")"); + itemAddition.setVisibility(View.VISIBLE); + } + } + + // favourite count + if (cache.favouriteCnt != null) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_favourite)); + itemValue.setText(String.format("%d", cache.favouriteCnt) + "×"); + detailsList.addView(itemLayout); + } + + // cache author + if (StringUtils.isNotBlank(cache.owner) || StringUtils.isNotBlank(cache.ownerReal)) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_owner)); + if (StringUtils.isNotBlank(cache.owner)) { + itemValue.setText(Html.fromHtml(cache.owner), TextView.BufferType.SPANNABLE); + } else if (StringUtils.isNotBlank(cache.ownerReal)) { + itemValue.setText(Html.fromHtml(cache.ownerReal), TextView.BufferType.SPANNABLE); + } + itemValue.setOnClickListener(new userActions()); + detailsList.addView(itemLayout); + } + + // cache hidden + if (cache.hidden != null && cache.hidden.getTime() > 0) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + if (cache.type != null && (cache.type.equalsIgnoreCase("event") || cache.type.equalsIgnoreCase("mega") || cache.type.equalsIgnoreCase("cito"))) { + itemName.setText(res.getString(R.string.cache_event)); + } else { + itemName.setText(res.getString(R.string.cache_hidden)); + } + itemValue.setText(base.formatFullDate(cache.hidden.getTime())); + detailsList.addView(itemLayout); + } + + // cache location + if (StringUtils.isNotBlank(cache.location)) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_location)); + itemValue.setText(cache.location); + detailsList.addView(itemLayout); + } + + // cache coordinates + if (cache.coords != null) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_coordinates)); + itemValue.setText(cache.latitudeString + " | " + cache.longitudeString); + detailsList.addView(itemLayout); + } + + // cache attributes + if (CollectionUtils.isNotEmpty(cache.attributes)) { + + final LinearLayout attribBox = (LinearLayout) findViewById( + R.id.attributes_innerbox); + + // maximum width for attribute icons is screen width - paddings of parents + attributeBoxMaxWidth = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)) + .getDefaultDisplay().getWidth(); + ViewParent child = attribBox; + do { + if (child instanceof View) + attributeBoxMaxWidth = attributeBoxMaxWidth - ((View) child).getPaddingLeft() + - ((View) child).getPaddingRight(); + child = child.getParent(); + } while (child != null); + + // delete views holding description / icons + attributeDescriptionsLayout = null; + attributeIconsLayout = null; + + attribBox.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + // toggle between attribute icons and descriptions + toggleAttributeDisplay(attribBox, attributeBoxMaxWidth); + } + }); + + // icons or text? + // + // also show icons when noAttributeImagesFound == true. Explanation: + // 1. no icons could be found in the first invocation of this method + // 2. user refreshes cache from web + // 3. now this method is called again + // 4. attributeShowAsIcons is false but noAttributeImagesFound is true + // => try to show them now + if (attributesShowAsIcons || noAttributeIconsFound) { + showAttributeIcons(attribBox, attributeBoxMaxWidth); + } else { + showAttributeDescriptions(attribBox); + } + + findViewById(R.id.attributes_box).setVisibility(View.VISIBLE); + } + + // cache inventory + if (CollectionUtils.isNotEmpty(cache.inventory)) { + final LinearLayout inventBox = (LinearLayout) findViewById(R.id.inventory_box); + final TextView inventView = (TextView) findViewById(R.id.inventory); + + StringBuilder inventoryString = new StringBuilder(); + for (cgTrackable inventoryItem : cache.inventory) { + if (inventoryString.length() > 0) { + inventoryString.append('\n'); + } + // avoid HTML parsing where possible + if (inventoryItem.name.indexOf('<') >= 0 || inventoryItem.name.indexOf('&') >= 0) { + inventoryString.append(Html.fromHtml(inventoryItem.name).toString()); + } + else { + inventoryString.append(inventoryItem.name); + } + } + inventView.setText(inventoryString); + inventBox.setClickable(true); + inventBox.setOnClickListener(new selectTrackable()); + inventBox.setVisibility(View.VISIBLE); + } + + // offline use + final TextView offlineText = (TextView) findViewById(R.id.offline_text); + final Button offlineRefresh = (Button) findViewById(R.id.offline_refresh); + final Button offlineStore = (Button) findViewById(R.id.offline_store); + + if (cache.reason >= 1) { + Long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.detailedUpdate / (60 * 1000)); // minutes + + String ago = ""; + if (diff < 15) { + ago = res.getString(R.string.cache_offline_time_mins_few); + } else if (diff < 50) { + ago = res.getString(R.string.cache_offline_time_about) + " " + diff + " " + res.getString(R.string.cache_offline_time_mins); + } else if (diff < 90) { + ago = res.getString(R.string.cache_offline_time_about) + " " + res.getString(R.string.cache_offline_time_hour); + } else if (diff < (48 * 60)) { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / 60) + " " + res.getString(R.string.cache_offline_time_hours); + } else { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / (24 * 60)) + " " + res.getString(R.string.cache_offline_time_days); + } + + offlineText.setText(res.getString(R.string.cache_offline_stored) + "\n" + ago); + offlineRefresh.setOnClickListener(new storeCache()); + + offlineStore.setText(res.getString(R.string.cache_offline_drop)); + offlineStore.setClickable(true); + offlineStore.setOnClickListener(new dropCache()); + } else { + offlineText.setText(res.getString(R.string.cache_offline_not_ready)); + offlineRefresh.setOnClickListener(new refreshCache()); + + offlineStore.setText(res.getString(R.string.cache_offline_store)); + offlineStore.setClickable(true); + offlineStore.setOnClickListener(new storeCache()); + } + offlineRefresh.setVisibility(cache.supportsRefresh() ? View.VISIBLE : View.GONE); + offlineRefresh.setClickable(true); + + // cache personal note + if (StringUtils.isNotBlank(cache.personalNote)) { + ((LinearLayout) findViewById(R.id.personalnote_box)).setVisibility(View.VISIBLE); + + TextView personalNoteText = (TextView) findViewById(R.id.personalnote); + personalNoteText.setVisibility(View.VISIBLE); + personalNoteText.setText(cache.personalNote, TextView.BufferType.SPANNABLE); + personalNoteText.setMovementMethod(LinkMovementMethod.getInstance()); + } + + // cache short desc + if (StringUtils.isNotBlank(cache.shortdesc)) { + ((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE); + + TextView descView = (TextView) findViewById(R.id.shortdesc); + descView.setVisibility(View.VISIBLE); + descView.setText(Html.fromHtml(cache.shortdesc.trim(), new cgHtmlImg(this, geocode, true, cache.reason, false), null), TextView.BufferType.SPANNABLE); + descView.setMovementMethod(LinkMovementMethod.getInstance()); + } + + // cache long desc + if (longDescDisplayed) { + parseLongDescription(); + + if (StringUtils.isNotBlank(longDesc)) { + ((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE); + + TextView descView = (TextView) findViewById(R.id.description); + descView.setVisibility(View.VISIBLE); + descView.setText(longDesc, TextView.BufferType.SPANNABLE); + descView.setMovementMethod(LinkMovementMethod.getInstance()); + + Button showDesc = (Button) findViewById(R.id.show_description); + showDesc.setVisibility(View.GONE); + showDesc.setOnTouchListener(null); + showDesc.setOnClickListener(null); + } + } else if (longDescDisplayed == false && StringUtils.isNotBlank(cache.description)) { + ((LinearLayout) findViewById(R.id.desc_box)).setVisibility(View.VISIBLE); + + Button showDesc = (Button) findViewById(R.id.show_description); + showDesc.setVisibility(View.VISIBLE); + showDesc.setOnClickListener(new View.OnClickListener() { + public void onClick(View arg0) { + loadLongDesc(); + } + }); + } + + // watchlist + Button buttonWatchlistAdd = (Button) findViewById(R.id.add_to_watchlist); + Button buttonWatchlistRemove = (Button) findViewById(R.id.remove_from_watchlist); + buttonWatchlistAdd.setOnClickListener(new AddToWatchlistClickListener()); + buttonWatchlistRemove.setOnClickListener(new RemoveFromWatchlistClickListener()); + updateWatchlistBox(); + + // waypoints + LinearLayout waypoints = (LinearLayout) findViewById(R.id.waypoints); + waypoints.removeAllViews(); + + if (CollectionUtils.isNotEmpty(cache.waypoints)) { + LinearLayout waypointView; + + // sort waypoints: PP, Sx, FI, OWN + List<cgWaypoint> sortedWaypoints = new ArrayList<cgWaypoint>(cache.waypoints); + Collections.sort(sortedWaypoints, new Comparator<cgWaypoint>() { + + @Override + public int compare(cgWaypoint wayPoint1, cgWaypoint wayPoint2) { + + return order(wayPoint1) - order(wayPoint2); + } + + private int order(cgWaypoint waypoint) { + if (StringUtils.isEmpty(waypoint.prefix)) { + return 0; + } + // check only the first character. sometimes there are inconsistencies like FI or FN for the FINAL + char firstLetter = Character.toUpperCase(waypoint.prefix.charAt(0)); + switch (firstLetter) { + case 'P': + return -100; // parking + case 'S': { // stage N + try { + Integer stageNumber = Integer.valueOf(waypoint.prefix.substring(1)); + return stageNumber; + } catch (NumberFormatException e) { + // nothing + } + return 0; + } + case 'F': + return 1000; // final + case 'O': + return 10000; // own + } + return 0; + } + }); + + for (cgWaypoint wpt : sortedWaypoints) { + waypointView = (LinearLayout) inflater.inflate(R.layout.waypoint_item, null); + final TextView identification = (TextView) waypointView.findViewById(R.id.identification); + + ((TextView) waypointView.findViewById(R.id.type)).setText(cgBase.waypointTypes.get(wpt.type)); + if (wpt.prefix.equalsIgnoreCase("OWN") == false) { + identification.setText(wpt.prefix.trim() + "/" + wpt.lookup.trim()); + } else { + identification.setText(res.getString(R.string.waypoint_custom)); + } + + TextView nameView = (TextView) waypointView.findViewById(R.id.name); + if (StringUtils.isBlank(wpt.name)) { + nameView.setText(cgBase.formatCoords(wpt.coords, true)); + } else { + // avoid HTML parsing + if (wpt.name.indexOf('<') >= 0 || wpt.name.indexOf('&') >= 0) { + nameView.setText(Html.fromHtml(wpt.name.trim()), TextView.BufferType.SPANNABLE); + } + else { + nameView.setText(wpt.name.trim()); + } + } + wpt.setIcon(res, base, nameView); + + // avoid HTML parsing + if (wpt.note.indexOf('<') >= 0 || wpt.note.indexOf('&') >= 0) { + ((TextView) waypointView.findViewById(R.id.note)).setText(Html.fromHtml(wpt.note.trim()), TextView.BufferType.SPANNABLE); + } + else { + ((TextView) waypointView.findViewById(R.id.note)).setText(wpt.note.trim()); + } + + waypointView.setOnClickListener(new waypointInfo(wpt.id)); + + waypoints.addView(waypointView); + } + } + + Button addWaypoint = (Button) findViewById(R.id.add_waypoint); + addWaypoint.setClickable(true); + addWaypoint.setOnClickListener(new addWaypoint()); + + // cache hint + if (StringUtils.isNotBlank(cache.hint)) { + ((LinearLayout) findViewById(R.id.hint_box)).setVisibility(View.VISIBLE); + TextView hintView = ((TextView) findViewById(R.id.hint)); + hintView.setText(cgBase.rot13(cache.hint.trim())); + hintView.setClickable(true); + hintView.setOnClickListener(new codeHint()); + } else { + ((LinearLayout) findViewById(R.id.hint_box)).setVisibility(View.GONE); + TextView hintView = ((TextView) findViewById(R.id.hint)); + hintView.setClickable(false); + hintView.setOnClickListener(null); + } + + if (geo != null && geo.coordsNow != null && cache != null && cache.coords != null) { + cacheDistance.setText(base.getHumanDistance(geo.coordsNow.distanceTo(cache.coords))); + cacheDistance.bringToFront(); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeodetail.setView: " + e.toString()); + } + + if (waitDialog != null && waitDialog.isShowing()) + waitDialog.dismiss(); + if (storeDialog != null && storeDialog.isShowing()) + storeDialog.dismiss(); + if (dropDialog != null && dropDialog.isShowing()) + dropDialog.dismiss(); + if (refreshDialog != null && refreshDialog.isShowing()) + refreshDialog.dismiss(); + + displayLogs(); + + if (geo != null) + geoUpdate.updateLoc(geo); + } + + private void parseLongDescription() { + if (longDesc == null && cache != null && cache.description != null) { + longDesc = Html.fromHtml(cache.description.trim(), new cgHtmlImg(this, geocode, true, cache.reason, false), new UnknownTagsHandler()); + } + } + + private RelativeLayout addStarRating(final LinearLayout detailsList, final String name, final float value) { + RelativeLayout itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_layout, null); + TextView itemName = (TextView) itemLayout.findViewById(R.id.name); + TextView itemValue = (TextView) itemLayout.findViewById(R.id.value); + LinearLayout itemStars = (LinearLayout) itemLayout.findViewById(R.id.stars); + + itemName.setText(name); + itemValue.setText(String.format(Locale.getDefault(), "%.1f", value) + ' ' + res.getString(R.string.cache_rating_of) + " 5"); + for (int i = 0; i <= 4; i++) { + ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + if ((value - i) >= 1.0) { + star.setImageResource(R.drawable.star_on); + } else if ((value - i) > 0.0) { + star.setImageResource(R.drawable.star_half); + } else { + star.setImageResource(R.drawable.star_off); + } + itemStars.addView(star, (1 + i)); + } + detailsList.addView(itemLayout); + return itemLayout; + } + + private void displayLogs() { + // cache logs + TextView textView = (TextView) findViewById(R.id.logcount); + int logCounter = 0; + if (cache != null && cache.logCounts != null) { + final StringBuffer buff = new StringBuffer(); + buff.append(res.getString(R.string.cache_log_types)); + buff.append(": "); + + // sort the log counts by type id ascending. that way the FOUND, DNF log types are the first and most visible ones + List<Entry<Integer, Integer>> sortedLogCounts = new ArrayList<Entry<Integer, Integer>>(); + sortedLogCounts.addAll(cache.logCounts.entrySet()); + Collections.sort(sortedLogCounts, new Comparator<Entry<Integer, Integer>>() { + + @Override + public int compare(Entry<Integer, Integer> logCountItem1, + Entry<Integer, Integer> logCountItem2) { + return logCountItem1.getKey().compareTo(logCountItem2.getKey()); + } + }); + for (Entry<Integer, Integer> pair : sortedLogCounts) { + int logTypeId = pair.getKey().intValue(); + String logTypeLabel = cgBase.logTypes1.get(logTypeId); + // it may happen that the label is unknown -> then avoid any output for this type + if (logTypeLabel != null) { + if (logCounter > 0) { + buff.append(", "); + } + buff.append(pair.getValue().intValue()); + buff.append("× "); + buff.append(logTypeLabel); + } + logCounter++; + } + textView.setText(buff.toString()); + } + // it may happen, that the logCounts map is available, but every log type has zero counts, + // therefore check again for the number of counted logs + if (logCounter > 0) { + textView.setVisibility(View.VISIBLE); + } else { + textView.setVisibility(View.GONE); + } + + // cache logs + LinearLayout listView = (LinearLayout) findViewById(R.id.log_list); + listView.removeAllViews(); + + RelativeLayout rowView; + + if (cache != null && cache.logs != null) { + for (cgLog log : cache.logs) { + rowView = (RelativeLayout) inflater.inflate(R.layout.log_item, null); + + if (log.date > 0) { + ((TextView) rowView.findViewById(R.id.added)).setText(base.formatShortDate(log.date)); + } else { + ((TextView) rowView.findViewById(R.id.added)).setVisibility(View.GONE); + } + + if (cgBase.logTypes1.containsKey(log.type)) { + ((TextView) rowView.findViewById(R.id.type)).setText(cgBase.logTypes1.get(log.type)); + } else { + ((TextView) rowView.findViewById(R.id.type)).setText(cgBase.logTypes1.get(4)); // note if type is unknown + } + // avoid parsing HTML if not necessary + if (log.author.indexOf('<') >= 0 || log.author.indexOf('&') >= 0) { + ((TextView) rowView.findViewById(R.id.author)).setText(Html.fromHtml(log.author), TextView.BufferType.SPANNABLE); + } + else { + ((TextView) rowView.findViewById(R.id.author)).setText(log.author); + } + + if (log.found == -1) { + ((TextView) rowView.findViewById(R.id.count)).setVisibility(View.GONE); + } else if (log.found == 0) { + ((TextView) rowView.findViewById(R.id.count)).setText(res.getString(R.string.cache_count_no)); + } else if (log.found == 1) { + ((TextView) rowView.findViewById(R.id.count)).setText(res.getString(R.string.cache_count_one)); + } else { + ((TextView) rowView.findViewById(R.id.count)).setText(log.found + " " + res.getString(R.string.cache_count_more)); + } + // avoid parsing HTML if not necessary + if (log.log.indexOf('<') >= 0 || log.log.indexOf('&') >= 0) { + ((TextView) rowView.findViewById(R.id.log)).setText(Html.fromHtml(log.log, new cgHtmlImg(this, null, false, cache.reason, false), null), TextView.BufferType.SPANNABLE); + } + else { + ((TextView) rowView.findViewById(R.id.log)).setText(log.log); + } + // add LogImages + LinearLayout logLayout = (LinearLayout) rowView.findViewById(R.id.log_layout); + + if ((log.logImages != null) && (!log.logImages.isEmpty())) { + for (int i_img_cnt = 0; i_img_cnt < log.logImages.size(); i_img_cnt++) { + String img_title = log.logImages.get(i_img_cnt).title; + if (img_title.equals("")) { + img_title = res.getString(R.string.cache_log_image_default_title); + } + final String title = img_title; + final String url = log.logImages.get(i_img_cnt).url; + LinearLayout log_imgView = (LinearLayout) inflater.inflate(R.layout.log_img, null); + TextView log_img_title = (TextView) log_imgView.findViewById(R.id.title); + log_img_title.setText(title); + log_img_title.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent logImgIntent = new Intent(cgeodetail.this, cgeoimages.class); + logImgIntent.putExtra("geocode", geocode.toUpperCase()); + logImgIntent.putExtra("type", cgeoimages.LOG_IMAGE); + logImgIntent.putExtra("title", title); + logImgIntent.putExtra("url", url); + startActivity(logImgIntent); + } + }); + logLayout.addView(log_imgView); + } + } + + // Add colored mark + final ImageView logMark = (ImageView) rowView.findViewById(R.id.log_mark); + if (log.type == cgBase.LOG_FOUND_IT + || log.type == cgBase.LOG_WEBCAM_PHOTO_TAKEN + || log.type == cgBase.LOG_ATTENDED) + { + logMark.setImageResource(R.drawable.mark_green); + } + else if (log.type == cgBase.LOG_PUBLISH_LISTING + || log.type == cgBase.LOG_ENABLE_LISTING + || log.type == cgBase.LOG_OWNER_MAINTENANCE) + { + logMark.setImageResource(R.drawable.mark_green_more); + } + else if (log.type == cgBase.LOG_DIDNT_FIND_IT + || log.type == cgBase.LOG_NEEDS_MAINTENANCE + || log.type == cgBase.LOG_NEEDS_ARCHIVE) + { + logMark.setImageResource(R.drawable.mark_red); + } + else if (log.type == cgBase.LOG_TEMP_DISABLE_LISTING + || log.type == cgBase.LOG_ARCHIVE) + { + logMark.setImageResource(R.drawable.mark_red_more); + } + else + { + logMark.setVisibility(View.GONE); + } + + ((TextView) rowView.findViewById(R.id.author)).setOnClickListener(new userActions()); + ((TextView) logLayout.findViewById(R.id.log)).setOnClickListener(new decryptLog()); + + listView.addView(rowView); + } + + if (cache.logs.size() > 0) { + ((LinearLayout) findViewById(R.id.log_box)).setVisibility(View.VISIBLE); + } + } + } + + private class loadCache extends Thread { + + private Handler handler = null; + + public loadCache(Handler handlerIn) { + handler = handlerIn; + + if (geocode == null && guid == null) { + showToast(res.getString(R.string.err_detail_cache_forgot)); + + finish(); + return; + } + } + + @Override + public void run() { + Map<String, String> params = new HashMap<String, String>(); + if (StringUtils.isNotBlank(geocode)) { + params.put("geocode", geocode); + } else if (StringUtils.isNotBlank(guid)) { + params.put("guid", guid); + } else { + return; + } + + searchId = base.searchByGeocode(params, 0, false); + + handler.sendMessage(new Message()); + } + } + + private class loadMapPreview extends Thread { + private Handler handler = null; + + public loadMapPreview(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + if (cache == null || cache.coords == null) { + return; + } + + BitmapDrawable image = null; + + try { + final String latlonMap = String.format((Locale) null, "%.6f", cache.coords.getLatitude()) + "," + + String.format((Locale) null, "%.6f", cache.coords.getLongitude()); + final Display display = ((WindowManager) getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); + + int width = display.getWidth(); + int height = (int) (90 * pixelRatio); + + String markerUrl = cgBase.urlencode_rfc3986("http://cgeo.carnero.cc/_markers/my_location_mdpi.png"); + + cgHtmlImg mapGetter = new cgHtmlImg(cgeodetail.this, cache.geocode, false, 0, false); + image = mapGetter.getDrawable("http://maps.google.com/maps/api/staticmap?center=" + latlonMap + "&zoom=15&size=" + width + "x" + height + "&maptype=terrain&markers=icon%3A" + markerUrl + "%7C" + latlonMap + "&sensor=false"); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeodetail.loadMapPreview.run: " + e.toString()); + } + } + } + + public void loadLongDesc() { + if (waitDialog == null || waitDialog.isShowing() == false) { + descDialog = ProgressDialog.show(this, null, res.getString(R.string.cache_dialog_loading_description), true); + descDialog.setCancelable(true); + } + + threadLongDesc = new loadLongDesc(loadDescriptionHandler); + threadLongDesc.start(); + } + + private class loadLongDesc extends Thread { + private Handler handler = null; + + public loadLongDesc(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + if (cache == null || cache.description == null || handler == null) { + return; + } + parseLongDescription(); + handler.sendMessage(new Message()); + } + } + + public List<cgCoord> getCoordinates() { + cgCoord coords = null; + List<cgCoord> coordinates = new ArrayList<cgCoord>(); + + try { + // cache + coords = new cgCoord(); + coords.type = "cache"; + if (StringUtils.isNotBlank(name)) { + coords.name = name; + } else { + coords.name = geocode.toUpperCase(); + } + coords.coords = cache.coords; + coordinates.add(coords); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeodetail.getCoordinates (cache): " + e.toString()); + } + + try { + // waypoints + for (cgWaypoint waypoint : cache.waypoints) { + if (waypoint.coords == null) { + continue; + } + + coords = new cgCoord(); + coords.type = "waypoint"; + coords.name = waypoint.name; + coords.coords = waypoint.coords; + coordinates.add(coords); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeodetail.getCoordinates (waypoint): " + e.toString()); + } + + return coordinates; + } + + private void cachesAround() { + cgeocaches.startActivityCachesAround(this, cache.coords); + + finish(); + } + + private void addToCalendar() { + String[] projection = new String[] { "_id", "displayName" }; + Uri calendarProvider = Compatibility.getCalendarProviderURI(); + + Cursor cursor = managedQuery(calendarProvider, projection, "selected=1", null, null); + + calendars.clear(); + int cnt = 0; + if (cursor != null) { + cnt = cursor.getCount(); + + if (cnt > 0) { + cursor.moveToFirst(); + + int calId = 0; + String calIdPre = null; + String calName = null; + int calIdIn = cursor.getColumnIndex("_id"); + int calNameIn = cursor.getColumnIndex("displayName"); + + do { + calIdPre = cursor.getString(calIdIn); + if (calIdPre != null) { + calId = new Integer(calIdPre); + } + calName = cursor.getString(calNameIn); + + if (calId > 0 && calName != null) { + calendars.put(calId, calName); + } + } while (cursor.moveToNext()); + } + } + + final CharSequence[] items = calendars.values().toArray(new CharSequence[calendars.size()]); + + AlertDialog.Builder builder = new AlertDialog.Builder(this); + builder.setTitle(R.string.cache_calendars); + builder.setItems(items, new DialogInterface.OnClickListener() { + public void onClick(DialogInterface dialog, int item) { + addToCalendarFn(item); + } + }); + AlertDialog alert = builder.create(); + alert.show(); + } + + private void addToCalendarFn(int index) { + if (calendars == null || calendars.isEmpty()) { + return; + } + + try { + Uri calendarProvider = Compatibility.getCalenderEventsProviderURI(); + + final Integer[] keys = calendars.keySet().toArray(new Integer[calendars.size()]); + final Integer calId = keys[index]; + + final Date eventDate = cache.hidden; + eventDate.setHours(0); + eventDate.setMinutes(0); + eventDate.setSeconds(0); + + StringBuilder description = new StringBuilder(); + description.append("http://coord.info/"); + description.append(cache.geocode.toUpperCase()); + description.append("\n\n"); + if (StringUtils.isNotBlank(cache.shortdesc)) { + description.append(Html.fromHtml(cache.shortdesc).toString()); + } + + if (StringUtils.isNotBlank(cache.personalNote)) { + description.append("\n\n" + Html.fromHtml(cache.personalNote).toString()); + } + + ContentValues event = new ContentValues(); + event.put("calendar_id", calId); + event.put("dtstart", eventDate.getTime() + 43200000); // noon + event.put("dtend", eventDate.getTime() + 43200000 + 3600000); // + one hour + event.put("eventTimezone", "UTC"); + event.put("title", Html.fromHtml(cache.name).toString()); + event.put("description", description.toString()); + String location = ""; + if (StringUtils.isNotBlank(cache.latitudeString) && StringUtils.isNotBlank(cache.longitudeString)) { + location += cache.latitudeString + " " + cache.longitudeString; + } + if (StringUtils.isNotBlank(cache.location)) { + boolean addParenteses = false; + if (location.length() > 0) { + addParenteses = true; + location += " ("; + } + + location += Html.fromHtml(cache.location).toString(); + if (addParenteses) { + location += ")"; + } + } + if (location.length() > 0) { + event.put("eventLocation", location); + } + event.put("allDay", 1); + event.put("hasAlarm", 0); + + getContentResolver().insert(calendarProvider, event); + + showToast(res.getString(R.string.event_success)); + } catch (Exception e) { + showToast(res.getString(R.string.event_fail)); + + Log.e(cgSettings.tag, "cgeodetail.addToCalendarFn: " + e.toString()); + } + } + + private void navigateTo() { + if (cache == null || cache.coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + } + + cgeonavigate navigateActivity = new cgeonavigate(); + + Intent navigateIntent = new Intent(this, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", cache.coords.getLatitude()); + navigateIntent.putExtra("longitude", cache.coords.getLongitude()); + navigateIntent.putExtra("geocode", cache.geocode.toUpperCase()); + navigateIntent.putExtra("name", cache.name); + + cgeonavigate.coordinates.clear(); + cgeonavigate.coordinates.addAll(getCoordinates()); + startActivity(navigateIntent); + } + + public void shareCache() { + if (geocode == null && cache == null) { + return; + } + + final Intent intent = new Intent(Intent.ACTION_SEND); + intent.setType("text/plain"); + + if (cache != null && cache.geocode != null) { + String subject = cache.geocode.toUpperCase(); + if (StringUtils.isNotBlank(cache.name)) { + subject = subject + " - " + cache.name; + } + intent.putExtra(Intent.EXTRA_SUBJECT, "Geocache " + subject); + intent.putExtra(Intent.EXTRA_TEXT, "http://coord.info/" + cache.geocode.toUpperCase()); + } else if (geocode != null) { + intent.putExtra(Intent.EXTRA_SUBJECT, "Geocache " + geocode.toUpperCase()); + intent.putExtra(Intent.EXTRA_TEXT, "http://coord.info/" + geocode.toUpperCase()); + } + + startActivity(Intent.createChooser(intent, res.getText(R.string.action_bar_share_title))); + } + + private class waypointInfo implements View.OnClickListener { + private int id = -1; + + public waypointInfo(int idIn) { + id = idIn; + } + + public void onClick(View arg0) { + Intent waypointIntent = new Intent(cgeodetail.this, cgeowaypoint.class); + waypointIntent.putExtra("waypoint", id); + waypointIntent.putExtra("geocode", cache.geocode); + startActivity(waypointIntent); + } + } + + private void logVisit() { + cache.logVisit(this); + } + + private void showSpoilers() { + if (cache == null || cache.spoilers == null || cache.spoilers.isEmpty()) { + showToast(res.getString(R.string.err_detail_no_spoiler)); + } + + Intent spoilersIntent = new Intent(this, cgeoimages.class); + spoilersIntent.putExtra("geocode", geocode.toUpperCase()); + spoilersIntent.putExtra("type", cgeoimages.SPOILER_IMAGE); + startActivity(spoilersIntent); + } + + public class codeHint implements View.OnClickListener { + public void onClick(View arg0) { + // code hint + TextView hintView = ((TextView) findViewById(R.id.hint)); + hintView.setText(cgBase.rot13(hintView.getText().toString())); + + } + } + + private class update extends cgUpdateLoc { + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + if (cacheDistance == null) { + return; + } + + try { + StringBuilder dist = new StringBuilder(); + + if (geo.coordsNow != null && cache != null && cache.coords != null) { + dist.append(base.getHumanDistance(geo.coordsNow.distanceTo(cache.coords))); + } + + if (cache != null && cache.elevation != null) { + if (geo.altitudeNow != null) { + Double diff = (cache.elevation - geo.altitudeNow); + if (diff >= 0) { + dist.append(" ↗"); + } else if (diff < 0) { + dist.append(" ↘"); + } + if (settings.units == cgSettings.unitsImperial) { + dist.append(String.format(Locale.getDefault(), "%.0f", (Math.abs(diff) * 3.2808399))); + dist.append(" ft"); + } else { + dist.append(String.format(Locale.getDefault(), "%.0f", (Math.abs(diff)))); + dist.append(" m"); + } + } + } + + cacheDistance.setText(dist.toString()); + cacheDistance.bringToFront(); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class selectTrackable implements View.OnClickListener { + public void onClick(View arg0) { + // show list of trackables + try { + // jump directly into details if there is only one trackable + if (cache != null && cache.inventory != null && cache.inventory.size() == 1) { + cgTrackable trackable = cache.inventory.get(0); + cgeotrackable.startActivity(cgeodetail.this, trackable.guid, trackable.geocode, trackable.name); + } + else { + Intent trackablesIntent = new Intent(cgeodetail.this, cgeotrackables.class); + trackablesIntent.putExtra("geocode", geocode.toUpperCase()); + startActivity(trackablesIntent); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeodetail.selectTrackable: " + e.toString()); + } + } + } + + private class storeCache implements View.OnClickListener { + public void onClick(View arg0) { + if (dropDialog != null && dropDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_removing)); + return; + } + if (refreshDialog != null && refreshDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_refreshing)); + return; + } + + storeDialog = ProgressDialog.show(cgeodetail.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true); + storeDialog.setCancelable(true); + + if (storeThread != null) { + storeThread.interrupt(); + } + + storeThread = new storeCacheThread(storeCacheHandler); + storeThread.start(); + } + } + + private class refreshCache implements View.OnClickListener { + public void onClick(View arg0) { + if (dropDialog != null && dropDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_removing)); + return; + } + if (storeDialog != null && storeDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_saving)); + return; + } + + refreshDialog = ProgressDialog.show(cgeodetail.this, res.getString(R.string.cache_dialog_refresh_title), res.getString(R.string.cache_dialog_refresh_message), true); + refreshDialog.setCancelable(true); + + if (refreshThread != null) { + refreshThread.interrupt(); + } + + refreshThread = new refreshCacheThread(refreshCacheHandler); + refreshThread.start(); + } + } + + private class storeCacheThread extends Thread { + private Handler handler = null; + + public storeCacheThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + int reason = (cache.reason > 1) ? cache.reason : 1; + base.storeCache(app, cgeodetail.this, cache, null, reason, handler); + } + } + + private class refreshCacheThread extends Thread { + private Handler handler = null; + + public refreshCacheThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + app.removeCacheFromCache(geocode); + + final Map<String, String> params = new HashMap<String, String>(); + params.put("geocode", cache.geocode); + searchId = base.searchByGeocode(params, 0, true); + + handler.sendEmptyMessage(0); + } + } + + private class dropCache implements View.OnClickListener { + public void onClick(View arg0) { + if (storeDialog != null && storeDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_saving)); + return; + } + if (refreshDialog != null && refreshDialog.isShowing()) { + showToast(res.getString(R.string.err_detail_still_refreshing)); + return; + } + + dropDialog = ProgressDialog.show(cgeodetail.this, res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true); + dropDialog.setCancelable(false); + Thread thread = new dropCacheThread(dropCacheHandler); + thread.start(); + } + } + + private class dropCacheThread extends Thread { + + private Handler handler = null; + + public dropCacheThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + cgBase.dropCache(app, cgeodetail.this, cache, handler); + } + } + + /** + * Abstract Listener for add / remove buttons for watchlist + */ + private abstract class AbstractWatchlistClickListener implements View.OnClickListener { + public void doExecute(int titleId, int messageId, Thread thread) { + if (watchlistDialog != null && watchlistDialog.isShowing()) { + showToast(res.getString(R.string.err_watchlist_still_managing)); + return; + } + watchlistDialog = ProgressDialog.show(cgeodetail.this, + res.getString(titleId), res.getString(messageId), true); + watchlistDialog.setCancelable(true); + + if (watchlistThread != null) { + watchlistThread.interrupt(); + } + + watchlistThread = thread; + watchlistThread.start(); + } + } + + /** + * Listener for "add to watchlist" button + */ + private class AddToWatchlistClickListener extends AbstractWatchlistClickListener { + public void onClick(View arg0) { + doExecute(R.string.cache_dialog_watchlist_add_title, + R.string.cache_dialog_watchlist_add_message, + new WatchlistAddThread(WatchlistHandler)); + } + } + + /** + * Listener for "remove from watchlist" button + */ + private class RemoveFromWatchlistClickListener extends AbstractWatchlistClickListener { + public void onClick(View arg0) { + doExecute(R.string.cache_dialog_watchlist_remove_title, + R.string.cache_dialog_watchlist_remove_message, + new WatchlistRemoveThread(WatchlistHandler)); + } + } + + /** Thread to add this cache to the watchlist of the user */ + private class WatchlistAddThread extends Thread { + private final Handler handler; + + public WatchlistAddThread(Handler handler) { + this.handler = handler; + } + + @Override + public void run() { + handler.sendEmptyMessage(base.addToWatchlist(cache)); + } + } + + /** Thread to remove this cache from the watchlist of the user */ + private class WatchlistRemoveThread extends Thread { + private final Handler handler; + + public WatchlistRemoveThread(Handler handler) { + this.handler = handler; + } + + @Override + public void run() { + handler.sendEmptyMessage(base.removeFromWatchlist(cache)); + } + } + + private class addWaypoint implements View.OnClickListener { + + public void onClick(View view) { + Intent addWptIntent = new Intent(cgeodetail.this, cgeowaypointadd.class); + + addWptIntent.putExtra("geocode", geocode); + int wpCount = 0; + if (cache.waypoints != null) { + wpCount = cache.waypoints.size(); + } + addWptIntent.putExtra("count", wpCount); + + startActivity(addWptIntent); + } + } + + private static class decryptLog implements View.OnClickListener { + + public void onClick(View view) { + if (view == null) { + return; + } + + try { + final TextView logView = (TextView) view; + Spannable span = (Spannable) logView.getText(); + + // I needed to re-implement the base.rot13() encryption here because we must work on + // a SpannableStringBuilder instead of the pure text and we must replace each character inline. + // Otherwise we loose all the images, colors and so on... + SpannableStringBuilder buffer = new SpannableStringBuilder(span); + boolean plaintext = false; + + int length = span.length(); + for (int index = 0; index < length; index++) { + int c = span.charAt(index); + if (c == '[') { + plaintext = true; + } else if (c == ']') { + plaintext = false; + } else if (!plaintext) { + int capitalized = c & 32; + c &= ~capitalized; + c = ((c >= 'A') && (c <= 'Z') ? ((c - 'A' + 13) % 26 + 'A') : c) + | capitalized; + } + buffer.replace(index, index + 1, String.valueOf((char) c)); + } + logView.setText(buffer); + } catch (Exception e) { + // nothing + } + } + } + + private class userActions implements View.OnClickListener { + + public void onClick(View view) { + if (view == null) { + return; + } + + try { + registerForContextMenu(view); + openContextMenu(view); + } catch (Exception e) { + // nothing + } + } + } + + public void goCompass(View view) { + if (cache == null || cache.coords == null) { + showToast(res.getString(R.string.cache_coordinates_no)); + + return; + } + + Intent navigateIntent = new Intent(this, cgeonavigate.class); + navigateIntent.putExtra("latitude", cache.coords.getLatitude()); + navigateIntent.putExtra("longitude", cache.coords.getLongitude()); + navigateIntent.putExtra("geocode", cache.geocode.toUpperCase()); + navigateIntent.putExtra("name", cache.name); + + cgeonavigate.coordinates.clear(); + cgeonavigate.coordinates.addAll(getCoordinates()); + startActivity(navigateIntent); + } + + /** + * lazy-creates the layout holding the icons of the chaches attributes + * and makes it visible + */ + private void showAttributeIcons(LinearLayout attribBox, int parentWidth) { + if (attributeIconsLayout == null) { + attributeIconsLayout = createAttributeIconsLayout(parentWidth); + // no matching icons found? show text + if (noAttributeIconsFound) { + showAttributeDescriptions(attribBox); + return; + } + } + attribBox.removeAllViews(); + attribBox.addView(attributeIconsLayout); + attributesShowAsIcons = true; + } + + /** + * lazy-creates the layout holding the discriptions of the chaches attributes + * and makes it visible + */ + private void showAttributeDescriptions(LinearLayout attribBox) { + if (attributeDescriptionsLayout == null) { + attributeDescriptionsLayout = createAttributeDescriptionsLayout(); + } + attribBox.removeAllViews(); + attribBox.addView(attributeDescriptionsLayout); + attributesShowAsIcons = false; + } + + /** + * toggle attribute descriptions and icons + */ + private void toggleAttributeDisplay(LinearLayout attribBox, int parentWidth) { + // Don't toggle when there are no icons to show. + if (noAttributeIconsFound) { + return; + } + + // toggle + if (attributesShowAsIcons) { + showAttributeDescriptions(attribBox); + } else { + showAttributeIcons(attribBox, parentWidth); + } + } + + private ViewGroup createAttributeIconsLayout(int parentWidth) { + LinearLayout rows = new LinearLayout(this); + rows.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.WRAP_CONTENT)); + rows.setOrientation(LinearLayout.VERTICAL); + + LinearLayout attributeRow = newAttributeIconsRow(); + rows.addView(attributeRow); + + noAttributeIconsFound = true; + + for (String attributeName : cache.attributes) { + boolean strikethru = attributeName.endsWith("_no"); + // cut off _yes / _no + if (attributeName.endsWith("_no") || attributeName.endsWith("_yes")) { + attributeName = attributeName.substring(0, attributeName.lastIndexOf("_")); + } + // check if another attribute icon fits in this row + attributeRow.measure(0, 0); + int rowWidth = attributeRow.getMeasuredWidth(); + FrameLayout fl = (FrameLayout) inflater.inflate(R.layout.attribute_image, null); + ImageView iv = (ImageView) fl.getChildAt(0); + if ((parentWidth - rowWidth) < iv.getLayoutParams().width) { + // make a new row + attributeRow = newAttributeIconsRow(); + rows.addView(attributeRow); + } + + // dynamically search icon of the attribute + Drawable d = null; + int id = res.getIdentifier("attribute_" + attributeName, "drawable", + base.context.getPackageName()); + if (id > 0) { + noAttributeIconsFound = false; + d = res.getDrawable(id); + iv.setImageDrawable(d); + // strike through? + if (strikethru) { + // generate strikethru image with same properties as attribute image + ImageView strikethruImage = new ImageView(this); + strikethruImage.setLayoutParams(iv.getLayoutParams()); + d = res.getDrawable(R.drawable.attribute__strikethru); + strikethruImage.setImageDrawable(d); + fl.addView(strikethruImage); + } + } else { + d = res.getDrawable(R.drawable.attribute_icon_not_found); + iv.setImageDrawable(d); + } + + attributeRow.addView(fl); + } + + return rows; + } + + private LinearLayout newAttributeIconsRow() { + LinearLayout rowLayout = new LinearLayout(this); + rowLayout.setLayoutParams(new LayoutParams(LayoutParams.FILL_PARENT, + LayoutParams.WRAP_CONTENT)); + rowLayout.setOrientation(LinearLayout.HORIZONTAL); + return rowLayout; + } + + private ViewGroup createAttributeDescriptionsLayout() { + final LinearLayout descriptions = (LinearLayout) inflater.inflate( + R.layout.attribute_descriptions, null); + TextView attribView = (TextView) descriptions.getChildAt(0); + + StringBuilder buffer = new StringBuilder(); + String attribute; + for (int i = 0; i < cache.attributes.size(); i++) { + attribute = cache.attributes.get(i); + + // dynamically search for a translation of the attribute + int id = res.getIdentifier("attribute_" + attribute, "string", + base.context.getPackageName()); + if (id > 0) { + String translated = res.getString(id); + if (StringUtils.isNotBlank(translated)) { + attribute = translated; + } + } + if (buffer.length() > 0) + buffer.append('\n'); + buffer.append(attribute); + } + + if (noAttributeIconsFound) + buffer.append("\n\n").append(res.getString(R.string.cache_attributes_no_icons)); + + attribView.setText(buffer); + + return descriptions; + } + + public static void startActivity(final Context context, final String geocode) { + final Intent detailIntent = new Intent(context, cgeodetail.class); + detailIntent.putExtra("geocode", geocode.toUpperCase()); + context.startActivity(detailIntent); + } +} diff --git a/main/src/cgeo/geocaching/cgeogpxes.java b/main/src/cgeo/geocaching/cgeogpxes.java new file mode 100644 index 0000000..e3e9ba6 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeogpxes.java @@ -0,0 +1,132 @@ +package cgeo.geocaching; + +import cgeo.geocaching.files.FileList; +import cgeo.geocaching.files.GPXParser; +import cgeo.geocaching.files.LocParser; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; + +import java.io.File; +import java.util.List; +import java.util.UUID; + +public class cgeogpxes extends FileList<cgGPXListAdapter> { + + private static final String EXTRAS_LIST_ID = "list"; + + public cgeogpxes() { + super(new String[] { "gpx" + // TODO , "loc" + }); + } + + private ProgressDialog parseDialog = null; + private int listId = 1; + private int imported = 0; + + final private Handler changeParseDialogHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (msg.obj != null && parseDialog != null) { + parseDialog.setMessage(res.getString(R.string.gpx_import_loading_stored) + " " + (Integer) msg.obj); + } + } + }; + final private Handler loadCachesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (parseDialog != null) { + parseDialog.dismiss(); + } + + helpDialog(res.getString(R.string.gpx_import_title_caches_imported), imported + " " + res.getString(R.string.gpx_import_caches_imported)); + imported = 0; + } catch (Exception e) { + if (parseDialog != null) { + parseDialog.dismiss(); + } + } + } + }; + + @Override + protected cgGPXListAdapter getAdapter(List<File> files) { + return new cgGPXListAdapter(this, getSettings(), files); + } + + @Override + protected String[] getBaseFolders() { + String base = Environment.getExternalStorageDirectory().toString(); + return new String[] { base + "/gpx" }; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + listId = extras.getInt(EXTRAS_LIST_ID); + } + if (listId <= 0) { + listId = 1; + } + + } + + @Override + protected void setTitle() { + setTitle(res.getString(R.string.gpx_import_title)); + } + + public void loadGPX(File file) { + + parseDialog = ProgressDialog.show( + this, + res.getString(R.string.gpx_import_title_reading_file), + res.getString(R.string.gpx_import_loading), + true, + false); + + new loadCaches(file).start(); + } + + private class loadCaches extends Thread { + + File file = null; + + public loadCaches(File fileIn) { + file = fileIn; + } + + @Override + public void run() { + final UUID searchId; + String name = file.getName().toLowerCase(); + if (name.endsWith("gpx")) { + searchId = GPXParser.parseGPX(app, file, listId, changeParseDialogHandler); + } + else { + searchId = LocParser.parseLoc(app, file, listId, changeParseDialogHandler); + } + imported = app.getCount(searchId); + + loadCachesHandler.sendMessage(new Message()); + } + } + + public static void startSubActivity(Activity fromActivity, int listId) { + final Intent intent = new Intent(fromActivity, cgeogpxes.class); + intent.putExtra(EXTRAS_LIST_ID, listId); + fromActivity.startActivityForResult(intent, 0); + } +} diff --git a/main/src/cgeo/geocaching/cgeohelpers.java b/main/src/cgeo/geocaching/cgeohelpers.java new file mode 100644 index 0000000..cc288b1 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeohelpers.java @@ -0,0 +1,64 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import android.view.View; + +import java.util.Locale; + +public class cgeohelpers extends AbstractActivity { + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + setTheme(); + setContentView(R.layout.helpers); + setTitle(res.getString(R.string.helpers)); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + private void installFromMarket(String marketId) { + try { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("market://search?q=pname:" + marketId))); + } catch (Exception e) { + // market not available in standard emulator + } + + finish(); + } + + public void installManual(View view) { + final Locale loc = Locale.getDefault(); + final String language = loc.getLanguage(); + + if ("de".equalsIgnoreCase(language)) { + installFromMarket("gnu.android.app.cgeomanual.de"); + } + else { + installFromMarket("gnu.android.app.cgeomanual.en"); + } + } + + public void installLocus(View view) { + installFromMarket("menion.android.locus"); + } + + public void installGpsStatus(View view) { + installFromMarket("com.eclipsim.gpsstatus2"); + } + + public void installBluetoothGps(View view) { + installFromMarket("googoo.android.btgps"); + } +} diff --git a/main/src/cgeo/geocaching/cgeoimages.java b/main/src/cgeo/geocaching/cgeoimages.java new file mode 100644 index 0000000..c1a6bee --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoimages.java @@ -0,0 +1,303 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.Bitmap.CompressFormat; +import android.graphics.Rect; +import android.graphics.drawable.BitmapDrawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.io.File; +import java.io.FileOutputStream; +import java.util.ArrayList; +import java.util.List; + +public class cgeoimages extends AbstractActivity { + + public static final int LOG_IMAGE = 1; + public static final int SPOILER_IMAGE = 2; + + private int img_type; + private List<cgImage> images = new ArrayList<cgImage>(); + private String geocode = null; + private String title = null; + private String url = null; + private LayoutInflater inflater = null; + private ProgressDialog progressDialog = null; + private ProgressDialog waitDialog = null; + private LinearLayout imagesView = null; + private int offline = 0; + private boolean save = true; + private int count = 0; + private int countDone = 0; + private String load_process_string; + + private Handler loadImagesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (images.isEmpty()) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + switch (img_type) { + case LOG_IMAGE: + showToast(res.getString(R.string.warn_load_log_image)); + break; + case SPOILER_IMAGE: + showToast(res.getString(R.string.warn_load_spoiler_image)); + break; + } + + finish(); + return; + } else { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + if (app.isOffline(geocode, null)) { + offline = 1; + if ((img_type == LOG_IMAGE) && (settings.storelogimages == false)) { + offline = 0; + } + } else { + offline = 0; + } + + count = images.size(); + progressDialog = new ProgressDialog(cgeoimages.this); + progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + progressDialog.setMessage(load_process_string); + progressDialog.setCancelable(true); + progressDialog.setMax(count); + progressDialog.show(); + + LinearLayout rowView = null; + for (final cgImage img : images) { + rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null); + + ((TextView) rowView.findViewById(R.id.title)).setText(Html.fromHtml(img.title)); + + if (StringUtils.isNotBlank(img.description)) { + final TextView descView = (TextView) rowView.findViewById(R.id.description); + descView.setText(Html.fromHtml(img.description), TextView.BufferType.SPANNABLE); + descView.setVisibility(View.VISIBLE); + } + + final Handler handler = new onLoadHandler(rowView, img); + + new Thread() { + + @Override + public void run() { + BitmapDrawable image = null; + try { + cgHtmlImg imgGetter = new cgHtmlImg(cgeoimages.this, geocode, true, offline, false, save); + + image = imgGetter.getDrawable(img.url); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoimages.onCreate.onClick.run: " + e.toString()); + } + + } + }.start(); + + imagesView.addView(rowView); + } + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + Log.e(cgSettings.tag, "cgeoimages.loadImagesHandler: " + e.toString()); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + setTheme(); + setContentView(R.layout.spoilers); + + // get parameters + Bundle extras = getIntent().getExtras(); + + // try to get data from extras + if (extras != null) { + geocode = extras.getString("geocode"); + img_type = extras.getInt("type", 0); + } + + // google analytics + if (img_type == SPOILER_IMAGE) + { + setTitle(res.getString(R.string.cache_spoiler_images_title)); + } else if (img_type == LOG_IMAGE) { + setTitle(res.getString(R.string.cache_log_images_title)); + } + + if (geocode == null) { + showToast("Sorry, c:geo forgot for what cache you want to load spoiler images."); + finish(); + return; + } + switch (img_type) { + case LOG_IMAGE: + title = extras.getString("title"); + url = extras.getString("url"); + if ((title == null) || (url == null)) { + showToast("Sorry, c:geo forgot which logimage you wanted to load."); + finish(); + return; + } + break; + } + + inflater = getLayoutInflater(); + if (imagesView == null) { + imagesView = (LinearLayout) findViewById(R.id.spoiler_list); + } + + switch (img_type) { + case SPOILER_IMAGE: + load_process_string = res.getString(R.string.cache_spoiler_images_loading); + save = true; + break; + case LOG_IMAGE: + load_process_string = res.getString(R.string.cache_log_images_loading); + if (settings.storelogimages) { + save = true; + } else { + save = false; + } + break; + default: + load_process_string = "Loading..."; + } + waitDialog = ProgressDialog.show(this, null, load_process_string, true); + waitDialog.setCancelable(true); + + switch (img_type) { + case LOG_IMAGE: + cgImage logimage = new cgImage(); + logimage.title = title; + logimage.url = url; + logimage.description = ""; + images.add(logimage); + try { + loadImagesHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoimages.loadImagesHandler.sendMessage: " + e.toString()); + } + break; + case SPOILER_IMAGE: + (new loadSpoilers()).start(); + break; + default: + showToast("Sorry, can't load unknown image type."); + finish(); + } + + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + private class loadSpoilers extends Thread { + + @Override + public void run() { + try { + images = app.loadSpoilers(geocode); + + loadImagesHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoimages.loadSpoilers.run: " + e.toString()); + } + } + } + + private class onLoadHandler extends Handler { + + LinearLayout view = null; + + public onLoadHandler(LinearLayout view, cgImage image) { + this.view = view; + } + + @Override + public void handleMessage(Message message) { + final BitmapDrawable image = (BitmapDrawable) message.obj; + if (image != null) { + ImageView image_view = null; + image_view = (ImageView) inflater.inflate(R.layout.image_item, null); + + Rect bounds = image.getBounds(); + + image_view.setImageResource(R.drawable.image_not_loaded); + image_view.setClickable(true); + image_view.setOnClickListener(new View.OnClickListener() { + + public void onClick(View arg0) { + final String directoryTarget = Environment.getExternalStorageDirectory() + "/" + cgSettings.cache + "/" + "temp.jpg"; + final File file = new File(directoryTarget); + try { + final FileOutputStream fos = new FileOutputStream(file); + image.getBitmap().compress(CompressFormat.JPEG, 100, fos); + fos.close(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeoimages.handleMessage.onClick: " + e.toString()); + return; + } + + Intent intent = new Intent(); + intent.setAction(android.content.Intent.ACTION_VIEW); + intent.setDataAndType(Uri.fromFile(file), "image/jpg"); + startActivity(intent); + + if (file.exists()) + file.deleteOnExit(); + } + }); + image_view.setImageDrawable(image); + image_view.setScaleType(ImageView.ScaleType.CENTER_CROP); + image_view.setLayoutParams(new LayoutParams(bounds.width(), bounds.height())); + + view.addView(image_view); + } + + countDone++; + progressDialog.setProgress(countDone); + if (progressDialog.getProgress() >= count) { + progressDialog.dismiss(); + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgeoinit.java b/main/src/cgeo/geocaching/cgeoinit.java new file mode 100644 index 0000000..10495ee --- /dev/null +++ b/main/src/cgeo/geocaching/cgeoinit.java @@ -0,0 +1,1081 @@ +package cgeo.geocaching; + +import cgeo.geocaching.LogTemplateProvider.LogTemplate; +import cgeo.geocaching.cgSettings.mapSourceEnum; +import cgeo.geocaching.activity.AbstractActivity; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemSelectedListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.Spinner; +import android.widget.TextView; + +import java.io.File; + +public class cgeoinit extends AbstractActivity { + + private final int SELECT_MAPFILE_REQUEST = 1; + + private ProgressDialog loginDialog = null; + private ProgressDialog webDialog = null; + private Handler logInHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (loginDialog != null && loginDialog.isShowing()) { + loginDialog.dismiss(); + } + + if (msg.what == 1) { + helpDialog(res.getString(R.string.init_login_popup), res.getString(R.string.init_login_popup_ok)); + } else { + if (cgBase.errorRetrieve.containsKey(msg.what)) { + helpDialog(res.getString(R.string.init_login_popup), + res.getString(R.string.init_login_popup_failed_reason) + " " + cgBase.errorRetrieve.get(msg.what) + "."); + } else { + helpDialog(res.getString(R.string.init_login_popup), res.getString(R.string.init_login_popup_failed)); + } + } + } catch (Exception e) { + showToast(res.getString(R.string.err_login_failed)); + + Log.e(cgSettings.tag, "cgeoinit.logInHandler: " + e.toString()); + } + + if (loginDialog != null && loginDialog.isShowing()) { + loginDialog.dismiss(); + } + + init(); + } + }; + + private Handler webAuthHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (webDialog != null && webDialog.isShowing()) { + webDialog.dismiss(); + } + + if (msg.what > 0) { + helpDialog(res.getString(R.string.init_sendToCgeo), res.getString(R.string.init_sendToCgeo_register_ok).replace("####", "" + msg.what)); + } else { + helpDialog(res.getString(R.string.init_sendToCgeo), res.getString(R.string.init_sendToCgeo_register_fail)); + } + } catch (Exception e) { + showToast(res.getString(R.string.init_sendToCgeo_register_fail)); + + Log.e(cgSettings.tag, "cgeoinit.webHandler: " + e.toString()); + } + + if (webDialog != null && webDialog.isShowing()) { + webDialog.dismiss(); + } + + init(); + } + }; + protected boolean enableTemplatesMenu = false; + + public cgeoinit() { + super("c:geo-configuration"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // init + + setTheme(); + setContentView(R.layout.init); + setTitle(res.getString(R.string.settings)); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onPause() { + saveValues(); + super.onPause(); + } + + @Override + public void onStop() { + saveValues(); + super.onStop(); + } + + @Override + public void onDestroy() { + saveValues(); + + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, 0, 0, res.getString(R.string.init_clear)).setIcon(android.R.drawable.ic_menu_delete); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == 0) { + boolean status = false; + + ((EditText) findViewById(R.id.username)).setText(""); + ((EditText) findViewById(R.id.password)).setText(""); + ((EditText) findViewById(R.id.passvote)).setText(""); + + status = saveValues(); + if (status) { + showToast(res.getString(R.string.init_cleared)); + } else { + showToast(res.getString(R.string.err_init_cleared)); + } + + finish(); + } + + return false; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + if (enableTemplatesMenu) { + menu.setHeaderTitle(R.string.init_signature_template_button); + for (LogTemplate template : LogTemplateProvider.getTemplates()) { + menu.add(0, template.getItemId(), 0, template.getResourceId()); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + LogTemplate template = LogTemplateProvider.getTemplate(item.getItemId()); + if (template != null) { + return insertSignatureTemplate(template); + } + return super.onContextItemSelected(item); + } + + private boolean insertSignatureTemplate(final LogTemplate template) { + EditText sig = (EditText) findViewById(R.id.signature); + String insertText = "[" + template.getTemplateString() + "]"; + cgBase.insertAtPosition(sig, insertText, true); + return true; + } + + public void init() { + + // geocaching.com settings + String usernameNow = prefs.getString("username", null); + if (usernameNow != null) { + ((EditText) findViewById(R.id.username)).setText(usernameNow); + } + String passwordNow = prefs.getString("password", null); + if (usernameNow != null) { + ((EditText) findViewById(R.id.password)).setText(passwordNow); + } + + Button logMeIn = (Button) findViewById(R.id.log_me_in); + logMeIn.setOnClickListener(new logIn()); + + TextView legalNote = (TextView) findViewById(R.id.legal_note); + legalNote.setClickable(true); + legalNote.setOnClickListener(new View.OnClickListener() { + + public void onClick(View arg0) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/about/termsofuse.aspx"))); + } + }); + + // gcvote settings + String passvoteNow = prefs.getString("pass-vote", null); + if (passvoteNow != null) { + ((EditText) findViewById(R.id.passvote)).setText(passvoteNow); + } + + // go4cache settings + TextView go4cache = (TextView) findViewById(R.id.about_go4cache); + go4cache.setClickable(true); + go4cache.setOnClickListener(new View.OnClickListener() { + + public void onClick(View arg0) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://go4cache.com/"))); + } + }); + + CheckBox publicButton = (CheckBox) findViewById(R.id.publicloc); + if (prefs.getInt("publicloc", 0) == 0) { + publicButton.setChecked(false); + } else { + publicButton.setChecked(true); + } + publicButton.setOnClickListener(new cgeoChangePublic()); + + // Twitter settings + Button authorizeTwitter = (Button) findViewById(R.id.authorize_twitter); + authorizeTwitter.setOnClickListener(new View.OnClickListener() { + + public void onClick(View arg0) { + Intent authIntent = new Intent(cgeoinit.this, cgeoauth.class); + startActivity(authIntent); + } + }); + + CheckBox twitterButton = (CheckBox) findViewById(R.id.twitter_option); + if (prefs.getInt("twitter", 0) == 0 || StringUtils.isBlank(settings.tokenPublic) || StringUtils.isBlank(settings.tokenSecret)) { + twitterButton.setChecked(false); + } else { + twitterButton.setChecked(true); + } + twitterButton.setOnClickListener(new cgeoChangeTwitter()); + + // Signature settings + EditText sigEdit = (EditText) findViewById(R.id.signature); + if (sigEdit.getText().length() == 0) { + sigEdit.setText(settings.getSignature()); + } + Button sigBtn = (Button) findViewById(R.id.signature_help); + sigBtn.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + helpDialog(res.getString(R.string.init_signature_help_title), res.getString(R.string.init_signature_help_text)); + } + }); + Button templates = (Button) findViewById(R.id.signature_template); + registerForContextMenu(templates); + templates.setOnClickListener(new View.OnClickListener() { + public void onClick(View v) { + enableTemplatesMenu = true; + openContextMenu(v); + enableTemplatesMenu = false; + } + }); + CheckBox autoinsertButton = (CheckBox) findViewById(R.id.sigautoinsert); + autoinsertButton.setChecked(prefs.getBoolean("sigautoinsert", false)); + autoinsertButton.setOnClickListener(new cgeoChangeSignatureAutoinsert()); + + // Other settings + CheckBox skinButton = (CheckBox) findViewById(R.id.skin); + if (prefs.getInt("skin", 0) == 0) { + skinButton.setChecked(false); + } else { + skinButton.setChecked(true); + } + skinButton.setOnClickListener(new cgeoChangeSkin()); + + CheckBox addressButton = (CheckBox) findViewById(R.id.address); + if (prefs.getInt("showaddress", 1) == 0) { + addressButton.setChecked(false); + } else { + addressButton.setChecked(true); + } + addressButton.setOnClickListener(new cgeoChangeAddress()); + + CheckBox captchaButton = (CheckBox) findViewById(R.id.captcha); + if (prefs.getBoolean("showcaptcha", false) == false) { + captchaButton.setChecked(false); + } else { + captchaButton.setChecked(true); + } + captchaButton.setOnClickListener(new cgeoChangeCaptcha()); + + final CheckBox dirImgButton = (CheckBox) findViewById(R.id.loaddirectionimg); + dirImgButton.setChecked(settings.getLoadDirImg()); + dirImgButton.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + settings.setLoadDirImg(!settings.getLoadDirImg()); + dirImgButton.setChecked(settings.getLoadDirImg()); + } + }); + + CheckBox useEnglishButton = (CheckBox) findViewById(R.id.useenglish); + if (prefs.getBoolean("useenglish", false) == false) { + useEnglishButton.setChecked(false); + } else { + useEnglishButton.setChecked(true); + } + useEnglishButton.setOnClickListener(new cgeoChangeUseEnglish()); + + CheckBox excludeButton = (CheckBox) findViewById(R.id.exclude); + if (prefs.getInt("excludemine", 0) == 0) { + excludeButton.setChecked(false); + } else { + excludeButton.setChecked(true); + } + excludeButton.setOnClickListener(new cgeoChangeExclude()); + + CheckBox disabledButton = (CheckBox) findViewById(R.id.disabled); + if (prefs.getInt("excludedisabled", 0) == 0) { + disabledButton.setChecked(false); + } else { + disabledButton.setChecked(true); + } + disabledButton.setOnClickListener(new cgeoChangeDisabled()); + + CheckBox autovisitButton = (CheckBox) findViewById(R.id.trackautovisit); + if (prefs.getBoolean("trackautovisit", false)) { + autovisitButton.setChecked(true); + } else { + autovisitButton.setChecked(false); + } + autovisitButton.setOnClickListener(new cgeoChangeAutovisit()); + + CheckBox offlineButton = (CheckBox) findViewById(R.id.offline); + if (prefs.getInt("offlinemaps", 1) == 0) { + offlineButton.setChecked(false); + } else { + offlineButton.setChecked(true); + } + offlineButton.setOnClickListener(new cgeoChangeOffline()); + + CheckBox saveLogImgButton = (CheckBox) findViewById(R.id.save_log_img); + if (prefs.getBoolean("logimages", false) == false) { + saveLogImgButton.setChecked(false); + } else { + saveLogImgButton.setChecked(true); + } + saveLogImgButton.setOnClickListener(new cgeoChangeSaveLogImg()); + + CheckBox autoloadButton = (CheckBox) findViewById(R.id.autoload); + if (prefs.getInt("autoloaddesc", 0) == 0) { + autoloadButton.setChecked(false); + } else { + autoloadButton.setChecked(true); + } + autoloadButton.setOnClickListener(new cgeoChangeAutoload()); + + CheckBox livelistButton = (CheckBox) findViewById(R.id.livelist); + if (prefs.getInt("livelist", 1) == 0) { + livelistButton.setChecked(false); + } else { + livelistButton.setChecked(true); + } + livelistButton.setOnClickListener(new cgeoChangeLivelist()); + + CheckBox unitsButton = (CheckBox) findViewById(R.id.units); + if (prefs.getInt("units", cgSettings.unitsMetric) == cgSettings.unitsMetric) { + unitsButton.setChecked(false); + } else { + unitsButton.setChecked(true); + } + unitsButton.setOnClickListener(new cgeoChangeUnits()); + + CheckBox gnavButton = (CheckBox) findViewById(R.id.gnav); + if (prefs.getInt("usegnav", 1) == 1) { + gnavButton.setChecked(true); + } else { + gnavButton.setChecked(false); + } + gnavButton.setOnClickListener(new cgeoChangeGNav()); + + final CheckBox logOffline = (CheckBox) findViewById(R.id.log_offline); + logOffline.setChecked(settings.getLogOffline()); + logOffline.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + settings.setLogOffline(!settings.getLogOffline()); + logOffline.setChecked(settings.getLogOffline()); + } + }); + + CheckBox browserButton = (CheckBox) findViewById(R.id.browser); + if (prefs.getInt("asbrowser", 1) == 0) { + browserButton.setChecked(false); + } else { + browserButton.setChecked(true); + } + browserButton.setOnClickListener(new cgeoChangeBrowser()); + + // Altitude settings + EditText altitudeEdit = (EditText) findViewById(R.id.altitude); + altitudeEdit.setText("" + prefs.getInt("altcorrection", 0)); + + //Send2cgeo settings + String webDeviceName = prefs.getString("webDeviceName", null); + + if (StringUtils.isNotBlank(webDeviceName)) { + ((EditText) findViewById(R.id.webDeviceName)).setText(webDeviceName); + } else { + String s = android.os.Build.MODEL; + ((EditText) findViewById(R.id.webDeviceName)).setText(s); + } + + Button webAuth = (Button) findViewById(R.id.sendToCgeo_register); + webAuth.setOnClickListener(new webAuth()); + + // Map source settings + Spinner mapSourceSelector = (Spinner) findViewById(R.id.mapsource); + ArrayAdapter<CharSequence> adapter = ArrayAdapter.createFromResource( + this, R.array.map_sources, android.R.layout.simple_spinner_item); + adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); + mapSourceSelector.setAdapter(adapter); + int mapsource = prefs.getInt("mapsource", 0); + mapSourceSelector.setSelection(mapsource); + mapSourceSelector.setOnItemSelectedListener(new cgeoChangeMapSource()); + + initMapfileEdittext(false); + + Button selectMapfile = (Button) findViewById(R.id.select_mapfile); + selectMapfile.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + Intent selectIntent = new Intent(cgeoinit.this, cgSelectMapfile.class); + startActivityForResult(selectIntent, SELECT_MAPFILE_REQUEST); + } + }); + + showBackupDate(); + + } + + private void initMapfileEdittext(boolean setFocus) { + EditText mfmapFileEdit = (EditText) findViewById(R.id.mapfile); + mfmapFileEdit.setText(prefs.getString("mfmapfile", "")); + if (setFocus) { + mfmapFileEdit.requestFocus(); + } + } + + public void backup(View view) { + // avoid overwriting an existing backup with an empty database (can happen directly after reinstalling the app) + if (app.getAllStoredCachesCount(true, null, null) == 0) { + return; + } + + final String file = app.backupDatabase(); + + if (file != null) { + helpDialog(res.getString(R.string.init_backup_backup), res.getString(R.string.init_backup_success) + "\n" + file); + } else { + helpDialog(res.getString(R.string.init_backup_backup), res.getString(R.string.init_backup_failed)); + } + + showBackupDate(); + } + + private void showBackupDate() { + TextView lastBackup = (TextView) findViewById(R.id.backup_last); + File lastBackupFile = cgeoapplication.isRestoreFile(); + if (lastBackupFile != null) { + lastBackup.setText(res.getString(R.string.init_backup_last) + " " + base.formatTime(lastBackupFile.lastModified()) + ", " + base.formatDate(lastBackupFile.lastModified())); + } else { + lastBackup.setText(res.getString(R.string.init_backup_last_no)); + } + } + + public void restore(View view) { + final boolean status = app.restoreDatabase(); + + if (status) { + helpDialog(res.getString(R.string.init_backup_restore), res.getString(R.string.init_restore_success)); + } else { + helpDialog(res.getString(R.string.init_backup_restore), res.getString(R.string.init_restore_failed)); + } + } + + public boolean saveValues() { + String usernameNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.username)).getText().toString()); + String passwordNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.password)).getText().toString()); + String passvoteNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.passvote)).getText().toString()); + // don't trim signature, user may want to have whitespace at the beginning + String signatureNew = ((EditText) findViewById(R.id.signature)).getText().toString(); + String altitudeNew = StringUtils.trimToNull(((EditText) findViewById(R.id.altitude)).getText().toString()); + String mfmapFileNew = StringUtils.trimToEmpty(((EditText) findViewById(R.id.mapfile)).getText().toString()); + + int altitudeNewInt = 0; + if (altitudeNew != null) { + try { + altitudeNewInt = Integer.parseInt(altitudeNew); + } catch (NumberFormatException e) { + altitudeNewInt = 0; + } + } + + final boolean status1 = settings.setLogin(usernameNew, passwordNew); + final boolean status2 = settings.setGCvoteLogin(passvoteNew); + final boolean status3 = settings.setSignature(signatureNew); + final boolean status4 = settings.setAltCorrection(altitudeNewInt); + final boolean status5 = settings.setMapFile(mfmapFileNew); + + return status1 && status2 && status3 && status4 && status5; + } + + private class cgeoChangeTwitter implements View.OnClickListener { + + public void onClick(View arg0) { + CheckBox twitterButton = (CheckBox) findViewById(R.id.twitter_option); + + if (twitterButton.isChecked()) { + settings.reloadTwitterTokens(); + + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("twitter", 0) == 0) { + edit.putInt("twitter", 1); + settings.twitter = 1; + } else { + edit.putInt("twitter", 0); + settings.twitter = 0; + } + edit.commit(); + + if (settings.twitter == 1 && (StringUtils.isBlank(settings.tokenPublic) || StringUtils.isBlank(settings.tokenSecret))) { + Intent authIntent = new Intent(cgeoinit.this, cgeoauth.class); + startActivity(authIntent); + } + + if (prefs.getInt("twitter", 0) == 0) { + twitterButton.setChecked(false); + } else { + twitterButton.setChecked(true); + } + } else { + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("twitter", 0); + settings.twitter = 0; + edit.commit(); + + twitterButton.setChecked(false); + } + + return; + } + } + + private class cgeoChangeSkin implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("skin", 0) == 0) { + edit.putInt("skin", 1); + settings.setSkin(1); + } else { + edit.putInt("skin", 0); + settings.setSkin(0); + } + edit.commit(); + + CheckBox skinButton = (CheckBox) findViewById(R.id.skin); + if (prefs.getInt("skin", 0) == 0) { + skinButton.setChecked(false); + } else { + skinButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeAddress implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("showaddress", 1) == 0) { + edit.putInt("showaddress", 1); + } else { + edit.putInt("showaddress", 0); + } + edit.commit(); + + CheckBox transparentButton = (CheckBox) findViewById(R.id.address); + if (prefs.getInt("showaddress", 1) == 0) { + transparentButton.setChecked(false); + } else { + transparentButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangePublic implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("publicloc", 0) == 0) { + edit.putInt("publicloc", 1); + settings.publicLoc = 1; + } else { + edit.putInt("publicloc", 0); + settings.publicLoc = 0; + } + edit.commit(); + + CheckBox publicloc = (CheckBox) findViewById(R.id.publicloc); + if (prefs.getInt("publicloc", 0) == 0) { + publicloc.setChecked(false); + } else { + publicloc.setChecked(true); + } + + return; + } + } + + private class cgeoChangeCaptcha implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getBoolean("showcaptcha", false) == false) { + edit.putBoolean("showcaptcha", true); + settings.showCaptcha = true; + } else { + edit.putBoolean("showcaptcha", false); + settings.showCaptcha = false; + } + edit.commit(); + + CheckBox captchaButton = (CheckBox) findViewById(R.id.captcha); + if (prefs.getBoolean("showcaptcha", false) == false) { + captchaButton.setChecked(false); + } else { + captchaButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeUseEnglish implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getBoolean("useenglish", false) == false) { + edit.putBoolean("useenglish", true); + settings.useEnglish = true; + settings.setLanguage(true); + } else { + edit.putBoolean("useenglish", false); + settings.useEnglish = false; + settings.setLanguage(false); + } + edit.commit(); + + CheckBox useEnglishButton = (CheckBox) findViewById(R.id.useenglish); + if (prefs.getBoolean("useenglish", false) == false) { + useEnglishButton.setChecked(false); + } else { + useEnglishButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeExclude implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("excludemine", 0) == 0) { + edit.putInt("excludemine", 1); + settings.excludeMine = 1; + } else { + edit.putInt("excludemine", 0); + settings.excludeMine = 0; + } + edit.commit(); + + CheckBox excludeButton = (CheckBox) findViewById(R.id.exclude); + if (prefs.getInt("excludemine", 0) == 0) { + excludeButton.setChecked(false); + } else { + excludeButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeAutovisit implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getBoolean("trackautovisit", false)) { + edit.putBoolean("trackautovisit", false); + settings.trackableAutovisit = false; + } else { + edit.putBoolean("trackautovisit", true); + settings.trackableAutovisit = true; + } + edit.commit(); + + CheckBox autovisitButton = (CheckBox) findViewById(R.id.trackautovisit); + if (prefs.getBoolean("trackautovisit", false) == false) { + autovisitButton.setChecked(false); + } else { + autovisitButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeSignatureAutoinsert implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getBoolean("sigautoinsert", false)) { + edit.putBoolean("sigautoinsert", false); + settings.signatureAutoinsert = false; + } else { + edit.putBoolean("sigautoinsert", true); + settings.signatureAutoinsert = true; + } + edit.commit(); + + CheckBox autoinsertButton = (CheckBox) findViewById(R.id.sigautoinsert); + if (prefs.getBoolean("sigautoinsert", false) == false) { + autoinsertButton.setChecked(false); + } else { + autoinsertButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeDisabled implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("excludedisabled", 0) == 0) { + edit.putInt("excludedisabled", 1); + settings.excludeDisabled = 1; + } else { + edit.putInt("excludedisabled", 0); + settings.excludeDisabled = 0; + } + edit.commit(); + + CheckBox disabledButton = (CheckBox) findViewById(R.id.disabled); + if (prefs.getInt("excludedisabled", 0) == 0) { + disabledButton.setChecked(false); + } else { + disabledButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeOffline implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("offlinemaps", 1) == 0) { + edit.putInt("offlinemaps", 1); + settings.excludeDisabled = 1; + } else { + edit.putInt("offlinemaps", 0); + settings.excludeDisabled = 0; + } + edit.commit(); + + CheckBox offlineButton = (CheckBox) findViewById(R.id.offline); + if (prefs.getInt("offlinemaps", 0) == 0) { + offlineButton.setChecked(false); + } else { + offlineButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeSaveLogImg implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getBoolean("logimages", true) == false) { + edit.putBoolean("logimages", true); + settings.storelogimages = true; + } else { + edit.putBoolean("logimages", false); + settings.storelogimages = false; + } + edit.commit(); + + CheckBox saveLogImgButton = (CheckBox) findViewById(R.id.save_log_img); + if (prefs.getBoolean("logimages", true) == false) { + saveLogImgButton.setChecked(false); + } else { + saveLogImgButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeLivelist implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("livelist", 1) == 0) { + edit.putInt("livelist", 1); + settings.livelist = 1; + } else { + edit.putInt("livelist", 0); + settings.livelist = 0; + } + edit.commit(); + + CheckBox livelistButton = (CheckBox) findViewById(R.id.livelist); + if (prefs.getInt("livelist", 1) == 0) { + livelistButton.setChecked(false); + } else { + livelistButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeAutoload implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("autoloaddesc", 0) == 0) { + edit.putInt("autoloaddesc", 1); + settings.autoLoadDesc = 1; + } else { + edit.putInt("autoloaddesc", 0); + settings.autoLoadDesc = 0; + } + edit.commit(); + + CheckBox autoloadButton = (CheckBox) findViewById(R.id.autoload); + if (prefs.getInt("autoloaddesc", 0) == 0) { + autoloadButton.setChecked(false); + } else { + autoloadButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeUnits implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("units", cgSettings.unitsMetric) == cgSettings.unitsMetric) { + edit.putInt("units", cgSettings.unitsImperial); + settings.units = cgSettings.unitsImperial; + } else { + edit.putInt("units", cgSettings.unitsMetric); + settings.units = cgSettings.unitsMetric; + } + edit.commit(); + + CheckBox unitsButton = (CheckBox) findViewById(R.id.units); + if (prefs.getInt("units", cgSettings.unitsMetric) == cgSettings.unitsMetric) { + unitsButton.setChecked(false); + } else { + unitsButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeGNav implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("usegnav", 1) == 1) { + edit.putInt("usegnav", 0); + settings.useGNavigation = 0; + } else { + edit.putInt("usegnav", 1); + settings.useGNavigation = 1; + } + edit.commit(); + + CheckBox gnavButton = (CheckBox) findViewById(R.id.gnav); + if (prefs.getInt("usegnav", 1) == 1) { + gnavButton.setChecked(true); + } else { + gnavButton.setChecked(false); + } + + return; + } + } + + private class cgeoChangeBrowser implements View.OnClickListener { + + public void onClick(View arg0) { + SharedPreferences.Editor edit = prefs.edit(); + if (prefs.getInt("asbrowser", 1) == 0) { + edit.putInt("asbrowser", 1); + settings.asBrowser = 1; + } else { + edit.putInt("asbrowser", 0); + settings.asBrowser = 0; + } + edit.commit(); + + CheckBox browserButton = (CheckBox) findViewById(R.id.browser); + if (prefs.getInt("asbrowser", 1) == 0) { + browserButton.setChecked(false); + } else { + browserButton.setChecked(true); + } + + return; + } + } + + private class cgeoChangeMapSource implements OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, + long arg3) { + settings.mapSource = mapSourceEnum.fromInt(arg2); + SharedPreferences.Editor edit = prefs.edit(); + edit.putInt("mapsource", arg2); + edit.commit(); + } + + @Override + public void onNothingSelected(AdapterView<?> arg0) { + arg0.setSelection(settings.mapSource.ordinal()); + } + } + + private class logIn implements View.OnClickListener { + + public void onClick(View arg0) { + final String username = ((EditText) findViewById(R.id.username)).getText().toString(); + final String password = ((EditText) findViewById(R.id.password)).getText().toString(); + + if (StringUtils.isBlank(username) || StringUtils.isBlank(password)) { + showToast(res.getString(R.string.err_missing_auth)); + return; + } + + loginDialog = ProgressDialog.show(cgeoinit.this, res.getString(R.string.init_login_popup), res.getString(R.string.init_login_popup_working), true); + loginDialog.setCancelable(false); + + settings.setLogin(username, password); + CookieJar.deleteCookies(prefs); + + (new Thread() { + + @Override + public void run() { + final int loginResult = base.login(); + if (1 == loginResult) + { + base.detectGcCustomDate(); + } + logInHandler.sendEmptyMessage(loginResult); + } + }).start(); + } + } + + private class webAuth implements View.OnClickListener { + + public void onClick(View arg0) { + final String deviceName = ((EditText) findViewById(R.id.webDeviceName)).getText().toString(); + final String deviceCode = prefs.getString("webDeviceCode", null); + + if (StringUtils.isBlank(deviceName)) { + showToast(res.getString(R.string.err_missing_device_name)); + return; + } + + webDialog = ProgressDialog.show(cgeoinit.this, res.getString(R.string.init_sendToCgeo), res.getString(R.string.init_sendToCgeo_registering), true); + webDialog.setCancelable(false); + + (new Thread() { + + @Override + public void run() { + int pin = 0; + + String nam = deviceName == null ? "" : deviceName; + String cod = deviceCode == null ? "" : deviceCode; + + String params = "name=" + cgBase.urlencode_rfc3986(nam) + "&code=" + cgBase.urlencode_rfc3986(cod); + + cgResponse response = base.request(false, "send2.cgeo.org", "/auth.html", "GET", params, 0, true); + + if (response.getStatusCode() == 200) + { + //response was OK + String[] strings = response.getData().split(","); + try { + pin = Integer.parseInt(strings[1].trim()); + } catch (Exception e) { + Log.e(cgSettings.tag, "webDialog: " + e.toString()); + } + String code = strings[0]; + settings.setWebNameCode(nam, code); + } + + webAuthHandler.sendEmptyMessage(pin); + } + }).start(); + } + } + + @Override + protected void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + + if (requestCode == SELECT_MAPFILE_REQUEST) { + if (resultCode == RESULT_OK) { + if (data.hasExtra("mapfile")) { + settings.setMapFile(data.getStringExtra("mapfile")); + } + } + initMapfileEdittext(true); + } + } +} diff --git a/main/src/cgeo/geocaching/cgeonavigate.java b/main/src/cgeo/geocaching/cgeonavigate.java new file mode 100644 index 0000000..f4f8393 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeonavigate.java @@ -0,0 +1,463 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; + +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.os.PowerManager; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.WindowManager; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.List; +import java.util.Locale; + +public class cgeonavigate extends AbstractActivity { + + public final static List<cgCoord> coordinates = new ArrayList<cgCoord>(); + private PowerManager pm = null; + private cgGeo geo = null; + private cgDirection dir = null; + private cgUpdateLoc geoUpdate = new update(); + private cgUpdateDir dirUpdate = new UpdateDirection(); + private Geopoint dstCoords = null; + private float cacheHeading = 0; + private float northHeading = 0; + private String title = null; + private String name = null; + private TextView navType = null; + private TextView navAccuracy = null; + private TextView navSatellites = null; + private TextView navLocation = null; + private TextView distanceView = null; + private TextView headingView = null; + private cgCompass compassView = null; + private updaterThread updater = null; + private Handler updaterHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (compassView != null) { + compassView.updateNorth(northHeading, cacheHeading); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeonavigate.updaterHandler: " + e.toString()); + } + } + }; + + public cgeonavigate() { + super("c:geo-compass"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // set layout + getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + setTheme(); + setContentView(R.layout.navigate); + setTitle(res.getString(R.string.compass_title)); + + // sensor & geolocation manager + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(this, dirUpdate); + } + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + title = extras.getString("geocode"); + name = extras.getString("name"); + dstCoords = new Geopoint(extras.getDouble("latitude"), extras.getDouble("longitude")); + + if (StringUtils.isNotBlank(name)) { + if (StringUtils.isNotBlank(title)) { + title = title + ": " + name; + } else { + title = name; + } + } + } else { + Intent pointIntent = new Intent(this, cgeopoint.class); + startActivity(pointIntent); + + finish(); + return; + } + + if (StringUtils.isNotBlank(title)) { + app.setAction(title); + } else if (StringUtils.isNotBlank(name)) { + app.setAction(name); + } + + // set header + setTitle(); + setDestCoords(); + + // get textviews once + compassView = (cgCompass) findViewById(R.id.rose); + + // start updater thread + updater = new updaterThread(updaterHandler); + updater.start(); + + if (geo != null) { + geoUpdate.updateLoc(geo); + } + if (dir != null) { + dirUpdate.updateDir(dir); + } + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + + if (StringUtils.isNotBlank(title)) { + app.setAction(title); + } else if (StringUtils.isNotBlank(name)) { + app.setAction(name); + } + + // sensor & geolocation manager + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(this, dirUpdate); + } + + // keep backlight on + if (pm == null) { + pm = (PowerManager) getSystemService(Context.POWER_SERVICE); + } + + // updater thread + if (updater == null) { + updater = new updaterThread(updaterHandler); + updater.start(); + } + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + if (dir != null) { + dir = app.removeDir(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + if (dir != null) { + dir = app.removeDir(); + } + + super.onPause(); + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + if (dir != null) { + dir = app.removeDir(); + } + + compassView.destroyDrawingCache(); + compassView = null; + + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + if (settings.useCompass == 1) { + menu.add(0, 1, 0, res.getString(R.string.use_gps)).setIcon(android.R.drawable.ic_menu_compass); + } else { + menu.add(0, 1, 0, res.getString(R.string.use_compass)).setIcon(android.R.drawable.ic_menu_compass); + } + menu.add(0, 0, 0, res.getString(R.string.caches_on_map)).setIcon(android.R.drawable.ic_menu_mapmode); + menu.add(0, 2, 0, res.getString(R.string.destination_set)).setIcon(android.R.drawable.ic_menu_edit); + if (coordinates.size() > 1) { + SubMenu subMenu = menu.addSubMenu(0, 3, 0, res.getString(R.string.destination_select)).setIcon(android.R.drawable.ic_menu_myplaces); + + int cnt = 4; + for (cgCoord coordinate : coordinates) { + subMenu.add(0, cnt, 0, coordinate.name + " (" + coordinate.type + ")"); + cnt++; + } + + return true; + } else { + menu.add(0, 3, 0, res.getString(R.string.destination_select)).setIcon(android.R.drawable.ic_menu_myplaces).setEnabled(false); + + return true; + } + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + MenuItem item; + item = menu.findItem(1); + if (settings.useCompass == 1) { + item.setTitle(res.getString(R.string.use_gps)); + } else { + item.setTitle(res.getString(R.string.use_compass)); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == 0) { + Intent mapIntent = new Intent(this, settings.getMapFactory().getMapClass()); + mapIntent.putExtra("detail", false); + mapIntent.putExtra("latitude", dstCoords.getLatitude()); + mapIntent.putExtra("longitude", dstCoords.getLongitude()); + + startActivity(mapIntent); + } else if (id == 1) { + if (settings.useCompass == 1) { + settings.useCompass = 0; + + if (dir != null) { + dir = app.removeDir(); + } + + SharedPreferences.Editor prefsEdit = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefsEdit.putInt("usecompass", settings.useCompass); + prefsEdit.commit(); + } else { + settings.useCompass = 1; + + if (dir == null) { + dir = app.startDir(this, dirUpdate); + } + + SharedPreferences.Editor prefsEdit = getSharedPreferences(cgSettings.preferences, 0).edit(); + prefsEdit.putInt("usecompass", settings.useCompass); + prefsEdit.commit(); + } + } else if (id == 2) { + Intent pointIntent = new Intent(this, cgeopoint.class); + startActivity(pointIntent); + + finish(); + return true; + } else if (id > 3 && coordinates.get(id - 4) != null) { + cgCoord coordinate = coordinates.get(id - 4); + + title = coordinate.name; + dstCoords = coordinate.coords; + setTitle(); + setDestCoords(); + updateDistanceInfo(); + + Log.d(cgSettings.tag, "destination set: " + title + " (" + + String.format(Locale.getDefault(), "%.8f", dstCoords.getLatitude()) + " | " + + String.format(Locale.getDefault(), "%.8f", dstCoords.getLongitude()) + ")"); + return true; + } + + return false; + } + + private void setTitle() { + if (StringUtils.isNotBlank(title)) { + setTitle(title); + } else { + setTitle(res.getString(R.string.navigation)); + } + } + + private void setDestCoords() { + if (dstCoords == null) { + return; + } + + ((TextView) findViewById(R.id.destination)).setText(cgBase.formatCoords(dstCoords, true)); + } + + public void setDest(final Geopoint coords) { + if (coords == null) { + return; + } + + title = "some place"; + setTitle(); + setDestCoords(); + + dstCoords = coords; + updateDistanceInfo(); + } + + public Geopoint getCoordinatesNow() { + return geo.coordsNow; + } + + private void updateDistanceInfo() { + if (geo == null || geo.coordsNow == null || dstCoords == null) { + return; + } + + if (distanceView == null) { + distanceView = (TextView) findViewById(R.id.distance); + } + if (headingView == null) { + headingView = (TextView) findViewById(R.id.heading); + } + + cacheHeading = geo.coordsNow.bearingTo(dstCoords); + distanceView.setText(base.getHumanDistance(geo.coordsNow.distanceTo(dstCoords))); + headingView.setText(String.format(Locale.getDefault(), "%.0f", cacheHeading) + "°"); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + if (navType == null || navLocation == null || navAccuracy == null) { + navType = (TextView) findViewById(R.id.nav_type); + navAccuracy = (TextView) findViewById(R.id.nav_accuracy); + navSatellites = (TextView) findViewById(R.id.nav_satellites); + navLocation = (TextView) findViewById(R.id.nav_location); + } + + if (geo.coordsNow != null) { + String satellites = null; + if (geo.satellitesVisible != null && geo.satellitesFixed != null && geo.satellitesFixed > 0) { + satellites = res.getString(R.string.loc_sat) + ": " + geo.satellitesFixed + "/" + geo.satellitesVisible; + } else if (geo.satellitesVisible != null) { + satellites = res.getString(R.string.loc_sat) + ": 0/" + geo.satellitesVisible; + } else { + satellites = ""; + } + navSatellites.setText(satellites); + + if (geo.gps == -1) { + navType.setText(res.getString(R.string.loc_last)); + } else if (geo.gps == 0) { + navType.setText(res.getString(R.string.loc_net)); + } else { + navType.setText(res.getString(R.string.loc_gps)); + } + + if (geo.accuracyNow != null) { + if (settings.units == cgSettings.unitsImperial) { + navAccuracy.setText("±" + String.format(Locale.getDefault(), "%.0f", (geo.accuracyNow * 3.2808399)) + " ft"); + } else { + navAccuracy.setText("±" + String.format(Locale.getDefault(), "%.0f", geo.accuracyNow) + " m"); + } + } else { + navAccuracy.setText(null); + } + + if (geo.altitudeNow != null) { + String humanAlt; + if (settings.units == cgSettings.unitsImperial) { + humanAlt = String.format("%.0f", (geo.altitudeNow * 3.2808399)) + " ft"; + } else { + humanAlt = String.format("%.0f", geo.altitudeNow) + " m"; + } + navLocation.setText(cgBase.formatCoords(geo.coordsNow, true) + " | " + humanAlt); + } else { + navLocation.setText(cgBase.formatCoords(geo.coordsNow, true)); + } + + updateDistanceInfo(); + } else { + navType.setText(null); + navAccuracy.setText(null); + navLocation.setText(res.getString(R.string.loc_trying)); + } + + if (settings.useCompass == 0 || (geo.speedNow != null && geo.speedNow > 5)) { // use GPS when speed is higher than 18 km/h + if (geo != null && geo.bearingNow != null) { + northHeading = geo.bearingNow; + } else { + northHeading = 0; + } + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class UpdateDirection extends cgUpdateDir { + + @Override + public void updateDir(cgDirection dir) { + if (dir == null || dir.directionNow == null) { + return; + } + + if (geo == null || geo.speedNow == null || geo.speedNow <= 5) { // use compass when speed is lower than 18 km/h + northHeading = dir.directionNow; + } + } + } + + private static class updaterThread extends Thread { + + private Handler handler = null; + + public updaterThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + while (!Thread.currentThread().isInterrupted()) { + if (handler != null) { + handler.sendMessage(new Message()); + } + + try { + Thread.sleep(20); + } catch (Exception e) { + Thread.currentThread().interrupt(); + } + } + } + } +} diff --git a/main/src/cgeo/geocaching/cgeopoint.java b/main/src/cgeo/geocaching/cgeopoint.java new file mode 100644 index 0000000..26e3bce --- /dev/null +++ b/main/src/cgeo/geocaching/cgeopoint.java @@ -0,0 +1,557 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.geopoint.DistanceParser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.GeopointParser; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Configuration; +import android.os.Bundle; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.View.OnCreateContextMenuListener; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.AdapterView.OnItemClickListener; +import android.widget.ArrayAdapter; +import android.widget.Button; +import android.widget.EditText; +import android.widget.ListView; +import android.widget.TextView; + +import java.util.List; + +public class cgeopoint extends AbstractActivity { + + private static class DestinationHistoryAdapter extends ArrayAdapter<cgDestination> { + private LayoutInflater inflater = null; + + public DestinationHistoryAdapter(Context context, + List<cgDestination> objects) { + super(context, 0, objects); + } + + @Override + public View getView(int position, View convertView, ViewGroup parent) { + + cgDestination loc = getItem(position); + + if (convertView == null) { + convertView = getInflater().inflate(R.layout.simple_way_point, + null); + } + TextView longitude = (TextView) convertView + .findViewById(R.id.simple_way_point_longitude); + TextView latitude = (TextView) convertView + .findViewById(R.id.simple_way_point_latitude); + TextView date = (TextView) convertView.findViewById(R.id.date); + + String lonString = cgBase.formatLongitude(loc.getCoords().getLongitude(), true); + String latString = cgBase.formatLatitude(loc.getCoords().getLatitude(), true); + + longitude.setText(lonString); + latitude.setText(latString); + date.setText(cgBase.formatShortDateTime(getContext(), loc.getDate())); + + return convertView; + } + + private LayoutInflater getInflater() { + if (inflater == null) { + inflater = ((Activity) getContext()).getLayoutInflater(); + } + + return inflater; + } + } + + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private Button latButton = null; + private Button lonButton = null; + private boolean changed = false; + private List<cgDestination> historyOfSearchedLocations; + private DestinationHistoryAdapter destionationHistoryAdapter; + private ListView historyListView; + private TextView historyFooter; + + private static final int CONTEXT_MENU_DELETE_WAYPOINT = Menu.FIRST; + + public cgeopoint() { + super("c:geo-navigate-any"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.point); + setTitle(res.getString(R.string.search_destination)); + + createHistoryView(); + + init(); + } + + private void createHistoryView() { + historyListView = (ListView) findViewById(R.id.historyList); + + View pointControls = getLayoutInflater().inflate( + R.layout.point_controls, null); + historyListView.addHeaderView(pointControls); + + if (getHistoryOfSearchedLocations().isEmpty()) { + historyListView.addFooterView(getEmptyHistoryFooter(), null, false); + } + + historyListView.setAdapter(getDestionationHistoryAdapter()); + historyListView.setOnItemClickListener(new OnItemClickListener() { + + @Override + public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, + long arg3) { + Object selection = arg0.getItemAtPosition(arg2); + if (selection instanceof cgDestination) { + navigateTo(((cgDestination) selection).getCoords()); + } + } + }); + historyListView + .setOnCreateContextMenuListener(new OnCreateContextMenuListener() { + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + menu.add(Menu.NONE, CONTEXT_MENU_DELETE_WAYPOINT, + Menu.NONE, R.string.waypoint_delete); + } + }); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + switch (item.getItemId()) { + case CONTEXT_MENU_DELETE_WAYPOINT: + AdapterView.AdapterContextMenuInfo menuInfo = (AdapterView.AdapterContextMenuInfo) item + .getMenuInfo(); + Object destination = historyListView + .getItemAtPosition(menuInfo.position); + if (destination instanceof cgDestination) { + removeFromHistory((cgDestination) destination); + } + return true; + default: + return super.onContextItemSelected(item); + } + } + + private TextView getEmptyHistoryFooter() { + if (historyFooter == null) { + historyFooter = (TextView) getLayoutInflater().inflate( + R.layout.caches_footer, null); + historyFooter.setText(R.string.search_history_empty); + } + return historyFooter; + } + + private DestinationHistoryAdapter getDestionationHistoryAdapter() { + if (destionationHistoryAdapter == null) { + destionationHistoryAdapter = new DestinationHistoryAdapter(this, + getHistoryOfSearchedLocations()); + } + return destionationHistoryAdapter; + } + + private List<cgDestination> getHistoryOfSearchedLocations() { + if (historyOfSearchedLocations == null) { + // Load from database + historyOfSearchedLocations = app.getHistoryOfSearchedLocations(); + } + + return historyOfSearchedLocations; + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + init(); + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + private void init() { + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + latButton = (Button) findViewById(R.id.buttonLatitude); + lonButton = (Button) findViewById(R.id.buttonLongitude); + + latButton.setOnClickListener(new coordDialogListener()); + lonButton.setOnClickListener(new coordDialogListener()); + + if (prefs.contains("anylatitude") && prefs.contains("anylongitude")) { + latButton.setText(cgBase.formatLatitude(Double.valueOf(prefs.getFloat("anylatitude", 0f)), true)); + lonButton.setText(cgBase.formatLongitude(Double.valueOf(prefs.getFloat("anylongitude", 0f)), true)); + } + + Button buttonCurrent = (Button) findViewById(R.id.current); + buttonCurrent.setOnClickListener(new currentListener()); + + getDestionationHistoryAdapter().notifyDataSetChanged(); + } + + private class coordDialogListener implements View.OnClickListener { + + public void onClick(View arg0) { + Geopoint gp = null; + if (latButton.getText().length() > 0 && lonButton.getText().length() > 0) { + gp = new Geopoint(latButton.getText().toString() + " " + lonButton.getText().toString()); + } + cgeocoords coordsDialog = new cgeocoords(cgeopoint.this, settings, gp, geo); + coordsDialog.setCancelable(true); + coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + @Override + public void update(Geopoint gp) { + latButton.setText(cgBase.formatLatitude(gp.getLatitude(), true)); + lonButton.setText(cgBase.formatLongitude(gp.getLongitude(), true)); + changed = true; + } + }); + coordsDialog.show(); + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, 2, 0, res.getString(R.string.cache_menu_compass)).setIcon(android.R.drawable.ic_menu_compass); // compass + + SubMenu subMenu = menu.addSubMenu(1, 0, 0, res.getString(R.string.cache_menu_navigate)).setIcon(android.R.drawable.ic_menu_more); + NavigationAppFactory.addMenuItems(subMenu, this, res); + + menu.add(0, 5, 0, res.getString(R.string.cache_menu_around)).setIcon(android.R.drawable.ic_menu_rotate); // caches around + + // clear history + MenuItem clearHistoryItem = menu.add(0, 6, 0, res.getString(R.string.search_clear_history)); + clearHistoryItem.setIcon(android.R.drawable.ic_menu_delete); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + try { + final Geopoint coords = getDestination(); + + if (coords != null) { + menu.findItem(0).setVisible(true); + menu.findItem(2).setVisible(true); + menu.findItem(5).setVisible(true); + } else { + menu.findItem(0).setVisible(false); + menu.findItem(2).setVisible(false); + menu.findItem(5).setVisible(false); + } + + menu.findItem(6).setEnabled(!getHistoryOfSearchedLocations().isEmpty()); + } catch (Exception e) { + // nothing + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int menuItem = item.getItemId(); + + final Geopoint coords = getDestination(); + + if (coords != null) + { + addToHistory(coords); + } + + if (menuItem == 2) { + navigateTo(); + return true; + } else if (menuItem == 5) { + cachesAround(); + return true; + } + else if (menuItem == 6) { + clearHistory(); + return true; + } + + return NavigationAppFactory.onMenuItemSelected(item, geo, this, res, null, null, null, coords); + } + + private void addToHistory(final Geopoint coords) { + // Add locations to history + cgDestination loc = new cgDestination(); + loc.setCoords(coords); + + if (!getHistoryOfSearchedLocations().contains(loc)) + { + loc.setDate(System.currentTimeMillis()); + getHistoryOfSearchedLocations().add(0, loc); + + // Save location + app.saveSearchedDestination(loc); + + // Ensure to remove the footer + historyListView.removeFooterView(getEmptyHistoryFooter()); + } + } + + private void removeFromHistory(cgDestination destination) { + if (getHistoryOfSearchedLocations().contains(destination)) { + getHistoryOfSearchedLocations().remove(destination); + + // Save + app.removeSearchedDestinations(destination); + + if (getHistoryOfSearchedLocations().isEmpty()) { + if (historyListView.getFooterViewsCount() == 0) { + historyListView.addFooterView(getEmptyHistoryFooter()); + } + } + + getDestionationHistoryAdapter().notifyDataSetChanged(); + + showToast(res.getString(R.string.search_remove_destination)); + } + } + + private void clearHistory() { + if (!getHistoryOfSearchedLocations().isEmpty()) { + getHistoryOfSearchedLocations().clear(); + + // Save + app.clearSearchedDestinations(); + + if (historyListView.getFooterViewsCount() == 0) { + historyListView.addFooterView(getEmptyHistoryFooter()); + } + + getDestionationHistoryAdapter().notifyDataSetChanged(); + + showToast(res.getString(R.string.search_history_cleared)); + } + } + + private void navigateTo() { + navigateTo(getDestination()); + } + + private void navigateTo(Geopoint geopoint) { + if (geopoint == null) { + showToast(res.getString(R.string.err_location_unknown)); + return; + } + + cgeonavigate navigateActivity = new cgeonavigate(); + + Intent navigateIntent = new Intent(this, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", geopoint.getLatitude()); + navigateIntent.putExtra("longitude", geopoint.getLongitude()); + navigateIntent.putExtra("geocode", ""); + navigateIntent.putExtra("name", "Some destination"); + + startActivity(navigateIntent); + } + + private void cachesAround() { + final Geopoint coords = getDestination(); + + if (coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + return; + } + + cgeocaches cachesActivity = new cgeocaches(); + + Intent cachesIntent = new Intent(this, cachesActivity.getClass()); + + cachesIntent.putExtra("type", "coordinate"); + cachesIntent.putExtra("latitude", coords.getLatitude()); + cachesIntent.putExtra("longitude", coords.getLongitude()); + cachesIntent.putExtra("cachetype", settings.cacheType); + + startActivity(cachesIntent); + + finish(); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + latButton.setHint(cgBase.formatLatitude(geo.coordsNow.getLatitude(), false)); + lonButton.setHint(cgBase.formatLongitude(geo.coordsNow.getLongitude(), false)); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class currentListener implements View.OnClickListener { + + public void onClick(View arg0) { + if (geo == null || geo.coordsNow == null) { + showToast(res.getString(R.string.err_point_unknown_position)); + return; + } + + latButton.setText(cgBase.formatLatitude(geo.coordsNow.getLatitude(), true)); + lonButton.setText(cgBase.formatLongitude(geo.coordsNow.getLongitude(), true)); + + changed = false; + } + } + + private Geopoint getDestination() { + Geopoint result = null; + Geopoint coords = null; + + String bearingText = ((EditText) findViewById(R.id.bearing)).getText().toString(); + String distanceText = ((EditText) findViewById(R.id.distance)).getText().toString(); + String latText = latButton.getText().toString(); + String lonText = lonButton.getText().toString(); + + if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) + && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { + showToast(res.getString(R.string.err_point_no_position_given)); + return null; + } + + if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { + try { + coords = GeopointParser.parse(latText, lonText); + } catch (GeopointParser.ParseException e) { + showToast(res.getString(e.resource)); + return null; + } + } else { + if (geo == null || geo.coordsNow == null) { + showToast(res.getString(R.string.err_point_curr_position_unavailable)); + return null; + } + + coords = geo.coordsNow; + } + + if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) { + // bearing & distance + Double bearing = null; + try { + bearing = new Double(bearingText); + } catch (Exception e) { + // probably not a number + } + if (bearing == null) { + helpDialog(res.getString(R.string.err_point_bear_and_dist_title), res.getString(R.string.err_point_bear_and_dist)); + return null; + } + + double distance; + try { + distance = DistanceParser.parseDistance(distanceText, settings.units); + } catch (NumberFormatException e) { + showToast(res.getString(R.string.err_parse_dist)); + return null; + } + + final Geopoint coordsDst = coords.project(bearing, distance); + + if (coordsDst == null) { + showToast(res.getString(R.string.err_point_location_error)); + return null; + } + + result = coordsDst; + } else if (coords != null) { + result = coords; + } else { + return null; + } + + saveCoords(result); + + return result; + } + + private void saveCoords(final Geopoint coords) { + if (changed && coords != null) { + SharedPreferences.Editor edit = prefs.edit(); + + edit.putFloat("anylatitude", (float) coords.getLatitude()); + edit.putFloat("anylongitude", (float) coords.getLongitude()); + + edit.commit(); + } else { + SharedPreferences.Editor edit = prefs.edit(); + + edit.remove("anylatitude"); + edit.remove("anylongitude"); + + edit.commit(); + } + } +} diff --git a/main/src/cgeo/geocaching/cgeopopup.java b/main/src/cgeo/geocaching/cgeopopup.java new file mode 100644 index 0000000..b5eff42 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeopopup.java @@ -0,0 +1,670 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.res.Configuration; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.View.OnClickListener; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.util.Locale; + +public class cgeopopup extends AbstractActivity { + + private Boolean fromDetail = false; + private LayoutInflater inflater = null; + private String geocode = null; + private cgCache cache = null; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private ProgressDialog storeDialog = null; + private ProgressDialog dropDialog = null; + private TextView cacheDistance = null; + private Handler ratingHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + final Bundle data = msg.getData(); + + setRating(data.getFloat("rating"), data.getInt("votes")); + } catch (Exception e) { + // nothing + } + } + }; + private Handler storeCacheHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (storeDialog != null) { + storeDialog.dismiss(); + } + + finish(); + return; + } catch (Exception e) { + showToast(res.getString(R.string.err_store)); + + Log.e(cgSettings.tag, "cgeopopup.storeCacheHandler: " + e.toString()); + } + + if (storeDialog != null) { + storeDialog.dismiss(); + } + init(); + } + }; + private Handler dropCacheHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (dropDialog != null) { + dropDialog.dismiss(); + } + + finish(); + return; + } catch (Exception e) { + showToast(res.getString(R.string.err_drop)); + + Log.e(cgSettings.tag, "cgeopopup.dropCacheHandler: " + e.toString()); + } + + if (dropDialog != null) { + dropDialog.dismiss(); + } + init(); + } + }; + + public cgeopopup() { + super("c:geo-cache-info"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // set layout + setTheme(R.style.transparent); + setContentView(R.layout.popup); + setTitle(res.getString(R.string.detail)); + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + fromDetail = extras.getBoolean("fromdetail"); + geocode = extras.getString("geocode"); + } + + if (StringUtils.isBlank(geocode)) { + showToast(res.getString(R.string.err_detail_cache_find)); + + finish(); + return; + } + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, 2, 0, res.getString(R.string.cache_menu_compass)).setIcon(android.R.drawable.ic_menu_compass); // compass + + SubMenu subMenu = menu.addSubMenu(1, 0, 0, res.getString(R.string.cache_menu_navigate)).setIcon(android.R.drawable.ic_menu_more); + NavigationAppFactory.addMenuItems(subMenu, this, res); + addVisitMenu(menu, cache); + menu.add(0, 5, 0, res.getString(R.string.cache_menu_around)).setIcon(android.R.drawable.ic_menu_rotate); // caches around + menu.add(0, 7, 0, res.getString(R.string.cache_menu_browser)).setIcon(android.R.drawable.ic_menu_info_details); // browser + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + try { + if (cache != null && cache.coords != null) { + menu.findItem(0).setVisible(true); + menu.findItem(2).setVisible(true); + menu.findItem(5).setVisible(true); + } else { + menu.findItem(0).setVisible(false); + menu.findItem(2).setVisible(false); + menu.findItem(5).setVisible(false); + } + + boolean visitPossible = fromDetail == false && settings.isLogin(); + menu.findItem(MENU_LOG_VISIT).setEnabled(visitPossible); + } catch (Exception e) { + // nothing + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int menuItem = item.getItemId(); + + if (menuItem == 2) { + navigateTo(); + return true; + } else if (menuItem == 5) { + cachesAround(); + return true; + } else if (menuItem == MENU_LOG_VISIT) { + cache.logVisit(this); + finish(); + return true; + } else if (menuItem == 7) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/seek/cache_details.aspx?wp=" + cache.geocode))); + return true; + } + + if (NavigationAppFactory.onMenuItemSelected(item, geo, this, res, cache, null, null, null)) { + return true; + } + + int logType = menuItem - MENU_LOG_VISIT_OFFLINE; + cache.logOffline(this, logType, settings, base); + return true; + } + + private void init() { + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + app.setAction(geocode); + + cache = app.getCacheByGeocode(geocode); + + if (cache == null) { + showToast(res.getString(R.string.err_detail_cache_find)); + + finish(); + return; + } + + try { + RelativeLayout itemLayout; + TextView itemName; + TextView itemValue; + LinearLayout itemStars; + + if (StringUtils.isNotBlank(cache.name)) { + setTitle(cache.name); + } else { + setTitle(geocode.toUpperCase()); + } + + inflater = getLayoutInflater(); + geocode = cache.geocode.toUpperCase(); + + ((ScrollView) findViewById(R.id.details_list_box)).setVisibility(View.VISIBLE); + LinearLayout detailsList = (LinearLayout) findViewById(R.id.details_list); + detailsList.removeAllViews(); + + // actionbar icon + ((TextView) findViewById(R.id.actionbar_title)).setCompoundDrawablesWithIntrinsicBounds((Drawable) getResources().getDrawable(cgBase.getCacheIcon(cache.type)), null, null, null); + + // cache type + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_type)); + if (cgBase.cacheTypesInv.containsKey(cache.type)) { // cache icon + if (cache.size != null) { + itemValue.setText(cgBase.cacheTypesInv.get(cache.type) + + " (" + res.getString(cache.size.stringId) + ")"); + } else { + itemValue.setText(cgBase.cacheTypesInv.get(cache.type)); + } + } else { + if (cache.size != null) { + itemValue.setText(cgBase.cacheTypesInv.get("mystery") + + " (" + res.getString(cache.size.stringId) + ")"); + } else { + itemValue.setText(cgBase.cacheTypesInv.get("mystery")); + } + } + detailsList.addView(itemLayout); + + // gc-code + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_geocode)); + itemValue.setText(cache.geocode.toUpperCase()); + detailsList.addView(itemLayout); + + // cache state + if (cache.archived || cache.disabled || cache.members || cache.found) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_status)); + + StringBuilder state = new StringBuilder(); + if (cache.found) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_found)); + } + if (cache.archived) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_archived)); + } + if (cache.disabled) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_disabled)); + } + if (cache.members) { + if (state.length() > 0) { + state.append(", "); + } + state.append(res.getString(R.string.cache_status_premium)); + } + + itemValue.setText(state.toString()); + detailsList.addView(itemLayout); + + state = null; + } + + // distance + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.cache_distance)); + itemValue.setText("--"); + detailsList.addView(itemLayout); + cacheDistance = itemValue; + + // difficulty + if (cache.difficulty > 0f) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_layout, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + itemStars = (LinearLayout) itemLayout.findViewById(R.id.stars); + + itemName.setText(res.getString(R.string.cache_difficulty)); + itemValue.setText(String.format(Locale.getDefault(), "%.1f", cache.difficulty) + " of 5"); + for (int i = 0; i <= 4; i++) { + ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + if ((cache.difficulty - i) >= 1.0) { + star.setImageResource(R.drawable.star_on); + } else if ((cache.difficulty - i) > 0.0) { + star.setImageResource(R.drawable.star_half); + } else { + star.setImageResource(R.drawable.star_off); + } + itemStars.addView(star); + } + detailsList.addView(itemLayout); + } + + // terrain + if (cache.terrain > 0f) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_layout, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + itemStars = (LinearLayout) itemLayout.findViewById(R.id.stars); + + itemName.setText(res.getString(R.string.cache_terrain)); + itemValue.setText(String.format(Locale.getDefault(), "%.1f", cache.terrain) + " of 5"); + for (int i = 0; i <= 4; i++) { + ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + if ((cache.terrain - i) >= 1.0) { + star.setImageResource(R.drawable.star_on); + } else if ((cache.terrain - i) > 0.0) { + star.setImageResource(R.drawable.star_half); + } else { + star.setImageResource(R.drawable.star_off); + } + itemStars.addView(star); + } + detailsList.addView(itemLayout); + } + + // rating + if (cache.rating != null && cache.rating > 0) { + setRating(cache.rating, cache.votes); + } else { + (new Thread() { + + public void run() { + cgRating rating = base.getRating(cache.guid, geocode); + + Message msg = new Message(); + Bundle bundle = new Bundle(); + + if (rating == null || rating.rating == null) { + return; + } + + bundle.putFloat("rating", rating.rating); + bundle.putInt("votes", rating.votes); + msg.setData(bundle); + + ratingHandler.sendMessage(msg); + } + }).start(); + } + + // more details + if (fromDetail == false) { + ((LinearLayout) findViewById(R.id.more_details_box)).setVisibility(View.VISIBLE); + + Button buttonMore = (Button) findViewById(R.id.more_details); + buttonMore.setOnClickListener(new OnClickListener() { + + public void onClick(View arg0) { + Intent cachesIntent = new Intent(cgeopopup.this, cgeodetail.class); + cachesIntent.putExtra("geocode", geocode.toUpperCase()); + startActivity(cachesIntent); + + finish(); + return; + } + }); + } else { + ((LinearLayout) findViewById(R.id.more_details_box)).setVisibility(View.GONE); + } + + if (fromDetail == false) { + ((LinearLayout) findViewById(R.id.offline_box)).setVisibility(View.VISIBLE); + + // offline use + final TextView offlineText = (TextView) findViewById(R.id.offline_text); + final Button offlineRefresh = (Button) findViewById(R.id.offline_refresh); + final Button offlineStore = (Button) findViewById(R.id.offline_store); + + if (cache.reason > 0) { + Long diff = (System.currentTimeMillis() / (60 * 1000)) - (cache.detailedUpdate / (60 * 1000)); // minutes + + String ago = ""; + if (diff < 15) { + ago = res.getString(R.string.cache_offline_time_mins_few); + } else if (diff < 50) { + ago = res.getString(R.string.cache_offline_time_about) + " " + diff + " " + res.getString(R.string.cache_offline_time_mins); + } else if (diff < 90) { + ago = res.getString(R.string.cache_offline_time_about) + " " + res.getString(R.string.cache_offline_time_hour); + } else if (diff < (48 * 60)) { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / 60) + " " + res.getString(R.string.cache_offline_time_hours); + } else { + ago = res.getString(R.string.cache_offline_time_about) + " " + (diff / (24 * 60)) + " " + res.getString(R.string.cache_offline_time_days); + } + + offlineText.setText(res.getString(R.string.cache_offline_stored) + "\n" + ago); + + offlineRefresh.setVisibility(View.VISIBLE); + offlineRefresh.setEnabled(true); + offlineRefresh.setOnClickListener(new storeCache()); + + offlineStore.setText(res.getString(R.string.cache_offline_drop)); + offlineStore.setEnabled(true); + offlineStore.setOnClickListener(new dropCache()); + } else { + offlineText.setText(res.getString(R.string.cache_offline_not_ready)); + + offlineRefresh.setVisibility(View.GONE); + offlineRefresh.setEnabled(false); + offlineRefresh.setOnTouchListener(null); + offlineRefresh.setOnClickListener(null); + + offlineStore.setText(res.getString(R.string.cache_offline_store)); + offlineStore.setEnabled(true); + offlineStore.setOnClickListener(new storeCache()); + } + } else { + ((LinearLayout) findViewById(R.id.offline_box)).setVisibility(View.GONE); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeopopup.init: " + e.toString()); + } + + if (geo != null) { + geoUpdate.updateLoc(geo); + } + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + init(); + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + if (geo.coordsNow != null && cache != null && cache.coords != null) { + cacheDistance.setText(base.getHumanDistance(geo.coordsNow.distanceTo(cache.coords))); + cacheDistance.bringToFront(); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private void navigateTo() { + if (cache == null || cache.coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + } + + cgeonavigate navigateActivity = new cgeonavigate(); + + Intent navigateIntent = new Intent(this, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", cache.coords.getLatitude()); + navigateIntent.putExtra("longitude", cache.coords.getLongitude()); + navigateIntent.putExtra("geocode", ""); + navigateIntent.putExtra("name", "Some destination"); + + startActivity(navigateIntent); + } + + private void cachesAround() { + if (cache == null || cache.coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + } + + cgeocaches.startActivityCachesAround(this, cache.coords); + + finish(); + } + + private class storeCache implements View.OnClickListener { + + public void onClick(View arg0) { + if (dropDialog != null && dropDialog.isShowing()) { + showToast("Still removing this cache."); + return; + } + + storeDialog = ProgressDialog.show(cgeopopup.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true); + storeDialog.setCancelable(false); + Thread thread = new storeCacheThread(storeCacheHandler); + thread.start(); + } + } + + private class storeCacheThread extends Thread { + + private Handler handler = null; + + public storeCacheThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + base.storeCache(app, cgeopopup.this, cache, null, 1, handler); + } + } + + private class dropCache implements View.OnClickListener { + + public void onClick(View arg0) { + if (storeDialog != null && storeDialog.isShowing()) { + showToast("Still saving this cache."); + return; + } + + dropDialog = ProgressDialog.show(cgeopopup.this, res.getString(R.string.cache_dialog_offline_drop_title), res.getString(R.string.cache_dialog_offline_drop_message), true); + dropDialog.setCancelable(false); + Thread thread = new dropCacheThread(dropCacheHandler); + thread.start(); + } + } + + private class dropCacheThread extends Thread { + + private Handler handler = null; + + public dropCacheThread(Handler handlerIn) { + handler = handlerIn; + } + + @Override + public void run() { + cgBase.dropCache(app, cgeopopup.this, cache, handler); + } + } + + private void setRating(Float rating, Integer votes) { + if (rating == null || rating <= 0) { + return; + } + + RelativeLayout itemLayout; + TextView itemName; + TextView itemValue; + LinearLayout itemStars; + LinearLayout detailsList = (LinearLayout) findViewById(R.id.details_list); + + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_layout, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + itemStars = (LinearLayout) itemLayout.findViewById(R.id.stars); + + itemName.setText(res.getString(R.string.cache_rating)); + itemValue.setText(String.format(Locale.getDefault(), "%.1f", rating) + " of 5"); + for (int i = 0; i <= 4; i++) { + ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + if ((rating - i) >= 1.0) { + star.setImageResource(R.drawable.star_on); + } else if ((rating - i) > 0.0) { + star.setImageResource(R.drawable.star_half); + } else { + star.setImageResource(R.drawable.star_off); + } + itemStars.addView(star, (1 + i)); + } + if (votes != null) { + final TextView itemAddition = (TextView) itemLayout.findViewById(R.id.addition); + itemAddition.setText("(" + votes + ")"); + itemAddition.setVisibility(View.VISIBLE); + } + detailsList.addView(itemLayout); + } + + public void goCompass(View view) { + if (cache == null || cache.coords == null) { + showToast(res.getString(R.string.cache_coordinates_no)); + + return; + } + + cgeonavigate navigateActivity = new cgeonavigate(); + + Intent navigateIntent = new Intent(cgeopopup.this, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", cache.coords.getLatitude()); + navigateIntent.putExtra("longitude", cache.coords.getLongitude()); + navigateIntent.putExtra("geocode", cache.geocode.toUpperCase()); + navigateIntent.putExtra("name", cache.name); + + startActivity(navigateIntent); + + finish(); + } + + public void goManual(View view) { + super.goManual(view); + finish(); + } +} diff --git a/main/src/cgeo/geocaching/cgeosmaps.java b/main/src/cgeo/geocaching/cgeosmaps.java new file mode 100644 index 0000000..96c9e0d --- /dev/null +++ b/main/src/cgeo/geocaching/cgeosmaps.java @@ -0,0 +1,147 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.utils.CollectionUtils; + +import android.app.ProgressDialog; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.LayoutInflater; +import android.widget.ImageView; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +public class cgeosmaps extends AbstractActivity { + + private List<Bitmap> maps = new ArrayList<Bitmap>(); + private String geocode = null; + private LayoutInflater inflater = null; + private ProgressDialog waitDialog = null; + private LinearLayout smapsView = null; + private BitmapFactory factory = null; + private Handler loadMapsHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (CollectionUtils.isEmpty(maps)) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + showToast(res.getString(R.string.err_detail_not_load_map_static)); + + finish(); + return; + } else { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + if (inflater == null) { + inflater = getLayoutInflater(); + } + + if (smapsView == null) { + smapsView = (LinearLayout) findViewById(R.id.maps_list); + } + smapsView.removeAllViews(); + + for (Bitmap image : maps) { + if (image != null) { + final ImageView map = (ImageView) inflater.inflate(R.layout.map_static_item, null); + map.setImageBitmap(image); + smapsView.addView(map); + } + } + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + Log.e(cgSettings.tag, "cgeosmaps.loadMapsHandler: " + e.toString()); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.map_static); + setTitle(res.getString(R.string.map_static_title)); + + // get parameters + Bundle extras = getIntent().getExtras(); + + // try to get data from extras + if (extras != null) { + geocode = extras.getString("geocode"); + } + + if (geocode == null) { + showToast("Sorry, c:geo forgot for what cache you want to load static maps."); + finish(); + return; + } + + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.map_static_loading), true); + waitDialog.setCancelable(true); + + (new loadMaps()).start(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + private class loadMaps extends Thread { + + @Override + public void run() { + try { + if (factory == null) { + factory = new BitmapFactory(); + } + + for (int level = 1; level <= 5; level++) { + try { + Bitmap image = BitmapFactory.decodeFile(cgSettings.getStorage() + geocode + "/map_" + level); + if (image != null) { + maps.add(image); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeosmaps.loadMaps.run.1: " + e.toString()); + } + } + + if (maps.isEmpty()) { + for (int level = 1; level <= 5; level++) { + try { + Bitmap image = BitmapFactory.decodeFile(cgSettings.getStorageSec() + geocode + "/map_" + level); + if (image != null) { + maps.add(image); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeosmaps.loadMaps.run.2: " + e.toString()); + } + } + } + + loadMapsHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeosmaps.loadMaps.run: " + e.toString()); + } + } + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgeotouch.java b/main/src/cgeo/geocaching/cgeotouch.java new file mode 100644 index 0000000..5943ef2 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeotouch.java @@ -0,0 +1,458 @@ +package cgeo.geocaching; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class cgeotouch extends cgLogForm { + private cgTrackable trackable = null; + private List<Integer> types = new ArrayList<Integer>(); + private ProgressDialog waitDialog = null; + private String guid = null; + private String geocode = null; + private String[] viewstates = null; + private Boolean gettingViewstate = true; + private Calendar date = Calendar.getInstance(); + private int typeSelected = -1; + private int attempts = 0; + private CheckBox tweetCheck = null; + private LinearLayout tweetBox = null; + + private Handler showProgressHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + showProgress(true); + } + }; + + private Handler loadDataHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (cgBase.isEmpty(viewstates) && attempts < 2) { + showToast(res.getString(R.string.err_log_load_data_again)); + + loadData thread; + thread = new loadData(guid); + thread.start(); + + return; + } else if (cgBase.isEmpty(viewstates) && attempts >= 2) { + showToast(res.getString(R.string.err_log_load_data)); + showProgress(false); + + return; + } + + gettingViewstate = false; // we're done, user can post log + + Button buttonPost = (Button) findViewById(R.id.post); + buttonPost.setEnabled(true); + buttonPost.setOnClickListener(new postListener()); + + showProgress(false); + } + }; + + private Handler postLogHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (msg.what == 1) { + showToast(res.getString(R.string.info_log_posted)); + + if (waitDialog != null) { + waitDialog.dismiss(); + } + finish(); + return; + } else if (msg.what >= 1000) { + if (msg.what == 1001) { + showToast(res.getString(R.string.warn_log_text_fill)); + } else if (msg.what == 1002) { + showToast(res.getString(R.string.err_log_failed_server)); + } else { + showToast(res.getString(R.string.err_log_post_failed)); + } + } else { + if (cgBase.errorRetrieve.get(msg.what) != null) { + showToast(res.getString(R.string.err_log_post_failed_because) + cgBase.errorRetrieve.get(msg.what) + "."); + } else { + showToast(res.getString(R.string.err_log_post_failed)); + } + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } + }; + + public cgeotouch() { + super("c:geo-log-trackable"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.touch); + setTitle(res.getString(R.string.trackable_touch)); + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + geocode = extras.getString("geocode"); + guid = extras.getString("guid"); + } + + trackable = app.getTrackableByGeocode("logging trackable"); + + if (StringUtils.isNotBlank(trackable.name)) { + setTitle(res.getString(R.string.trackable_touch) + trackable.name); + } else { + setTitle(res.getString(R.string.trackable_touch) + trackable.geocode.toUpperCase()); + } + + app.setAction("logging trackable"); + + if (trackable == null || guid == null) { + showToast(res.getString(R.string.err_tb_forgot_saw)); + + finish(); + return; + } + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + SubMenu subMenu = menu.addSubMenu(0, 0, 0, res.getString(R.string.log_add)).setIcon(android.R.drawable.ic_menu_add); + + subMenu.add(0, 0x6, 0, res.getString(R.string.log_date_time)); + subMenu.add(0, 0x4, 0, res.getString(R.string.log_date)); + subMenu.add(0, 0x2, 0, res.getString(R.string.log_time)); + subMenu.add(0, 0x1, 0, res.getString(R.string.init_signature)); + subMenu.add(0, 0x7, 0, res.getString(R.string.log_date_time) + " & " + res.getString(R.string.init_signature)); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + if (settings.getSignature() == null) { + menu.findItem(0x1).setVisible(false); + menu.findItem(0x7).setVisible(false); + } else { + menu.findItem(0x1).setVisible(true); + menu.findItem(0x7).setVisible(true); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + EditText text = null; + String textContent = null; + String dateString = null; + String timeString = null; + String addText = ""; + + if ((id >= 0x1 && id <= 0x7)) { + text = (EditText) findViewById(R.id.log); + textContent = text.getText().toString(); + + final long now = System.currentTimeMillis(); + dateString = base.formatDate(now); + timeString = base.formatTime(now); + + if ((id & 0x4) == 0x4) { + addText += dateString; + if ((id & 0x2) == 0x2) { + addText += " | "; + } + } + if ((id & 0x2) == 0x2) { + addText += timeString; + } + if ((id & 0x1) == 0x1 && settings.getSignature() != null) { + if (addText.length() > 0) { + addText += "\n"; + } + addText += settings.getSignature() + .replaceAll("\\[DATE\\]", dateString) + .replaceAll("\\[TIME\\]", timeString) + .replaceAll("\\[USER\\]", settings.getUsername()) + .replaceAll("\\[NUMBER\\]", ""); + } + if (textContent.length() > 0 && addText.length() > 0) { + addText = "\n" + addText; + } + text.setText(textContent + addText, TextView.BufferType.NORMAL); + text.setSelection(text.getText().toString().length()); + return true; + } + + return false; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + super.onCreateContextMenu(menu, view, info); + final int viewId = view.getId(); + + if (viewId == R.id.type) { + for (final int typeOne : types) + menu.add(viewId, typeOne, 0, cgBase.logTypes2.get(typeOne)); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int group = item.getGroupId(); + final int id = item.getItemId(); + + if (group == R.id.type) { + setType(id); + + return true; + } + + return false; + } + + public void init() { + if (geocode != null) + app.setAction("logging trackable"); + + types.clear(); + types.add(cgBase.LOG_RETRIEVED_IT); + types.add(cgBase.LOG_GRABBED_IT); + types.add(cgBase.LOG_NOTE); + types.add(cgBase.LOG_DISCOVERED_IT); + + if (typeSelected < 0 && cgBase.logTypes2.get(typeSelected) == null) + typeSelected = types.get(2); + setType(typeSelected); + + Button typeButton = (Button) findViewById(R.id.type); + registerForContextMenu(typeButton); + typeButton.setText(cgBase.logTypes2.get(typeSelected)); + typeButton.setOnClickListener(new View.OnClickListener() { + public void onClick(View view) { + openContextMenu(view); + } + }); + + Button dateButton = (Button) findViewById(R.id.date); + dateButton.setText(base.formatShortDate(date.getTime().getTime())); + dateButton.setOnClickListener(new cgeotouchDateListener()); + + if (tweetBox == null) + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + if (tweetCheck == null) + tweetCheck = (CheckBox) findViewById(R.id.tweet); + tweetCheck.setChecked(true); + + Button buttonPost = (Button) findViewById(R.id.post); + if (cgBase.isEmpty(viewstates)) { + buttonPost.setEnabled(false); + buttonPost.setOnTouchListener(null); + buttonPost.setOnClickListener(null); + + loadData thread; + thread = new loadData(guid); + thread.start(); + } else { + buttonPost.setEnabled(true); + buttonPost.setOnClickListener(new postListener()); + } + } + + public void setDate(Calendar dateIn) { + date = dateIn; + + final Button dateButton = (Button) findViewById(R.id.date); + dateButton.setText(base.formatShortDate(date.getTime().getTime())); + } + + public void setType(int type) { + final Button typeButton = (Button) findViewById(R.id.type); + + if (cgBase.logTypes2.get(type) != null) + typeSelected = type; + if (cgBase.logTypes2.get(typeSelected) == null) + typeSelected = 0; + typeButton.setText(cgBase.logTypes2.get(typeSelected)); + + if (tweetBox == null) + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + if (settings.twitter == 1) + tweetBox.setVisibility(View.VISIBLE); + else + tweetBox.setVisibility(View.GONE); + } + + private class cgeotouchDateListener implements View.OnClickListener { + public void onClick(View arg0) { + Dialog dateDialog = new cgeodate(cgeotouch.this, cgeotouch.this, date); + dateDialog.setCancelable(true); + dateDialog.show(); + } + } + + private class postListener implements View.OnClickListener { + public void onClick(View arg0) { + if (gettingViewstate == false) { + waitDialog = ProgressDialog.show(cgeotouch.this, null, res.getString(R.string.log_saving), true); + waitDialog.setCancelable(true); + + String tracking = ((EditText) findViewById(R.id.tracking)).getText().toString(); + String log = ((EditText) findViewById(R.id.log)).getText().toString(); + Thread thread = new postLog(postLogHandler, tracking, log); + thread.start(); + } else { + showToast(res.getString(R.string.err_log_load_data_still)); + } + } + } + + private class loadData extends Thread { + private String guid = null; + + public loadData(String guidIn) { + guid = guidIn; + + if (guid == null) { + showToast(res.getString(R.string.err_tb_forgot_saw)); + + finish(); + return; + } + } + + @Override + public void run() { + final Map<String, String> params = new HashMap<String, String>(); + + showProgressHandler.sendEmptyMessage(0); + gettingViewstate = true; + attempts++; + + try { + if (StringUtils.isNotBlank(guid)) { + params.put("wid", guid); + } else { + loadDataHandler.sendEmptyMessage(0); + return; + } + + final String page = base.request(false, "www.geocaching.com", "/track/log.aspx", "GET", params, false, false, false).getData(); + + viewstates = cgBase.getViewstates(page); + + final List<Integer> typesPre = cgBase.parseTypes(page); + if (typesPre.size() > 0) { + types.clear(); + types.addAll(typesPre); + } + typesPre.clear(); + + if (types.contains(typeSelected) == false) { + typeSelected = types.get(0); + setType(typeSelected); + showToast(res.getString(R.string.info_log_type_changed)); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeotouch.loadData.run: " + e.toString()); + } + + loadDataHandler.sendEmptyMessage(0); + } + } + + private class postLog extends Thread { + Handler handler = null; + String tracking = null; + String log = null; + + public postLog(Handler handlerIn, String trackingIn, String logIn) { + handler = handlerIn; + tracking = trackingIn; + log = logIn; + } + + @Override + public void run() { + int ret = -1; + + ret = postLogFn(tracking, log); + + handler.sendEmptyMessage(ret); + } + } + + public int postLogFn(String tracking, String log) { + int status = -1; + + try { + if (tweetBox == null) + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + if (tweetCheck == null) + tweetCheck = (CheckBox) findViewById(R.id.tweet); + + status = base.postLogTrackable(guid, tracking, viewstates, typeSelected, date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), log); + + if (status == 1 && settings.twitter == 1 && + StringUtils.isNotBlank(settings.tokenPublic) && StringUtils.isNotBlank(settings.tokenSecret) && + tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { + cgBase.postTweetTrackable(app, settings, geocode); + } + + return status; + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeotouch.postLogFn: " + e.toString()); + } + + return 1000; + } +} diff --git a/main/src/cgeo/geocaching/cgeotrackable.java b/main/src/cgeo/geocaching/cgeotrackable.java new file mode 100644 index 0000000..b1e6293 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeotrackable.java @@ -0,0 +1,629 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.graphics.drawable.BitmapDrawable; +import android.graphics.drawable.Drawable; +import android.net.Uri; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.text.method.LinkMovementMethod; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.ImageView; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.ScrollView; +import android.widget.TextView; + +import java.net.URLEncoder; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; + +public class cgeotrackable extends AbstractActivity { + public cgTrackable trackable = null; + public String geocode = null; + public String name = null; + public String guid = null; + public String id = null; + private String contextMenuUser = null; + private LayoutInflater inflater = null; + private ProgressDialog waitDialog = null; + private Handler loadTrackableHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + RelativeLayout itemLayout; + TextView itemName; + TextView itemValue; + + if (trackable != null && trackable.errorRetrieve != 0) { + showToast(res.getString(R.string.err_tb_details_download) + " " + cgBase.errorRetrieve.get(trackable.errorRetrieve) + "."); + + finish(); + return; + } + + if (trackable != null && StringUtils.isNotBlank(trackable.error)) { + showToast(res.getString(R.string.err_tb_details_download) + " " + trackable.error + "."); + + finish(); + return; + } + + if (trackable == null) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + if (StringUtils.isNotBlank(geocode)) { + showToast(res.getString(R.string.err_tb_find) + " " + geocode + "."); + } else { + showToast(res.getString(R.string.err_tb_find_that)); + } + + finish(); + return; + } + + try { + inflater = getLayoutInflater(); + geocode = trackable.geocode.toUpperCase(); + + if (StringUtils.isNotBlank(trackable.name)) { + setTitle(Html.fromHtml(trackable.name).toString()); + } else { + setTitle(trackable.name.toUpperCase()); + } + + ((ScrollView) findViewById(R.id.details_list_box)).setVisibility(View.VISIBLE); + LinearLayout detailsList = (LinearLayout) findViewById(R.id.details_list); + + // actiobar icon + if (StringUtils.isNotBlank(trackable.iconUrl)) { + final tbIconHandler iconHandler = new tbIconHandler(((TextView) findViewById(R.id.actionbar_title))); + final tbIconThread iconThread = new tbIconThread(trackable.iconUrl, iconHandler); + iconThread.start(); + } + + // trackable name + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_name)); + if (StringUtils.isNotBlank(trackable.name)) { + itemValue.setText(Html.fromHtml(trackable.name).toString()); + } else { + itemValue.setText(res.getString(R.string.trackable_unknown)); + } + detailsList.addView(itemLayout); + + // trackable type + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + String tbType = null; + if (StringUtils.isNotBlank(trackable.type)) { + tbType = Html.fromHtml(trackable.type).toString(); + } else { + tbType = res.getString(R.string.trackable_unknown); + } + itemName.setText(res.getString(R.string.trackable_type)); + itemValue.setText(tbType); + detailsList.addView(itemLayout); + + // trackable geocode + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_code)); + itemValue.setText(trackable.geocode.toUpperCase()); + detailsList.addView(itemLayout); + + // trackable owner + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_owner)); + if (StringUtils.isNotBlank(trackable.owner)) { + itemValue.setText(Html.fromHtml(trackable.owner), TextView.BufferType.SPANNABLE); + itemLayout.setOnClickListener(new userActions()); + } else { + itemValue.setText(res.getString(R.string.trackable_unknown)); + } + detailsList.addView(itemLayout); + + // trackable spotted + if (StringUtils.isNotBlank(trackable.spottedName) || + trackable.spottedType == cgTrackable.SPOTTED_UNKNOWN || + trackable.spottedType == cgTrackable.SPOTTED_OWNER + ) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_spotted)); + String text = null; + + if (trackable.spottedType == cgTrackable.SPOTTED_CACHE) { + text = res.getString(R.string.trackable_spotted_in_cache) + " " + Html.fromHtml(trackable.spottedName).toString(); + } else if (trackable.spottedType == cgTrackable.SPOTTED_USER) { + text = res.getString(R.string.trackable_spotted_at_user) + " " + Html.fromHtml(trackable.spottedName).toString(); + } else if (trackable.spottedType == cgTrackable.SPOTTED_UNKNOWN) { + text = res.getString(R.string.trackable_spotted_unknown_location); + } else if (trackable.spottedType == cgTrackable.SPOTTED_OWNER) { + text = res.getString(R.string.trackable_spotted_owner); + } else { + text = "N/A"; + } + + itemValue.setText(text); + itemLayout.setClickable(true); + if (cgTrackable.SPOTTED_CACHE == trackable.spottedType) { + itemLayout.setOnClickListener(new View.OnClickListener() { + public void onClick(View arg0) { + Intent cacheIntent = new Intent(cgeotrackable.this, cgeodetail.class); + cacheIntent.putExtra("guid", (String) trackable.spottedGuid); + cacheIntent.putExtra("name", (String) trackable.spottedName); + startActivity(cacheIntent); + } + }); + } else if (cgTrackable.SPOTTED_USER == trackable.spottedType) { + itemLayout.setOnClickListener(new userActions()); + //activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?guid=" + trackable.spottedGuid))); + } + + detailsList.addView(itemLayout); + } + + // trackable origin + if (StringUtils.isNotBlank(trackable.origin)) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_origin)); + itemValue.setText(Html.fromHtml(trackable.origin), TextView.BufferType.SPANNABLE); + detailsList.addView(itemLayout); + } + + // trackable released + if (trackable.released != null) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_released)); + itemValue.setText(base.formatDate(trackable.released.getTime())); + detailsList.addView(itemLayout); + } + + // trackable distance + if (trackable.distance != null) { + itemLayout = (RelativeLayout) inflater.inflate(R.layout.cache_item, null); + itemName = (TextView) itemLayout.findViewById(R.id.name); + itemValue = (TextView) itemLayout.findViewById(R.id.value); + + itemName.setText(res.getString(R.string.trackable_distance)); + itemValue.setText(base.getHumanDistance(trackable.distance)); + detailsList.addView(itemLayout); + } + + // trackable goal + if (StringUtils.isNotBlank(trackable.goal)) { + ((LinearLayout) findViewById(R.id.goal_box)).setVisibility(View.VISIBLE); + TextView descView = (TextView) findViewById(R.id.goal); + descView.setVisibility(View.VISIBLE); + descView.setText(Html.fromHtml(trackable.goal, new cgHtmlImg(cgeotrackable.this, geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); + descView.setMovementMethod(LinkMovementMethod.getInstance()); + } + + // trackable details + if (StringUtils.isNotBlank(trackable.details)) { + ((LinearLayout) findViewById(R.id.details_box)).setVisibility(View.VISIBLE); + TextView descView = (TextView) findViewById(R.id.details); + descView.setVisibility(View.VISIBLE); + descView.setText(Html.fromHtml(trackable.details, new cgHtmlImg(cgeotrackable.this, geocode, true, 0, false), null), TextView.BufferType.SPANNABLE); + descView.setMovementMethod(LinkMovementMethod.getInstance()); + } + + // trackable image + if (StringUtils.isNotBlank(trackable.image)) { + ((LinearLayout) findViewById(R.id.image_box)).setVisibility(View.VISIBLE); + LinearLayout imgView = (LinearLayout) findViewById(R.id.image); + + final ImageView trackableImage = (ImageView) inflater.inflate(R.layout.trackable_image, null); + + trackableImage.setImageResource(R.drawable.image_not_loaded); + trackableImage.setClickable(true); + trackableImage.setOnClickListener(new View.OnClickListener() { + + public void onClick(View arg0) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(trackable.image))); + } + }); + + // try to load image + final Handler handler = new Handler() { + + @Override + public void handleMessage(Message message) { + BitmapDrawable image = (BitmapDrawable) message.obj; + if (image != null) { + trackableImage.setImageDrawable((BitmapDrawable) message.obj); + } + } + }; + + new Thread() { + + @Override + public void run() { + BitmapDrawable image = null; + try { + cgHtmlImg imgGetter = new cgHtmlImg(cgeotrackable.this, geocode, true, 0, false); + + image = imgGetter.getDrawable(trackable.image); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeospoilers.onCreate.onClick.run: " + e.toString()); + } + } + }.start(); + + imgView.addView(trackableImage); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeotrackable.loadTrackableHandler: " + e.toString() + Arrays.toString(e.getStackTrace())); + } + + displayLogs(); + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } + }; + + public cgeotrackable() { + super("c:geo-trackable-details"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.trackable_detail); + setTitle(res.getString(R.string.trackable)); + + // get parameters + Bundle extras = getIntent().getExtras(); + Uri uri = getIntent().getData(); + + // try to get data from extras + if (extras != null) { + geocode = extras.getString("geocode"); + name = extras.getString("name"); + guid = extras.getString("guid"); + id = extras.getString("id"); + } + + // try to get data from URI + if (geocode == null && guid == null && id == null && uri != null) { + String uriHost = uri.getHost().toLowerCase(); + if (uriHost.contains("geocaching.com")) { + geocode = uri.getQueryParameter("tracker"); + guid = uri.getQueryParameter("guid"); + id = uri.getQueryParameter("id"); + + if (StringUtils.isNotBlank(geocode)) { + geocode = geocode.toUpperCase(); + guid = null; + id = null; + } else if (StringUtils.isNotBlank(guid)) { + geocode = null; + guid = guid.toLowerCase(); + id = null; + } else if (StringUtils.isNotBlank(id)) { + geocode = null; + guid = null; + id = id.toLowerCase(); + } else { + showToast(res.getString(R.string.err_tb_details_open)); + finish(); + return; + } + } else if (uriHost.contains("coord.info")) { + String uriPath = uri.getPath().toLowerCase(); + if (uriPath != null && uriPath.startsWith("/tb")) { + geocode = uriPath.substring(1).toUpperCase(); + guid = null; + id = null; + } else { + showToast(res.getString(R.string.err_tb_details_open)); + finish(); + return; + } + } + } + + // no given data + if (geocode == null && guid == null && id == null) { + showToast(res.getString(R.string.err_tb_display)); + finish(); + return; + } + + if (StringUtils.isNotBlank(name)) { + waitDialog = ProgressDialog.show(this, Html.fromHtml(name).toString(), res.getString(R.string.trackable_details_loading), true); + } else if (StringUtils.isNotBlank(geocode)) { + waitDialog = ProgressDialog.show(this, geocode.toUpperCase(), res.getString(R.string.trackable_details_loading), true); + } else { + waitDialog = ProgressDialog.show(this, res.getString(R.string.trackable), res.getString(R.string.trackable_details_loading), true); + } + waitDialog.setCancelable(true); + + loadTrackable thread; + thread = new loadTrackable(loadTrackableHandler, geocode, guid, id); + thread.start(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + super.onCreateContextMenu(menu, view, info); + final int viewId = view.getId(); + + if (viewId == R.id.author) { // Log item author + contextMenuUser = ((TextView) view).getText().toString(); + } else { // Trackable owner, and user holding trackable now + RelativeLayout itemLayout = (RelativeLayout) view; + TextView itemName = (TextView) itemLayout.findViewById(R.id.name); + + String selectedName = itemName.getText().toString(); + if (selectedName.equals(res.getString(R.string.trackable_owner))) { + contextMenuUser = trackable.owner; + } else if (selectedName.equals(res.getString(R.string.trackable_spotted))) { + contextMenuUser = trackable.spottedName; + } + } + + menu.setHeaderTitle(res.getString(R.string.user_menu_title) + " " + contextMenuUser); + menu.add(viewId, 1, 0, res.getString(R.string.user_menu_view_hidden)); + menu.add(viewId, 2, 0, res.getString(R.string.user_menu_view_found)); + menu.add(viewId, 3, 0, res.getString(R.string.user_menu_open_browser)); + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int id = item.getItemId(); + + if (id == 1) { + cgeocaches.startActivityCacheOwner(this, contextMenuUser); + return true; + } else if (id == 2) { + cgeocaches.startActivityCacheUser(this, contextMenuUser); + return true; + } else if (id == 3) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/profile/?u=" + URLEncoder.encode(contextMenuUser)))); + + return true; + } + return false; + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, 1, 0, res.getString(R.string.trackable_log_touch)).setIcon(android.R.drawable.ic_menu_agenda); // log touch + + menu.add(0, 2, 0, res.getString(R.string.trackable_browser_open)).setIcon(android.R.drawable.ic_menu_info_details); // browser + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case 1: + logTouch(); + return true; + case 2: + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/track/details.aspx?tracker=" + trackable.geocode))); + return true; + } + + return false; + } + + private class loadTrackable extends Thread { + + private Handler handler = null; + private String geocode = null; + private String guid = null; + private String id = null; + + public loadTrackable(Handler handlerIn, String geocodeIn, String guidIn, String idIn) { + handler = handlerIn; + geocode = geocodeIn; + guid = guidIn; + id = idIn; + + if (geocode == null && guid == null && id == null) { + showToast(res.getString(R.string.err_tb_forgot)); + + stop(); + finish(); + return; + } + } + + @Override + public void run() { + loadTrackableFn(geocode, guid, id); + handler.sendMessage(new Message()); + } + } + + public void loadTrackableFn(String geocode, String guid, String id) { + Map<String, String> params = new HashMap<String, String>(); + if (StringUtils.isNotBlank(geocode)) { + params.put("geocode", geocode); + } else if (StringUtils.isNotBlank(guid)) { + params.put("guid", guid); + } else if (StringUtils.isNotBlank(id)) { + params.put("id", id); + } else { + return; + } + + trackable = base.searchTrackable(params); + } + + private void displayLogs() { + // trackable logs + LinearLayout listView = (LinearLayout) findViewById(R.id.log_list); + listView.removeAllViews(); + + RelativeLayout rowView; + + if (trackable != null && trackable.logs != null) { + for (cgLog log : trackable.logs) { + rowView = (RelativeLayout) inflater.inflate(R.layout.trackable_logitem, null); + + if (log.date > 0) { + ((TextView) rowView.findViewById(R.id.added)).setText(base.formatShortDate(log.date)); + } + + if (cgBase.logTypes1.containsKey(log.type)) { + ((TextView) rowView.findViewById(R.id.type)).setText(cgBase.logTypes1.get(log.type)); + } else { + ((TextView) rowView.findViewById(R.id.type)).setText(cgBase.logTypes1.get(4)); // note if type is unknown + } + ((TextView) rowView.findViewById(R.id.author)).setText(Html.fromHtml(log.author), TextView.BufferType.SPANNABLE); + + if (StringUtils.isBlank(log.cacheName)) { + ((TextView) rowView.findViewById(R.id.location)).setVisibility(View.GONE); + } else { + ((TextView) rowView.findViewById(R.id.location)).setText(Html.fromHtml(log.cacheName)); + final String cacheGuid = log.cacheGuid; + final String cacheName = log.cacheName; + ((TextView) rowView.findViewById(R.id.location)).setOnClickListener(new View.OnClickListener() { + public void onClick(View arg0) { + Intent cacheIntent = new Intent(cgeotrackable.this, cgeodetail.class); + cacheIntent.putExtra("guid", (String) cacheGuid); + cacheIntent.putExtra("name", (String) Html.fromHtml(cacheName).toString()); + startActivity(cacheIntent); + } + }); + } + + ((TextView) rowView.findViewById(R.id.log)).setText(Html.fromHtml(log.log, new cgHtmlImg(cgeotrackable.this, null, false, 0, false), null), TextView.BufferType.SPANNABLE); + + ((TextView) rowView.findViewById(R.id.author)).setOnClickListener(new userActions()); + listView.addView(rowView); + } + + if (trackable.logs.size() > 0) { + ((LinearLayout) findViewById(R.id.log_box)).setVisibility(View.VISIBLE); + } + } + } + + private class userActions implements View.OnClickListener { + + public void onClick(View view) { + if (view == null) { + return; + } + + try { + registerForContextMenu(view); + openContextMenu(view); + } catch (Exception e) { + // nothing + } + } + } + + private void logTouch() { + Intent logTouchIntent = new Intent(this, cgeotouch.class); + logTouchIntent.putExtra("geocode", trackable.geocode.toUpperCase()); + logTouchIntent.putExtra("guid", trackable.guid); + startActivity(logTouchIntent); + } + + private class tbIconThread extends Thread { + String url = null; + Handler handler = null; + + public tbIconThread(String urlIn, Handler handlerIn) { + url = urlIn; + handler = handlerIn; + } + + @Override + public void run() { + if (url == null || handler == null) { + return; + } + + BitmapDrawable image = null; + try { + cgHtmlImg imgGetter = new cgHtmlImg(cgeotrackable.this, trackable.geocode, false, 0, false); + + image = imgGetter.getDrawable(url); + Message message = handler.obtainMessage(0, image); + handler.sendMessage(message); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeotrackable.tbIconThread.run: " + e.toString()); + } + } + } + + private static class tbIconHandler extends Handler { + TextView view = null; + + public tbIconHandler(TextView viewIn) { + view = viewIn; + } + + @Override + public void handleMessage(Message message) { + BitmapDrawable image = (BitmapDrawable) message.obj; + if (image != null && view != null) { + view.setCompoundDrawablesWithIntrinsicBounds((Drawable) image, null, null, null); + } + } + } + + public static void startActivity(final AbstractActivity fromContext, + final String guid, final String geocode, final String name) { + Intent trackableIntent = new Intent(fromContext, cgeotrackable.class); + trackableIntent.putExtra("guid", guid); + trackableIntent.putExtra("geocode", geocode); + trackableIntent.putExtra("name", name); + fromContext.startActivity(trackableIntent); + } +} diff --git a/main/src/cgeo/geocaching/cgeotrackables.java b/main/src/cgeo/geocaching/cgeotrackables.java new file mode 100644 index 0000000..ed67782 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeotrackables.java @@ -0,0 +1,152 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; + +import android.app.ProgressDialog; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; + +import java.util.ArrayList; +import java.util.List; + +public class cgeotrackables extends AbstractActivity { + private List<cgTrackable> trackables = new ArrayList<cgTrackable>(); + private String geocode = null; + private LayoutInflater inflater = null; + private LinearLayout addList = null; + private ProgressDialog waitDialog = null; + private Handler loadInventoryHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (inflater == null) { + inflater = getLayoutInflater(); + } + + if (addList == null) { + addList = (LinearLayout) findViewById(R.id.trackable_list); + } + + if (trackables.isEmpty()) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + showToast("Sorry, c:geo failed to load cache inventory."); + + finish(); + return; + } else if (trackables.size() == 1) { + cgTrackable trackable = trackables.get(0); + cgeotrackable.startActivity(cgeotrackables.this, trackable.guid, trackable.geocode, trackable.name); + finish(); + return; + } else { + LinearLayout oneTbPre = null; + for (cgTrackable trackable : trackables) { + oneTbPre = (LinearLayout) inflater.inflate(R.layout.trackable_button, null); + + Button oneTb = (Button) oneTbPre.findViewById(R.id.button); + + if (trackable.name != null) { + oneTb.setText(Html.fromHtml(trackable.name).toString()); + } else { + oneTb.setText("some trackable"); + } + oneTb.setClickable(true); + oneTb.setOnClickListener(new buttonListener(trackable.guid, trackable.geocode, trackable.name)); + addList.addView(oneTbPre); + } + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + Log.e(cgSettings.tag, "cgeotrackables.loadInventoryHandler: " + e.toString()); + } + } + }; + + public cgeotrackables() { + super("c:geo-trackable-list"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.trackables); + setTitle("Trackables"); + + // get parameters + Bundle extras = getIntent().getExtras(); + + // try to get data from extras + if (extras != null) { + geocode = extras.getString("geocode"); + } + + if (geocode == null) { + showToast("Sorry, c:geo forgot for what cache you want to load trackables."); + finish(); + return; + } + + waitDialog = ProgressDialog.show(this, null, "loading cache inventory...", true); + waitDialog.setCancelable(true); + + (new loadInventory()).start(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + private class loadInventory extends Thread { + + @Override + public void run() { + try { + trackables = app.loadInventory(geocode); + + loadInventoryHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeotrackables.loadInventory.run: " + e.toString()); + } + } + } + + private class buttonListener implements View.OnClickListener { + + private String guid = null; + private String geocode = null; + private String name = null; + + public buttonListener(String guidIn, String geocodeIn, String nameIn) { + guid = guidIn; + geocode = geocodeIn; + name = nameIn; + } + + public void onClick(View arg0) { + cgeotrackable.startActivity(cgeotrackables.this, guid, geocode, name); + return; + } + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/cgeovisit.java b/main/src/cgeo/geocaching/cgeovisit.java new file mode 100644 index 0000000..482ea27 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeovisit.java @@ -0,0 +1,822 @@ +package cgeo.geocaching; + +import cgeo.geocaching.LogTemplateProvider.LogTemplate; +import cgeo.geocaching.utils.CollectionUtils; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Dialog; +import android.app.ProgressDialog; +import android.content.Intent; +import android.content.res.Configuration; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.ContextMenu; +import android.view.LayoutInflater; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.widget.Button; +import android.widget.CheckBox; +import android.widget.EditText; +import android.widget.LinearLayout; +import android.widget.TextView; + +import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; + +public class cgeovisit extends cgLogForm { + static final String EXTRAS_FOUND = "found"; + static final String EXTRAS_TEXT = "text"; + static final String EXTRAS_GEOCODE = "geocode"; + static final String EXTRAS_ID = "id"; + + private static final int MENU_SIGNATURE = 1; + private static final int SUBMENU_VOTE = 2; + + private LayoutInflater inflater = null; + private cgCache cache = null; + private List<Integer> types = new ArrayList<Integer>(); + private ProgressDialog waitDialog = null; + private String cacheid = null; + private String geocode = null; + private String text = null; + private boolean alreadyFound = false; + private String[] viewstates = null; + private Boolean gettingViewstate = true; + private List<cgTrackableLog> trackables = null; + private Calendar date = Calendar.getInstance(); + private int typeSelected = 1; + private int attempts = 0; + private boolean progressBar = false; + private Button post = null; + private Button save = null; + private Button clear = null; + private CheckBox tweetCheck = null; + private LinearLayout tweetBox = null; + private double rating = 0.0; + private boolean tbChanged = false; + + // handlers + private Handler showProgressHandler = new Handler() { + @Override + public void handleMessage(Message msg) { + if (progressBar) { + showProgress(true); + } + } + }; + private Handler loadDataHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (types.contains(typeSelected) == false) { + typeSelected = types.get(0); + setType(typeSelected); + + showToast(res.getString(R.string.info_log_type_changed)); + } + + if (cgBase.isEmpty(viewstates) && attempts < 2) { + showToast(res.getString(R.string.err_log_load_data_again)); + + loadData thread; + thread = new loadData(); + thread.start(); + + return; + } else if (cgBase.isEmpty(viewstates) && attempts >= 2) { + showToast(res.getString(R.string.err_log_load_data)); + showProgress(false); + + return; + } + + gettingViewstate = false; // we're done, user can post log + + if (post == null) { + post = (Button) findViewById(R.id.post); + } + post.setEnabled(true); + post.setOnClickListener(new postListener()); + + // add trackables + if (CollectionUtils.isNotEmpty(trackables)) { + if (inflater == null) { + inflater = getLayoutInflater(); + } + + final LinearLayout inventoryView = (LinearLayout) findViewById(R.id.inventory); + inventoryView.removeAllViews(); + + for (cgTrackableLog tb : trackables) { + LinearLayout inventoryItem = (LinearLayout) inflater.inflate(R.layout.visit_trackable, null); + + ((TextView) inventoryItem.findViewById(R.id.trackcode)).setText(tb.trackCode); + ((TextView) inventoryItem.findViewById(R.id.name)).setText(tb.name); + ((TextView) inventoryItem.findViewById(R.id.action)).setText(cgBase.logTypesTrackable.get(settings.trackableAutovisit ? 1 : 0)); + + inventoryItem.setId(tb.id); + final String tbCode = tb.trackCode; + inventoryItem.setClickable(true); + registerForContextMenu(inventoryItem); + inventoryItem.findViewById(R.id.info).setOnClickListener(new View.OnClickListener() { + + public void onClick(View view) { + final Intent trackablesIntent = new Intent(cgeovisit.this, cgeotrackable.class); + trackablesIntent.putExtra(EXTRAS_GEOCODE, tbCode); + startActivity(trackablesIntent); + } + }); + inventoryItem.findViewById(R.id.action).setOnClickListener(new View.OnClickListener() { + + public void onClick(View view) { + openContextMenu(view); + } + }); + + inventoryView.addView(inventoryItem); + + if (settings.trackableAutovisit) + { + tb.action = 1; + tbChanged = true; + } + } + + if (inventoryView.getChildCount() > 0) { + ((LinearLayout) findViewById(R.id.inventory_box)).setVisibility(View.VISIBLE); + } + if (inventoryView.getChildCount() > 1) { + final LinearLayout inventoryChangeAllView = (LinearLayout) findViewById(R.id.inventory_changeall); + + Button changeButton = (Button) inventoryChangeAllView.findViewById(R.id.changebutton); + registerForContextMenu(changeButton); + changeButton.setOnClickListener(new View.OnClickListener() { + + public void onClick(View view) { + openContextMenu(view); + } + }); + + ((LinearLayout) findViewById(R.id.inventory_changeall)).setVisibility(View.VISIBLE); + } + } + + showProgress(false); + } + }; + + private Handler postLogHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (msg.what == 1) { + showToast(res.getString(R.string.info_log_posted)); + + if (waitDialog != null) { + waitDialog.dismiss(); + } + + finish(); + return; + } else if (msg.what == 2) { + showToast(res.getString(R.string.info_log_saved)); + + if (waitDialog != null) { + waitDialog.dismiss(); + } + + finish(); + return; + } else if (msg.what >= 1000) { + if (msg.what == 1001) { + showToast(res.getString(R.string.warn_log_text_fill)); + } else if (msg.what == 1002) { + showToast(res.getString(R.string.err_log_failed_server)); + } else { + showToast(res.getString(R.string.err_log_post_failed)); + } + } else { + if (cgBase.errorRetrieve.get(msg.what) != null) { + showToast(res.getString(R.string.err_log_post_failed_because) + " " + cgBase.errorRetrieve.get(msg.what) + "."); + } else { + showToast(res.getString(R.string.err_log_post_failed)); + } + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } + }; + + public cgeovisit() { + super("c:geo-log"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.visit); + setTitle(res.getString(R.string.log_new_log)); + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + cacheid = extras.getString(EXTRAS_ID); + geocode = extras.getString(EXTRAS_GEOCODE); + text = extras.getString(EXTRAS_TEXT); + alreadyFound = extras.getBoolean(EXTRAS_FOUND); + } + + if ((StringUtils.isBlank(cacheid)) && StringUtils.isNotBlank(geocode)) { + cacheid = app.getCacheid(geocode); + } + if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(cacheid)) { + geocode = app.getGeocode(cacheid); + } + + cache = app.getCacheByGeocode(geocode); + + if (StringUtils.isNotBlank(cache.name)) { + setTitle(res.getString(R.string.log_new_log) + " " + cache.name); + } else { + setTitle(res.getString(R.string.log_new_log) + " " + cache.geocode.toUpperCase()); + } + + app.setAction(geocode); + + if (cache == null) { + showToast(res.getString(R.string.err_detail_cache_forgot_visit)); + + finish(); + return; + } + + init(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + } + + @Override + public void onConfigurationChanged(Configuration newConfig) { + super.onConfigurationChanged(newConfig); + + init(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + SubMenu menuLog = null; + + menuLog = menu.addSubMenu(0, 0, 0, res.getString(R.string.log_add)).setIcon(android.R.drawable.ic_menu_add); + for (LogTemplate template : LogTemplateProvider.getTemplates()) { + menuLog.add(0, template.getItemId(), 0, template.getResourceId()); + } + menuLog.add(0, MENU_SIGNATURE, 0, res.getString(R.string.init_signature)); + + SubMenu menuStars = menu.addSubMenu(0, SUBMENU_VOTE, 0, res.getString(R.string.log_rating)).setIcon(android.R.drawable.ic_menu_sort_by_size); + menuStars.add(0, 10, 0, res.getString(R.string.log_no_rating)); + menuStars.add(0, 19, 0, res.getString(R.string.log_stars_5) + " (" + res.getString(R.string.log_stars_5_description) + ")"); + menuStars.add(0, 18, 0, res.getString(R.string.log_stars_45) + " (" + res.getString(R.string.log_stars_45_description) + ")"); + menuStars.add(0, 17, 0, res.getString(R.string.log_stars_4) + " (" + res.getString(R.string.log_stars_4_description) + ")"); + menuStars.add(0, 16, 0, res.getString(R.string.log_stars_35) + " (" + res.getString(R.string.log_stars_35_description) + ")"); + menuStars.add(0, 15, 0, res.getString(R.string.log_stars_3) + " (" + res.getString(R.string.log_stars_3_description) + ")"); + menuStars.add(0, 14, 0, res.getString(R.string.log_stars_25) + " (" + res.getString(R.string.log_stars_25_description) + ")"); + menuStars.add(0, 13, 0, res.getString(R.string.log_stars_2) + " (" + res.getString(R.string.log_stars_2_description) + ")"); + menuStars.add(0, 12, 0, res.getString(R.string.log_stars_15) + " (" + res.getString(R.string.log_stars_15_description) + ")"); + menuStars.add(0, 11, 0, res.getString(R.string.log_stars_1) + " (" + res.getString(R.string.log_stars_1_description) + ")"); + + return true; + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + boolean signatureAvailable = settings.getSignature() != null; + menu.findItem(MENU_SIGNATURE).setVisible(signatureAvailable); + + boolean voteAvailable = settings.isGCvoteLogin() && typeSelected == cgBase.LOG_FOUND_IT && StringUtils.isNotBlank(cache.guid); + menu.findItem(SUBMENU_VOTE).setVisible(voteAvailable); + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int id = item.getItemId(); + + if (id == MENU_SIGNATURE) { + EditText log = (EditText) findViewById(R.id.log); + String content = log.getText().toString(); + if (StringUtils.isNotBlank(content)) { + insertIntoLog("\n"); + } + insertIntoLog(LogTemplateProvider.applyTemplates(settings.getSignature(), base, false)); + return true; + } else if (id >= 10 && id <= 19) { + rating = (id - 9) / 2.0; + + if (post == null) { + post = (Button) findViewById(R.id.post); + } + if (rating == 0.0) { + post.setText(res.getString(R.string.log_post_no_rate)); + } else { + post.setText(res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"); + } + return true; + } + LogTemplate template = LogTemplateProvider.getTemplate(id); + if (template != null) { + String newText = template.getValue(base, false); + insertIntoLog(newText); + return true; + } + return false; + } + + private void insertIntoLog(String newText) { + EditText log = (EditText) findViewById(R.id.log); + cgBase.insertAtPosition(log, newText, true); + } + + private static String ratingTextValue(final double rating) { + return String.format(Locale.getDefault(), "%.1f", rating); + } + + public boolean setRating(String guid, double vote) { + if (StringUtils.isBlank(guid)) { + return false; + } + if (vote < 0.0 || vote > 5.0) { + return false; + } + + final Map<String, String> login = settings.getGCvoteLogin(); + if (login == null) { + return false; + } + + final Map<String, String> params = new HashMap<String, String>(); + params.put("userName", login.get("username")); + params.put("password", login.get("password")); + params.put("cacheId", guid); + params.put("voteUser", String.format("%.1f", rating).replace(',', '.')); + params.put("version", "cgeo"); + + final String result = base.request(false, "gcvote.com", "/setVote.php", "GET", params, false, false, false).getData(); + + return result.trim().equalsIgnoreCase("ok"); + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { + super.onCreateContextMenu(menu, view, info); + final int viewId = view.getId(); + + if (viewId == R.id.type) { + for (final int typeOne : types) { + menu.add(viewId, typeOne, 0, cgBase.logTypes2.get(typeOne)); + Log.w(cgSettings.tag, "Adding " + typeOne + " " + cgBase.logTypes2.get(typeOne)); + } + } else if (viewId == R.id.changebutton) { + final int textId = ((TextView) findViewById(viewId)).getId(); + + menu.setHeaderTitle(res.getString(R.string.log_tb_changeall)); + for (final int logTbAction : cgBase.logTypesTrackable.keySet()) { + menu.add(textId, logTbAction, 0, cgBase.logTypesTrackable.get(logTbAction)); + } + } else { + final int realViewId = ((LinearLayout) findViewById(viewId)).getId(); + + for (final cgTrackableLog tb : trackables) { + if (tb.id == realViewId) { + menu.setHeaderTitle(tb.name); + } + } + for (final int logTbAction : cgBase.logTypesTrackable.keySet()) { + menu.add(realViewId, logTbAction, 0, cgBase.logTypesTrackable.get(logTbAction)); + } + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + final int group = item.getGroupId(); + final int id = item.getItemId(); + + if (group == R.id.type) { + setType(id); + + return true; + } else if (group == R.id.changebutton) { + try { + final String logTbAction = cgBase.logTypesTrackable.get(id); + if (logTbAction != null) { + final LinearLayout inventView = (LinearLayout) findViewById(R.id.inventory); + for (int count = 0; count < inventView.getChildCount(); count++) { + final LinearLayout tbView = (LinearLayout) inventView.getChildAt(count); + if (tbView == null) { + return false; + } + + final TextView tbText = (TextView) tbView.findViewById(R.id.action); + if (tbText == null) { + return false; + } + tbText.setText(logTbAction); + } + for (cgTrackableLog tb : trackables) { + tb.action = id; + } + tbChanged = true; + return true; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeovisit.onContextItemSelected: " + e.toString()); + } + } else { + try { + final String logTbAction = cgBase.logTypesTrackable.get(id); + if (logTbAction != null) { + final LinearLayout tbView = (LinearLayout) findViewById(group); + if (tbView == null) { + return false; + } + + final TextView tbText = (TextView) tbView.findViewById(R.id.action); + if (tbText == null) { + return false; + } + + for (cgTrackableLog tb : trackables) { + if (tb.id == group) { + tbChanged = true; + + tb.action = id; + tbText.setText(logTbAction); + + Log.i(cgSettings.tag, "Trackable " + tb.trackCode + " (" + tb.name + ") has new action: #" + id); + } + } + + return true; + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeovisit.onContextItemSelected: " + e.toString()); + } + } + + return false; + } + + public void init() { + if (geocode != null) { + app.setAction(geocode); + } + + types = cache.getPossibleLogTypes(settings); + + final cgLog log = app.loadLogOffline(geocode); + if (log != null) { + typeSelected = log.type; + date.setTime(new Date(log.date)); + text = log.log; + if (typeSelected == cgBase.LOG_FOUND_IT && settings.isGCvoteLogin()) { + if (post == null) { + post = (Button) findViewById(R.id.post); + } + post.setText(res.getString(R.string.log_post_no_rate)); + } + } else if (StringUtils.isNotBlank(settings.getSignature()) + && settings.signatureAutoinsert + && StringUtils.isBlank(((EditText) findViewById(R.id.log)).getText())) { + insertIntoLog(LogTemplateProvider.applyTemplates(settings.getSignature(), base, false)); + } + + if (types.contains(typeSelected) == false) { + if (alreadyFound) { + typeSelected = cgBase.LOG_NOTE; + } else { + typeSelected = types.get(0); + } + setType(typeSelected); + } + + Button typeButton = (Button) findViewById(R.id.type); + registerForContextMenu(typeButton); + typeButton.setText(cgBase.logTypes2.get(typeSelected)); + typeButton.setOnClickListener(new View.OnClickListener() { + + public void onClick(View view) { + openContextMenu(view); + } + }); + + Button dateButton = (Button) findViewById(R.id.date); + dateButton.setText(base.formatShortDate(date.getTime().getTime())); + dateButton.setOnClickListener(new cgeovisitDateListener()); + + EditText logView = (EditText) findViewById(R.id.log); + if (StringUtils.isBlank(logView.getText()) && StringUtils.isNotBlank(text)) { + logView.setText(text); + } + + if (tweetBox == null) { + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + } + if (tweetCheck == null) { + tweetCheck = (CheckBox) findViewById(R.id.tweet); + } + tweetCheck.setChecked(true); + + if (post == null) { + post = (Button) findViewById(R.id.post); + } + if (cgBase.isEmpty(viewstates)) { + post.setEnabled(false); + post.setOnTouchListener(null); + post.setOnClickListener(null); + + loadData thread; + thread = new loadData(); + thread.start(); + } else { + post.setEnabled(true); + post.setOnClickListener(new postListener()); + } + + if (save == null) { + save = (Button) findViewById(R.id.save); + } + save.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + String log = ((EditText) findViewById(R.id.log)).getText().toString(); + cache.logOffline(cgeovisit.this, log, date, typeSelected); + } + }); + + if (clear == null) { + clear = (Button) findViewById(R.id.clear); + } + clear.setOnClickListener(new clearListener()); + } + + public void setDate(Calendar dateIn) { + date = dateIn; + + final Button dateButton = (Button) findViewById(R.id.date); + dateButton.setText(base.formatShortDate(date.getTime().getTime())); + } + + public void setType(int type) { + final Button typeButton = (Button) findViewById(R.id.type); + + if (cgBase.logTypes2.get(type) != null) { + typeSelected = type; + } + if (cgBase.logTypes2.get(typeSelected) == null) { + typeSelected = 1; + } + typeButton.setText(cgBase.logTypes2.get(typeSelected)); + + if (tweetBox == null) { + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + } + + if (type == 2 && tbChanged == false) { + // TODO: change action + } else if (type != 2 && tbChanged == false) { + // TODO: change action + } + + if (type == cgBase.LOG_FOUND_IT && settings.twitter == 1) { + tweetBox.setVisibility(View.VISIBLE); + } else { + tweetBox.setVisibility(View.GONE); + } + + if (post == null) { + post = (Button) findViewById(R.id.post); + } + + if (type == cgBase.LOG_FOUND_IT && settings.isGCvoteLogin()) { + if (rating == 0) { + post.setText(res.getString(R.string.log_post_no_rate)); + } else { + post.setText(res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"); + } + } else { + post.setText(res.getString(R.string.log_post)); + } + } + + private class cgeovisitDateListener implements View.OnClickListener { + + public void onClick(View arg0) { + Dialog dateDialog = new cgeodate(cgeovisit.this, cgeovisit.this, date); + dateDialog.setCancelable(true); + dateDialog.show(); + } + } + + private class postListener implements View.OnClickListener { + + public void onClick(View arg0) { + if (gettingViewstate == false) { + waitDialog = ProgressDialog.show(cgeovisit.this, null, res.getString(R.string.log_saving), true); + waitDialog.setCancelable(true); + + String log = ((EditText) findViewById(R.id.log)).getText().toString(); + Thread thread = new postLog(postLogHandler, log); + thread.start(); + } else { + showToast(res.getString(R.string.err_log_load_data_still)); + } + } + } + + private class clearListener implements View.OnClickListener { + + public void onClick(View arg0) { + app.clearLogOffline(geocode); + + if (alreadyFound) { + typeSelected = cgBase.LOG_NOTE; + } else { + typeSelected = types.get(0); + } + date.setTime(new Date()); + text = null; + + setType(typeSelected); + + Button dateButton = (Button) findViewById(R.id.date); + dateButton.setText(base.formatShortDate(date.getTime().getTime())); + dateButton.setOnClickListener(new cgeovisitDateListener()); + + EditText logView = (EditText) findViewById(R.id.log); + if (StringUtils.isNotBlank(text)) { + logView.setText(text); + } else { + logView.setText(""); + } + + if (clear == null) { + clear = (Button) findViewById(R.id.clear); + } + clear.setOnClickListener(new clearListener()); + + showToast(res.getString(R.string.info_log_cleared)); + } + } + + private class loadData extends Thread { + + public loadData() { + if (cacheid == null) { + showToast(res.getString(R.string.err_detail_cache_forgot_visit)); + + finish(); + return; + } + } + + @Override + public void run() { + final Map<String, String> params = new HashMap<String, String>(); + + showProgressHandler.sendEmptyMessage(0); + gettingViewstate = true; + attempts++; + + try { + if (StringUtils.isNotBlank(cacheid)) { + params.put("ID", cacheid); + } else { + loadDataHandler.sendEmptyMessage(0); + return; + } + + final String page = base.request(false, "www.geocaching.com", "/seek/log.aspx", "GET", params, false, false, false).getData(); + + viewstates = cgBase.getViewstates(page); + trackables = cgBase.parseTrackableLog(page); + + final List<Integer> typesPre = cgBase.parseTypes(page); + if (CollectionUtils.isNotEmpty(typesPre)) { + types.clear(); + types.addAll(typesPre); + types.remove(Integer.valueOf(cgBase.LOG_UPDATE_COORDINATES)); + } + typesPre.clear(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeovisit.loadData.run: " + e.toString()); + } + + loadDataHandler.sendEmptyMessage(0); + } + } + + private class postLog extends Thread { + + Handler handler = null; + String log = null; + + public postLog(Handler handlerIn, String logIn) { + handler = handlerIn; + log = logIn; + } + + @Override + public void run() { + int ret = -1; + + ret = postLogFn(log); + + handler.sendEmptyMessage(ret); + } + } + + public int postLogFn(String log) { + int status = -1; + + try { + if (tweetBox == null) { + tweetBox = (LinearLayout) findViewById(R.id.tweet_box); + } + if (tweetCheck == null) { + tweetCheck = (CheckBox) findViewById(R.id.tweet); + } + + status = base.postLog(app, geocode, cacheid, viewstates, typeSelected, + date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), + log, trackables); + + if (status == 1) { + cgLog logNow = new cgLog(); + logNow.author = settings.getUsername(); + logNow.date = date.getTimeInMillis(); + logNow.type = typeSelected; + logNow.log = log; + + if (cache != null && null != cache.logs) { + cache.logs.add(0, logNow); + } + app.addLog(geocode, logNow); + + if (typeSelected == cgBase.LOG_FOUND_IT) { + app.markFound(geocode); + if (cache != null) { + cache.found = true; + } + } + + if (cache != null) { + app.putCacheInCache(cache); + } else { + app.removeCacheFromCache(geocode); + } + } + + if (status == 1) { + app.clearLogOffline(geocode); + } + + if (status == 1 && typeSelected == cgBase.LOG_FOUND_IT && settings.twitter == 1 + && StringUtils.isNotBlank(settings.tokenPublic) && StringUtils.isNotBlank(settings.tokenSecret) + && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { + cgBase.postTweetCache(app, settings, geocode); + } + + if (status == 1 && typeSelected == cgBase.LOG_FOUND_IT && settings.isGCvoteLogin()) { + setRating(cache.guid, rating); + } + + return status; + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeovisit.postLogFn: " + e.toString()); + } + + return 1000; + } + +} diff --git a/main/src/cgeo/geocaching/cgeowaypoint.java b/main/src/cgeo/geocaching/cgeowaypoint.java new file mode 100644 index 0000000..e6dcb4f --- /dev/null +++ b/main/src/cgeo/geocaching/cgeowaypoint.java @@ -0,0 +1,348 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.content.Intent; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.util.Log; +import android.view.ContextMenu; +import android.view.ContextMenu.ContextMenuInfo; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.widget.Button; +import android.widget.ImageView; +import android.widget.TextView; + +public class cgeowaypoint extends AbstractActivity { + + private static final int MENU_ID_NAVIGATION = 0; + private static final int MENU_ID_CACHES_AROUND = 5; + private static final int MENU_ID_COMPASS = 2; + private cgWaypoint waypoint = null; + private String geocode = null; + private int id = -1; + private ProgressDialog waitDialog = null; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private Handler loadWaypointHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (waypoint == null) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + + showToast(res.getString(R.string.err_waypoint_load_failed)); + + finish(); + return; + } else { + final TextView identification = (TextView) findViewById(R.id.identification); + final TextView coords = (TextView) findViewById(R.id.coordinates); + final ImageView compass = (ImageView) findViewById(R.id.compass); + final View separator = (View) findViewById(R.id.separator); + + final View headline = (View) findViewById(R.id.headline); + registerNavigationMenu(headline); + + if (StringUtils.isNotBlank(waypoint.name)) { + setTitle(Html.fromHtml(waypoint.name.trim()).toString()); + } else { + setTitle(res.getString(R.string.waypoint_title)); + } + + if (waypoint.prefix.equalsIgnoreCase("OWN") == false) { + identification.setText(waypoint.prefix.trim() + "/" + waypoint.lookup.trim()); + } else { + identification.setText(res.getString(R.string.waypoint_custom)); + } + registerNavigationMenu(identification); + waypoint.setIcon(res, base, identification); + + if (waypoint.coords != null) { + coords.setText(Html.fromHtml(cgBase.formatCoords(waypoint.coords, true)), TextView.BufferType.SPANNABLE); + compass.setVisibility(View.VISIBLE); + separator.setVisibility(View.VISIBLE); + } else { + coords.setText(res.getString(R.string.waypoint_unknown_coordinates)); + compass.setVisibility(View.GONE); + separator.setVisibility(View.GONE); + } + registerNavigationMenu(coords); + + if (StringUtils.isNotBlank(waypoint.note)) { + final TextView note = (TextView) findViewById(R.id.note); + note.setText(Html.fromHtml(waypoint.note.trim()), TextView.BufferType.SPANNABLE); + registerNavigationMenu(note); + } + + Button buttonEdit = (Button) findViewById(R.id.edit); + buttonEdit.setOnClickListener(new editWaypointListener()); + + Button buttonDelete = (Button) findViewById(R.id.delete); + if (waypoint.isUserDefined()) { + buttonDelete.setOnClickListener(new deleteWaypointListener()); + buttonDelete.setVisibility(View.VISIBLE); + } + + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + Log.e(cgSettings.tag, "cgeowaypoint.loadWaypointHandler: " + e.toString()); + } + } + + private void registerNavigationMenu(View view) { + view.setOnClickListener(new View.OnClickListener() { + + @Override + public void onClick(View v) { + registerForContextMenu(v); + if (navigationPossible()) { + openContextMenu(v); + } + } + }); + } + }; + + public cgeowaypoint() { + super("c:geo-waypoint-details"); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.waypoint); + setTitle("waypoint"); + + // get parameters + Bundle extras = getIntent().getExtras(); + + // try to get data from extras + if (extras != null) { + id = extras.getInt("waypoint"); + geocode = extras.getString("geocode"); + } + + if (id <= 0) { + showToast(res.getString(R.string.err_waypoint_unknown)); + finish(); + return; + } + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); + waitDialog.setCancelable(true); + + (new loadWaypoint()).start(); + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + if (waitDialog == null) { + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); + waitDialog.setCancelable(true); + + (new loadWaypoint()).start(); + } + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + menu.add(0, MENU_ID_COMPASS, 0, res.getString(R.string.cache_menu_compass)).setIcon(android.R.drawable.ic_menu_compass); // compass + + SubMenu subMenu = menu.addSubMenu(1, MENU_ID_NAVIGATION, 0, res.getString(R.string.cache_menu_navigate)).setIcon(android.R.drawable.ic_menu_more); + addNavigationMenuItems(subMenu); + + menu.add(0, MENU_ID_CACHES_AROUND, 0, res.getString(R.string.cache_menu_around)).setIcon(android.R.drawable.ic_menu_rotate); // caches around + + return true; + } + + private void addNavigationMenuItems(Menu menu) { + NavigationAppFactory.addMenuItems(menu, this, res); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + try { + boolean visible = waypoint != null && waypoint.coords != null; + menu.findItem(MENU_ID_NAVIGATION).setVisible(visible); + menu.findItem(MENU_ID_COMPASS).setVisible(visible); + menu.findItem(MENU_ID_CACHES_AROUND).setVisible(visible); + } catch (Exception e) { + // nothing + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int menuItem = item.getItemId(); + if (menuItem == MENU_ID_COMPASS) { + goCompass(null); + return true; + } else if (menuItem == MENU_ID_CACHES_AROUND) { + cachesAround(); + return true; + } + + return NavigationAppFactory.onMenuItemSelected(item, geo, this, res, null, null, waypoint, null); + } + + private void cachesAround() { + if (waypoint == null || waypoint.coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + } + + cgeocaches.startActivityCachesAround(this, waypoint.coords); + + finish(); + } + + private class loadWaypoint extends Thread { + + @Override + public void run() { + try { + waypoint = app.loadWaypoint(id); + + loadWaypointHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeowaypoint.loadWaypoint.run: " + e.toString()); + } + } + } + + private static class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + // nothing + } + } + + private class editWaypointListener implements View.OnClickListener { + + public void onClick(View arg0) { + Intent editIntent = new Intent(cgeowaypoint.this, cgeowaypointadd.class); + editIntent.putExtra("waypoint", id); + startActivity(editIntent); + } + } + + private class deleteWaypointListener implements View.OnClickListener { + + public void onClick(View arg0) { + if (app.deleteWaypoint(id) == false) { + showToast(res.getString(R.string.err_waypoint_delete_failed)); + } else { + app.removeCacheFromCache(geocode); + + finish(); + return; + } + } + } + + public void goCompass(View view) { + if (!navigationPossible()) { + return; + } + + Intent navigateIntent = new Intent(this, cgeonavigate.class); + navigateIntent.putExtra("latitude", waypoint.coords.getLatitude()); + navigateIntent.putExtra("longitude", waypoint.coords.getLongitude()); + navigateIntent.putExtra("geocode", waypoint.prefix.trim() + "/" + waypoint.lookup.trim()); + navigateIntent.putExtra("name", waypoint.name); + + cgeonavigate.coordinates.clear(); + cgeonavigate.coordinates.add(new cgCoord(waypoint)); + startActivity(navigateIntent); + } + + private boolean navigationPossible() { + if (waypoint == null || waypoint.coords == null) { + showToast(res.getString(R.string.err_location_unknown)); + return false; + } + return true; + } + + @Override + public void onCreateContextMenu(ContextMenu menu, View v, + ContextMenuInfo menuInfo) { + if (navigationPossible()) { + menu.setHeaderTitle(res.getString(R.string.cache_menu_navigate)); + addNavigationMenuItems(menu); + } + } + + @Override + public boolean onContextItemSelected(MenuItem item) { + return onOptionsItemSelected(item); + } +} diff --git a/main/src/cgeo/geocaching/cgeowaypointadd.java b/main/src/cgeo/geocaching/cgeowaypointadd.java new file mode 100644 index 0000000..5ecfc41 --- /dev/null +++ b/main/src/cgeo/geocaching/cgeowaypointadd.java @@ -0,0 +1,358 @@ +package cgeo.geocaching; + +import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.geopoint.DistanceParser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.geopoint.GeopointParser; + +import org.apache.commons.lang3.StringUtils; + +import android.app.ProgressDialog; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.text.Html; +import android.util.Log; +import android.view.View; +import android.widget.ArrayAdapter; +import android.widget.AutoCompleteTextView; +import android.widget.Button; +import android.widget.EditText; + +import java.util.ArrayList; +import java.util.List; + +public class cgeowaypointadd extends AbstractActivity { + + private String geocode = null; + private int id = -1; + private cgGeo geo = null; + private cgUpdateLoc geoUpdate = new update(); + private ProgressDialog waitDialog = null; + private cgWaypoint waypoint = null; + private String type = "own"; + private String prefix = "OWN"; + private String lookup = "---"; + /** + * number of waypoints that the corresponding cache has until now + */ + private int wpCount = 0; + private Handler loadWaypointHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (waypoint == null) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + + id = -1; + } else { + geocode = waypoint.geocode; + type = waypoint.type; + prefix = waypoint.prefix; + lookup = waypoint.lookup; + + app.setAction(geocode); + + ((Button) findViewById(R.id.buttonLatitude)).setText(cgBase.formatLatitude(waypoint.coords.getLatitude(), true)); + ((Button) findViewById(R.id.buttonLongitude)).setText(cgBase.formatLongitude(waypoint.coords.getLongitude(), true)); + ((EditText) findViewById(R.id.name)).setText(Html.fromHtml(waypoint.name.trim()).toString()); + ((EditText) findViewById(R.id.note)).setText(Html.fromHtml(waypoint.note.trim()).toString()); + + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog = null; + } + Log.e(cgSettings.tag, "cgeowaypointadd.loadWaypointHandler: " + e.toString()); + } + } + }; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.waypoint_new); + setTitle("waypoint"); + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + // get parameters + Bundle extras = getIntent().getExtras(); + if (extras != null) { + geocode = extras.getString("geocode"); + wpCount = extras.getInt("count", 0); + id = extras.getInt("waypoint"); + } + + if (StringUtils.isBlank(geocode) && id <= 0) { + showToast(res.getString(R.string.err_waypoint_cache_unknown)); + + finish(); + return; + } + + if (id <= 0) { + setTitle(res.getString(R.string.waypoint_add_title)); + } else { + setTitle(res.getString(R.string.waypoint_edit_title)); + } + + if (geocode != null) { + app.setAction(geocode); + } + + Button buttonLat = (Button) findViewById(R.id.buttonLatitude); + buttonLat.setOnClickListener(new coordDialogListener()); + Button buttonLon = (Button) findViewById(R.id.buttonLongitude); + buttonLon.setOnClickListener(new coordDialogListener()); + + Button addWaypoint = (Button) findViewById(R.id.add_waypoint); + addWaypoint.setOnClickListener(new coordsListener()); + + List<String> wayPointNames = new ArrayList<String>(cgBase.waypointTypes.values()); + AutoCompleteTextView textView = (AutoCompleteTextView) findViewById(R.id.name); + ArrayAdapter<String> adapter = new ArrayAdapter<String>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); + textView.setAdapter(adapter); + + if (id > 0) { + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); + waitDialog.setCancelable(true); + + (new loadWaypoint()).start(); + } + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + + if (geo == null) { + geo = app.startGeo(this, geoUpdate, base, settings, 0, 0); + } + + if (id > 0) { + if (waitDialog == null) { + waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); + waitDialog.setCancelable(true); + + (new loadWaypoint()).start(); + } + } + } + + @Override + public void onDestroy() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onDestroy(); + } + + @Override + public void onStop() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (geo != null) { + geo = app.removeGeo(); + } + + super.onPause(); + } + + private class update extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null || geo.coordsNow == null) { + return; + } + + try { + Button bLat = (Button) findViewById(R.id.buttonLatitude); + Button bLon = (Button) findViewById(R.id.buttonLongitude); + bLat.setHint(cgBase.formatLatitude(geo.coordsNow.getLatitude(), false)); + bLon.setHint(cgBase.formatLongitude(geo.coordsNow.getLongitude(), false)); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + private class loadWaypoint extends Thread { + + @Override + public void run() { + try { + waypoint = app.loadWaypoint(id); + + loadWaypointHandler.sendMessage(new Message()); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeowaypoint.loadWaypoint.run: " + e.toString()); + } + } + } + + private class coordDialogListener implements View.OnClickListener { + + public void onClick(View arg0) { + Geopoint gp = null; + if (waypoint != null && waypoint.coords != null) + gp = waypoint.coords; + cgeocoords coordsDialog = new cgeocoords(cgeowaypointadd.this, settings, gp, geo); + coordsDialog.setCancelable(true); + coordsDialog.setOnCoordinateUpdate(new cgeocoords.CoordinateUpdate() { + @Override + public void update(final Geopoint gp) { + ((Button) findViewById(R.id.buttonLatitude)).setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + ((Button) findViewById(R.id.buttonLongitude)).setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); + if (waypoint != null) { + waypoint.coords = gp; + } + } + }); + coordsDialog.show(); + } + } + + private class coordsListener implements View.OnClickListener { + + public void onClick(View arg0) { + List<Double> coords = new ArrayList<Double>(); + Double latitude = null; + Double longitude = null; + + final String bearingText = ((EditText) findViewById(R.id.bearing)).getText().toString(); + final String distanceText = ((EditText) findViewById(R.id.distance)).getText().toString(); + final String latText = ((Button) findViewById(R.id.buttonLatitude)).getText().toString(); + final String lonText = ((Button) findViewById(R.id.buttonLongitude)).getText().toString(); + + if (StringUtils.isBlank(bearingText) && StringUtils.isBlank(distanceText) + && StringUtils.isBlank(latText) && StringUtils.isBlank(lonText)) { + helpDialog(res.getString(R.string.err_point_no_position_given_title), res.getString(R.string.err_point_no_position_given)); + return; + } + + if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { + try { + latitude = GeopointParser.parseLatitude(latText); + longitude = GeopointParser.parseLongitude(lonText); + } catch (GeopointParser.ParseException e) { + showToast(res.getString(e.resource)); + return; + } + } else { + if (geo == null || geo.coordsNow == null) { + showToast(res.getString(R.string.err_point_curr_position_unavailable)); + return; + } + + latitude = geo.coordsNow.getLatitude(); + longitude = geo.coordsNow.getLongitude(); + } + + if (StringUtils.isNotBlank(bearingText) && StringUtils.isNotBlank(distanceText)) { + // bearing & distance + Double bearing = null; + try { + bearing = new Double(bearingText); + } catch (Exception e) { + // probably not a number + } + if (bearing == null) { + helpDialog(res.getString(R.string.err_point_bear_and_dist_title), res.getString(R.string.err_point_bear_and_dist)); + return; + } + + double distance; + try { + distance = DistanceParser.parseDistance(distanceText, settings.units); + } catch (NumberFormatException e) { + showToast(res.getString(R.string.err_parse_dist)); + return; + } + + Double latParsed = null; + Double lonParsed = null; + + final Geopoint coordsDst = new Geopoint(latitude, longitude).project(bearing, distance); + + latParsed = coordsDst.getLatitude(); + lonParsed = coordsDst.getLongitude(); + + if (latParsed == null || lonParsed == null) { + showToast(res.getString(R.string.err_point_location_error)); + return; + } + + coords.add(0, (Double) latParsed); + coords.add(1, (Double) lonParsed); + } else if (latitude != null && longitude != null) { + coords.add(0, latitude); + coords.add(1, longitude); + } else { + showToast(res.getString(R.string.err_point_location_error)); + return; + } + + String name = ((EditText) findViewById(R.id.name)).getText().toString().trim(); + // if no name is given, just give the waypoint its number as name + if (name.length() == 0) { + name = res.getString(R.string.waypoint) + " " + String.valueOf(wpCount + 1); + } + final String note = ((EditText) findViewById(R.id.note)).getText().toString().trim(); + + final cgWaypoint waypoint = new cgWaypoint(); + waypoint.type = type; + waypoint.geocode = geocode; + waypoint.prefix = prefix; + waypoint.lookup = lookup; + waypoint.name = name; + waypoint.coords = new Geopoint(coords.get(0), coords.get(1)); + waypoint.latitudeString = cgBase.formatLatitude(coords.get(0), true); + waypoint.longitudeString = cgBase.formatLongitude(coords.get(1), true); + waypoint.note = note; + + if (app.saveOwnWaypoint(id, geocode, waypoint)) { + app.removeCacheFromCache(geocode); + + finish(); + return; + } else { + showToast(res.getString(R.string.err_waypoint_add_failed)); + } + } + } + + public void goManual(View view) { + if (id >= 0) { + ActivityMixin.goManual(this, "c:geo-waypoint-edit"); + } else { + ActivityMixin.goManual(this, "c:geo-waypoint-new"); + } + } +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java new file mode 100644 index 0000000..9c5148a --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8.java @@ -0,0 +1,27 @@ +package cgeo.geocaching.compatibility; + +import android.app.Activity; + +public class AndroidLevel8 { + static { + try { + Class.forName("cgeo.geocaching.compatibility.AndroidLevel8Internal"); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + private AndroidLevel8Internal internal; + + public static void check() { + // nothing + } + + public AndroidLevel8() { + internal = new AndroidLevel8Internal(); + } + + public int getRotation(Activity activity) { + return internal.getRotation(activity); + } +} diff --git a/main/src/cgeo/geocaching/compatibility/AndroidLevel8Internal.java b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Internal.java new file mode 100644 index 0000000..45b465b --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/AndroidLevel8Internal.java @@ -0,0 +1,15 @@ +package cgeo.geocaching.compatibility; + +import android.app.Activity; +import android.view.Display; + +class AndroidLevel8Internal { + + public AndroidLevel8Internal() { + } + + public static int getRotation(final Activity activity) { + Display display = activity.getWindowManager().getDefaultDisplay(); + return display.getRotation(); + } +} diff --git a/main/src/cgeo/geocaching/compatibility/Compatibility.java b/main/src/cgeo/geocaching/compatibility/Compatibility.java new file mode 100644 index 0000000..2f94915 --- /dev/null +++ b/main/src/cgeo/geocaching/compatibility/Compatibility.java @@ -0,0 +1,72 @@ +package cgeo.geocaching.compatibility; + +import android.app.Activity; +import android.content.res.Configuration; +import android.net.Uri; +import android.os.Build; +import android.view.Display; +import android.view.Surface; + +public final class Compatibility { + + private static AndroidLevel8 level8; + private static boolean initialized = false; + + private static AndroidLevel8 getLevel8() { + if (!initialized) { + try { + final int sdk = Integer.valueOf(Build.VERSION.SDK).intValue(); + if (sdk >= 8) { + level8 = new AndroidLevel8(); + } + } catch (Exception e) { + // nothing + } + initialized = true; + } + return level8; + } + + public static Float getDirectionNow(final Float directionNowPre, + final Activity activity) { + AndroidLevel8 level8 = getLevel8(); + + if (level8 != null) { + final int rotation = level8.getRotation(activity); + if (rotation == Surface.ROTATION_90) { + return directionNowPre + 90; + } else if (rotation == Surface.ROTATION_180) { + return directionNowPre + 180; + } else if (rotation == Surface.ROTATION_270) { + return directionNowPre + 270; + } + } else { + final Display display = activity.getWindowManager() + .getDefaultDisplay(); + final int rotation = display.getOrientation(); + if (rotation == Configuration.ORIENTATION_LANDSCAPE) { + return directionNowPre + 90; + } + } + return directionNowPre; + } + + public static Uri getCalendarProviderURI() { + final int sdk = Integer.valueOf(Build.VERSION.SDK).intValue(); + if (sdk >= 8) { + return Uri.parse("content://com.android.calendar/calendars"); + } else { + return Uri.parse("content://calendar/calendars"); + } + } + + public static Uri getCalenderEventsProviderURI() { + final int sdk = Integer.valueOf(Build.VERSION.SDK).intValue(); + if (sdk >= 8) { + return Uri.parse("content://com.android.calendar/events"); + } else { + return Uri.parse("content://calendar/events"); + } + } + +} diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java new file mode 100644 index 0000000..50a328e --- /dev/null +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -0,0 +1,26 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +public abstract class AbstractConnector implements IConnector { + + @Override + public boolean canHandle(String geocode) { + return false; + } + + @Override + public boolean supportsRefreshCache(cgCache cache) { + return false; + } + + @Override + public boolean supportsWatchList() { + return false; + } + + @Override + public boolean supportsLogging() { + return false; + } +} diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java new file mode 100644 index 0000000..867f48d --- /dev/null +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -0,0 +1,31 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +public final class ConnectorFactory { + private static final GCConnector GC_CONNECTOR = new GCConnector(); + private static final IConnector[] connectors = new IConnector[] { GC_CONNECTOR, new OCConnector(), new OXConnector() }; + + public static IConnector[] getConnectors() { + return connectors; + } + + public static boolean canHandle(final String geocode) { + for (IConnector connector : connectors) { + if (connector.canHandle(geocode)) { + return true; + } + } + return false; + } + + public static IConnector getConnector(cgCache cache) { + for (IConnector connector : connectors) { + if (connector.canHandle(cache.geocode)) { + return connector; + } + } + // in case of errors, assume GC as default + return GC_CONNECTOR; + } +} diff --git a/main/src/cgeo/geocaching/connector/GCConnector.java b/main/src/cgeo/geocaching/connector/GCConnector.java new file mode 100644 index 0000000..c7db821 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/GCConnector.java @@ -0,0 +1,33 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +import org.apache.commons.lang3.StringUtils; + +public class GCConnector extends AbstractConnector implements IConnector { + + @Override + public boolean canHandle(String geocode) { + return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("GC"); + } + + @Override + public boolean supportsRefreshCache(cgCache cache) { + return true; + } + + @Override + public String getCacheUrl(cgCache cache) { + return "http://www.geocaching.com/seek/cache_details.aspx?wp=" + cache.geocode; + } + + @Override + public boolean supportsWatchList() { + return true; + } + + @Override + public boolean supportsLogging() { + return true; + } +} diff --git a/main/src/cgeo/geocaching/connector/IConnector.java b/main/src/cgeo/geocaching/connector/IConnector.java new file mode 100644 index 0000000..66defd6 --- /dev/null +++ b/main/src/cgeo/geocaching/connector/IConnector.java @@ -0,0 +1,15 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +public interface IConnector { + public boolean canHandle(final String geocode); + + public boolean supportsRefreshCache(final cgCache cache); + + public String getCacheUrl(final cgCache cache); + + public boolean supportsWatchList(); + + public boolean supportsLogging(); +} diff --git a/main/src/cgeo/geocaching/connector/OCConnector.java b/main/src/cgeo/geocaching/connector/OCConnector.java new file mode 100644 index 0000000..ca252ad --- /dev/null +++ b/main/src/cgeo/geocaching/connector/OCConnector.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +import org.apache.commons.lang3.StringUtils; + +/** + * connector for OpenCaching.de (and several other country domains) + * + */ +public class OCConnector extends AbstractConnector implements IConnector { + @Override + public boolean canHandle(String geocode) { + return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("OC"); + } + + @Override + public String getCacheUrl(cgCache cache) { + return "http://www.opencaching.de/viewcache.php?wp=" + cache.geocode; + } +} diff --git a/main/src/cgeo/geocaching/connector/OXConnector.java b/main/src/cgeo/geocaching/connector/OXConnector.java new file mode 100644 index 0000000..a1203bc --- /dev/null +++ b/main/src/cgeo/geocaching/connector/OXConnector.java @@ -0,0 +1,23 @@ +package cgeo.geocaching.connector; + +import cgeo.geocaching.cgCache; + +import org.apache.commons.lang3.StringUtils; + +/** + * connector for OpenCaching.com + * + */ +public class OXConnector extends AbstractConnector implements IConnector { + + @Override + public boolean canHandle(String geocode) { + return StringUtils.isNotBlank(geocode) && geocode.toUpperCase().startsWith("OX"); + } + + @Override + public String getCacheUrl(cgCache cache) { + return "http://www.opencaching.com/#!geocache/" + cache.geocode; + } + +} diff --git a/main/src/cgeo/geocaching/enumerations/CacheSize.java b/main/src/cgeo/geocaching/enumerations/CacheSize.java new file mode 100644 index 0000000..b690b7c --- /dev/null +++ b/main/src/cgeo/geocaching/enumerations/CacheSize.java @@ -0,0 +1,42 @@ +package cgeo.geocaching.enumerations; + +import cgeo.geocaching.R; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Enum listing cache sizes + * + * @author koem + */ +public enum CacheSize { + MICRO("micro", 1, R.string.cache_size_micro), + SMALL("small", 2, R.string.cache_size_small), + REGULAR("regular", 3, R.string.cache_size_regular), + LARGE("large", 4, R.string.cache_size_large), + VIRTUAL("virtual", 0, R.string.cache_size_virtual), + NOT_CHOSEN("not chosen", 0, R.string.cache_size_notchosen), + OTHER("other", 0, R.string.cache_size_other); + + public final String id; + public final int comparable; + public final int stringId; + + private CacheSize(String id, int comparable, int stringId) { + this.id = id; + this.comparable = comparable; + this.stringId = stringId; + } + + final public static Map<String, CacheSize> FIND_BY_ID; + static { + final HashMap<String, CacheSize> mapping = new HashMap<String, CacheSize>(); + for (CacheSize cs : values()) { + mapping.put(cs.id, cs); + } + FIND_BY_ID = Collections.unmodifiableMap(mapping); + } + +} diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java new file mode 100644 index 0000000..59c774b --- /dev/null +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -0,0 +1,52 @@ +package cgeo.geocaching.enumerations; + +import cgeo.geocaching.R; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Enum listing all cache types + * + * @author koem + */ +public enum CacheType { + TRADITIONAL("traditional", "traditional cache", "32bc9333-5e52-4957-b0f6-5a2c8fc7b257", R.string.traditional), + MULTI("multi", "multi-cache", "a5f6d0ad-d2f2-4011-8c14-940a9ebf3c74", R.string.multi), + MYSTERY("mystery", "unknown cache", "40861821-1835-4e11-b666-8d41064d03fe", R.string.mystery), + LETTERBOX("letterbox", "letterbox hybrid", "4bdd8fb2-d7bc-453f-a9c5-968563b15d24", R.string.letterbox), + EVENT("event", "event cache", "69eb8534-b718-4b35-ae3c-a856a55b0874", R.string.event), + MEGA_EVENT("mega", "mega-event cache", "69eb8535-b718-4b35-ae3c-a856a55b0874", R.string.mega), + EARTH("earth", "earthcache", "c66f5cf3-9523-4549-b8dd-759cd2f18db8", R.string.earth), + CITO("cito", "cache in trash out event", "57150806-bc1a-42d6-9cf0-538d171a2d22", R.string.cito), + WEBCAM("webcam", "webcam cache", "31d2ae3c-c358-4b5f-8dcd-2185bf472d3d", R.string.webcam), + VIRTUAL("virtual", "virtual cache", "294d4360-ac86-4c83-84dd-8113ef678d7e", R.string.virtual), + WHERIGO("wherigo", "wherigo cache", "0544fa55-772d-4e5c-96a9-36a51ebcf5c9", R.string.wherigo), + LOSTANDFOUND("lostfound", "lost & found", "3ea6533d-bb52-42fe-b2d2-79a3424d4728", R.string.lostfound), + PROJECT_APE("ape", "project ape cache", "2555690d-b2bc-4b55-b5ac-0cb704c0b768", R.string.ape), + GCHQ("gchq", "groundspeak hq", "416f2494-dc17-4b6a-9bab-1a29dd292d8c", R.string.gchq), + GPS_EXHIBIT("gps", "gps cache exhibit", "72e69af2-7986-4990-afd9-bc16cbbb4ce3", R.string.gps); + + public final String id; + public final String pattern; + public final String guid; + public final int stringId; + + private CacheType(String id, String pattern, String guid, int stringId) { + this.id = id; + this.pattern = pattern; + this.guid = guid; + this.stringId = stringId; + } + + public final static Map<String, CacheType> FIND_BY_ID; + static { + final HashMap<String, CacheType> mapping = new HashMap<String, CacheType>(); + for (CacheType ct : values()) { + mapping.put(ct.id, ct); + } + FIND_BY_ID = Collections.unmodifiableMap(mapping); + } + +} diff --git a/main/src/cgeo/geocaching/enumerations/WaypointType.java b/main/src/cgeo/geocaching/enumerations/WaypointType.java new file mode 100644 index 0000000..2cb22a0 --- /dev/null +++ b/main/src/cgeo/geocaching/enumerations/WaypointType.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.enumerations; + +import cgeo.geocaching.R; + +import java.util.Collections; +import java.util.HashMap; +import java.util.Map; + +/** + * Enum listing waypoint types + * + * @author koem + */ +public enum WaypointType { + FLAG("flag", R.string.wp_final), + OWN("own", R.string.wp_stage), + PKG("pkg", R.string.wp_pkg), + PUZZLE("puzzle", R.string.wp_puzzle), + STAGE("stage", R.string.wp_stage), + TRAILHEAD("trailhead", R.string.wp_trailhead), + WAYPOINT("waypoint", R.string.wp_waypoint); + + public final String id; + public final int stringId; + + private WaypointType(String id, int stringId) { + this.id = id; + this.stringId = stringId; + } + + public static final Map<String, WaypointType> FIND_BY_ID; + static { + final HashMap<String, WaypointType> mapping = new HashMap<String, WaypointType>(); + for (WaypointType wt : values()) { + mapping.put(wt.id, wt); + } + FIND_BY_ID = Collections.unmodifiableMap(mapping); + } + +} diff --git a/main/src/cgeo/geocaching/files/FileList.java b/main/src/cgeo/geocaching/files/FileList.java new file mode 100644 index 0000000..5998436 --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileList.java @@ -0,0 +1,246 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.activity.AbstractListActivity; + +import org.apache.commons.lang3.ArrayUtils; + +import android.app.ProgressDialog; +import android.content.DialogInterface; +import android.os.Bundle; +import android.os.Environment; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.widget.ArrayAdapter; + +import java.io.File; +import java.util.ArrayList; +import java.util.List; + +public abstract class FileList<T extends ArrayAdapter<File>> extends AbstractListActivity { + + private List<File> files = new ArrayList<File>(); + private T adapter = null; + private ProgressDialog waitDialog = null; + private loadFiles searchingThread = null; + private boolean endSearching = false; + private int listId = 1; + final private Handler changeWaitDialogHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (msg.obj != null && waitDialog != null) { + waitDialog.setMessage(res.getString(R.string.file_searching_in) + " " + (String) msg.obj); + } + } + }; + final private Handler loadFilesHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + try { + if (files == null || files.isEmpty()) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + showToast(res.getString(R.string.file_list_no_files)); + + finish(); + return; + } else { + if (adapter != null) { + adapter.notifyDataSetChanged(); + } + } + + if (waitDialog != null) { + waitDialog.dismiss(); + } + } catch (Exception e) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + Log.e(cgSettings.tag, "cgFileList.loadFilesHandler: " + e.toString()); + } + } + }; + private String[] extensions; + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setTheme(); + setContentView(R.layout.gpx); + setTitle(); + + Bundle extras = getIntent().getExtras(); + if (extras != null) { + listId = extras.getInt("list"); + } + if (listId <= 0) { + listId = 1; + } + + setAdapter(); + + waitDialog = ProgressDialog.show( + this, + res.getString(R.string.file_title_searching), + res.getString(R.string.file_searching), + true, + true, + new DialogInterface.OnCancelListener() { + public void onCancel(DialogInterface arg0) { + if (searchingThread != null && searchingThread.isAlive()) { + searchingThread.notifyEnd(); + } + if (files.isEmpty()) { + finish(); + } + } + } + ); + + endSearching = false; + searchingThread = new loadFiles(); + searchingThread.start(); + } + + @Override + public void onResume() { + super.onResume(); + + getSettings().load(); + } + + protected abstract T getAdapter(List<File> files); + + private void setAdapter() { + if (adapter == null) { + adapter = getAdapter(files); + setListAdapter(adapter); + } + } + + /** + * Gets the base folder for file searches + * + * @return The folder to start the recursive search in + */ + protected abstract String[] getBaseFolders(); + + /** + * Triggers the deriving class to set the title + */ + protected abstract void setTitle(); + + private class loadFiles extends Thread { + public void notifyEnd() { + endSearching = true; + } + + @Override + public void run() { + List<File> list = new ArrayList<File>(); + + try { + if (Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { + boolean loaded = false; + for (String baseFolder : getBaseFolders()) + { + final File dir = new File(baseFolder); + + if (dir.exists() && dir.isDirectory()) { + listDir(list, dir); + if (list.size() > 0) { + loaded = true; + break; + } + } + } + if (!loaded) { + listDir(list, Environment.getExternalStorageDirectory()); + } + } else { + Log.w(cgSettings.tag, "No external media mounted."); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgFileList.loadFiles.run: " + e.toString()); + } + + final Message msg = new Message(); + msg.obj = "loaded directories"; + changeWaitDialogHandler.sendMessage(msg); + + files.addAll(list); + list.clear(); + + loadFilesHandler.sendMessage(new Message()); + } + } + + private void listDir(List<File> result, File directory) { + if (directory == null || !directory.isDirectory() || !directory.canRead()) { + return; + } + + final File[] files = directory.listFiles(); + + if (ArrayUtils.isNotEmpty(files)) { + for (File file : files) { + if (endSearching) { + return; + } + if (!file.canRead()) { + continue; + } + String name = file.getName(); + if (file.isFile()) { + for (String ext : extensions) { + int extLength = ext.length(); + if (name.length() > extLength && name.substring(name.length() - extLength, name.length()).equalsIgnoreCase(ext)) { + result.add(file); // add file to list + break; + } + } + + } else if (file.isDirectory()) { + if (name.charAt(0) == '.') { + continue; // skip hidden directories + } + if (name.length() > 16) { + name = name.substring(0, 14) + "..."; + } + final Message msg = new Message(); + msg.obj = name; + changeWaitDialogHandler.sendMessage(msg); + + listDir(result, file); // go deeper + } + } + } + + return; + } + + protected FileList(final String extension) { + setExtensions(new String[] { extension }); + } + + protected FileList(final String[] extensions) { + setExtensions(extensions); + } + + private void setExtensions(String[] extensionsIn) { + for (String extension : extensionsIn) { + if (extension.length() == 0 || extension.charAt(0) != '.') { + extension = "." + extension; + } + } + extensions = extensionsIn; + } +} diff --git a/main/src/cgeo/geocaching/files/FileParser.java b/main/src/cgeo/geocaching/files/FileParser.java new file mode 100644 index 0000000..158d390 --- /dev/null +++ b/main/src/cgeo/geocaching/files/FileParser.java @@ -0,0 +1,53 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSearch; + +import android.os.Handler; +import android.os.Message; + +import java.io.BufferedReader; +import java.io.File; +import java.io.FileNotFoundException; +import java.io.FileReader; +import java.io.IOException; +import java.util.Date; + +public abstract class FileParser { + protected static StringBuilder readFile(File file) + throws FileNotFoundException, IOException { + StringBuilder buffer = new StringBuilder(); + BufferedReader input = new BufferedReader(new FileReader(file)); + try { + String line = null; + while ((line = input.readLine()) != null) { + buffer.append(line); + } + } finally { + input.close(); + } + return buffer; + } + + static void showFinishedMessage(Handler handler, cgSearch search) { + if (handler != null) { + final Message msg = new Message(); + msg.obj = search.getCount(); + handler.sendMessage(msg); + } + } + + protected static void fixCache(cgCache cache) { + cache.latitudeString = cgBase.formatLatitude(cache.coords.getLatitude(), true); + cache.longitudeString = cgBase.formatLongitude(cache.coords.getLongitude(), true); + if (cache.inventory != null) { + cache.inventoryItems = cache.inventory.size(); + } else { + cache.inventoryItems = 0; + } + cache.updated = new Date().getTime(); + cache.detailedUpdate = new Date().getTime(); + } + +} diff --git a/main/src/cgeo/geocaching/files/GPX10Parser.java b/main/src/cgeo/geocaching/files/GPX10Parser.java new file mode 100644 index 0000000..c3b95b2 --- /dev/null +++ b/main/src/cgeo/geocaching/files/GPX10Parser.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.cgSearch; +import cgeo.geocaching.cgeoapplication; + +import android.sax.Element; + +public final class GPX10Parser extends GPXParser { + + public GPX10Parser(cgeoapplication appIn, int listIdIn, + cgSearch searchIn) { + super(appIn, listIdIn, searchIn, "http://www.topografix.com/GPX/1/0", "1.0"); + } + + @Override + protected Element getCacheParent(Element waypoint) { + return waypoint; + } + +} diff --git a/main/src/cgeo/geocaching/files/GPX11Parser.java b/main/src/cgeo/geocaching/files/GPX11Parser.java new file mode 100644 index 0000000..4c8960b --- /dev/null +++ b/main/src/cgeo/geocaching/files/GPX11Parser.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.cgSearch; +import cgeo.geocaching.cgeoapplication; + +import android.sax.Element; + +public final class GPX11Parser extends GPXParser { + + public GPX11Parser(cgeoapplication appIn, int listIdIn, + cgSearch searchIn) { + super(appIn, listIdIn, searchIn, "http://www.topografix.com/GPX/1/1", "1.1"); + } + + @Override + protected Element getCacheParent(Element waypoint) { + return waypoint.getChild(namespace, "extensions"); + } + +} diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java new file mode 100644 index 0000000..a0da184 --- /dev/null +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -0,0 +1,795 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgLog; +import cgeo.geocaching.cgSearch; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgTrackable; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.geopoint.Geopoint; + +import org.apache.commons.lang3.StringUtils; +import org.xml.sax.Attributes; +import org.xml.sax.SAXException; + +import android.os.Handler; +import android.sax.Element; +import android.sax.EndElementListener; +import android.sax.EndTextElementListener; +import android.sax.RootElement; +import android.sax.StartElementListener; +import android.util.Log; +import android.util.Xml; + +import java.io.File; +import java.io.FileInputStream; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.io.InputStream; +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Date; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public abstract class GPXParser extends FileParser { + + private static final SimpleDateFormat formatSimple = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'"); // 2010-04-20T07:00:00Z + private static final SimpleDateFormat formatTimezone = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'.000'Z"); // 2010-04-20T01:01:03.000-04:00 + + private static final Pattern patternGeocode = Pattern.compile("([A-Z]{2}[0-9A-Z]+)", Pattern.CASE_INSENSITIVE); + private static final Pattern patternGuid = Pattern.compile(".*" + Pattern.quote("guid=") + "([0-9a-z\\-]+)", Pattern.CASE_INSENSITIVE); + private static final String[] nsGCList = new String[] { + "http://www.groundspeak.com/cache/1/1", // PQ 1.1 + "http://www.groundspeak.com/cache/1/0/1", // PQ 1.0.1 + "http://www.groundspeak.com/cache/1/0", // PQ 1.0 + }; + + private static final String GSAK_NS = "http://www.gsak.net/xmlv1/5"; + + private static cgeoapplication app = null; + private int listId = 1; + private cgSearch search = null; + final protected String namespace; + final private String version; + private Handler handler = null; + + private cgCache cache = new cgCache(); + private cgTrackable trackable = new cgTrackable(); + private cgLog log = new cgLog(); + + private String type = null; + private String sym = null; + private String name = null; + private String cmt = null; + private String desc = null; + protected String[] userData = new String[5]; // take 5 cells, that makes indexing 1..4 easier + + private final class UserDataListener implements EndTextElementListener { + private int index; + + public UserDataListener(int index) { + this.index = index; + } + + @Override + public void end(String user) { + userData[index] = validate(user); + } + } + + private static final class CacheAttributeTranslator { + // List of cache attributes matching IDs used in GPX files. + // The ID is represented by the position of the String in the array. + // Strings are not used as text but as resource IDs of strings, just to be aware of changes + // made in strings.xml which then will lead to compile errors here and not to runtime errors. + private static final int[] CACHE_ATTRIBUTES = { + -1, // 0 + R.string.attribute_dogs_yes, // 1 + R.string.attribute_fee_yes, // 2 + R.string.attribute_rappelling_yes, // 3 + R.string.attribute_boat_yes, // 4 + R.string.attribute_scuba_yes, // 5 + R.string.attribute_kids_yes, // 6 + R.string.attribute_onehour_yes, // 7 + R.string.attribute_scenic_yes, // 8 + R.string.attribute_hiking_yes, // 9 + R.string.attribute_climbing_yes, // 10 + R.string.attribute_wading_yes, // 11 + R.string.attribute_swimming_yes, // 12 + R.string.attribute_available_yes, // 13 + R.string.attribute_night_yes, // 14 + R.string.attribute_winter_yes, // 15 + -1, // 16 + R.string.attribute_poisonoak_yes, // 17 + R.string.attribute_dangerousanimals_yes, // 18 + R.string.attribute_ticks_yes, // 19 + R.string.attribute_mine_yes, // 20 + R.string.attribute_cliff_yes, // 21 + R.string.attribute_hunting_yes, // 22 + R.string.attribute_danger_yes, // 23 + R.string.attribute_wheelchair_yes, // 24 + R.string.attribute_parking_yes, // 25 + R.string.attribute_public_yes, // 26 + R.string.attribute_water_yes, // 27 + R.string.attribute_restrooms_yes, // 28 + R.string.attribute_phone_yes, // 29 + R.string.attribute_picnic_yes, // 30 + R.string.attribute_camping_yes, // 31 + R.string.attribute_bicycles_yes, // 32 + R.string.attribute_motorcycles_yes, // 33 + R.string.attribute_quads_yes, // 34 + R.string.attribute_jeeps_yes, // 35 + R.string.attribute_snowmobiles_yes, // 36 + R.string.attribute_horses_yes, // 37 + R.string.attribute_campfires_yes, // 38 + R.string.attribute_thorn_yes, // 39 + R.string.attribute_stealth_yes, // 40 + R.string.attribute_stroller_yes, // 41 + R.string.attribute_firstaid_yes, // 42 + R.string.attribute_cow_yes, // 43 + R.string.attribute_flashlight_yes, // 44 + R.string.attribute_landf_yes, // 45 + R.string.attribute_rv_yes, // 46 + R.string.attribute_field_puzzle_yes, // 47 + R.string.attribute_uv_yes, // 48 + R.string.attribute_snowshoes_yes, // 49 + R.string.attribute_skiis_yes, // 50 + R.string.attribute_s_tool_yes, // 51 + R.string.attribute_nightcache_yes, // 52 + R.string.attribute_parkngrab_yes, // 53 + R.string.attribute_abandonedbuilding_yes, // 54 + R.string.attribute_hike_short_yes, // 55 + R.string.attribute_hike_med_yes, // 56 + R.string.attribute_hike_long_yes, // 57 + R.string.attribute_fuel_yes, // 58 + R.string.attribute_food_yes, // 59 + R.string.attribute_wirelessbeacon_yes, // 60 + R.string.attribute_partnership_yes, // 61 + R.string.attribute_seasonal_yes, // 62 + R.string.attribute_touristok_yes, // 63 + R.string.attribute_treeclimbing_yes, // 64 + R.string.attribute_frontyard_yes, // 65 + R.string.attribute_teamwork_yes, // 66 + }; + private static final String YES = "_yes"; + private static final String NO = "_no"; + private static final Pattern BASENAME_PATTERN = Pattern.compile("^.*attribute_(.*)(_yes|_no)"); + + // map GPX-Attribute-Id to baseName + public static String getBaseName(final int id) { + // get String out of array + if (CACHE_ATTRIBUTES.length <= id) { + return null; + } + final int stringId = CACHE_ATTRIBUTES[id]; + if (stringId == -1) { + return null; // id not found + } + // get text for string + String stringName = null; + try { + stringName = app.getResources().getResourceName(stringId); + } catch (NullPointerException e) { + return null; + } + if (stringName == null) { + return null; + } + // cut out baseName + final Matcher m = BASENAME_PATTERN.matcher(stringName); + if (!m.matches()) { + return null; + } + return m.group(1); + } + + // @return baseName + "_yes" or "_no" e.g. "food_no" or "uv_yes" + public static String getInternalId(final int attributeId, final boolean active) { + final String baseName = CacheAttributeTranslator.getBaseName(attributeId); + if (baseName == null) { + return null; + } + return baseName + (active ? YES : NO); + } + } + + protected GPXParser(cgeoapplication appIn, int listIdIn, cgSearch searchIn, String namespaceIn, String versionIn) { + app = appIn; + listId = listIdIn; + search = searchIn; + namespace = namespaceIn; + version = versionIn; + } + + private static Date parseDate(String inputUntrimmed) throws ParseException { + final String input = inputUntrimmed.trim(); + if (input.length() >= 3 && input.charAt(input.length() - 3) == ':') { + final String removeColon = input.substring(0, input.length() - 3) + input.substring(input.length() - 2); + return formatTimezone.parse(removeColon); + } + return formatSimple.parse(input); + } + + public UUID parse(final InputStream stream, Handler handlerIn) { + handler = handlerIn; + + final RootElement root = new RootElement(namespace, "gpx"); + final Element waypoint = root.getChild(namespace, "wpt"); + + // waypoint - attributes + waypoint.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attrs) { + try { + if (attrs.getIndex("lat") > -1 && attrs.getIndex("lon") > -1) { + cache.coords = new Geopoint(new Double(attrs.getValue("lat")), + new Double(attrs.getValue("lon"))); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse waypoint's latitude and/or longitude."); + } + } + }); + + // waypoint + waypoint.setEndElementListener(new EndElementListener() { + + @Override + public void end() { + if (StringUtils.isBlank(cache.geocode)) { + // try to find geocode somewhere else + findGeoCode(name); + findGeoCode(desc); + findGeoCode(cmt); + } + + if (StringUtils.isNotBlank(cache.geocode) + && cache.coords != null + && ((type == null && sym == null) + || (type != null && type.indexOf("geocache") > -1) + || (sym != null && sym.indexOf("geocache") > -1))) { + fixCache(cache); + cache.reason = listId; + cache.detailed = true; + + if (StringUtils.isBlank(cache.personalNote)) { + StringBuilder buffer = new StringBuilder(); + for (int i = 0; i < userData.length; i++) { + if (StringUtils.isNotBlank(userData[i])) { + buffer.append(' ').append(userData[i]); + } + } + String note = buffer.toString().trim(); + if (StringUtils.isNotBlank(note)) { + cache.personalNote = note; + } + } + + app.addCacheToSearch(search, cache); + } + + showFinishedMessage(handler, search); + + resetCache(); + } + }); + + // waypoint.time + waypoint.getChild(namespace, "time").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + try { + cache.hidden = parseDate(body); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse cache date: " + e.toString()); + } + } + }); + + // waypoint.name + waypoint.getChild(namespace, "name").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + name = body; + + final String content = body.trim(); + cache.name = content; + + findGeoCode(cache.name); + findGeoCode(cache.description); + } + }); + + // waypoint.desc + waypoint.getChild(namespace, "desc").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + desc = body; + + cache.shortdesc = validate(body); + } + }); + + // waypoint.cmt + waypoint.getChild(namespace, "cmt").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + cmt = body; + + cache.description = validate(body); + } + }); + + // waypoint.type + waypoint.getChild(namespace, "type").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String[] content = body.split("\\|"); + if (content.length > 0) { + type = content[0].toLowerCase().trim(); + } + } + }); + + // waypoint.sym + waypoint.getChild(namespace, "sym").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + body = body.toLowerCase(); + sym = body; + if (body.indexOf("geocache") != -1 && body.indexOf("found") != -1) { + cache.found = true; + } + } + }); + + // waypoint.url + waypoint.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String url) { + final Matcher matcher = patternGuid.matcher(url); + if (matcher.matches()) { + String guid = matcher.group(1); + if (StringUtils.isNotBlank(guid)) { + cache.guid = guid; + } + } + } + }); + + // for GPX 1.0, cache info comes from waypoint node (so called private children, + // for GPX 1.1 from extensions node + final Element cacheParent = getCacheParent(waypoint); + + // GSAK extensions + final Element gsak = cacheParent.getChild(GSAK_NS, "wptExtension"); + gsak.getChild(GSAK_NS, "Watch").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String watchList) { + cache.onWatchlist = Boolean.valueOf(watchList.trim()); + } + }); + + gsak.getChild(GSAK_NS, "UserData").setEndTextElementListener(new UserDataListener(1)); + + for (int i = 2; i <= 4; i++) { + gsak.getChild(GSAK_NS, "User" + i).setEndTextElementListener(new UserDataListener(i)); + } + + // 3 different versions of the GC schema + for (String nsGC : nsGCList) { + // waypoints.cache + final Element gcCache = cacheParent.getChild(nsGC, "cache"); + + gcCache.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attrs) { + try { + if (attrs.getIndex("id") > -1) { + cache.cacheId = attrs.getValue("id"); + } + if (attrs.getIndex("archived") > -1) { + cache.archived = attrs.getValue("archived").equalsIgnoreCase("true"); + } + if (attrs.getIndex("available") > -1) { + cache.disabled = !attrs.getValue("available").equalsIgnoreCase("true"); + } + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse cache attributes."); + } + } + }); + + // waypoint.cache.name + gcCache.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String cacheName) { + cache.name = validate(cacheName); + } + }); + + // waypoint.cache.owner + gcCache.getChild(nsGC, "owner").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String cacheOwner) { + cache.owner = validate(cacheOwner); + } + }); + + // waypoint.cache.type + gcCache.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + setType(validate(body.toLowerCase())); + } + }); + + // waypoint.cache.container + gcCache.getChild(nsGC, "container").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + cache.size = CacheSize.FIND_BY_ID.get(validate(body.toLowerCase())); + } + }); + + // waypoint.cache.attributes + // @see issue #299 + + // <groundspeak:attributes> + // <groundspeak:attribute id="32" inc="1">Bicycles</groundspeak:attribute> + // <groundspeak:attribute id="13" inc="1">Available at all times</groundspeak:attribute> + // where inc = 0 => _no, inc = 1 => _yes + // IDs see array CACHE_ATTRIBUTES + final Element gcAttributes = gcCache.getChild(nsGC, "attributes"); + + // waypoint.cache.attribute + final Element gcAttribute = gcAttributes.getChild(nsGC, "attribute"); + + gcAttribute.setStartElementListener(new StartElementListener() { + @Override + public void start(Attributes attrs) { + try { + if (attrs.getIndex("id") > -1 && attrs.getIndex("inc") > -1) { + int attributeId = Integer.parseInt(attrs.getValue("id")); + boolean attributeActive = Integer.parseInt(attrs.getValue("inc")) != 0; + String internalId = CacheAttributeTranslator.getInternalId(attributeId, attributeActive); + if (internalId != null) { + if (cache.attributes == null) { + cache.attributes = new ArrayList<String>(); + } + cache.attributes.add(internalId); + } + } + } catch (NumberFormatException e) { + // nothing + } + } + }); + + // waypoint.cache.difficulty + gcCache.getChild(nsGC, "difficulty").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + try { + cache.difficulty = new Float(body); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse difficulty: " + e.toString()); + } + } + }); + + // waypoint.cache.terrain + gcCache.getChild(nsGC, "terrain").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + try { + cache.terrain = new Float(body); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse terrain: " + e.toString()); + } + } + }); + + // waypoint.cache.country + gcCache.getChild(nsGC, "country").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String country) { + if (StringUtils.isBlank(cache.location)) { + cache.location = validate(country); + } else { + cache.location = cache.location + ", " + country.trim(); + } + } + }); + + // waypoint.cache.state + gcCache.getChild(nsGC, "state").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String state) { + if (StringUtils.isBlank(cache.location)) { + cache.location = validate(state); + } else { + cache.location = state.trim() + ", " + cache.location; + } + } + }); + + // waypoint.cache.encoded_hints + gcCache.getChild(nsGC, "encoded_hints").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String encoded) { + cache.hint = validate(encoded); + } + }); + + gcCache.getChild(nsGC, "short_description").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String shortDesc) { + cache.shortdesc = validate(shortDesc); + } + }); + + gcCache.getChild(nsGC, "long_description").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String desc) { + cache.description = validate(desc); + } + }); + + // waypoint.cache.travelbugs + final Element gcTBs = gcCache.getChild(nsGC, "travelbugs"); + + // waypoint.cache.travelbugs.travelbug + gcTBs.getChild(nsGC, "travelbug").setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attrs) { + trackable = new cgTrackable(); + + try { + if (attrs.getIndex("ref") > -1) { + trackable.geocode = attrs.getValue("ref").toUpperCase(); + } + } catch (Exception e) { + // nothing + } + } + }); + + // waypoint.cache.travelbug + final Element gcTB = gcTBs.getChild(nsGC, "travelbug"); + + gcTB.setEndElementListener(new EndElementListener() { + + @Override + public void end() { + if (StringUtils.isNotBlank(trackable.geocode) && StringUtils.isNotBlank(trackable.name)) { + if (cache.inventory == null) { + cache.inventory = new ArrayList<cgTrackable>(); + } + cache.inventory.add(trackable); + } + } + }); + + // waypoint.cache.travelbugs.travelbug.name + gcTB.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String tbName) { + trackable.name = validate(tbName); + } + }); + + // waypoint.cache.logs + final Element gcLogs = gcCache.getChild(nsGC, "logs"); + + // waypoint.cache.log + final Element gcLog = gcLogs.getChild(nsGC, "log"); + + gcLog.setStartElementListener(new StartElementListener() { + + @Override + public void start(Attributes attrs) { + log = new cgLog(); + + try { + if (attrs.getIndex("id") > -1) { + log.id = Integer.parseInt(attrs.getValue("id")); + } + } catch (Exception e) { + // nothing + } + } + }); + + gcLog.setEndElementListener(new EndElementListener() { + + @Override + public void end() { + if (StringUtils.isNotBlank(log.log)) { + if (cache.logs == null) { + cache.logs = new ArrayList<cgLog>(); + } + cache.logs.add(log); + } + } + }); + + // waypoint.cache.logs.log.date + gcLog.getChild(nsGC, "date").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + try { + log.date = parseDate(body).getTime(); + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to parse log date: " + e.toString()); + } + } + }); + + // waypoint.cache.logs.log.type + gcLog.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String body) { + final String logType = validate(body).toLowerCase(); + if (cgBase.logTypes0.containsKey(logType)) { + log.type = cgBase.logTypes0.get(logType); + } else { + log.type = cgBase.LOG_NOTE; + } + } + }); + + // waypoint.cache.logs.log.finder + gcLog.getChild(nsGC, "finder").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String finderName) { + log.author = validate(finderName); + } + }); + + // waypoint.cache.logs.log.text + gcLog.getChild(nsGC, "text").setEndTextElementListener(new EndTextElementListener() { + + @Override + public void end(String logText) { + log.log = validate(logText); + } + }); + } + boolean parsed = false; + try { + Xml.parse(stream, Xml.Encoding.UTF_8, root.getContentHandler()); + parsed = true; + } catch (IOException e) { + Log.e(cgSettings.tag, "Cannot parse .gpx file as GPX " + version + ": could not read file!"); + } catch (SAXException e) { + Log.e(cgSettings.tag, "Cannot parse .gpx file as GPX " + version + ": could not parse XML - " + e.toString()); + } + return parsed ? search.getCurrentId() : null; + } + + private UUID parse(final File file, final Handler handlerIn) { + if (file == null) { + return null; + } + + FileInputStream fis = null; + UUID result = null; + try { + fis = new FileInputStream(file); + result = parse(fis, handlerIn); + } catch (FileNotFoundException e) { + Log.e(cgSettings.tag, "Cannot parse .gpx file " + file.getAbsolutePath() + " as GPX " + version + ": file not found!"); + } + try { + if (fis != null) { + fis.close(); + } + } catch (IOException e) { + Log.e(cgSettings.tag, "Error after parsing .gpx file " + file.getAbsolutePath() + " as GPX " + version + ": could not close file!"); + } + return result; + } + + protected abstract Element getCacheParent(Element waypoint); + + protected static String validate(String input) { + if ("nil".equalsIgnoreCase(input)) { + return ""; + } + return input.trim(); + } + + private void setType(String parsedString) { + final String knownType = cgBase.cacheTypes.get(parsedString); + if (knownType != null) { + cache.type = knownType; + } + else { + if (StringUtils.isBlank(cache.type)) { + cache.type = "mystery"; // default for not recognized types + } + } + } + + private void findGeoCode(final String input) { + if (input == null || StringUtils.isNotBlank(cache.geocode)) { + return; + } + final Matcher matcherGeocode = patternGeocode.matcher(input); + if (matcherGeocode.find()) { + if (matcherGeocode.groupCount() > 0) { + final String geocode = matcherGeocode.group(1); + if (ConnectorFactory.canHandle(geocode)) { + cache.geocode = geocode; + } + } + } + } + + private void resetCache() { + type = null; + sym = null; + name = null; + desc = null; + cmt = null; + + cache = new cgCache(); + for (int i = 0; i < userData.length; i++) { + userData[i] = null; + } + } + + public static UUID parseGPX(cgeoapplication app, File file, int listId, Handler handler) { + final cgSearch search = new cgSearch(); + UUID searchId = null; + + try { + GPXParser parser = new GPX10Parser(app, listId, search); + searchId = parser.parse(file, handler); + if (searchId == null) { + parser = new GPX11Parser(app, listId, search); + searchId = parser.parse(file, handler); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.parseGPX: " + e.toString()); + } + + Log.i(cgSettings.tag, "Caches found in .gpx file: " + app.getCount(searchId)); + + return search.getCurrentId(); + } +} diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java new file mode 100644 index 0000000..240d61d --- /dev/null +++ b/main/src/cgeo/geocaching/files/LocParser.java @@ -0,0 +1,177 @@ +package cgeo.geocaching.files; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgCacheWrap; +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgSearch; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.enumerations.CacheSize; +import cgeo.geocaching.geopoint.GeopointParser; + +import org.apache.commons.lang3.StringUtils; + +import android.os.Handler; +import android.util.Log; + +import java.io.File; +import java.util.HashMap; +import java.util.Map; +import java.util.Map.Entry; +import java.util.UUID; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class LocParser extends FileParser { + private static final Pattern patternGeocode = Pattern + .compile("name id=\"([^\"]+)\""); + private static final Pattern patternLat = Pattern + .compile("lat=\"([^\"]+)\""); + private static final Pattern patternLon = Pattern + .compile("lon=\"([^\"]+)\""); + // premium only >> + private static final Pattern patternDifficulty = Pattern + .compile("<difficulty>([^<]+)</difficulty>"); + private static final Pattern patternTerrain = Pattern + .compile("<terrain>([^<]+)</terrain>"); + private static final Pattern patternContainer = Pattern + .compile("<container>([^<]+)</container>"); + private static final Pattern patternName = Pattern.compile("CDATA\\[([^\\]]+)\\]"); + + public static void parseLoc(final cgCacheWrap caches, + final String fileContent) { + final Map<String, cgCoord> cidCoords = parseCoordinates(fileContent); + + // save found cache coordinates + for (cgCache cache : caches.cacheList) { + if (cidCoords.containsKey(cache.geocode)) { + cgCoord coord = cidCoords.get(cache.geocode); + + copyCoordToCache(coord, cache); + } + } + } + + private static void copyCoordToCache(final cgCoord coord, final cgCache cache) { + cache.coords = coord.coords; + cache.difficulty = coord.difficulty; + cache.terrain = coord.terrain; + cache.size = coord.size; + cache.geocode = coord.geocode.toUpperCase(); + if (StringUtils.isBlank(cache.name)) { + cache.name = coord.name; + } + } + + private static Map<String, cgCoord> parseCoordinates( + final String fileContent) { + final Map<String, cgCoord> coords = new HashMap<String, cgCoord>(); + if (StringUtils.isBlank(fileContent)) { + return coords; + } + // >> premium only + + final String[] points = fileContent.split("<waypoint>"); + + // parse coordinates + for (String pointString : points) { + final cgCoord pointCoord = new cgCoord(); + + final Matcher matcherGeocode = patternGeocode.matcher(pointString); + if (matcherGeocode.find()) { + String geocode = matcherGeocode.group(1).trim().toUpperCase(); + pointCoord.name = geocode; + pointCoord.geocode = geocode; + } + final Matcher matcherName = patternName.matcher(pointString); + if (matcherName.find()) { + String name = matcherName.group(1).trim(); + int pos = name.indexOf(" by "); + if (pos > 0) { + name = name.substring(0, pos).trim(); + } + pointCoord.name = name; + } + final Matcher matcherLat = patternLat.matcher(pointString); + final Matcher matcherLon = patternLon.matcher(pointString); + if (matcherLat.find() && matcherLon.find()) { + pointCoord.coords = + GeopointParser.parse(matcherLat.group(1).trim(), matcherLon.group(1).trim()); + } + final Matcher matcherDifficulty = patternDifficulty.matcher(pointString); + if (matcherDifficulty.find()) { + pointCoord.difficulty = new Float(matcherDifficulty.group(1) + .trim()); + } + final Matcher matcherTerrain = patternTerrain.matcher(pointString); + if (matcherTerrain.find()) { + pointCoord.terrain = new Float(matcherTerrain.group(1).trim()); + } + final Matcher matcherContainer = patternContainer.matcher(pointString); + if (matcherContainer.find()) { + final int size = Integer.parseInt(matcherContainer.group(1) + .trim()); + + if (size == 1) { + pointCoord.size = CacheSize.NOT_CHOSEN; + } else if (size == 2) { + pointCoord.size = CacheSize.MICRO; + } else if (size == 3) { + pointCoord.size = CacheSize.REGULAR; + } else if (size == 4) { + pointCoord.size = CacheSize.LARGE; + } else if (size == 5) { + pointCoord.size = CacheSize.VIRTUAL; + } else if (size == 6) { + pointCoord.size = CacheSize.OTHER; + } else if (size == 8) { + pointCoord.size = CacheSize.SMALL; + } else { + pointCoord.size = null; + } + } + + if (StringUtils.isNotBlank(pointCoord.geocode)) { + coords.put(pointCoord.geocode, pointCoord); + } + } + + Log.i(cgSettings.tag, + "Coordinates found in .loc file: " + coords.size()); + return coords; + } + + public static UUID parseLoc(cgeoapplication app, File file, int listId, + Handler handler) { + cgSearch search = new cgSearch(); + UUID searchId = null; + + try { + Map<String, cgCoord> coords = parseCoordinates(readFile(file).toString()); + final cgCacheWrap caches = new cgCacheWrap(); + for (Entry<String, cgCoord> entry : coords.entrySet()) { + cgCoord coord = entry.getValue(); + if (StringUtils.isBlank(coord.geocode) || StringUtils.isBlank(coord.name)) { + continue; + } + cgCache cache = new cgCache(); + copyCoordToCache(coord, cache); + caches.cacheList.add(cache); + + fixCache(cache); + cache.reason = listId; + cache.detailed = false; + + app.addCacheToSearch(search, cache); + } + caches.totalCnt = caches.cacheList.size(); + showFinishedMessage(handler, search); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgBase.parseGPX: " + e.toString()); + } + + Log.i(cgSettings.tag, "Caches found in .gpx file: " + app.getCount(searchId)); + + return search.getCurrentId(); + } +} diff --git a/main/src/cgeo/geocaching/filter/cgFilter.java b/main/src/cgeo/geocaching/filter/cgFilter.java new file mode 100644 index 0000000..36463ba --- /dev/null +++ b/main/src/cgeo/geocaching/filter/cgFilter.java @@ -0,0 +1,30 @@ +package cgeo.geocaching.filter; + +import cgeo.geocaching.cgCache; + +import java.util.ArrayList; +import java.util.List; + +public abstract class cgFilter { + String name; + + public cgFilter(String name) { + this.name = name; + } + + abstract boolean applyFilter(cgCache cache); + + public void filter(List<cgCache> list) { + List<cgCache> itemsToRemove = new ArrayList<cgCache>(); + for (cgCache item : list) { + if (!applyFilter(item)) { + itemsToRemove.add(item); + } + } + list.removeAll(itemsToRemove); + } + + public String getFilterName() { + return name; + } +} diff --git a/main/src/cgeo/geocaching/filter/cgFilterBySize.java b/main/src/cgeo/geocaching/filter/cgFilterBySize.java new file mode 100644 index 0000000..1e9930c --- /dev/null +++ b/main/src/cgeo/geocaching/filter/cgFilterBySize.java @@ -0,0 +1,24 @@ +package cgeo.geocaching.filter; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.enumerations.CacheSize; + +public class cgFilterBySize extends cgFilter { + private final CacheSize size; + + public cgFilterBySize(CacheSize size) { + super(size.id); + this.size = size; + } + + @Override + boolean applyFilter(cgCache cache) { + return this.size == cache.size; + } + + @Override + public String getFilterName() { + return cgBase.cacheSizesInv.get(this.size); + } +} diff --git a/main/src/cgeo/geocaching/filter/cgFilterByTrackables.java b/main/src/cgeo/geocaching/filter/cgFilterByTrackables.java new file mode 100644 index 0000000..90cae99 --- /dev/null +++ b/main/src/cgeo/geocaching/filter/cgFilterByTrackables.java @@ -0,0 +1,14 @@ +package cgeo.geocaching.filter; + +import cgeo.geocaching.cgCache; + +public class cgFilterByTrackables extends cgFilter { + public cgFilterByTrackables(String name) { + super(name); + } + + @Override + boolean applyFilter(cgCache cache) { + return cache.hasTrackables(); + } +} diff --git a/main/src/cgeo/geocaching/filter/cgFilterByType.java b/main/src/cgeo/geocaching/filter/cgFilterByType.java new file mode 100644 index 0000000..57607a5 --- /dev/null +++ b/main/src/cgeo/geocaching/filter/cgFilterByType.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.filter; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; + +public class cgFilterByType extends cgFilter { + public cgFilterByType(String type) { + super(type); + } + + @Override + boolean applyFilter(cgCache cache) { + return name.equals(cache.type); + } + + @Override + public String getFilterName() { + return cgBase.cacheTypesInv.get(name); + } +} diff --git a/main/src/cgeo/geocaching/geopoint/DistanceParser.java b/main/src/cgeo/geocaching/geopoint/DistanceParser.java new file mode 100644 index 0000000..a48d347 --- /dev/null +++ b/main/src/cgeo/geocaching/geopoint/DistanceParser.java @@ -0,0 +1,49 @@ +package cgeo.geocaching.geopoint; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgSettings; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public final class DistanceParser { + + private static final Pattern pattern = Pattern.compile("^([0-9\\.\\,]+)[ ]*(m|km|ft|yd|mi|)?$", Pattern.CASE_INSENSITIVE); + + /** + * Parse a distance string composed by a number and an optional suffix + * (such as "1.2km"). + * + * @param distanceText + * the string to analyze + * @return the distance in kilometers + * + * @throws NumberFormatException + * if the given number is invalid + */ + public static float parseDistance(String distanceText, final int defaultUnit) { + final Matcher matcher = pattern.matcher(distanceText); + + if (!matcher.find()) { + throw new NumberFormatException(distanceText); + } + + final float value = Float.parseFloat(matcher.group(1)); + final String unit = matcher.group(2).toLowerCase(); + + if (unit.equals("m") || (unit.length() == 0 && defaultUnit == cgSettings.unitsMetric)) { + return value / 1000; + } + if (unit.equals("km")) { + return value; + } + if (unit.equals("yd")) { + return value * cgBase.yards2km; + } + if (unit.equals("mi")) { + return value * cgBase.miles2km; + } + return value * cgBase.feet2km; + } + +} diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java new file mode 100644 index 0000000..e82d547 --- /dev/null +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -0,0 +1,274 @@ +package cgeo.geocaching.geopoint; + +import android.location.Location; + +/** + * Abstraction of geographic point. + */ +public final class Geopoint +{ + public static final double deg2rad = Math.PI / 180; + public static final double rad2deg = 180 / Math.PI; + public static final float erad = 6371.0f; + + private final double latitude; + private final double longitude; + + /** + * Creates new Geopoint with given latitude and longitude (both degree). + * + * @param lat + * latitude + * @param lon + * longitude + */ + public Geopoint(final double lat, final double lon) + { + if (lat <= 90 && lat >= -90) { + latitude = lat; + } else { + throw new MalformedCoordinateException("malformed latitude: " + lat); + } + if (lon <= 180 && lon >= -180) { + longitude = lon; + } else { + throw new MalformedCoordinateException("malformed longitude: " + lon); + } + } + + /** + * Creates new Geopoint with given latitude and longitude (both microdegree). + * + * @param lat + * latitude + * @param lon + * longitude + */ + public Geopoint(final int lat, final int lon) + { + this(lat / 1e6, lon / 1e6); + } + + /** + * Creates new Geopoint with latitude and longitude parsed from string. + * + * @param text + * string to parse + * @see GeopointParser.parse() + */ + public Geopoint(final String text) + { + this(GeopointParser.parseLatitude(text), GeopointParser.parseLongitude(text)); + } + + /** + * Creates new Geopoint with given Location. + * + * @param gp + * the Location to clone + */ + public Geopoint(final Location loc) { + this(loc.getLatitude(), loc.getLongitude()); + } + + /** + * Get latitude in degree. + * + * @return latitude + */ + public double getLatitude() + { + return latitude; + } + + /** + * Get latitude in microdegree. + * + * @return latitude + */ + public int getLatitudeE6() + { + return (int) (latitude * 1E6); + } + + /** + * Get longitude in degree. + * + * @return longitude + */ + public double getLongitude() + { + return longitude; + } + + /** + * Get longitude in microdegree. + * + * @return longitude + */ + public int getLongitudeE6() + { + return (int) (longitude * 1E6); + } + + /** + * Get distance and bearing from the current point to a target. + * + * @param target + * The target + * @return An array of floats: the distance in meters, then the bearing in degrees + */ + private float[] pathTo(final Geopoint target) { + float[] results = new float[2]; + android.location.Location.distanceBetween(getLatitude(), getLongitude(), target.getLatitude(), target.getLongitude(), results); + return results; + } + + /** + * Calculates distance to given Geopoint in km. + * + * @param gp + * target + * @return distance in km + * @throws GeopointException + * if there is an error in distance calculation + */ + public float distanceTo(final Geopoint gp) + { + return pathTo(gp)[0] / 1000; + } + + /** + * Calculates bearing to given Geopoint in degree. + * + * @param gp + * target + * @return bearing in degree, in the [0,360[ range + */ + public float bearingTo(final Geopoint gp) + { + // Android library returns a bearing in the [-180;180] range + final float bearing = pathTo(gp)[1]; + return bearing < 0 ? bearing + 360 : bearing; + } + + /** + * Calculates geopoint from given bearing and distance. + * + * @param bearing + * bearing in degree + * @param distance + * distance in km + * @return the projected geopoint + */ + public Geopoint project(final double bearing, final double distance) + { + final double rlat1 = latitude * deg2rad; + final double rlon1 = longitude * deg2rad; + final double rbearing = bearing * deg2rad; + final double rdistance = distance / erad; + + final double rlat = Math.asin(Math.sin(rlat1) * Math.cos(rdistance) + Math.cos(rlat1) * Math.sin(rdistance) * Math.cos(rbearing)); + final double rlon = rlon1 + Math.atan2(Math.sin(rbearing) * Math.sin(rdistance) * Math.cos(rlat1), Math.cos(rdistance) - Math.sin(rlat1) * Math.sin(rlat)); + + return new Geopoint(rlat * rad2deg, rlon * rad2deg); + } + + /** + * Checks if given Geopoint is identical with this Geopoint. + * + * @param gp + * Geopoint to check + * @return true if identical, false otherwise + */ + public boolean isEqualTo(Geopoint gp) + { + return null != gp && gp.getLatitude() == latitude && gp.getLongitude() == longitude; + } + + /** + * Checks if given Geopoint is similar to this Geopoint with tolerance. + * + * @param gp + * Geopoint to check + * @param tolerance + * tolerance in km + * @return true if similar, false otherwise + */ + public boolean isEqualTo(Geopoint gp, double tolerance) + { + return null != gp && distanceTo(gp) <= tolerance; + } + + /** + * Returns formatted coordinates. + * + * @param format + * the desired format + * @see GeopointFormatter + * @return formatted coordinates + */ + public String format(GeopointFormatter format) + { + return format.format(this); + } + + /** + * Returns formatted coordinates. + * + * @param format + * the desired format + * @see GeopointFormatter + * @return formatted coordinates + */ + public String format(String format) + { + return GeopointFormatter.format(format, this); + } + + /** + * Returns formatted coordinates. + * + * @param format + * the desired format + * @see GeopointFormatter + * @return formatted coordinates + */ + public String format(GeopointFormatter.Format format) + { + return GeopointFormatter.format(format, this); + } + + /** + * Returns formatted coordinates with default format. + * Default format is decimalminutes, e.g. N 52° 36.123 E 010° 03.456 + * + * @return formatted coordinates + */ + public String toString() + { + return format(GeopointFormatter.Format.LAT_LON_DECMINUTE); + } + + public static class GeopointException + extends RuntimeException + { + private static final long serialVersionUID = 1L; + + public GeopointException(String msg) + { + super(msg); + } + } + + public static class MalformedCoordinateException + extends GeopointException + { + private static final long serialVersionUID = 1L; + + public MalformedCoordinateException(String msg) + { + super(msg); + } + } +} diff --git a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java new file mode 100644 index 0000000..e37cbed --- /dev/null +++ b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java @@ -0,0 +1,219 @@ +package cgeo.geocaching.geopoint; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Formatting of Geopoint. + */ +public class GeopointFormatter +{ + private static final Pattern pattern = Pattern.compile("%([yx])(\\d)?([ndms])"); + private final String format; + private final Format enumFormat; + + /** + * A few default formats. They can be parsed hardcoded, so the use of them + * can improve the performance. + */ + public enum Format + { + LAT_LON_DECDEGREE("%y6d %x6d"), + LAT_LON_DECMINUTE("%yn %yd° %y3m %xn %xd° %x3m"), + LAT_LON_DECSECOND("%yn %yd° %ym' %ys\" %xn %xd° %xm' %xs\""), + LAT_DECDEGREE("%y6d"), + LAT_DECMINUTE("%yn %yd° %y3m"), + LAT_DECSECOND("%yn %yd° %ym' %ys\""), + LON_DECDEGREE("%x6d"), + LON_DECMINUTE("%xn %xd° %x3m"), + LON_DECSECOND("%xn %xd° %xm' %xs\""); + + private final String format; + + Format(String format) + { + this.format = format; + } + + public String toString() + { + return format; + } + } + + /** + * Creates a new formatter with given format-string. + * + * @param format + * the format-string + * @see format() + */ + public GeopointFormatter(final String format) + { + enumFormat = null; + this.format = format; + } + + /** + * Creates a new formatter with given default-format. + * + * @param format + * one of the default formats + * @see GeopointFormatter.Format + */ + public GeopointFormatter(final Format format) + { + enumFormat = format; + this.format = format.toString(); + } + + /** + * Formats a Geopoint. + * + * Syntax: + * %[dir][precision][value] + * + * [dir] + * y = latitude + * x = longitude + * + * [precision] (optional) + * 0..9, number of digits after the decimal point + * + * [value] + * n = direction + * d = degree + * m = minute + * s = second + * + * Example: + * "%yn %yd° %y3m" = "N 52° 36.123" + * + * All other characters are not interpreted and can be used. + * + * @param gp + * the Geopoint to format + * @param format + * the format-string with syntax from above + * @return the formatted coordinates + */ + public static String format(final String format, final Geopoint gp) + { + final Matcher matcher = pattern.matcher(format); + final StringBuffer formattedResult = new StringBuffer(); + + while (matcher.find()) + { + StringBuilder replacement = new StringBuilder(); + + final double coord = (matcher.group(1).equals("y")) ? gp.getLatitude() : gp.getLongitude(); + + if (matcher.group(3).equals("n")) + { + if (matcher.group(1).equals("y")) + { + replacement.append((coord < 0) ? "S" : "N"); + } + else + { + replacement.append((coord < 0) ? "W" : "E"); + } + } + else if (matcher.group(3).equals("d")) + { + if (null == matcher.group(2)) + { + replacement.append(String.format("%0" + ((matcher.group(1).equals("y")) ? "2." : "3.") + "0f", Math.floor(Math.abs(coord)))); + } + else + { + replacement.append(String.format("%0" + ((matcher.group(1).equals("y")) ? "2." : "3.") + Integer.parseInt(matcher.group(2)) + "f", coord)); + } + } + else if (matcher.group(3).equals("m")) + { + final double value = Math.abs(coord); + final double minutes = (value - Math.floor(value)) * 60; + replacement.append(String.format("%02." + ((null == matcher.group(2)) ? 0 : Integer.parseInt(matcher.group(2))) + "f", (null == matcher.group(2)) ? Math.floor(minutes) : minutes)); + } + else if (matcher.group(3).equals("s")) + { + final double value = Math.abs(coord); + final double minutes = (value - Math.floor(value)) * 60; + replacement.append(String.format("%02." + ((null == matcher.group(2)) ? 0 : Integer.parseInt(matcher.group(2))) + "f", (minutes - Math.floor(minutes)) * 60)); + } + + matcher.appendReplacement(formattedResult, replacement.toString()); + } + + matcher.appendTail(formattedResult); + + return formattedResult.toString(); + } + + /** + * Formats a Geopoint. + * + * @param gp + * the Geopoint to format + * @param format + * one of the default formats + * @see cgeo.geocaching.GeopointFormatter.Format + * @return the formatted coordinates + */ + public static String format(final Format format, final Geopoint gp) + { + // Don't parse often used formats + + switch (format) + { + case LAT_LON_DECDEGREE: + return String.format("%.6f %.6f", gp.getLatitude(), gp.getLongitude()); + + case LAT_LON_DECMINUTE: + final double lat = Math.abs(gp.getLatitude()); + final double lon = Math.abs(gp.getLongitude()); + final boolean latPos = (gp.getLatitude() < 0); + final boolean lonPos = (gp.getLongitude() < 0); + + return String.format("%s %02.0f° %.3f %s %03.0f° %.3f", (latPos) ? "S" : "N", + Math.floor(lat), + (lat - Math.floor(lat)) * 60, + (lonPos) ? "W" : "E", + Math.floor(lon), + (lon - Math.floor(lon)) * 60); + + default: + return format(format.toString(), gp); + } + } + + /** + * Formats a Geopoint with the format of this instance. + * + * @param gp + * the Geopoint to format + * @return the formatted coordinates of the Geopoint + */ + public String format(final Geopoint gp) + { + if (null == enumFormat) + { + return format(format, gp); + } + else + { + return format(enumFormat, gp); + } + } + + /** + * Returns the format of this instance. + * + * @return the format of this instance. + */ + public String toString() + { + return format; + } +} diff --git a/main/src/cgeo/geocaching/geopoint/GeopointParser.java b/main/src/cgeo/geocaching/geopoint/GeopointParser.java new file mode 100644 index 0000000..aef054e --- /dev/null +++ b/main/src/cgeo/geocaching/geopoint/GeopointParser.java @@ -0,0 +1,173 @@ +package cgeo.geocaching.geopoint; + +import cgeo.geocaching.R; +import cgeo.geocaching.geopoint.Geopoint.GeopointException; + +import org.apache.commons.lang3.StringUtils; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Parse coordinates. + */ +public class GeopointParser +{ + // ( 1 ) ( 2 ) ( 3 ) ( 4 ) ( 5 ) + private static final Pattern patternLat = Pattern.compile("([NS])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[\\.,](\\d+)|'?\\s*(\\d+(?:[\\.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); + private static final Pattern patternLon = Pattern.compile("([WE])\\s*(\\d+)°?(?:\\s*(\\d+)(?:[\\.,](\\d+)|'?\\s*(\\d+(?:[\\.,]\\d+)?)(?:''|\")?)?)?", Pattern.CASE_INSENSITIVE); + + private enum LatLon + { + LAT, + LON + } + + /** + * Parses a pair of coordinates (latitude and longitude) out of a String. + * Accepts following formats and combinations of it: + * X DD + * X DD° + * X DD° MM + * X DD° MM.MMM + * X DD° MM SS + * + * as well as: + * DD.DDDDDDD + * + * Both . and , are accepted, also variable count of spaces (also 0) + * + * @param text + * the string to parse + * @return an Geopoint with parsed latitude and longitude + * @throws ParseException + * if lat or lon could not be parsed + */ + public static Geopoint parse(final String text) + { + + double lat = parseLatitude(text); + double lon = parseLongitude(text); + + return new Geopoint(lat, lon); + } + + /** + * Parses a pair of coordinates (latitude and longitude) out of a String. + * Accepts following formats and combinations of it: + * X DD + * X DD° + * X DD° MM + * X DD° MM.MMM + * X DD° MM SS + * + * as well as: + * DD.DDDDDDD + * + * Both . and , are accepted, also variable count of spaces (also 0) + * + * @param latitude + * the latitude string to parse + * @param longitude + * the longitude string to parse + * @return an Geopoint with parsed latitude and longitude + * @throws ParseException + * if lat or lon could not be parsed + */ + public static Geopoint parse(final String latitude, final String longitude) + { + final double lat = parseLatitude(latitude); + final double lon = parseLongitude(longitude); + + return new Geopoint(lat, lon); + } + + /* + * (non JavaDoc) + * Helper for coordinates-parsing. + */ + private static double parseHelper(final String text, final LatLon latlon) + { + + final Pattern pattern = LatLon.LAT == latlon ? patternLat : patternLon; + final Matcher matcher = pattern.matcher(text); + + if (matcher.find()) { + final int sign = matcher.group(1).equalsIgnoreCase("S") || matcher.group(1).equalsIgnoreCase("W") ? -1 : 1; + final int degree = Integer.parseInt(matcher.group(2)); + + int minutes = 0; + double seconds = 0; + + if (null != matcher.group(3)) { + minutes = Integer.parseInt(matcher.group(3)); + + if (null != matcher.group(4)) { + seconds = Double.parseDouble("0." + matcher.group(4)) * 60; + } else if (null != matcher.group(5)) { + seconds = Double.parseDouble(matcher.group(5)); + } + } + + return (double) sign * ((double) degree + (double) minutes / 60 + seconds / 3600); + + } else { + + // Nothing found with "N 52...", try to match string as decimaldegree + try { + final String[] items = StringUtils.split(text.trim()); + if (items.length > 0) { + final int index = latlon == LatLon.LON ? items.length - 1 : 0; + return Double.parseDouble(items[index]); + } + } catch (NumberFormatException e) { + // The right exception will be raised below. + } + } + + throw new ParseException("Could not parse coordinates as " + latlon + ": \"" + text + "\"", latlon); + } + + /** + * Parses latitude out of a given string. + * + * @see parse() + * @param text + * the string to be parsed + * @return the latitude as decimaldegree + * @throws ParseException + * if latitude could not be parsed + */ + public static double parseLatitude(final String text) + { + return parseHelper(text, LatLon.LAT); + } + + /** + * Parses longitude out of a given string. + * + * @see parse() + * @param text + * the string to be parsed + * @return the longitude as decimaldegree + * @throws ParseException + * if longitude could not be parsed + */ + public static double parseLongitude(final String text) + { + return parseHelper(text, LatLon.LON); + } + + public static class ParseException + extends GeopointException + { + private static final long serialVersionUID = 1L; + public final int resource; + + public ParseException(final String msg, final LatLon faulty) + { + super(msg); + resource = faulty == LatLon.LAT ? R.string.err_parse_lat : R.string.err_parse_lon; + } + } +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleCacheOverlay.java b/main/src/cgeo/geocaching/googlemaps/googleCacheOverlay.java new file mode 100644 index 0000000..bcf9733 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleCacheOverlay.java @@ -0,0 +1,116 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapOverlay; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; + +import com.google.android.maps.ItemizedOverlay; +import com.google.android.maps.MapView; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Google specific implementation of the itemized cache overlay + * + * @author rsudev + * + */ +public class googleCacheOverlay extends ItemizedOverlay<googleCacheOverlayItem> implements ItemizedOverlayImpl { + + private cgMapOverlay base; + private Lock lock = new ReentrantLock(); + + public googleCacheOverlay(cgSettings settingsIn, Context contextIn, Drawable markerIn, Boolean fromDetailIn) { + super(boundCenterBottom(markerIn)); + base = new cgMapOverlay(settingsIn, this, contextIn, fromDetailIn); + } + + @Override + public cgMapOverlay getBase() { + return base; + } + + @Override + protected googleCacheOverlayItem createItem(int i) { + if (base == null) + return null; + + return (googleCacheOverlayItem) base.createItem(i); + } + + @Override + public int size() { + if (base == null) + return 0; + + return base.size(); + } + + @Override + protected boolean onTap(int arg0) { + if (base == null) + return false; + + return base.onTap(arg0); + } + + @Override + public void draw(Canvas canvas, MapView mapView, boolean shadow) { + base.draw(canvas, (MapViewImpl) mapView, shadow); + } + + @Override + public void superPopulate() { + populate(); + } + + @Override + public Drawable superBoundCenter(Drawable markerIn) { + return super.boundCenter(markerIn); + } + + @Override + public Drawable superBoundCenterBottom(Drawable marker) { + return super.boundCenterBottom(marker); + } + + @Override + public void superSetLastFocusedItemIndex(int i) { + super.setLastFocusedIndex(i); + } + + @Override + public boolean superOnTap(int index) { + return super.onTap(index); + } + + @Override + public void superDraw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + super.draw(canvas, (MapView) mapView, shadow); + } + + @Override + public void superDrawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + // Nothing to do here... + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleCacheOverlayItem.java b/main/src/cgeo/geocaching/googlemaps/googleCacheOverlayItem.java new file mode 100644 index 0000000..4bc7671 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleCacheOverlayItem.java @@ -0,0 +1,28 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; + +import com.google.android.maps.GeoPoint; +import com.google.android.maps.OverlayItem; + +public class googleCacheOverlayItem extends OverlayItem implements CacheOverlayItemImpl { + private String cacheType = null; + private cgCoord coord; + + public googleCacheOverlayItem(cgCoord coordinate, String type) { + super(new GeoPoint(coordinate.coords.getLatitudeE6(), coordinate.coords.getLongitudeE6()), coordinate.name, ""); + + this.cacheType = type; + this.coord = coordinate; + } + + public cgCoord getCoord() { + return coord; + } + + public String getType() { + return cacheType; + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleGeoPoint.java b/main/src/cgeo/geocaching/googlemaps/googleGeoPoint.java new file mode 100644 index 0000000..2c72d60 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleGeoPoint.java @@ -0,0 +1,13 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; + +import com.google.android.maps.GeoPoint; + +public class googleGeoPoint extends GeoPoint implements GeoPointImpl { + + public googleGeoPoint(int latitudeE6, int longitudeE6) { + super(latitudeE6, longitudeE6); + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleMapActivity.java b/main/src/cgeo/geocaching/googlemaps/googleMapActivity.java new file mode 100644 index 0000000..6c60a9a --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleMapActivity.java @@ -0,0 +1,123 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.mapcommon.MapBase; +import cgeo.geocaching.mapcommon.cgeomap; +import cgeo.geocaching.mapinterfaces.ActivityImpl; + +import com.google.android.maps.MapActivity; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +public class googleMapActivity extends MapActivity implements ActivityImpl { + + private MapBase mapBase; + + public googleMapActivity() { + mapBase = new cgeomap(this); + } + + @Override + protected boolean isRouteDisplayed() { + return false; + } + + @Override + public Activity getActivity() { + return this; + } + + @Override + protected void onCreate(Bundle icicle) { + mapBase.onCreate(icicle); + } + + @Override + protected void onDestroy() { + mapBase.onDestroy(); + } + + @Override + protected void onPause() { + mapBase.onPause(); + } + + @Override + protected void onResume() { + mapBase.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return mapBase.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return mapBase.onOptionsItemSelected(item); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return mapBase.onPrepareOptionsMenu(menu); + } + + @Override + protected void onStop() { + mapBase.onStop(); + } + + @Override + public void superOnCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public boolean superOnCreateOptionsMenu(Menu menu) { + return super.onCreateOptionsMenu(menu); + } + + @Override + public void superOnDestroy() { + super.onDestroy(); + } + + @Override + public boolean superOnOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } + + @Override + public void superOnResume() { + super.onResume(); + } + + @Override + public void superOnStop() { + super.onStop(); + } + + @Override + public void superOnPause() { + super.onPause(); + } + + @Override + public boolean superOnPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } + + // close activity and open homescreen + public void goHome(View view) { + mapBase.goHome(view); + } + + // open manual entry + public void goManual(View view) { + mapBase.goManual(view); + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleMapController.java b/main/src/cgeo/geocaching/googlemaps/googleMapController.java new file mode 100644 index 0000000..fd413f8 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleMapController.java @@ -0,0 +1,37 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapControllerImpl; + +import com.google.android.maps.GeoPoint; +import com.google.android.maps.MapController; + +public class googleMapController implements MapControllerImpl { + + private MapController mapController; + + public googleMapController(MapController mapControllerIn) { + mapController = mapControllerIn; + } + + @Override + public void animateTo(GeoPointImpl geoPoint) { + mapController.animateTo((GeoPoint) geoPoint); + } + + @Override + public void setCenter(GeoPointImpl geoPoint) { + mapController.setCenter((GeoPoint) geoPoint); + } + + @Override + public void setZoom(int mapzoom) { + mapController.setZoom(mapzoom); + } + + @Override + public void zoomToSpan(int latSpanE6, int lonSpanE6) { + mapController.zoomToSpan(latSpanE6, lonSpanE6); + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleMapFactory.java b/main/src/cgeo/geocaching/googlemaps/googleMapFactory.java new file mode 100644 index 0000000..890f638 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleMapFactory.java @@ -0,0 +1,50 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import com.google.android.maps.MapActivity; + +import android.content.Context; + +public class googleMapFactory implements MapFactory { + + @Override + public Class<? extends MapActivity> getMapClass() { + return googleMapActivity.class; + } + + @Override + public int getMapViewId() { + return R.id.map; + } + + @Override + public int getMapLayoutId() { + return R.layout.googlemap; + } + + @Override + public GeoPointImpl getGeoPointBase(final Geopoint coords) { + return new googleGeoPoint(coords.getLatitudeE6(), coords.getLongitudeE6()); + } + + @Override + public CacheOverlayItemImpl getCacheOverlayItem(cgCoord coordinate, String type) { + googleCacheOverlayItem baseItem = new googleCacheOverlayItem(coordinate, type); + return baseItem; + } + + @Override + public UserOverlayItemImpl getUserOverlayItemBase(Context context, cgUser userOne) { + googleUsersOverlayItem baseItem = new googleUsersOverlayItem(context, userOne); + return baseItem; + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleMapProjection.java b/main/src/cgeo/geocaching/googlemaps/googleMapProjection.java new file mode 100644 index 0000000..ea4b97c --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleMapProjection.java @@ -0,0 +1,29 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; + +import com.google.android.maps.GeoPoint; +import com.google.android.maps.Projection; + +import android.graphics.Point; + +public class googleMapProjection implements MapProjectionImpl { + + private Projection projection; + + public googleMapProjection(Projection projectionIn) { + projection = projectionIn; + } + + @Override + public void toPixels(GeoPointImpl leftGeo, Point left) { + projection.toPixels((GeoPoint) leftGeo, left); + } + + @Override + public Object getImpl() { + return projection; + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleMapView.java b/main/src/cgeo/geocaching/googlemaps/googleMapView.java new file mode 100644 index 0000000..57defbe --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleMapView.java @@ -0,0 +1,194 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapMyOverlay; +import cgeo.geocaching.mapcommon.cgMapOverlay; +import cgeo.geocaching.mapcommon.cgOverlayScale; +import cgeo.geocaching.mapcommon.cgUsersOverlay; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapControllerImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OnDragListener; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; +import cgeo.geocaching.mapinterfaces.OverlayImpl.overlayType; + +import com.google.android.maps.GeoPoint; +import com.google.android.maps.MapView; +import com.google.android.maps.Overlay; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + +public class googleMapView extends MapView implements MapViewImpl { + private GestureDetector gestureDetector; + private OnDragListener onDragListener; + + public googleMapView(Context context, AttributeSet attrs) { + super(context, attrs); + gestureDetector = new GestureDetector(context, new GestureListener()); + } + + public googleMapView(Context context, AttributeSet attrs, int defStyle) { + super(context, attrs, defStyle); + gestureDetector = new GestureDetector(context, new GestureListener()); + } + + public googleMapView(Context context, String apiKey) { + super(context, apiKey); + gestureDetector = new GestureDetector(context, new GestureListener()); + } + + @Override + public void draw(Canvas canvas) { + try { + if (getMapZoomLevel() >= 22) { // to avoid too close zoom level (mostly on Samsung Galaxy S series) + getController().setZoom(22); + } + + super.draw(canvas); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapView.draw: " + e.toString()); + } + } + + @Override + public void displayZoomControls(boolean takeFocus) { + try { + super.displayZoomControls(takeFocus); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapView.displayZoomControls: " + e.toString()); + } + } + + @Override + public MapControllerImpl getMapController() { + return new googleMapController(getController()); + } + + @Override + public GeoPointImpl getMapViewCenter() { + GeoPoint point = getMapCenter(); + return new googleGeoPoint(point.getLatitudeE6(), point.getLongitudeE6()); + } + + @Override + public void addOverlay(OverlayImpl ovl) { + getOverlays().add((Overlay) ovl); + } + + @Override + public void clearOverlays() { + getOverlays().clear(); + } + + @Override + public MapProjectionImpl getMapProjection() { + return new googleMapProjection(getProjection()); + } + + @Override + public cgMapOverlay createAddMapOverlay(cgSettings settings, + Context context, Drawable drawable, boolean fromDetailIntent) { + + googleCacheOverlay ovl = new googleCacheOverlay(settings, context, drawable, fromDetailIntent); + getOverlays().add(ovl); + return ovl.getBase(); + } + + @Override + public cgUsersOverlay createAddUsersOverlay(Context context, Drawable markerIn) { + googleUsersOverlay ovl = new googleUsersOverlay(context, markerIn); + getOverlays().add(ovl); + return ovl.getBase(); + } + + @Override + public cgMapMyOverlay createAddPositionOverlay(Activity activity, + cgSettings settingsIn) { + + googleOverlay ovl = new googleOverlay(activity, settingsIn, overlayType.PositionOverlay); + getOverlays().add(ovl); + return (cgMapMyOverlay) ovl.getBase(); + } + + @Override + public cgOverlayScale createAddScaleOverlay(Activity activity, + cgSettings settingsIn) { + + googleOverlay ovl = new googleOverlay(activity, settingsIn, overlayType.ScaleOverlay); + getOverlays().add(ovl); + return (cgOverlayScale) ovl.getBase(); + } + + @Override + public int getMapZoomLevel() { + return getZoomLevel(); + } + + @Override + public void setMapSource(cgSettings settings) { + + switch (settings.mapSource) { + case googleSat: + setSatellite(true); + break; + default: + setSatellite(false); + } + } + + @Override + public boolean needsScaleOverlay() { + return true; + } + + @Override + public void setBuiltinScale(boolean b) { + //Nothing to do for google maps... + } + + @Override + public void repaintRequired(OverlayBase overlay) { + invalidate(); + } + + @Override + public void setOnDragListener(OnDragListener onDragListener) { + this.onDragListener = onDragListener; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + gestureDetector.onTouchEvent(ev); + return super.onTouchEvent(ev); + } + + private class GestureListener extends SimpleOnGestureListener { + @Override + public boolean onDoubleTap(MotionEvent e) { + getController().zoomInFixing((int) e.getX(), (int) e.getY()); + if (onDragListener != null) { + onDragListener.onDrag(); + } + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + if (onDragListener != null) { + onDragListener.onDrag(); + } + return super.onScroll(e1, e2, distanceX, distanceY); + } + } +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleOverlay.java b/main/src/cgeo/geocaching/googlemaps/googleOverlay.java new file mode 100644 index 0000000..b47824e --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleOverlay.java @@ -0,0 +1,56 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapMyOverlay; +import cgeo.geocaching.mapcommon.cgOverlayScale; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; + +import com.google.android.maps.MapView; +import com.google.android.maps.Overlay; + +import android.app.Activity; +import android.graphics.Canvas; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class googleOverlay extends Overlay implements OverlayImpl { + + private OverlayBase overlayBase = null; + private Lock lock = new ReentrantLock(); + + public googleOverlay(Activity activityIn, cgSettings settingsIn, overlayType ovlType) { + switch (ovlType) { + case PositionOverlay: + overlayBase = new cgMapMyOverlay(settingsIn, activityIn, this); + break; + case ScaleOverlay: + overlayBase = new cgOverlayScale(activityIn, settingsIn, this); + } + } + + @Override + public void draw(Canvas canvas, MapView mapView, boolean shadow) { + super.draw(canvas, mapView, shadow); + + if (overlayBase != null) { + overlayBase.draw(canvas, (MapViewImpl) mapView, shadow); + } + } + + public OverlayBase getBase() { + return overlayBase; + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleUsersOverlay.java b/main/src/cgeo/geocaching/googlemaps/googleUsersOverlay.java new file mode 100644 index 0000000..43b2a3c --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleUsersOverlay.java @@ -0,0 +1,109 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.mapcommon.cgUsersOverlay; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; + +import com.google.android.maps.ItemizedOverlay; +import com.google.android.maps.MapView; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class googleUsersOverlay extends ItemizedOverlay<googleUsersOverlayItem> implements ItemizedOverlayImpl { + + private cgUsersOverlay base; + private Lock lock = new ReentrantLock(); + + public googleUsersOverlay(Context contextIn, Drawable markerIn) { + super(boundCenter(markerIn)); + base = new cgUsersOverlay(this, contextIn); + } + + @Override + public cgUsersOverlay getBase() { + return base; + } + + @Override + protected googleUsersOverlayItem createItem(int i) { + if (base == null) + return null; + + return (googleUsersOverlayItem) base.createItem(i); + } + + @Override + public int size() { + if (base == null) + return 0; + + return base.size(); + } + + @Override + protected boolean onTap(int arg0) { + if (base == null) + return false; + + return base.onTap(arg0); + } + + @Override + public void draw(Canvas canvas, MapView mapView, boolean shadow) { + base.draw(canvas, (MapViewImpl) mapView, shadow); + } + + @Override + public void superPopulate() { + populate(); + } + + @Override + public Drawable superBoundCenter(Drawable markerIn) { + return super.boundCenter(markerIn); + } + + @Override + public Drawable superBoundCenterBottom(Drawable marker) { + return super.boundCenterBottom(marker); + } + + @Override + public void superSetLastFocusedItemIndex(int i) { + super.setLastFocusedIndex(i); + } + + @Override + public boolean superOnTap(int index) { + return super.onTap(index); + } + + @Override + public void superDraw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + super.draw(canvas, (MapView) mapView, shadow); + } + + @Override + public void superDrawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + // Nothing to do here + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +} diff --git a/main/src/cgeo/geocaching/googlemaps/googleUsersOverlayItem.java b/main/src/cgeo/geocaching/googlemaps/googleUsersOverlayItem.java new file mode 100644 index 0000000..915f291 --- /dev/null +++ b/main/src/cgeo/geocaching/googlemaps/googleUsersOverlayItem.java @@ -0,0 +1,44 @@ +package cgeo.geocaching.googlemaps; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import com.google.android.maps.GeoPoint; +import com.google.android.maps.OverlayItem; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +public class googleUsersOverlayItem extends OverlayItem implements UserOverlayItemImpl { + private Context context = null; + private cgUser user = null; + + public googleUsersOverlayItem(Context contextIn, cgUser userIn) { + super(new GeoPoint(userIn.coords.getLatitudeE6(), userIn.coords.getLongitudeE6()), userIn.username, ""); + + context = contextIn; + user = userIn; + } + + @Override + public Drawable getMarker(int state) { + Drawable marker = null; + + if (user != null && user.located != null && user.located.getTime() >= (System.currentTimeMillis() - (20 * 60 * 1000))) { + marker = context.getResources().getDrawable(R.drawable.user_location_active); + } else { + marker = context.getResources().getDrawable(R.drawable.user_location); + } + + marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight()); + marker.setAlpha(190); + setMarker(marker); + + return marker; + } + + public cgUser getUser() { + return user; + } +} diff --git a/main/src/cgeo/geocaching/mapcommon/ItemizedOverlayBase.java b/main/src/cgeo/geocaching/mapcommon/ItemizedOverlayBase.java new file mode 100644 index 0000000..355e78c --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/ItemizedOverlayBase.java @@ -0,0 +1,66 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; +import cgeo.geocaching.mapinterfaces.OverlayItemImpl; + +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +/** + * Base class for itemized overlays. Delegates calls from deriving classes to the contained + * provider-specific implementation. + * + * @author rsudev + * + */ +public abstract class ItemizedOverlayBase implements OverlayBase { + + private ItemizedOverlayImpl ovlImpl; + + protected ItemizedOverlayBase(ItemizedOverlayImpl ovlImplIn) { + ovlImpl = ovlImplIn; + } + + void populate() { + ovlImpl.superPopulate(); + } + + public boolean onTap(int index) { + return ovlImpl.superOnTap(index); + } + + Drawable boundCenter(Drawable markerIn) { + return ovlImpl.superBoundCenter(markerIn); + } + + Drawable boundCenterBottom(Drawable markerIn) { + return ovlImpl.superBoundCenterBottom(markerIn); + } + + void setLastFocusedItemIndex(int index) { + ovlImpl.superSetLastFocusedItemIndex(index); + } + + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + ovlImpl.superDraw(canvas, mapView, shadow); + } + + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + ovlImpl.superDrawOverlayBitmap(canvas, drawPosition, projection, drawZoomLevel); + } + + @Override + public OverlayImpl getOverlayImpl() { + return ovlImpl; + } + + public abstract OverlayItemImpl createItem(int index); + + public abstract int size(); +} diff --git a/main/src/cgeo/geocaching/mapcommon/MapBase.java b/main/src/cgeo/geocaching/mapcommon/MapBase.java new file mode 100644 index 0000000..830c660 --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/MapBase.java @@ -0,0 +1,71 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.mapinterfaces.ActivityImpl; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +/** + * Base class for the map activity. Delegates base class calls to the + * provider-specific implementation. + * + * @author rsudev + * + */ +public abstract class MapBase { + + ActivityImpl mapActivity; + + protected MapBase(ActivityImpl activity) { + mapActivity = activity; + } + + public Resources getResources() { + return mapActivity.getResources(); + } + + public Activity getActivity() { + return mapActivity.getActivity(); + } + + public void onCreate(Bundle savedInstanceState) { + mapActivity.superOnCreate(savedInstanceState); + } + + public void onResume() { + mapActivity.superOnResume(); + } + + public void onStop() { + mapActivity.superOnStop(); + } + + public void onPause() { + mapActivity.superOnPause(); + } + + public void onDestroy() { + mapActivity.superOnDestroy(); + } + + public boolean onCreateOptionsMenu(Menu menu) { + return mapActivity.superOnCreateOptionsMenu(menu); + } + + public boolean onPrepareOptionsMenu(Menu menu) { + return mapActivity.superOnPrepareOptionsMenu(menu); + } + + public boolean onOptionsItemSelected(MenuItem item) { + return mapActivity.superOnOptionsItemSelected(item); + } + + public abstract void goHome(View view); + + public abstract void goManual(View view); + +} diff --git a/main/src/cgeo/geocaching/mapcommon/cgMapMyOverlay.java b/main/src/cgeo/geocaching/mapcommon/cgMapMyOverlay.java new file mode 100644 index 0000000..dba8ba6 --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/cgMapMyOverlay.java @@ -0,0 +1,224 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; + +import android.app.Activity; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.Canvas; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Point; +import android.location.Location; + +import java.util.ArrayList; +import java.util.List; + +public class cgMapMyOverlay implements OverlayBase { + private cgSettings settings = null; + private Location coordinates = null; + private GeoPointImpl location = null; + private Float heading = 0f; + private Paint accuracyCircle = null; + private Paint historyLine = null; + private Paint historyLineShadow = null; + private Point center = new Point(); + private Point left = new Point(); + private Bitmap arrow = null; + private int widthArrowHalf = 0; + private int heightArrowHalf = 0; + private PaintFlagsDrawFilter setfil = null; + private PaintFlagsDrawFilter remfil = null; + private Location historyRecent = null; + private List<Location> history = new ArrayList<Location>(); + private Point historyPointN = new Point(); + private Point historyPointP = new Point(); + private Activity activity; + private MapFactory mapFactory = null; + private OverlayImpl ovlImpl = null; + + public cgMapMyOverlay(cgSettings settingsIn, Activity activity, OverlayImpl ovlImpl) { + settings = settingsIn; + this.activity = activity; + this.mapFactory = settings.getMapFactory(); + this.ovlImpl = ovlImpl; + } + + public void setCoordinates(Location coordinatesIn) { + coordinates = coordinatesIn; + location = settings.getMapFactory().getGeoPointBase(new Geopoint(coordinates)); + } + + public void setHeading(Float bearingNow) { + heading = bearingNow; + } + + @Override + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + + drawInternal(canvas, projection); + } + + @Override + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + + drawInternal(canvas, mapView.getMapProjection()); + } + + private void drawInternal(Canvas canvas, MapProjectionImpl projection) { + + if (coordinates == null || location == null) + return; + + if (accuracyCircle == null) { + accuracyCircle = new Paint(); + accuracyCircle.setAntiAlias(true); + accuracyCircle.setStrokeWidth(1.0f); + } + + if (historyLine == null) { + historyLine = new Paint(); + historyLine.setAntiAlias(true); + historyLine.setStrokeWidth(3.0f); + historyLine.setColor(0xFFFFFFFF); + } + + if (historyLineShadow == null) { + historyLineShadow = new Paint(); + historyLineShadow.setAntiAlias(true); + historyLineShadow.setStrokeWidth(7.0f); + historyLineShadow.setColor(0x66000000); + } + + if (setfil == null) + setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); + if (remfil == null) + remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); + + canvas.setDrawFilter(setfil); + + double latitude = coordinates.getLatitude(); + double longitude = coordinates.getLongitude(); + float accuracy = coordinates.getAccuracy(); + + float[] result = new float[1]; + + Location.distanceBetween(latitude, longitude, latitude, longitude + 1, result); + float longitudeLineDistance = result[0]; + + final Geopoint leftCoords = new Geopoint(latitude, longitude - accuracy / longitudeLineDistance); + GeoPointImpl leftGeo = mapFactory.getGeoPointBase(leftCoords); + projection.toPixels(leftGeo, left); + projection.toPixels(location, center); + int radius = center.x - left.x; + + accuracyCircle.setColor(0x66000000); + accuracyCircle.setStyle(Style.STROKE); + canvas.drawCircle(center.x, center.y, radius, accuracyCircle); + + accuracyCircle.setColor(0x08000000); + accuracyCircle.setStyle(Style.FILL); + canvas.drawCircle(center.x, center.y, radius, accuracyCircle); + + if (coordinates.getAccuracy() < 50f && ((historyRecent != null && historyRecent.distanceTo(coordinates) > 5.0) || historyRecent == null)) { + if (historyRecent != null) + history.add(historyRecent); + historyRecent = coordinates; + + int toRemove = history.size() - 700; + + if (toRemove > 0) { + for (int cnt = 0; cnt < toRemove; cnt++) { + history.remove(cnt); + } + } + } + + if (settings.maptrail == 1) { + int size = history.size(); + if (size > 1) { + int alpha = 0; + int alphaCnt = size - 201; + if (alphaCnt < 1) + alphaCnt = 1; + + for (int cnt = 1; cnt < size; cnt++) { + Location prev = history.get(cnt - 1); + Location now = history.get(cnt); + + if (prev != null && now != null) { + projection.toPixels(mapFactory.getGeoPointBase(new Geopoint(prev)), historyPointP); + projection.toPixels(mapFactory.getGeoPointBase(new Geopoint(now)), historyPointN); + + if ((alphaCnt - cnt) > 0) { + alpha = 255 / (alphaCnt - cnt); + } + else { + alpha = 255; + } + + historyLineShadow.setAlpha(alpha); + historyLine.setAlpha(alpha); + + canvas.drawLine(historyPointP.x, historyPointP.y, historyPointN.x, historyPointN.y, historyLineShadow); + canvas.drawLine(historyPointP.x, historyPointP.y, historyPointN.x, historyPointN.y, historyLine); + } + } + } + + if (size > 0) { + Location prev = history.get(size - 1); + Location now = coordinates; + + if (prev != null && now != null) { + projection.toPixels(mapFactory.getGeoPointBase(new Geopoint(prev)), historyPointP); + projection.toPixels(mapFactory.getGeoPointBase(new Geopoint(now)), historyPointN); + + historyLineShadow.setAlpha(255); + historyLine.setAlpha(255); + + canvas.drawLine(historyPointP.x, historyPointP.y, historyPointN.x, historyPointN.y, historyLineShadow); + canvas.drawLine(historyPointP.x, historyPointP.y, historyPointN.x, historyPointN.y, historyLine); + } + } + } + + if (arrow == null) { + arrow = BitmapFactory.decodeResource(activity.getResources(), R.drawable.my_location_chevron); + widthArrowHalf = arrow.getWidth() / 2; + heightArrowHalf = arrow.getHeight() / 2; + } + + int marginLeft; + int marginTop; + + marginLeft = center.x - widthArrowHalf; + marginTop = center.y - heightArrowHalf; + + Matrix matrix = new Matrix(); + matrix.setRotate(heading.floatValue(), widthArrowHalf, heightArrowHalf); + matrix.postTranslate(marginLeft, marginTop); + + canvas.drawBitmap(arrow, matrix, null); + + canvas.setDrawFilter(remfil); + + //super.draw(canvas, mapView, shadow); + } + + @Override + public OverlayImpl getOverlayImpl() { + return this.ovlImpl; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/mapcommon/cgMapOverlay.java b/main/src/cgeo/geocaching/mapcommon/cgMapOverlay.java new file mode 100644 index 0000000..1321fd2 --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/cgMapOverlay.java @@ -0,0 +1,359 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgeodetail; +import cgeo.geocaching.cgeonavigate; +import cgeo.geocaching.cgeopopup; +import cgeo.geocaching.cgeowaypoint; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; + +import org.apache.commons.lang3.StringUtils; + +import android.app.AlertDialog; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.DashPathEffect; +import android.graphics.Paint; +import android.graphics.Paint.Style; +import android.graphics.PaintFlagsDrawFilter; +import android.graphics.Point; +import android.location.Location; +import android.text.Html; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; + +public class cgMapOverlay extends ItemizedOverlayBase implements OverlayBase { + + private List<CacheOverlayItemImpl> items = new ArrayList<CacheOverlayItemImpl>(); + private Context context = null; + private Boolean fromDetail = false; + private boolean displayCircles = false; + private ProgressDialog waitDialog = null; + private Point center = new Point(); + private Point left = new Point(); + private Paint blockedCircle = null; + private PaintFlagsDrawFilter setfil = null; + private PaintFlagsDrawFilter remfil = null; + private cgSettings settings; + private MapFactory mapFactory = null; + + public cgMapOverlay(cgSettings settingsIn, ItemizedOverlayImpl ovlImpl, Context contextIn, Boolean fromDetailIn) { + super(ovlImpl); + + populate(); + settings = settingsIn; + + context = contextIn; + fromDetail = fromDetailIn; + + mapFactory = settings.getMapFactory(); + } + + public void updateItems(CacheOverlayItemImpl item) { + List<CacheOverlayItemImpl> itemsPre = new ArrayList<CacheOverlayItemImpl>(); + itemsPre.add(item); + + updateItems(itemsPre); + } + + public void updateItems(List<CacheOverlayItemImpl> itemsPre) { + if (itemsPre == null) { + return; + } + + for (CacheOverlayItemImpl item : itemsPre) { + item.setMarker(boundCenterBottom(item.getMarker(0))); + } + + // ensure no interference between the draw and content changing routines + getOverlayImpl().lock(); + try { + items = new ArrayList<CacheOverlayItemImpl>(itemsPre); + + setLastFocusedItemIndex(-1); // to reset tap during data change + populate(); + } finally { + getOverlayImpl().unlock(); + } + } + + public boolean getCircles() { + return displayCircles; + } + + public void switchCircles() { + displayCircles = !displayCircles; + } + + @Override + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + + drawInternal(canvas, mapView.getMapProjection()); + + super.draw(canvas, mapView, false); + } + + @Override + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + + drawInternal(canvas, projection); + + super.drawOverlayBitmap(canvas, drawPosition, projection, drawZoomLevel); + } + + private void drawInternal(Canvas canvas, MapProjectionImpl projection) { + + // prevent content changes + getOverlayImpl().lock(); + try { + if (displayCircles) { + if (blockedCircle == null) { + blockedCircle = new Paint(); + blockedCircle.setAntiAlias(true); + blockedCircle.setStrokeWidth(1.0f); + blockedCircle.setARGB(127, 0, 0, 0); + blockedCircle.setPathEffect(new DashPathEffect(new float[] { 3, 2 }, 0)); + } + + if (setfil == null) + setfil = new PaintFlagsDrawFilter(0, Paint.FILTER_BITMAP_FLAG); + if (remfil == null) + remfil = new PaintFlagsDrawFilter(Paint.FILTER_BITMAP_FLAG, 0); + + canvas.setDrawFilter(setfil); + + for (CacheOverlayItemImpl item : items) { + final cgCoord itemCoord = item.getCoord(); + float[] result = new float[1]; + + Location.distanceBetween(itemCoord.coords.getLatitude(), itemCoord.coords.getLongitude(), + itemCoord.coords.getLatitude(), itemCoord.coords.getLongitude() + 1, result); + final float longitudeLineDistance = result[0]; + + GeoPointImpl itemGeo = mapFactory.getGeoPointBase(itemCoord.coords); + + final Geopoint leftCoords = new Geopoint(itemCoord.coords.getLatitude(), + itemCoord.coords.getLongitude() - 161 / longitudeLineDistance); + GeoPointImpl leftGeo = mapFactory.getGeoPointBase(leftCoords); + + projection.toPixels(itemGeo, center); + projection.toPixels(leftGeo, left); + int radius = center.x - left.x; + + final String type = item.getType(); + if (type == null || "multi".equals(type) || "mystery".equals(type) || "virtual".equals(type)) { + blockedCircle.setColor(0x66000000); + blockedCircle.setStyle(Style.STROKE); + canvas.drawCircle(center.x, center.y, radius, blockedCircle); + } else { + blockedCircle.setColor(0x66BB0000); + blockedCircle.setStyle(Style.STROKE); + canvas.drawCircle(center.x, center.y, radius, blockedCircle); + + blockedCircle.setColor(0x44BB0000); + blockedCircle.setStyle(Style.FILL); + canvas.drawCircle(center.x, center.y, radius, blockedCircle); + } + } + canvas.setDrawFilter(remfil); + } + } finally { + getOverlayImpl().unlock(); + } + } + + @Override + public boolean onTap(int index) { + + try { + if (items.size() <= index) { + return false; + } + + if (waitDialog == null) { + waitDialog = new ProgressDialog(context); + waitDialog.setMessage("loading details..."); + waitDialog.setCancelable(false); + } + waitDialog.show(); + + CacheOverlayItemImpl item = null; + + // prevent concurrent changes + getOverlayImpl().lock(); + try { + if (index < items.size()) { + item = items.get(index); + } + } finally { + getOverlayImpl().unlock(); + } + + if (item == null) { + return false; + } + + cgCoord coordinate = item.getCoord(); + + if (StringUtils.isNotBlank(coordinate.type) && coordinate.type.equalsIgnoreCase("cache") && StringUtils.isNotBlank(coordinate.geocode)) { + Intent popupIntent = new Intent(context, cgeopopup.class); + + popupIntent.putExtra("fromdetail", fromDetail); + popupIntent.putExtra("geocode", coordinate.geocode); + + context.startActivity(popupIntent); + } else if (coordinate.type != null && coordinate.type.equalsIgnoreCase("waypoint") && coordinate.id != null && coordinate.id > 0) { + Intent popupIntent = new Intent(context, cgeowaypoint.class); + + popupIntent.putExtra("waypoint", coordinate.id); + popupIntent.putExtra("geocode", coordinate.geocode); + + context.startActivity(popupIntent); + } else { + waitDialog.dismiss(); + return false; + } + + waitDialog.dismiss(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapOverlay.onTap: " + e.toString()); + } + + return false; + } + + @Override + public CacheOverlayItemImpl createItem(int index) { + try { + return items.get(index); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapOverlay.createItem: " + e.toString()); + } + + return null; + } + + @Override + public int size() { + try { + return items.size(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapOverlay.size: " + e.toString()); + } + + return 0; + } + + public void infoDialog(int index) { + + final CacheOverlayItemImpl item = items.get(index); + final cgCoord coordinate = item.getCoord(); + + if (coordinate == null) { + Log.e(cgSettings.tag, "cgMapOverlay:infoDialog: No coordinates given"); + return; + } + + try { + AlertDialog.Builder dialog = new AlertDialog.Builder(context); + dialog.setCancelable(true); + + if (coordinate.type.equalsIgnoreCase("cache")) { + dialog.setTitle("cache"); + + String cacheType; + if (cgBase.cacheTypesInv.containsKey(coordinate.typeSpec)) { + cacheType = cgBase.cacheTypesInv.get(coordinate.typeSpec); + } else { + cacheType = cgBase.cacheTypesInv.get("mystery"); + } + + dialog.setMessage(Html.fromHtml(item.getTitle()) + "\n\ngeocode: " + coordinate.geocode.toUpperCase() + "\ntype: " + cacheType); + if (fromDetail == false) { + dialog.setPositiveButton("detail", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + Intent cachesIntent = new Intent(context, cgeodetail.class); + cachesIntent.putExtra("geocode", coordinate.geocode.toUpperCase()); + context.startActivity(cachesIntent); + + dialog.cancel(); + } + }); + } else { + dialog.setPositiveButton("navigate", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + cgeonavigate navigateActivity = new cgeonavigate(); + + cgeonavigate.coordinates.clear(); + cgeonavigate.coordinates.add(coordinate); + + Intent navigateIntent = new Intent(context, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", coordinate.coords.getLatitude()); + navigateIntent.putExtra("longitude", coordinate.coords.getLongitude()); + navigateIntent.putExtra("geocode", coordinate.geocode.toUpperCase()); + context.startActivity(navigateIntent); + dialog.cancel(); + } + }); + } + } else { + dialog.setTitle("waypoint"); + + String waypointType; + if (cgBase.cacheTypesInv.containsKey(coordinate.typeSpec)) { + waypointType = cgBase.waypointTypes.get(coordinate.typeSpec); + } else { + waypointType = cgBase.waypointTypes.get("waypoint"); + } + + dialog.setMessage(Html.fromHtml(item.getTitle()) + "\n\ntype: " + waypointType); + dialog.setPositiveButton("navigate", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + cgeonavigate navigateActivity = new cgeonavigate(); + + cgeonavigate.coordinates.clear(); + cgeonavigate.coordinates.add(coordinate); + + Intent navigateIntent = new Intent(context, navigateActivity.getClass()); + navigateIntent.putExtra("latitude", coordinate.coords.getLatitude()); + navigateIntent.putExtra("longitude", coordinate.coords.getLongitude()); + navigateIntent.putExtra("geocode", coordinate.name); + + context.startActivity(navigateIntent); + dialog.cancel(); + } + }); + } + + dialog.setNegativeButton("dismiss", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapOverlay.infoDialog: " + e.toString()); + } + } +} diff --git a/main/src/cgeo/geocaching/mapcommon/cgOverlayScale.java b/main/src/cgeo/geocaching/mapcommon/cgOverlayScale.java new file mode 100644 index 0000000..7c689a3 --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/cgOverlayScale.java @@ -0,0 +1,154 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgSettings.mapSourceEnum; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; + +import android.app.Activity; +import android.graphics.BlurMaskFilter; +import android.graphics.Canvas; +import android.graphics.Paint; +import android.graphics.Point; +import android.graphics.Typeface; +import android.util.DisplayMetrics; + +public class cgOverlayScale implements OverlayBase { + private cgSettings settings = null; + private Paint scale = null; + private Paint scaleShadow = null; + private BlurMaskFilter blur = null; + private float pixelDensity = 0L; + private double pixels = 0d; + private int bottom = 0; + private double distance = 0d; + private double distanceRound = 0d; + private String units = null; + private OverlayImpl ovlImpl = null; + + public cgOverlayScale(Activity activity, cgSettings settingsIn, OverlayImpl overlayImpl) { + settings = settingsIn; + this.ovlImpl = overlayImpl; + + DisplayMetrics metrics = new DisplayMetrics(); + activity.getWindowManager().getDefaultDisplay().getMetrics(metrics); + pixelDensity = metrics.density; + } + + @Override + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + // Scale overlay is only necessary for google maps, so the mapsforge + // related draw method needs not to be filled. + } + + @Override + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + //super.draw(canvas, mapView, shadow); + + final double span = mapView.getLongitudeSpan() / 1e6; + final GeoPointImpl center = mapView.getMapViewCenter(); + + pixels = mapView.getWidth() / 2.0; // pixels related to following latitude span + bottom = mapView.getHeight() - 14; // pixels from bottom side of screen + + final Geopoint leftCoords = new Geopoint(center.getLatitudeE6() / 1e6, center.getLongitudeE6() / 1e6 - span / 2); + final Geopoint rightCoords = new Geopoint(center.getLatitudeE6() / 1e6, center.getLongitudeE6() / 1e6 + span / 2); + + distance = leftCoords.distanceTo(rightCoords) / 2; + distanceRound = 0d; + + if (settings.units == cgSettings.unitsImperial) { + distance /= cgBase.miles2km; + + if (distance > 100) { // 100+ mi > 1xx mi + distanceRound = Math.floor(distance / 100) * 100; + units = "mi"; + } else if (distance > 10) { // 10 - 100 mi > 1x mi + distanceRound = Math.floor(distance / 10) * 10; + units = "mi"; + } else if (distance > 1) { // 1 - 10 mi > 1.x mi + distanceRound = Math.floor(distance); + units = "mi"; + } else if (distance > 0.1) { // 0.1 mi - 1.0 mi > 1xx ft + distance *= 5280; + distanceRound = Math.floor(distance / 100) * 100; + units = "ft"; + } else { // 1 - 100 ft > 1x ft + distance *= 5280; + distanceRound = Math.round(distance / 10) * 10; + units = "ft"; + } + } else { + if (distance > 100) { // 100+ km > 1xx km + distanceRound = Math.floor(distance / 100) * 100; + units = "km"; + } else if (distance > 10) { // 10 - 100 km > 1x km + distanceRound = Math.floor(distance / 10) * 10; + units = "km"; + } else if (distance > 1) { // 1 - 10 km > 1.x km + distanceRound = Math.floor(distance); + units = "km"; + } else if (distance > 0.1) { // 100 m - 1 km > 1xx m + distance *= 1000; + distanceRound = Math.floor(distance / 100) * 100; + units = "m"; + } else { // 1 - 100 m > 1x m + distance *= 1000; + distanceRound = Math.round(distance / 10) * 10; + units = "m"; + } + } + + pixels = Math.round((pixels / distance) * distanceRound); + + if (blur == null) { + blur = new BlurMaskFilter(3, BlurMaskFilter.Blur.NORMAL); + } + + if (scaleShadow == null) { + scaleShadow = new Paint(); + scaleShadow.setAntiAlias(true); + scaleShadow.setStrokeWidth(4 * pixelDensity); + scaleShadow.setMaskFilter(blur); + scaleShadow.setTextSize(14 * pixelDensity); + scaleShadow.setTypeface(Typeface.DEFAULT_BOLD); + } + + if (scale == null) { + scale = new Paint(); + scale.setAntiAlias(true); + scale.setStrokeWidth(2 * pixelDensity); + scale.setTextSize(14 * pixelDensity); + scale.setTypeface(Typeface.DEFAULT_BOLD); + } + + if (mapSourceEnum.googleSat == settings.mapSource) { + scaleShadow.setColor(0xFF000000); + scale.setColor(0xFFFFFFFF); + } else { + scaleShadow.setColor(0xFFFFFFFF); + scale.setColor(0xFF000000); + } + + canvas.drawLine(10, bottom, 10, (bottom - (8 * pixelDensity)), scaleShadow); + canvas.drawLine((int) (pixels + 10), bottom, (int) (pixels + 10), (bottom - (8 * pixelDensity)), scaleShadow); + canvas.drawLine(8, bottom, (int) (pixels + 12), bottom, scaleShadow); + canvas.drawText(String.format("%.0f", distanceRound) + " " + units, (float) (pixels - (10 * pixelDensity)), (bottom - (10 * pixelDensity)), scaleShadow); + + canvas.drawLine(11, bottom, 11, (bottom - (6 * pixelDensity)), scale); + canvas.drawLine((int) (pixels + 9), bottom, (int) (pixels + 9), (bottom - (6 * pixelDensity)), scale); + canvas.drawLine(10, bottom, (int) (pixels + 10), bottom, scale); + canvas.drawText(String.format("%.0f", distanceRound) + " " + units, (float) (pixels - (10 * pixelDensity)), (bottom - (10 * pixelDensity)), scale); + } + + @Override + public OverlayImpl getOverlayImpl() { + return ovlImpl; + } +} diff --git a/main/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java b/main/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java new file mode 100644 index 0000000..664a37d --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java @@ -0,0 +1,188 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.cgeodetail; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import org.apache.commons.lang3.StringUtils; + +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Canvas; +import android.graphics.Point; +import android.util.Log; + +import java.util.ArrayList; +import java.util.List; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class cgUsersOverlay extends ItemizedOverlayBase implements OverlayBase { + + private List<UserOverlayItemImpl> items = new ArrayList<UserOverlayItemImpl>(); + private Context context = null; + private final Pattern patternGeocode = Pattern.compile("^(GC[A-Z0-9]+)(\\: ?(.+))?$", Pattern.CASE_INSENSITIVE); + + public cgUsersOverlay(ItemizedOverlayImpl ovlImplIn, Context contextIn) { + super(ovlImplIn); + populate(); + + context = contextIn; + } + + protected void updateItems(UserOverlayItemImpl item) { + List<UserOverlayItemImpl> itemsPre = new ArrayList<UserOverlayItemImpl>(); + itemsPre.add(item); + + updateItems(itemsPre); + } + + public void updateItems(List<UserOverlayItemImpl> itemsPre) { + if (itemsPre == null) { + return; + } + + for (UserOverlayItemImpl item : itemsPre) { + item.setMarker(boundCenter(item.getMarker(0))); + } + + items.clear(); + + if (itemsPre.size() > 0) { + items = new ArrayList<UserOverlayItemImpl>(itemsPre); + } + + setLastFocusedItemIndex(-1); // to reset tap during data change + populate(); + } + + @Override + public boolean onTap(int index) { + try { + if (items.size() <= index) { + return false; + } + + final UserOverlayItemImpl item = items.get(index); + final cgUser user = item.getUser(); + + // set action + String action = null; + String geocode = null; + final Matcher matcherGeocode = patternGeocode.matcher(user.action.trim()); + + if (user.action.length() == 0 || user.action.equalsIgnoreCase("pending")) { + action = "Looking around"; + } else if (user.action.equalsIgnoreCase("tweeting")) { + action = "Tweeting"; + } else if (matcherGeocode.find()) { + if (matcherGeocode.group(1) != null) { + geocode = matcherGeocode.group(1).trim().toUpperCase(); + } + if (matcherGeocode.group(3) != null) { + action = "Heading to " + geocode + " (" + matcherGeocode.group(3).trim() + ")"; + } else { + action = "Heading to " + geocode; + } + } else { + action = user.action; + } + + // set icon + int icon = -1; + if (user.client.equalsIgnoreCase("c:geo")) { + icon = R.drawable.client_cgeo; + } else if (user.client.equalsIgnoreCase("preCaching")) { + icon = R.drawable.client_precaching; + } else if (user.client.equalsIgnoreCase("Handy Geocaching")) { + icon = R.drawable.client_handygeocaching; + } + + final AlertDialog.Builder dialog = new AlertDialog.Builder(context); + if (icon > -1) { + dialog.setIcon(icon); + } + dialog.setTitle(user.username); + dialog.setMessage(action); + dialog.setCancelable(true); + if (StringUtils.isNotBlank(geocode)) { + dialog.setPositiveButton(geocode + "?", new cacheDetails(geocode)); + } + dialog.setNeutralButton("Dismiss", new DialogInterface.OnClickListener() { + + public void onClick(DialogInterface dialog, int id) { + dialog.cancel(); + } + }); + + AlertDialog alert = dialog.create(); + alert.show(); + + return true; + } catch (Exception e) { + Log.e(cgSettings.tag, "cgUsersOverlay.onTap: " + e.toString()); + } + + return false; + } + + @Override + public void draw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + super.draw(canvas, mapView, false); + } + + @Override + public void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + super.drawOverlayBitmap(canvas, drawPosition, projection, drawZoomLevel); + } + + @Override + public UserOverlayItemImpl createItem(int index) { + try { + return items.get(index); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgUsersOverlay.createItem: " + e.toString()); + } + + return null; + } + + @Override + public int size() { + try { + return items.size(); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgUsersOverlay.size: " + e.toString()); + } + + return 0; + } + + private class cacheDetails implements DialogInterface.OnClickListener { + + private String geocode = null; + + public cacheDetails(String geocodeIn) { + geocode = geocodeIn; + } + + public void onClick(DialogInterface dialog, int id) { + if (geocode != null) { + Intent detailIntent = new Intent(context, cgeodetail.class); + detailIntent.putExtra("geocode", geocode); + context.startActivity(detailIntent); + } + + dialog.cancel(); + } + } +} diff --git a/main/src/cgeo/geocaching/mapcommon/cgeomap.java b/main/src/cgeo/geocaching/mapcommon/cgeomap.java new file mode 100644 index 0000000..a185314 --- /dev/null +++ b/main/src/cgeo/geocaching/mapcommon/cgeomap.java @@ -0,0 +1,1794 @@ +package cgeo.geocaching.mapcommon; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgDirection; +import cgeo.geocaching.cgGeo; +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.cgUpdateDir; +import cgeo.geocaching.cgUpdateLoc; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.cgWaypoint; +import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.cgSettings.mapSourceEnum; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.ActivityImpl; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapControllerImpl; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OnDragListener; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.content.SharedPreferences; +import android.content.res.Resources; +import android.graphics.drawable.Drawable; +import android.os.Bundle; +import android.os.Handler; +import android.os.Message; +import android.util.Log; +import android.view.Menu; +import android.view.MenuItem; +import android.view.SubMenu; +import android.view.View; +import android.view.WindowManager; +import android.view.ViewGroup.LayoutParams; +import android.widget.ImageSwitcher; +import android.widget.ImageView; +import android.widget.TextView; +import android.widget.ImageView.ScaleType; +import android.widget.ViewSwitcher.ViewFactory; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Locale; +import java.util.Map; +import java.util.UUID; + +public class cgeomap extends MapBase implements OnDragListener, ViewFactory { + + private static final int MENU_SELECT_MAPVIEW = 1; + private static final int MENU_MAP_LIVE = 2; + private static final int MENU_STORE_CACHES = 3; + private static final int MENU_TRAIL_MODE = 4; + private static final int MENU_CIRCLE_MODE = 5; + + private static final int SUBMENU_VIEW_GOOGLE_MAP = 10; + private static final int SUBMENU_VIEW_GOOGLE_SAT = 11; + private static final int SUBMENU_VIEW_MF_MAPNIK = 13; + private static final int SUBMENU_VIEW_MF_OSMARENDER = 14; + private static final int SUBMENU_VIEW_MF_CYCLEMAP = 15; + private static final int SUBMENU_VIEW_MF_OFFLINE = 16; + + private Resources res = null; + private Activity activity = null; + private MapViewImpl mapView = null; + private MapControllerImpl mapController = null; + private cgSettings settings = null; + private cgBase base = null; + private cgeoapplication app = null; + private SharedPreferences.Editor prefsEdit = null; + private cgGeo geo = null; + private cgDirection dir = null; + private cgUpdateLoc geoUpdate = new UpdateLoc(); + private cgUpdateDir dirUpdate = new UpdateDir(); + // from intent + private boolean fromDetailIntent = false; + private String searchIdIntent = null; + private String geocodeIntent = null; + private Geopoint coordsIntent = null; + private String waypointTypeIntent = null; + private int[] mapStateIntent = null; + // status data + private UUID searchId = null; + private String token = null; + private boolean noMapTokenShowed = false; + // map status data + private boolean followMyLocation = false; + private Integer centerLatitude = null; + private Integer centerLongitude = null; + private Integer spanLatitude = null; + private Integer spanLongitude = null; + private Integer centerLatitudeUsers = null; + private Integer centerLongitudeUsers = null; + private Integer spanLatitudeUsers = null; + private Integer spanLongitudeUsers = null; + // thread + private LoadTimer loadTimer = null; + private UsersTimer usersTimer = null; + private LoadThread loadThread = null; + private DownloadThread downloadThread = null; + private DisplayThread displayThread = null; + private UsersThread usersThread = null; + private DisplayUsersThread displayUsersThread = null; + private LoadDetails loadDetailsThread = null; + private volatile long loadThreadRun = 0L; + private volatile long usersThreadRun = 0L; + private volatile boolean downloaded = false; + // overlays + private cgMapOverlay overlayCaches = null; + private cgUsersOverlay overlayUsers = null; + private cgOverlayScale overlayScale = null; + private cgMapMyOverlay overlayMyLoc = null; + // data for overlays + private int cachesCnt = 0; + private Map<Integer, Drawable> iconsCache = new HashMap<Integer, Drawable>(); + private List<cgCache> caches = new ArrayList<cgCache>(); + private List<cgUser> users = new ArrayList<cgUser>(); + private List<cgCoord> coordinates = new ArrayList<cgCoord>(); + // storing for offline + private ProgressDialog waitDialog = null; + private int detailTotal = 0; + private int detailProgress = 0; + private Long detailProgressTime = 0L; + // views + private ImageSwitcher myLocSwitch = null; + // other things + private boolean live = true; // live map (live, dead) or rest (displaying caches on map) + private boolean liveChanged = false; // previous state for loadTimer + private boolean centered = false; // if map is already centered + private boolean alreadyCentered = false; // -""- for setting my location + // handlers + final private Handler displayHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + final int what = msg.what; + + if (what == 0) { + // set title + final StringBuilder title = new StringBuilder(); + + if (live) { + title.append(res.getString(R.string.map_live)); + } else { + title.append(res.getString(R.string.map_map)); + } + + if (caches != null && cachesCnt > 0) { + title.append(" ["); + title.append(caches.size()); + title.append(']'); + } + + ActivityMixin.setTitle(activity, title.toString()); + } else if (what == 1 && mapView != null) { + mapView.invalidate(); + } + } + }; + final private Handler showProgressHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + final int what = msg.what; + + if (what == 0) { + ActivityMixin.showProgress(activity, false); + } else if (what == 1) { + ActivityMixin.showProgress(activity, true); + } + } + }; + final private Handler loadDetailsHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (msg.what == 0) { + if (waitDialog != null) { + int secondsElapsed = (int) ((System.currentTimeMillis() - detailProgressTime) / 1000); + int secondsRemaining; + if (detailProgress > 0) //DP can be zero and cause devisionByZero + secondsRemaining = (detailTotal - detailProgress) * secondsElapsed / detailProgress; + else + secondsRemaining = (detailTotal - detailProgress) * secondsElapsed; + + waitDialog.setProgress(detailProgress); + if (secondsRemaining < 40) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); + } else if (secondsRemaining < 90) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%d", (secondsRemaining / 60)) + " " + res.getString(R.string.caches_eta_min)); + } else { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%d", (secondsRemaining / 60)) + " " + res.getString(R.string.caches_eta_mins)); + } + } + } else { + if (waitDialog != null) { + waitDialog.dismiss(); + waitDialog.setOnCancelListener(null); + } + + if (geo == null) { + geo = app.startGeo(activity, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(activity, dirUpdate); + } + } + } + }; + final private Handler noMapTokenHandler = new Handler() { + + @Override + public void handleMessage(Message msg) { + if (!noMapTokenShowed) { + ActivityMixin.showToast(activity, res.getString(R.string.map_token_err)); + + noMapTokenShowed = true; + } + } + }; + + public cgeomap(ActivityImpl activity) { + super(activity); + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + // class init + res = this.getResources(); + activity = this.getActivity(); + app = (cgeoapplication) activity.getApplication(); + app.setAction(null); + settings = new cgSettings(activity, activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE)); + base = new cgBase(app, settings, activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE)); + prefsEdit = activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE).edit(); + MapFactory mapFactory = settings.getMapFactory(); + + // reset status + noMapTokenShowed = false; + + // set layout + activity.getWindow().addFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); + + // set layout + ActivityMixin.setTheme(activity); + activity.setContentView(settings.getMapFactory().getMapLayoutId()); + ActivityMixin.setTitle(activity, res.getString(R.string.map_map)); + + if (geo == null) { + geo = app.startGeo(activity, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(activity, dirUpdate); + } + + // initialize map + mapView = (MapViewImpl) activity.findViewById(mapFactory.getMapViewId()); + mapView.setMapSource(settings); + if (!mapView.needsScaleOverlay()) { + mapView.setBuiltinScale(true); + } + mapView.setBuiltInZoomControls(true); + mapView.displayZoomControls(true); + mapView.preLoad(); + mapView.setOnDragListener(this); + + // initialize overlays + mapView.clearOverlays(); + + if (overlayMyLoc == null) { + overlayMyLoc = mapView.createAddPositionOverlay(activity, settings); + } + + if (settings.publicLoc > 0 && overlayUsers == null) { + overlayUsers = mapView.createAddUsersOverlay(activity, getResources().getDrawable(R.drawable.user_location)); + } + + if (overlayCaches == null) { + overlayCaches = mapView.createAddMapOverlay(settings, mapView.getContext(), getResources().getDrawable(R.drawable.marker), fromDetailIntent); + } + + if (overlayScale == null && mapView.needsScaleOverlay()) { + overlayScale = mapView.createAddScaleOverlay(activity, settings); + } + + mapView.invalidate(); + + mapController = mapView.getMapController(); + mapController.setZoom(settings.mapzoom); + + // start location and directory services + if (geo != null) { + geoUpdate.updateLoc(geo); + } + if (dir != null) { + dirUpdate.updateDir(dir); + } + + // get parameters + Bundle extras = activity.getIntent().getExtras(); + if (extras != null) { + fromDetailIntent = extras.getBoolean("detail"); + searchIdIntent = extras.getString("searchid"); + geocodeIntent = extras.getString("geocode"); + final double latitudeIntent = extras.getDouble("latitude"); + final double longitudeIntent = extras.getDouble("longitude"); + coordsIntent = new Geopoint(latitudeIntent, longitudeIntent); + waypointTypeIntent = extras.getString("wpttype"); + mapStateIntent = extras.getIntArray("mapstate"); + + if ("".equals(searchIdIntent)) { + searchIdIntent = null; + } + if (coordsIntent.getLatitude() == 0.0 || coordsIntent.getLongitude() == 0.0) { + coordsIntent = null; + } + } + + // live or death + if (searchIdIntent == null && geocodeIntent == null && coordsIntent == null) { + live = true; + } else { + live = false; + } + + if (null == mapStateIntent) { + if (live) { + followMyLocation = true; + } else { + followMyLocation = false; + } + } else { + followMyLocation = 1 == mapStateIntent[3] ? true : false; + } + if (geocodeIntent != null || searchIdIntent != null || coordsIntent != null || mapStateIntent != null) { + centerMap(geocodeIntent, searchIdIntent, coordsIntent, mapStateIntent); + } + + // prepare my location button + myLocSwitch = (ImageSwitcher) activity.findViewById(R.id.my_position); + myLocSwitch.setFactory(this); + myLocSwitch.setInAnimation(activity, android.R.anim.fade_in); + myLocSwitch.setOutAnimation(activity, android.R.anim.fade_out); + myLocSwitch.setOnClickListener(new MyLocationListener()); + switchMyLocationButton(); + + startTimer(); + + // show the filter warning bar if the filter is set + if (settings.cacheType != null) { + String cacheType = cgBase.cacheTypesInv.get(settings.cacheType); + ((TextView) activity.findViewById(R.id.filter_text)).setText(cacheType); + activity.findViewById(R.id.filter_bar).setVisibility(View.VISIBLE); + } + } + + @Override + public void onResume() { + super.onResume(); + + settings.load(); + + app.setAction(null); + if (geo == null) { + geo = app.startGeo(activity, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(activity, dirUpdate); + } + + if (geo != null) { + geoUpdate.updateLoc(geo); + } + if (dir != null) { + dirUpdate.updateDir(dir); + } + + startTimer(); + } + + @Override + public void onStop() { + if (loadTimer != null) { + loadTimer.stopIt(); + loadTimer = null; + } + + if (usersTimer != null) { + usersTimer.stopIt(); + usersTimer = null; + } + + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + savePrefs(); + + if (mapView != null) { + mapView.destroyDrawingCache(); + } + + super.onStop(); + } + + @Override + public void onPause() { + if (loadTimer != null) { + loadTimer.stopIt(); + loadTimer = null; + } + + if (usersTimer != null) { + usersTimer.stopIt(); + usersTimer = null; + } + + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + savePrefs(); + + if (mapView != null) { + mapView.destroyDrawingCache(); + } + + super.onPause(); + } + + @Override + public void onDestroy() { + if (loadTimer != null) { + loadTimer.stopIt(); + loadTimer = null; + } + + if (usersTimer != null) { + usersTimer.stopIt(); + usersTimer = null; + } + + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + savePrefs(); + + if (mapView != null) { + mapView.destroyDrawingCache(); + } + + super.onDestroy(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + + SubMenu submenu = menu.addSubMenu(1, MENU_SELECT_MAPVIEW, 0, res.getString(R.string.map_view_map)).setIcon(android.R.drawable.ic_menu_mapmode); + addMapViewMenuItems(submenu); + + menu.add(0, MENU_MAP_LIVE, 0, res.getString(R.string.map_live_disable)).setIcon(R.drawable.ic_menu_notifications); + menu.add(0, MENU_STORE_CACHES, 0, res.getString(R.string.caches_store_offline)).setIcon(android.R.drawable.ic_menu_set_as).setEnabled(false); + menu.add(0, MENU_TRAIL_MODE, 0, res.getString(R.string.map_trail_hide)).setIcon(android.R.drawable.ic_menu_recent_history); + menu.add(0, MENU_CIRCLE_MODE, 0, res.getString(R.string.map_circles_hide)).setIcon(R.drawable.ic_menu_circle); + + return true; + } + + private void addMapViewMenuItems(final Menu menu) { + String[] mapViews = res.getStringArray(R.array.map_sources); + mapSourceEnum mapSource = settings.mapSource; + + menu.add(1, SUBMENU_VIEW_GOOGLE_MAP, 0, mapViews[0]).setCheckable(true).setChecked(mapSource == mapSourceEnum.googleMap); + menu.add(1, SUBMENU_VIEW_GOOGLE_SAT, 0, mapViews[1]).setCheckable(true).setChecked(mapSource == mapSourceEnum.googleSat); + menu.add(1, SUBMENU_VIEW_MF_MAPNIK, 0, mapViews[2]).setCheckable(true).setChecked(mapSource == mapSourceEnum.mapsforgeMapnik); + menu.add(1, SUBMENU_VIEW_MF_OSMARENDER, 0, mapViews[3]).setCheckable(true).setChecked(mapSource == mapSourceEnum.mapsforgeOsmarender); + menu.add(1, SUBMENU_VIEW_MF_CYCLEMAP, 0, mapViews[4]).setCheckable(true).setChecked(mapSource == mapSourceEnum.mapsforgeCycle); + menu.add(1, SUBMENU_VIEW_MF_OFFLINE, 0, mapViews[5]).setCheckable(true).setChecked(mapSource == mapSourceEnum.mapsforgeOffline); + menu.setGroupCheckable(1, true, true); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + super.onPrepareOptionsMenu(menu); + + MenuItem item; + try { + item = menu.findItem(MENU_TRAIL_MODE); // show trail + if (settings.maptrail == 1) { + item.setTitle(res.getString(R.string.map_trail_hide)); + } else { + item.setTitle(res.getString(R.string.map_trail_show)); + } + + item = menu.findItem(MENU_MAP_LIVE); // live map + if (live == false) { + item.setEnabled(false); + item.setTitle(res.getString(R.string.map_live_enable)); + } else { + if (settings.maplive == 1) { + item.setTitle(res.getString(R.string.map_live_disable)); + } else { + item.setTitle(res.getString(R.string.map_live_enable)); + } + } + + item = menu.findItem(MENU_STORE_CACHES); // store loaded + if (live && !isLoading() && app.getNotOfflineCount(searchId) > 0 && caches != null && caches.size() > 0) { + item.setEnabled(true); + } else { + item.setEnabled(false); + } + + item = menu.findItem(MENU_CIRCLE_MODE); // show circles + if (overlayCaches != null && overlayCaches.getCircles()) { + item.setTitle(res.getString(R.string.map_circles_hide)); + } else { + item.setTitle(res.getString(R.string.map_circles_show)); + } + + item = menu.findItem(SUBMENU_VIEW_MF_OFFLINE); + if (settings.hasValidMapFile()) { + item.setEnabled(true); + } else { + item.setEnabled(false); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeomap.onPrepareOptionsMenu: " + e.toString()); + } + + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + final int id = item.getItemId(); + + if (id == MENU_TRAIL_MODE) { + if (settings.maptrail == 1) { + prefsEdit.putInt("maptrail", 0); + prefsEdit.commit(); + + settings.maptrail = 0; + } else { + prefsEdit.putInt("maptrail", 1); + prefsEdit.commit(); + + settings.maptrail = 1; + } + } else if (id == MENU_MAP_LIVE) { + if (settings.maplive == 1) { + settings.liveMapDisable(); + } else { + settings.liveMapEnable(); + } + liveChanged = true; + searchId = null; + searchIdIntent = null; + } else if (id == MENU_STORE_CACHES) { + if (live && !isLoading() && caches != null && !caches.isEmpty()) { + final List<String> geocodes = new ArrayList<String>(); + + List<cgCache> cachesProtected = new ArrayList<cgCache>(caches); + try { + if (cachesProtected.size() > 0) { + final GeoPointImpl mapCenter = mapView.getMapViewCenter(); + final int mapCenterLat = mapCenter.getLatitudeE6(); + final int mapCenterLon = mapCenter.getLongitudeE6(); + final int mapSpanLat = mapView.getLatitudeSpan(); + final int mapSpanLon = mapView.getLongitudeSpan(); + + for (cgCache oneCache : cachesProtected) { + if (oneCache != null && oneCache.coords != null) { + if (cgBase.isCacheInViewPort(mapCenterLat, mapCenterLon, mapSpanLat, mapSpanLon, oneCache.coords) && app.isOffline(oneCache.geocode, null) == false) { + geocodes.add(oneCache.geocode); + } + } + } + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeomap.onOptionsItemSelected.#4: " + e.toString()); + } + + detailTotal = geocodes.size(); + + if (detailTotal == 0) { + ActivityMixin.showToast(activity, res.getString(R.string.warn_save_nothing)); + + return true; + } + + waitDialog = new ProgressDialog(activity); + waitDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL); + waitDialog.setCancelable(true); + waitDialog.setMax(detailTotal); + waitDialog.setOnCancelListener(new DialogInterface.OnCancelListener() { + + public void onCancel(DialogInterface arg0) { + try { + if (loadDetailsThread != null) { + loadDetailsThread.stopIt(); + } + + if (geo == null) { + geo = app.startGeo(activity, geoUpdate, base, settings, 0, 0); + } + if (settings.useCompass == 1 && dir == null) { + dir = app.startDir(activity, dirUpdate); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.onPrepareOptionsMenu.onCancel: " + e.toString()); + } + } + }); + + Float etaTime = Float.valueOf((detailTotal * (float) 7) / 60); + if (etaTime < 0.4) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + res.getString(R.string.caches_eta_ltm)); + } else if (etaTime < 1.5) { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", etaTime) + " " + res.getString(R.string.caches_eta_min)); + } else { + waitDialog.setMessage(res.getString(R.string.caches_downloading) + " " + String.format(Locale.getDefault(), "%.0f", etaTime) + " " + res.getString(R.string.caches_eta_mins)); + } + waitDialog.show(); + + detailProgressTime = System.currentTimeMillis(); + + loadDetailsThread = new LoadDetails(loadDetailsHandler, geocodes); + loadDetailsThread.start(); + + return true; + } + } else if (id == MENU_CIRCLE_MODE) { + if (overlayCaches == null) { + return false; + } + + overlayCaches.switchCircles(); + mapView.invalidate(); + + } else if (SUBMENU_VIEW_GOOGLE_MAP <= id && SUBMENU_VIEW_MF_OFFLINE >= id) { + + item.setChecked(true); + mapSourceEnum mapSource = getMapSourceFromMenuId(id); + + boolean mapRestartRequired = switchMapSource(mapSource); + + if (mapRestartRequired) { + // close old mapview + activity.finish(); + + // prepare information to restart a similar view + Intent mapIntent = new Intent(activity, settings.getMapFactory().getMapClass()); + + mapIntent.putExtra("detail", fromDetailIntent); + mapIntent.putExtra("searchid", searchIdIntent); + mapIntent.putExtra("geocode", geocodeIntent); + if (coordsIntent != null) { + mapIntent.putExtra("latitude", coordsIntent.getLatitude()); + mapIntent.putExtra("longitude", coordsIntent.getLongitude()); + } + mapIntent.putExtra("wpttype", waypointTypeIntent); + int[] mapState = new int[4]; + GeoPointImpl mapCenter = mapView.getMapViewCenter(); + mapState[0] = mapCenter.getLatitudeE6(); + mapState[1] = mapCenter.getLongitudeE6(); + mapState[2] = mapView.getMapZoomLevel(); + mapState[3] = followMyLocation ? 1 : 0; + mapIntent.putExtra("mapstate", mapState); + + // start the new map + activity.startActivity(mapIntent); + + } + + return true; + } + + return false; + } + + private static mapSourceEnum getMapSourceFromMenuId(int menuItemId) { + + switch (menuItemId) { + case SUBMENU_VIEW_GOOGLE_MAP: + return mapSourceEnum.googleMap; + case SUBMENU_VIEW_GOOGLE_SAT: + return mapSourceEnum.googleSat; + case SUBMENU_VIEW_MF_OSMARENDER: + return mapSourceEnum.mapsforgeOsmarender; + case SUBMENU_VIEW_MF_MAPNIK: + return mapSourceEnum.mapsforgeMapnik; + case SUBMENU_VIEW_MF_CYCLEMAP: + return mapSourceEnum.mapsforgeCycle; + case SUBMENU_VIEW_MF_OFFLINE: + return mapSourceEnum.mapsforgeOffline; + default: + return mapSourceEnum.googleMap; + } + } + + private boolean switchMapSource(mapSourceEnum mapSource) { + + settings.mapSource = mapSource; + + prefsEdit.putInt("mapsource", settings.mapSource.ordinal()); + prefsEdit.commit(); + + boolean mapRestartRequired = settings.mapSource.isGoogleMapSource() != settings.mapSourceUsed.isGoogleMapSource(); + + if (!mapRestartRequired) { + mapView.setMapSource(settings); + } + + return mapRestartRequired; + } + + private void savePrefs() { + if (mapView == null) { + return; + } + + if (prefsEdit == null) { + prefsEdit = activity.getSharedPreferences(cgSettings.preferences, Context.MODE_PRIVATE).edit(); + } + + prefsEdit.putInt("mapzoom", mapView.getMapZoomLevel()); + prefsEdit.commit(); + } + + // set center of map to my location + private void myLocationInMiddle() { + if (geo == null) { + return; + } + if (!followMyLocation) { + return; + } + + centerMap(geo.coordsNow); + } + + // class: update location + private class UpdateLoc extends cgUpdateLoc { + + @Override + public void updateLoc(cgGeo geo) { + if (geo == null) { + return; + } + + try { + boolean repaintRequired = false; + + if (overlayMyLoc == null && mapView != null) { + overlayMyLoc = mapView.createAddPositionOverlay(activity, settings); + } + + if (overlayMyLoc != null && geo.location != null) { + overlayMyLoc.setCoordinates(geo.location); + } + + if (geo.coordsNow != null) { + if (followMyLocation) { + myLocationInMiddle(); + } else { + repaintRequired = true; + } + } + + if (settings.useCompass == 0 || (geo.speedNow != null && geo.speedNow > 5)) { // use GPS when speed is higher than 18 km/h + if (geo.bearingNow != null) { + overlayMyLoc.setHeading(geo.bearingNow); + } else { + overlayMyLoc.setHeading(0f); + } + repaintRequired = true; + } + + if (repaintRequired && mapView != null) { + mapView.repaintRequired(overlayMyLoc); + } + + } catch (Exception e) { + Log.w(cgSettings.tag, "Failed to update location."); + } + } + } + + // class: update direction + private class UpdateDir extends cgUpdateDir { + + @Override + public void updateDir(cgDirection dir) { + if (dir == null || dir.directionNow == null) { + return; + } + + if (overlayMyLoc != null && mapView != null && (geo == null || geo.speedNow == null || geo.speedNow <= 5)) { // use compass when speed is lower than 18 km/h + overlayMyLoc.setHeading(dir.directionNow); + mapView.invalidate(); + } + } + } + + public void startTimer() { + if (coordsIntent != null) { + // display just one point + (new DisplayPointThread()).start(); + } else { + // start timer + if (loadTimer != null) { + loadTimer.stopIt(); + loadTimer = null; + } + loadTimer = new LoadTimer(); + loadTimer.start(); + } + + if (settings.publicLoc > 0) { + if (usersTimer != null) { + usersTimer.stopIt(); + usersTimer = null; + } + usersTimer = new UsersTimer(); + usersTimer.start(); + } + } + + // loading timer + private class LoadTimer extends Thread { + + private volatile boolean stop = false; + + public void stopIt() { + stop = true; + + if (loadThread != null) { + loadThread.stopIt(); + loadThread = null; + } + + if (downloadThread != null) { + downloadThread.stopIt(); + downloadThread = null; + } + + if (displayThread != null) { + displayThread.stopIt(); + displayThread = null; + } + } + + @Override + public void run() { + GeoPointImpl mapCenterNow; + int centerLatitudeNow; + int centerLongitudeNow; + int spanLatitudeNow; + int spanLongitudeNow; + boolean moved = false; + boolean force = false; + long currentTime = 0; + + while (!stop) { + try { + sleep(250); + + if (mapView != null) { + // get current viewport + mapCenterNow = mapView.getMapViewCenter(); + centerLatitudeNow = mapCenterNow.getLatitudeE6(); + centerLongitudeNow = mapCenterNow.getLongitudeE6(); + spanLatitudeNow = mapView.getLatitudeSpan(); + spanLongitudeNow = mapView.getLongitudeSpan(); + + // check if map moved or zoomed + moved = false; + force = false; + + if (liveChanged) { + moved = true; + force = true; + } else if (live && settings.maplive == 1 && downloaded == false) { + moved = true; + } else if (centerLatitude == null || centerLongitude == null) { + moved = true; + } else if (spanLatitude == null || spanLongitude == null) { + moved = true; + } else if (((Math.abs(spanLatitudeNow - spanLatitude) > 50) || (Math.abs(spanLongitudeNow - spanLongitude) > 50) || // changed zoom + (Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow / 4)) || (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow / 4)) // map moved + ) && (cachesCnt <= 0 || caches == null || caches.isEmpty() + || !cgBase.isInViewPort(centerLatitude, centerLongitude, centerLatitudeNow, centerLongitudeNow, spanLatitude, spanLongitude, spanLatitudeNow, spanLongitudeNow))) { + moved = true; + } + + if (moved && caches != null && centerLatitude != null && centerLongitude != null && ((Math.abs(centerLatitudeNow - centerLatitude) > (spanLatitudeNow * 1.2)) || (Math.abs(centerLongitudeNow - centerLongitude) > (spanLongitudeNow * 1.2)))) { + force = true; + } + + //LeeB + // save new values + if (moved) { + liveChanged = false; + + currentTime = System.currentTimeMillis(); + + if (1000 < (currentTime - loadThreadRun)) { + // from web + if (20000 < (currentTime - loadThreadRun)) { + force = true; // probably stucked thread + } + + if (force && loadThread != null && loadThread.isWorking()) { + loadThread.stopIt(); + + try { + sleep(100); + } catch (Exception e) { + // nothing + } + } + + if (loadThread != null && loadThread.isWorking()) { + continue; + } + + centerLatitude = centerLatitudeNow; + centerLongitude = centerLongitudeNow; + spanLatitude = spanLatitudeNow; + spanLongitude = spanLongitudeNow; + + showProgressHandler.sendEmptyMessage(1); // show progress + + loadThread = new LoadThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude); + loadThread.setName("loadThread"); + loadThread.start(); //loadThread will kick off downloadThread once it's done + } + } + } + + if (!isLoading()) { + showProgressHandler.sendEmptyMessage(0); // hide progress + } + + yield(); + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeomap.LoadTimer.run: " + e.toString()); + } + } + } + } + + // loading timer + private class UsersTimer extends Thread { + + private volatile boolean stop = false; + + public void stopIt() { + stop = true; + + if (usersThread != null) { + usersThread.stopIt(); + usersThread = null; + } + + if (displayUsersThread != null) { + displayUsersThread.stopIt(); + displayUsersThread = null; + } + } + + @Override + public void run() { + GeoPointImpl mapCenterNow; + int centerLatitudeNow; + int centerLongitudeNow; + int spanLatitudeNow; + int spanLongitudeNow; + boolean moved = false; + long currentTime = 0; + + while (!stop) { + try { + sleep(250); + + if (mapView != null) { + // get current viewport + mapCenterNow = mapView.getMapViewCenter(); + centerLatitudeNow = mapCenterNow.getLatitudeE6(); + centerLongitudeNow = mapCenterNow.getLongitudeE6(); + spanLatitudeNow = mapView.getLatitudeSpan(); + spanLongitudeNow = mapView.getLongitudeSpan(); + + // check if map moved or zoomed + moved = false; + + currentTime = System.currentTimeMillis(); + + if (60000 < (currentTime - usersThreadRun)) { + moved = true; + } else if (centerLatitudeUsers == null || centerLongitudeUsers == null) { + moved = true; + } else if (spanLatitudeUsers == null || spanLongitudeUsers == null) { + moved = true; + } else if (((Math.abs(spanLatitudeNow - spanLatitudeUsers) > 50) || (Math.abs(spanLongitudeNow - spanLongitudeUsers) > 50) || // changed zoom + (Math.abs(centerLatitudeNow - centerLatitudeUsers) > (spanLatitudeNow / 4)) || (Math.abs(centerLongitudeNow - centerLongitudeUsers) > (spanLongitudeNow / 4)) // map moved + ) && !cgBase.isInViewPort(centerLatitudeUsers, centerLongitudeUsers, centerLatitudeNow, centerLongitudeNow, spanLatitudeUsers, spanLongitudeUsers, spanLatitudeNow, spanLongitudeNow)) { + moved = true; + } + + // save new values + if (moved && (1000 < (currentTime - usersThreadRun))) { + if (usersThread != null && usersThread.isWorking()) { + continue; + } + + centerLatitudeUsers = centerLatitudeNow; + centerLongitudeUsers = centerLongitudeNow; + spanLatitudeUsers = spanLatitudeNow; + spanLongitudeUsers = spanLongitudeNow; + + usersThread = new UsersThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude); + usersThread.start(); + } + } + + yield(); + } catch (Exception e) { + Log.w(cgSettings.tag, "cgeomap.LoadUsersTimer.run: " + e.toString()); + } + } + } + } + + // load caches from database + private class LoadThread extends DoThread { + + public LoadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); + } + + @Override + public void run() { + try { + stop = false; + working = true; + loadThreadRun = System.currentTimeMillis(); + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + //LeeB - I think this can be done better: + //1. fetch and draw(in another thread) caches from the db (fast? db read will be the slow bit) + //2. fetch and draw(in another thread) and then insert into the db caches from geocaching.com - dont draw/insert if exist in memory? + + // stage 1 - pull and render from the DB only + + if (fromDetailIntent || StringUtils.isNotEmpty(searchIdIntent)) { + searchId = UUID.fromString(searchIdIntent); + } else { + if (!live || settings.maplive == 0) { + searchId = app.getStoredInViewport(centerLat, centerLon, spanLat, spanLon, settings.cacheType); + } else { + searchId = app.getCachedInViewport(centerLat, centerLon, spanLat, spanLon, settings.cacheType); + } + } + + if (searchId != null) { + downloaded = true; + } + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + caches = app.getCaches(searchId); + + //if in live map and stored caches are found / disables are also shown. + if (live && settings.maplive >= 1) { + final boolean excludeMine = settings.excludeMine > 0; + final boolean excludeDisabled = settings.excludeDisabled > 0; + + for (int i = caches.size() - 1; i >= 0; i--) { + cgCache cache = caches.get(i); + if ((cache.found && excludeMine) || (cache.own && excludeMine) || (cache.disabled && excludeDisabled)) { + caches.remove(i); + } + } + + } + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + //render + if (displayThread != null && displayThread.isWorking()) { + displayThread.stopIt(); + } + displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon); + displayThread.start(); + + if (stop) { + displayThread.stopIt(); + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + //*** this needs to be in it's own thread + // stage 2 - pull and render from geocaching.com + //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes + + if (live && settings.maplive >= 1) { + if (downloadThread != null && downloadThread.isWorking()) { + downloadThread.stopIt(); + } + downloadThread = new DownloadThread(centerLat, centerLon, spanLat, spanLon); + downloadThread.setName("downloadThread"); + downloadThread.start(); + } + } finally { + working = false; + } + } + } + + // load caches from internet + private class DownloadThread extends DoThread { + + public DownloadThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); + } + + @Override + public void run() { //first time we enter we have crappy long/lat.... + try { + stop = false; + working = true; + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); + double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); + double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); + double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); + double llCache; + + if (latMin > latMax) { + llCache = latMax; + latMax = latMin; + latMin = llCache; + } + if (lonMin > lonMax) { + llCache = lonMax; + lonMax = lonMin; + lonMin = llCache; + } + + //*** this needs to be in it's own thread + // stage 2 - pull and render from geocaching.com + //this should just fetch and insert into the db _and_ be cancel-able if the viewport changes + + if (token == null) { + token = base.getMapUserToken(noMapTokenHandler); + } + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + Map<String, String> params = new HashMap<String, String>(); + params.put("usertoken", token); + params.put("latitude-min", String.format((Locale) null, "%.6f", latMin)); + params.put("latitude-max", String.format((Locale) null, "%.6f", latMax)); + params.put("longitude-min", String.format((Locale) null, "%.6f", lonMin)); + params.put("longitude-max", String.format((Locale) null, "%.6f", lonMax)); + + searchId = base.searchByViewport(params, 0); + if (searchId != null) { + downloaded = true; + } + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + caches = app.getCaches(searchId, centerLat, centerLon, spanLat, spanLon); + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + //render + if (displayThread != null && displayThread.isWorking()) { + displayThread.stopIt(); + } + displayThread = new DisplayThread(centerLat, centerLon, spanLat, spanLon); + displayThread.start(); + + } finally { + working = false; + } + } + } + + // display (down)loaded caches + private class DisplayThread extends DoThread { + + public DisplayThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); + } + + @Override + public void run() { + try { + stop = false; + working = true; + + if (mapView == null || caches == null) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + // display caches + final List<cgCache> cachesProtected = new ArrayList<cgCache>(caches); + final List<CacheOverlayItemImpl> items = new ArrayList<CacheOverlayItemImpl>(); + + if (cachesProtected != null && !cachesProtected.isEmpty()) { + int icon = 0; + Drawable pin = null; + CacheOverlayItemImpl item = null; + + for (cgCache cacheOne : cachesProtected) { + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + if (cacheOne.coords == null) { + continue; + } + + final cgCoord coord = new cgCoord(cacheOne); + coordinates.add(coord); + + item = settings.getMapFactory().getCacheOverlayItem(coord, cacheOne.type); + icon = cgBase.getMarkerIcon(true, cacheOne.type, cacheOne.own, cacheOne.found, cacheOne.disabled || cacheOne.archived); + pin = null; + + if (iconsCache.containsKey(icon)) { + pin = iconsCache.get(icon); + } else { + pin = getResources().getDrawable(icon); + pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight()); + + iconsCache.put(icon, pin); + } + item.setMarker(pin); + + items.add(item); + } + + overlayCaches.updateItems(items); + displayHandler.sendEmptyMessage(1); + + cachesCnt = cachesProtected.size(); + + if (stop) { + displayHandler.sendEmptyMessage(0); + working = false; + + return; + } + + // display cache waypoints + if (cachesCnt == 1 && (geocodeIntent != null || searchIdIntent != null) && !live) { + if (cachesCnt == 1 && live == false) { + cgCache oneCache = cachesProtected.get(0); + + if (oneCache != null && oneCache.waypoints != null && !oneCache.waypoints.isEmpty()) { + for (cgWaypoint oneWaypoint : oneCache.waypoints) { + if (oneWaypoint.coords == null) { + continue; + } + + cgCoord coord = new cgCoord(oneWaypoint); + + coordinates.add(coord); + item = settings.getMapFactory().getCacheOverlayItem(coord, null); + + icon = cgBase.getMarkerIcon(false, oneWaypoint.type, false, false, false); + if (iconsCache.containsKey(icon)) { + pin = iconsCache.get(icon); + } else { + pin = getResources().getDrawable(icon); + pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight()); + iconsCache.put(icon, pin); + } + item.setMarker(pin); + + items.add(item); + } + + overlayCaches.updateItems(items); + displayHandler.sendEmptyMessage(1); + } + } + } + } else { + overlayCaches.updateItems(items); + displayHandler.sendEmptyMessage(1); + } + + cachesProtected.clear(); + + displayHandler.sendEmptyMessage(0); + } finally { + working = false; + } + } + } + + // load users from Go 4 Cache + private class UsersThread extends DoThread { + + public UsersThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); + } + + @Override + public void run() { + try { + stop = false; + working = true; + usersThreadRun = System.currentTimeMillis(); + + if (stop) { + return; + } + + double latMin = (centerLat / 1e6) - ((spanLat / 1e6) / 2) - ((spanLat / 1e6) / 4); + double latMax = (centerLat / 1e6) + ((spanLat / 1e6) / 2) + ((spanLat / 1e6) / 4); + double lonMin = (centerLon / 1e6) - ((spanLon / 1e6) / 2) - ((spanLon / 1e6) / 4); + double lonMax = (centerLon / 1e6) + ((spanLon / 1e6) / 2) + ((spanLon / 1e6) / 4); + double llCache; + + if (latMin > latMax) { + llCache = latMax; + latMax = latMin; + latMin = llCache; + } + if (lonMin > lonMax) { + llCache = lonMax; + lonMax = lonMin; + lonMin = llCache; + } + + users = base.getGeocachersInViewport(settings.getUsername(), latMin, latMax, lonMin, lonMax); + + if (stop) { + return; + } + + if (displayUsersThread != null && displayUsersThread.isWorking()) { + displayUsersThread.stopIt(); + } + displayUsersThread = new DisplayUsersThread(users, centerLat, centerLon, spanLat, spanLon); + displayUsersThread.start(); + } finally { + working = false; + } + } + } + + // display users of Go 4 Cache + private class DisplayUsersThread extends DoThread { + + private List<cgUser> users = null; + + public DisplayUsersThread(List<cgUser> usersIn, long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + super(centerLatIn, centerLonIn, spanLatIn, spanLonIn); + + users = usersIn; + } + + @Override + public void run() { + try { + stop = false; + working = true; + + if (mapView == null || users == null || users.isEmpty()) { + return; + } + + // display users + List<UserOverlayItemImpl> items = new ArrayList<UserOverlayItemImpl>(); + + int counter = 0; + UserOverlayItemImpl item = null; + + for (cgUser userOne : users) { + if (stop) { + return; + } + + if (userOne.coords == null) { + continue; + } + + item = settings.getMapFactory().getUserOverlayItemBase(activity, userOne); + items.add(item); + + counter++; + if ((counter % 10) == 0) { + overlayUsers.updateItems(items); + displayHandler.sendEmptyMessage(1); + } + } + + overlayUsers.updateItems(items); + } finally { + working = false; + } + } + } + + // display one point + private class DisplayPointThread extends Thread { + + @Override + public void run() { + if (mapView == null || caches == null) { + return; + } + + if (coordsIntent != null) { + cgCoord coord = new cgCoord(); + coord.type = "waypoint"; + coord.coords = coordsIntent; + coord.name = "some place"; + + coordinates.add(coord); + CacheOverlayItemImpl item = settings.getMapFactory().getCacheOverlayItem(coord, null); + + final int icon = cgBase.getMarkerIcon(false, waypointTypeIntent, false, false, false); + Drawable pin = null; + if (iconsCache.containsKey(icon)) { + pin = iconsCache.get(icon); + } else { + pin = getResources().getDrawable(icon); + pin.setBounds(0, 0, pin.getIntrinsicWidth(), pin.getIntrinsicHeight()); + iconsCache.put(icon, pin); + } + item.setMarker(pin); + + overlayCaches.updateItems(item); + displayHandler.sendEmptyMessage(1); + + cachesCnt = 1; + } else { + cachesCnt = 0; + } + + displayHandler.sendEmptyMessage(0); + } + } + + // parent for those above :) + private class DoThread extends Thread { + + protected boolean working = true; + protected boolean stop = false; + protected long centerLat = 0L; + protected long centerLon = 0L; + protected long spanLat = 0L; + protected long spanLon = 0L; + + public DoThread(long centerLatIn, long centerLonIn, long spanLatIn, long spanLonIn) { + centerLat = centerLatIn; + centerLon = centerLonIn; + spanLat = spanLatIn; + spanLon = spanLonIn; + } + + public synchronized boolean isWorking() { + return working; + } + + public synchronized void stopIt() { + stop = true; + } + } + + // get if map is loading something + private synchronized boolean isLoading() { + boolean loading = false; + + if (loadThread != null && loadThread.isWorking()) { + loading = true; + } else if (downloadThread != null && downloadThread.isWorking()) { + loading = true; + } else if (displayThread != null && displayThread.isWorking()) { + loading = true; + } + + return loading; + } + + // store caches + private class LoadDetails extends Thread { + + private Handler handler = null; + private List<String> geocodes = null; + private volatile boolean stop = false; + private long last = 0L; + + public LoadDetails(Handler handlerIn, List<String> geocodesIn) { + handler = handlerIn; + geocodes = geocodesIn; + } + + public void stopIt() { + stop = true; + } + + @Override + public void run() { + if (geocodes == null || geocodes.isEmpty()) { + return; + } + + if (dir != null) { + dir = app.removeDir(); + } + if (geo != null) { + geo = app.removeGeo(); + } + + for (String geocode : geocodes) { + try { + if (stop) { + break; + } + + if (!app.isOffline(geocode, null)) { + if ((System.currentTimeMillis() - last) < 1500) { + try { + int delay = 1000 + ((Double) (Math.random() * 1000)).intValue() - (int) (System.currentTimeMillis() - last); + if (delay < 0) { + delay = 500; + } + + sleep(delay); + } catch (Exception e) { + // nothing + } + } + + if (stop) { + Log.i(cgSettings.tag, "Stopped storing process."); + + break; + } + + base.storeCache(app, activity, null, geocode, 1, handler); + } + } catch (Exception e) { + Log.e(cgSettings.tag, "cgeocaches.LoadDetails.run: " + e.toString()); + } finally { + // one more cache over + detailProgress++; + handler.sendEmptyMessage(0); + } + + yield(); + + last = System.currentTimeMillis(); + } + + // we're done + handler.sendEmptyMessage(1); + } + } + + // center map to desired location + private void centerMap(final Geopoint coords) { + if (coords == null) { + return; + } + if (mapView == null) { + return; + } + + if (!alreadyCentered) { + alreadyCentered = true; + + mapController.setCenter(makeGeoPoint(coords)); + } else { + mapController.animateTo(makeGeoPoint(coords)); + } + } + + // move map to view results of searchIdIntent + private void centerMap(String geocodeCenter, String searchIdCenter, final Geopoint coordsCenter, int[] mapState) { + + if (!centered && mapState != null) { + try { + mapController.setCenter(settings.getMapFactory().getGeoPointBase(new Geopoint(mapState[0] / 1.0e6, mapState[1] / 1.0e6))); + mapController.setZoom(mapState[2]); + } catch (Exception e) { + // nothing at all + } + + centered = true; + alreadyCentered = true; + } else if (!centered && (geocodeCenter != null || searchIdIntent != null)) { + try { + List<Object> viewport = null; + + if (geocodeCenter != null) { + viewport = app.getBounds(geocodeCenter); + } else { + viewport = app.getBounds(UUID.fromString(searchIdCenter)); + } + + if (viewport == null) + return; + + Integer cnt = (Integer) viewport.get(0); + Integer minLat = null; + Integer maxLat = null; + Integer minLon = null; + Integer maxLon = null; + + if (viewport.get(1) != null) { + minLat = (int) ((Double) viewport.get(1) * 1e6); + } + if (viewport.get(2) != null) { + maxLat = (int) ((Double) viewport.get(2) * 1e6); + } + if (viewport.get(3) != null) { + maxLon = (int) ((Double) viewport.get(3) * 1e6); + } + if (viewport.get(4) != null) { + minLon = (int) ((Double) viewport.get(4) * 1e6); + } + + if (cnt == null || cnt <= 0 || minLat == null || maxLat == null || minLon == null || maxLon == null) { + return; + } + + int centerLat = 0; + int centerLon = 0; + + if ((Math.abs(maxLat) - Math.abs(minLat)) != 0) { + centerLat = minLat + ((maxLat - minLat) / 2); + } else { + centerLat = maxLat; + } + if ((Math.abs(maxLon) - Math.abs(minLon)) != 0) { + centerLon = minLon + ((maxLon - minLon) / 2); + } else { + centerLon = maxLon; + } + + if (cnt != null && cnt > 0) { + mapController.setCenter(settings.getMapFactory().getGeoPointBase(new Geopoint(centerLat, centerLon))); + if (Math.abs(maxLat - minLat) != 0 && Math.abs(maxLon - minLon) != 0) { + mapController.zoomToSpan(Math.abs(maxLat - minLat), Math.abs(maxLon - minLon)); + } + } + } catch (Exception e) { + // nothing at all + } + + centered = true; + alreadyCentered = true; + } else if (!centered && coordsCenter != null) { + try { + mapController.setCenter(makeGeoPoint(coordsCenter)); + } catch (Exception e) { + // nothing at all + } + + centered = true; + alreadyCentered = true; + } + } + + // switch My Location button image + private void switchMyLocationButton() { + if (followMyLocation) { + myLocSwitch.setImageResource(R.drawable.actionbar_mylocation_on); + myLocationInMiddle(); + } else { + myLocSwitch.setImageResource(R.drawable.actionbar_mylocation_off); + } + } + + // set my location listener + private class MyLocationListener implements View.OnClickListener { + public void onClick(View view) { + followMyLocation = !followMyLocation; + switchMyLocationButton(); + } + } + + @Override + public void onDrag() { + if (followMyLocation) { + followMyLocation = false; + switchMyLocationButton(); + } + } + + // make geopoint + private GeoPointImpl makeGeoPoint(final Geopoint coords) { + return settings.getMapFactory().getGeoPointBase(coords); + } + + // close activity and open homescreen + public void goHome(View view) { + ActivityMixin.goHome(activity); + } + + // open manual entry + public void goManual(View view) { + ActivityMixin.goManual(activity, "c:geo-live-map"); + } + + @Override + public View makeView() { + ImageView imageView = new ImageView(activity); + imageView.setScaleType(ScaleType.CENTER); + imageView.setLayoutParams(new ImageSwitcher.LayoutParams(LayoutParams.FILL_PARENT, LayoutParams.FILL_PARENT)); + return imageView; + } +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/ActivityImpl.java b/main/src/cgeo/geocaching/mapinterfaces/ActivityImpl.java new file mode 100644 index 0000000..043ca59 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/ActivityImpl.java @@ -0,0 +1,43 @@ +package cgeo.geocaching.mapinterfaces; + +import android.app.Activity; +import android.content.res.Resources; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +/** + * Defines the common functions of the provider-specific + * MapActivity implementations. + * + * @author rsudev + * + */ +public interface ActivityImpl { + + Resources getResources(); + + Activity getActivity(); + + void superOnCreate(Bundle savedInstanceState); + + void superOnResume(); + + void superOnDestroy(); + + void superOnStop(); + + void superOnPause(); + + boolean superOnCreateOptionsMenu(Menu menu); + + boolean superOnPrepareOptionsMenu(Menu menu); + + boolean superOnOptionsItemSelected(MenuItem item); + + public abstract void goHome(View view); + + public abstract void goManual(View view); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/CacheOverlayItemImpl.java b/main/src/cgeo/geocaching/mapinterfaces/CacheOverlayItemImpl.java new file mode 100644 index 0000000..df74a4c --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/CacheOverlayItemImpl.java @@ -0,0 +1,18 @@ +package cgeo.geocaching.mapinterfaces; + +import cgeo.geocaching.cgCoord; + +/** + * Covers the common functions of the provider-specific + * CacheOverlayItem implementations + * + * @author rsudev + * + */ +public interface CacheOverlayItemImpl extends OverlayItemImpl { + + public cgCoord getCoord(); + + public String getType(); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/GeoPointImpl.java b/main/src/cgeo/geocaching/mapinterfaces/GeoPointImpl.java new file mode 100644 index 0000000..4142368 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/GeoPointImpl.java @@ -0,0 +1,16 @@ +package cgeo.geocaching.mapinterfaces; + +/** + * Defines the common functions of the provider-specific + * GeoPoint implementations + * + * @author rsudev + * + */ +public interface GeoPointImpl { + + int getLatitudeE6(); + + int getLongitudeE6(); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/ItemizedOverlayImpl.java b/main/src/cgeo/geocaching/mapinterfaces/ItemizedOverlayImpl.java new file mode 100644 index 0000000..b738e41 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/ItemizedOverlayImpl.java @@ -0,0 +1,35 @@ +package cgeo.geocaching.mapinterfaces; + +import cgeo.geocaching.mapcommon.ItemizedOverlayBase; + +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +/** + * Defines the common functions to access the provider-specific + * ItemizedOverlay implementation + * + * @author rsudev + * + */ +public interface ItemizedOverlayImpl extends OverlayImpl { + + ItemizedOverlayBase getBase(); + + void superPopulate(); + + void superSetLastFocusedItemIndex(int i); + + Drawable superBoundCenter(Drawable markerIn); + + Drawable superBoundCenterBottom(Drawable marker); + + boolean superOnTap(int index); + + void superDraw(Canvas canvas, MapViewImpl mapView, boolean shadow); + + void superDrawOverlayBitmap(Canvas canvas, Point drawPosition, MapProjectionImpl projection, + byte drawZoomLevel); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/MapControllerImpl.java b/main/src/cgeo/geocaching/mapinterfaces/MapControllerImpl.java new file mode 100644 index 0000000..00e31d2 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/MapControllerImpl.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.mapinterfaces; + +/** + * Defines the common functions of the provider-specific + * MapController implementations + * + * @author rsudev + * + */ +public interface MapControllerImpl { + + void setZoom(int mapzoom); + + void setCenter(GeoPointImpl geoPoint); + + void animateTo(GeoPointImpl geoPoint); + + void zoomToSpan(int latSpanE6, int lonSpanE6); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/MapFactory.java b/main/src/cgeo/geocaching/mapinterfaces/MapFactory.java new file mode 100644 index 0000000..467947d --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/MapFactory.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.mapinterfaces; + +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.geopoint.Geopoint; + +import android.app.Activity; +import android.content.Context; + +/** + * Defines functions of a factory class to get implementation specific objects + * (GeoPoints, OverlayItems, ...) + * + * @author rsudev + * + */ +public interface MapFactory { + + public Class<? extends Activity> getMapClass(); + + public int getMapViewId(); + + public int getMapLayoutId(); + + public GeoPointImpl getGeoPointBase(final Geopoint coords); + + CacheOverlayItemImpl getCacheOverlayItem(cgCoord coordinate, String type); + + public UserOverlayItemImpl getUserOverlayItemBase(Context context, + cgUser userOne); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/MapProjectionImpl.java b/main/src/cgeo/geocaching/mapinterfaces/MapProjectionImpl.java new file mode 100644 index 0000000..e3cc1fa --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/MapProjectionImpl.java @@ -0,0 +1,18 @@ +package cgeo.geocaching.mapinterfaces; + +import android.graphics.Point; + +/** + * Defines common functions of the provider-specific + * MapProjection implementations + * + * @author rsudev + * + */ +public interface MapProjectionImpl { + + Object getImpl(); + + void toPixels(GeoPointImpl leftGeo, Point left); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/MapViewImpl.java b/main/src/cgeo/geocaching/mapinterfaces/MapViewImpl.java new file mode 100644 index 0000000..2cd016f --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/MapViewImpl.java @@ -0,0 +1,72 @@ +package cgeo.geocaching.mapinterfaces; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapMyOverlay; +import cgeo.geocaching.mapcommon.cgMapOverlay; +import cgeo.geocaching.mapcommon.cgOverlayScale; +import cgeo.geocaching.mapcommon.cgUsersOverlay; + +import android.app.Activity; +import android.content.Context; +import android.graphics.drawable.Drawable; + +/** + * Defines common functions of the provider-specific + * MapView implementations + * + * @author rsudev + * + */ +public interface MapViewImpl { + + void invalidate(); + + void setBuiltInZoomControls(boolean b); + + void displayZoomControls(boolean b); + + void preLoad(); + + void clearOverlays(); + + void addOverlay(OverlayImpl ovl); + + MapControllerImpl getMapController(); + + void destroyDrawingCache(); + + GeoPointImpl getMapViewCenter(); + + int getLatitudeSpan(); + + int getLongitudeSpan(); + + int getMapZoomLevel(); + + int getWidth(); + + int getHeight(); + + MapProjectionImpl getMapProjection(); + + Context getContext(); + + cgMapOverlay createAddMapOverlay(cgSettings settings, Context context, + Drawable drawable, boolean fromDetailIntent); + + cgUsersOverlay createAddUsersOverlay(Context context, Drawable markerIn); + + cgOverlayScale createAddScaleOverlay(Activity activity, cgSettings settingsIn); + + cgMapMyOverlay createAddPositionOverlay(Activity activity, cgSettings settingsIn); + + boolean needsScaleOverlay(); + + void setBuiltinScale(boolean b); + + void setMapSource(cgSettings settings); + + void repaintRequired(OverlayBase overlay); + + void setOnDragListener(OnDragListener onDragListener); +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/OnDragListener.java b/main/src/cgeo/geocaching/mapinterfaces/OnDragListener.java new file mode 100644 index 0000000..4ae2d41 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/OnDragListener.java @@ -0,0 +1,13 @@ +package cgeo.geocaching.mapinterfaces; + +/** + * Notifies the parent class when a MapView has been dragged + * + * @author cachapa + * + */ +public interface OnDragListener { + + public void onDrag(); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/OverlayBase.java b/main/src/cgeo/geocaching/mapinterfaces/OverlayBase.java new file mode 100644 index 0000000..e544f0d --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/OverlayBase.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.mapinterfaces; + +import android.graphics.Canvas; +import android.graphics.Point; + +/** + * Defines the base functions of the provider-independent + * Overlay implementations + * + * @author rsudev + * + */ +public interface OverlayBase { + + void draw(Canvas canvas, MapViewImpl mapView, boolean shadow); + + void drawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel); + + OverlayImpl getOverlayImpl(); +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/OverlayImpl.java b/main/src/cgeo/geocaching/mapinterfaces/OverlayImpl.java new file mode 100644 index 0000000..ede6872 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/OverlayImpl.java @@ -0,0 +1,21 @@ +package cgeo.geocaching.mapinterfaces; + +/** + * Marker interface of the provider-specific + * Overlay implementations + * + * @author rsudev + * + */ +public interface OverlayImpl { + + public enum overlayType { + PositionOverlay, + ScaleOverlay + } + + void lock(); + + void unlock(); + +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/OverlayItemImpl.java b/main/src/cgeo/geocaching/mapinterfaces/OverlayItemImpl.java new file mode 100644 index 0000000..3cc2061 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/OverlayItemImpl.java @@ -0,0 +1,19 @@ +package cgeo.geocaching.mapinterfaces; + +import android.graphics.drawable.Drawable; + +/** + * Common functions of the provider-specific + * OverlayItem implementations + * + * @author rsudev + * + */ +public interface OverlayItemImpl { + + public String getTitle(); + + public Drawable getMarker(int index); + + public void setMarker(Drawable markerIn); +} diff --git a/main/src/cgeo/geocaching/mapinterfaces/UserOverlayItemImpl.java b/main/src/cgeo/geocaching/mapinterfaces/UserOverlayItemImpl.java new file mode 100644 index 0000000..3e552e8 --- /dev/null +++ b/main/src/cgeo/geocaching/mapinterfaces/UserOverlayItemImpl.java @@ -0,0 +1,15 @@ +package cgeo.geocaching.mapinterfaces; + +import cgeo.geocaching.cgUser; + +/** + * Common functions of the provider-specific + * UserOverlayItem implementations + * + * @author rsudev + * + */ +public interface UserOverlayItemImpl extends OverlayItemImpl { + + public cgUser getUser(); +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfCacheOverlay.java b/main/src/cgeo/geocaching/mapsforge/mfCacheOverlay.java new file mode 100644 index 0000000..1bfc102 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfCacheOverlay.java @@ -0,0 +1,111 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapOverlay; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; + +import org.mapsforge.android.maps.ItemizedOverlay; +import org.mapsforge.android.maps.Projection; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class mfCacheOverlay extends ItemizedOverlay<mfCacheOverlayItem> implements ItemizedOverlayImpl { + + private cgMapOverlay base; + private Lock lock = new ReentrantLock(); + + public mfCacheOverlay(cgSettings settingsIn, Context contextIn, Drawable markerIn, Boolean fromDetailIn) { + super(boundCenterBottom(markerIn)); + base = new cgMapOverlay(settingsIn, this, contextIn, fromDetailIn); + } + + @Override + public cgMapOverlay getBase() { + return base; + } + + @Override + protected mfCacheOverlayItem createItem(int i) { + if (base == null) + return null; + + return (mfCacheOverlayItem) base.createItem(i); + } + + @Override + public int size() { + if (base == null) + return 0; + + return base.size(); + } + + @Override + protected boolean onTap(int arg0) { + if (base == null) + return false; + + return base.onTap(arg0); + } + + @Override + protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, + Projection projection, byte drawZoomLevel) { + base.drawOverlayBitmap(canvas, drawPosition, new mfMapProjection(projection), drawZoomLevel); + } + + @Override + public void superPopulate() { + populate(); + } + + @Override + public Drawable superBoundCenter(Drawable markerIn) { + return super.boundCenter(markerIn); + } + + @Override + public Drawable superBoundCenterBottom(Drawable marker) { + return super.boundCenterBottom(marker); + } + + @Override + public void superSetLastFocusedItemIndex(int i) { + // nothing to do + } + + @Override + public boolean superOnTap(int index) { + return super.onTap(index); + } + + @Override + public void superDraw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + // nothing to do here... + } + + @Override + public void superDrawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + super.drawOverlayBitmap(canvas, drawPosition, (Projection) projection.getImpl(), drawZoomLevel); + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfCacheOverlayItem.java b/main/src/cgeo/geocaching/mapsforge/mfCacheOverlayItem.java new file mode 100644 index 0000000..038b29b --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfCacheOverlayItem.java @@ -0,0 +1,35 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; + +import org.mapsforge.android.maps.GeoPoint; +import org.mapsforge.android.maps.OverlayItem; + +import android.graphics.drawable.Drawable; + +public class mfCacheOverlayItem extends OverlayItem implements CacheOverlayItemImpl { + private String cacheType = null; + private cgCoord coord; + + public mfCacheOverlayItem(cgCoord coordinate, String type) { + super(new GeoPoint(coordinate.coords.getLatitudeE6(), coordinate.coords.getLongitudeE6()), coordinate.name, ""); + + this.cacheType = type; + this.coord = coordinate; + } + + public cgCoord getCoord() { + return coord; + } + + public String getType() { + return cacheType; + } + + @Override + public Drawable getMarker(int index) { + return getMarker(); + } + +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfGeoPoint.java b/main/src/cgeo/geocaching/mapsforge/mfGeoPoint.java new file mode 100644 index 0000000..905d3bf --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfGeoPoint.java @@ -0,0 +1,12 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; + +import org.mapsforge.android.maps.GeoPoint; + +public class mfGeoPoint extends GeoPoint implements GeoPointImpl { + + public mfGeoPoint(int latitudeE6, int longitudeE6) { + super(latitudeE6, longitudeE6); + } +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfMapActivity.java b/main/src/cgeo/geocaching/mapsforge/mfMapActivity.java new file mode 100644 index 0000000..79ff290 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfMapActivity.java @@ -0,0 +1,117 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.mapcommon.MapBase; +import cgeo.geocaching.mapcommon.cgeomap; +import cgeo.geocaching.mapinterfaces.ActivityImpl; + +import org.mapsforge.android.maps.MapActivity; + +import android.app.Activity; +import android.os.Bundle; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; + +public class mfMapActivity extends MapActivity implements ActivityImpl { + + private MapBase mapBase; + + public mfMapActivity() { + mapBase = new cgeomap(this); + } + + @Override + public Activity getActivity() { + return this; + } + + @Override + protected void onCreate(Bundle icicle) { + mapBase.onCreate(icicle); + } + + @Override + protected void onDestroy() { + mapBase.onDestroy(); + } + + @Override + protected void onPause() { + mapBase.onPause(); + } + + @Override + protected void onResume() { + mapBase.onResume(); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + return mapBase.onCreateOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + return mapBase.onOptionsItemSelected(item); + } + + @Override + public boolean onPrepareOptionsMenu(Menu menu) { + return mapBase.onPrepareOptionsMenu(menu); + } + + @Override + protected void onStop() { + mapBase.onStop(); + } + + @Override + public void superOnCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + } + + @Override + public boolean superOnCreateOptionsMenu(Menu menu) { + return super.onCreateOptionsMenu(menu); + } + + @Override + public void superOnDestroy() { + super.onDestroy(); + } + + @Override + public boolean superOnOptionsItemSelected(MenuItem item) { + return super.onOptionsItemSelected(item); + } + + @Override + public void superOnResume() { + super.onResume(); + } + + @Override + public void superOnStop() { + super.onStop(); + } + + @Override + public void superOnPause() { + super.onPause(); + } + + @Override + public boolean superOnPrepareOptionsMenu(Menu menu) { + return super.onPrepareOptionsMenu(menu); + } + + // close activity and open homescreen + public void goHome(View view) { + mapBase.goHome(view); + } + + // open manual entry + public void goManual(View view) { + mapBase.goManual(view); + } +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfMapController.java b/main/src/cgeo/geocaching/mapsforge/mfMapController.java new file mode 100644 index 0000000..f31d9b2 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfMapController.java @@ -0,0 +1,48 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapControllerImpl; + +import org.mapsforge.android.maps.GeoPoint; +import org.mapsforge.android.maps.MapController; + +public class mfMapController implements MapControllerImpl { + + private MapController mapController; + private int maxZoomLevel; + + public mfMapController(MapController mapControllerIn, int maxZoomLevelIn) { + mapController = mapControllerIn; + maxZoomLevel = maxZoomLevelIn; + } + + @Override + public void animateTo(GeoPointImpl geoPoint) { + mapController.setCenter((GeoPoint) geoPoint); + } + + @Override + public void setCenter(GeoPointImpl geoPoint) { + mapController.setCenter((GeoPoint) geoPoint); + } + + @Override + public void setZoom(int mapzoom) { + int mfzoom = mapzoom - 1; + if (mfzoom > maxZoomLevel) { + mfzoom = maxZoomLevel; + } + mapController.setZoom(mfzoom); + } + + @Override + public void zoomToSpan(int latSpanE6, int lonSpanE6) { + + if (latSpanE6 != 0 && lonSpanE6 != 0) { + // calculate zoomlevel + int distDegree = Math.max(latSpanE6, lonSpanE6); + int zoomLevel = (int) Math.floor(Math.log(360.0 * 1e6 / distDegree) / Math.log(2)); + mapController.setZoom(zoomLevel + 1); + } + } +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfMapFactory.java b/main/src/cgeo/geocaching/mapsforge/mfMapFactory.java new file mode 100644 index 0000000..7e41951 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfMapFactory.java @@ -0,0 +1,49 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgCoord; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.mapinterfaces.CacheOverlayItemImpl; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapFactory; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import android.app.Activity; +import android.content.Context; + +public class mfMapFactory implements MapFactory { + + @Override + public Class<? extends Activity> getMapClass() { + return mfMapActivity.class; + } + + @Override + public int getMapViewId() { + return R.id.mfmap; + } + + @Override + public int getMapLayoutId() { + return R.layout.mfmap; + } + + @Override + public GeoPointImpl getGeoPointBase(final Geopoint coords) { + return new mfGeoPoint(coords.getLatitudeE6(), coords.getLongitudeE6()); + } + + @Override + public CacheOverlayItemImpl getCacheOverlayItem(cgCoord coordinate, String type) { + mfCacheOverlayItem baseItem = new mfCacheOverlayItem(coordinate, type); + return baseItem; + } + + @Override + public UserOverlayItemImpl getUserOverlayItemBase(Context context, cgUser userOne) { + mfUsersOverlayItem baseItem = new mfUsersOverlayItem(context, userOne); + return baseItem; + } + +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfMapProjection.java b/main/src/cgeo/geocaching/mapsforge/mfMapProjection.java new file mode 100644 index 0000000..f3e6dc1 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfMapProjection.java @@ -0,0 +1,29 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; + +import org.mapsforge.android.maps.GeoPoint; +import org.mapsforge.android.maps.Projection; + +import android.graphics.Point; + +public class mfMapProjection implements MapProjectionImpl { + + private Projection projection; + + public mfMapProjection(Projection projectionIn) { + projection = projectionIn; + } + + @Override + public void toPixels(GeoPointImpl leftGeo, Point left) { + projection.toPixels((GeoPoint) leftGeo, left); + } + + @Override + public Object getImpl() { + return projection; + } + +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfMapView.java b/main/src/cgeo/geocaching/mapsforge/mfMapView.java new file mode 100644 index 0000000..a4d1e40 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfMapView.java @@ -0,0 +1,245 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapMyOverlay; +import cgeo.geocaching.mapcommon.cgMapOverlay; +import cgeo.geocaching.mapcommon.cgOverlayScale; +import cgeo.geocaching.mapcommon.cgUsersOverlay; +import cgeo.geocaching.mapinterfaces.GeoPointImpl; +import cgeo.geocaching.mapinterfaces.MapControllerImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; +import cgeo.geocaching.mapinterfaces.OnDragListener; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; +import cgeo.geocaching.mapinterfaces.OverlayImpl.overlayType; + +import org.mapsforge.android.maps.GeoPoint; +import org.mapsforge.android.maps.MapDatabase; +import org.mapsforge.android.maps.MapView; +import org.mapsforge.android.maps.MapViewMode; +import org.mapsforge.android.maps.Overlay; +import org.mapsforge.android.maps.Projection; + +import android.app.Activity; +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.drawable.Drawable; +import android.util.AttributeSet; +import android.util.Log; +import android.view.GestureDetector; +import android.view.GestureDetector.SimpleOnGestureListener; +import android.view.MotionEvent; + +public class mfMapView extends MapView implements MapViewImpl { + private GestureDetector gestureDetector; + private OnDragListener onDragListener; + + public mfMapView(Context context, AttributeSet attrs) { + super(context, attrs); + gestureDetector = new GestureDetector(context, new GestureListener()); + } + + @Override + public void draw(Canvas canvas) { + try { + if (getMapZoomLevel() >= 22) { // to avoid too close zoom level (mostly on Samsung Galaxy S series) + getController().setZoom(22); + } + + super.draw(canvas); + } catch (Exception e) { + Log.e(cgSettings.tag, "cgMapView.draw: " + e.toString()); + } + } + + @Override + public void displayZoomControls(boolean takeFocus) { + // nothing to do here + } + + @Override + public MapControllerImpl getMapController() { + return new mfMapController(getController(), getMaxZoomLevel()); + } + + @Override + public GeoPointImpl getMapViewCenter() { + GeoPoint point = getMapCenter(); + return new mfGeoPoint(point.getLatitudeE6(), point.getLongitudeE6()); + } + + @Override + public void addOverlay(OverlayImpl ovl) { + getOverlays().add((Overlay) ovl); + } + + @Override + public void clearOverlays() { + getOverlays().clear(); + } + + @Override + public MapProjectionImpl getMapProjection() { + return new mfMapProjection(getProjection()); + } + + @Override + public cgMapOverlay createAddMapOverlay(cgSettings settings, + Context context, Drawable drawable, boolean fromDetailIntent) { + + mfCacheOverlay ovl = new mfCacheOverlay(settings, context, drawable, fromDetailIntent); + getOverlays().add(ovl); + return ovl.getBase(); + } + + @Override + public cgUsersOverlay createAddUsersOverlay(Context context, Drawable markerIn) { + mfUsersOverlay ovl = new mfUsersOverlay(context, markerIn); + getOverlays().add(ovl); + return ovl.getBase(); + } + + @Override + public cgMapMyOverlay createAddPositionOverlay(Activity activity, + cgSettings settingsIn) { + mfOverlay ovl = new mfOverlay(activity, settingsIn, overlayType.PositionOverlay); + getOverlays().add(ovl); + return (cgMapMyOverlay) ovl.getBase(); + } + + @Override + public cgOverlayScale createAddScaleOverlay(Activity activity, + cgSettings settingsIn) { + mfOverlay ovl = new mfOverlay(activity, settingsIn, overlayType.ScaleOverlay); + getOverlays().add(ovl); + return (cgOverlayScale) ovl.getBase(); + } + + @Override + public int getLatitudeSpan() { + + int span = 0; + + Projection projection = getProjection(); + + if (projection != null && getHeight() > 0) { + + GeoPoint low = projection.fromPixels(0, 0); + GeoPoint high = projection.fromPixels(0, getHeight()); + + if (low != null && high != null) { + span = Math.abs(high.getLatitudeE6() - low.getLatitudeE6()); + } + } + + return span; + } + + @Override + public int getLongitudeSpan() { + + int span = 0; + + Projection projection = getProjection(); + + if (projection != null && getWidth() > 0) { + GeoPoint low = projection.fromPixels(0, 0); + GeoPoint high = projection.fromPixels(getWidth(), 0); + + if (low != null && high != null) { + span = Math.abs(high.getLongitudeE6() - low.getLongitudeE6()); + } + } + + return span; + } + + @Override + public void preLoad() { + // Nothing to do here + } + + @Override + public int getMapZoomLevel() { + return getZoomLevel() + 1; + } + + @Override + public boolean needsScaleOverlay() { + return false; + } + + @Override + public void setBuiltinScale(boolean b) { + setScaleBar(b); + } + + @Override + public void setMapSource(cgSettings settings) { + + switch (settings.mapSource) { + case mapsforgeOsmarender: + setMapViewMode(MapViewMode.OSMARENDER_TILE_DOWNLOAD); + break; + case mapsforgeCycle: + setMapViewMode(MapViewMode.OPENCYCLEMAP_TILE_DOWNLOAD); + break; + case mapsforgeOffline: + if (MapDatabase.isValidMapFile(settings.getMapFile())) { + setMapViewMode(MapViewMode.CANVAS_RENDERER); + super.setMapFile(settings.getMapFile()); + } else { + setMapViewMode(MapViewMode.MAPNIK_TILE_DOWNLOAD); + } + break; + default: + setMapViewMode(MapViewMode.MAPNIK_TILE_DOWNLOAD); + } + } + + @Override + public void repaintRequired(OverlayBase overlay) { + + try { + mfOverlay ovl = (mfOverlay) overlay.getOverlayImpl(); + + if (ovl != null) { + ovl.requestRedraw(); + } + + } catch (Exception e) { + Log.e(cgSettings.tag, "mfMapView.repaintRequired: " + e.toString()); + } + } + + @Override + public void setOnDragListener(OnDragListener onDragListener) { + this.onDragListener = onDragListener; + } + + @Override + public boolean onTouchEvent(MotionEvent ev) { + gestureDetector.onTouchEvent(ev); + return super.onTouchEvent(ev); + } + + private class GestureListener extends SimpleOnGestureListener { + @Override + public boolean onDoubleTap(MotionEvent e) { + if (onDragListener != null) { + onDragListener.onDrag(); + } + return true; + } + + @Override + public boolean onScroll(MotionEvent e1, MotionEvent e2, + float distanceX, float distanceY) { + if (onDragListener != null) { + onDragListener.onDrag(); + } + return super.onScroll(e1, e2, distanceX, distanceY); + } + } +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfOverlay.java b/main/src/cgeo/geocaching/mapsforge/mfOverlay.java new file mode 100644 index 0000000..69ec499 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfOverlay.java @@ -0,0 +1,57 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.cgSettings; +import cgeo.geocaching.mapcommon.cgMapMyOverlay; +import cgeo.geocaching.mapcommon.cgOverlayScale; +import cgeo.geocaching.mapinterfaces.OverlayBase; +import cgeo.geocaching.mapinterfaces.OverlayImpl; + +import org.mapsforge.android.maps.Overlay; +import org.mapsforge.android.maps.Projection; + +import android.app.Activity; +import android.graphics.Canvas; +import android.graphics.Point; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class mfOverlay extends Overlay implements OverlayImpl { + + private OverlayBase overlayBase = null; + private Lock lock = new ReentrantLock(); + + public mfOverlay(Activity activityIn, cgSettings settingsIn, OverlayImpl.overlayType ovlType) { + + switch (ovlType) { + case PositionOverlay: + overlayBase = new cgMapMyOverlay(settingsIn, activityIn, this); + break; + case ScaleOverlay: + overlayBase = new cgOverlayScale(activityIn, settingsIn, this); + } + } + + @Override + protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, + Projection projection, byte drawZoomLevel) { + + if (overlayBase != null) { + overlayBase.drawOverlayBitmap(canvas, drawPosition, new mfMapProjection(projection), drawZoomLevel); + } + } + + public OverlayBase getBase() { + return overlayBase; + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } +} diff --git a/main/src/cgeo/geocaching/mapsforge/mfUsersOverlay.java b/main/src/cgeo/geocaching/mapsforge/mfUsersOverlay.java new file mode 100644 index 0000000..06b9f3e --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfUsersOverlay.java @@ -0,0 +1,112 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.mapcommon.cgUsersOverlay; +import cgeo.geocaching.mapinterfaces.ItemizedOverlayImpl; +import cgeo.geocaching.mapinterfaces.MapProjectionImpl; +import cgeo.geocaching.mapinterfaces.MapViewImpl; + +import org.mapsforge.android.maps.ItemizedOverlay; +import org.mapsforge.android.maps.Projection; + +import android.content.Context; +import android.graphics.Canvas; +import android.graphics.Point; +import android.graphics.drawable.Drawable; + +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +public class mfUsersOverlay extends ItemizedOverlay<mfUsersOverlayItem> implements ItemizedOverlayImpl { + + private cgUsersOverlay base; + private Lock lock = new ReentrantLock(); + + public mfUsersOverlay(Context contextIn, Drawable markerIn) { + super(boundCenter(markerIn)); + base = new cgUsersOverlay(this, contextIn); + } + + @Override + public cgUsersOverlay getBase() { + return base; + } + + @Override + protected mfUsersOverlayItem createItem(int i) { + if (base == null) + return null; + + return (mfUsersOverlayItem) base.createItem(i); + } + + @Override + public int size() { + if (base == null) + return 0; + + return base.size(); + } + + @Override + protected boolean onTap(int arg0) { + if (base == null) + return false; + + return base.onTap(arg0); + } + + @Override + protected void drawOverlayBitmap(Canvas canvas, Point drawPosition, + Projection projection, byte drawZoomLevel) { + + base.drawOverlayBitmap(canvas, drawPosition, new mfMapProjection(projection), drawZoomLevel); + } + + @Override + public void superPopulate() { + populate(); + } + + @Override + public Drawable superBoundCenter(Drawable markerIn) { + return super.boundCenter(markerIn); + } + + @Override + public Drawable superBoundCenterBottom(Drawable marker) { + return super.boundCenterBottom(marker); + } + + @Override + public void superSetLastFocusedItemIndex(int i) { + // Nothing to do here + } + + @Override + public boolean superOnTap(int index) { + return super.onTap(index); + } + + @Override + public void superDraw(Canvas canvas, MapViewImpl mapView, boolean shadow) { + // Nothing to do here + } + + @Override + public void superDrawOverlayBitmap(Canvas canvas, Point drawPosition, + MapProjectionImpl projection, byte drawZoomLevel) { + + super.drawOverlayBitmap(canvas, drawPosition, (Projection) projection.getImpl(), drawZoomLevel); + } + + @Override + public void lock() { + lock.lock(); + } + + @Override + public void unlock() { + lock.unlock(); + } + +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/mapsforge/mfUsersOverlayItem.java b/main/src/cgeo/geocaching/mapsforge/mfUsersOverlayItem.java new file mode 100644 index 0000000..d53a863 --- /dev/null +++ b/main/src/cgeo/geocaching/mapsforge/mfUsersOverlayItem.java @@ -0,0 +1,44 @@ +package cgeo.geocaching.mapsforge; + +import cgeo.geocaching.R; +import cgeo.geocaching.cgUser; +import cgeo.geocaching.mapinterfaces.UserOverlayItemImpl; + +import org.mapsforge.android.maps.GeoPoint; +import org.mapsforge.android.maps.OverlayItem; + +import android.content.Context; +import android.graphics.drawable.Drawable; + +public class mfUsersOverlayItem extends OverlayItem implements UserOverlayItemImpl { + private Context context = null; + private cgUser user = null; + + public mfUsersOverlayItem(Context contextIn, cgUser userIn) { + super(new GeoPoint((int) (userIn.coords.getLatitudeE6()), userIn.coords.getLongitudeE6()), userIn.username, ""); + + context = contextIn; + user = userIn; + } + + @Override + public Drawable getMarker(int state) { + Drawable marker = null; + + if (user != null && user.located != null && user.located.getTime() >= (System.currentTimeMillis() - (20 * 60 * 1000))) { + marker = context.getResources().getDrawable(R.drawable.user_location_active); + } else { + marker = context.getResources().getDrawable(R.drawable.user_location); + } + + marker.setBounds(0, 0, marker.getIntrinsicWidth(), marker.getIntrinsicHeight()); + marker.setAlpha(190); + setMarker(marker); + + return marker; + } + + public cgUser getUser() { + return user; + } +} diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java new file mode 100644 index 0000000..03db8df --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java @@ -0,0 +1,46 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgSettings; + +import android.util.Log; + +/** + * abstract super implementation for all cache comparators + * + */ +public abstract class AbstractCacheComparator implements CacheComparator { + + @Override + public final int compare(cgCache cache1, cgCache cache2) { + try { + // first check that we have all necessary data for the comparison + if (!canCompare(cache1, cache2)) { + return 0; + } + return compareCaches(cache1, cache2); + } catch (Exception e) { + Log.e(cgSettings.tag, "AbstractCacheComparator.compare: " + e.toString()); + } + return 0; + } + + /** + * check necessary preconditions (like missing fields) before running the comparison itself + * + * @param cache1 + * @param cache2 + * @return + */ + protected abstract boolean canCompare(final cgCache cache1, final cgCache cache2); + + /** + * compares two caches. Logging and exception handling is implemented outside this method already. + * + * @param cache1 + * @param cache2 + * @return an integer < 0 if cache1 is less than cache2, 0 if they are equal, and > 0 if cache1 is greater than + * cache2. + */ + protected abstract int compareCaches(final cgCache cache1, final cgCache cache2); +} diff --git a/main/src/cgeo/geocaching/sorting/CacheComparator.java b/main/src/cgeo/geocaching/sorting/CacheComparator.java new file mode 100644 index 0000000..c9b5150 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/CacheComparator.java @@ -0,0 +1,9 @@ +package cgeo.geocaching.sorting; + +import java.util.Comparator; + +import cgeo.geocaching.cgCache; + +public interface CacheComparator extends Comparator<cgCache> { + +} diff --git a/main/src/cgeo/geocaching/sorting/DateComparator.java b/main/src/cgeo/geocaching/sorting/DateComparator.java new file mode 100644 index 0000000..b59e84e --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/DateComparator.java @@ -0,0 +1,35 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +import java.util.Date; + +/** + * compares caches by date + * + * @author bananeweizen + * + */ +public class DateComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return true; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + Date date1 = cache1.hidden; + Date date2 = cache2.hidden; + if (date1 != null && date2 != null) { + return date1.compareTo(date2); + } + if (date1 != null) { + return -1; + } + if (date2 != null) { + return 1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/sorting/DifficultyComparator.java b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java new file mode 100644 index 0000000..846417c --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/DifficultyComparator.java @@ -0,0 +1,25 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by difficulty + * + */ +public class DifficultyComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.difficulty != null && cache2.difficulty != null; + } + + @Override + protected int compareCaches(final cgCache cache1, final cgCache cache2) { + if (cache1.difficulty > cache2.difficulty) { + return 1; + } else if (cache2.difficulty > cache1.difficulty) { + return -1; + } + return 0; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java new file mode 100644 index 0000000..7051718 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.geopoint.Geopoint; + +/** + * sorts caches by distance to current position + * + */ +public class DistanceComparator extends AbstractCacheComparator { + private final Geopoint coords; + + public DistanceComparator(final Geopoint coords) { + this.coords = coords; + } + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return true; + } + + @Override + protected int compareCaches(final cgCache cache1, final cgCache cache2) { + if ((cache1.coords == null || cache2.coords == null) + && cache1.distance != null && cache2.distance != null) { + return Double.compare(cache1.distance, cache2.distance); + } else { + if (cache1.coords == null) { + return 1; + } + if (cache2.coords == null) { + return -1; + } + + return Float.compare(coords.distanceTo(cache1.coords), + coords.distanceTo(cache2.coords)); + } + } + +} diff --git a/main/src/cgeo/geocaching/sorting/FindsComparator.java b/main/src/cgeo/geocaching/sorting/FindsComparator.java new file mode 100644 index 0000000..efde22d --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/FindsComparator.java @@ -0,0 +1,40 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgeoapplication; + +public class FindsComparator extends AbstractCacheComparator implements + CacheComparator { + + private cgeoapplication app; + + public FindsComparator(cgeoapplication app) { + this.app = app; + } + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.logCounts != null && cache2.logCounts != null; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + int finds1 = getFindsCount(cache1); + int finds2 = getFindsCount(cache2); + return finds2 - finds1; + } + + private int getFindsCount(cgCache cache) { + int finds = 0; + if (cache.logCounts.isEmpty()) { + cache.logCounts = app.loadLogCounts(cache.geocode); + } + Integer logged = cache.logCounts.get(cgBase.LOG_FOUND_IT); + if (logged != null) { + finds = logged; + } + return finds; + } + +} diff --git a/main/src/cgeo/geocaching/sorting/GeocodeComparator.java b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java new file mode 100644 index 0000000..136d7f5 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/GeocodeComparator.java @@ -0,0 +1,28 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +import org.apache.commons.lang3.StringUtils; + +/** + * sorts caches by GC code, therefore effectively sorting by cache age + * + */ +public class GeocodeComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return StringUtils.isNotBlank(cache1.geocode) + && StringUtils.isNotBlank(cache2.geocode); + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + if (cache1.geocode.length() > cache2.geocode.length()) { + return 1; + } else if (cache2.geocode.length() > cache1.geocode.length()) { + return -1; + } + return cache1.geocode.compareToIgnoreCase(cache2.geocode); + } +} diff --git a/main/src/cgeo/geocaching/sorting/InventoryComparator.java b/main/src/cgeo/geocaching/sorting/InventoryComparator.java new file mode 100644 index 0000000..0833094 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/InventoryComparator.java @@ -0,0 +1,29 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by number of items in inventory + * + * @author bananeweizen + * + */ +public class InventoryComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return true; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + int itemCount1 = cache1.inventoryItems; + int itemCount2 = cache2.inventoryItems; + if (itemCount1 < itemCount2) { + return 1; + } else if (itemCount2 < itemCount1) { + return -1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/sorting/NameComparator.java b/main/src/cgeo/geocaching/sorting/NameComparator.java new file mode 100644 index 0000000..d301180 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/NameComparator.java @@ -0,0 +1,22 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +import org.apache.commons.lang3.StringUtils; + +/** + * sorts caches by name + * + */ +public class NameComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return StringUtils.isNotBlank(cache1.name) && StringUtils.isNotBlank(cache2.name); + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + return cache1.name.compareToIgnoreCase(cache2.name); + } +} diff --git a/main/src/cgeo/geocaching/sorting/PopularityComparator.java b/main/src/cgeo/geocaching/sorting/PopularityComparator.java new file mode 100644 index 0000000..dbc418a --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/PopularityComparator.java @@ -0,0 +1,25 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by popularity (favorite count) + * + */ +public class PopularityComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.favouriteCnt != null && cache2.favouriteCnt != null; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + if (cache1.favouriteCnt < cache2.favouriteCnt) { + return 1; + } else if (cache2.favouriteCnt < cache1.favouriteCnt) { + return -1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/sorting/RatingComparator.java b/main/src/cgeo/geocaching/sorting/RatingComparator.java new file mode 100644 index 0000000..b7140c6 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/RatingComparator.java @@ -0,0 +1,36 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by gcvote.com rating + * + */ +public class RatingComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.rating != null && cache2.rating != null; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + Float rating1 = cache1.rating; + Float rating2 = cache2.rating; + + // voting can be disabled for caches, then assume an average rating instead + if (rating1 == 0.0) { + rating1 = 2.5f; + } + if (rating2 == 0.0) { + rating2 = 2.5f; + } + + if (rating1 < rating2) { + return 1; + } else if (rating2 < rating1) { + return -1; + } + return 0; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/sorting/SizeComparator.java b/main/src/cgeo/geocaching/sorting/SizeComparator.java new file mode 100644 index 0000000..a633a06 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/SizeComparator.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by size + * + */ +public class SizeComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.size != null && cache2.size != null; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + return cache2.size.comparable - cache1.size.comparable; + } +}
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/sorting/StateComparator.java b/main/src/cgeo/geocaching/sorting/StateComparator.java new file mode 100644 index 0000000..f56d3fe --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/StateComparator.java @@ -0,0 +1,32 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sort caches by state (normal, disabled, archived) + * + */ +public class StateComparator extends AbstractCacheComparator implements + CacheComparator { + + @Override + protected boolean canCompare(final cgCache cache1, final cgCache cache2) { + return true; + } + + @Override + protected int compareCaches(final cgCache cache1, final cgCache cache2) { + return getState(cache1) - getState(cache2); + } + + private static int getState(final cgCache cache) { + if (cache.disabled) { + return 1; + } + if (cache.archived) { + return 2; + } + return 0; + } + +} diff --git a/main/src/cgeo/geocaching/sorting/TerrainComparator.java b/main/src/cgeo/geocaching/sorting/TerrainComparator.java new file mode 100644 index 0000000..88c75e4 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/TerrainComparator.java @@ -0,0 +1,25 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by terrain rating + * + */ +public class TerrainComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.terrain != null && cache2.terrain != null; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + if (cache1.terrain > cache2.terrain) { + return 1; + } else if (cache2.terrain > cache1.terrain) { + return -1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/sorting/VisitComparator.java b/main/src/cgeo/geocaching/sorting/VisitComparator.java new file mode 100644 index 0000000..597efb5 --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/VisitComparator.java @@ -0,0 +1,26 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by last visited date + * + */ +public class VisitComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return cache1.visitedDate != null && cache1.visitedDate > 0 + && cache2.visitedDate != null && cache2.visitedDate > 0; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + if (cache1.visitedDate > cache2.visitedDate) { + return -1; + } else if (cache1.visitedDate < cache2.visitedDate) { + return 1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/sorting/VoteComparator.java b/main/src/cgeo/geocaching/sorting/VoteComparator.java new file mode 100644 index 0000000..2a4b4ac --- /dev/null +++ b/main/src/cgeo/geocaching/sorting/VoteComparator.java @@ -0,0 +1,39 @@ +package cgeo.geocaching.sorting; + +import cgeo.geocaching.cgCache; + +/** + * sorts caches by the users own voting (if available at all) + * + * @author bananeweizen + * + */ +public class VoteComparator extends AbstractCacheComparator { + + @Override + protected boolean canCompare(cgCache cache1, cgCache cache2) { + return true; + } + + @Override + protected int compareCaches(cgCache cache1, cgCache cache2) { + // if there is no vote available, put that cache at the end of the list + float vote1 = 0; + if (cache1.myVote != null) { + vote1 = cache1.myVote; + } + + float vote2 = 0; + if (cache2.myVote != null) { + vote2 = cache2.myVote; + } + + // compare + if (vote1 < vote2) { + return 1; + } else if (vote2 < vote1) { + return -1; + } + return 0; + } +} diff --git a/main/src/cgeo/geocaching/utils/CollectionUtils.java b/main/src/cgeo/geocaching/utils/CollectionUtils.java new file mode 100644 index 0000000..06e2419 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/CollectionUtils.java @@ -0,0 +1,24 @@ +package cgeo.geocaching.utils; + +import java.util.List; +import java.util.Map; + +public class CollectionUtils { + + public static <T> boolean isEmpty(List<T> list) { + return (list != null && list.size() == 0); + } + + public static <T, T2> boolean isEmpty(Map<T, T2> map) { + return (map != null && map.size() == 0); + } + + public static <T> boolean isNotEmpty(List<T> list) { + return (list != null && list.size() != 0); + } + + public static <T, T2> boolean isNotEmpty(Map<T, T2> map) { + return (map != null && map.size() != 0); + } + +} |
