aboutsummaryrefslogtreecommitdiffstats
path: root/main/src/cgeo
diff options
context:
space:
mode:
authorSamuel Tardieu <sam@rfc1149.net>2011-09-16 14:36:28 +0200
committerSamuel Tardieu <sam@rfc1149.net>2011-09-16 14:36:28 +0200
commit579ef7a535489d4aa632db11667a3b01deb6cafd (patch)
tree55810021c02ac7d80d3a9702ef0b59e4af154b9c /main/src/cgeo
parent96ea21fd50334479c262da692038965d0e4d596a (diff)
downloadcgeo-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')
-rw-r--r--main/src/cgeo/geocaching/CookieJar.java96
-rw-r--r--main/src/cgeo/geocaching/ICache.java101
-rw-r--r--main/src/cgeo/geocaching/LogTemplateProvider.java155
-rw-r--r--main/src/cgeo/geocaching/StaticMapsProvider.java190
-rw-r--r--main/src/cgeo/geocaching/UnknownTagsHandler.java33
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractActivity.java86
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractListActivity.java87
-rw-r--r--main/src/cgeo/geocaching/activity/ActivityMixin.java150
-rw-r--r--main/src/cgeo/geocaching/activity/IAbstractActivity.java36
-rw-r--r--main/src/cgeo/geocaching/apps/AbstractApp.java82
-rw-r--r--main/src/cgeo/geocaching/apps/AbstractAppFactory.java16
-rw-r--r--main/src/cgeo/geocaching/apps/AbstractLocusApp.java272
-rw-r--r--main/src/cgeo/geocaching/apps/App.java11
-rw-r--r--main/src/cgeo/geocaching/apps/LocusDataStorageProvider.java71
-rw-r--r--main/src/cgeo/geocaching/apps/cache/AbstractGeneralApp.java34
-rw-r--r--main/src/cgeo/geocaching/apps/cache/GccApp.java11
-rw-r--r--main/src/cgeo/geocaching/apps/cache/GeneralApp.java14
-rw-r--r--main/src/cgeo/geocaching/apps/cache/GeneralAppsFactory.java48
-rw-r--r--main/src/cgeo/geocaching/apps/cache/WhereYouGoApp.java17
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractInternalMap.java15
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractNavigationApp.java15
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsApp.java67
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/GoogleNavigationApp.java102
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/InternalMap.java47
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/LocusApp.java57
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/NavigationApp.java20
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java65
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java59
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/RMapsApp.java68
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java47
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/StaticMapApp.java49
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/StreetviewApp.java60
-rw-r--r--main/src/cgeo/geocaching/apps/cachelist/CacheListApp.java19
-rw-r--r--main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java79
-rw-r--r--main/src/cgeo/geocaching/apps/cachelist/InternalCacheListMap.java37
-rw-r--r--main/src/cgeo/geocaching/apps/cachelist/LocusCacheListApp.java36
-rw-r--r--main/src/cgeo/geocaching/cgBase.java4809
-rw-r--r--main/src/cgeo/geocaching/cgCache.java461
-rw-r--r--main/src/cgeo/geocaching/cgCacheListAdapter.java912
-rw-r--r--main/src/cgeo/geocaching/cgCacheView.java28
-rw-r--r--main/src/cgeo/geocaching/cgCacheWrap.java15
-rw-r--r--main/src/cgeo/geocaching/cgCompass.java271
-rw-r--r--main/src/cgeo/geocaching/cgCompassMini.java172
-rw-r--r--main/src/cgeo/geocaching/cgCoord.java46
-rw-r--r--main/src/cgeo/geocaching/cgData.java3272
-rw-r--r--main/src/cgeo/geocaching/cgDestination.java72
-rw-r--r--main/src/cgeo/geocaching/cgDirection.java75
-rw-r--r--main/src/cgeo/geocaching/cgDirectionImg.java95
-rw-r--r--main/src/cgeo/geocaching/cgDistanceView.java44
-rw-r--r--main/src/cgeo/geocaching/cgGPXListAdapter.java75
-rw-r--r--main/src/cgeo/geocaching/cgGPXView.java9
-rw-r--r--main/src/cgeo/geocaching/cgGeo.java441
-rw-r--r--main/src/cgeo/geocaching/cgHtmlImg.java295
-rw-r--r--main/src/cgeo/geocaching/cgImage.java7
-rw-r--r--main/src/cgeo/geocaching/cgList.java21
-rw-r--r--main/src/cgeo/geocaching/cgLog.java15
-rw-r--r--main/src/cgeo/geocaching/cgLogForm.java15
-rw-r--r--main/src/cgeo/geocaching/cgMapfileListAdapter.java91
-rw-r--r--main/src/cgeo/geocaching/cgOAuth.java58
-rw-r--r--main/src/cgeo/geocaching/cgRating.java7
-rw-r--r--main/src/cgeo/geocaching/cgResponse.java40
-rw-r--r--main/src/cgeo/geocaching/cgSearch.java39
-rw-r--r--main/src/cgeo/geocaching/cgSearchHandler.java105
-rw-r--r--main/src/cgeo/geocaching/cgSearchThread.java46
-rw-r--r--main/src/cgeo/geocaching/cgSelectMapfile.java61
-rw-r--r--main/src/cgeo/geocaching/cgSettings.java610
-rw-r--r--main/src/cgeo/geocaching/cgTrackable.java37
-rw-r--r--main/src/cgeo/geocaching/cgTrackableLog.java9
-rw-r--r--main/src/cgeo/geocaching/cgUpdateDir.java7
-rw-r--r--main/src/cgeo/geocaching/cgUpdateLoc.java7
-rw-r--r--main/src/cgeo/geocaching/cgUser.java13
-rw-r--r--main/src/cgeo/geocaching/cgWaypoint.java97
-rw-r--r--main/src/cgeo/geocaching/cgeo.java756
-rw-r--r--main/src/cgeo/geocaching/cgeoabout.java90
-rw-r--r--main/src/cgeo/geocaching/cgeoaddresses.java178
-rw-r--r--main/src/cgeo/geocaching/cgeoadvsearch.java499
-rw-r--r--main/src/cgeo/geocaching/cgeoapplication.java842
-rw-r--r--main/src/cgeo/geocaching/cgeoauth.java392
-rw-r--r--main/src/cgeo/geocaching/cgeocaches.java2642
-rw-r--r--main/src/cgeo/geocaching/cgeocoords.java494
-rw-r--r--main/src/cgeo/geocaching/cgeodate.java53
-rw-r--r--main/src/cgeo/geocaching/cgeodetail.java2128
-rw-r--r--main/src/cgeo/geocaching/cgeogpxes.java132
-rw-r--r--main/src/cgeo/geocaching/cgeohelpers.java64
-rw-r--r--main/src/cgeo/geocaching/cgeoimages.java303
-rw-r--r--main/src/cgeo/geocaching/cgeoinit.java1081
-rw-r--r--main/src/cgeo/geocaching/cgeonavigate.java463
-rw-r--r--main/src/cgeo/geocaching/cgeopoint.java557
-rw-r--r--main/src/cgeo/geocaching/cgeopopup.java670
-rw-r--r--main/src/cgeo/geocaching/cgeosmaps.java147
-rw-r--r--main/src/cgeo/geocaching/cgeotouch.java458
-rw-r--r--main/src/cgeo/geocaching/cgeotrackable.java629
-rw-r--r--main/src/cgeo/geocaching/cgeotrackables.java152
-rw-r--r--main/src/cgeo/geocaching/cgeovisit.java822
-rw-r--r--main/src/cgeo/geocaching/cgeowaypoint.java348
-rw-r--r--main/src/cgeo/geocaching/cgeowaypointadd.java358
-rw-r--r--main/src/cgeo/geocaching/compatibility/AndroidLevel8.java27
-rw-r--r--main/src/cgeo/geocaching/compatibility/AndroidLevel8Internal.java15
-rw-r--r--main/src/cgeo/geocaching/compatibility/Compatibility.java72
-rw-r--r--main/src/cgeo/geocaching/connector/AbstractConnector.java26
-rw-r--r--main/src/cgeo/geocaching/connector/ConnectorFactory.java31
-rw-r--r--main/src/cgeo/geocaching/connector/GCConnector.java33
-rw-r--r--main/src/cgeo/geocaching/connector/IConnector.java15
-rw-r--r--main/src/cgeo/geocaching/connector/OCConnector.java21
-rw-r--r--main/src/cgeo/geocaching/connector/OXConnector.java23
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheSize.java42
-rw-r--r--main/src/cgeo/geocaching/enumerations/CacheType.java52
-rw-r--r--main/src/cgeo/geocaching/enumerations/WaypointType.java40
-rw-r--r--main/src/cgeo/geocaching/files/FileList.java246
-rw-r--r--main/src/cgeo/geocaching/files/FileParser.java53
-rw-r--r--main/src/cgeo/geocaching/files/GPX10Parser.java20
-rw-r--r--main/src/cgeo/geocaching/files/GPX11Parser.java20
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java795
-rw-r--r--main/src/cgeo/geocaching/files/LocParser.java177
-rw-r--r--main/src/cgeo/geocaching/filter/cgFilter.java30
-rw-r--r--main/src/cgeo/geocaching/filter/cgFilterBySize.java24
-rw-r--r--main/src/cgeo/geocaching/filter/cgFilterByTrackables.java14
-rw-r--r--main/src/cgeo/geocaching/filter/cgFilterByType.java20
-rw-r--r--main/src/cgeo/geocaching/geopoint/DistanceParser.java49
-rw-r--r--main/src/cgeo/geocaching/geopoint/Geopoint.java274
-rw-r--r--main/src/cgeo/geocaching/geopoint/GeopointFormatter.java219
-rw-r--r--main/src/cgeo/geocaching/geopoint/GeopointParser.java173
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleCacheOverlay.java116
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleCacheOverlayItem.java28
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleGeoPoint.java13
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleMapActivity.java123
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleMapController.java37
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleMapFactory.java50
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleMapProjection.java29
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleMapView.java194
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleOverlay.java56
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleUsersOverlay.java109
-rw-r--r--main/src/cgeo/geocaching/googlemaps/googleUsersOverlayItem.java44
-rw-r--r--main/src/cgeo/geocaching/mapcommon/ItemizedOverlayBase.java66
-rw-r--r--main/src/cgeo/geocaching/mapcommon/MapBase.java71
-rw-r--r--main/src/cgeo/geocaching/mapcommon/cgMapMyOverlay.java224
-rw-r--r--main/src/cgeo/geocaching/mapcommon/cgMapOverlay.java359
-rw-r--r--main/src/cgeo/geocaching/mapcommon/cgOverlayScale.java154
-rw-r--r--main/src/cgeo/geocaching/mapcommon/cgUsersOverlay.java188
-rw-r--r--main/src/cgeo/geocaching/mapcommon/cgeomap.java1794
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/ActivityImpl.java43
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/CacheOverlayItemImpl.java18
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/GeoPointImpl.java16
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/ItemizedOverlayImpl.java35
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/MapControllerImpl.java20
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/MapFactory.java32
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/MapProjectionImpl.java18
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/MapViewImpl.java72
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/OnDragListener.java13
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/OverlayBase.java21
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/OverlayImpl.java21
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/OverlayItemImpl.java19
-rw-r--r--main/src/cgeo/geocaching/mapinterfaces/UserOverlayItemImpl.java15
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfCacheOverlay.java111
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfCacheOverlayItem.java35
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfGeoPoint.java12
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfMapActivity.java117
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfMapController.java48
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfMapFactory.java49
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfMapProjection.java29
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfMapView.java245
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfOverlay.java57
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfUsersOverlay.java112
-rw-r--r--main/src/cgeo/geocaching/mapsforge/mfUsersOverlayItem.java44
-rw-r--r--main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java46
-rw-r--r--main/src/cgeo/geocaching/sorting/CacheComparator.java9
-rw-r--r--main/src/cgeo/geocaching/sorting/DateComparator.java35
-rw-r--r--main/src/cgeo/geocaching/sorting/DifficultyComparator.java25
-rw-r--r--main/src/cgeo/geocaching/sorting/DistanceComparator.java40
-rw-r--r--main/src/cgeo/geocaching/sorting/FindsComparator.java40
-rw-r--r--main/src/cgeo/geocaching/sorting/GeocodeComparator.java28
-rw-r--r--main/src/cgeo/geocaching/sorting/InventoryComparator.java29
-rw-r--r--main/src/cgeo/geocaching/sorting/NameComparator.java22
-rw-r--r--main/src/cgeo/geocaching/sorting/PopularityComparator.java25
-rw-r--r--main/src/cgeo/geocaching/sorting/RatingComparator.java36
-rw-r--r--main/src/cgeo/geocaching/sorting/SizeComparator.java20
-rw-r--r--main/src/cgeo/geocaching/sorting/StateComparator.java32
-rw-r--r--main/src/cgeo/geocaching/sorting/TerrainComparator.java25
-rw-r--r--main/src/cgeo/geocaching/sorting/VisitComparator.java26
-rw-r--r--main/src/cgeo/geocaching/sorting/VoteComparator.java39
-rw-r--r--main/src/cgeo/geocaching/utils/CollectionUtils.java24
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[^>]+>&nbsp;([^<]+)</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&currency_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&currency_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);
+ }
+
+}