diff options
53 files changed, 1911 insertions, 1624 deletions
diff --git a/cgeo-calendar/src/cgeo/calendar/CalendarActivity.java b/cgeo-calendar/src/cgeo/calendar/CalendarActivity.java index 6ff9450..b339954 100644 --- a/cgeo-calendar/src/cgeo/calendar/CalendarActivity.java +++ b/cgeo-calendar/src/cgeo/calendar/CalendarActivity.java @@ -4,6 +4,7 @@ import android.app.Activity; import android.app.AlertDialog; import android.content.ContentValues; import android.content.DialogInterface; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import android.os.Bundle; @@ -49,8 +50,14 @@ public final class CalendarActivity extends Activity { name = getParameter(ICalendar.PARAM_NAME); location = getParameter(ICalendar.PARAM_LOCATION); coords = getParameter(ICalendar.PARAM_COORDS); + if (name.length() > 0 && hiddenDate.length() > 0) { - selectCalendarForAdding(); + if (Compatibility.isLevel14()) { + addToCalendarLevel14(); + finish(); + } else { + selectCalendarForAdding(); + } } } catch (Exception e) { Log.e(LOG_TAG, e.getMessage(), e); @@ -81,33 +88,32 @@ public final class CalendarActivity extends Activity { // TODO: Handle missing provider final Cursor cursor = managedQuery(calendarProvider, projection, "selected=1", null, null); + if (cursor == null || cursor.getCount() <= 0) { + showToast(getResources().getString(R.string.event_fail)); + return; + } + final Map<Integer, String> calendars = new HashMap<Integer, String>(); - if (cursor != null) { - if (cursor.getCount() > 0) { - cursor.moveToFirst(); - - final int indexId = cursor.getColumnIndex("_id"); - final int indexName = cursor.getColumnIndex("displayName"); - - do { - final String idString = cursor.getString(indexId); - if (idString != null) { - try { - int id = Integer.parseInt(idString); - final String calName = cursor.getString(indexName); - - if (id > 0 && calName != null) { - calendars.put(id, calName); - } - } catch (NumberFormatException e) { - // TODO Auto-generated catch block - e.printStackTrace(); - } + cursor.moveToFirst(); + + final int indexId = cursor.getColumnIndex("_id"); + final int indexName = cursor.getColumnIndex("displayName"); + + do { + final String idString = cursor.getString(indexId); + if (idString != null) { + try { + int id = Integer.parseInt(idString); + final String calName = cursor.getString(indexName); + + if (id > 0 && calName != null) { + calendars.put(id, calName); } - } while (cursor.moveToNext()); + } catch (NumberFormatException e) { + Log.e(LOG_TAG, "CalendarActivity.selectCalendarForAdding", e); + } } - cursor.close(); - } + } while (cursor.moveToNext()); if (calendars.isEmpty()) { return; @@ -135,59 +141,79 @@ public final class CalendarActivity extends Activity { } /** - * @param calendars - * - * @param index - * The selected calendar + * @return <code>Date</code> based on hidden date. Time is set to 00:00:00. */ - private void addToCalendar(Integer calendarId) { - try { - final Uri calendarProvider = Compatibility.getCalenderEventsProviderURI(); - - // date - final Date eventDate = new Date(Long.parseLong(hiddenDate)); - eventDate.setHours(0); - eventDate.setMinutes(0); - eventDate.setSeconds(0); - - // description - final StringBuilder description = new StringBuilder(); - description.append(url); - if (shortDesc.length() > 0) { - // remove images in short description - final Spanned spanned = Html.fromHtml(shortDesc, null, null); - String text = spanned.toString(); - final ImageSpan[] spans = spanned.getSpans(0, spanned.length(), ImageSpan.class); - for (int i = spans.length - 1; i >= 0; i--) { - text = text.substring(0, spanned.getSpanStart(spans[i])) + text.substring(spanned.getSpanEnd(spans[i])); - } - if (text.length() > 0) { - description.append("\n\n"); - description.append(text); - } - } + private Date parseDate() { + final Date eventDate = new Date(Long.parseLong(hiddenDate)); + eventDate.setHours(0); + eventDate.setMinutes(0); + eventDate.setSeconds(0); - if (personalNote.length() > 0) { - description.append("\n\n").append(Html.fromHtml(personalNote).toString()); + return eventDate; + } + + /** + * @return description string with images removed and personal note included + */ + private String parseDescription() { + final StringBuilder description = new StringBuilder(); + description.append(url); + if (shortDesc.length() > 0) { + // remove images in short description + final Spanned spanned = Html.fromHtml(shortDesc, null, null); + String text = spanned.toString(); + final ImageSpan[] spans = spanned.getSpans(0, spanned.length(), ImageSpan.class); + for (int i = spans.length - 1; i >= 0; i--) { + text = text.substring(0, spanned.getSpanStart(spans[i])) + text.substring(spanned.getSpanEnd(spans[i])); } + if (text.length() > 0) { + description.append("\n\n"); + description.append(text); + } + } + + if (personalNote.length() > 0) { + description.append("\n\n").append(Html.fromHtml(personalNote).toString()); + } - // location - final StringBuilder locBuffer = new StringBuilder(); - if (coords.length() > 0) { - locBuffer.append(coords); + return description.toString(); + } + + /** + * @return location string with coordinates and location + */ + private String parseLocation() { + final StringBuilder locBuffer = new StringBuilder(); + if (coords.length() > 0) { + locBuffer.append(coords); + } + if (location.length() > 0) { + boolean addParentheses = false; + if (locBuffer.length() > 0) { + addParentheses = true; + locBuffer.append(" ("); } - if (location.length() > 0) { - boolean addParentheses = false; - if (locBuffer.length() > 0) { - addParentheses = true; - locBuffer.append(" ("); - } - locBuffer.append(Html.fromHtml(location).toString()); - if (addParentheses) { - locBuffer.append(')'); - } + locBuffer.append(Html.fromHtml(location).toString()); + if (addParentheses) { + locBuffer.append(')'); } + } + + return locBuffer.toString(); + } + + /** + * @param calendarId + * The selected calendar + */ + private void addToCalendar(Integer calendarId) { + try { + final Uri calendarProvider = Compatibility.getCalendarEventsProviderURI(); + + final Date eventDate = parseDate(); + final String description = parseDescription(); + final String location = parseLocation(); // values final ContentValues event = new ContentValues(); @@ -196,10 +222,10 @@ public final class CalendarActivity extends Activity { event.put("dtend", eventDate.getTime() + 43200000 + 3600000); // + one hour event.put("eventTimezone", "UTC"); event.put("title", Html.fromHtml(name).toString()); - event.put("description", description.toString()); + event.put("description", description); - if (locBuffer.length() > 0) { - event.put("eventLocation", locBuffer.toString()); + if (location.length() > 0) { + event.put("eventLocation", location); } event.put("allDay", 1); event.put("hasAlarm", 0); @@ -210,7 +236,40 @@ public final class CalendarActivity extends Activity { } catch (Exception e) { showToast(getResources().getString(R.string.event_fail)); - Log.e(LOG_TAG, "CalendarActivity.addToCalendarFn: " + e.toString()); + Log.e(LOG_TAG, "CalendarActivity.addToCalendarFn", e); + } + } + + /** + * Add cache to calendar in Android versions 4.0 and greater using <code>Intent</code>. This does not require + * calendar permissions. + * TODO Does this work with apps other than default calendar app? + */ + private void addToCalendarLevel14() { + try { + final Date eventDate = parseDate(); + final String description = parseDescription(); + final String location = parseLocation(); + + /* + * TODO These strings are available as constants starting with API 14 and can be used when + * targetSdkVersion changes to 14. For example CalendarContract.EXTRA_EVENT_BEGIN_TIME and + * Events.TITLE + */ + final Intent intent = new Intent(Intent.ACTION_INSERT) + .setData(Compatibility.getCalendarEventsProviderURI()) + .putExtra("beginTime", eventDate.getTime() + 43200000) + .putExtra("allDay", true) + .putExtra("title", Html.fromHtml(name).toString()) + .putExtra("description", description) + .putExtra("hasAlarm", false) + .putExtra("eventTimezone", "UTC") + .putExtra("eventLocation", location); + startActivity(intent); + } catch (Exception e) { + showToast(getResources().getString(R.string.event_fail)); + + Log.e(LOG_TAG, "CalendarActivity.addToCalendarLevel14: " + e.toString()); } } diff --git a/cgeo-calendar/src/cgeo/calendar/Compatibility.java b/cgeo-calendar/src/cgeo/calendar/Compatibility.java index 7d770ea..905d6df 100644 --- a/cgeo-calendar/src/cgeo/calendar/Compatibility.java +++ b/cgeo-calendar/src/cgeo/calendar/Compatibility.java @@ -7,12 +7,17 @@ public final class Compatibility { private final static int sdkVersion = Integer.parseInt(Build.VERSION.SDK); private final static boolean isLevel8 = sdkVersion >= 8; + private final static boolean isLevel14 = sdkVersion >= 14; public static Uri getCalendarProviderURI() { return Uri.parse(isLevel8 ? "content://com.android.calendar/calendars" : "content://calendar/calendars"); } - public static Uri getCalenderEventsProviderURI() { + public static Uri getCalendarEventsProviderURI() { return Uri.parse(isLevel8 ? "content://com.android.calendar/events" : "content://calendar/events"); } + + public static boolean isLevel14() { + return isLevel14; + } } diff --git a/main/res/layout/cacheview_details.xml b/main/res/layout/cacheview_details.xml index 3da0789..4baf5c2 100644 --- a/main/res/layout/cacheview_details.xml +++ b/main/res/layout/cacheview_details.xml @@ -136,37 +136,6 @@ </RelativeLayout>
</LinearLayout>
- <!-- Debug info Box -->
-
- <LinearLayout
- android:id="@+id/debug_box"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:orientation="vertical" >
-
- <View
- style="@style/separator_horizontal"
- android:layout_marginBottom="9dp"
- android:layout_marginTop="9dp" />
-
- <RelativeLayout
- android:layout_width="fill_parent"
- android:layout_height="wrap_content" >
-
- <TextView
- android:id="@+id/debug_text"
- android:layout_width="fill_parent"
- android:layout_height="wrap_content"
- android:layout_alignParentLeft="true"
- android:layout_gravity="left"
- android:layout_marginLeft="6dip"
- android:layout_marginRight="130dip"
- android:paddingRight="3dip"
- android:textColor="?text_color"
- android:textSize="14dip" />
- </RelativeLayout>
- </LinearLayout>
-
<!-- License Box -->
<LinearLayout
diff --git a/main/res/values-de/strings.xml b/main/res/values-de/strings.xml index b88d066..1bb2dc6 100644 --- a/main/res/values-de/strings.xml +++ b/main/res/values-de/strings.xml @@ -165,7 +165,6 @@ <string name="err_store_failed">Speichern des Caches fehlgeschlagen.</string> <string name="err_refresh_failed">Aktualisierung fehlgeschlagen.</string> <string name="err_dwld_details_failed">Download der Cache-Details fehlgeschlagen.</string> - <string name="err_dwld_details_failed_reason">Download der Cache-Details fehlgeschlagen, wegen</string> <string name="err_load_descr_failed">Laden der Cachebeschreibung fehlgeschlagen.</string> <string name="err_location_unknown">c:geo erkennt die Position des Caches nicht.</string> <string name="err_missing_device_name">Bitte lege einen Namen für dein Handy fest, bevor du dich registrierst.</string> @@ -423,7 +422,7 @@ <string name="init_maptrail">Zeige Spur auf Karte</string> <string name="init_trackautovisit">Trackables automatisch auf \"besuchen\" setzen</string> <string name="init_sigautoinsert">Signatur automatisch einfügen</string> - <string name="init_loaddirectionimg">Richtungs-Grafik laden wenn nötig</string> + <string name="init_loaddirectionimg">Richtungs-Grafik laden wenn nötig (nur Basic Member)</string> <string name="init_default_navigation_tool">Standardnavigation</string> <string name="init_default_navigation_tool_description">Hier kannst du dein bevorzugtes Navigationswerkzeug festlegen.</string> <string name="init_default_navigation_tool_select">Wähle Werkzeug</string> @@ -658,6 +657,12 @@ <string name="map_static_loading">Lade statische Karte…</string> <string name="map_token_err">c:geo konnte nur Teildaten herunterladen, die Koordinaten der Caches könnten ungenau sein.</string> <string name="map_as_list">Als Liste anzeigen</string> + <string name="map_strategy">Strategie</string> + <string name="map_strategy_title">Strategie für Live-Karte</string> + <string name="map_strategy_fastest">Schnellste</string> + <string name="map_strategy_fast">Schnell</string> + <string name="map_strategy_auto">Geschwindigkeitsabhängig</string> + <string name="map_strategy_detailed">Detailliert</string> <!-- search --> <string name="search_bar_hint">Suche nach Caches</string> diff --git a/main/res/values-it/strings.xml b/main/res/values-it/strings.xml index a3ab64b..8790469 100644 --- a/main/res/values-it/strings.xml +++ b/main/res/values-it/strings.xml @@ -117,6 +117,14 @@ <string name="log_new_log">Log</string> <string name="log_new_log_text">Testo Log</string> <string name="log_announcement">Annuncio</string> + + <!-- copy --> + <string name="options_context_menu_title">Opzioni</string> + <string name="copy_coords">Copia Coordinate</string> + <string name="copy_desc">Copia Descrizione</string> + <string name="copy_personalnote">Copia Note personali</string> + <string name="copy_hint">Copia Aiuti</string> + <string name="copy_log">Copia Log</string> <!-- translation --> <string name="translate_to_sys_lang">Traduci in %s</string> @@ -429,7 +437,7 @@ <string name="init_default_navigation_tool">Navigatore preferito</string> <string name="init_default_navigation_tool_description">Qui puoi scegliere il tuo strumento di navigazione preferito.</string> <string name="init_default_navigation_tool_select">Scegli navigatore</string> - <string name="init_default_navigation_tool_2_description">Qui puoi scegliere il tuo secondo navigatore preferito. Sarà attivato tenendo cliccato l\'icona di navigazione nella action bar.</string> + <string name="init_default_navigation_tool_2_description">Qui puoi scegliere il tuo secondo navigatore preferito. Sarà attivato tenendo premuto l\'icona di navigazione vicino al titolo della cache.</string> <string name="init_sendToCgeo">Send to c:geo</string> <string name="init_sendToCgeo_name">Nome dispositivo:</string> diff --git a/main/res/values-sv/strings.xml b/main/res/values-sv/strings.xml index f607727..fe3c759 100755..100644 --- a/main/res/values-sv/strings.xml +++ b/main/res/values-sv/strings.xml @@ -589,6 +589,7 @@ <!-- gpx --> <string name="gpx_import_loading_caches">Läser in cacher från .gpx filen</string> <string name="gpx_import_loading_waypoints">Läser in punkter från .gpx file</string> + <string name="gpx_import_store_static_maps">Sparar kartor</string> <string name="gpx_import_storing">Skriver cacher till databasen</string> <string name="gpx_import_caches_imported">cacher importerade</string> <string name="gpx_import_title">Importera GPX</string> @@ -664,6 +665,12 @@ <string name="map_static_loading">Laddar sparade kartor…</string> <string name="map_token_err">Eftersom c:geo bara kunde hämta en del av informationen så kan positionen för cacher vara felaktig.</string> <string name="map_as_list">Visa som lista</string> + <string name="map_strategy">Strategi</string> + <string name="map_strategy_title">Strategi för Live karta</string> + <string name="map_strategy_fastest">Snabbaste</string> + <string name="map_strategy_fast">Snabbt</string> + <string name="map_strategy_auto">Hastighetsberoende (gps)</string> + <string name="map_strategy_detailed">Exakta positioner</string> <!-- search --> <string name="search_bar_hint">Sök cache/TB</string> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index a5d9228..c60aa99 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -1,4 +1,4 @@ -<?xml version="1.0" encoding="UTF-8"?> +<?xml version="1.0" encoding="UTF-8"?> <resources> <string name="app_name">c:geo</string> <string name="app_name_compass">c:geo compass</string> @@ -423,7 +423,7 @@ <string name="init_maptrail">Show trail on Map</string> <string name="init_trackautovisit">Set trackables to \"Visited\" as a default</string> <string name="init_sigautoinsert">Insert signature automatically</string> - <string name="init_loaddirectionimg">Load direction-image if necessary</string> + <string name="init_loaddirectionimg">Load direction-image if necessary (only Basic Member)</string> <string name="init_default_navigation_tool">Default Navigation</string> <string name="init_default_navigation_tool_description">Here you can select your preferred navigation tool.</string> <string name="init_default_navigation_tool_select">Select tool</string> @@ -692,6 +692,7 @@ <string name="map_token_err">Since c:geo is able to download only partial data, coordinates of caches could be inaccurate.</string> <string name="map_as_list">Show as list</string> <string name="map_strategy">Strategy</string> + <string name="map_strategy_title">Live Map strategy</string> <string name="map_strategy_fastest">Fastest</string> <string name="map_strategy_fast">Fast</string> <string name="map_strategy_auto">Speed dependent</string> @@ -1005,157 +1006,49 @@ <!-- changelog --> <string name="changelog">\n - <b>next release</b>\n - · New: Moved calendar operations to separate calendar add-on (available on Android Market).\n + <b>Next Release</b>\n\n + <b>New Features/Functions:</b>\n + · Live Map re-implementation with selectable strategy:\n + Fastest: Load approximated cache coords only\n + Fast: Same as fastest but try to identify the cache type\n + Detailed: Same as fast but load details for 20 caches around your position\n + Speed dependent: Automatic switch from detailed to fastest at 30kmh\n + · New markers in map and partly in lists:\n + Personal Note available(Pen icon)\n + Modified coordinates available (Flagpost icon)\n + Caches is saved on device (Disk icon)\n + Offline log is stored (Red smiley)\n + Cache coords are approximated (Encircled in Orange)\n + · Calendar operations moved to calendar add-on (Android Market)\n c:geo doesn\'t require calendar permissions anymore\n - · New: Login status and find count shown on start screen\n - · New: Improved markers in the map with additional information for stored caches:\n - Personal Note available, Modified coordinates, Saved on device, Offline log available, Non-reliable coordinates (orange circle)\n - · New: Copy, translate and forward available with long press for cache- and log-text\n - · New: User can select two preffered navigation tools for easier accessibility\n - · New: Type of waypoint can be selected for custom waypoints\n - · New: Day of week is now shown for event-caches\n - · New: Static maps can now also be stored for waypoints of a cache\n - · Fix: Live Map\n - · Fix: Deletion of outdated caches did not work\n - · Fix: Changed internal caching causing a better overall performance\n - · Fix: Caches with modified coordinates are displayed at the modified coords and not at the original coords\n - · Fix: Improved consistency for offline-logging operations\n - · Fix: Removed OSM:Osmarender map as it is no longer available\n - · Fix: Find-count not working in signature\n - · Fix: Keep screen on while viewing cache-lists\n -\n - <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=3&state=closed">Detailed list of all Changes</a>\n - \n\n - <b>04.01.2012</b>\n - · new: additional page in the details view for logs from friends\n - · new: horizontal scrollable pages for cache details\n - · new: click on cache coordinates to change format\n - · new: show own rating in cache details\n - · new: import zipped pocket queries\n - · new: import pocket queries from e-mail\n - · new: show original coordinates as waypoint if user has modified listing-coordinates on gc.com\n - · new: delete caches and list together\n - · new: have link from hint section to spoiler images\n - · new: edit and more options for any-location-history\n - · new: allow offline log without any login data stored\n - · new: ask for restore after reinstallation\n - · new: context menu to open spoiler/log images online\n - · new: create waypoints from personal cache note\n - · fix: several calendar bugs\n - · fix: some enhancement due to gc.com update\n - · fix: new translation IT, several translation-updates\n - · fix: lot of design enhancements (icons, font-size, autoresizing text-fields, auto-suggestion, seperators, inventory-actions, stars color and value, keyboard, colors, many more)\n - · fix: Refresh not stored caches\n - · fix: batch-actions for trackables not working\n - · fix: missing cache-name as title in cache details\n - · fix: some map-issues for gc.com-premium-members\n - · fix: edited waypoints are overwritten by cache update\n - · fix: spoiler images are too small\n - · fix: incorrect storage time in cache details\n - · fix: incorrect handling of GeoKrety trackables\n - · 100+ bugfixes and enhancements\n - \n\n - <b>08.11.2011</b>\n - · fix: adaption to GC.com changes\n - \n\n - <b>31.10.2011</b>\n - · fix: Store for offline from Live map\n - \n\n - <b>30.10.2011</b>\n - · new: cleanup the cgeo private cache directory when caches are removed from database\n - · new: display user avatar when checking login/password from geocaching.com\n - · new: support Geocaching Australia caches (GAxxxx, TPxxxx)\n - · new: import GPX from mail\n - · new: support geopeitus.ee caches\n - · new: verbose cache-loading\n - · fix: Twitter authorization\n - · fix: login problems\n - · fix: Google & OSM maps consistency\n - · fix: readable cache description (black on black, white on white)\n - · fix: many other bugs\n - \n\n - <b>09.10.2011</b>\n - · fix: small bugs due to GC.com change\n - \n\n - <b>06.10.2011</b>\n - · new: import waypoint files from pocket query\n - · new: automatically filter out counter images from cache description\n - · new: show all the images belonging to a log entry at once\n - · new: make it possible to display intermediate waypoints on live map\n - · new: better progress information when importing GPX files\n - · new: better progress information during backup/restore of database\n - · new: better French, German, Hungarian, and Slovak translations\n - · new: better icon for caches needing maintenance\n - · fix: work again on all devices, it was broken on some Android 1.6 models\n - · fix: correctly display target on map when using any coordinates mode\n - · fix: forwarding of OC caches used wrong URL\n - · fix: better performance when parsing cache description\n - · fix: performance improvements when importing waypoints\n - · fix: reclaim bitmap memory earlier to prevent memory exhaustion\n - · fix: cleaner location names (no more HTML) in cache descriptions\n - · fix: load descriptions from database only when needed (lower memory consumption)\n - · fix: one of the coordinates entry mode was using the wrong coordinate when reediting\n - · fix: latitude and longitude are now localized\n - · fix: remove redundant fields in the database to reduce used space\n - · fix: various bug fixes and internal enhancements\n - \n\n - <b>24.09.2011</b>\n - · new: import of LOC files\n - · new: search for geo code supports OpenCaching.ORG.UK/.PL/.US\n - · new: restore settings after reinstallation (Android 2.3+)\n - · new: save the log locally when leaving the log activity\n - · new: insert signature after the cursor when logging\n - · fix: various bug fixes\n - \n\n - <b>18.09.2011</b>\n - · fix: Android 3+ compatibility\n - \n\n - <b>17.09.2011</b>\n - · fix: more small fixes\n - \n\n - <b>16.09.2011</b>\n - · fix: many small fixed due to GC.com changes\n - \n\n - <b>15.09.2011</b>\n - · new: sort by state, sort by find count\n - · new: experimental support for .gpx from opencaching.com\n - · new: scan QR code to open cache description (Barcode Scanner from Market)\n - · new: context menu on stored caches button (main screen)\n - · new: compass immediately shows the right direction\n - · new: active filters are shown in list mode\n - · fix: performance of GPX import\n - · fix: performance of deleting caches from list\n - · fix: performance of web traffic\n - · fix: return to list after GPX import\n - · fix: fill signature when logging offline\n - · fix: show strikethrough in caches\n - · fix: no more empty map previews\n - · fix: send2c:geo authorization\n - \n\n - <b>26.08.2011</b>\n - · new: street view\n - · new: attribute icons\n - · new: experimental support for .gpx from opencaching.de\n - · fix: log-dates\n - · fix: coordinates input\n - · fix: nearby list performance update\n - \n\n - <b>22.08.2011</b>\n - · fix: due to GC.com changes c:geo hangs in displaying cache detail\n - \n\n - <b>20.08.2011</b>\n - · fix: Additional adaption to GC.com changes\n - \n\n - <b>19.08.2011</b>\n - · new: Numeric entry for coordinates\n - · fix: adaption to GC.com changes\n - \n\n - <b>15.08.2011</b>\n - · new: OpenStreetMap support\n - · new: Send to c:geo from web\n - · new: Export field notes\n - · new: Clear history\n - · fix: A lot of bugs\n + · Login status and find count shown on main screen\n + · Copy, translate, forward with long press for cache- and log-text\n + · Two preferred navigation tools selectable\n + · Type of waypoint can be selected on creation\n + · Day of week shown for event-caches\n + · Static maps can be saved for waypoints\n + · Static maps saved while importing GPX-files\n\n + <b>Bugfixing:</b>\n + · Deletion of outdated caches improved\n + · Changed internal caching for better performance\n + · Caches with modified coords are displayed at the modified coords\n + · Improved consistency for offline logging\n + · Removed OSM:Osmarender as it is no longer available\n + · Find-count not working in signature\n + · Keep screen on while viewing cache-lists\n + · TB-icons now shown in correct size\n + · Cache count on live map corrected\n + \n + <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=3&state=closed">Detailed list of all changes</a>\n + \n + <b>Known Limitations/Bugs:</b>\n + · Live map:\n + Approximated coords due to limitations on the GC-website\n + In fast mode the cache type might be wrong in rare cases\n + Fast mode only detectes Tradi, Multi, Mystery, Event caches\n + · Other:\n + Log images with huge size will cause a long loading time\n\n\n + <b>Old releases</b>\n + · Please refer to the release notes on the <a href="http://www.cgeo.org">c:geo-website</a>.\n \n</string> </resources> diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index f3776ea..4649477 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -1,7 +1,6 @@ package cgeo.geocaching; import cgeo.calendar.ICalendar; -import cgeo.geocaching.cgData.StorageLocation; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.apps.cache.GeneralAppsFactory; @@ -17,6 +16,7 @@ import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.geopoint.HumanDistance; import cgeo.geocaching.geopoint.IConversion; import cgeo.geocaching.network.HtmlImage; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.DecryptTextClickListener; import cgeo.geocaching.ui.Formatter; @@ -635,11 +635,6 @@ public class CacheDetailActivity extends AbstractActivity { // Data loaded, we're ready to show it! notifyDataSetChanged(); - // cache isn't available until after notifyDataSetChanged is called - if (cache != null) { - cache.setChangeNotificationHandler(cacheChangeNotificationHandler); - } - } } @@ -670,6 +665,9 @@ public class CacheDetailActivity extends AbstractActivity { return; } + // allow cache to notify CacheDetailActivity when it changes so it can be reloaded + cache.setChangeNotificationHandler(cacheChangeNotificationHandler); + // notify all creators that the data has changed for (PageViewCreator creator : viewCreators.values()) { creator.notifyDataSetChanged(); @@ -1521,8 +1519,6 @@ public class CacheDetailActivity extends AbstractActivity { buttonWatchlistRemove.setOnClickListener(new RemoveFromWatchlistClickListener()); updateWatchlistBox(); - updateDebugInfos(); - // data license IConnector connector = ConnectorFactory.getConnector(cache); if (connector != null) { @@ -1562,7 +1558,7 @@ public class CacheDetailActivity extends AbstractActivity { viewName.setText(res.getString(nameId)); viewValue.setText(String.format("%.1f", value) + ' ' + res.getString(R.string.cache_rating_of) + " 5"); - layoutStars.addView(cgBase.createStarRating(value, 5, CacheDetailActivity.this), 1); + layoutStars.addView(createStarRating(value, 5, CacheDetailActivity.this), 1); detailsList.addView(layout); return layout; @@ -1877,26 +1873,6 @@ public class CacheDetailActivity extends AbstractActivity { offlineRefresh.setClickable(true); } - private void updateDebugInfos() { - - if (Settings.isDebugInfos()) { - ((LinearLayout) view.findViewById(R.id.debug_box)).setVisibility(View.VISIBLE); - final TextView internalsText = (TextView) view.findViewById(R.id.debug_text); - - String sl = "Storage location: "; - if (cache.getStorageLocation().contains(StorageLocation.CACHE)) { - sl += "Cache "; - } - if (cache.getStorageLocation().contains(StorageLocation.DATABASE)) { - sl += "Database (" + cache.getListId() + ")"; - } - - internalsText.setText(sl); - } else { - ((LinearLayout) view.findViewById(R.id.debug_box)).setVisibility(View.GONE); - } - } - private class PreviewMapTask extends AsyncTask<Void, Void, BitmapDrawable> { @Override protected BitmapDrawable doInBackground(Void... params) { @@ -1912,7 +1888,7 @@ public class CacheDetailActivity extends AbstractActivity { final int height = (int) (110 * metrics.density); // TODO move this code to StaticMapProvider and use its constant values - final String markerUrl = cgBase.urlencode_rfc3986("http://cgeo.carnero.cc/_markers/my_location_mdpi.png"); + final String markerUrl = Network.urlencode_rfc3986("http://cgeo.carnero.cc/_markers/my_location_mdpi.png"); final HtmlImage mapGetter = new HtmlImage(CacheDetailActivity.this, cache.getGeocode(), false, 0, false); image = mapGetter.getDrawable("http://maps.google.com/maps/api/staticmap?zoom=15&size=" + width + "x" + height + "&maptype=roadmap&markers=icon%3A" + markerUrl + "%7Cshadow:false%7C" + latlonMap + "&sensor=false"); @@ -2013,7 +1989,7 @@ public class CacheDetailActivity extends AbstractActivity { if (StringUtils.isNotBlank(cache.getHint())) { TextView hintView = ((TextView) view.findViewById(R.id.hint)); if (BaseUtils.containsHtml(cache.getHint())) { - hintView.setText(Html.fromHtml(cache.getHint(), new HtmlImage(CacheDetailActivity.this, null, false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); + hintView.setText(Html.fromHtml(cache.getHint(), new HtmlImage(CacheDetailActivity.this, cache.getGeocode(), false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); hintView.setText(CryptUtils.rot13((Spannable) hintView.getText())); } else { @@ -2245,6 +2221,9 @@ public class CacheDetailActivity extends AbstractActivity { } view.setAdapter(new ArrayAdapter<cgLog>(CacheDetailActivity.this, R.layout.cacheview_logs_item, cache.getLogs(allLogs)) { + final UserActionsClickListener userActionsClickListener = new UserActionsClickListener(); + final DecryptTextClickListener decryptTextClickListener = new DecryptTextClickListener(); + @Override public View getView(final int position, final View convertView, final ViewGroup parent) { View rowView = convertView; @@ -2283,7 +2262,7 @@ public class CacheDetailActivity extends AbstractActivity { // logtext, avoid parsing HTML if not necessary if (BaseUtils.containsHtml(log.log)) { - holder.text.setText(Html.fromHtml(log.log, new HtmlImage(CacheDetailActivity.this, null, false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); + holder.text.setText(Html.fromHtml(log.log, new HtmlImage(CacheDetailActivity.this, cache.getGeocode(), false, cache.getListId(), false), null), TextView.BufferType.SPANNABLE); } else { holder.text.setText(log.log); @@ -2337,9 +2316,9 @@ public class CacheDetailActivity extends AbstractActivity { if (null == convertView) { // if convertView != null then this listeners are already set - holder.author.setOnClickListener(new UserActionsClickListener()); + holder.author.setOnClickListener(userActionsClickListener); holder.text.setMovementMethod(LinkMovementMethod.getInstance()); - holder.text.setOnClickListener(new DecryptTextClickListener()); + holder.text.setOnClickListener(decryptTextClickListener); registerForContextMenu(holder.text); } diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java index 40ad96d..4949b62 100644 --- a/main/src/cgeo/geocaching/Settings.java +++ b/main/src/cgeo/geocaching/Settings.java @@ -76,7 +76,6 @@ public final class Settings { private static final String KEY_COOKIE_STORE = "cookiestore"; private static final String KEY_OPEN_LAST_DETAILS_PAGE = "opendetailslastpage"; private static final String KEY_LAST_DETAILS_PAGE = "lastdetailspage"; - private static final String KEY_DEBUG_INFORMATIONS = "debuginfos"; private static final String KEY_DEFAULT_NAVIGATION_TOOL = "defaultNavigationTool"; private static final String KEY_DEFAULT_NAVIGATION_TOOL_2 = "defaultNavigationTool2"; private static final String KEY_LIVE_MAP_STRATEGY = "livemapstrategy"; @@ -416,10 +415,10 @@ public final class Settings { } public static boolean getLoadDirImg() { - return sharedPrefs.getBoolean(KEY_LOAD_DIRECTION_IMG, true); + return isPremiumMember() ? false : sharedPrefs.getBoolean(KEY_LOAD_DIRECTION_IMG, true); } - static void setGcCustomDate(final String format) { + public static void setGcCustomDate(final String format) { editSharedSettings(new PrefRunnable() { @Override @@ -974,20 +973,6 @@ public final class Settings { }); } - public static boolean isDebugInfos() { - return sharedPrefs.getBoolean(KEY_DEBUG_INFORMATIONS, false); - } - - public static void setDebugInfos(final boolean showDebugInfos) { - editSharedSettings(new PrefRunnable() { - - @Override - public void edit(Editor edit) { - edit.putBoolean(KEY_DEBUG_INFORMATIONS, showDebugInfos); - } - }); - } - public static int getDefaultNavigationTool() { return sharedPrefs.getInt(KEY_DEFAULT_NAVIGATION_TOOL, 0); } diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index 1461e59..fcb8ed9 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -4,6 +4,7 @@ import cgeo.geocaching.concurrent.BlockingThreadPool; import cgeo.geocaching.concurrent.Task; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.geopoint.GeopointFormatter.Format; +import cgeo.geocaching.network.Network; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -41,10 +42,10 @@ public class StaticMapsProvider { final String mapUrl = "http://maps.google.com/maps/api/staticmap?center=" + latlonMap; final String url = mapUrl + "&zoom=" + zoom + "&size=" + edge + "x" + edge + "&maptype=" + mapType + "&markers=icon%3A" + markerUrl + "%7C" + latlonMap + waypoints + "&sensor=false"; final File file = getMapFile(cache.getGeocode(), prefix, level, true); - final HttpResponse httpResponse = cgBase.request(url, null, false); + final HttpResponse httpResponse = Network.request(url, null, false); if (httpResponse != null) { - if (LocalStorage.saveEntityToFile(httpResponse.getEntity(), file)) { + if (LocalStorage.saveEntityToFile(httpResponse, file)) { // Delete image if it has no contents final long fileSize = file.length(); if (fileSize < MIN_MAP_IMAGE_BYTES) { @@ -167,12 +168,12 @@ public class StaticMapsProvider { type += "_disabled"; } - return cgBase.urlencode_rfc3986(MARKERS_URL + "marker_cache_" + type + ".png"); + return Network.urlencode_rfc3986(MARKERS_URL + "marker_cache_" + type + ".png"); } private static String getWpMarkerUrl(final cgWaypoint waypoint) { String type = waypoint.getWaypointType() != null ? waypoint.getWaypointType().id : null; - return cgBase.urlencode_rfc3986(MARKERS_URL + "marker_waypoint_" + type + ".png"); + return Network.urlencode_rfc3986(MARKERS_URL + "marker_waypoint_" + type + ".png"); } public static void removeWpStaticMaps(int wp_id, String geocode) { diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index 6d0097a..f0acd97 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/VisitCacheActivity.java @@ -8,7 +8,10 @@ import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.LogTypeTrackable; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.gcvote.GCVote; +import cgeo.geocaching.network.Login; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.DateDialog; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogTemplate; @@ -352,7 +355,7 @@ public class VisitCacheActivity extends AbstractActivity implements DateDialog.D private void insertIntoLog(String newText, final boolean moveCursor) { final EditText log = (EditText) findViewById(R.id.log); - cgBase.insertAtPosition(log, newText, moveCursor); + insertAtPosition(log, newText, moveCursor); } private static String ratingTextValue(final double rating) { @@ -653,9 +656,9 @@ public class VisitCacheActivity extends AbstractActivity implements DateDialog.D return; } - final String page = cgBase.getResponseData(cgBase.request("http://www.geocaching.com/seek/log.aspx", params, false, false, false)); + final String page = Network.getResponseData(Network.request("http://www.geocaching.com/seek/log.aspx", params, false, false, false)); - viewstates = cgBase.getViewstates(page); + viewstates = Login.getViewstates(page); trackables = cgBase.parseTrackableLog(page); final List<LogType> typesPre = cgBase.parseTypes(page); @@ -729,7 +732,7 @@ public class VisitCacheActivity extends AbstractActivity implements DateDialog.D if (status == StatusCode.NO_ERROR && typeSelected == LogType.LOG_FOUND_IT && Settings.isUseTwitter() && Settings.isTwitterLoginValid() && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { - cgBase.postTweetCache(geocode); + Twitter.postTweetCache(geocode); } if (status == StatusCode.NO_ERROR && typeSelected == LogType.LOG_FOUND_IT && Settings.isGCvoteLogin()) { diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index 941cd08..8e8ad9d 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -1,18 +1,24 @@ package cgeo.geocaching.activity; +import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.cgBase; import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.compatibility.Compatibility; +import cgeo.geocaching.network.Network; import android.app.Activity; +import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; import android.os.Bundle; +import android.view.LayoutInflater; import android.view.Menu; import android.view.View; import android.widget.EditText; +import android.widget.ImageView; +import android.widget.LinearLayout; public abstract class AbstractActivity extends Activity implements IAbstractActivity { @@ -82,7 +88,7 @@ public abstract class AbstractActivity extends Activity implements IAbstractActi cgBase.initialize(app); // Restore cookie store if needed - cgBase.restoreCookieStore(Settings.getCookieStore()); + Network.restoreCookieStore(Settings.getCookieStore()); ActivityMixin.keepScreenOn(this, keepScreenOn); } @@ -103,4 +109,51 @@ public abstract class AbstractActivity extends Activity implements IAbstractActi ActivityMixin.invalidateOptionsMenu(this); } + public static LinearLayout createStarRating(final float value, final int count, final Context context) { + LayoutInflater inflater = LayoutInflater.from(context); + LinearLayout starsContainer = new LinearLayout(context); + starsContainer.setOrientation(LinearLayout.HORIZONTAL); + + for (int i = 0; i < count; i++) { + ImageView star = (ImageView) inflater.inflate(R.layout.star, null); + if (value - i >= 0.75) { + star.setImageResource(R.drawable.star_on); + } else if (value - i >= 0.25) { + star.setImageResource(R.drawable.star_half); + } else { + star.setImageResource(R.drawable.star_off); + } + starsContainer.addView(star); + } + + return starsContainer; + } + + /** + * insert text into the EditText at the current cursor position + * + * @param editText + * @param insertText + * @param moveCursor + * place the cursor after the inserted text + */ + public static void insertAtPosition(final EditText editText, final String insertText, final boolean moveCursor) { + int selectionStart = editText.getSelectionStart(); + int selectionEnd = editText.getSelectionEnd(); + int start = Math.min(selectionStart, selectionEnd); + int end = Math.max(selectionStart, selectionEnd); + + final String content = editText.getText().toString(); + String completeText; + if (start > 0 && !Character.isWhitespace(content.charAt(start - 1))) { + completeText = " " + insertText; + } else { + completeText = insertText; + } + + editText.getText().replace(start, end, completeText); + int newCursor = moveCursor ? start + completeText.length() : start; + editText.setSelection(newCursor, newCursor); + } + } diff --git a/main/src/cgeo/geocaching/cgBase.java b/main/src/cgeo/geocaching/cgBase.java index 3c24cdb..0023499 100644 --- a/main/src/cgeo/geocaching/cgBase.java +++ b/main/src/cgeo/geocaching/cgBase.java @@ -19,10 +19,10 @@ import cgeo.geocaching.gcvote.GCVote; import cgeo.geocaching.gcvote.GCVoteRating; import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; -import cgeo.geocaching.geopoint.GeopointFormatter.Format; import cgeo.geocaching.network.HtmlImage; +import cgeo.geocaching.network.Login; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.DirectionImage; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; @@ -30,25 +30,7 @@ import cgeo.geocaching.utils.CancellableHandler; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.http.HttpResponse; -import org.apache.http.NameValuePair; -import org.apache.http.client.CookieStore; -import org.apache.http.client.HttpClient; -import org.apache.http.client.entity.UrlEncodedFormEntity; -import org.apache.http.client.methods.HttpGet; -import org.apache.http.client.methods.HttpPost; -import org.apache.http.client.methods.HttpRequestBase; -import org.apache.http.cookie.Cookie; -import org.apache.http.impl.client.BasicCookieStore; -import org.apache.http.impl.client.DefaultHttpClient; -import org.apache.http.impl.cookie.BasicClientCookie; -import org.apache.http.params.BasicHttpParams; -import org.apache.http.params.CoreConnectionPNames; -import org.apache.http.params.CoreProtocolPNames; -import org.apache.http.params.HttpParams; -import org.apache.http.protocol.HTTP; -import org.apache.http.util.EntityUtils; import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; @@ -60,9 +42,6 @@ import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.ResolveInfo; import android.content.res.Resources; -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Handler; import android.os.Message; @@ -72,81 +51,29 @@ import android.text.Spanned; import android.text.format.DateUtils; import android.text.style.StrikethroughSpan; import android.util.Log; -import android.view.LayoutInflater; -import android.widget.EditText; -import android.widget.ImageView; -import android.widget.LinearLayout; - -import java.io.File; -import java.io.IOException; -import java.net.InetAddress; -import java.net.NetworkInterface; -import java.net.SocketException; + import java.net.URLDecoder; -import java.net.URLEncoder; 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.EnumSet; -import java.util.Enumeration; -import java.util.HashMap; import java.util.List; import java.util.Locale; -import java.util.Map; import java.util.Set; import java.util.regex.Matcher; -import javax.net.ssl.HostnameVerifier; -import javax.net.ssl.SSLSession; - public class cgBase { - private static final String passMatch = "(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+"; - - private 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); - } private final static SimpleDateFormat dateTbIn1 = new SimpleDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 private final static SimpleDateFormat dateTbIn2 = new SimpleDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009 public static String version = null; private static Context context; - private static Resources res; - - private static final int NB_DOWNLOAD_RETRIES = 4; + public static Resources res; public static final int UPDATE_LOAD_PROGRESS_DETAIL = 42186; - // false = not logged in - private static boolean actualLoginStatus = false; - private static String actualUserName = ""; - private static String actualMemberStatus = ""; - private static int actualCachesFound = -1; - private static String actualStatus = ""; - - /** User agent id */ - public final static String USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"; - private cgBase() { //initialize(app); throw new UnsupportedOperationException(); // static class, not to be instantiated @@ -171,10 +98,6 @@ public class cgBase { } } - public static String hidePassword(final String message) { - return message.replaceAll(passMatch, "password=***"); - } - public static void sendLoadProgressDetail(final Handler handler, final int str) { if (null != handler) { handler.obtainMessage(UPDATE_LOAD_PROGRESS_DETAIL, cgeoapplication.getInstance().getString(str)).sendToTarget(); @@ -182,72 +105,6 @@ public class cgBase { } /** - * 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 - - if (page == null) { // no network access - return null; - } - - int count = 1; - final Matcher matcherViewstateCount = GCConstants.PATTERN_VIEWSTATEFIELDCOUNT.matcher(page); - if (matcherViewstateCount.find()) { - count = Integer.parseInt(matcherViewstateCount.group(1)); - } - - String[] viewstates = new String[count]; - - // Get the viewstates - int no; - final Matcher matcherViewstates = GCConstants.PATTERN_VIEWSTATES.matcher(page); - while (matcherViewstates.find()) { - String sno = matcherViewstates.group(1); // number of viewstate - if (sno.length() == 0) { - no = 0; - } - else { - no = Integer.parseInt(sno); - } - viewstates[no] = matcherViewstates.group(2); - } - - if (viewstates.length != 1 || viewstates[0] != null) { - return viewstates; - } - // no viewstates were present - return null; - } - - /** - * put viewstates into request parameters - */ - private static void putViewstates(final Parameters params, final String[] viewstates) { - 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", String.valueOf(viewstates.length)); - } - } - - /** - * transfers the viewstates variables from a page (response) to parameters - * (next request) - */ - public static void transferViewstates(final String page, final Parameters params) { - putViewstates(params, getViewstates(page)); - } - - /** * checks if an Array of Strings is empty or not. Empty means: * - Array is null * - or all elements are null or empty strings @@ -265,157 +122,6 @@ public class cgBase { return true; } - public static StatusCode login() { - final ImmutablePair<String, String> login = Settings.getLogin(); - - if (login == null || StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) { - cgBase.setActualStatus(res.getString(R.string.err_login)); - Log.e(Settings.tag, "cgeoBase.login: No login information stored"); - return StatusCode.NO_LOGIN_INFO_STORED; - } - - // res is null during the unit tests - if (res != null) { - cgBase.setActualStatus(res.getString(R.string.init_login_popup_working)); - } - HttpResponse loginResponse = request("https://www.geocaching.com/login/default.aspx", null, false, false, false); - String loginData = getResponseData(loginResponse); - if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { - return StatusCode.MAINTENANCE; - } - - if (StringUtils.isBlank(loginData)) { - Log.e(Settings.tag, "cgeoBase.login: Failed to retrieve login page (1st)"); - return StatusCode.CONNECTION_FAILED; // no loginpage - } - - if (getLoginStatus(loginData)) { - Log.i(Settings.tag, "Already logged in Geocaching.com as " + login.left); - switchToEnglish(loginData); - return StatusCode.NO_ERROR; // logged in - } - - clearCookies(); - Settings.setCookieStore(null); - - final Parameters params = new Parameters( - "__EVENTTARGET", "", - "__EVENTARGUMENT", "", - "ctl00$ContentBody$tbUsername", login.left, - "ctl00$ContentBody$tbPassword", login.right, - "ctl00$ContentBody$cbRememberMe", "on", - "ctl00$ContentBody$btnSignIn", "Login"); - final String[] viewstates = getViewstates(loginData); - if (isEmpty(viewstates)) { - Log.e(Settings.tag, "cgeoBase.login: Failed to find viewstates"); - return StatusCode.LOGIN_PARSE_ERROR; // no viewstates - } - putViewstates(params, viewstates); - - loginResponse = postRequest("https://www.geocaching.com/login/default.aspx", params); - loginData = getResponseData(loginResponse); - - if (StringUtils.isNotBlank(loginData)) { - if (getLoginStatus(loginData)) { - Log.i(Settings.tag, "Successfully logged in Geocaching.com as " + login.left); - - switchToEnglish(loginData); - Settings.setCookieStore(dumpCookieStore()); - - return StatusCode.NO_ERROR; // logged in - } else { - if (loginData.contains("Your username/password combination does not match.")) { - Log.i(Settings.tag, "Failed to log in Geocaching.com as " + login.left + " because of wrong username/password"); - return StatusCode.WRONG_LOGIN_DATA; // wrong login - } else { - Log.i(Settings.tag, "Failed to log in Geocaching.com as " + login.left + " for some unknown reason"); - return StatusCode.UNKNOWN_ERROR; // can't login - } - } - } else { - Log.e(Settings.tag, "cgeoBase.login: Failed to retrieve login page (2nd)"); - // FIXME: should it be CONNECTION_FAILED to match the first attempt? - return StatusCode.COMMUNICATION_ERROR; // no login page - } - } - - public static StatusCode logout() { - HttpResponse logoutResponse = request("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f", null, false, false, false); - String logoutData = getResponseData(logoutResponse); - if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { - return StatusCode.MAINTENANCE; - } - - clearCookies(); - Settings.setCookieStore(null); - return StatusCode.NO_ERROR; - } - - /** - * Check if the user has been logged in when he retrieved the data. - * - * @param page - * @return <code>true</code> if user is logged in, <code>false</code> otherwise - */ - private static boolean getLoginStatus(final String page) { - if (StringUtils.isBlank(page)) { - Log.e(Settings.tag, "cgeoBase.checkLogin: No page given"); - return false; - } - - // res is null during the unit tests - if (res != null) { - setActualStatus(res.getString(R.string.init_login_popup_ok)); - } - - // on every page except login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); - if (isActualLoginStatus()) { - setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); - setActualMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, "???")); - setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", ""))); - return true; - } - - // login page - setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); - if (isActualLoginStatus()) { - setActualUserName(Settings.getUsername()); - setActualMemberStatus(Settings.getMemberStatus()); - // number of caches found is not part of this page - return true; - } - - // res is null during the unit tests - if (res != null) { - setActualStatus(res.getString(R.string.init_login_popup_failed)); - } - return false; - } - - public static void switchToEnglish(String previousPage) { - final String ENGLISH = "English▼"; - if (previousPage != null && previousPage.indexOf(ENGLISH) >= 0) { - Log.i(Settings.tag, "Geocaching.com language already set to English"); - // get find count - getLoginStatus(getResponseData(request("http://www.geocaching.com/email/", null, false))); - } else { - final String page = getResponseData(request("http://www.geocaching.com/default.aspx", null, false)); - getLoginStatus(page); - if (page == null) { - Log.e(Settings.tag, "Failed to read viewstates to set geocaching.com language"); - } - final Parameters params = new Parameters( - "__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem", // switch to english - "__EVENTARGUMENT", ""); - transferViewstates(page, params); - final HttpResponse response = postRequest("http://www.geocaching.com/default.aspx", params); - if (!isSuccess(response)) { - Log.e(Settings.tag, "Failed to set geocaching.com language to English"); - } - } - } - private static SearchResult parseSearch(final cgSearchThread thread, final String url, final String pageContent, final boolean showCaptcha) { if (StringUtils.isBlank(pageContent)) { Log.e(Settings.tag, "cgeoBase.parseSearch: No page given"); @@ -430,7 +136,7 @@ public class cgBase { final SearchResult searchResult = new SearchResult(); searchResult.setUrl(url); - searchResult.viewstates = getViewstates(page); + searchResult.viewstates = Login.getViewstates(page); // recaptcha if (showCaptcha) { @@ -438,7 +144,7 @@ public class cgBase { if (recaptchaJsParam != null) { final Parameters params = new Parameters("k", recaptchaJsParam.trim()); - final String recaptchaJs = cgBase.getResponseData(request("http://www.google.com/recaptcha/api/challenge", params, true)); + final String recaptchaJs = Network.getResponseData(Network.request("http://www.google.com/recaptcha/api/challenge", params, true)); if (StringUtils.isNotBlank(recaptchaJs)) { recaptchaChallenge = BaseUtils.getMatch(recaptchaJs, GCConstants.PATTERN_SEARCH_RECAPTCHACHALLENGE, true, 1, null, true); @@ -587,9 +293,6 @@ public class cgBase { } } - // location is reliable because the search return correct coordinates independent of the login status - cache.setReliableLatLon(true); - searchResult.addCache(cache); } @@ -638,7 +341,7 @@ public class cgBase { } params.put("ctl00$ContentBody$uxDownloadLoc", "Download Waypoints"); - final String coordinates = getResponseData(postRequest("http://www.geocaching.com/seek/nearest.aspx", params), false); + final String coordinates = Network.getResponseData(Network.postRequest("http://www.geocaching.com/seek/nearest.aspx", params), false); if (StringUtils.isNotBlank(coordinates)) { if (coordinates.contains("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")) { @@ -651,6 +354,11 @@ public class cgBase { } LocParser.parseLoc(searchResult, coordinates); + + // now we have the coords... + for (cgCache cache : searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB)) { + cache.setReliableLatLon(true); + } } catch (Exception e) { Log.e(Settings.tag, "cgBase.parseSearch.CIDs: " + e.toString()); } @@ -658,7 +366,7 @@ public class cgBase { // get direction images if (Settings.getLoadDirImg()) { - final Set<cgCache> caches = cgeoapplication.getInstance().loadCaches(searchResult.getGeocodes(), LoadFlags.LOAD_CACHE_OR_DB); + final Set<cgCache> caches = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); for (cgCache cache : caches) { if (cache.getCoords() == null && StringUtils.isNotEmpty(cache.getDirectionImg())) { DirectionImage.getDrawable(cache.getGeocode(), cache.getDirectionImg()); @@ -711,6 +419,12 @@ public class cgBase { return searchResult; } + final String cacheName = Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_NAME, true, "")).toString(); + if ("An Error Has Occurred".equalsIgnoreCase(cacheName)) { + searchResult.error = StatusCode.UNKNOWN_ERROR; + return searchResult; + } + final cgCache cache = new cgCache(); cache.setDisabled(page.contains("<li>This cache is temporarily unavailable.")); @@ -730,7 +444,7 @@ public class cgBase { cache.setGuid(BaseUtils.getMatch(page, GCConstants.PATTERN_GUID, true, cache.getGuid())); // name - cache.setName(Html.fromHtml(BaseUtils.getMatch(page, GCConstants.PATTERN_NAME, true, cache.getName())).toString()); + cache.setName(cacheName); // owner real name cache.setOwnerReal(URLDecoder.decode(BaseUtils.getMatch(page, GCConstants.PATTERN_OWNERREAL, true, cache.getOwnerReal()))); @@ -778,13 +492,13 @@ public class cgBase { try { String hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDEN, true, null); if (StringUtils.isNotBlank(hiddenString)) { - cache.setHidden(parseGcCustomDate(hiddenString)); + cache.setHidden(Login.parseGcCustomDate(hiddenString)); } if (cache.getHiddenDate() == null) { // event date hiddenString = BaseUtils.getMatch(tableInside, GCConstants.PATTERN_HIDDENEVENT, true, null); if (StringUtils.isNotBlank(hiddenString)) { - cache.setHidden(parseGcCustomDate(hiddenString)); + cache.setHidden(Login.parseGcCustomDate(hiddenString)); } } } catch (ParseException e) { @@ -1088,7 +802,7 @@ public class cgBase { } sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_elevation); if (cache.getCoords() != null) { - cache.setElevation(getElevation(cache.getCoords())); + cache.setElevation(cache.getCoords().getElevation()); } } @@ -1135,7 +849,7 @@ public class cgBase { // "sp", Boolean.toString(personal), // personal logs "sf", Boolean.toString(friends)); - final HttpResponse response = request("http://www.geocaching.com/seek/geocache.logbook", params, false, false, false); + final HttpResponse response = Network.request("http://www.geocaching.com/seek/geocache.logbook", params, false, false, false); if (response == null) { Log.e(Settings.tag, "cgBase.loadLogsFromDetails: cannot log logs, response is null"); return null; @@ -1145,7 +859,7 @@ public class cgBase { Log.e(Settings.tag, "cgBase.loadLogsFromDetails: error " + statusCode + " when requesting log information"); return null; } - rawResponse = cgBase.getResponseData(response); + rawResponse = Network.getResponseData(response); if (rawResponse == null) { Log.e(Settings.tag, "cgBase.loadLogsFromDetails: unable to read whole response"); return null; @@ -1177,7 +891,7 @@ public class cgBase { logDone.type = LogType.getByIconName(logIconName); try { - logDone.date = parseGcCustomDate(entry.getString("Visited")).getTime(); + logDone.date = Login.parseGcCustomDate(entry.getString("Visited")).getTime(); } catch (ParseException e) { Log.e(Settings.tag, "cgBase.loadLogsFromDetails: failed to parse log date."); } @@ -1250,73 +964,6 @@ public class cgBase { } } - public static Date parseGcCustomDate(final String input, final String format) throws ParseException { - if (StringUtils.isBlank(input)) { - throw new ParseException("Input is null", 0); - } - - final String trimmed = input.trim(); - - if (gcCustomDateFormats.containsKey(format)) { - try { - return gcCustomDateFormats.get(format).parse(trimmed); - } catch (ParseException e) { - } - } - - for (SimpleDateFormat sdf : gcCustomDateFormats.values()) { - try { - return sdf.parse(trimmed); - } catch (ParseException e) { - } - } - - throw new ParseException("No matching pattern", 0); - } - - public static Date parseGcCustomDate(final String input) throws ParseException { - return parseGcCustomDate(input, Settings.getGcCustomDate()); - } - - /** - * Detect user date settings on geocaching.com - */ - public static void detectGcCustomDate() { - - final String result = getResponseData(request("http://www.geocaching.com/account/ManagePreferences.aspx", null, false, false, false)); - - if (null == result) { - Log.w(Settings.tag, "cgeoBase.detectGcCustomDate: result is null"); - return; - } - - String customDate = BaseUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); - if (null != customDate) { - Settings.setGcCustomDate(customDate); - } - } - - public static BitmapDrawable downloadAvatarAndGetMemberStatus(final Context context) { - try { - final String profile = BaseUtils.replaceWhitespace(getResponseData(request("http://www.geocaching.com/my/", null, false))); - - Settings.setMemberStatus(BaseUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); - - setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); - - final String avatarURL = BaseUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); - if (null != avatarURL) { - final HtmlImage imgGetter = new HtmlImage(context, "", false, 0, false); - return imgGetter.getDrawable(avatarURL); - } - // No match? There may be no avatar set by user. - Log.d(Settings.tag, "No avatar set for user"); - } catch (Exception e) { - Log.w(Settings.tag, "Error when retrieving user avatar", e); - } - return null; - } - /** * Parse a trackable HTML description into a cgTrackable object * @@ -1462,7 +1109,7 @@ public class cgBase { try { - logDone.date = parseGcCustomDate(matcherLogs.group(2)).getTime(); + logDone.date = Login.parseGcCustomDate(matcherLogs.group(2)).getTime(); } catch (ParseException e) { } @@ -1631,13 +1278,13 @@ public class cgBase { final Parameters params = new Parameters( "__EVENTTARGET", "ctl00$ContentBody$pgrBottom$ctl08", "__EVENTARGUMENT", ""); - putViewstates(params, viewstates); + Login.putViewstates(params, viewstates); - String page = getResponseData(postRequest(uri, params)); - if (!getLoginStatus(page)) { - final StatusCode loginState = login(); + String page = Network.getResponseData(Network.postRequest(uri, params)); + if (!Login.getLoginStatus(page)) { + final StatusCode loginState = Login.login(); if (loginState == StatusCode.NO_ERROR) { - page = getResponseData(postRequest(uri, params)); + page = Network.getResponseData(Network.postRequest(uri, params)); } else if (loginState == StatusCode.NO_LOGIN_INFO_STORED) { Log.i(Settings.tag, "Working as guest."); } else { @@ -1708,7 +1355,7 @@ public class cgBase { final String uri = "http://www.geocaching.com/seek/nearest.aspx"; final String fullUri = uri + "?" + addFToParams(params, false, true); - String page = requestLogged(uri, params, false, my, true); + String page = Network.requestLogged(uri, params, false, my, true); if (StringUtils.isBlank(page)) { Log.e(Settings.tag, "cgeoBase.searchByAny: No data from server"); @@ -1723,7 +1370,7 @@ public class cgBase { final SearchResult search = searchResult.filterSearchResults(Settings.isExcludeDisabledCaches(), false, cacheType); - getLoginStatus(page); + Login.getLoginStatus(page); return search; } @@ -1770,32 +1417,6 @@ public class cgBase { return searchByAny(thread, cacheType, false, showCaptcha, params); } - /** Request .png image for a tile. */ - public static Bitmap requestMapTile(final String url, final String referer) { - final HttpGet request = new HttpGet(url); - request.addHeader("Accept", "image/png,image/*;q=0.8,*/*;q=0.5"); - //request.addHeader("Accept-Encoding", "gzip, deflate"); - request.addHeader("Referer", referer); - request.addHeader("X-Requested-With", "XMLHttpRequest"); - final HttpResponse response = request(request); - try { - return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; - } catch (IOException e) { - Log.e(Settings.tag, "cgBase.requestMapTile() " + e.getMessage()); - } - return null; - } - - /** Request JSON informations for a tile */ - public static String requestMapInfo(final String url, final String referer) { - final HttpGet request = new HttpGet(url); - request.addHeader("Accept", "application/json, text/javascript, */*; q=0.01"); - // NO request.addHeader("Accept-Encoding", "gzip deflate"); - request.addHeader("Referer", referer); - request.addHeader("X-Requested-With", "XMLHttpRequest"); - return getResponseData(request(request), false); - } - public static cgTrackable searchTrackable(final String geocode, final String guid, final String id) { if (StringUtils.isBlank(geocode) && StringUtils.isBlank(guid) && StringUtils.isBlank(id)) { Log.w(Settings.tag, "cgeoBase.searchTrackable: No geocode nor guid nor id given"); @@ -1814,7 +1435,7 @@ public class cgBase { params.put("id", id); } - String page = requestLogged("http://www.geocaching.com/track/details.aspx", params, false, false, false); + String page = Network.requestLogged("http://www.geocaching.com/track/details.aspx", params, false, false, false); if (StringUtils.isBlank(page)) { Log.e(Settings.tag, "cgeoBase.searchTrackable: No data from server"); @@ -1879,7 +1500,7 @@ public class cgBase { "ctl00$ContentBody$LogBookPanel1$uxLogInfo", logInfo, "ctl00$ContentBody$LogBookPanel1$LogButton", "Submit Log Entry", "ctl00$ContentBody$uxVistOtherListingGC", ""); - putViewstates(params, viewstates); + Login.putViewstates(params, viewstates); if (trackables != null && !trackables.isEmpty()) { // we have some trackables to proceed final StringBuilder hdnSelected = new StringBuilder(); @@ -1896,11 +1517,11 @@ public class cgBase { } final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/log.aspx").encodedQuery("ID=" + cacheid).build().toString(); - String page = getResponseData(postRequest(uri, params)); - if (!getLoginStatus(page)) { - final StatusCode loginState = login(); + String page = Network.getResponseData(Network.postRequest(uri, params)); + if (!Login.getLoginStatus(page)) { + final StatusCode loginState = Login.login(); if (loginState == StatusCode.NO_ERROR) { - page = getResponseData(postRequest(uri, params)); + page = Network.getResponseData(Network.postRequest(uri, params)); } else { Log.e(Settings.tag, "cgeoBase.postLog: Can not log in geocaching (error: " + loginState + ")"); return loginState; @@ -1918,7 +1539,7 @@ public class cgBase { try { if (matcher.find() && matcher.groupCount() > 0) { - final String[] viewstatesConfirm = getViewstates(page); + final String[] viewstatesConfirm = Login.getViewstates(page); if (isEmpty(viewstatesConfirm)) { Log.e(Settings.tag, "cgeoBase.postLog: No viewstate for confirm log"); @@ -1926,7 +1547,7 @@ public class cgBase { } params.clear(); - putViewstates(params, viewstatesConfirm); + Login.putViewstates(params, viewstatesConfirm); params.put("__EVENTTARGET", ""); params.put("__EVENTARGUMENT", ""); params.put("__LASTFOCUS", ""); @@ -1957,7 +1578,7 @@ public class cgBase { params.put("ctl00$ContentBody$LogBookPanel1$uxTrackables$hdnCurrentFilter", ""); } - page = getResponseData(postRequest(uri, params)); + page = Network.getResponseData(Network.postRequest(uri, params)); } } catch (Exception e) { Log.e(Settings.tag, "cgeoBase.postLog.confim: " + e.toString()); @@ -1973,10 +1594,10 @@ public class cgBase { cgeoapplication.getInstance().saveVisitDate(geocode); } - getLoginStatus(page); + Login.getLoginStatus(page); // the log-successful-page contains still the old value - if (getActualCachesFound() >= 0) { - setActualCachesFound(getActualCachesFound() + 1); + if (Login.getActualCachesFound() >= 0) { + Login.setActualCachesFound(Login.getActualCachesFound() + 1); } return StatusCode.NO_ERROR; } @@ -2011,7 +1632,7 @@ public class cgBase { "__LASTFOCUS", "", "ctl00$ContentBody$LogBookPanel1$ddLogType", Integer.toString(logType.id), "ctl00$ContentBody$LogBookPanel1$tbCode", trackingCode); - putViewstates(params, viewstates); + Login.putViewstates(params, viewstates); if (currentDate.get(Calendar.YEAR) == year && (currentDate.get(Calendar.MONTH) + 1) == month && currentDate.get(Calendar.DATE) == day) { params.put("ctl00$ContentBody$LogBookPanel1$DateTimeLogged", ""); } else { @@ -2026,11 +1647,11 @@ public class cgBase { "ctl00$ContentBody$uxVistOtherListingGC", ""); final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/track/log.aspx").encodedQuery("wid=" + tbid).build().toString(); - String page = getResponseData(postRequest(uri, params)); - if (!getLoginStatus(page)) { - final StatusCode loginState = login(); + String page = Network.getResponseData(Network.postRequest(uri, params)); + if (!Login.getLoginStatus(page)) { + final StatusCode loginState = Login.login(); if (loginState == StatusCode.NO_ERROR) { - page = getResponseData(postRequest(uri, params)); + page = Network.getResponseData(Network.postRequest(uri, params)); } else { Log.e(Settings.tag, "cgeoBase.postLogTrackable: Can not log in geocaching (error: " + loginState + ")"); return loginState; @@ -2066,7 +1687,7 @@ public class cgBase { */ public static int addToWatchlist(final cgCache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?w=" + cache.getCacheId(); - String page = postRequestLogged(uri); + String page = Network.postRequestLogged(uri); if (StringUtils.isBlank(page)) { Log.e(Settings.tag, "cgBase.addToWatchlist: No data from server"); @@ -2092,7 +1713,7 @@ public class cgBase { */ public static int removeFromWatchlist(final cgCache cache) { final String uri = "http://www.geocaching.com/my/watchlist.aspx?ds=1&action=rem&id=" + cache.getCacheId(); - String page = postRequestLogged(uri); + String page = Network.postRequestLogged(uri); if (StringUtils.isBlank(page)) { Log.e(Settings.tag, "cgBase.removeFromWatchlist: No data from server"); @@ -2104,9 +1725,9 @@ public class cgBase { "__EVENTTARGET", "", "__EVENTARGUMENT", "", "ctl00$ContentBody$btnYes", "Yes"); - transferViewstates(page, params); + Login.transferViewstates(page, params); - page = getResponseData(postRequest(uri, params)); + page = Network.getResponseData(Network.postRequest(uri, params)); boolean guidOnPage = cache.isGuidContainedInPage(page); if (!guidOnPage) { Log.i(Settings.tag, "cgBase.removeFromWatchlist: cache removed from watchlist"); @@ -2117,75 +1738,6 @@ public class cgBase { 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 postTweetCache(String geocode) { - final cgCache cache = cgeoapplication.getInstance().loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); - String status; - final String url = cache.getUrl(); - if (url.length() >= 100) { - status = "I found " + url; - } - else { - String name = cache.getName(); - status = "I found " + name + " (" + url + ")"; - if (status.length() > Twitter.MAX_TWEET_SIZE) { - name = name.substring(0, name.length() - (status.length() - Twitter.MAX_TWEET_SIZE) - 3) + "..."; - } - status = "I found " + name + " (" + url + ")"; - status = Twitter.appendHashTag(status, "cgeo"); - status = Twitter.appendHashTag(status, "geocaching"); - } - - Twitter.postTweet(cgeoapplication.getInstance(), status, null); - } - - public static void postTweetTrackable(String geocode) { - final cgTrackable trackable = cgeoapplication.getInstance().getTrackableByGeocode(geocode); - String name = trackable.getName(); - if (name.length() > 82) { - name = name.substring(0, 79) + "..."; - } - StringBuilder builder = new StringBuilder("I touched "); - builder.append(name); - if (trackable.getUrl() != null) { - builder.append(" (").append(trackable.getUrl()).append(')'); - } - builder.append('!'); - String status = Twitter.appendHashTag(builder.toString(), "cgeo"); - status = Twitter.appendHashTag(status, "geocaching"); - Twitter.postTweet(cgeoapplication.getInstance(), status, null); - } - - 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 urlencode_rfc3986(String text) { - final String encoded = StringUtils.replace(URLEncoder.encode(text).replace("+", "%20"), "%7E", "~"); - - return encoded; - } - /** * Possibly hide caches found or hidden by user. This mutates its params argument when possible. * @@ -2207,269 +1759,6 @@ public class cgBase { return params; } - static private String prepareParameters(final String baseUri, final Parameters params) { - return CollectionUtils.isNotEmpty(params) ? baseUri + "?" + params.toString() : baseUri; - } - - static public String[] requestViewstates(final String uri, final Parameters params, boolean xContentType, boolean my) { - final HttpResponse response = request(uri, params, xContentType, my, false); - - return getViewstates(getResponseData(response)); - } - - static private String getResponseDataNoError(final HttpResponse response, boolean replaceWhitespace) { - try { - String data = EntityUtils.toString(response.getEntity(), HTTP.UTF_8); - return replaceWhitespace ? BaseUtils.replaceWhitespace(data) : data; - } catch (Exception e) { - Log.e(Settings.tag, "getResponseData", e); - return null; - } - } - - static public String getResponseData(final HttpResponse response) { - return getResponseData(response, true); - } - - static public String getResponseData(final HttpResponse response, boolean replaceWhitespace) { - if (!isSuccess(response)) { - return null; - } - return getResponseDataNoError(response, replaceWhitespace); - } - - /** - * POST HTTP request. Do the request a second time if the user is not logged in - * - * @param uri - * @return - */ - public static String postRequestLogged(final String uri) { - HttpResponse response = postRequest(uri, null); - String data = getResponseData(response); - - if (!getLoginStatus(data)) { - if (login() == StatusCode.NO_ERROR) { - response = postRequest(uri, null); - data = getResponseData(response); - } else { - Log.i(Settings.tag, "Working as guest."); - } - } - return data; - } - - /** - * GET HTTP request. Do the request a second time if the user is not logged in - * - * @param uri - * @param params - * @param xContentType - * @param my - * @param addF - * @return - */ - public static String requestLogged(final String uri, final Parameters params, boolean xContentType, boolean my, boolean addF) { - HttpResponse response = request(uri, params, xContentType, my, addF); - String data = getResponseData(response); - - if (!getLoginStatus(data)) { - if (login() == StatusCode.NO_ERROR) { - response = request(uri, params, xContentType, my, addF); - data = getResponseData(response); - } else { - Log.i(Settings.tag, "Working as guest."); - } - } - return data; - } - - /** - * GET HTTP request - * - * @param uri - * @param params - * @param xContentType - * @param my - * @param addF - * @return - */ - public static HttpResponse request(final String uri, final Parameters params, boolean xContentType, boolean my, boolean addF) { - return request(uri, addFToParams(params, my, addF), xContentType); - } - - final private static CookieStore cookieStore = new BasicCookieStore(); - private static boolean cookieStoreRestored = false; - final private static HttpParams clientParams = new BasicHttpParams(); - - static { - clientParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8); - clientParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000); - clientParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, 30000); - } - - public static HttpClient getHttpClient() { - final DefaultHttpClient client = new DefaultHttpClient(); - client.setCookieStore(cookieStore); - client.setParams(clientParams); - return client; - } - - public static void restoreCookieStore(final String oldCookies) { - if (!cookieStoreRestored) { - clearCookies(); - if (oldCookies != null) { - for (final String cookie : StringUtils.split(oldCookies, ';')) { - final String[] split = StringUtils.split(cookie, "=", 3); - if (split.length == 3) { - final BasicClientCookie newCookie = new BasicClientCookie(split[0], split[1]); - newCookie.setDomain(split[2]); - cookieStore.addCookie(newCookie); - } - } - } - cookieStoreRestored = true; - } - } - - public static String dumpCookieStore() { - StringBuilder cookies = new StringBuilder(); - for (final Cookie cookie : cookieStore.getCookies()) { - cookies.append(cookie.getName()); - cookies.append('='); - cookies.append(cookie.getValue()); - cookies.append('='); - cookies.append(cookie.getDomain()); - cookies.append(';'); - } - return cookies.toString(); - } - - public static void clearCookies() { - cookieStore.clear(); - } - - /** - * POST HTTP request - * - * @param uri - * @param params - * @return - */ - public static HttpResponse postRequest(final String uri, final List<? extends NameValuePair> params) { - try { - HttpPost request = new HttpPost(uri); - if (params != null) { - request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); - } - request.setHeader("X-Requested-With", "XMLHttpRequest"); - return request(request); - } catch (Exception e) { - // Can be UnsupportedEncodingException, ClientProtocolException or IOException - Log.e(Settings.tag, "postRequest", e); - return null; - } - } - - /** - * GET HTTP request - * - * @param uri - * @param params - * @param xContentType - * @return - */ - public static HttpResponse request(final String uri, final Parameters params, final boolean xContentType) { - final String fullUri = params == null ? uri : Uri.parse(uri).buildUpon().encodedQuery(params.toString()).build().toString(); - final HttpRequestBase request = new HttpGet(fullUri); - - request.setHeader("X-Requested-With", "XMLHttpRequest"); - - if (xContentType) { - request.setHeader("Content-Type", "application/x-www-form-urlencoded"); - } - - return request(request); - } - - private static HttpResponse request(final HttpRequestBase request) { - request.setHeader("Accept-Charset", "utf-8,iso-8859-1;q=0.8,utf-16;q=0.8,*;q=0.7"); - request.setHeader("Accept-Language", "en-US,*;q=0.9"); - request.getParams().setParameter(CoreProtocolPNames.USER_AGENT, cgBase.USER_AGENT); - return doRequest(request); - } - - static private String formatTimeSpan(final long before) { - // don't use String.format in a pure logging routine, it has very bad performance - return " (" + (System.currentTimeMillis() - before) + " ms) "; - } - - static public boolean isSuccess(final HttpResponse response) { - return response != null && response.getStatusLine().getStatusCode() == 200; - } - - static public HttpResponse doRequest(final HttpRequestBase request) { - final String reqLogStr = request.getMethod() + " " + hidePassword(request.getURI().toString()); - Log.d(Settings.tag, reqLogStr); - - final HttpClient client = getHttpClient(); - for (int i = 0; i <= NB_DOWNLOAD_RETRIES; i++) { - final long before = System.currentTimeMillis(); - try { - final HttpResponse response = client.execute(request); - int status = response.getStatusLine().getStatusCode(); - if (status == 200) { - Log.d(Settings.tag, status + formatTimeSpan(before) + reqLogStr); - } else { - Log.w(Settings.tag, status + " [" + response.getStatusLine().getReasonPhrase() + "]" + formatTimeSpan(before) + reqLogStr); - } - return response; - } catch (IOException e) { - final String timeSpan = formatTimeSpan(before); - final String tries = (i + 1) + "/" + (NB_DOWNLOAD_RETRIES + 1); - if (i == NB_DOWNLOAD_RETRIES) { - Log.e(Settings.tag, "Failure " + tries + timeSpan + reqLogStr, e); - } else { - Log.e(Settings.tag, "Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); - } - } - } - - return null; - } - - public static JSONObject requestJSON(final String uri, final Parameters params) { - final HttpGet request = new HttpGet(prepareParameters(uri, params)); - request.setHeader("Accept", "application/json, text/javascript, */*; q=0.01"); - request.setHeader("Content-Type", "application/json; charset=UTF-8"); - request.setHeader("X-Requested-With", "XMLHttpRequest"); - - final HttpResponse response = doRequest(request); - if (response != null && response.getStatusLine().getStatusCode() == 200) { - try { - return new JSONObject(getResponseData(response)); - } catch (JSONException e) { - Log.e(Settings.tag, "cgeoBase.requestJSON", e); - } - } - - return null; - } - - public static boolean deleteDirectory(File path) { - if (path.exists()) { - for (final File file : path.listFiles()) { - if (file.isDirectory()) { - deleteDirectory(file); - } else { - file.delete(); - } - } - } - - return path.delete(); - } - public static void storeCache(Activity activity, cgCache origCache, String geocode, int listId, CancellableHandler handler) { try { cgCache cache; @@ -2615,34 +1904,6 @@ public class cgBase { return false; } - public static Double getElevation(final Geopoint coords) { - try { - final String uri = "http://maps.googleapis.com/maps/api/elevation/json"; - final Parameters params = new Parameters( - "sensor", "false", - "locations", coords.format(Format.LAT_LON_DECDEGREE_COMMA)); - final JSONObject response = requestJSON(uri, params); - - if (response == null) { - return null; - } - - if (!StringUtils.equalsIgnoreCase(response.getString("status"), "OK")) { - 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(Settings.tag, "cgBase.getElevation: " + e.toString()); - } - - return null; - } - /** * Generate a time string according to system-wide settings (locale, 12/24 hour) * such as "13:24". @@ -2709,113 +1970,6 @@ public class cgBase { } /** - * insert text into the EditText at the current cursor position - * - * @param editText - * @param insertText - * @param moveCursor - * place the cursor after the inserted text - */ - public static void insertAtPosition(final EditText editText, final String insertText, final boolean moveCursor) { - int selectionStart = editText.getSelectionStart(); - int selectionEnd = editText.getSelectionEnd(); - int start = Math.min(selectionStart, selectionEnd); - int end = Math.max(selectionStart, selectionEnd); - - final String content = editText.getText().toString(); - String completeText; - if (start > 0 && !Character.isWhitespace(content.charAt(start - 1))) { - completeText = " " + insertText; - } else { - completeText = insertText; - } - - editText.getText().replace(start, end, completeText); - int newCursor = moveCursor ? start + completeText.length() : start; - editText.setSelection(newCursor, newCursor); - } - - public static LinearLayout createStarRating(final float value, final int count, final Context context) { - LayoutInflater inflater = LayoutInflater.from(context); - LinearLayout starsContainer = new LinearLayout(context); - starsContainer.setOrientation(LinearLayout.HORIZONTAL); - - for (int i = 0; i < count; i++) { - ImageView star = (ImageView) inflater.inflate(R.layout.star, null); - if (value - i >= 0.75) { - star.setImageResource(R.drawable.star_on); - } else if (value - i >= 0.25) { - star.setImageResource(R.drawable.star_half); - } else { - star.setImageResource(R.drawable.star_off); - } - starsContainer.addView(star); - } - - return starsContainer; - } - - public static boolean isActualLoginStatus() { - return actualLoginStatus; - } - - private static void setActualLoginStatus(boolean actualLoginStatus) { - cgBase.actualLoginStatus = actualLoginStatus; - } - - public static String getActualUserName() { - return actualUserName; - } - - private static void setActualUserName(String actualUserName) { - cgBase.actualUserName = actualUserName; - } - - public static String getActualMemberStatus() { - return actualMemberStatus; - } - - private static void setActualMemberStatus(final String actualMemberStatus) { - cgBase.actualMemberStatus = actualMemberStatus; - } - - public static int getActualCachesFound() { - return actualCachesFound; - } - - private static void setActualCachesFound(final int actualCachesFound) { - cgBase.actualCachesFound = actualCachesFound; - } - - public static String getActualStatus() { - return actualStatus; - } - - private static void setActualStatus(final String actualStatus) { - cgBase.actualStatus = actualStatus; - } - - /** - * Indicates whether the specified action can be used as an intent. This - * method queries the package manager for installed packages that can - * respond to an intent with the specified action. If no suitable package is - * found, this method returns false. - * - * From: http://android-developers.blogspot.com/2009/01/can-i-use-this-intent.html - * - * @param context - * The application's environment. - * @param action - * The Intent action to check for availability. - * - * @return True if an Intent with the specified action can be sent and - * responded to, false otherwise. - */ - public static boolean isIntentAvailable(Context context, String action) { - return isIntentAvailable(context, action, null); - } - - /** * Indicates whether the specified action can be used as an intent. This * method queries the package manager for installed packages that can * respond to an intent with the specified action. If no suitable package is diff --git a/main/src/cgeo/geocaching/cgCache.java b/main/src/cgeo/geocaching/cgCache.java index 821b9de..79a4209 100644 --- a/main/src/cgeo/geocaching/cgCache.java +++ b/main/src/cgeo/geocaching/cgCache.java @@ -6,6 +6,7 @@ import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.gc.GCBase; import cgeo.geocaching.connector.gc.GCConnector; +import cgeo.geocaching.connector.gc.Tile; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags.RemoveFlag; @@ -99,7 +100,7 @@ public class cgCache implements ICache { private String nameForSorting; private final EnumSet<StorageLocation> storageLocation = EnumSet.of(StorageLocation.HEAP); private boolean finalDefined = false; - private int zoomlevel = -1; + private int zoomlevel = Tile.ZOOMLEVEL_MAX; private static final Pattern NUMBER_PATTERN = Pattern.compile("\\d+"); @@ -135,9 +136,13 @@ public class cgCache implements ICache { detailed = true; detailedUpdate = other.detailedUpdate; coords = other.coords; + // boolean values must be enumerated here. Other types are assigned outside this if-statement premiumMembersOnly = other.premiumMembersOnly; reliableLatLon = other.reliableLatLon; archived = other.archived; + found = other.found; + own = other.own; + disabled = other.disabled; favorite = other.favorite; onWatchlist = other.onWatchlist; logOffline = other.logOffline; @@ -145,7 +150,7 @@ public class cgCache implements ICache { } /* - * No gathering for boolean members + * No gathering for boolean members if a cache is not-detailed * - found * - own * - disabled @@ -1163,17 +1168,13 @@ public class cgCache implements ICache { finalDefined = true; } } else { // this is a waypoint being edited - deleteWaypoint(waypoint); - - waypoints.add(waypoint); - // when waypoint was edited, finalDefined may have changed. check all waypoints and set again - finalDefined = false; - for (cgWaypoint wp : waypoints) { - if (wp.isFinalWithCoords()) { - finalDefined = true; - break; - } + final int index = getWaypointIndex(waypoint); + if (index >= 0) { + waypoints.remove(index); } + waypoints.add(waypoint); + // when waypoint was edited, finalDefined may have changed + resetFinalDefined(); } if (saveToDatabase) { @@ -1196,6 +1197,19 @@ public class cgCache implements ICache { this.finalDefined = finalDefined; } + /** + * Reset <code>finalDefined</code> based on current list of stored waypoints + */ + private void resetFinalDefined() { + finalDefined = false; + for (cgWaypoint wp : waypoints) { + if (wp.isFinalWithCoords()) { + finalDefined = true; + break; + } + } + } + public boolean hasUserModifiedCoords() { return userModifiedCoords; } @@ -1247,13 +1261,7 @@ public class cgCache implements ICache { cgeoapplication.getInstance().removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); // Check status if Final is defined if (waypoint.isFinalWithCoords()) { - finalDefined = false; - for (cgWaypoint wp : waypoints) { - if (wp.isFinalWithCoords()) { - finalDefined = true; - break; - } - } + resetFinalDefined(); } return true; } @@ -1272,16 +1280,32 @@ public class cgCache implements ICache { return false; } + final int index = getWaypointIndex(waypoint); + if (index >= 0) { + return deleteWaypoint(index); + } + + return false; + } + + /** + * Find index of given <code>waypoint</code> in cache's <code>waypoints</code> list + * + * @param waypoint + * to find index for + * @return index in <code>waypoints</code> if found, else -1 + */ + private int getWaypointIndex(cgWaypoint waypoint) { int index = 0; for (cgWaypoint wp : waypoints) { if (wp.getId() == waypoint.getId()) { - return deleteWaypoint(index); + return index; } index++; } - return false; + return -1; } /** diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java index ac0be8f..f6d17c7 100644 --- a/main/src/cgeo/geocaching/cgData.java +++ b/main/src/cgeo/geocaching/cgData.java @@ -969,7 +969,7 @@ public class cgData { public void run() { for (final File dir : toRemove) { Log.i(Settings.tag, "Removing obsolete cache directory for " + dir.getName()); - cgBase.deleteDirectory(dir); + LocalStorage.deleteDirectory(dir); } } }).start(); @@ -3047,7 +3047,7 @@ public class cgData { // Delete cache directories for (final String geocode : geocodes) { - cgBase.deleteDirectory(LocalStorage.getStorageDir(geocode)); + LocalStorage.deleteDirectory(LocalStorage.getStorageDir(geocode)); } } } diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/cgeo.java index 03184b3..7367d82 100644 --- a/main/src/cgeo/geocaching/cgeo.java +++ b/main/src/cgeo/geocaching/cgeo.java @@ -12,6 +12,7 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.HumanDistance; import cgeo.geocaching.geopoint.IConversion; import cgeo.geocaching.maps.CGeoMap; +import cgeo.geocaching.network.Login; import cgeo.geocaching.ui.Formatter; import org.apache.commons.collections.CollectionUtils; @@ -77,14 +78,14 @@ public class cgeo extends AbstractActivity { TextView userInfoView = (TextView) findViewById(R.id.user_info); StringBuilder userInfo = new StringBuilder("geocaching.com").append(Formatter.SEPARATOR); - if (cgBase.isActualLoginStatus()) { - userInfo.append(cgBase.getActualUserName()); - if (cgBase.getActualCachesFound() >= 0) { - userInfo.append(" (").append(String.valueOf(cgBase.getActualCachesFound())).append(')'); + if (Login.isActualLoginStatus()) { + userInfo.append(Login.getActualUserName()); + if (Login.getActualCachesFound() >= 0) { + userInfo.append(" (").append(String.valueOf(Login.getActualCachesFound())).append(')'); } userInfo.append(Formatter.SEPARATOR); } - userInfo.append(cgBase.getActualStatus()); + userInfo.append(Login.getActualStatus()); userInfoView.setText(userInfo.toString()); } @@ -790,11 +791,11 @@ public class cgeo extends AbstractActivity { } // login - final StatusCode status = cgBase.login(); + final StatusCode status = Login.login(); if (status == StatusCode.NO_ERROR) { app.firstRun = false; - cgBase.detectGcCustomDate(); + Login.detectGcCustomDate(); updateUserInfoHandler.sendEmptyMessage(-1); } diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java index b38c466..fa5ca02 100644 --- a/main/src/cgeo/geocaching/cgeocaches.java +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -21,6 +21,7 @@ import cgeo.geocaching.filter.TrackablesFilter; import cgeo.geocaching.filter.TypeFilter; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.CGeoMap; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.sorting.CacheComparator; import cgeo.geocaching.sorting.DateComparator; @@ -205,14 +206,14 @@ public class cgeocaches extends AbstractListActivity { dialog.setNegativeButton(res.getString(R.string.license_dismiss), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - cgBase.clearCookies(); + Network.clearCookies(); dialog.cancel(); } }); dialog.setPositiveButton(res.getString(R.string.license_show), new DialogInterface.OnClickListener() { public void onClick(DialogInterface dialog, int id) { - cgBase.clearCookies(); + Network.clearCookies(); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse("http://www.geocaching.com/software/agreement.aspx?ID=0"))); } }); @@ -1903,10 +1904,10 @@ public class cgeocaches extends AbstractListActivity { deviceCode = ""; } final Parameters params = new Parameters("code", deviceCode); - HttpResponse responseFromWeb = cgBase.request("http://send2.cgeo.org/read.html", params, true); + HttpResponse responseFromWeb = Network.request("http://send2.cgeo.org/read.html", params, true); if (responseFromWeb != null && responseFromWeb.getStatusLine().getStatusCode() == 200) { - final String response = cgBase.getResponseData(responseFromWeb); + final String response = Network.getResponseData(responseFromWeb); if (response.length() > 2) { String GCcode = response; @@ -2246,11 +2247,12 @@ public class cgeocaches extends AbstractListActivity { } public void switchListById(int id) { - StoredList list = null; + if (id < 0) { + return; + } - if (id >= 0) { - list = app.getList(id); - } else { + StoredList list = app.getList(id); + if (list == null) { return; } diff --git a/main/src/cgeo/geocaching/cgeoinit.java b/main/src/cgeo/geocaching/cgeoinit.java index 1a694bd..a8e660a 100644 --- a/main/src/cgeo/geocaching/cgeoinit.java +++ b/main/src/cgeo/geocaching/cgeoinit.java @@ -7,6 +7,8 @@ import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.maps.MapProviderFactory; +import cgeo.geocaching.network.Login; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.twitter.TwitterAuthorizationActivity; import cgeo.geocaching.utils.LogTemplateProvider; @@ -213,7 +215,7 @@ public class cgeoinit extends AbstractActivity { private boolean insertSignatureTemplate(final LogTemplate template) { EditText sig = (EditText) findViewById(R.id.signature); String insertText = "[" + template.getTemplateString() + "]"; - cgBase.insertAtPosition(sig, insertText, true); + insertAtPosition(sig, insertText, true); return true; } @@ -404,6 +406,7 @@ public class cgeoinit extends AbstractActivity { }); final CheckBox dirImgButton = (CheckBox) findViewById(R.id.loaddirectionimg); + dirImgButton.setEnabled(!Settings.isPremiumMember()); dirImgButton.setChecked(Settings.getLoadDirImg()); dirImgButton.setOnClickListener(new View.OnClickListener() { @@ -807,17 +810,17 @@ public class cgeoinit extends AbstractActivity { loginDialog.setCancelable(false); Settings.setLogin(username, password); - cgBase.clearCookies(); + Network.clearCookies(); (new Thread() { @Override public void run() { - final StatusCode loginResult = cgBase.login(); + final StatusCode loginResult = Login.login(); Object payload = loginResult; if (loginResult == StatusCode.NO_ERROR) { - cgBase.detectGcCustomDate(); - payload = cgBase.downloadAvatarAndGetMemberStatus(cgeoinit.this); + Login.detectGcCustomDate(); + payload = Login.downloadAvatarAndGetMemberStatus(cgeoinit.this); } logInHandler.obtainMessage(0, payload).sendToTarget(); } @@ -849,12 +852,12 @@ public class cgeoinit extends AbstractActivity { final String cod = StringUtils.defaultString(deviceCode); final Parameters params = new Parameters("name", nam, "code", cod); - HttpResponse response = cgBase.request("http://send2.cgeo.org/auth.html", params, true); + HttpResponse response = Network.request("http://send2.cgeo.org/auth.html", params, true); if (response != null && response.getStatusLine().getStatusCode() == 200) { //response was OK - String[] strings = cgBase.getResponseData(response).split(","); + String[] strings = Network.getResponseData(response).split(","); try { pin = Integer.parseInt(strings[1].trim()); } catch (Exception e) { diff --git a/main/src/cgeo/geocaching/cgeopopup.java b/main/src/cgeo/geocaching/cgeopopup.java index ac74519..91546b8 100644 --- a/main/src/cgeo/geocaching/cgeopopup.java +++ b/main/src/cgeo/geocaching/cgeopopup.java @@ -605,7 +605,7 @@ public class cgeopopup extends AbstractActivity { itemName.setText(res.getString(R.string.cache_rating)); itemValue.setText(String.format("%.1f", rating) + ' ' + res.getString(R.string.cache_rating_of) + " 5"); - itemStars.addView(cgBase.createStarRating(rating, 5, this), 1); + itemStars.addView(createStarRating(rating, 5, this), 1); if (votes > 0) { final TextView itemAddition = (TextView) itemLayout.findViewById(R.id.addition); diff --git a/main/src/cgeo/geocaching/cgeotouch.java b/main/src/cgeo/geocaching/cgeotouch.java index 6271ec1..e5af4d3 100644 --- a/main/src/cgeo/geocaching/cgeotouch.java +++ b/main/src/cgeo/geocaching/cgeotouch.java @@ -3,7 +3,10 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.network.Login; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.DateDialog; import org.apache.commons.lang3.StringUtils; @@ -389,9 +392,9 @@ public class cgeotouch extends AbstractActivity implements DateDialog.DateDialog return; } - final String page = cgBase.getResponseData(cgBase.request("http://www.geocaching.com/track/log.aspx", params, false, false, false)); + final String page = Network.getResponseData(Network.request("http://www.geocaching.com/track/log.aspx", params, false, false, false)); - viewstates = cgBase.getViewstates(page); + viewstates = Login.getViewstates(page); final List<LogType> typesPre = cgBase.parseTypes(page); if (typesPre.size() > 0) { @@ -444,7 +447,7 @@ public class cgeotouch extends AbstractActivity implements DateDialog.DateDialog if (status == StatusCode.NO_ERROR && Settings.isUseTwitter() && Settings.isTwitterLoginValid() && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { - cgBase.postTweetTrackable(geocode); + Twitter.postTweetTrackable(geocode); } return status; diff --git a/main/src/cgeo/geocaching/connector/gc/GCBase.java b/main/src/cgeo/geocaching/connector/gc/GCBase.java index e7008aa..993e062 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCBase.java +++ b/main/src/cgeo/geocaching/connector/gc/GCBase.java @@ -9,7 +9,10 @@ import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; import cgeo.geocaching.enumerations.LiveMapStrategy.StrategyFlag; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.geopoint.IConversion; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.LeastRecentlyUsedCache; @@ -29,8 +32,6 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; -import java.util.regex.Matcher; -import java.util.regex.Pattern; /** * GC.com/Groundspeak (GS) specific stuff @@ -46,25 +47,6 @@ public class GCBase { protected final static long GC_BASE31 = 31; protected final static long GC_BASE16 = 16; - // Pixel colors in tile - private final static int BORDER_GRAY = 0x5F5F5F; - private final static int DARK_GREEN = 0x316013; // Tradi 14 - private final static int LIGHT_GREEN = 0x80AF64; // Tradi 13 - private final static int DARK_BLUE = 0x243C97; // Mystery - private final static int YELLOW = 0xFFDE19; // Multi 14,13 - private final static int FOUND = 0xFBEA5D; // Found - - // Offset inside cache icon - private final static int POSX_TRADI = 7; - private final static int POSY_TRADI = -12; - private final static int POSX_MULTI = 5; // for orange 8 - private final static int POSY_MULTI = -9; // for orange 10 - private final static int POSX_MYSTERY = 5; - private final static int POSY_MYSTERY = -13; - private final static int POSX_FOUND = 10; - private final static int POSY_FOUND = -8; - - private final static Pattern PATTERN_JSON_KEY = Pattern.compile("[^\\d]*" + "(\\d+),\\s*(\\d+)" + "[^\\d]*"); // (12, 34) /** * Searches the view port on the live map with Strategy.AUTO * @@ -78,9 +60,23 @@ public class GCBase { Strategy strategy = Settings.getLiveMapStrategy(); if (strategy == Strategy.AUTO) { float speedNow = cgeoapplication.getInstance().getSpeedFromGeo(); - strategy = speedNow >= 8 ? Strategy.FASTEST : Strategy.DETAILED; // 8 m/s = 30 km/h + strategy = speedNow >= 8 ? Strategy.FAST : Strategy.DETAILED; // 8 m/s = 30 km/h + } + // return searchByViewport(viewport, tokens, strategy); + + // testing purpose + { + SearchResult result = searchByViewport(viewport, tokens, strategy); + String text = Formatter.SEPARATOR + strategy.getL10n() + Formatter.SEPARATOR; + int speed = (int) cgeoapplication.getInstance().getSpeedFromGeo(); + if (Settings.isUseMetricUnits()) { + text += speed + " km/h"; + } else { + text += speed / IConversion.MILES_TO_KILOMETER + " mph"; + } + result.setUrl(result.getUrl() + text); + return result; } - return searchByViewport(viewport, tokens, strategy); } /** @@ -108,6 +104,12 @@ public class GCBase { final Set<Tile> tiles = getTilesForViewport(viewport); for (Tile tile : tiles) { + + // testing purpose + { + searchResult.setUrl("Zoom=" + tile.getZoomlevel()); + } + StringBuilder url = new StringBuilder(); url.append("?x=").append(tile.getX()) // x tile .append("&y=").append(tile.getY()) // y tile @@ -136,12 +138,12 @@ public class GCBase { final String urlString = url.toString(); // The PNG must be request before ! Else the following request would return with 204 - No Content - Bitmap bitmap = cgBase.requestMapTile(GCConstants.URL_MAP_TILE + urlString, referer); + Bitmap bitmap = Tile.requestMapTile(GCConstants.URL_MAP_TILE + urlString, referer); assert bitmap.getWidth() == Tile.TILE_SIZE : "Bitmap has wrong width"; assert bitmap.getHeight() == Tile.TILE_SIZE : "Bitmap has wrong height"; - String data = cgBase.requestMapInfo(GCConstants.URL_MAP_INFO + urlString, referer); + String data = Tile.requestMapInfo(GCConstants.URL_MAP_INFO + urlString, referer); if (StringUtils.isEmpty(data)) { Log.e(Settings.tag, "GCBase.searchByViewport: No data from server for tile (" + tile.getX() + "/" + tile.getY() + ")"); } else { @@ -234,7 +236,7 @@ public class GCBase { for (int i = 1; i < keys.length(); i++) { // index 0 is empty String key = keys.getString(i); if (StringUtils.isNotBlank(key)) { - int[] xy = splitJSONKey(key); + UTFGridPosition pos = UTFGridPosition.fromString(key); JSONArray dataForKey = dataObject.getJSONArray(key); for (int j = 0; j < dataForKey.length(); j++) { JSONObject cacheInfo = dataForKey.getJSONObject(j); @@ -244,21 +246,17 @@ public class GCBase { List<UTFGridPosition> listOfPositions = positions.get(id); if (listOfPositions == null) { listOfPositions = new ArrayList<UTFGridPosition>(); + positions.put(id, listOfPositions); } - /* - * Optimization - * UTFGridPosition pos = keyPositions.get(key); - */ - UTFGridPosition pos = new UTFGridPosition(xy[0], xy[1]); + listOfPositions.add(pos); - positions.put(id, listOfPositions); } } } for (String id : positions.keySet()) { List<UTFGridPosition> pos = positions.get(id); - UTFGridPosition xy = getPositionInGrid(pos); + UTFGridPosition xy = UTFGrid.getPositionInGrid(pos); cgCache cache = new cgCache(); cache.setDetailed(false); cache.setReliableLatLon(false); @@ -266,12 +264,9 @@ public class GCBase { cache.setName(nameCache.get(id)); cache.setZoomlevel(tile.getZoomlevel()); cache.setCoords(tile.getCoord(xy)); - if (strategy.flags.contains(StrategyFlag.PARSE_TILES)) { - if (tile.getZoomlevel() >= 14) { - parseMapPNG14(cache, bitmap, xy); - } else { - parseMapPNG13(cache, bitmap, xy); - } + if (strategy.flags.contains(StrategyFlag.PARSE_TILES) && positions.size() < 64) { + // don't parse if there are too many caches. The decoding would return too much wrong results + IconDecoder.parseMapPNG(cache, bitmap, xy, tile.getZoomlevel()); } else { cache.setType(CacheType.UNKNOWN); } @@ -286,89 +281,6 @@ public class GCBase { return searchResult; } - // Try to get the cache type from the PNG image for a tile */ - public static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; - int countX = 0; - int countY = 0; - - // search for left border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != BORDER_GRAY) { - if (--x < 0 || ++countX > 20) { - return; - } - } - // search for bottom border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { - if (++y >= Tile.TILE_SIZE || ++countY > 20) { - return; - } - } - - try { - if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == DARK_GREEN) { - cache.setType(CacheType.TRADITIONAL); - return; - } - if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == DARK_BLUE) { - cache.setType(CacheType.MYSTERY); - return; - } - if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == YELLOW) { - cache.setType(CacheType.MULTI); - return; - } - if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == FOUND) { - cache.setFound(true); - return; - } - } catch (IllegalArgumentException e) { - // intentionally left blank - } - - return; - } - - // Try to get the cache type from the PNG image for a tile */ - public static void parseMapPNG13(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { - - int x = xy.getX() * 4 + 2; - int y = xy.getY() * 4 + 2; - int countY = 0; - - // search for top border - while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != BORDER_GRAY) { - if (--y < 0 || ++countY > 12) { - return; - } - } - - try { - int color = bitmap.getPixel(x, y + 2) & 0x00FFFFFF; - - switch (color) { - case LIGHT_GREEN: - cache.setType(CacheType.TRADITIONAL); - return; - case YELLOW: - cache.setType(CacheType.MULTI); - return; - } - if ((color | 0x00FFFF) == 0x00FFFF) { // BLUE - cache.setType(CacheType.MYSTERY); - return; - } - // Found consists out of too many different colors - } - catch (IllegalArgumentException e) { - // intentionally left blank - } - - return; - } - - /** * Calculate needed tiles for the given viewport @@ -387,21 +299,6 @@ public class GCBase { return tiles; } - /** Calculate from a list of positions (x/y) the coords */ - protected static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { - int minX = UTFGrid.GRID_MAXX; - int maxX = 0; - int minY = UTFGrid.GRID_MAXY; - int maxY = 0; - for (UTFGridPosition pos : positions) { - minX = Math.min(minX, pos.x); - maxX = Math.max(maxX, pos.x); - minY = Math.min(minY, pos.y); - maxY = Math.max(maxY, pos.y); - } - return new UTFGridPosition((minX + maxX) / 2, (minY + maxY) / 2); - } - /** * Convert GCCode (geocode) to (old) GCIds * @@ -485,29 +382,11 @@ public class GCBase { /** Get user session & session token from the Live Map. Needed for following requests */ public static String[] getTokens() { - final HttpResponse response = cgBase.request(GCConstants.URL_LIVE_MAP, null, false); - final String data = cgBase.getResponseData(response); + final HttpResponse response = Network.request(GCConstants.URL_LIVE_MAP, null, false); + final String data = Network.getResponseData(response); String userSession = BaseUtils.getMatch(data, GCConstants.PATTERN_USERSESSION, ""); String sessionToken = BaseUtils.getMatch(data, GCConstants.PATTERN_SESSIONTOKEN, ""); return new String[] { userSession, sessionToken }; } - /** - * @param key - * Key in the format (xx, xx) - * @return - */ - static int[] splitJSONKey(String key) { - final Matcher matcher = PATTERN_JSON_KEY.matcher(key); - try { - if (matcher.matches()) { - final int x = Integer.parseInt(matcher.group(1)); - final int y = Integer.parseInt(matcher.group(2)); - return new int[] { x, y }; - } - } catch (NumberFormatException e) { - } - return new int[] { 0, 0 }; - } - } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 95e4921..4f319f3 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -8,8 +8,8 @@ import cgeo.geocaching.cgCache; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.enumerations.StatusCode; -import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.CancellableHandler; @@ -104,10 +104,10 @@ public class GCConnector extends AbstractConnector { cgBase.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_loadpage); - final String page = cgBase.requestLogged("http://www.geocaching.com/seek/cache_details.aspx", params, false, false, false); + final String page = Network.requestLogged("http://www.geocaching.com/seek/cache_details.aspx", params, false, false, false); if (StringUtils.isEmpty(page)) { - SearchResult search = new SearchResult(); + final SearchResult search = new SearchResult(); if (app.isThere(geocode, guid, true, false)) { if (StringUtils.isBlank(geocode) && StringUtils.isNotBlank(guid)) { Log.i(Settings.tag, "Loading old cache from cache."); @@ -132,18 +132,12 @@ public class GCConnector extends AbstractConnector { return searchResult; } - SearchResult search = searchResult.filterSearchResults(false, false, Settings.getCacheType()); + final SearchResult search = searchResult.filterSearchResults(false, false, Settings.getCacheType()); return search; } @Override - public SearchResult searchByCoordinate(Geopoint center) { - // TODO Auto-generated method stub - return super.searchByCoordinate(center); - } - - @Override public SearchResult searchByViewport(Viewport viewport, String[] tokens) { return GCBase.searchByViewport(viewport, tokens); } diff --git a/main/src/cgeo/geocaching/connector/gc/IconDecoder.java b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java new file mode 100644 index 0000000..43ca2ce --- /dev/null +++ b/main/src/cgeo/geocaching/connector/gc/IconDecoder.java @@ -0,0 +1,164 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.cgCache; +import cgeo.geocaching.enumerations.CacheType; + +import android.graphics.Bitmap; + +/** + * icon decoder for cache icons + * + */ +public abstract class IconDecoder { + + public static void parseMapPNG(final cgCache cache, Bitmap bitmap, UTFGridPosition xy, int zoomlevel) { + if (zoomlevel >= 14) { + parseMapPNG14(cache, bitmap, xy); + } else { + parseMapPNG13(cache, bitmap, xy); + } + } + + private static final int[] OFFSET_X = new int[] { 0, -1, -1, 0, 1, 1, 1, 0, -1, -2, -2, -2, -2, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2 }; + private static final int[] OFFSET_Y = new int[] { 0, 0, 1, 1, 1, 0, -1, -1, -1, -1, 0, 1, 2, 2, 2, 2, 2, 1, 0, -1, -2, -2, -2, -2, -2 }; + + /** + * The icon decoder walks a spiral around the center pixel position of the cache + * and searches for characteristic colors. + * + * @param cache + * @param bitmap + * @param xy + */ + private static void parseMapPNG13(final cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + final int xCenter = xy.getX() * 4 + 2; + final int yCenter = xy.getY() * 4 + 2; + final int bitmapWidth = bitmap.getWidth(); + final int bitmapHeight = bitmap.getHeight(); + + int countMulti = 0; + int countFound = 0; + + for (int i = 0; i < OFFSET_X.length; i++) { + + // assert that we are still in the tile + final int x = xCenter + OFFSET_X[i]; + if (x < 0 || x >= bitmapWidth) { + continue; + } + + final int y = yCenter + OFFSET_Y[i]; + if (y < 0 || y >= bitmapHeight) { + continue; + } + + int color = bitmap.getPixel(x, y) & 0x00FFFFFF; + + // transparent pixels are not interesting + if (color == 0) { + continue; + } + + int red = (color & 0xFF0000) >> 16; + int green = (color & 0xFF00) >> 8; + int blue = color & 0xFF; + + // these are quite sure, so one pixel is enough for matching + if (green > 0x80 && green > red && green > blue) { + cache.setType(CacheType.TRADITIONAL); + return; + } + if (blue > 0x80 && blue > red && blue > green) { + cache.setType(CacheType.MYSTERY); + return; + } + if (red > 0x90 && blue < 0x10 && green < 0x10) { + cache.setType(CacheType.EVENT); + return; + } + + // next two are hard to distinguish, therefore we sample all pixels of the spiral + if (red > 0xFA && green > 0xD0) { + countMulti++; + } + if (red < 0xF3 && red > 0xa0 && green > 0x20 && blue < 0x80) { + countFound++; + } + } + + // now check whether we are sure about found/multi + if (countFound > countMulti && countFound >= 2) { + cache.setFound(true); + } + if (countMulti > countFound && countMulti >= 5) { + cache.setType(CacheType.MULTI); + } + } + + // Pixel colors in tile + private final static int COLOR_BORDER_GRAY = 0x5F5F5F; + private final static int COLOR_TRADITIONAL = 0x316013; + private final static int COLOR_MYSTERY = 0x243C97; + private final static int COLOR_MULTI = 0xFFDE19; + private final static int COLOR_FOUND = 0xFBEA5D; + + // Offset inside cache icon + private final static int POSX_TRADI = 7; + private final static int POSY_TRADI = -12; + private final static int POSX_MULTI = 5; // for orange 8 + private final static int POSY_MULTI = -9; // for orange 10 + private final static int POSX_MYSTERY = 5; + private final static int POSY_MYSTERY = -13; + private final static int POSX_FOUND = 10; + private final static int POSY_FOUND = -8; + + /** + * For level 14 find the borders of the icons and then use a single pixel and color to match. + * + * @param cache + * @param bitmap + * @param xy + */ + private static void parseMapPNG14(cgCache cache, Bitmap bitmap, UTFGridPosition xy) { + int x = xy.getX() * 4 + 2; + int y = xy.getY() * 4 + 2; + + // search for left border + int countX = 0; + while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != COLOR_BORDER_GRAY) { + if (--x < 0 || ++countX > 20) { + return; + } + } + // search for bottom border + int countY = 0; + while ((bitmap.getPixel(x, y) & 0x00FFFFFF) != 0x000000) { + if (++y >= Tile.TILE_SIZE || ++countY > 20) { + return; + } + } + + try { + if ((bitmap.getPixel(x + POSX_TRADI, y + POSY_TRADI) & 0x00FFFFFF) == COLOR_TRADITIONAL) { + cache.setType(CacheType.TRADITIONAL); + return; + } + if ((bitmap.getPixel(x + POSX_MYSTERY, y + POSY_MYSTERY) & 0x00FFFFFF) == COLOR_MYSTERY) { + cache.setType(CacheType.MYSTERY); + return; + } + if ((bitmap.getPixel(x + POSX_MULTI, y + POSY_MULTI) & 0x00FFFFFF) == COLOR_MULTI) { + cache.setType(CacheType.MULTI); + return; + } + if ((bitmap.getPixel(x + POSX_FOUND, y + POSY_FOUND) & 0x00FFFFFF) == COLOR_FOUND) { + cache.setFound(true); + return; + } + } catch (IllegalArgumentException e) { + // intentionally left blank + } + + return; + } +} diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 9d38a64..eec83b0 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -1,6 +1,17 @@ package cgeo.geocaching.connector.gc; +import cgeo.geocaching.Settings; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.network.Network; + +import org.apache.http.HttpResponse; +import org.apache.http.client.methods.HttpGet; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +import java.io.IOException; /** * All about tiles. @@ -200,4 +211,28 @@ public class Tile { public int hashCode() { return toString().hashCode(); } + + /** Request JSON informations for a tile */ + public static String requestMapInfo(final String url, final String referer) { + final HttpGet request = new HttpGet(url); + request.addHeader("Accept", "application/json, text/javascript, */*; q=0.01"); + request.addHeader("Referer", referer); + request.addHeader("X-Requested-With", "XMLHttpRequest"); + return Network.getResponseData(Network.request(request), false); + } + + /** Request .png image for a tile. */ + public static Bitmap requestMapTile(final String url, final String referer) { + final HttpGet request = new HttpGet(url); + request.addHeader("Accept", "image/png,image/*;q=0.8,*/*;q=0.5"); + request.addHeader("Referer", referer); + request.addHeader("X-Requested-With", "XMLHttpRequest"); + final HttpResponse response = Network.request(request); + try { + return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; + } catch (IOException e) { + Log.e(Settings.tag, "cgBase.requestMapTile() " + e.getMessage()); + } + return null; + } } diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java index cf490ec..a663a71 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGrid.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGrid.java @@ -1,5 +1,7 @@ package cgeo.geocaching.connector.gc; +import java.util.List; + /** * * @author blafoo @@ -28,4 +30,19 @@ public final class UTFGrid { return (short) (result - 32); } + /** Calculate from a list of positions (x/y) the coords */ + protected static UTFGridPosition getPositionInGrid(List<UTFGridPosition> positions) { + int minX = GRID_MAXX; + int maxX = 0; + int minY = GRID_MAXY; + int maxY = 0; + for (UTFGridPosition pos : positions) { + minX = Math.min(minX, pos.x); + maxX = Math.max(maxX, pos.x); + minY = Math.min(minY, pos.y); + maxY = Math.max(maxY, pos.y); + } + return new UTFGridPosition((minX + maxX) / 2, (minY + maxY) / 2); + } + } diff --git a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java index e88a425..1aae560 100644 --- a/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java +++ b/main/src/cgeo/geocaching/connector/gc/UTFGridPosition.java @@ -1,5 +1,8 @@ package cgeo.geocaching.connector.gc; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + /** * Representation of a position inside an UTFGrid @@ -11,6 +14,7 @@ public final class UTFGridPosition { public final int x; public final int y; + private final static Pattern PATTERN_JSON_KEY = Pattern.compile("[^\\d]*" + "(\\d+),\\s*(\\d+)" + "[^\\d]*"); // (12, 34) public UTFGridPosition(final int x, final int y) { assert x >= 0 && x <= UTFGrid.GRID_MAXX : "x outside bounds"; @@ -28,4 +32,22 @@ public final class UTFGridPosition { return y; } + /** + * @param key + * Key in the format (xx, xx) + * @return + */ + static UTFGridPosition fromString(String key) { + final Matcher matcher = UTFGridPosition.PATTERN_JSON_KEY.matcher(key); + try { + if (matcher.matches()) { + final int x = Integer.parseInt(matcher.group(1)); + final int y = Integer.parseInt(matcher.group(2)); + return new UTFGridPosition(x, y); + } + } catch (NumberFormatException e) { + } + return new UTFGridPosition(0, 0); + } + } diff --git a/main/src/cgeo/geocaching/connector/opencaching/OkapiClient.java b/main/src/cgeo/geocaching/connector/opencaching/OkapiClient.java index a69d5c9..9c3ab4a 100644 --- a/main/src/cgeo/geocaching/connector/opencaching/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/opencaching/OkapiClient.java @@ -1,7 +1,6 @@ package cgeo.geocaching.connector.opencaching; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; import cgeo.geocaching.cgCache; import cgeo.geocaching.cgImage; import cgeo.geocaching.cgLog; @@ -13,6 +12,7 @@ import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.geopoint.GeopointParser; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import org.apache.commons.lang3.StringUtils; @@ -304,6 +304,6 @@ final public class OkapiClient { final String uri = "http://" + host + service; ((ApiOpenCachingConnector) connector).addAuthentication(params); - return cgBase.requestJSON(uri, params); + return Network.requestJSON(uri, params); } } diff --git a/main/src/cgeo/geocaching/files/LocalStorage.java b/main/src/cgeo/geocaching/files/LocalStorage.java index c127e89..b5fa5f6 100644 --- a/main/src/cgeo/geocaching/files/LocalStorage.java +++ b/main/src/cgeo/geocaching/files/LocalStorage.java @@ -4,7 +4,7 @@ import cgeo.geocaching.Settings; import cgeo.geocaching.utils.CryptUtils; import org.apache.commons.lang3.StringUtils; -import org.apache.http.HttpEntity; +import org.apache.http.HttpResponse; import android.os.Environment; import android.util.Log; @@ -143,27 +143,49 @@ public class LocalStorage { * the entity whose content will be saved * @param targetFile * the target file, which will be created if necessary - * @return true if the operation was sucessful, false otherwise + * @return true if the operation was successful, false otherwise */ - public static boolean saveEntityToFile(final HttpEntity entity, final File targetFile) { - if (entity == null) { + public static boolean saveEntityToFile(final HttpResponse response, final File targetFile) { + if (response == null) { + return false; + } + + try { + return saveToFile(response.getEntity().getContent(), targetFile); + } catch (IOException e) { + Log.e(Settings.tag, "LocalStorage.saveEntityToFile", e); + } + + return false; + } + + /** + * Save an HTTP response to a file. + * + * @param entity + * the entity whose content will be saved + * @param targetFile + * the target file, which will be created if necessary + * @return true if the operation was successful, false otherwise + */ + public static boolean saveToFile(final InputStream inputStream, final File targetFile) { + if (inputStream == null) { return false; } try { - final InputStream is = entity.getContent(); try { final FileOutputStream fos = new FileOutputStream(targetFile); try { - return copy(is, fos); + return copy(inputStream, fos); } finally { fos.close(); } } finally { - is.close(); + inputStream.close(); } } catch (IOException e) { - Log.e(Settings.tag, "LocalStorage.saveEntityToFile", e); + Log.e(Settings.tag, "LocalStorage.saveToFile", e); } return false; } @@ -235,4 +257,18 @@ public class LocalStorage { return Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED); } + public static boolean deleteDirectory(File path) { + if (path.exists()) { + for (final File file : path.listFiles()) { + if (file.isDirectory()) { + deleteDirectory(file); + } else { + file.delete(); + } + } + } + + return path.delete(); + } + } diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index c98d4d9..fa2af0d 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -1,8 +1,8 @@ package cgeo.geocaching.gcvote; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; import cgeo.geocaching.cgCache; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.LeastRecentlyUsedCache; @@ -96,7 +96,7 @@ public final class GCVote { params.put("waypoints", StringUtils.join(geocodes.toArray(), ',')); } params.put("version", "cgeo"); - final String page = cgBase.getResponseData(cgBase.request("http://gcvote.com/getVotes.php", params, false, false, false)); + final String page = Network.getResponseData(Network.request("http://gcvote.com/getVotes.php", params, false, false, false)); if (page == null) { return null; } @@ -220,7 +220,7 @@ public final class GCVote { "voteUser", String.format("%.1f", vote).replace(',', '.'), "version", "cgeo"); - final String result = cgBase.getResponseData(cgBase.request("http://gcvote.com/setVote.php", params, false, false, false)); + final String result = Network.getResponseData(Network.request("http://gcvote.com/setVote.php", params, false, false, false)); return result.trim().equalsIgnoreCase("ok"); } diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index ca67014..fdcc663 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -1,8 +1,16 @@ package cgeo.geocaching.geopoint; +import cgeo.geocaching.Settings; +import cgeo.geocaching.geopoint.GeopointFormatter.Format; +import cgeo.geocaching.network.Network; +import cgeo.geocaching.network.Parameters; + import org.apache.commons.lang3.StringUtils; +import org.json.JSONArray; +import org.json.JSONObject; import android.location.Location; +import android.util.Log; import java.math.BigDecimal; import java.math.RoundingMode; @@ -234,7 +242,7 @@ public final class Geopoint /** * Returns formatted coordinates with default format. * Default format is decimalminutes, e.g. N 52° 36.123 E 010° 03.456 - * + * * @return formatted coordinates */ @Override @@ -475,4 +483,33 @@ public final class Geopoint super(msg); } } + + public Double getElevation() { + try { + final String uri = "http://maps.googleapis.com/maps/api/elevation/json"; + final Parameters params = new Parameters( + "sensor", "false", + "locations", format(Format.LAT_LON_DECDEGREE_COMMA)); + final JSONObject response = Network.requestJSON(uri, params); + + if (response == null) { + return null; + } + + if (!StringUtils.equalsIgnoreCase(response.getString("status"), "OK")) { + 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(Settings.tag, "cgBase.getElevation: " + e.toString()); + } + + return null; + } + } diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java index 3e92b78..4d46970 100644 --- a/main/src/cgeo/geocaching/geopoint/Viewport.java +++ b/main/src/cgeo/geocaching/geopoint/Viewport.java @@ -50,10 +50,9 @@ public class Viewport { return "(" + bottomLeft.toString() + "," + topRight.toString() + ")"; } - // viewport is defined by center, span and some (10%) reserve on every side /** * Check if coordinates are located in a viewport (defined by its center and span - * in each direction). The viewport also includes a 10% extension on each side. + * in each direction). * * @param centerLat * the viewport center latitude @@ -68,39 +67,55 @@ public class Viewport { * @return true if the coordinates are in the viewport */ public static boolean isCacheInViewPort(int centerLat, int centerLon, int spanLat, int spanLon, final Geopoint coords) { - return Math.abs(coords.getLatitudeE6() - centerLat) <= Math.abs(spanLat) * 0.6 && - Math.abs(coords.getLongitudeE6() - centerLon) <= Math.abs(spanLon) * 0.6; + return 2 * Math.abs(coords.getLatitudeE6() - centerLat) <= Math.abs(spanLat) && + 2 * Math.abs(coords.getLongitudeE6() - centerLon) <= Math.abs(spanLon); } + /** + * Check if an area is located in a viewport (defined by its center and span + * in each direction). + * + * expects coordinates in E6 format + * + * @param centerLat1 + * @param centerLon1 + * @param centerLat2 + * @param centerLon2 + * @param spanLat1 + * @param spanLon1 + * @param spanLat2 + * @param spanLon2 + * @return + */ 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; } + + final int right1 = centerLat1 + (spanLat1 / 2); + final int right2 = centerLat2 + (spanLat2 / 2); if (right2 >= right1) { return false; } + + final int top1 = centerLon1 + (spanLon1 / 2); + final int top2 = centerLon2 + (spanLon2 / 2); if (top2 >= top1) { return false; } + + final int bottom1 = centerLon1 - (spanLon1 / 2); + final int bottom2 = centerLon2 - (spanLon2 / 2); if (bottom2 <= bottom1) { return false; } - + return true; } catch (Exception e) { - Log.e(Settings.tag, "cgBase.isInViewPort: " + e.toString()); + Log.e(Settings.tag, "Viewport.isInViewPort: " + e.toString()); return false; } } diff --git a/main/src/cgeo/geocaching/go4cache/Go4Cache.java b/main/src/cgeo/geocaching/go4cache/Go4Cache.java index 7243383..fb53c27 100644 --- a/main/src/cgeo/geocaching/go4cache/Go4Cache.java +++ b/main/src/cgeo/geocaching/go4cache/Go4Cache.java @@ -6,6 +6,7 @@ import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter.Format; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.utils.CryptUtils; @@ -99,7 +100,7 @@ public final class Go4Cache extends Thread { params.put("v", cgBase.version); } - cgBase.postRequest("http://api.go4cache.com/", params); + Network.postRequest("http://api.go4cache.com/", params); // Update our coordinates even if the request was not successful, as not to hammer the server // with invalid requests for every new GPS position. @@ -134,7 +135,7 @@ public final class Go4Cache extends Thread { "lnm", viewport.bottomLeft.format(Format.LON_DECDEGREE_RAW), "lnx", viewport.topRight.format(Format.LON_DECDEGREE_RAW)); - final String data = cgBase.getResponseData(cgBase.postRequest("http://api.go4cache.com/get.php", params)); + final String data = Network.getResponseData(Network.postRequest("http://api.go4cache.com/get.php", params)); if (StringUtils.isBlank(data)) { Log.e(Settings.tag, "cgeoBase.getGeocachersInViewport: No data from server"); diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 2ce4c42..8e451b6 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -34,7 +34,9 @@ import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; import cgeo.geocaching.maps.interfaces.OtherCachersOverlayItemImpl; +import cgeo.geocaching.network.Login; import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.LRUList; import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; @@ -75,6 +77,9 @@ import java.util.Set; */ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFactory { + /** max. number of caches displayed in the Live Map */ + public static final int MAX_CACHES = 500; + /** Handler Messages */ private static final int HIDE_PROGRESS = 0; private static final int SHOW_PROGRESS = 1; @@ -134,6 +139,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private Integer centerLongitudeUsers = null; private Integer spanLatitudeUsers = null; private Integer spanLongitudeUsers = null; + private int zoom = -100; // threads private LoadTimer loadTimer = null; private UsersTimer usersTimer = null; @@ -164,7 +170,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private static Map<Integer, LayerDrawable> overlaysCache = new HashMap<Integer, LayerDrawable>(); private int cachesCnt = 0; /** List of caches in the viewport */ - private Set<cgCache> caches = new HashSet<cgCache>(); + private final LRUList<cgCache> caches = new LRUList<cgCache>(MAX_CACHES); /** List of users in the viewport */ private List<Go4CacheUser> users = new ArrayList<Go4CacheUser>(); private List<cgCoord> coordinates = new ArrayList<cgCoord>(); @@ -200,11 +206,20 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto title.append(mapTitle); } + countVisibleCaches(); if (caches != null && caches.size() > 0 && !mapTitle.contains("[")) { - title.append(" ["); - title.append(caches.size()); + title.append(" [").append(cachesCnt); + if (cachesCnt != caches.size()) { + title.append('/').append(caches.size()); + } title.append(']'); } + // testing purpose + { + if (search != null && StringUtils.isNotBlank(search.getUrl())) { + title.append("[" + search.getUrl() + "]"); + } + } ActivityMixin.setTitle(activity, title.toString()); break; @@ -305,6 +320,28 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto super(activity); } + protected void countVisibleCaches() { + final ArrayList<cgCache> protectedCaches = new ArrayList<cgCache>(caches); + + int count = 0; + if (protectedCaches.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 cache : protectedCaches) { + if (cache != null && cache.getCoords() != null) { + if (Viewport.isCacheInViewPort(mapCenterLat, mapCenterLon, mapSpanLat, mapSpanLon, cache.getCoords())) { + count++; + } + } + } + } + cachesCnt = count; + } + @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -456,7 +493,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } dirtyCaches.clear(); // force an update of the display. Includes a call to DownloadThread :-( - liveChanged = true; + // liveChanged = true; } startTimer(); @@ -558,7 +595,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto Strategy strategy = Settings.getLiveMapStrategy(); SubMenu subMenuStrategy = menu.addSubMenu(0, SUBMENU_STRATEGY, 0, res.getString(R.string.map_strategy)); - subMenuStrategy.setHeaderTitle("Live Map strategy"); + subMenuStrategy.setHeaderTitle(res.getString(R.string.map_strategy_title)); subMenuStrategy.add(2, MENU_STRATEGY_FASTEST, 0, Strategy.FASTEST.getL10n()).setCheckable(true).setChecked(strategy == Strategy.FASTEST); subMenuStrategy.add(2, MENU_STRATEGY_FAST, 0, Strategy.FAST.getL10n()).setCheckable(true).setChecked(strategy == Strategy.FAST); subMenuStrategy.add(2, MENU_STRATEGY_AUTO, 0, Strategy.AUTO.getL10n()).setCheckable(true).setChecked(strategy == Strategy.AUTO); @@ -648,10 +685,10 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto final int mapSpanLat = mapView.getLatitudeSpan(); final int mapSpanLon = mapView.getLongitudeSpan(); - for (cgCache oneCache : cachesProtected) { - if (oneCache != null && oneCache.getCoords() != null) { - if (Viewport.isCacheInViewPort(mapCenterLat, mapCenterLon, mapSpanLat, mapSpanLon, oneCache.getCoords()) && !app.isOffline(oneCache.getGeocode(), null)) { - geocodes.add(oneCache.getGeocode()); + for (cgCache cache : cachesProtected) { + if (cache != null && cache.getCoords() != null) { + if (Viewport.isCacheInViewPort(mapCenterLat, mapCenterLon, mapSpanLat, mapSpanLon, cache.getCoords()) && !app.isOffline(cache.getGeocode(), null)) { + geocodes.add(cache.getGeocode()); } } } @@ -945,6 +982,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto int centerLongitudeNow; int spanLatitudeNow; int spanLongitudeNow; + int zoomNow; boolean moved = false; boolean force = false; long currentTime = 0; @@ -986,7 +1024,13 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto force = true; } - //LeeB + // update title on any change + zoomNow = mapView.getMapZoomLevel(); + if (moved || zoomNow != zoom || spanLatitudeNow != spanLatitude || spanLongitudeNow != spanLongitude || centerLatitudeNow != centerLatitude || centerLongitudeNow != centerLongitude) { + displayHandler.sendEmptyMessage(UPDATE_TITLE); + } + zoom = zoomNow; + // save new values if (moved) { liveChanged = false; @@ -1018,7 +1062,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto spanLatitude = spanLatitudeNow; spanLongitude = spanLongitudeNow; - showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); // show progress + showProgressHandler.sendEmptyMessage(SHOW_PROGRESS); loadThread = new LoadThread(centerLatitude, centerLongitude, spanLatitude, spanLongitude); loadThread.start(); //loadThread will kick off downloadThread once it's done @@ -1027,7 +1071,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } if (!isLoading()) { - showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress + showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); } yield(); @@ -1330,12 +1374,11 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } final Viewport viewport = new Viewport(new Geopoint(latMin, lonMin), new Geopoint(latMax, lonMax)); - // search = cgBase.searchByViewport(token, viewport); search = ConnectorFactory.searchByViewport(viewport, tokens); if (search != null) { downloaded = true; if (search.error == StatusCode.NOT_LOGGED_IN) { - cgBase.login(); + Login.login(); tokens = null; } else { break; @@ -1395,50 +1438,50 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } // display caches - final List<cgCache> cachesProtected = new ArrayList<cgCache>(caches); - final List<CachesOverlayItemImpl> items = new ArrayList<CachesOverlayItemImpl>(); + final List<cgCache> cachesToDisplay = new ArrayList<cgCache>(caches); + final List<CachesOverlayItemImpl> itemsToDisplay = new ArrayList<CachesOverlayItemImpl>(); - if (!cachesProtected.isEmpty()) { - for (cgCache cacheOne : cachesProtected) { + if (!cachesToDisplay.isEmpty()) { + for (cgCache cache : cachesToDisplay) { if (stop) { throw new ThreadDeath(); } - if (cacheOne.getCoords() == null) { + if (cache.getCoords() == null) { continue; } // display cache waypoints - if (cacheOne.hasWaypoints() + if (cache.hasWaypoints() // Only show waypoints for single view or setting // when less than showWaypointsthreshold Caches shown - && (cachesProtected.size() == 1 || (cachesProtected.size() < Settings.getWayPointsThreshold()))) { - for (cgWaypoint oneWaypoint : cacheOne.getWaypoints()) { - if (oneWaypoint.getCoords() == null) { + && (cachesToDisplay.size() == 1 || (cachesToDisplay.size() < Settings.getWayPointsThreshold()))) { + for (cgWaypoint waypoint : cache.getWaypoints()) { + if (waypoint.getCoords() == null) { continue; } - items.add(getItem(new cgCoord(oneWaypoint), null, oneWaypoint)); + itemsToDisplay.add(getItem(new cgCoord(waypoint), null, waypoint)); } } - items.add(getItem(new cgCoord(cacheOne), cacheOne, null)); + itemsToDisplay.add(getItem(new cgCoord(cache), cache, null)); } - overlayCaches.updateItems(items); + overlayCaches.updateItems(itemsToDisplay); displayHandler.sendEmptyMessage(INVALIDATE_MAP); - cachesCnt = cachesProtected.size(); + cachesCnt = cachesToDisplay.size(); if (stop) { throw new ThreadDeath(); } } else { - overlayCaches.updateItems(items); + overlayCaches.updateItems(itemsToDisplay); displayHandler.sendEmptyMessage(INVALIDATE_MAP); } - cachesProtected.clear(); + cachesToDisplay.clear(); displayHandler.sendEmptyMessage(UPDATE_TITLE); } catch (ThreadDeath e) { diff --git a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java index 766c941..8afba89 100644 --- a/main/src/cgeo/geocaching/maps/google/GoogleMapView.java +++ b/main/src/cgeo/geocaching/maps/google/GoogleMapView.java @@ -60,7 +60,7 @@ public class GoogleMapView extends MapView implements MapViewImpl { super.draw(canvas); } catch (Exception e) { - Log.e(Settings.tag, "cgMapView.draw: " + e.toString()); + Log.e(Settings.tag, "GoogleMapView.draw: " + e.toString()); } } @@ -74,7 +74,7 @@ public class GoogleMapView extends MapView implements MapViewImpl { super.displayZoomControls(takeFocus); } catch (Exception e) { - Log.e(Settings.tag, "cgMapView.displayZoomControls: " + e.toString()); + Log.e(Settings.tag, "GoogleMapView.displayZoomControls: " + e.toString()); } } diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index 9f010fb..d14ea8c 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -49,7 +49,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { super.draw(canvas); } catch (Exception e) { - Log.e(Settings.tag, "cgMapView.draw: " + e.toString()); + Log.e(Settings.tag, "MapsforgeMapView.draw: " + e.toString()); } } @@ -196,7 +196,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } } catch (Exception e) { - Log.e(Settings.tag, "mfMapView.repaintRequired: " + e.toString()); + Log.e(Settings.tag, "MapsforgeMapView.repaintRequired: " + e.toString()); } } } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index a643383..1811110 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -3,13 +3,11 @@ package cgeo.geocaching.network; import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.StoredList; -import cgeo.geocaching.cgBase; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.files.LocalStorage; import org.apache.commons.lang3.StringUtils; import org.apache.http.HttpResponse; -import org.apache.http.entity.BufferedHttpEntity; import android.content.Context; import android.graphics.Bitmap; @@ -84,21 +82,18 @@ public class HtmlImage implements Html.ImageGetter { // Download image and save it to the cache if (imagePre == null || onlySave) { final String absoluteURL = makeAbsoluteURL(url); - BufferedHttpEntity bufferedEntity = null; if (absoluteURL != null) { try { - final HttpResponse httpResponse = cgBase.request(absoluteURL, null, false); + final HttpResponse httpResponse = Network.request(absoluteURL, null, false); if (httpResponse != null) { - bufferedEntity = new BufferedHttpEntity(httpResponse.getEntity()); + final File file = LocalStorage.getStorageFile(geocode, url, true, true); + LocalStorage.saveEntityToFile(httpResponse, file); } } catch (Exception e) { Log.e(Settings.tag, "HtmlImage.getDrawable (downloading from web)", e); } } - - final File file = LocalStorage.getStorageFile(geocode, url, true, true); - LocalStorage.saveEntityToFile(bufferedEntity, file); } if (onlySave) { diff --git a/main/src/cgeo/geocaching/network/Login.java b/main/src/cgeo/geocaching/network/Login.java new file mode 100644 index 0000000..8db370d --- /dev/null +++ b/main/src/cgeo/geocaching/network/Login.java @@ -0,0 +1,389 @@ +package cgeo.geocaching.network; + +import cgeo.geocaching.R; +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgBase; +import cgeo.geocaching.connector.gc.GCConstants; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.utils.BaseUtils; + +import org.apache.commons.lang3.ArrayUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.http.HttpResponse; + +import android.content.Context; +import android.graphics.drawable.BitmapDrawable; +import android.util.Log; + +import java.text.ParseException; +import java.text.SimpleDateFormat; +import java.util.Collections; +import java.util.Date; +import java.util.HashMap; +import java.util.Locale; +import java.util.Map; +import java.util.regex.Matcher; + +public abstract class Login { + + private final static String ENGLISH = "English▼"; + + // false = not logged in + private static boolean actualLoginStatus = false; + private static String actualUserName = ""; + private static String actualMemberStatus = ""; + private static int actualCachesFound = -1; + private static String actualStatus = ""; + + private 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 static StatusCode login() { + final ImmutablePair<String, String> login = Settings.getLogin(); + + if (login == null || StringUtils.isEmpty(login.left) || StringUtils.isEmpty(login.right)) { + Login.setActualStatus(cgBase.res.getString(R.string.err_login)); + Log.e(Settings.tag, "cgeoBase.login: No login information stored"); + return StatusCode.NO_LOGIN_INFO_STORED; + } + + // res is null during the unit tests + if (cgBase.res != null) { + Login.setActualStatus(cgBase.res.getString(R.string.init_login_popup_working)); + } + HttpResponse loginResponse = Network.request("https://www.geocaching.com/login/default.aspx", null, false, false, false); + String loginData = Network.getResponseData(loginResponse); + if (loginResponse != null && loginResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(loginData, GCConstants.PATTERN_MAINTENANCE)) { + return StatusCode.MAINTENANCE; + } + + if (StringUtils.isBlank(loginData)) { + Log.e(Settings.tag, "cgeoBase.login: Failed to retrieve login page (1st)"); + return StatusCode.CONNECTION_FAILED; // no loginpage + } + + if (Login.getLoginStatus(loginData)) { + Log.i(Settings.tag, "Already logged in Geocaching.com as " + login.left); + Login.switchToEnglish(loginData); + return StatusCode.NO_ERROR; // logged in + } + + Network.clearCookies(); + Settings.setCookieStore(null); + + final Parameters params = new Parameters( + "__EVENTTARGET", "", + "__EVENTARGUMENT", "", + "ctl00$ContentBody$tbUsername", login.left, + "ctl00$ContentBody$tbPassword", login.right, + "ctl00$ContentBody$cbRememberMe", "on", + "ctl00$ContentBody$btnSignIn", "Login"); + final String[] viewstates = Login.getViewstates(loginData); + if (cgBase.isEmpty(viewstates)) { + Log.e(Settings.tag, "cgeoBase.login: Failed to find viewstates"); + return StatusCode.LOGIN_PARSE_ERROR; // no viewstates + } + Login.putViewstates(params, viewstates); + + loginResponse = Network.postRequest("https://www.geocaching.com/login/default.aspx", params); + loginData = Network.getResponseData(loginResponse); + + if (StringUtils.isNotBlank(loginData)) { + if (Login.getLoginStatus(loginData)) { + Log.i(Settings.tag, "Successfully logged in Geocaching.com as " + login.left); + + Login.switchToEnglish(loginData); + Settings.setCookieStore(Network.dumpCookieStore()); + + return StatusCode.NO_ERROR; // logged in + } else { + if (loginData.contains("Your username/password combination does not match.")) { + Log.i(Settings.tag, "Failed to log in Geocaching.com as " + login.left + " because of wrong username/password"); + return StatusCode.WRONG_LOGIN_DATA; // wrong login + } else { + Log.i(Settings.tag, "Failed to log in Geocaching.com as " + login.left + " for some unknown reason"); + return StatusCode.UNKNOWN_ERROR; // can't login + } + } + } else { + Log.e(Settings.tag, "cgeoBase.login: Failed to retrieve login page (2nd)"); + // FIXME: should it be CONNECTION_FAILED to match the first attempt? + return StatusCode.COMMUNICATION_ERROR; // no login page + } + } + + public static StatusCode logout() { + HttpResponse logoutResponse = Network.request("https://www.geocaching.com/login/default.aspx?RESET=Y&redir=http%3a%2f%2fwww.geocaching.com%2fdefault.aspx%3f", null, false, false, false); + String logoutData = Network.getResponseData(logoutResponse); + if (logoutResponse != null && logoutResponse.getStatusLine().getStatusCode() == 503 && BaseUtils.matches(logoutData, GCConstants.PATTERN_MAINTENANCE)) { + return StatusCode.MAINTENANCE; + } + + Network.clearCookies(); + Settings.setCookieStore(null); + return StatusCode.NO_ERROR; + } + + public static void setActualCachesFound(final int found) { + actualCachesFound = found; + } + + public static String getActualStatus() { + return actualStatus; + } + + public static void setActualStatus(final String status) { + actualStatus = status; + } + + public static boolean isActualLoginStatus() { + return actualLoginStatus; + } + + public static void setActualLoginStatus(boolean loginStatus) { + actualLoginStatus = loginStatus; + } + + public static String getActualUserName() { + return actualUserName; + } + + public static void setActualUserName(String userName) { + actualUserName = userName; + } + + public static String getActualMemberStatus() { + return actualMemberStatus; + } + + public static void setActualMemberStatus(final String memberStatus) { + actualMemberStatus = memberStatus; + } + + public static int getActualCachesFound() { + return actualCachesFound; + } + + /** + * Check if the user has been logged in when he retrieved the data. + * + * @param page + * @return <code>true</code> if user is logged in, <code>false</code> otherwise + */ + public static boolean getLoginStatus(final String page) { + if (StringUtils.isBlank(page)) { + Log.e(Settings.tag, "cgeoBase.checkLogin: No page given"); + return false; + } + + // res is null during the unit tests + if (cgBase.res != null) { + setActualStatus(cgBase.res.getString(R.string.init_login_popup_ok)); + } + + // on every page except login page + setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME)); + if (isActualLoginStatus()) { + setActualUserName(BaseUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); + setActualMemberStatus(BaseUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, "???")); + setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", ""))); + return true; + } + + // login page + setActualLoginStatus(BaseUtils.matches(page, GCConstants.PATTERN_LOGIN_NAME_LOGIN_PAGE)); + if (isActualLoginStatus()) { + setActualUserName(Settings.getUsername()); + setActualMemberStatus(Settings.getMemberStatus()); + // number of caches found is not part of this page + return true; + } + + // res is null during the unit tests + if (cgBase.res != null) { + setActualStatus(cgBase.res.getString(R.string.init_login_popup_failed)); + } + return false; + } + + private static void switchToEnglish(String previousPage) { + if (previousPage != null && previousPage.indexOf(ENGLISH) >= 0) { + Log.i(Settings.tag, "Geocaching.com language already set to English"); + // get find count + getLoginStatus(Network.getResponseData(Network.request("http://www.geocaching.com/email/", null, false))); + } else { + final String page = Network.getResponseData(Network.request("http://www.geocaching.com/default.aspx", null, false)); + getLoginStatus(page); + if (page == null) { + Log.e(Settings.tag, "Failed to read viewstates to set geocaching.com language"); + } + final Parameters params = new Parameters( + "__EVENTTARGET", "ctl00$uxLocaleList$uxLocaleList$ctl00$uxLocaleItem", // switch to english + "__EVENTARGUMENT", ""); + Login.transferViewstates(page, params); + final HttpResponse response = Network.postRequest("http://www.geocaching.com/default.aspx", params); + if (!Network.isSuccess(response)) { + Log.e(Settings.tag, "Failed to set geocaching.com language to English"); + } + } + } + + public static BitmapDrawable downloadAvatarAndGetMemberStatus(final Context context) { + try { + final String profile = BaseUtils.replaceWhitespace(Network.getResponseData(Network.request("http://www.geocaching.com/my/", null, false))); + + Settings.setMemberStatus(BaseUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + + setActualCachesFound(Integer.parseInt(BaseUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); + + final String avatarURL = BaseUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); + if (null != avatarURL) { + final HtmlImage imgGetter = new HtmlImage(context, "", false, 0, false); + return imgGetter.getDrawable(avatarURL); + } + // No match? There may be no avatar set by user. + Log.d(Settings.tag, "No avatar set for user"); + } catch (Exception e) { + Log.w(Settings.tag, "Error when retrieving user avatar", e); + } + return null; + } + + /** + * Detect user date settings on geocaching.com + */ + public static void detectGcCustomDate() { + + final String result = Network.getResponseData(Network.request("http://www.geocaching.com/account/ManagePreferences.aspx", null, false, false, false)); + + if (null == result) { + Log.w(Settings.tag, "cgeoBase.detectGcCustomDate: result is null"); + return; + } + + String customDate = BaseUtils.getMatch(result, GCConstants.PATTERN_CUSTOMDATE, true, null); + if (null != customDate) { + Settings.setGcCustomDate(customDate); + } + } + + public static Date parseGcCustomDate(final String input, final String format) throws ParseException { + if (StringUtils.isBlank(input)) { + throw new ParseException("Input is null", 0); + } + + final String trimmed = input.trim(); + + if (gcCustomDateFormats.containsKey(format)) { + try { + return gcCustomDateFormats.get(format).parse(trimmed); + } catch (ParseException e) { + } + } + + for (SimpleDateFormat sdf : gcCustomDateFormats.values()) { + try { + return sdf.parse(trimmed); + } catch (ParseException e) { + } + } + + throw new ParseException("No matching pattern", 0); + } + + public static Date parseGcCustomDate(final String input) throws ParseException { + return parseGcCustomDate(input, Settings.getGcCustomDate()); + } + + /** + * 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 + + if (page == null) { // no network access + return null; + } + + int count = 1; + final Matcher matcherViewstateCount = GCConstants.PATTERN_VIEWSTATEFIELDCOUNT.matcher(page); + if (matcherViewstateCount.find()) { + count = Integer.parseInt(matcherViewstateCount.group(1)); + } + + String[] viewstates = new String[count]; + + // Get the viewstates + int no; + final Matcher matcherViewstates = GCConstants.PATTERN_VIEWSTATES.matcher(page); + while (matcherViewstates.find()) { + String sno = matcherViewstates.group(1); // number of viewstate + if (sno.length() == 0) { + no = 0; + } + else { + no = Integer.parseInt(sno); + } + viewstates[no] = matcherViewstates.group(2); + } + + if (viewstates.length != 1 || viewstates[0] != null) { + return viewstates; + } + // no viewstates were present + return null; + } + + /** + * put viewstates into request parameters + */ + public static void putViewstates(final Parameters params, final String[] viewstates) { + 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", String.valueOf(viewstates.length)); + } + } + + /** + * transfers the viewstates variables from a page (response) to parameters + * (next request) + */ + public static void transferViewstates(final String page, final Parameters params) { + putViewstates(params, getViewstates(page)); + } + + static public String[] requestViewstates(final String uri, final Parameters params, boolean xContentType, boolean my) { + final HttpResponse response = Network.request(uri, params, xContentType, my, false); + + return getViewstates(Network.getResponseData(response)); + } + +} diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java new file mode 100644 index 0000000..0afe095 --- /dev/null +++ b/main/src/cgeo/geocaching/network/Network.java @@ -0,0 +1,364 @@ +package cgeo.geocaching.network; + +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgBase; +import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.utils.BaseUtils; + +import org.apache.commons.collections.CollectionUtils; +import org.apache.commons.lang3.StringUtils; +import org.apache.http.Header; +import org.apache.http.HeaderElement; +import org.apache.http.HttpEntity; +import org.apache.http.HttpException; +import org.apache.http.HttpRequest; +import org.apache.http.HttpRequestInterceptor; +import org.apache.http.HttpResponse; +import org.apache.http.HttpResponseInterceptor; +import org.apache.http.NameValuePair; +import org.apache.http.client.CookieStore; +import org.apache.http.client.HttpClient; +import org.apache.http.client.entity.UrlEncodedFormEntity; +import org.apache.http.client.methods.HttpGet; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.client.methods.HttpRequestBase; +import org.apache.http.cookie.Cookie; +import org.apache.http.entity.HttpEntityWrapper; +import org.apache.http.impl.client.BasicCookieStore; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.impl.cookie.BasicClientCookie; +import org.apache.http.params.BasicHttpParams; +import org.apache.http.params.CoreConnectionPNames; +import org.apache.http.params.CoreProtocolPNames; +import org.apache.http.params.HttpParams; +import org.apache.http.protocol.HTTP; +import org.apache.http.protocol.HttpContext; +import org.apache.http.util.EntityUtils; +import org.json.JSONException; +import org.json.JSONObject; + +import android.net.Uri; +import android.util.Log; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URLEncoder; +import java.util.List; +import java.util.zip.GZIPInputStream; + + +public abstract class Network { + + static class GzipDecompressingEntity extends HttpEntityWrapper { + public GzipDecompressingEntity(final HttpEntity entity) { + super(entity); + } + + @Override + public InputStream getContent() throws IOException, IllegalStateException { + // the wrapped entity's getContent() decides about repeatability + InputStream wrappedin = wrappedEntity.getContent(); + return new GZIPInputStream(wrappedin); + } + + @Override + public long getContentLength() { + // length of ungzipped content is not known + return -1; + } + } + + private static final int NB_DOWNLOAD_RETRIES = 4; + /** User agent id */ + private final static String USER_AGENT = "Mozilla/5.0 (X11; Linux x86_64; rv:9.0.1) Gecko/20100101 Firefox/9.0.1"; + private static final String PATTERN_PASSWORD = "(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+"; + + private final static HttpParams clientParams = new BasicHttpParams(); + private static boolean cookieStoreRestored = false; + private final static CookieStore cookieStore = new BasicCookieStore(); + + static { + Network.clientParams.setParameter(CoreProtocolPNames.HTTP_CONTENT_CHARSET, HTTP.UTF_8); + Network.clientParams.setParameter(CoreConnectionPNames.CONNECTION_TIMEOUT, 30000); + Network.clientParams.setParameter(CoreConnectionPNames.SO_TIMEOUT, 30000); + } + + private static String hidePassword(final String message) { + return message.replaceAll(Network.PATTERN_PASSWORD, "password=***"); + } + + private static HttpClient getHttpClient() { + final DefaultHttpClient client = new DefaultHttpClient(); + client.setCookieStore(cookieStore); + client.setParams(clientParams); + + client.addRequestInterceptor(new HttpRequestInterceptor() { + + @Override + public void process( + final HttpRequest request, + final HttpContext context) throws HttpException, IOException { + if (!request.containsHeader("Accept-Encoding")) { + request.addHeader("Accept-Encoding", "gzip"); + } + } + }); + client.addResponseInterceptor(new HttpResponseInterceptor() { + + public void process( + final HttpResponse response, + final HttpContext context) throws HttpException, IOException { + HttpEntity entity = response.getEntity(); + if (null != entity) { + Header ceheader = entity.getContentEncoding(); + if (ceheader != null) { + HeaderElement[] codecs = ceheader.getElements(); + for (int i = 0; i < codecs.length; i++) { + if (codecs[i].getName().equalsIgnoreCase("gzip")) { + Log.d(Settings.tag, "Decompressing response"); + response.setEntity( + new Network.GzipDecompressingEntity(response.getEntity())); + return; + } + } + } + } + } + + }); + + return client; + } + + public static void restoreCookieStore(final String oldCookies) { + if (!cookieStoreRestored) { + Network.clearCookies(); + if (oldCookies != null) { + for (final String cookie : StringUtils.split(oldCookies, ';')) { + final String[] split = StringUtils.split(cookie, "=", 3); + if (split.length == 3) { + final BasicClientCookie newCookie = new BasicClientCookie(split[0], split[1]); + newCookie.setDomain(split[2]); + cookieStore.addCookie(newCookie); + } + } + } + cookieStoreRestored = true; + } + } + + public static String dumpCookieStore() { + StringBuilder cookies = new StringBuilder(); + for (final Cookie cookie : cookieStore.getCookies()) { + cookies.append(cookie.getName()); + cookies.append('='); + cookies.append(cookie.getValue()); + cookies.append('='); + cookies.append(cookie.getDomain()); + cookies.append(';'); + } + return cookies.toString(); + } + + public static void clearCookies() { + cookieStore.clear(); + } + + /** + * POST HTTP request + * + * @param uri + * @param params + * @return + */ + public static HttpResponse postRequest(final String uri, final List<? extends NameValuePair> params) { + try { + HttpPost request = new HttpPost(uri); + if (params != null) { + request.setEntity(new UrlEncodedFormEntity(params, HTTP.UTF_8)); + } + request.setHeader("X-Requested-With", "XMLHttpRequest"); + return Network.request(request); + } catch (Exception e) { + // Can be UnsupportedEncodingException, ClientProtocolException or IOException + Log.e(Settings.tag, "postRequest", e); + return null; + } + } + + /** + * GET HTTP request + * + * @param uri + * @param params + * @param xContentType + * @param my + * @param addF + * @return + */ + public static HttpResponse request(final String uri, final Parameters params, boolean xContentType, boolean my, boolean addF) { + return Network.request(uri, cgBase.addFToParams(params, my, addF), xContentType); + } + + /** + * GET HTTP request + * + * @param uri + * @param params + * @param xContentType + * @return + */ + public static HttpResponse request(final String uri, final Parameters params, final boolean xContentType) { + final String fullUri = params == null ? uri : Uri.parse(uri).buildUpon().encodedQuery(params.toString()).build().toString(); + final HttpRequestBase request = new HttpGet(fullUri); + + request.setHeader("X-Requested-With", "XMLHttpRequest"); + + if (xContentType) { + request.setHeader("Content-Type", "application/x-www-form-urlencoded"); + } + + return Network.request(request); + } + + public static HttpResponse request(final HttpRequestBase request) { + request.setHeader("Accept-Charset", "utf-8,iso-8859-1;q=0.8,utf-16;q=0.8,*;q=0.7"); + request.setHeader("Accept-Language", "en-US,*;q=0.9"); + request.getParams().setParameter(CoreProtocolPNames.USER_AGENT, USER_AGENT); + return Network.doRequest(request); + } + + private static HttpResponse doRequest(final HttpRequestBase request) { + final String reqLogStr = request.getMethod() + " " + hidePassword(request.getURI().toString()); + Log.d(Settings.tag, reqLogStr); + + final HttpClient client = getHttpClient(); + for (int i = 0; i <= NB_DOWNLOAD_RETRIES; i++) { + final long before = System.currentTimeMillis(); + try { + final HttpResponse response = client.execute(request); + int status = response.getStatusLine().getStatusCode(); + if (status == 200) { + Log.d(Settings.tag, status + Network.formatTimeSpan(before) + reqLogStr); + } else { + Log.w(Settings.tag, status + " [" + response.getStatusLine().getReasonPhrase() + "]" + Network.formatTimeSpan(before) + reqLogStr); + } + return response; + } catch (IOException e) { + final String timeSpan = Network.formatTimeSpan(before); + final String tries = (i + 1) + "/" + (NB_DOWNLOAD_RETRIES + 1); + if (i == NB_DOWNLOAD_RETRIES) { + Log.e(Settings.tag, "Failure " + tries + timeSpan + reqLogStr, e); + } else { + Log.e(Settings.tag, "Failure " + tries + " (" + e.toString() + ")" + timeSpan + "- retrying " + reqLogStr); + } + } + } + + return null; + } + + private static String formatTimeSpan(final long before) { + // don't use String.format in a pure logging routine, it has very bad performance + return " (" + (System.currentTimeMillis() - before) + " ms) "; + } + + static public boolean isSuccess(final HttpResponse response) { + return response != null && response.getStatusLine().getStatusCode() == 200; + } + + public static JSONObject requestJSON(final String uri, final Parameters params) { + final HttpGet request = new HttpGet(Network.prepareParameters(uri, params)); + request.setHeader("Accept", "application/json, text/javascript, */*; q=0.01"); + request.setHeader("Content-Type", "application/json; charset=UTF-8"); + request.setHeader("X-Requested-With", "XMLHttpRequest"); + + final HttpResponse response = doRequest(request); + if (response != null && response.getStatusLine().getStatusCode() == 200) { + try { + return new JSONObject(Network.getResponseData(response)); + } catch (JSONException e) { + Log.e(Settings.tag, "Network.requestJSON", e); + } + } + + return null; + } + + private static String prepareParameters(final String baseUri, final Parameters params) { + return CollectionUtils.isNotEmpty(params) ? baseUri + "?" + params.toString() : baseUri; + } + + private static String getResponseDataNoError(final HttpResponse response, boolean replaceWhitespace) { + try { + String data = EntityUtils.toString(response.getEntity(), HTTP.UTF_8); + return replaceWhitespace ? BaseUtils.replaceWhitespace(data) : data; + } catch (Exception e) { + Log.e(Settings.tag, "getResponseData", e); + return null; + } + } + + public static String getResponseData(final HttpResponse response) { + return Network.getResponseData(response, true); + } + + static public String getResponseData(final HttpResponse response, boolean replaceWhitespace) { + if (!isSuccess(response)) { + return null; + } + return getResponseDataNoError(response, replaceWhitespace); + } + + /** + * POST HTTP request. Do the request a second time if the user is not logged in + * + * @param uri + * @return + */ + public static String postRequestLogged(final String uri) { + HttpResponse response = postRequest(uri, null); + String data = getResponseData(response); + + if (!Login.getLoginStatus(data)) { + if (Login.login() == StatusCode.NO_ERROR) { + response = postRequest(uri, null); + data = getResponseData(response); + } else { + Log.i(Settings.tag, "Working as guest."); + } + } + return data; + } + + /** + * GET HTTP request. Do the request a second time if the user is not logged in + * + * @param uri + * @param params + * @param xContentType + * @param my + * @param addF + * @return + */ + public static String requestLogged(final String uri, final Parameters params, boolean xContentType, boolean my, boolean addF) { + HttpResponse response = request(uri, params, xContentType, my, addF); + String data = getResponseData(response); + + if (!Login.getLoginStatus(data)) { + if (Login.login() == StatusCode.NO_ERROR) { + response = request(uri, params, xContentType, my, addF); + data = getResponseData(response); + } else { + Log.i(Settings.tag, "Working as guest."); + } + } + return data; + } + + public static String urlencode_rfc3986(String text) { + final String encoded = StringUtils.replace(URLEncoder.encode(text).replace("+", "%20"), "%7E", "~"); + + return encoded; + } +} diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java index a5393f6..cefa90d 100644 --- a/main/src/cgeo/geocaching/network/OAuth.java +++ b/main/src/cgeo/geocaching/network/OAuth.java @@ -1,7 +1,6 @@ package cgeo.geocaching.network; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; import cgeo.geocaching.utils.CryptUtils; import org.apache.commons.lang3.StringUtils; @@ -24,11 +23,11 @@ public class OAuth { final List<String> paramsEncoded = new ArrayList<String>(); for (final NameValuePair nameValue : params) { - paramsEncoded.add(nameValue.getName() + "=" + cgBase.urlencode_rfc3986(nameValue.getValue())); + paramsEncoded.add(nameValue.getName() + "=" + Network.urlencode_rfc3986(nameValue.getValue())); } final String keysPacked = Settings.getKeyConsumerSecret() + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them! - final String requestPacked = method + "&" + cgBase.urlencode_rfc3986((https ? "https" : "http") + "://" + host + path) + "&" + cgBase.urlencode_rfc3986(StringUtils.join(paramsEncoded.toArray(), '&')); + final String requestPacked = method + "&" + Network.urlencode_rfc3986((https ? "https" : "http") + "://" + host + path) + "&" + Network.urlencode_rfc3986(StringUtils.join(paramsEncoded.toArray(), '&')); params.put("oauth_signature", CryptUtils.base64Encode(CryptUtils.hashHmac(requestPacked, keysPacked))); } } diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index a47409f..fa1a39d 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -1,10 +1,13 @@ package cgeo.geocaching.twitter; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.cgTrackable; import cgeo.geocaching.cgeoapplication; +import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter.Format; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.OAuth; import cgeo.geocaching.network.Parameters; @@ -33,7 +36,7 @@ public final class Twitter { } OAuth.signOAuth("api.twitter.com", "/1/statuses/update.json", "POST", false, parameters, Settings.getTokenPublic(), Settings.getTokenSecret()); - final HttpResponse httpResponse = cgBase.postRequest("http://api.twitter.com/1/statuses/update.json", parameters); + final HttpResponse httpResponse = Network.postRequest("http://api.twitter.com/1/statuses/update.json", parameters); if (httpResponse != null && httpResponse.getStatusLine().getStatusCode() == 200) { Log.i(Settings.tag, "Tweet posted"); } else { @@ -51,4 +54,42 @@ public final class Twitter { } return result; } + + public static void postTweetCache(String geocode) { + final cgCache cache = cgeoapplication.getInstance().loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + String status; + final String url = cache.getUrl(); + if (url.length() >= 100) { + status = "I found " + url; + } + else { + String name = cache.getName(); + status = "I found " + name + " (" + url + ")"; + if (status.length() > MAX_TWEET_SIZE) { + name = name.substring(0, name.length() - (status.length() - MAX_TWEET_SIZE) - 3) + "..."; + } + status = "I found " + name + " (" + url + ")"; + status = appendHashTag(status, "cgeo"); + status = appendHashTag(status, "geocaching"); + } + + postTweet(cgeoapplication.getInstance(), status, null); + } + + public static void postTweetTrackable(String geocode) { + final cgTrackable trackable = cgeoapplication.getInstance().getTrackableByGeocode(geocode); + String name = trackable.getName(); + if (name.length() > 82) { + name = name.substring(0, 79) + "..."; + } + StringBuilder builder = new StringBuilder("I touched "); + builder.append(name); + if (trackable.getUrl() != null) { + builder.append(" (").append(trackable.getUrl()).append(')'); + } + builder.append('!'); + String status = appendHashTag(builder.toString(), "cgeo"); + status = appendHashTag(status, "geocaching"); + postTweet(cgeoapplication.getInstance(), status, null); + } } diff --git a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java index 83b1569..fffe12e 100644 --- a/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/twitter/TwitterAuthorizationActivity.java @@ -2,8 +2,8 @@ package cgeo.geocaching.twitter; import cgeo.geocaching.R; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; import cgeo.geocaching.activity.AbstractActivity; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.OAuth; import cgeo.geocaching.network.Parameters; @@ -141,7 +141,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { try { final Parameters params = new Parameters(); OAuth.signOAuth(host, pathRequest, method, true, params, null, null); - final String line = cgBase.getResponseData(cgBase.request("https://" + host + pathRequest, params, false)); + final String line = Network.getResponseData(Network.request("https://" + host + pathRequest, params, false)); if (StringUtils.isNotBlank(line)) { @@ -186,7 +186,7 @@ public class TwitterAuthorizationActivity extends AbstractActivity { final Parameters params = new Parameters("oauth_verifier", pinEntry.getText().toString()); OAuth.signOAuth(host, path, method, true, params, OAtoken, OAtokenSecret); - final String line = StringUtils.defaultString(cgBase.getResponseData(cgBase.postRequest("https://" + host + path, params))); + final String line = StringUtils.defaultString(Network.getResponseData(Network.postRequest("https://" + host + path, params))); OAtoken = ""; OAtokenSecret = ""; diff --git a/main/src/cgeo/geocaching/ui/DirectionImage.java b/main/src/cgeo/geocaching/ui/DirectionImage.java index c559531..aa01b97 100644 --- a/main/src/cgeo/geocaching/ui/DirectionImage.java +++ b/main/src/cgeo/geocaching/ui/DirectionImage.java @@ -1,7 +1,7 @@ package cgeo.geocaching.ui; -import cgeo.geocaching.cgBase; import cgeo.geocaching.files.LocalStorage; +import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import org.apache.commons.lang3.StringUtils; @@ -17,9 +17,9 @@ public class DirectionImage { } final HttpResponse httpResponse = - cgBase.request("http://www.geocaching.com/ImgGen/seek/CacheDir.ashx", new Parameters("k", code), false); + Network.request("http://www.geocaching.com/ImgGen/seek/CacheDir.ashx", new Parameters("k", code), false); if (httpResponse != null) { - LocalStorage.saveEntityToFile(httpResponse.getEntity(), getDirectionFile(geocode, true)); + LocalStorage.saveEntityToFile(httpResponse, getDirectionFile(geocode, true)); } } diff --git a/main/src/cgeo/geocaching/utils/LRUList.java b/main/src/cgeo/geocaching/utils/LRUList.java new file mode 100644 index 0000000..13596b1 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/LRUList.java @@ -0,0 +1,46 @@ +package cgeo.geocaching.utils; + +import java.util.ArrayList; +import java.util.Collection; + +/** + * Base class for a limited list. + * + * @author blafoo + */ +public class LRUList<T> extends ArrayList<T> { + + private static final long serialVersionUID = -5077882607489806620L; + private final int maxEntries; + + public LRUList(int maxEntries) { + this.maxEntries = maxEntries; + } + + private void removeElements(int count) { + for (int i = 0; i < count; i++) { + this.remove(0); + } + } + + @Override + public boolean add(T item) { + removeElements(this.size() + 1 - maxEntries); + return super.add(item); + } + + @Override + public boolean addAll(Collection<? extends T> collection) { + if (collection.size() > this.size()) { + this.clear(); + for (T item : collection) { + this.add(item); + } + return false; + } else { + removeElements(this.size() + collection.size() - maxEntries); + return super.addAll(collection); + } + } + +} diff --git a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java index 522bbf4..da7fb86 100644 --- a/main/src/cgeo/geocaching/utils/LogTemplateProvider.java +++ b/main/src/cgeo/geocaching/utils/LogTemplateProvider.java @@ -4,6 +4,8 @@ import cgeo.geocaching.R; import cgeo.geocaching.Settings; import cgeo.geocaching.cgBase; import cgeo.geocaching.connector.gc.GCConstants; +import cgeo.geocaching.network.Login; +import cgeo.geocaching.network.Network; import org.apache.commons.lang3.StringUtils; @@ -81,12 +83,12 @@ public class LogTemplateProvider { @Override public String getValue(final boolean offline) { - int current = cgBase.getActualCachesFound(); + int current = Login.getActualCachesFound(); if (current == 0) { if (offline) { return ""; } - final String page = cgBase.getResponseData(cgBase.request("http://www.geocaching.com/email/", null, false, false, false)); + final String page = Network.getResponseData(Network.request("http://www.geocaching.com/email/", null, false, false, false)); current = parseFindCount(page); } diff --git a/tests/res/raw/tile12.png b/tests/res/raw/tile12.png Binary files differnew file mode 100644 index 0000000..f8d9cb3 --- /dev/null +++ b/tests/res/raw/tile12.png diff --git a/tests/src/cgeo/geocaching/cgBaseTest.java b/tests/src/cgeo/geocaching/cgBaseTest.java index e6aa862..c89e41c 100644 --- a/tests/src/cgeo/geocaching/cgBaseTest.java +++ b/tests/src/cgeo/geocaching/cgBaseTest.java @@ -31,7 +31,7 @@ public class cgBaseTest extends AndroidTestCase { } public static void testElevation() { - assertEquals(125.663703918457, cgBase.getElevation(new Geopoint(48.0, 2.0)), 0.1); + assertEquals(125.663703918457, (new Geopoint(48.0, 2.0)).getElevation(), 0.1); } public static void testCompareCaches(ICache expected, cgCache actual) { diff --git a/tests/src/cgeo/geocaching/cgeoApplicationTest.java b/tests/src/cgeo/geocaching/cgeoApplicationTest.java index e28cb80..0bdd3f2 100644 --- a/tests/src/cgeo/geocaching/cgeoApplicationTest.java +++ b/tests/src/cgeo/geocaching/cgeoApplicationTest.java @@ -8,6 +8,7 @@ import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.network.Login; import cgeo.geocaching.test.RegExPerformanceTest; import cgeo.geocaching.test.mock.GC1ZXX2; import cgeo.geocaching.test.mock.GC2CJPF; @@ -55,7 +56,7 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { @SuppressWarnings("static-method") @SmallTest public void testPreconditions() { - assertEquals(StatusCode.NO_ERROR, cgBase.login()); + assertEquals(StatusCode.NO_ERROR, Login.login()); } /** @@ -130,7 +131,7 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { try { // non premium cache - MockedCache cache = new GC1ZXX2(); + MockedCache cache = new GC2CJPF(); deleteCacheFromDBAndLogout(cache.getGeocode()); @@ -155,7 +156,33 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { // restore user and password Settings.setLogin(login.left, login.right); Settings.setMemberStatus(memberStatus); - cgBase.login(); + Login.login(); + } + } + + /** + * Test {@link cgBase#searchByGeocode(String, String, int, boolean, CancellableHandler)} + */ + @MediumTest + public static void testSearchErrorOccured() { + ImmutablePair<String, String> login = Settings.getLogin(); + String memberStatus = Settings.getMemberStatus(); + + try { + // non premium cache + MockedCache cache = new GC1ZXX2(); + + deleteCacheFromDBAndLogout(cache.getGeocode()); + + SearchResult search = cgBase.searchByGeocode(cache.getGeocode(), null, StoredList.TEMPORARY_LIST_ID, true, null); + assertNotNull(search); + assertEquals(0, search.getGeocodes().size()); + + } finally { + // restore user and password + Settings.setLogin(login.left, login.right); + Settings.setMemberStatus(memberStatus); + Login.login(); } } @@ -237,7 +264,8 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { ImmutablePair<String, String> login = Settings.getLogin(); String memberStatus = Settings.getMemberStatus(); Strategy strategy = Settings.getLiveMapStrategy(); - Settings.setLiveMapStrategy(Strategy.DETAILED); + Strategy testStrategy = Strategy.FAST; // FASTEST, FAST or DETAILED for tests + Settings.setLiveMapStrategy(testStrategy); try { @@ -257,7 +285,8 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { Log.d(Settings.tag, "cgeoApplicationTest.testSearchByViewportNotLoggedIn: Coords expected = " + cache.getCoords()); Log.d(Settings.tag, "cgeoApplicationTest.testSearchByViewportNotLoggedIn: Coords actual = " + cacheFromViewport.getCoords()); assertFalse(cache.getCoords().isEqualTo(cacheFromViewport.getCoords(), 1e-3)); - // issue #1242 assertFalse(cacheFromViewport.isReliableLatLon()); + // depending on the chosen strategy the coords can be reliable or not + assertEquals(testStrategy == Strategy.DETAILED, cacheFromViewport.isReliableLatLon()); // premium cache cache = new GC2JVEH(); @@ -267,14 +296,14 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { search = ConnectorFactory.searchByViewport(viewport, tokens); assertNotNull(search); - // It's a premium member cache only and thus not visible to guests (old Live Map !). Visible in new Live Map - assertTrue(search.getGeocodes().contains(cache.getGeocode())); + // depending on the chosen strategy the cache is part of the search or not + assertEquals(testStrategy == Strategy.DETAILED, search.getGeocodes().contains(cache.getGeocode())); } finally { // restore user and password Settings.setLogin(login.left, login.right); Settings.setMemberStatus(memberStatus); - cgBase.login(); + Login.login(); Settings.setLiveMapStrategy(strategy); } } @@ -307,7 +336,7 @@ public class cgeoApplicationTest extends ApplicationTestCase<cgeoapplication> { private static void deleteCacheFromDBAndLogout(String geocode) { cgeoapplication.getInstance().removeCache(geocode, LoadFlags.REMOVE_ALL); - cgBase.logout(); + Login.logout(); // Modify login data to avoid an automatic login again Settings.setLogin("c:geo", "c:geo"); Settings.setMemberStatus("Basic member"); diff --git a/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java b/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java index 5f07f60..32ab1f2 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCBaseTest.java @@ -1,14 +1,18 @@ package cgeo.geocaching.connector.gc; -import java.util.Arrays; - import junit.framework.TestCase; public class GCBaseTest extends TestCase { public static void testSplitJSONKey() { - assertTrue(Arrays.equals(new int[] { 1, 2 }, GCBase.splitJSONKey("(1, 2)"))); - assertTrue(Arrays.equals(new int[] { 12, 34 }, GCBase.splitJSONKey("(12, 34)"))); - assertTrue(Arrays.equals(new int[] { 1234, 56 }, GCBase.splitJSONKey("(1234,56)"))); - assertTrue(Arrays.equals(new int[] { 1234, 567 }, GCBase.splitJSONKey("(1234, 567)"))); + assertKey("(1, 2)", 1, 2); + assertKey("(12, 34)", 12, 34); + assertKey("(1234,56)", 1234, 56); + assertKey("(1234, 567)", 1234, 567); + } + + private static void assertKey(String key, int x, int y) { + UTFGridPosition pos = UTFGridPosition.fromString(key); + assertEquals(x, pos.getX()); + assertEquals(y, pos.getY()); } } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java index 7f63ef3..dfa9a8b 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCConnectorTest.java @@ -1,24 +1,16 @@ package cgeo.geocaching.connector.gc; import cgeo.geocaching.SearchResult; -import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; -import cgeo.geocaching.cgCache; import cgeo.geocaching.connector.ConnectorFactory; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Viewport; +import cgeo.geocaching.network.Login; import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; -import cgeo.geocaching.test.R; - -import android.graphics.Bitmap; -import android.graphics.BitmapFactory; -import android.util.Log; public class GCConnectorTest extends AbstractResourceInstrumentationTestCase { public static void testGetViewport() { - cgBase.login(); + Login.login(); String[] tokens = GCBase.getTokens(); @@ -50,78 +42,25 @@ public class GCConnectorTest extends AbstractResourceInstrumentationTestCase { /** Tile computation with different zoom levels */ public static void testTile() { - { - // http://coord.info/GC2CT8K = N 52° 30.462 E 013° 27.906 - Tile tile = new Tile(new Geopoint(52.5077, 13.4651), 14); - assertEquals(8804, tile.getX()); - assertEquals(5374, tile.getY()); - } - { - // (8633, 5381); N 52° 24,516 E 009° 42,592 - Tile tile = new Tile(new Geopoint("N 52° 24,516 E 009° 42,592"), 14); - assertEquals(8633, tile.getX()); - assertEquals(5381, tile.getY()); - } - { - // Hannover, GC22VTB UKM Memorial Tour - Tile tile = new Tile(new Geopoint("N 52° 22.177 E 009° 45.385"), 12); - assertEquals(2159, tile.getX()); - assertEquals(1346, tile.getY()); - } - { - // Seatle, GCK25B Groundspeak Headquarters - Tile tile = new Tile(new Geopoint("N 47° 38.000 W 122° 20.000"), 15); - assertEquals(5248, tile.getX()); - assertEquals(11440, tile.getY()); - } - { - // Sydney, GCXT2R Victoria Cross - Tile tile = new Tile(new Geopoint("S 33° 50.326 E 151° 12.426"), 13); - assertEquals(7536, tile.getX()); - assertEquals(4915, tile.getY()); - } - } + // http://coord.info/GC2CT8K = N 52° 30.462 E 013° 27.906 + assertTileAt(8804, 5374, new Tile(new Geopoint(52.5077, 13.4651), 14)); - public void testparseMapPNG() { - // createApplication(); - // cgBase.initialize(getApplication()); + // (8633, 5381); N 52° 24,516 E 009° 42,592 + assertTileAt(8633, 5381, new Tile(new Geopoint("N 52° 24,516 E 009° 42,592"), 14)); - BitmapFactory.Options options = new BitmapFactory.Options(); - options.inScaled = false; - Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(R.raw.tile14)); - assert bitmap.getWidth() == Tile.TILE_SIZE : "Wrong size"; + // Hannover, GC22VTB UKM Memorial Tour + assertTileAt(2159, 1346, new Tile(new Geopoint("N 52° 22.177 E 009° 45.385"), 12)); - Log.d(Settings.tag, "Bitmap=" + bitmap.getWidth() + "x" + bitmap.getHeight()); + // Seattle, GCK25B Groundspeak Headquarters + assertTileAt(5248, 11440, new Tile(new Geopoint("N 47° 38.000 W 122° 20.000"), 15)); - cgCache cache = new cgCache(); - - // Tradi - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(97 / 4, 136 / 4)); - assertEquals(CacheType.TRADITIONAL, cache.getType()); - // Mystery - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(226 / 4, 104 / 4)); - assertEquals(CacheType.MYSTERY, cache.getType()); - // Multi - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(54 / 4, 97 / 4)); - assertEquals(CacheType.MULTI, cache.getType()); - // Found - GCBase.parseMapPNG14(cache, bitmap, new UTFGridPosition(119 / 4, 108 / 4)); - assertTrue(cache.isFound()); - cache.setFound(false); // reset - - bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(R.raw.tile13)); - - // Tradi - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(146 / 4, 225 / 4)); - assertEquals(CacheType.TRADITIONAL, cache.getType()); - // Mystery - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(181 / 4, 116 / 4)); - assertEquals(CacheType.MYSTERY, cache.getType()); - // Multi - GCBase.parseMapPNG13(cache, bitmap, new UTFGridPosition(118 / 4, 230 / 4)); - assertEquals(CacheType.MULTI, cache.getType()); - // Found - not available in parseMapPNG13 + // Sydney, GCXT2R Victoria Cross + assertTileAt(7536, 4915, new Tile(new Geopoint("S 33° 50.326 E 151° 12.426"), 13)); } + private static void assertTileAt(int x, int y, final Tile tile) { + assertEquals(x, tile.getX()); + assertEquals(y, tile.getY()); + } } diff --git a/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java b/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java new file mode 100644 index 0000000..74704b3 --- /dev/null +++ b/tests/src/cgeo/geocaching/connector/gc/IconDecoderTest.java @@ -0,0 +1,82 @@ +package cgeo.geocaching.connector.gc; + +import cgeo.geocaching.Settings; +import cgeo.geocaching.cgCache; +import cgeo.geocaching.enumerations.CacheType; +import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; +import cgeo.geocaching.test.R; + +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.util.Log; + +public class IconDecoderTest extends AbstractResourceInstrumentationTestCase { + + public void testparseMapPNG14() { + final Bitmap bitmap = getBitmap(R.raw.tile14); + Log.d(Settings.tag, "Bitmap=" + bitmap.getWidth() + "x" + bitmap.getHeight()); + + assertEquals(CacheType.TRADITIONAL, parseMapPNG(bitmap, 97, 136, 14).getType()); + assertEquals(CacheType.MYSTERY, parseMapPNG(bitmap, 226, 104, 14).getType()); + assertEquals(CacheType.MULTI, parseMapPNG(bitmap, 54, 97, 14).getType()); + assertTrue(parseMapPNG(bitmap, 119, 108, 14).isFound()); + } + + private Bitmap getBitmap(int resourceId) { + final BitmapFactory.Options options = new BitmapFactory.Options(); + options.inScaled = false; + final Bitmap bitmap = BitmapFactory.decodeStream(getInstrumentation().getContext().getResources().openRawResource(resourceId)); + assert bitmap.getWidth() == Tile.TILE_SIZE : "Wrong size"; + return bitmap; + } + + private static cgCache parseMapPNG(Bitmap bitmap, int x, int y, int zoomlevel) { + final cgCache cache = new cgCache(); + IconDecoder.parseMapPNG(cache, bitmap, new UTFGridPosition(x / 4, y / 4), zoomlevel); + return cache; + } + + public void testParseMap13() { + final Bitmap bitmap = getBitmap(R.raw.tile13); + + assertEquals(CacheType.TRADITIONAL, parseMapPNG(bitmap, 146, 225, 13).getType()); + assertEquals(CacheType.MYSTERY, parseMapPNG(bitmap, 181, 116, 13).getType()); + assertEquals(CacheType.MULTI, parseMapPNG(bitmap, 118, 230, 13).getType()); + } + + public void testParseMap12() { + final Bitmap bitmap = getBitmap(R.raw.tile12); + + int multi = 0; + multi = parseMapPNG(bitmap, 130, 92, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 93, 222, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 129, 227, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 234, 170, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 195, 113, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 195, 124, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + multi = parseMapPNG(bitmap, 111, 74, 12).getType() == CacheType.MULTI ? multi + 1 : multi; + + int mystery = 0; + mystery = parseMapPNG(bitmap, 37, 25, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 49, 183, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 183, 181, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 176, 94, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 161, 124, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 168, 118, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + mystery = parseMapPNG(bitmap, 231, 114, 12).getType() == CacheType.MYSTERY ? mystery + 1 : mystery; + + int tradi = 0; + tradi = parseMapPNG(bitmap, 179, 27, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 106, 93, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 145, 147, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 204, 163, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 9, 146, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 117, 225, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + tradi = parseMapPNG(bitmap, 90, 107, 12).getType() == CacheType.TRADITIONAL ? tradi + 1 : tradi; + + assertEquals(7, multi); + assertEquals(7, mystery); + assertEquals(7, tradi); + + } +} diff --git a/tests/src/cgeo/geocaching/test/mock/GC1ZXX2.java b/tests/src/cgeo/geocaching/test/mock/GC1ZXX2.java index 0f6fe79..bf4d0cf 100644 --- a/tests/src/cgeo/geocaching/test/mock/GC1ZXX2.java +++ b/tests/src/cgeo/geocaching/test/mock/GC1ZXX2.java @@ -1,10 +1,10 @@ package cgeo.geocaching.test.mock; -import cgeo.geocaching.cgBase; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.network.Login; import java.text.ParseException; import java.util.Arrays; @@ -96,7 +96,7 @@ public class GC1ZXX2 extends MockedCache { @Override public Date getHiddenDate() { try { - return cgBase.parseGcCustomDate("16/10/2009", getDateFormat()); + return Login.parseGcCustomDate("16/10/2009", getDateFormat()); } catch (ParseException e) { // intentionally left blank } diff --git a/tests/src/cgeo/geocaching/test/mock/GC2CJPF.java b/tests/src/cgeo/geocaching/test/mock/GC2CJPF.java index f78527f..b97d2bc 100644 --- a/tests/src/cgeo/geocaching/test/mock/GC2CJPF.java +++ b/tests/src/cgeo/geocaching/test/mock/GC2CJPF.java @@ -1,11 +1,11 @@ package cgeo.geocaching.test.mock; import cgeo.geocaching.Settings; -import cgeo.geocaching.cgBase; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.network.Login; import java.text.ParseException; import java.util.Arrays; @@ -122,7 +122,7 @@ public class GC2CJPF extends MockedCache { @Override public Date getHiddenDate() { try { - return cgBase.parseGcCustomDate("31/07/2010", getDateFormat()); + return Login.parseGcCustomDate("31/07/2010", getDateFormat()); } catch (ParseException e) { // intentionally left blank } diff --git a/tests/src/cgeo/geocaching/test/mock/GC2JVEH.java b/tests/src/cgeo/geocaching/test/mock/GC2JVEH.java index fe860a1..6fdd753 100644 --- a/tests/src/cgeo/geocaching/test/mock/GC2JVEH.java +++ b/tests/src/cgeo/geocaching/test/mock/GC2JVEH.java @@ -1,12 +1,12 @@ package cgeo.geocaching.test.mock; -import cgeo.geocaching.cgBase; import cgeo.geocaching.cgImage; import cgeo.geocaching.cgTrackable; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.network.Login; import java.text.ParseException; import java.util.ArrayList; @@ -90,7 +90,7 @@ public class GC2JVEH extends MockedCache { @Override public Date getHiddenDate() { try { - return cgBase.parseGcCustomDate("28/11/2010", getDateFormat()); + return Login.parseGcCustomDate("28/11/2010", getDateFormat()); } catch (ParseException e) { // intentionally left blank } |
