diff options
48 files changed, 1082 insertions, 911 deletions
diff --git a/main/res/layout/fragment_edit_note.xml b/main/res/layout/fragment_edit_note.xml new file mode 100644 index 0000000..68e2b2c --- /dev/null +++ b/main/res/layout/fragment_edit_note.xml @@ -0,0 +1,15 @@ +<LinearLayout + xmlns:android="http://schemas.android.com/apk/res/android" + android:id="@+id/edit_note" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:layout_gravity="center" + android:orientation="vertical"> + + <EditText + android:id="@+id/note" + android:layout_width="fill_parent" + android:layout_height="wrap_content" + android:inputType="text" + android:imeOptions="actionDone"/> +</LinearLayout> diff --git a/main/res/layout/spoilers.xml b/main/res/layout/spoilers.xml index 5513934..233972a 100644 --- a/main/res/layout/spoilers.xml +++ b/main/res/layout/spoilers.xml @@ -2,6 +2,7 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="fill_parent" android:layout_height="fill_parent" + android:background="?background_color" android:orientation="vertical" > <LinearLayout style="@style/action_bar" > diff --git a/main/res/values/strings_not_translatable.xml b/main/res/values/strings_not_translatable.xml index b082e39..8de1483 100644 --- a/main/res/values/strings_not_translatable.xml +++ b/main/res/values/strings_not_translatable.xml @@ -82,36 +82,15 @@ <string name="changelog" translatable="false">\n
<b>Next release</b>\n\n
<b>New Features/Functions:</b>\n
- · Support of attaching pictures to logs\n
- · Support of opencaching.de online API in live map and nearby search\n
- · Support for searching opencaching.nl caches\n
- · Alphabetical sorting of OSM:Offline maps in map selection\n
- · Color markers also in trackable logbook\n
- · Edit cache type filter by clicking on filter bar\n
- · Inverse sorting of cache lists (hit the same menu again)\n
- · View pager for trackable activity\n
- · Menu item to delete past events\n
- · Settings: Option to ask for list to store caches\n
- · Change list in cache details\n
- · Ability to select \"All\" list from main screen\n
- · Default log type for event caches will be \"Attended\" if \"Will attend\" was logged before\n
- · Improved and extended cache type detection on live map\n
- · Waypoints can be marked as visited\n
- · Possibility to delete offline logs in lists\n
- · Support of language specific characters in log text equally to the website\n
+ · ...\n
\n
<b>Bugfixing:</b>\n
- · Final flag icon lost when updating cache with self defined final\n
- · Bad selection in directory chooser\n
- · Log type \"Retract Listing\" now parsed correctly\n
- · Active cache detail page now remembered when rotating device\n
- · Replaced the term \"GC-Code\" by \"Geocode\"\n
- · Improvements for light theme\n
- · Share function uses short URL again\n
- · Offline log marker now shown after autosave of log\n
- · GPX export no longer exports waypoints without coordinates\n
+ · Do not apply encryption/decryption when clicking on a link in a log\n
+ · Also show a picture attached to a log locally after sending the log\n
+ · Improved display of log text for OC-caches\n
+ · Improved performance of GPX exports\n
\n
- <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=9&state=closed">Detailed list of all changes</a>\n
+ <a href="https://github.com/cgeo/c-geo-opensource/issues?milestone=17&state=closed">Detailed list of all changes</a>\n
\n
<b>Known Limitations/Bugs:</b>\n
· Live map:\n
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index ea8794f..daeb14e 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -23,12 +23,13 @@ import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.ui.CoordinatesFormatSwitcher; import cgeo.geocaching.ui.DecryptTextClickListener; +import cgeo.geocaching.ui.EditNoteDialog; +import cgeo.geocaching.ui.EditNoteDialog.EditNoteDialogListener; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.ImagesList; import cgeo.geocaching.ui.ImagesList.ImageType; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; -import cgeo.geocaching.ui.dialog.EditorDialog; import cgeo.geocaching.utils.BaseUtils; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.ClipboardUtils; @@ -66,6 +67,7 @@ import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Message; +import android.support.v4.app.FragmentManager; import android.text.Editable; import android.text.Html; import android.text.Spannable; @@ -113,7 +115,8 @@ import java.util.regex.Pattern; * * e.g. details, description, logs, waypoints, inventory... */ -public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> { +public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailActivity.Page> + implements EditNoteDialogListener { private static final int MENU_FIELD_COPY = 1; private static final int MENU_FIELD_TRANSLATE = 2; @@ -140,6 +143,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private final Progress progress = new Progress(); private SearchResult search; + private EditNoteDialogListener editNoteDialogListener; + private final GeoDirHandler locationUpdater = new GeoDirHandler() { @Override public void updateGeoData(final IGeoData geo) { @@ -1779,16 +1784,16 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc personalNoteEdit.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { - EditorDialog editor = new EditorDialog(CacheDetailActivity.this, personalNoteView.getText()); - editor.setOnEditorUpdate(new EditorDialog.EditorUpdate() { + editNoteDialogListener = new EditNoteDialogListener() { @Override - public void update(CharSequence editorText) { - cache.setPersonalNote(editorText.toString()); + public void onFinishEditNoteDialog(final String note) { + cache.setPersonalNote(note); setPersonalNote(personalNoteView); - cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - } - }); - editor.show(); + cgData.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); } + }; + final FragmentManager fm = getSupportFragmentManager(); + final EditNoteDialog dialog = new EditNoteDialog(cache.getPersonalNote()); + dialog.show(fm, "fragment_edit_note"); } }); } else { @@ -1851,13 +1856,10 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc personalNoteView.setText(personalNote, TextView.BufferType.SPANNABLE); if (StringUtils.isNotBlank(personalNote)) { personalNoteView.setVisibility(View.VISIBLE); - } - else { + } else { personalNoteView.setVisibility(View.GONE); } - } - - private void loadLongDescription() { + }private void loadLongDescription() { Button showDesc = (Button) view.findViewById(R.id.show_description); showDesc.setVisibility(View.GONE); showDesc.setOnClickListener(null); @@ -1869,6 +1871,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } + @Override + public void onFinishEditNoteDialog(final String note) { + editNoteDialogListener.onFinishEditNoteDialog(note); + } + private static class HtmlImageCounter implements Html.ImageGetter { private int imageCount = 0; @@ -2080,7 +2087,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (imageCounter.getImageCount() > 0) { // Complete view: parse again with loading images - if necessary ! If there are any images causing problems the user can see at least the preview LogImageLoader loader = new LogImageLoader(holder); - loader.execute(new String[] { logText }); + loader.execute(logText); } } else { diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 9a8325d..4b78e4d 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -49,6 +49,7 @@ import java.util.EnumSet; import java.util.HashMap; import java.util.LinkedList; import java.util.List; +import java.util.Locale; import java.util.Map; import java.util.regex.Pattern; @@ -710,10 +711,7 @@ public class Geocache implements ICache, IWaypoint { public String getPersonalNote() { // non premium members have no personal notes, premium members have an empty string by default. // map both to null, so other code doesn't need to differentiate - if (StringUtils.isBlank(personalNote)) { - return null; - } - return personalNote; + return StringUtils.defaultIfBlank(personalNote, null); } public boolean supportsUserActions() { @@ -1360,6 +1358,9 @@ public class Geocache implements ICache, IWaypoint { return null; } + /** + * Detect coordinates in the personal note and convert them to user defined waypoints. Works by rule of thumb. + */ public void parseWaypointsFromNote() { try { if (StringUtils.isBlank(getPersonalNote())) { @@ -1375,7 +1376,8 @@ public class Geocache implements ICache, IWaypoint { // coords must have non zero latitude and longitude and at least one part shall have fractional degrees if (point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 && ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0)) { final String name = cgeoapplication.getInstance().getString(R.string.cache_personal_note) + " " + count; - final Waypoint waypoint = new Waypoint(name, WaypointType.WAYPOINT, false); + final String potentialWaypointType = note.substring(Math.max(0, matcher.start() - 15)); + final Waypoint waypoint = new Waypoint(name, parseWaypointType(potentialWaypointType), false); waypoint.setCoords(point); addOrChangeWaypoint(waypoint, false); count++; @@ -1392,6 +1394,25 @@ public class Geocache implements ICache, IWaypoint { } } + /** + * Detect waypoint types in the personal note text. It works by rule of thumb only. + */ + private static WaypointType parseWaypointType(final String input) { + final String lowerInput = StringUtils.substring(input, 0, 20).toLowerCase(Locale.getDefault()); + for (WaypointType wpType : WaypointType.values()) { + if (lowerInput.contains(wpType.getL10n().toLowerCase(Locale.getDefault()))) { + return wpType; + } + if (lowerInput.contains(wpType.id)) { + return wpType; + } + if (lowerInput.contains(wpType.name().toLowerCase(Locale.US))) { + return wpType; + } + } + return WaypointType.WAYPOINT; + } + /* * For working in the debugger * (non-Javadoc) diff --git a/main/src/cgeo/geocaching/Settings.java b/main/src/cgeo/geocaching/Settings.java index 0c157e1..93bfa9b 100644 --- a/main/src/cgeo/geocaching/Settings.java +++ b/main/src/cgeo/geocaching/Settings.java @@ -149,6 +149,7 @@ public final class Settings { // maps private static MapProvider mapProvider = null; + private static String cacheTwitterMessage = "I found [NAME] ([URL])"; private Settings() { // this class is not to be instantiated; @@ -1424,4 +1425,18 @@ public final class Settings { } }); } + + public static String getCacheTwitterMessage() { + // TODO make customizable from UI + return cacheTwitterMessage; + } + + public static String getTrackableTwitterMessage() { + // TODO make customizable from UI + return "I touched [NAME] ([URL])!"; + } + + public static void setCacheTwitterMessage(final String message) { + cacheTwitterMessage = message; + } } diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index 69f5cc4..9a4c00b 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -10,7 +10,6 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; -import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import android.content.Context; @@ -76,18 +75,11 @@ public class StaticMapsProvider { final long fileSize = file.length(); if (fileSize < MIN_MAP_IMAGE_BYTES) { file.delete(); - return; } - return; } - return; } public static void downloadMaps(Geocache cache) { - if (cache == null) { - Log.e("downloadMaps - missing input parameter cache"); - return; - } if ((!Settings.isStoreOfflineMaps() && !Settings.isStoreOfflineWpMaps()) || StringUtils.isBlank(cache.getGeocode())) { return; } @@ -99,8 +91,8 @@ public class StaticMapsProvider { } // clean old and download static maps for waypoints if one is missing - if (Settings.isStoreOfflineWpMaps() && CollectionUtils.isNotEmpty(cache.getWaypoints())) { - for (Waypoint waypoint : cache.getWaypoints()) { + if (Settings.isStoreOfflineWpMaps()) { + for (final Waypoint waypoint : cache.getWaypoints()) { if (!hasAllStaticMapsForWaypoint(cache.getGeocode(), waypoint)) { refreshAllWpStaticMaps(cache, edge); } @@ -170,10 +162,6 @@ public class StaticMapsProvider { } public static void storeCachePreviewMap(final Geocache cache) { - if (cache == null) { - Log.e("storeCachePreviewMap - missing input parameter cache"); - return; - } final String latlonMap = cache.getCoords().format(Format.LAT_LON_DECDEGREE_COMMA); final Display display = ((WindowManager) cgeoapplication.getInstance().getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay(); DisplayMetrics metrics = new DisplayMetrics(); @@ -186,12 +174,7 @@ public class StaticMapsProvider { private static int guessMaxDisplaySide() { Point displaySize = Compatibility.getDisplaySize(); - final int maxWidth = displaySize.x - 25; - final int maxHeight = displaySize.y - 25; - if (maxWidth > maxHeight) { - return maxWidth; - } - return maxHeight; + return Math.max(displaySize.x, displaySize.y) - 25; } private static void downloadMaps(final String geocode, final String markerUrl, final String prefix, final String latlonMap, final int edge, diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java index 8f47606..74890f4 100644 --- a/main/src/cgeo/geocaching/VisitCacheActivity.java +++ b/main/src/cgeo/geocaching/VisitCacheActivity.java @@ -12,6 +12,7 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.twitter.Twitter; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.DateDialog; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; @@ -20,17 +21,15 @@ import org.apache.commons.collections.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import android.app.Activity; import android.app.AlertDialog; import android.app.AlertDialog.Builder; import android.app.Dialog; -import android.app.ProgressDialog; import android.content.DialogInterface; import android.content.DialogInterface.OnClickListener; import android.content.Intent; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.support.v4.app.LoaderManager; import android.support.v4.content.Loader; import android.util.SparseArray; @@ -67,7 +66,6 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private LayoutInflater inflater = null; private Geocache cache = null; - private ProgressDialog waitDialog = null; private String cacheid = null; private String geocode = null; private String text = null; @@ -240,34 +238,6 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD return res.getString(R.string.log_post_rate) + " " + ratingTextValue(rating) + "*"; } - private final Handler postLogHandler = new Handler() { - - @Override - public void handleMessage(final Message msg) { - if (waitDialog != null) { - waitDialog.dismiss(); - } - - final StatusCode error = (StatusCode) msg.obj; - if (error == StatusCode.NO_ERROR) { - showToast(res.getString(R.string.info_log_posted)); - // No need to save the log when quitting if it has been posted. - text = currentLogText(); - finish(); - } else if (error == StatusCode.LOG_SAVED) { - showToast(res.getString(R.string.info_log_saved)); - - if (waitDialog != null) { - waitDialog.dismiss(); - } - - finish(); - } else { - showToast(error.getErrorString(res)); - } - } - }; - public VisitCacheActivity() { super("c:geo-log"); } @@ -543,73 +513,79 @@ public class VisitCacheActivity extends AbstractLoggingActivity implements DateD private class PostListener implements View.OnClickListener { @Override public void onClick(View arg0) { - waitDialog = ProgressDialog.show(VisitCacheActivity.this, null, - res.getString(StringUtils.isBlank(imageUri.getPath()) ? R.string.log_saving : R.string.log_saving_and_uploading), true); - waitDialog.setCancelable(true); - - final Thread thread = new PostLogThread(postLogHandler, currentLogText()); - thread.start(); + final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? + R.string.log_saving : + R.string.log_saving_and_uploading); + new Poster(VisitCacheActivity.this, message).execute(currentLogText()); } } - private class PostLogThread extends Thread { - - private final Handler handler; - private final String log; + private class Poster extends AsyncTaskWithProgress<String, StatusCode> { - public PostLogThread(Handler handlerIn, String logIn) { - super("Post log"); - handler = handlerIn; - log = logIn; + public Poster(final Activity activity, final String progressMessage) { + super(activity, null, progressMessage, true); } @Override - public void run() { - final StatusCode status = postLogFn(log); - handler.sendMessage(handler.obtainMessage(0, status)); - } - } - - public StatusCode postLogFn(String log) { - StatusCode result = StatusCode.LOG_POST_ERROR; + protected StatusCode doInBackgroundInternal(final String[] logTexts) { + final String log = logTexts[0]; + try { + final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, + date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), + log, trackables); - try { + if (postResult.left == StatusCode.NO_ERROR) { + final LogEntry logNow = new LogEntry(date, typeSelected, log); - final ImmutablePair<StatusCode, String> postResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected, - date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE), - log, trackables); - result = postResult.left; + cache.getLogs().add(0, logNow); - if (result == StatusCode.NO_ERROR) { - final LogEntry logNow = new LogEntry(date, typeSelected, log); + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { + cache.setFound(true); + } - cache.getLogs().add(0, logNow); + cgData.saveChangedCache(cache); + cgData.clearLogOffline(geocode); - if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { - cache.setFound(true); - } - - cgData.saveChangedCache(cache); - cgData.clearLogOffline(geocode); + if (typeSelected == LogType.FOUND_IT) { + if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { + Twitter.postTweetCache(geocode); + } + GCVote.setRating(cache, rating); + } - if (typeSelected == LogType.FOUND_IT) { - if (tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { - Twitter.postTweetCache(geocode); + if (StringUtils.isNotBlank(imageUri.getPath())) { + ImmutablePair<StatusCode, String> imageResult = GCParser.uploadLogImage(postResult.right, imageCaption, imageDescription, imageUri); + final String uploadedImageUrl = imageResult.right; + if (StringUtils.isNotEmpty(uploadedImageUrl)) { + logNow.addLogImage(new Image(uploadedImageUrl, imageCaption, imageDescription)); + cgData.saveChangedCache(cache); + } + return imageResult.left; } - GCVote.setRating(cache, rating); } - if (StringUtils.isNotBlank(imageUri.getPath())) { - result = GCParser.uploadLogImage(postResult.right, imageCaption, imageDescription, imageUri); - } + return postResult.left; + } catch (Exception e) { + Log.e("cgeovisit.postLogFn", e); } - return result; - } catch (Exception e) { - Log.e("cgeovisit.postLogFn", e); + return StatusCode.LOG_POST_ERROR; } - return StatusCode.LOG_POST_ERROR; + @Override + protected void onPostExecuteInternal(final StatusCode status) { + if (status == StatusCode.NO_ERROR) { + showToast(res.getString(R.string.info_log_posted)); + // No need to save the log when quitting if it has been posted. + text = currentLogText(); + finish(); + } else if (status == StatusCode.LOG_SAVED) { + showToast(res.getString(R.string.info_log_saved)); + finish(); + } else { + showToast(status.getErrorString(res)); + } + } } private void saveLog(final boolean force) { diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index 48c9bc5..6112986 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -275,7 +275,7 @@ public class Waypoint implements IWaypoint, Comparable<Waypoint> { if (coords != null) { hash = coords.hashCode(); } - hash = hash ^ waypointType.markerId; + hash ^= waypointType.markerId; return (int) hash; } } diff --git a/main/src/cgeo/geocaching/cgData.java b/main/src/cgeo/geocaching/cgData.java index 28485a5..86821fb 100644 --- a/main/src/cgeo/geocaching/cgData.java +++ b/main/src/cgeo/geocaching/cgData.java @@ -33,6 +33,7 @@ import java.io.File; import java.io.FilenameFilter; import java.util.ArrayList; import java.util.Arrays; +import java.util.Collection; import java.util.Collections; import java.util.Date; import java.util.EnumSet; @@ -1396,7 +1397,7 @@ public class cgData { * @param geocodes * @return Set of loaded caches. Never null. */ - public static Set<Geocache> loadCaches(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { + public static Set<Geocache> loadCaches(final Collection<String> geocodes, final EnumSet<LoadFlag> loadFlags) { if (CollectionUtils.isEmpty(geocodes)) { return new HashSet<Geocache>(); } diff --git a/main/src/cgeo/geocaching/cgeo.java b/main/src/cgeo/geocaching/cgeo.java index 5680ff3..0140c3a 100644 --- a/main/src/cgeo/geocaching/cgeo.java +++ b/main/src/cgeo/geocaching/cgeo.java @@ -311,6 +311,7 @@ public class cgeo extends AbstractActivity { @Override public void onActivityResult(int requestCode, int resultCode, Intent intent) { if (requestCode == SCAN_REQUEST_CODE) { + // Only handle positive results, don't do anything if cancelled. if (resultCode == RESULT_OK) { String scan = intent.getStringExtra("SCAN_RESULT"); if (StringUtils.isBlank(scan)) { @@ -318,8 +319,6 @@ public class cgeo extends AbstractActivity { } SearchActivity.startActivityScan(scan, this); - } else if (resultCode == RESULT_CANCELED) { - // do nothing } } else if (requestCode == SEARCH_REQUEST_CODE) { // SearchActivity activity returned without making a search diff --git a/main/src/cgeo/geocaching/cgeocaches.java b/main/src/cgeo/geocaching/cgeocaches.java index df09936..bcd9531 100644 --- a/main/src/cgeo/geocaching/cgeocaches.java +++ b/main/src/cgeo/geocaching/cgeocaches.java @@ -23,6 +23,7 @@ import cgeo.geocaching.loaders.AddressGeocacheListLoader; import cgeo.geocaching.loaders.CoordsGeocacheListLoader; import cgeo.geocaching.loaders.HistoryGeocacheListLoader; import cgeo.geocaching.loaders.KeywordGeocacheListLoader; +import cgeo.geocaching.loaders.NextPageGeocacheListLoader; import cgeo.geocaching.loaders.OfflineGeocacheListLoader; import cgeo.geocaching.loaders.OwnerGeocacheListLoader; import cgeo.geocaching.loaders.RemoveFromHistoryLoader; @@ -38,6 +39,7 @@ import cgeo.geocaching.sorting.VisitComparator; import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; +import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.GeoDirHandler; import cgeo.geocaching.utils.Log; @@ -73,6 +75,7 @@ import android.widget.ListView; import android.widget.TextView; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.Date; import java.util.HashSet; @@ -372,21 +375,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } }; - private Handler dropDetailsHandler = new Handler() { - - @Override - public void handleMessage(Message msg) { - if (msg.what != MSG_CANCEL) { - adapter.setSelectMode(false); - - refreshCurrentList(); - - replaceCacheListFromSearch(); - - progress.dismiss(); - } - } - }; private Handler clearOfflineLogsHandler = new Handler() { @Override @@ -427,7 +415,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity if (extras != null) { Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE); type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE; - coords = (Geopoint) extras.getParcelable(Intents.EXTRAS_COORDS); + coords = extras.getParcelable(Intents.EXTRAS_COORDS); } else { extras = new Bundle(); @@ -464,7 +452,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public void onConfigurationChanged(Configuration newConfig) { super.onConfigurationChanged(newConfig); - if (currentLoader.isLoading()) { + if (currentLoader != null && currentLoader.isLoading()) { showFooterLoadingCaches(); } } @@ -793,7 +781,6 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } public void deletePastEvents() { - progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL)); final List<Geocache> deletion = new ArrayList<Geocache>(); for (Geocache cache : adapter.getCheckedOrAllCaches()) { if (cache.isEventCache()) { @@ -803,7 +790,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } } - new DropDetailsThread(dropDetailsHandler, deletion).start(); + new DropDetailsTask(false).execute(deletion.toArray(new Geocache[deletion.size()])); } public void clearOfflineLogs() { @@ -1143,7 +1130,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity public void removeFromHistory() { final List<Geocache> caches = adapter.getCheckedOrAllCaches(); - final String geocodes[] = new String[caches.size()]; + final String[] geocodes = new String[caches.size()]; for (int i = 0; i < geocodes.length; i++) { geocodes[i] = caches.get(i).getGeocode(); } @@ -1176,10 +1163,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public void onClick(DialogInterface dialog, int id) { - dropSelected(); - if (removeListAfterwards) { - removeList(false); - } + dropSelected(removeListAfterwards); dialog.cancel(); } }); @@ -1195,9 +1179,9 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity alert.show(); } - public void dropSelected() { - progress.show(this, null, res.getString(R.string.caches_drop_progress), true, dropDetailsHandler.obtainMessage(MSG_CANCEL)); - new DropDetailsThread(dropDetailsHandler, adapter.getCheckedOrAllCaches()).start(); + public void dropSelected(boolean removeListAfterwards) { + final List<Geocache> selected = adapter.getCheckedOrAllCaches(); + new DropDetailsTask(removeListAfterwards).execute(selected.toArray(new Geocache[selected.size()])); } /** @@ -1382,24 +1366,35 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } } - private class DropDetailsThread extends Thread { + private class DropDetailsTask extends AsyncTaskWithProgress<Geocache, Void> { - final private Handler handler; - final private List<Geocache> selected; + private final boolean removeListAfterwards; - public DropDetailsThread(Handler handlerIn, List<Geocache> selectedIn) { - handler = handlerIn; - selected = selectedIn; + public DropDetailsTask(boolean removeListAfterwards) { + super(cgeocaches.this, null, res.getString(R.string.caches_drop_progress), true); + this.removeListAfterwards = removeListAfterwards; } @Override - public void run() { + protected Void doInBackgroundInternal(Geocache[] caches) { removeGeoAndDir(); - cgData.markDropped(selected); - handler.sendEmptyMessage(MSG_DONE); - + cgData.markDropped(Arrays.asList(caches)); startGeoAndDir(); + return null; + } + + @Override + protected void onPostExecuteInternal(Void result) { + // remove list in UI because of toast + if (removeListAfterwards) { + removeList(false); + } + + adapter.setSelectMode(false); + refreshCurrentList(); + replaceCacheListFromSearch(); } + } private class ClearOfflineLogsThread extends Thread { @@ -1427,7 +1422,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity showFooterLoadingCaches(); listFooter.setOnClickListener(null); - currentLoader.startLoading(); + getSupportLoaderManager().restartLoader(CacheListLoaderType.NEXT_PAGE.ordinal(), null, cgeocaches.this); } } @@ -1444,7 +1439,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity * @param view * unused here but needed since this method is referenced from XML layout */ - public void selectList(View view) { + public void selectList(@SuppressWarnings("unused") View view) { if (type != CacheListType.OFFLINE) { return; } @@ -1543,7 +1538,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity * @param view * unused here but needed since this method is referenced from XML layout */ - public void goMap(View view) { + public void goMap(@SuppressWarnings("unused") View view) { if (search == null || CollectionUtils.isEmpty(cacheList)) { showToast(res.getString(R.string.warn_no_cache_coord)); @@ -1724,9 +1719,11 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity @Override public Loader<SearchResult> onCreateLoader(int type, Bundle extras) { - AbstractSearchLoader loader = null; + if (type >= CacheListLoaderType.values().length) { + throw new IllegalArgumentException("invalid loader type " + type); + } CacheListLoaderType enumType = CacheListLoaderType.values()[type]; - final String username = extras.getString(Intents.EXTRA_USERNAME); + AbstractSearchLoader loader = null; switch (enumType) { case OFFLINE: listId = Settings.getLastList(); @@ -1775,12 +1772,14 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity } break; case USERNAME: + final String username = extras.getString(Intents.EXTRA_USERNAME); title = username; loader = new UsernameGeocacheListLoader(app, username); break; case OWNER: - title = username; - loader = new OwnerGeocacheListLoader(app, username); + final String ownerName = extras.getString(Intents.EXTRA_USERNAME); + title = ownerName; + loader = new OwnerGeocacheListLoader(app, ownerName); break; case MAP: //TODO Build Nullloader @@ -1793,12 +1792,9 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity title = res.getString(R.string.caches_history); loader = new RemoveFromHistoryLoader(app, extras.getStringArray(Intents.EXTRA_CACHELIST), coords); break; - - default: - title = "caches"; - setTitle(title); - Log.e("cgeocaches.onCreate: No action or unknown action specified"); - return null; + case NEXT_PAGE: + loader = new NextPageGeocacheListLoader(app, search); + break; } setTitle(title); showProgress(true); diff --git a/main/src/cgeo/geocaching/cgeonavigate.java b/main/src/cgeo/geocaching/cgeonavigate.java index 17c2e20..929f0a3 100644 --- a/main/src/cgeo/geocaching/cgeonavigate.java +++ b/main/src/cgeo/geocaching/cgeonavigate.java @@ -63,7 +63,7 @@ public class cgeonavigate extends AbstractActivity { if (extras != null) { title = extras.getString(EXTRAS_GEOCODE); final String name = extras.getString(EXTRAS_NAME); - dstCoords = (Geopoint) extras.getParcelable(EXTRAS_COORDS); + dstCoords = extras.getParcelable(EXTRAS_COORDS); info = extras.getString(EXTRAS_CACHE_INFO); if (StringUtils.isNotBlank(name)) { diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 4d27617..d8711cf 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -149,7 +149,7 @@ public final class GCConstants { public final static Pattern PATTERN_MAINTENANCE = Pattern.compile("<span id=\"ctl00_ContentBody_LogBookPanel1_lbConfirm\"[^>]*>([^<]*<font[^>]*>)?([^<]+)(</font>[^<]*)?</span>", Pattern.CASE_INSENSITIVE); public final static Pattern PATTERN_OK1 = Pattern.compile("<h2[^>]*>[^<]*<span id=\"ctl00_ContentBody_lbHeading\"[^>]*>[^<]*</span>[^<]*</h2>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_OK2 = Pattern.compile("<div id=[\"|']ctl00_ContentBody_LogBookPanel1_ViewLogPanel[\"|']>", Pattern.CASE_INSENSITIVE); - public final static Pattern PATTERN_OK_IMAGEUPLOAD = Pattern.compile("<div id=[\"|']ctl00_ContentBody_ImageUploadControl1_uxUploadDonePanel[\"|']>", Pattern.CASE_INSENSITIVE); + public final static Pattern PATTERN_IMAGE_UPLOAD_URL = Pattern.compile("title=\"Click for Larger Image\"\\s*src=\"(.*?)\"", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATEFIELDCOUNT = Pattern.compile("id=\"__VIEWSTATEFIELDCOUNT\"[^(value)]+value=\"(\\d+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_VIEWSTATES = Pattern.compile("id=\"__VIEWSTATE(\\d*)\"[^(value)]+value=\"([^\"]+)\"[^>]+>", Pattern.CASE_INSENSITIVE | Pattern.MULTILINE); public final static Pattern PATTERN_USERTOKEN = Pattern.compile("userToken\\s*=\\s*'([^']+)'"); diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 76351c1..4b5d7b9 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -1059,7 +1059,7 @@ public abstract class GCParser { * the URI for the image to be uploaded * @return status code to indicate success or failure */ - public static StatusCode uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { + public static ImmutablePair<StatusCode, String> uploadLogImage(final String logId, final String caption, final String description, final Uri imageUri) { final String uri = new Uri.Builder().scheme("http").authority("www.geocaching.com").path("/seek/upload.aspx").encodedQuery("LID=" + logId).build().toString(); String page = Network.getResponseData(Network.getRequest(uri)); @@ -1071,7 +1071,7 @@ public abstract class GCParser { page = Network.getResponseData(Network.getRequest(uri)); } else { Log.e("Image upload: No login (error: " + loginState + ')'); - return StatusCode.NOT_LOGGED_IN; + return ImmutablePair.of(StatusCode.NOT_LOGGED_IN, null); } } @@ -1088,16 +1088,16 @@ public abstract class GCParser { final File image = new File(imageUri.getPath()); final String response = Network.getResponseData(Network.postRequest(uri, uploadParams, "ctl00$ContentBody$ImageUploadControl1$uxFileUpload", "image/jpeg", image)); - MatcherWrapper matcherOK = new MatcherWrapper(GCConstants.PATTERN_OK_IMAGEUPLOAD, response); + MatcherWrapper matcherUrl = new MatcherWrapper(GCConstants.PATTERN_IMAGE_UPLOAD_URL, response); - if (matcherOK.find()) { + if (matcherUrl.find()) { Log.i("Logimage successfully uploaded."); - - return StatusCode.NO_ERROR; + final String uploadedImageUrl = matcherUrl.group(1); + return ImmutablePair.of(StatusCode.NO_ERROR, uploadedImageUrl); } Log.e("GCParser.uploadLogIMage: Failed to upload image because of unknown error"); - return StatusCode.LOGIMAGE_POST_ERROR; + return ImmutablePair.of(StatusCode.LOGIMAGE_POST_ERROR, null); } /** diff --git a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java index 621032f..d03062f 100644 --- a/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java +++ b/main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java @@ -48,6 +48,8 @@ public class OC11XMLParser { private static Pattern LOCAL_URL = Pattern.compile("href=\"(.*)\""); private static final int CACHE_PARSE_LIMIT = 250; private static final Resources res = cgeoapplication.getInstance().getResources(); + private static final Pattern WHITESPACE = Pattern.compile("<p>(\\s| )*</p>"); + private static ImageHolder imageHolder = null; @@ -513,7 +515,7 @@ public class OC11XMLParser { @Override public void end(String body) { final String content = body.trim(); - descHolder.shortDesc = linkify(stripMarkup(content)); + descHolder.shortDesc = linkify(stripEmptyText(content)); } }); @@ -523,7 +525,7 @@ public class OC11XMLParser { @Override public void end(String body) { final String content = body.trim(); - descHolder.desc = linkify(stripMarkup(content)); + descHolder.desc = linkify(stripEmptyText(content)); } }); @@ -626,7 +628,7 @@ public class OC11XMLParser { @Override public void end(String logText) { - logHolder.logEntry.log = stripMarkup(logText); + logHolder.logEntry.log = stripEmptyText(logText); } }); @@ -728,14 +730,20 @@ public class OC11XMLParser { } /** - * Removes unneeded markup. Log texts are typically encapsulated in paragraph tags which lead to more empty space on - * rendering. + * Removes some unneeded markup and whitespace. Log texts are typically encapsulated in paragraph tags which lead to + * more empty space on rendering. */ - protected static String stripMarkup(String input) { - if (!StringUtils.startsWith(input, "<")) { - return input; + protected static String stripEmptyText(String input) { + final Matcher matcher = WHITESPACE.matcher(input); + String result = matcher.replaceAll("").trim(); + if (!StringUtils.startsWith(result, "<")) { + return result; } - String result = input.trim(); + return stripMarkup(result); + } + + private static String stripMarkup(final String input) { + String result = input; for (String tagName : MARKUP) { final String startTag = "<" + tagName + ">"; if (StringUtils.startsWith(result, startTag)) { diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java index eadcb77..2900781 100644 --- a/main/src/cgeo/geocaching/export/FieldnoteExport.java +++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java @@ -56,18 +56,19 @@ class FieldnoteExport extends AbstractExport { } @Override - public void export(final List<Geocache> caches, final Activity activity) { + public void export(final List<Geocache> cachesList, final Activity activity) { + final Geocache[] caches = cachesList.toArray(new Geocache[cachesList.size()]); if (null == activity) { // No activity given, so no user interaction possible. // Start export with default parameters. - new ExportTask(caches, null, false, false).execute((Void) null); + new ExportTask(null, false, false).execute(caches); } else { // Show configuration dialog getExportOptionsDialog(caches, activity).show(); } } - private Dialog getExportOptionsDialog(final List<Geocache> caches, final Activity activity) { + private Dialog getExportOptionsDialog(final Geocache[] caches, final Activity activity) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); // AlertDialog has always dark style, so we have to apply it as well always @@ -90,19 +91,17 @@ class FieldnoteExport extends AbstractExport { public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); new ExportTask( - caches, activity, uploadOption.isChecked(), onlyNewOption.isChecked()) - .execute((Void) null); + .execute(caches); } }); return builder.create(); } - private class ExportTask extends AsyncTaskWithProgress<Void, Boolean> { - private final List<Geocache> caches; + private class ExportTask extends AsyncTaskWithProgress<Geocache, Boolean> { private final Activity activity; private final boolean upload; private final boolean onlyNew; @@ -113,8 +112,6 @@ class FieldnoteExport extends AbstractExport { /** * Instantiates and configures the task for exporting field notes. * - * @param caches - * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts * @param upload @@ -122,16 +119,15 @@ class FieldnoteExport extends AbstractExport { * @param onlyNew * Upload/export only new logs since last export */ - public ExportTask(final List<Geocache> caches, final Activity activity, final boolean upload, final boolean onlyNew) { + public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) { super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating)); - this.caches = caches; this.activity = activity; this.upload = upload; this.onlyNew = onlyNew; } @Override - protected Boolean doInBackground(Void... params) { + protected Boolean doInBackgroundInternal(Geocache[] caches) { final StringBuilder fieldNoteBuffer = new StringBuilder(); try { int i = 0; @@ -219,8 +215,7 @@ class FieldnoteExport extends AbstractExport { } @Override - protected void onPostExecute(Boolean result) { - super.onPostExecute(result); + protected void onPostExecuteInternal(Boolean result) { if (null != activity) { if (result) { // if (onlyNew) { @@ -239,12 +234,12 @@ class FieldnoteExport extends AbstractExport { } @Override - protected void onProgressUpdate(Integer... status) { + protected void onProgressUpdateInternal(int status) { if (null != activity) { - if (STATUS_UPLOAD == status[0]) { + if (STATUS_UPLOAD == status) { setMessage(getString(R.string.export_fieldnotes_uploading)); } else { - setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status[0] + ')'); + setMessage(getString(R.string.export_fieldnotes_creating) + " (" + status + ')'); } } } diff --git a/main/src/cgeo/geocaching/export/GpxExport.java b/main/src/cgeo/geocaching/export/GpxExport.java index 19b8518..c6f72fd 100644 --- a/main/src/cgeo/geocaching/export/GpxExport.java +++ b/main/src/cgeo/geocaching/export/GpxExport.java @@ -37,9 +37,9 @@ import java.io.FileWriter; import java.io.IOException; import java.text.SimpleDateFormat; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; import java.util.Date; -import java.util.HashSet; -import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Set; @@ -61,18 +61,19 @@ class GpxExport extends AbstractExport { @Override public void export(final List<Geocache> caches, final Activity activity) { + String[] geocodes = getGeocodes(caches); if (null == activity) { // No activity given, so no user interaction possible. // Start export with default parameters. - new ExportTask(caches, null).execute((Void) null); + new ExportTask(null).execute(geocodes); } else { // Show configuration dialog - getExportDialog(caches, activity).show(); + getExportDialog(geocodes, activity).show(); } } - private Dialog getExportDialog(final List<Geocache> caches, final Activity activity) { + private Dialog getExportDialog(final String[] geocodes, final Activity activity) { AlertDialog.Builder builder = new AlertDialog.Builder(activity); // AlertDialog has always dark style, so we have to apply it as well always @@ -98,43 +99,47 @@ class GpxExport extends AbstractExport { @Override public void onClick(DialogInterface dialog, int which) { dialog.dismiss(); - new ExportTask(caches, activity).execute((Void) null); + new ExportTask(activity).execute(geocodes); } }); return builder.create(); } - private class ExportTask extends AsyncTaskWithProgress<Void, File> { - private final Set<String> allGeocodes = new HashSet<String>(); + private static String[] getGeocodes(final List<Geocache> caches) { + ArrayList<String> allGeocodes = new ArrayList<String>(caches.size()); + for (final Geocache geocache : caches) { + allGeocodes.add(geocache.getGeocode()); + } + return allGeocodes.toArray(new String[allGeocodes.size()]); + } + + private class ExportTask extends AsyncTaskWithProgress<String, File> { private final Activity activity; private int countExported = 0; /** * Instantiates and configures the task for exporting field notes. * - * @param caches - * The {@link List} of {@link cgeo.geocaching.Geocache} to be exported * @param activity * optional: Show a progress bar and toasts */ - public ExportTask(final List<Geocache> caches, final Activity activity) { - super(activity, caches.size(), getProgressTitle(), cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, caches.size(), caches.size())); - - // get rid of the (half loaded) caches, we will reload them as full caches during the export - for (Geocache geocache : caches) { - allGeocodes.add(geocache.getGeocode()); - } + public ExportTask(final Activity activity) { + super(activity, getProgressTitle()); this.activity = activity; } @Override - protected File doInBackground(Void... params) { + protected File doInBackgroundInternal(String[] geocodes) { // quick check for being able to write the GPX file if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) { return null; } + List<String> allGeocodes = new ArrayList<String>(Arrays.asList(geocodes)); + + setMessage(cgeoapplication.getInstance().getResources().getQuantityString(R.plurals.cache_counts, allGeocodes.size(), allGeocodes.size())); + final SimpleDateFormat fileNameDateFormat = new SimpleDateFormat("yyyyMMddHHmmss", Locale.US); final File exportFile = new File(Settings.getGpxExportDir() + File.separatorChar + "export_" + fileNameDateFormat.format(new Date()) + ".gpx"); FileWriter writer = null; @@ -159,17 +164,10 @@ class GpxExport extends AbstractExport { // Split the overall set of geocodes into small chunks. That is a compromise between memory efficiency (because // we don't load all caches fully into memory) and speed (because we don't query each cache separately). - while (!allGeocodes.isEmpty()) { - int cachesInBatch = 0; - HashSet<String> geocodesOfBatch = new HashSet<String>(CACHES_PER_BATCH); - for (Iterator<String> iterator = allGeocodes.iterator(); iterator.hasNext() && cachesInBatch < CACHES_PER_BATCH;) { - final String geocode = iterator.next(); - geocodesOfBatch.add(geocode); - cachesInBatch++; - } - allGeocodes.removeAll(geocodesOfBatch); - exportBatch(gpx, geocodesOfBatch); + final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size())); + exportBatch(gpx, batch); + batch.clear(); } gpx.endTag(PREFIX_GPX, "gpx"); @@ -195,7 +193,7 @@ class GpxExport extends AbstractExport { return exportFile; } - private void exportBatch(final XmlSerializer gpx, HashSet<String> geocodesOfBatch) throws IOException { + private void exportBatch(final XmlSerializer gpx, Collection<String> geocodesOfBatch) throws IOException { Set<Geocache> caches = cgData.loadCaches(geocodesOfBatch, LoadFlags.LOAD_ALL_DB_ONLY); for (Geocache cache : caches) { gpx.startTag(PREFIX_GPX, "wpt"); @@ -366,8 +364,7 @@ class GpxExport extends AbstractExport { } @Override - protected void onPostExecute(final File exportFile) { - super.onPostExecute(exportFile); + protected void onPostExecuteInternal(final File exportFile) { if (null != activity) { if (exportFile != null) { ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString()); diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index b8dcbb3..ff81fe1 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -1,458 +1,462 @@ -package cgeo.geocaching.files;
-
-import cgeo.geocaching.Geocache;
-import cgeo.geocaching.R;
-import cgeo.geocaching.SearchResult;
-import cgeo.geocaching.Settings;
-import cgeo.geocaching.StaticMapsProvider;
-import cgeo.geocaching.cgData;
-import cgeo.geocaching.activity.IAbstractActivity;
-import cgeo.geocaching.activity.Progress;
-import cgeo.geocaching.enumerations.LoadFlags;
-import cgeo.geocaching.utils.CancellableHandler;
-import cgeo.geocaching.utils.Log;
-
-import org.apache.commons.lang3.StringUtils;
-
-import android.app.Activity;
-import android.app.ProgressDialog;
-import android.content.ContentResolver;
-import android.content.Context;
-import android.content.Intent;
-import android.content.res.Resources;
-import android.net.Uri;
-import android.os.Handler;
-import android.os.Message;
-
-import java.io.File;
-import java.io.FileInputStream;
-import java.io.IOException;
-import java.io.InputStream;
-import java.util.Arrays;
-import java.util.Collection;
-import java.util.Collections;
-import java.util.List;
-import java.util.concurrent.CancellationException;
-import java.util.zip.ZipEntry;
-import java.util.zip.ZipInputStream;
-
-public class GPXImporter {
- static final int IMPORT_STEP_START = 0;
- static final int IMPORT_STEP_READ_FILE = 1;
- static final int IMPORT_STEP_READ_WPT_FILE = 2;
- static final int IMPORT_STEP_STORE_STATIC_MAPS = 4;
- static final int IMPORT_STEP_FINISHED = 5;
- static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6;
- static final int IMPORT_STEP_CANCEL = 7;
- static final int IMPORT_STEP_CANCELED = 8;
- static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9;
-
- public static final String GPX_FILE_EXTENSION = ".gpx";
- public static final String ZIP_FILE_EXTENSION = ".zip";
- public static final String WAYPOINTS_FILE_SUFFIX = "-wpts";
- public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION;
-
- private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml");
- private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream");
-
- private Progress progress = new Progress(true);
-
- private Resources res;
- private int listId;
- private IAbstractActivity fromActivity;
- private Handler importFinishedHandler;
-
- public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) {
- this.listId = listId;
- this.fromActivity = fromActivity;
- res = ((Activity) fromActivity).getResources();
- this.importFinishedHandler = importFinishedHandler;
- }
-
- /**
- * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files.
- *
- * @param file
- * the file to import
- */
- public void importGPX(final File file) {
- if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) {
- new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start();
- } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) {
- new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start();
- } else {
- new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start();
- }
- }
-
- /**
- * Import GPX provided via intent of activity that instantiated this GPXImporter.
- */
- public void importGPX() {
- final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver();
- final Intent intent = ((Activity) fromActivity).getIntent();
- final Uri uri = intent.getData();
-
- String mimeType = intent.getType();
- // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension
- // contentResolver.getType(uri) doesn't help but throws exception for emulators email app
- // Permission Denial: reading com.android.email.provider.EmailProvider uri
- // Google search says: there is no solution for this problem
- // Gmail doesn't work at all, see #967
- if (mimeType == null) {
- if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) {
- mimeType = "application/xml";
- } else {
- // if we can't determine a better type, default to zip import
- // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null
- mimeType = "application/zip";
- }
- }
-
- Log.i("importGPX: " + uri + ", mimetype=" + mimeType);
- if (GPX_MIME_TYPES.contains(mimeType)) {
- new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else if (ZIP_MIME_TYPES.contains(mimeType)) {
- new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start();
- } else {
- importFinished();
- }
- }
-
- static abstract class ImportThread extends Thread {
- final int listId;
- final Handler importStepHandler;
- final CancellableHandler progressHandler;
-
- protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- this.listId = listId;
- this.importStepHandler = importStepHandler;
- this.progressHandler = progressHandler;
- }
-
- @Override
- public void run() {
- try {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START));
- final Collection<Geocache> caches = doImport();
- Log.i("Imported successfully " + caches.size() + " caches.");
-
- final SearchResult search = new SearchResult();
- for (Geocache cache : caches) {
- search.addCache(cache);
- }
-
- if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount()));
- boolean finishedWithoutCancel = importStaticMaps(search);
- // Skip last message if static maps where canceled
- if (!finishedWithoutCancel) {
- return;
- }
- }
-
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search));
- } catch (IOException e) {
- Log.i("Importing caches failed - error reading data: " + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage()));
- } catch (ParserException e) {
- Log.i("Importing caches failed - data format error" + e.getMessage());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage()));
- } catch (CancellationException e) {
- Log.i("Importing caches canceled");
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED));
- } catch (Exception e) {
- Log.e("Importing caches failed - unknown error: ", e);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage()));
- }
- }
-
- protected abstract Collection<Geocache> doImport() throws IOException, ParserException;
-
- private boolean importStaticMaps(final SearchResult importedCaches) {
- int storedCacheMaps = 0;
- for (String geocode : importedCaches.getGeocodes()) {
- Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
- Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode);
- StaticMapsProvider.downloadMaps(cache);
- storedCacheMaps++;
- if (progressHandler.isCancelled()) {
- return false;
- }
- progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0));
- }
- return true;
- }
- }
-
- static class ImportLocFileThread extends ImportThread {
- private final File file;
-
- public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.file = file;
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- Log.i("Import LOC file: " + file.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length()));
- LocParser parser = new LocParser(listId);
- return parser.parse(file, progressHandler);
- }
- }
-
- static abstract class ImportGpxThread extends ImportThread {
-
- protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport() throws IOException, ParserException {
- try {
- // try to parse cache file as GPX 10
- return doImport(new GPX10Parser(listId));
- } catch (ParserException pe) {
- // didn't work -> lets try GPX11
- return doImport(new GPX11Parser(listId));
- }
- }
-
- protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException;
- }
-
- static class ImportGpxFileThread extends ImportGpxThread {
- private final File cacheFile;
-
- public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Log.i("Import GPX file: " + cacheFile.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length()));
- Collection<Geocache> caches = parser.parse(cacheFile, progressHandler);
-
- final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile);
- if (wptsFilename != null) {
- final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename);
- if (wptsFile.canRead()) {
- Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath());
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length()));
- caches = parser.parse(wptsFile, progressHandler);
- }
- }
- return caches;
- }
- }
-
- static class ImportGpxAttachmentThread extends ImportGpxThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Log.i("Import GPX from uri: " + uri);
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1));
- InputStream is = contentResolver.openInputStream(uri);
- try {
- return parser.parse(is, progressHandler);
- } finally {
- is.close();
- }
- }
- }
-
- static abstract class ImportGpxZipThread extends ImportGpxThread {
-
- protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- }
-
- @Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
- Collection<Geocache> caches = Collections.emptySet();
- // can't assume that GPX file comes before waypoint file in zip -> so we need two passes
- // 1. parse GPX files
- ZipInputStream zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) {
- if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- } else {
- throw new ParserException("Imported zip is not a GPX zip file.");
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- // 2. parse waypoint files
- zis = new ZipInputStream(getInputStream());
- try {
- for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) {
- if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) {
- importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize()));
- caches = parser.parse(new NoCloseInputStream(zis), progressHandler);
- }
- zis.closeEntry();
- }
- } finally {
- zis.close();
- }
-
- return caches;
- }
-
- protected abstract InputStream getInputStream() throws IOException;
- }
-
- static class ImportGpxZipFileThread extends ImportGpxZipThread {
- private final File cacheFile;
-
- public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.cacheFile = file;
- Log.i("Import zipped GPX: " + file);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return new FileInputStream(cacheFile);
- }
- }
-
- static class ImportGpxZipAttachmentThread extends ImportGpxZipThread {
- private final Uri uri;
- private ContentResolver contentResolver;
-
- public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
- super(listId, importStepHandler, progressHandler);
- this.uri = uri;
- this.contentResolver = contentResolver;
- Log.i("Import zipped GPX from uri: " + uri);
- }
-
- @Override
- protected InputStream getInputStream() throws IOException {
- return contentResolver.openInputStream(uri);
- }
- }
-
- final private CancellableHandler progressHandler = new CancellableHandler() {
- @Override
- public void handleRegularMessage(Message msg) {
- progress.setProgress(msg.arg1);
- }
- };
-
- final private Handler importStepHandler = new Handler() {
- private boolean showProgressAfterCancel = false;
-
- @Override
- public void handleMessage(Message msg) {
- switch (msg.what) {
- case IMPORT_STEP_START:
- Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage);
- break;
-
- case IMPORT_STEP_READ_FILE:
- case IMPORT_STEP_READ_WPT_FILE:
- progress.setMessage(res.getString(msg.arg1));
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STORE_STATIC_MAPS:
- progress.dismiss();
- Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0);
- progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage);
- progress.setMaxProgressAndReset(msg.arg2);
- break;
-
- case IMPORT_STEP_STATIC_MAPS_SKIPPED:
- progress.dismiss();
- progressHandler.cancel();
- StringBuilder bufferSkipped = new StringBuilder(20);
- bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString());
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported));
- importFinished();
- break;
-
- case IMPORT_STEP_FINISHED_WITH_ERROR:
- progress.dismiss();
- fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj);
- importFinished();
- break;
-
- case IMPORT_STEP_CANCEL:
- progress.dismiss();
- progressHandler.cancel();
- break;
-
- case IMPORT_STEP_CANCELED:
- StringBuilder bufferCanceled = new StringBuilder(20);
- bufferCanceled.append(res.getString(R.string.gpx_import_canceled));
- if (showProgressAfterCancel) {
- bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported));
- }
- fromActivity.showShortToast(bufferCanceled.toString());
- importFinished();
- break;
-
- default:
- break;
- }
- }
- };
-
- /**
- * @param gpxfile
- * the gpx file
- * @return the expected file name of the waypoints file
- */
- static String getWaypointsFileNameForGpxFile(final File gpxfile) {
- if (gpxfile == null || !gpxfile.canRead()) {
- return null;
- }
- final String gpxFileName = gpxfile.getName();
- File dir = gpxfile.getParentFile();
- String[] filenameList = dir.list();
- for (String filename : filenameList) {
- if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) {
- continue;
- }
- String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX)
- + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX);
- if (gpxFileName.equals(expectedGpxFileName)) {
- return filename;
- }
- }
- return null;
- }
-
- protected void importFinished() {
- if (importFinishedHandler != null) {
- importFinishedHandler.sendEmptyMessage(0);
- }
- }
-}
+package cgeo.geocaching.files; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.Settings; +import cgeo.geocaching.StaticMapsProvider; +import cgeo.geocaching.cgData; +import cgeo.geocaching.activity.IAbstractActivity; +import cgeo.geocaching.activity.Progress; +import cgeo.geocaching.enumerations.LoadFlags; +import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.Log; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.ContentResolver; +import android.content.Context; +import android.content.Intent; +import android.content.res.Resources; +import android.net.Uri; +import android.os.Handler; +import android.os.Message; + +import java.io.File; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Arrays; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.CancellationException; +import java.util.zip.ZipEntry; +import java.util.zip.ZipInputStream; + +public class GPXImporter { + static final int IMPORT_STEP_START = 0; + static final int IMPORT_STEP_READ_FILE = 1; + static final int IMPORT_STEP_READ_WPT_FILE = 2; + static final int IMPORT_STEP_STORE_STATIC_MAPS = 4; + static final int IMPORT_STEP_FINISHED = 5; + static final int IMPORT_STEP_FINISHED_WITH_ERROR = 6; + static final int IMPORT_STEP_CANCEL = 7; + static final int IMPORT_STEP_CANCELED = 8; + static final int IMPORT_STEP_STATIC_MAPS_SKIPPED = 9; + + public static final String GPX_FILE_EXTENSION = ".gpx"; + public static final String ZIP_FILE_EXTENSION = ".zip"; + public static final String WAYPOINTS_FILE_SUFFIX = "-wpts"; + public static final String WAYPOINTS_FILE_SUFFIX_AND_EXTENSION = WAYPOINTS_FILE_SUFFIX + GPX_FILE_EXTENSION; + + private static final List<String> GPX_MIME_TYPES = Arrays.asList("text/xml", "application/xml"); + private static final List<String> ZIP_MIME_TYPES = Arrays.asList("application/zip", "application/x-compressed", "application/x-zip-compressed", "application/x-zip", "application/octet-stream"); + + private Progress progress = new Progress(true); + + private Resources res; + private int listId; + private IAbstractActivity fromActivity; + private Handler importFinishedHandler; + + public GPXImporter(final IAbstractActivity fromActivity, final int listId, final Handler importFinishedHandler) { + this.listId = listId; + this.fromActivity = fromActivity; + res = ((Activity) fromActivity).getResources(); + this.importFinishedHandler = importFinishedHandler; + } + + /** + * Import GPX file. Currently supports *.gpx, *.zip (containing gpx files, e.g. PQ queries) or *.loc files. + * + * @param file + * the file to import + */ + public void importGPX(final File file) { + if (StringUtils.endsWithIgnoreCase(file.getName(), GPX_FILE_EXTENSION)) { + new ImportGpxFileThread(file, listId, importStepHandler, progressHandler).start(); + } else if (StringUtils.endsWithIgnoreCase(file.getName(), ZIP_FILE_EXTENSION)) { + new ImportGpxZipFileThread(file, listId, importStepHandler, progressHandler).start(); + } else { + new ImportLocFileThread(file, listId, importStepHandler, progressHandler).start(); + } + } + + /** + * Import GPX provided via intent of activity that instantiated this GPXImporter. + */ + public void importGPX() { + final ContentResolver contentResolver = ((Activity) fromActivity).getContentResolver(); + final Intent intent = ((Activity) fromActivity).getIntent(); + final Uri uri = intent.getData(); + + String mimeType = intent.getType(); + // if mimetype can't be determined (e.g. for emulators email app), derive it from uri file extension + // contentResolver.getType(uri) doesn't help but throws exception for emulators email app + // Permission Denial: reading com.android.email.provider.EmailProvider uri + // Google search says: there is no solution for this problem + // Gmail doesn't work at all, see #967 + if (mimeType == null) { + if (StringUtils.endsWithIgnoreCase(uri.getPath(), GPX_FILE_EXTENSION)) { + mimeType = "application/xml"; + } else { + // if we can't determine a better type, default to zip import + // emulator email sends e.g. content://com.android.email.attachmentprovider/1/1/RAW, mimetype=null + mimeType = "application/zip"; + } + } + + Log.i("importGPX: " + uri + ", mimetype=" + mimeType); + if (GPX_MIME_TYPES.contains(mimeType)) { + new ImportGpxAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); + } else if (ZIP_MIME_TYPES.contains(mimeType)) { + new ImportGpxZipAttachmentThread(uri, contentResolver, listId, importStepHandler, progressHandler).start(); + } else { + importFinished(); + } + } + + static abstract class ImportThread extends Thread { + final int listId; + final Handler importStepHandler; + final CancellableHandler progressHandler; + + protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + this.listId = listId; + this.importStepHandler = importStepHandler; + this.progressHandler = progressHandler; + } + + @Override + public void run() { + try { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_START)); + final Collection<Geocache> caches = doImport(); + Log.i("Imported successfully " + caches.size() + " caches."); + + final SearchResult search = new SearchResult(); + for (Geocache cache : caches) { + search.addCache(cache); + } + + if (Settings.isStoreOfflineMaps() || Settings.isStoreOfflineWpMaps()) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_STORE_STATIC_MAPS, R.string.gpx_import_store_static_maps, search.getCount())); + boolean finishedWithoutCancel = importStaticMaps(search); + // Skip last message if static maps where canceled + if (!finishedWithoutCancel) { + return; + } + } + + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED, search.getCount(), 0, search)); + } catch (IOException e) { + Log.i("Importing caches failed - error reading data: " + e.getMessage()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_io, 0, e.getLocalizedMessage())); + } catch (ParserException e) { + Log.i("Importing caches failed - data format error" + e.getMessage()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_parser, 0, e.getLocalizedMessage())); + } catch (CancellationException e) { + Log.i("Importing caches canceled"); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_CANCELED)); + } catch (Exception e) { + Log.e("Importing caches failed - unknown error: ", e); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_FINISHED_WITH_ERROR, R.string.gpx_import_error_unexpected, 0, e.getLocalizedMessage())); + } + } + + protected abstract Collection<Geocache> doImport() throws IOException, ParserException; + + private boolean importStaticMaps(final SearchResult importedCaches) { + int storedCacheMaps = 0; + for (final String geocode : importedCaches.getGeocodes()) { + final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + if (cache != null) { + Log.d("GPXImporter.ImportThread.importStaticMaps start downloadMaps for cache " + geocode); + StaticMapsProvider.downloadMaps(cache); + } else { + Log.d("GPXImporter.ImportThread.importStaticMaps: no data found for " + geocode); + } + storedCacheMaps++; + if (progressHandler.isCancelled()) { + return false; + } + progressHandler.sendMessage(progressHandler.obtainMessage(0, storedCacheMaps, 0)); + } + return true; + } + } + + static class ImportLocFileThread extends ImportThread { + private final File file; + + public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.file = file; + } + + @Override + protected Collection<Geocache> doImport() throws IOException, ParserException { + Log.i("Import LOC file: " + file.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) file.length())); + LocParser parser = new LocParser(listId); + return parser.parse(file, progressHandler); + } + } + + static abstract class ImportGpxThread extends ImportThread { + + protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + } + + @Override + protected Collection<Geocache> doImport() throws IOException, ParserException { + try { + // try to parse cache file as GPX 10 + return doImport(new GPX10Parser(listId)); + } catch (ParserException pe) { + // didn't work -> lets try GPX11 + return doImport(new GPX11Parser(listId)); + } + } + + protected abstract Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException; + } + + static class ImportGpxFileThread extends ImportGpxThread { + private final File cacheFile; + + public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.cacheFile = file; + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Log.i("Import GPX file: " + cacheFile.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length())); + Collection<Geocache> caches = parser.parse(cacheFile, progressHandler); + + final String wptsFilename = getWaypointsFileNameForGpxFile(cacheFile); + if (wptsFilename != null) { + final File wptsFile = new File(cacheFile.getParentFile(), wptsFilename); + if (wptsFile.canRead()) { + Log.i("Import GPX waypoint file: " + wptsFile.getAbsolutePath()); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) wptsFile.length())); + caches = parser.parse(wptsFile, progressHandler); + } + } + return caches; + } + } + + static class ImportGpxAttachmentThread extends ImportGpxThread { + private final Uri uri; + private ContentResolver contentResolver; + + public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.uri = uri; + this.contentResolver = contentResolver; + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Log.i("Import GPX from uri: " + uri); + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1)); + InputStream is = contentResolver.openInputStream(uri); + try { + return parser.parse(is, progressHandler); + } finally { + is.close(); + } + } + } + + static abstract class ImportGpxZipThread extends ImportGpxThread { + + protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + } + + @Override + protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + Collection<Geocache> caches = Collections.emptySet(); + // can't assume that GPX file comes before waypoint file in zip -> so we need two passes + // 1. parse GPX files + ZipInputStream zis = new ZipInputStream(getInputStream()); + try { + for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) { + if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), GPX_FILE_EXTENSION)) { + if (!StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) zipEntry.getSize())); + caches = parser.parse(new NoCloseInputStream(zis), progressHandler); + } + } else { + throw new ParserException("Imported zip is not a GPX zip file."); + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + + // 2. parse waypoint files + zis = new ZipInputStream(getInputStream()); + try { + for (ZipEntry zipEntry = zis.getNextEntry(); zipEntry != null; zipEntry = zis.getNextEntry()) { + if (StringUtils.endsWithIgnoreCase(zipEntry.getName(), WAYPOINTS_FILE_SUFFIX_AND_EXTENSION)) { + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_WPT_FILE, R.string.gpx_import_loading_waypoints, (int) zipEntry.getSize())); + caches = parser.parse(new NoCloseInputStream(zis), progressHandler); + } + zis.closeEntry(); + } + } finally { + zis.close(); + } + + return caches; + } + + protected abstract InputStream getInputStream() throws IOException; + } + + static class ImportGpxZipFileThread extends ImportGpxZipThread { + private final File cacheFile; + + public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.cacheFile = file; + Log.i("Import zipped GPX: " + file); + } + + @Override + protected InputStream getInputStream() throws IOException { + return new FileInputStream(cacheFile); + } + } + + static class ImportGpxZipAttachmentThread extends ImportGpxZipThread { + private final Uri uri; + private ContentResolver contentResolver; + + public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + super(listId, importStepHandler, progressHandler); + this.uri = uri; + this.contentResolver = contentResolver; + Log.i("Import zipped GPX from uri: " + uri); + } + + @Override + protected InputStream getInputStream() throws IOException { + return contentResolver.openInputStream(uri); + } + } + + final private CancellableHandler progressHandler = new CancellableHandler() { + @Override + public void handleRegularMessage(Message msg) { + progress.setProgress(msg.arg1); + } + }; + + final private Handler importStepHandler = new Handler() { + private boolean showProgressAfterCancel = false; + + @Override + public void handleMessage(Message msg) { + switch (msg.what) { + case IMPORT_STEP_START: + Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL); + progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_reading_file), res.getString(R.string.gpx_import_loading_caches), ProgressDialog.STYLE_HORIZONTAL, cancelMessage); + break; + + case IMPORT_STEP_READ_FILE: + case IMPORT_STEP_READ_WPT_FILE: + progress.setMessage(res.getString(msg.arg1)); + progress.setMaxProgressAndReset(msg.arg2); + break; + + case IMPORT_STEP_STORE_STATIC_MAPS: + progress.dismiss(); + Message skipMessage = importStepHandler.obtainMessage(IMPORT_STEP_STATIC_MAPS_SKIPPED, msg.arg2, 0); + progress.show((Context) fromActivity, res.getString(R.string.gpx_import_title_static_maps), res.getString(R.string.gpx_import_store_static_maps), ProgressDialog.STYLE_HORIZONTAL, skipMessage); + progress.setMaxProgressAndReset(msg.arg2); + break; + + case IMPORT_STEP_STATIC_MAPS_SKIPPED: + progress.dismiss(); + progressHandler.cancel(); + StringBuilder bufferSkipped = new StringBuilder(20); + bufferSkipped.append(res.getString(R.string.gpx_import_static_maps_skipped)).append(", ").append(msg.arg1).append(' ').append(res.getString(R.string.gpx_import_caches_imported)); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), bufferSkipped.toString()); + importFinished(); + break; + + case IMPORT_STEP_FINISHED: + progress.dismiss(); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_imported), msg.arg1 + " " + res.getString(R.string.gpx_import_caches_imported)); + importFinished(); + break; + + case IMPORT_STEP_FINISHED_WITH_ERROR: + progress.dismiss(); + fromActivity.helpDialog(res.getString(R.string.gpx_import_title_caches_import_failed), res.getString(msg.arg1) + "\n\n" + msg.obj); + importFinished(); + break; + + case IMPORT_STEP_CANCEL: + progress.dismiss(); + progressHandler.cancel(); + break; + + case IMPORT_STEP_CANCELED: + StringBuilder bufferCanceled = new StringBuilder(20); + bufferCanceled.append(res.getString(R.string.gpx_import_canceled)); + if (showProgressAfterCancel) { + bufferCanceled.append(", ").append(progress.getProgress()).append(' ').append(res.getString(R.string.gpx_import_caches_imported)); + } + fromActivity.showShortToast(bufferCanceled.toString()); + importFinished(); + break; + + default: + break; + } + } + }; + + /** + * @param gpxfile + * the gpx file + * @return the expected file name of the waypoints file + */ + static String getWaypointsFileNameForGpxFile(final File gpxfile) { + if (gpxfile == null || !gpxfile.canRead()) { + return null; + } + final String gpxFileName = gpxfile.getName(); + File dir = gpxfile.getParentFile(); + String[] filenameList = dir.list(); + for (String filename : filenameList) { + if (!StringUtils.containsIgnoreCase(filename, WAYPOINTS_FILE_SUFFIX)) { + continue; + } + String expectedGpxFileName = StringUtils.substringBeforeLast(filename, WAYPOINTS_FILE_SUFFIX) + + StringUtils.substringAfterLast(filename, WAYPOINTS_FILE_SUFFIX); + if (gpxFileName.equals(expectedGpxFileName)) { + return filename; + } + } + return null; + } + + protected void importFinished() { + if (importFinishedHandler != null) { + importFinishedHandler.sendEmptyMessage(0); + } + } +} diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index 5647d14..96c90cc 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -190,6 +190,9 @@ public abstract class GPXParser extends FileParser { // map GPX-Attribute-Id to baseName public static String getBaseName(final int id) { + if (id < 0) { + return null; + } // get String out of array if (CACHE_ATTRIBUTES.length <= id) { return null; diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java index 4b6f382..cadcf49 100644 --- a/main/src/cgeo/geocaching/filter/AttributeFilter.java +++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java @@ -1,16 +1,16 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import cgeo.geocaching.cgData; import cgeo.geocaching.cgeoapplication; import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; -import org.apache.commons.lang3.StringUtils; - import android.content.res.Resources; import java.util.EnumSet; +import java.util.LinkedList; +import java.util.List; class AttributeFilter extends AbstractFilter { @@ -24,13 +24,7 @@ class AttributeFilter extends AbstractFilter { private static String getName(final String attribute, final Resources res, final String packageName) { // dynamically search for a translation of the attribute final int id = res.getIdentifier(attribute, "string", packageName); - if (id > 0) { - final String translated = res.getString(id); - if (StringUtils.isNotBlank(translated)) { - return translated; - } - } - return attribute; + return id > 0 ? res.getString(id) : attribute; } @Override @@ -45,14 +39,13 @@ class AttributeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final String packageName = cgeoapplication.getInstance().getBaseContext().getPackageName(); final Resources res = cgeoapplication.getInstance().getResources(); - final String[] ids = res.getStringArray(R.array.attribute_ids); - final IFilter[] filters = new IFilter[ids.length]; - for (int i = 0; i < ids.length; i++) { - filters[i] = new AttributeFilter(getName("attribute_" + ids[i], res, packageName), ids[i]); + final List<IFilter> filters = new LinkedList<IFilter>(); + for (final String id: res.getStringArray(R.array.attribute_ids)) { + filters.add(new AttributeFilter(getName("attribute_" + id, res, packageName), id)); } return filters; } diff --git a/main/src/cgeo/geocaching/filter/DifficultyFilter.java b/main/src/cgeo/geocaching/filter/DifficultyFilter.java index c0ec61a..8099a51 100644 --- a/main/src/cgeo/geocaching/filter/DifficultyFilter.java +++ b/main/src/cgeo/geocaching/filter/DifficultyFilter.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import java.util.ArrayList; +import java.util.List; class DifficultyFilter extends AbstractRangeFilter { @@ -19,12 +20,12 @@ class DifficultyFilter extends AbstractRangeFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final ArrayList<IFilter> filters = new ArrayList<IFilter>(5); for (int difficulty = 1; difficulty <= 5; difficulty++) { filters.add(new DifficultyFilter(difficulty)); } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/FilterUserInterface.java b/main/src/cgeo/geocaching/filter/FilterUserInterface.java index be63a08..a1d42cc 100644 --- a/main/src/cgeo/geocaching/filter/FilterUserInterface.java +++ b/main/src/cgeo/geocaching/filter/FilterUserInterface.java @@ -16,6 +16,7 @@ import android.widget.ArrayAdapter; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; public final class FilterUserInterface { @@ -101,9 +102,9 @@ public final class FilterUserInterface { } private void selectFromFactory(final IFilterFactory factory, final String menuTitle, final RunnableWithArgument<IFilter> runAfterwards) { - final IFilter[] filters = factory.getFilters(); - if (filters.length == 1) { - runAfterwards.run(filters[0]); + final List<IFilter> filters = Collections.unmodifiableList(factory.getFilters()); + if (filters.size() == 1) { + runAfterwards.run(filters.get(0)); return; } @@ -114,7 +115,7 @@ public final class FilterUserInterface { builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int item) { - runAfterwards.run(filters[item]); + runAfterwards.run(filters.get(item)); } }); diff --git a/main/src/cgeo/geocaching/filter/IFilterFactory.java b/main/src/cgeo/geocaching/filter/IFilterFactory.java index 3491fd7..e750639 100644 --- a/main/src/cgeo/geocaching/filter/IFilterFactory.java +++ b/main/src/cgeo/geocaching/filter/IFilterFactory.java @@ -1,5 +1,7 @@ package cgeo.geocaching.filter; +import java.util.List; + interface IFilterFactory { - public IFilter[] getFilters(); + public List<? extends IFilter> getFilters(); } diff --git a/main/src/cgeo/geocaching/filter/ModifiedFilter.java b/main/src/cgeo/geocaching/filter/ModifiedFilter.java index f3e57de..74befda 100644 --- a/main/src/cgeo/geocaching/filter/ModifiedFilter.java +++ b/main/src/cgeo/geocaching/filter/ModifiedFilter.java @@ -4,6 +4,9 @@ import cgeo.geocaching.Geocache; import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; +import java.util.Collections; +import java.util.List; + class ModifiedFilter extends AbstractFilter implements IFilterFactory { public ModifiedFilter() { @@ -17,7 +20,7 @@ class ModifiedFilter extends AbstractFilter implements IFilterFactory { } @Override - public IFilter[] getFilters() { - return new IFilter[] { this }; + public List<ModifiedFilter> getFilters() { + return Collections.singletonList(this); } } diff --git a/main/src/cgeo/geocaching/filter/OriginFilter.java b/main/src/cgeo/geocaching/filter/OriginFilter.java index a880092..bd4e41e 100644 --- a/main/src/cgeo/geocaching/filter/OriginFilter.java +++ b/main/src/cgeo/geocaching/filter/OriginFilter.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.IConnector; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; public class OriginFilter extends AbstractFilter { @@ -25,7 +26,7 @@ public class OriginFilter extends AbstractFilter { public static final class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<OriginFilter> getFilters() { final ArrayList<OriginFilter> filters = new ArrayList<OriginFilter>(); for (IConnector connector : ConnectorFactory.getConnectors()) { filters.add(new OriginFilter(connector)); @@ -40,7 +41,7 @@ public class OriginFilter extends AbstractFilter { } }); - return filters.toArray(new OriginFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/SizeFilter.java b/main/src/cgeo/geocaching/filter/SizeFilter.java index 7a34c83..8ddc475 100644 --- a/main/src/cgeo/geocaching/filter/SizeFilter.java +++ b/main/src/cgeo/geocaching/filter/SizeFilter.java @@ -3,7 +3,8 @@ package cgeo.geocaching.filter; import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheSize; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; class SizeFilter extends AbstractFilter { private final CacheSize cacheSize; @@ -26,15 +27,15 @@ class SizeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final CacheSize[] cacheSizes = CacheSize.values(); - final ArrayList<SizeFilter> filters = new ArrayList<SizeFilter>(); + final List<IFilter> filters = new LinkedList<IFilter>(); for (CacheSize cacheSize : cacheSizes) { if (cacheSize != CacheSize.UNKNOWN) { filters.add(new SizeFilter(cacheSize)); } } - return filters.toArray(new SizeFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index 0df47c1..ae3fac2 100644 --- a/main/src/cgeo/geocaching/filter/StateFilter.java +++ b/main/src/cgeo/geocaching/filter/StateFilter.java @@ -9,6 +9,7 @@ import android.content.res.Resources; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; +import java.util.List; abstract class StateFilter extends AbstractFilter { @@ -89,8 +90,8 @@ abstract class StateFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { - final ArrayList<StateFilter> filters = new ArrayList<StateFilter>(); + public List<StateFilter> getFilters() { + final List<StateFilter> filters = new ArrayList<StateFilter>(6); filters.add(new StateFoundFilter()); filters.add(new StateArchivedFilter()); filters.add(new StateDisabledFilter()); @@ -106,7 +107,7 @@ abstract class StateFilter extends AbstractFilter { } }); - return filters.toArray(new StateFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/TerrainFilter.java b/main/src/cgeo/geocaching/filter/TerrainFilter.java index f7703d5..87372c6 100644 --- a/main/src/cgeo/geocaching/filter/TerrainFilter.java +++ b/main/src/cgeo/geocaching/filter/TerrainFilter.java @@ -1,10 +1,10 @@ package cgeo.geocaching.filter; - -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import java.util.ArrayList; +import java.util.List; class TerrainFilter extends AbstractRangeFilter { @@ -19,12 +19,12 @@ class TerrainFilter extends AbstractRangeFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final ArrayList<IFilter> filters = new ArrayList<IFilter>(5); for (int terrain = 1; terrain <= 5; terrain++) { filters.add(new TerrainFilter(terrain)); } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/filter/TrackablesFilter.java b/main/src/cgeo/geocaching/filter/TrackablesFilter.java index 3225daa..5eff8a7 100644 --- a/main/src/cgeo/geocaching/filter/TrackablesFilter.java +++ b/main/src/cgeo/geocaching/filter/TrackablesFilter.java @@ -1,9 +1,12 @@ package cgeo.geocaching.filter; -import cgeo.geocaching.R; import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; import cgeo.geocaching.cgeoapplication; +import java.util.Collections; +import java.util.List; + class TrackablesFilter extends AbstractFilter implements IFilterFactory { public TrackablesFilter() { super(cgeoapplication.getInstance().getString(R.string.caches_filter_track)); @@ -15,8 +18,8 @@ class TrackablesFilter extends AbstractFilter implements IFilterFactory { } @Override - public IFilter[] getFilters() { - return new IFilter[] { this }; + public List<TrackablesFilter> getFilters() { + return Collections.singletonList(this); } } diff --git a/main/src/cgeo/geocaching/filter/TypeFilter.java b/main/src/cgeo/geocaching/filter/TypeFilter.java index eeab552..ea0ccff 100644 --- a/main/src/cgeo/geocaching/filter/TypeFilter.java +++ b/main/src/cgeo/geocaching/filter/TypeFilter.java @@ -3,7 +3,8 @@ package cgeo.geocaching.filter; import cgeo.geocaching.Geocache; import cgeo.geocaching.enumerations.CacheType; -import java.util.ArrayList; +import java.util.LinkedList; +import java.util.List; class TypeFilter extends AbstractFilter { private final CacheType cacheType; @@ -26,15 +27,15 @@ class TypeFilter extends AbstractFilter { public static class Factory implements IFilterFactory { @Override - public IFilter[] getFilters() { + public List<IFilter> getFilters() { final CacheType[] types = CacheType.values(); - final ArrayList<IFilter> filters = new ArrayList<IFilter>(types.length); + final List<IFilter> filters = new LinkedList<IFilter>(); for (CacheType cacheType : types) { if (cacheType != CacheType.ALL) { filters.add(new TypeFilter(cacheType)); } } - return filters.toArray(new IFilter[filters.size()]); + return filters; } } diff --git a/main/src/cgeo/geocaching/geopoint/Geopoint.java b/main/src/cgeo/geocaching/geopoint/Geopoint.java index c03a2bc..dbe0aa4 100644 --- a/main/src/cgeo/geocaching/geopoint/Geopoint.java +++ b/main/src/cgeo/geocaching/geopoint/Geopoint.java @@ -173,6 +173,16 @@ public final class Geopoint implements ICoordinates, Parcelable { return longitude; } + /* + * Return a waypoint which is the copy of this one rounded to the given limit. + * For example, to get a waypoint adapter to a display with 3 digits after the + * seconds decimal point, a rounding factor of 3600*1000 would be appropriate. + */ + Geopoint roundedAt(final long factor) { + return new Geopoint(((double) Math.round(latitude * factor)) / factor, + ((double) Math.round(longitude * factor)) / factor); + } + /** * Get longitude in microdegree. * diff --git a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java index ba0a4d5..67f5ebb 100644 --- a/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java +++ b/main/src/cgeo/geocaching/geopoint/GeopointFormatter.java @@ -64,36 +64,51 @@ public class GeopointFormatter { case LAT_LON_DECDEGREE_COMMA: return String.format((Locale) null, "%.6f,%.6f", latSigned, lonSigned); - case LAT_LON_DECMINUTE: + case LAT_LON_DECMINUTE: { + final Geopoint rgp = gp.roundedAt(60 * 1000); return String.format(Locale.getDefault(), "%c %02d° %06.3f · %c %03d° %06.3f", - gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw(), rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw()); + } - case LAT_LON_DECMINUTE_RAW: + case LAT_LON_DECMINUTE_RAW: { + final Geopoint rgp = gp.roundedAt(60 * 1000); return String.format((Locale) null, "%c %02d° %06.3f %c %03d° %06.3f", - gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw(), gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw(), rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw()); + } - case LAT_LON_DECSECOND: + case LAT_LON_DECSECOND: { + final Geopoint rgp = gp.roundedAt(3600 * 1000); return String.format(Locale.getDefault(), "%c %02d° %02d' %06.3f\" · %c %03d° %02d' %06.3f\"", - gp.getLatDir(), gp.getLatDeg(), gp.getLatMin(), gp.getLatSecRaw(), - gp.getLonDir(), gp.getLonDeg(), gp.getLonMin(), gp.getLonSecRaw()); + rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMin(), rgp.getLatSecRaw(), + rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMin(), rgp.getLonSecRaw()); + } case LAT_DECDEGREE_RAW: return String.format((Locale) null, "%.6f", latSigned); - case LAT_DECMINUTE: - return String.format(Locale.getDefault(), "%c %02d° %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); + case LAT_DECMINUTE: { + final Geopoint rgp = gp.roundedAt(60 * 1000); + return String.format(Locale.getDefault(), "%c %02d° %06.3f", rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw()); + } - case LAT_DECMINUTE_RAW: - return String.format(Locale.getDefault(), "%c %02d %06.3f", gp.getLatDir(), gp.getLatDeg(), gp.getLatMinRaw()); + case LAT_DECMINUTE_RAW: { + final Geopoint rgp = gp.roundedAt(60 * 1000); + return String.format(Locale.getDefault(), "%c %02d %06.3f", rgp.getLatDir(), rgp.getLatDeg(), rgp.getLatMinRaw()); + } case LON_DECDEGREE_RAW: return String.format((Locale) null, "%.6f", lonSigned); - case LON_DECMINUTE: - return String.format(Locale.getDefault(), "%c %03d° %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); + case LON_DECMINUTE: { + final Geopoint rgp = gp.roundedAt(60 * 1000); + return String.format(Locale.getDefault(), "%c %03d° %06.3f", rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw()); + } + + case LON_DECMINUTE_RAW: { + final Geopoint rgp = gp.roundedAt(60 * 1000); + return String.format(Locale.getDefault(), "%c %03d %06.3f", rgp.getLonDir(), rgp.getLonDeg(), rgp.getLonMinRaw()); + } - case LON_DECMINUTE_RAW: - return String.format(Locale.getDefault(), "%c %03d %06.3f", gp.getLonDir(), gp.getLonDeg(), gp.getLonMinRaw()); default: throw new IllegalArgumentException(); } diff --git a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java index 46912c5..ece5c2f 100644 --- a/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java +++ b/main/src/cgeo/geocaching/loaders/AbstractSearchLoader.java @@ -19,7 +19,8 @@ public abstract class AbstractSearchLoader extends AsyncTaskLoader<SearchResult> USERNAME, OWNER, MAP, - REMOVE_FROM_HISTORY; + REMOVE_FROM_HISTORY, + NEXT_PAGE; } private Handler recaptchaHandler = null; diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index f2e455c..21c4984 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -382,9 +382,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto if (extras != null) { mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE); isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false); - searchIntent = (SearchResult) extras.getParcelable(EXTRAS_SEARCH); + searchIntent = extras.getParcelable(EXTRAS_SEARCH); geocodeIntent = extras.getString(EXTRAS_GEOCODE); - coordsIntent = (Geopoint) extras.getParcelable(EXTRAS_COORDS); + coordsIntent = extras.getParcelable(EXTRAS_COORDS); waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE)); mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE); mapTitle = extras.getString(EXTRAS_MAP_TITLE); @@ -750,9 +750,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto @Override public void onClick(DialogInterface dialog, int newItem) { - if (newItem == selectedItem) { - // no change - } else { + if (newItem != selectedItem) { // Adjust index because of <default> selection if (newItem > 0) { Settings.setCustomRenderThemeFile(themeFiles[newItem - 1].getPath()); diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index d3b2718..e3d3f77 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -15,7 +15,10 @@ import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; +import org.apache.commons.lang3.StringUtils; + public final class Twitter { + private static final String HASH_PREFIX_WITH_BLANK = " #"; public static final int MAX_TWEET_SIZE = 140; public static void postTweet(final cgeoapplication app, final String status, final Geopoint coords) { @@ -47,12 +50,13 @@ public final class Twitter { } } - public static String appendHashTag(final String status, final String tag) { - String result = status; - if (result.length() + 2 + tag.length() <= 140) { - result += " #" + tag; + public static void appendHashTag(final StringBuilder status, final String tag) { + if (status.length() + HASH_PREFIX_WITH_BLANK.length() + tag.length() <= MAX_TWEET_SIZE) { + final String tagWithPrefix = HASH_PREFIX_WITH_BLANK + tag; + if (status.indexOf(tagWithPrefix, 0) == -1) { + status.append(tagWithPrefix); + } } - return result; } public static void postTweetCache(String geocode) { @@ -63,39 +67,39 @@ public final class Twitter { return; } final Geocache cache = cgData.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) - 1) + '…'; + postTweet(cgeoapplication.getInstance(), getStatusMessage(cache), null); + } + + static String getStatusMessage(Geocache cache) { + String name = cache.getName(); + if (name.length() > 100) { + name = name.substring(0, 100) + '…'; } - status = "I found " + name + " (" + url + ")"; - status = appendHashTag(status, "cgeo"); - status = appendHashTag(status, "geocaching"); - } + final String url = StringUtils.defaultString(cache.getUrl()); + return fillTemplate(Settings.getCacheTwitterMessage(), name, url); + } - postTweet(cgeoapplication.getInstance(), status, null); + private static String fillTemplate(String template, String name, final String url) { + String result = StringUtils.replace(template, "[NAME]", name); + result = StringUtils.replace(result, "[URL]", url); + StringBuilder builder = new StringBuilder(result); + appendHashTag(builder, "cgeo"); + appendHashTag(builder, "geocaching"); + return builder.toString(); } public static void postTweetTrackable(String geocode) { final Trackable trackable = cgData.loadTrackable(geocode); + postTweet(cgeoapplication.getInstance(), getStatusMessage(trackable), null); + } + + static String getStatusMessage(Trackable trackable) { String name = trackable.getName(); if (name.length() > 82) { name = name.substring(0, 81) + '…'; } - 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); + String url = StringUtils.defaultString(trackable.getUrl()); + String status = Settings.getTrackableTwitterMessage(); + return fillTemplate(status, name, url); } } diff --git a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java index 4ba88ae..f10e13a 100644 --- a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java +++ b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java @@ -16,6 +16,12 @@ public class DecryptTextClickListener implements View.OnClickListener { try {
final TextView logView = (TextView) view;
+
+ // do not run the click listener if a link was clicked
+ if (logView.getSelectionStart() != -1 || logView.getSelectionEnd() != -1) {
+ return;
+ }
+
CharSequence text = logView.getText();
if (text instanceof Spannable) {
Spannable span = (Spannable) text;
diff --git a/main/src/cgeo/geocaching/ui/EditNoteDialog.java b/main/src/cgeo/geocaching/ui/EditNoteDialog.java new file mode 100644 index 0000000..23e57f2 --- /dev/null +++ b/main/src/cgeo/geocaching/ui/EditNoteDialog.java @@ -0,0 +1,65 @@ +package cgeo.geocaching.ui; + +import cgeo.geocaching.R; +import cgeo.geocaching.R.string; + +import android.os.Bundle; +import android.support.v4.app.DialogFragment; +import android.view.KeyEvent; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.WindowManager.LayoutParams; +import android.view.inputmethod.EditorInfo; +import android.widget.EditText; +import android.widget.TextView; +import android.widget.TextView.OnEditorActionListener; + +public class EditNoteDialog extends DialogFragment implements OnEditorActionListener { + + public interface EditNoteDialogListener { + void onFinishEditNoteDialog(final String inputText); + } + + private EditText mEditText; + private String initialNote; + + public EditNoteDialog() { + // Empty constructor required for DialogFragment + } + + public EditNoteDialog(final String initialNote) { + this.initialNote = initialNote; + } + + @Override + public View onCreateView(final LayoutInflater inflater, final ViewGroup container, + final Bundle savedInstanceState) { + View view = inflater.inflate(R.layout.fragment_edit_note, container); + mEditText = (EditText) view.findViewById(R.id.note); + if (initialNote != null) { + mEditText.setText(initialNote); + initialNote = null; + } + getDialog().setTitle(string.cache_personal_note); + mEditText.requestFocus(); + getDialog().getWindow().setSoftInputMode( + LayoutParams.SOFT_INPUT_STATE_VISIBLE); + mEditText.setOnEditorActionListener(this); + + return view; + } + + @Override + public boolean onEditorAction(final TextView v, final int actionId, final KeyEvent event) { + if (EditorInfo.IME_ACTION_DONE == actionId) { + final EditNoteDialogListener activity = (EditNoteDialogListener) getActivity(); + activity.onFinishEditNoteDialog(mEditText.getText().toString()); + dismiss(); + return true; + } + return false; + } + + +} diff --git a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java b/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java deleted file mode 100644 index 4db69e5..0000000 --- a/main/src/cgeo/geocaching/ui/dialog/EditorDialog.java +++ /dev/null @@ -1,60 +0,0 @@ -package cgeo.geocaching.ui.dialog; - -import cgeo.geocaching.CacheDetailActivity; -import cgeo.geocaching.R; -import cgeo.geocaching.activity.ActivityMixin; - -import android.app.Dialog; -import android.os.Bundle; -import android.view.View; -import android.view.ViewGroup.LayoutParams; -import android.view.Window; -import android.widget.Button; -import android.widget.EditText; - -public class EditorDialog extends Dialog { - - private CharSequence editorText; - private EditorUpdate editorUpdate; - - public EditorDialog(CacheDetailActivity cacheDetailActivity, CharSequence editable) { - super(cacheDetailActivity, ActivityMixin.getTheme()); - this.editorText = editable; - } - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - requestWindowFeature(Window.FEATURE_NO_TITLE); - setContentView(R.layout.editor); - - final EditText editText = (EditText) findViewById(R.id.editorEditText); - editText.setText(editorText); - - final Button buttonSave = (Button) findViewById(R.id.editorSave); - buttonSave.setOnClickListener(new View.OnClickListener() { - @Override - public void onClick(View v) { - editorUpdate.update(editText.getEditableText()); - EditorDialog.this.hide(); - } - }); - } - - public interface EditorUpdate { - public void update(CharSequence editorText); - } - - public void setOnEditorUpdate(EditorUpdate editorUpdate) { - this.editorUpdate = editorUpdate; - - } - - @Override - public void show() { - super.show(); - getWindow().setLayout(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT); - } - -} diff --git a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java index b23fa9d..7526d92 100644 --- a/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java +++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java @@ -8,8 +8,12 @@ import android.os.AsyncTask; /** * AsyncTask which automatically shows a progress dialog. Use it like the {@code AsyncTask} class, but leave away the - * middle template parameter. - * + * middle template parameter. Override {@link #doInBackgroundInternal(Object[])} and related methods. + * <p> + * If no style is given, the progress dialog uses "determinate" style with known maximum. The progress maximum is + * automatically derived from the number of {@code Params} given to the task in {@link #execute(Object...)}. + * </p> + * * @param <Params> * @param <Result> */ @@ -17,64 +21,119 @@ public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Pa private final Progress progress = new Progress(); private final Activity activity; - private final int maxProgress; private final String progressTitle; private final String progressMessage; + private boolean indeterminate = false; /** - * Creates an AsyncTask with progress dialog, where the maximum is set to the given maxProgress. - * + * Creates an AsyncTask with progress dialog. + * * @param activity - * @param maxProgress * @param progressTitle * @param progressMessage */ - public AsyncTaskWithProgress(final Activity activity, final int maxProgress, final String progressTitle, final String progressMessage) { + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) { + this(activity, progressTitle, progressMessage, false); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle) { + this(activity, progressTitle, null); + } + + /** + * Creates an AsyncTask with progress dialog. + * + * @param activity + * @param progressTitle + * @param progressMessage + */ + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage, boolean indeterminate) { this.activity = activity; - this.maxProgress = maxProgress; this.progressTitle = progressTitle; this.progressMessage = progressMessage; + this.indeterminate = indeterminate; } /** - * Creates an AsyncTask with progress dialog, where the maximum is set to indeterminate. - * + * Creates an AsyncTask with progress dialog. + * * @param activity * @param progressTitle - * @param progressMessage */ - public AsyncTaskWithProgress(final Activity activity, final String progressTitle, final String progressMessage) { - this(activity, 0, progressTitle, progressMessage); + public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) { + this(activity, progressTitle, null, indeterminate); } @Override - protected void onPreExecute() { + protected final void onPreExecute() { if (null != activity) { - if (maxProgress <= 0) { + if (indeterminate) { progress.show(activity, progressTitle, progressMessage, true, null); } else { progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null); } - progress.setMaxProgressAndReset(maxProgress); } + onPreExecuteInternal(); + } + + /** + * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}. + */ + protected void onPreExecuteInternal() { + // empty by default } @Override - protected void onPostExecute(Result result) { + protected final void onPostExecute(Result result) { + onPostExecuteInternal(result); if (null != activity) { progress.dismiss(); } } + /** + * This method should typically be overridden by sub classes instead of {@link #onPostExecute(Object)}. + * + * @param result + */ + protected void onPostExecuteInternal(Result result) { + // empty by default + } + @Override - protected void onProgressUpdate(Integer... status) { - if (null != activity) { - progress.setProgress(status[0]); + protected final void onProgressUpdate(Integer... status) { + final int progressValue = status[0]; + if (null != activity && progressValue >= 0) { + progress.setProgress(progressValue); } + onProgressUpdateInternal(progressValue); + } + + /** + * This method should by overridden by sub classes instead of {@link #onProgressUpdate(Integer...)}. + */ + protected void onProgressUpdateInternal(@SuppressWarnings("unused") int progress) { + // empty by default } protected void setMessage(final String message) { progress.setMessage(message); } + + @Override + protected final Result doInBackground(Params... params) { + if (params != null) { + progress.setMaxProgressAndReset(params.length); + } + return doInBackgroundInternal(params); + } + + protected abstract Result doInBackgroundInternal(Params[] params); } diff --git a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java index cf1df46..ff7ddff 100644 --- a/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java +++ b/tests/src/cgeo/geocaching/connector/gc/GCParserTest.java @@ -7,6 +7,7 @@ import cgeo.geocaching.Settings; import cgeo.geocaching.Waypoint; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.test.AbstractResourceInstrumentationTestCase; import cgeo.geocaching.test.R; @@ -22,6 +23,7 @@ import android.os.Handler; import android.test.suitebuilder.annotation.MediumTest; import java.util.ArrayList; +import java.util.List; public class GCParserTest extends AbstractResourceInstrumentationTestCase { @@ -187,6 +189,22 @@ public class GCParserTest extends AbstractResourceInstrumentationTestCase { assertEquals(13, cache.getWaypoints().size()); } + public static void testNoteParsingWaypointTypes() { + final Geocache cache = new Geocache(); + cache.setWaypoints(new ArrayList<Waypoint>(), false); + cache.setPersonalNote("\"Parking area at PARKING=N 50° 40.666E 006° 58.222\n" + + "My calculated final coordinates: FINAL=N 50° 40.777E 006° 58.111\n" + + "Get some ice cream at N 50° 40.555E 006° 58.000\""); + + cache.parseWaypointsFromNote(); + final List<Waypoint> waypoints = cache.getWaypoints(); + + assertEquals(3, waypoints.size()); + assertEquals(WaypointType.PARKING, waypoints.get(0).getWaypointType()); + assertEquals(WaypointType.FINAL, waypoints.get(1).getWaypointType()); + assertEquals(WaypointType.WAYPOINT, waypoints.get(2).getWaypointType()); + } + private Geocache parseCache(int resourceId) { final String page = getFileContent(resourceId); final SearchResult result = GCParser.parseCacheFromText(page, null); diff --git a/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java b/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java index b12823a..3563991 100644 --- a/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java +++ b/tests/src/cgeo/geocaching/connector/oc/OCXMLTest.java @@ -111,13 +111,13 @@ public class OCXMLTest extends CGeoTestCase { } public static void testRemoveMarkup() { - assertEquals("", OC11XMLParser.stripMarkup("")); - assertEquals("Test", OC11XMLParser.stripMarkup("Test")); - assertEquals("<b>bold and others not removed</b>", OC11XMLParser.stripMarkup("<b>bold and others not removed</b>")); - assertEquals("unnecessary paragraph", OC11XMLParser.stripMarkup("<p>unnecessary paragraph</p>")); - assertEquals("unnecessary span", OC11XMLParser.stripMarkup("<span>unnecessary span</span>")); - assertEquals("nested", OC11XMLParser.stripMarkup("<span><span>nested</span></span>")); - assertEquals("mixed", OC11XMLParser.stripMarkup("<span> <p> mixed </p> </span>")); - assertEquals("<p>not</p><p>removable</p>", OC11XMLParser.stripMarkup("<p>not</p><p>removable</p>")); + assertEquals("", OC11XMLParser.stripEmptyText("")); + assertEquals("Test", OC11XMLParser.stripEmptyText("Test")); + assertEquals("<b>bold and others not removed</b>", OC11XMLParser.stripEmptyText("<b>bold and others not removed</b>")); + assertEquals("unnecessary paragraph", OC11XMLParser.stripEmptyText("<p>unnecessary paragraph</p>")); + assertEquals("unnecessary span", OC11XMLParser.stripEmptyText("<span>unnecessary span</span>")); + assertEquals("nested", OC11XMLParser.stripEmptyText("<span><span>nested</span></span>")); + assertEquals("mixed", OC11XMLParser.stripEmptyText("<span> <p> mixed </p> </span>")); + assertEquals("<p>not</p><p>removable</p>", OC11XMLParser.stripEmptyText("<p>not</p><p>removable</p>")); } } diff --git a/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java b/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java index b3ff056..ebe4cf6 100644 --- a/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java +++ b/tests/src/cgeo/geocaching/filter/DifficultyFilterTest.java @@ -19,6 +19,6 @@ public class DifficultyFilterTest extends CGeoTestCase { } public static void testAllFilters() { - assertTrue(new DifficultyFilter.Factory().getFilters().length == 5); // difficulty ranges from 1 to 5 + assertTrue(new DifficultyFilter.Factory().getFilters().size() == 5); // difficulty ranges from 1 to 5 } } diff --git a/tests/src/cgeo/geocaching/filter/SizeFilterTest.java b/tests/src/cgeo/geocaching/filter/SizeFilterTest.java index 2c6552b..8b909f3 100644 --- a/tests/src/cgeo/geocaching/filter/SizeFilterTest.java +++ b/tests/src/cgeo/geocaching/filter/SizeFilterTest.java @@ -33,7 +33,7 @@ public class SizeFilterTest extends CGeoTestCase { public static void testGetAllFilters() { final int expectedSizes = CacheSize.values().length - 1; // hide "UNKNOWN" - assertEquals(expectedSizes, new SizeFilter.Factory().getFilters().length); + assertEquals(expectedSizes, new SizeFilter.Factory().getFilters().size()); } public void testFilter() { diff --git a/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java b/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java index d64f4d9..234af7e 100644 --- a/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java +++ b/tests/src/cgeo/geocaching/filter/TerrainFilterTest.java @@ -19,6 +19,6 @@ public class TerrainFilterTest extends CGeoTestCase { } public static void testAllFilters() { - assertTrue(new TerrainFilter.Factory().getFilters().length == 5); // terrain ranges from 1 to 5 + assertTrue(new TerrainFilter.Factory().getFilters().size() == 5); // terrain ranges from 1 to 5 } } diff --git a/tests/src/cgeo/geocaching/filter/TypeFilterTest.java b/tests/src/cgeo/geocaching/filter/TypeFilterTest.java index 3a43b33..e813052 100644 --- a/tests/src/cgeo/geocaching/filter/TypeFilterTest.java +++ b/tests/src/cgeo/geocaching/filter/TypeFilterTest.java @@ -46,7 +46,7 @@ public class TypeFilterTest extends CGeoTestCase { public static void testGetAllFilters() { final int expectedEntries = CacheType.values().length - 1; // hide "all" - assertEquals(expectedEntries, new TypeFilter.Factory().getFilters().length); + assertEquals(expectedEntries, new TypeFilter.Factory().getFilters().size()); } } diff --git a/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java b/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java index c0cea01..a1be25d 100644 --- a/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java +++ b/tests/src/cgeo/geocaching/geopoint/GeoPointFormatterTest.java @@ -6,6 +6,17 @@ import android.test.AndroidTestCase; public class GeoPointFormatterTest extends AndroidTestCase { + public static void testConfluence() { + // From issue #2624: coordinate is wrong near to a confluence point + final Geopoint point = new Geopoint(49.9999999999999, 5.0); + final String format = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECDEGREE_COMMA, point); + assertEquals("50.000000,5.000000", format); + final String formatMinute = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECMINUTE_RAW, point); + assertEquals("N 50° 00.000 E 005° 00.000", formatMinute); + final String formatSecond = GeopointFormatter.format(GeopointFormatter.Format.LAT_LON_DECSECOND, point).replaceAll(",", "."); + assertEquals(formatSecond, "N 50° 00' 00.000\"" + Formatter.SEPARATOR + "E 005° 00' 00.000\"", formatSecond); + } + public static void testFormat() { // taken from GC30R6G final Geopoint point = new Geopoint("N 51° 21.104 E 010° 15.369"); diff --git a/tests/src/cgeo/geocaching/twitter/TwitterTest.java b/tests/src/cgeo/geocaching/twitter/TwitterTest.java new file mode 100644 index 0000000..a27e57a --- /dev/null +++ b/tests/src/cgeo/geocaching/twitter/TwitterTest.java @@ -0,0 +1,37 @@ +package cgeo.geocaching.twitter; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.Settings; +import cgeo.geocaching.Trackable; + +import junit.framework.TestCase; + +public class TwitterTest extends TestCase { + + public static void testTrackableMessage() { + Trackable tb = new Trackable(); + tb.setName("Travel bug"); + tb.setGeocode("TB1234"); + assertEquals("I touched Travel bug (http://www.geocaching.com//track/details.aspx?tracker=TB1234)! #cgeo #geocaching", Twitter.getStatusMessage(tb)); + } + + public static void testCacheMessage() { + Geocache cache = new Geocache(); + cache.setGeocode("GC1234"); + cache.setName("TwitterTest"); + assertEquals("I found TwitterTest (http://coord.info/GC1234) #cgeo #geocaching", Twitter.getStatusMessage(cache)); + } + + public static void testAvoidDuplicateTags() { + String oldMessage = Settings.getCacheTwitterMessage(); + try { + Geocache cache = new Geocache(); + cache.setGeocode("GC1234"); + cache.setName("TwitterTest"); + Settings.setCacheTwitterMessage("[NAME] #cgeo"); + assertEquals("TwitterTest #cgeo #geocaching", Twitter.getStatusMessage(cache)); + } finally { + Settings.setCacheTwitterMessage(oldMessage); + } + } +} |
