aboutsummaryrefslogtreecommitdiffstats
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/cgeo/geocaching/AbstractPopupActivity.java10
-rw-r--r--main/src/cgeo/geocaching/CacheDetailActivity.java62
-rw-r--r--main/src/cgeo/geocaching/Geocache.java47
-rw-r--r--main/src/cgeo/geocaching/ImageSelectActivity.java66
-rw-r--r--main/src/cgeo/geocaching/ImagesActivity.java18
-rw-r--r--main/src/cgeo/geocaching/Intents.java4
-rw-r--r--main/src/cgeo/geocaching/SearchActivity.java14
-rw-r--r--main/src/cgeo/geocaching/Settings.java15
-rw-r--r--main/src/cgeo/geocaching/StaticMapsProvider.java27
-rw-r--r--main/src/cgeo/geocaching/StoredList.java27
-rw-r--r--main/src/cgeo/geocaching/VisitCacheActivity.java154
-rw-r--r--main/src/cgeo/geocaching/Waypoint.java2
-rw-r--r--main/src/cgeo/geocaching/WaypointPopup.java13
-rw-r--r--main/src/cgeo/geocaching/cgData.java3
-rw-r--r--main/src/cgeo/geocaching/cgeo.java3
-rw-r--r--main/src/cgeo/geocaching/cgeocaches.java78
-rw-r--r--main/src/cgeo/geocaching/cgeonavigate.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java2
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java21
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OC11XMLParser.java26
-rw-r--r--main/src/cgeo/geocaching/export/AbstractExport.java7
-rw-r--r--main/src/cgeo/geocaching/export/FieldnoteExport.java46
-rw-r--r--main/src/cgeo/geocaching/export/GpxExport.java194
-rw-r--r--main/src/cgeo/geocaching/files/GPXImporter.java920
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java3
-rw-r--r--main/src/cgeo/geocaching/files/SimpleDirChooser.java2
-rw-r--r--main/src/cgeo/geocaching/filter/AttributeFilter.java23
-rw-r--r--main/src/cgeo/geocaching/filter/DifficultyFilter.java5
-rw-r--r--main/src/cgeo/geocaching/filter/FilterUserInterface.java9
-rw-r--r--main/src/cgeo/geocaching/filter/IFilterFactory.java4
-rw-r--r--main/src/cgeo/geocaching/filter/ModifiedFilter.java7
-rw-r--r--main/src/cgeo/geocaching/filter/OriginFilter.java5
-rw-r--r--main/src/cgeo/geocaching/filter/SizeFilter.java9
-rw-r--r--main/src/cgeo/geocaching/filter/StateFilter.java31
-rw-r--r--main/src/cgeo/geocaching/filter/TerrainFilter.java8
-rw-r--r--main/src/cgeo/geocaching/filter/TrackablesFilter.java9
-rw-r--r--main/src/cgeo/geocaching/filter/TypeFilter.java9
-rw-r--r--main/src/cgeo/geocaching/gcvote/GCVote.java7
-rw-r--r--main/src/cgeo/geocaching/geopoint/Geopoint.java10
-rw-r--r--main/src/cgeo/geocaching/geopoint/GeopointFormatter.java45
-rw-r--r--main/src/cgeo/geocaching/maps/CGeoMap.java8
-rw-r--r--main/src/cgeo/geocaching/network/HtmlImage.java6
-rw-r--r--main/src/cgeo/geocaching/twitter/Twitter.java68
-rw-r--r--main/src/cgeo/geocaching/ui/CacheDetailsCreator.java21
-rw-r--r--main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java38
-rw-r--r--main/src/cgeo/geocaching/ui/DecryptTextClickListener.java6
-rw-r--r--main/src/cgeo/geocaching/ui/EditNoteDialog.java65
-rw-r--r--main/src/cgeo/geocaching/ui/ImagesList.java33
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/EditorDialog.java60
-rw-r--r--main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java139
-rw-r--r--main/src/cgeo/org/kxml2/io/KXmlSerializer.java605
51 files changed, 1974 insertions, 1022 deletions
diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java
index f903d00..44acfc5 100644
--- a/main/src/cgeo/geocaching/AbstractPopupActivity.java
+++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java
@@ -67,12 +67,22 @@ public abstract class AbstractPopupActivity extends AbstractActivity {
cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords())));
cacheDistance.bringToFront();
}
+ onUpdateGeoData(geo);
} catch (Exception e) {
Log.w("Failed to UpdateLocation location.");
}
}
};
+ /**
+ * Callback to run when new location information is available.
+ * This may be overriden by deriving classes. The default implementation does nothing.
+ *
+ * @param geo the new data
+ */
+ public void onUpdateGeoData(final IGeoData geo) {
+ }
+
protected AbstractPopupActivity(String helpTopic, int layout) {
super(helpTopic);
this.layout = layout;
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java
index 43ce65f..c4287ed 100644
--- a/main/src/cgeo/geocaching/CacheDetailActivity.java
+++ b/main/src/cgeo/geocaching/CacheDetailActivity.java
@@ -21,13 +21,14 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.ui.AbstractCachingPageViewCreator;
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;
@@ -65,6 +66,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;
@@ -112,7 +114,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;
@@ -139,6 +142,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) {
@@ -899,7 +904,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
return;
}
imagesList = new ImagesList(this, cache.getGeocode());
- imagesList.loadImages(imageView, cache.getImages(), ImageType.AllImages, false);
+ imagesList.loadImages(imageView, cache.getImages(), false);
}
public static void startActivity(final Context context, final String geocode) {
@@ -1202,23 +1207,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
// cache coordinates
if (cache.getCoords() != null) {
TextView valueView = details.add(R.string.cache_coordinates, cache.getCoords().toString());
- valueView.setOnClickListener(new View.OnClickListener() {
- private int position = 0;
- private GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] {
- GeopointFormatter.Format.LAT_LON_DECMINUTE,
- GeopointFormatter.Format.LAT_LON_DECSECOND,
- GeopointFormatter.Format.LAT_LON_DECDEGREE
- };
-
- // rotate coordinate formats on click
- @Override
- public void onClick(View view) {
- position = (position + 1) % availableFormats.length;
-
- final TextView valueView = (TextView) view.findViewById(R.id.value);
- valueView.setText(cache.getCoords().format(availableFormats[position]));
- }
- });
+ valueView.setOnClickListener(new CoordinatesFormatSwitcher(cache.getCoords()));
registerForContextMenu(valueView);
}
@@ -1794,16 +1783,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 {
@@ -1866,13 +1855,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);
@@ -1884,6 +1870,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;
@@ -2095,7 +2086,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 {
@@ -2206,6 +2197,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
// coordinates
if (null != wpt.getCoords()) {
final TextView coordinatesView = (TextView) waypointView.findViewById(R.id.coordinates);
+ coordinatesView.setOnClickListener(new CoordinatesFormatSwitcher(wpt.getCoords()));
coordinatesView.setText(wpt.getCoords().toString());
coordinatesView.setVisibility(View.VISIBLE);
}
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index 9a8325d..000435e 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())) {
@@ -1372,10 +1373,14 @@ public class Geocache implements ICache, IWaypoint {
while (matcher.find()) {
try {
final Geopoint point = new Geopoint(note.substring(matcher.start()));
- // 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)) {
+ // Coords must have non zero latitude and longitude, at least one part shall have fractional degrees,
+ // and there must exist no waypoint with the same coordinates already.
+ if (point.getLatitudeE6() != 0 && point.getLongitudeE6() != 0 &&
+ ((point.getLatitudeE6() % 1000) != 0 || (point.getLongitudeE6() % 1000) != 0) &&
+ !hasIdenticalWaypoint(point)) {
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 +1397,34 @@ 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;
+ }
+
+ private boolean hasIdenticalWaypoint(final Geopoint point) {
+ for (final Waypoint waypoint: waypoints) {
+ if (waypoint.getCoords().equals(point)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
/*
* For working in the debugger
* (non-Javadoc)
diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java
index 08622a9..347cd86 100644
--- a/main/src/cgeo/geocaching/ImageSelectActivity.java
+++ b/main/src/cgeo/geocaching/ImageSelectActivity.java
@@ -159,6 +159,10 @@ public class ImageSelectActivity extends AbstractActivity {
Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
imageUri = getOutputImageFileUri(); // create a file to save the image
+ if (imageUri == null) {
+ showFailure();
+ return;
+ }
intent.putExtra(MediaStore.EXTRA_OUTPUT, imageUri); // set the image file name
// start the image capture Intent
@@ -180,37 +184,61 @@ public class ImageSelectActivity extends AbstractActivity {
return;
}
- if (resultCode == RESULT_OK) {
- if (data != null) {
- Uri selectedImage = data.getData();
- String[] filePathColumn = { MediaColumns.DATA };
+ if (resultCode != RESULT_OK) {
+ // Image capture failed, advise user
+ showFailure();
+ return;
+ }
- Cursor cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+ if (data != null) {
+ Uri selectedImage = data.getData();
+ String[] filePathColumn = { MediaColumns.DATA };
+
+ Cursor cursor = null;
+ try {
+ cursor = getContentResolver().query(selectedImage, filePathColumn, null, null, null);
+ if (cursor == null) {
+ showFailure();
+ return;
+ }
cursor.moveToFirst();
int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
String filePath = cursor.getString(columnIndex);
+ if (StringUtils.isBlank(filePath)) {
+ showFailure();
+ return;
+ }
imageUri = Uri.parse(filePath);
- cursor.close();
-
- Log.d("SELECT IMAGE data = " + data.toString());
- } else {
- Log.d("SELECT IMAGE data is null");
+ } catch (Exception e) {
+ Log.e("ImageSelectActivity.onActivityResult", e);
+ showFailure();
+ } finally {
+ if (cursor != null) {
+ cursor.close();
+ }
}
- if (requestCode == SELECT_NEW_IMAGE) {
- showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri);
- }
+ Log.d("SELECT IMAGE data = " + data.toString());
} else {
- // Image capture failed, advise user
- showToast(getResources().getString(R.string.err_aquire_image_failed));
- return;
+ Log.d("SELECT IMAGE data is null");
+ }
+
+ if (requestCode == SELECT_NEW_IMAGE) {
+ showToast(getResources().getString(R.string.info_stored_image) + "\n" + imageUri);
}
loadImagePreview();
}
+ private void showFailure() {
+ showToast(getResources().getString(R.string.err_aquire_image_failed));
+ }
+
private void loadImagePreview() {
+ if (imageUri == null) {
+ return;
+ }
if (!new File(imageUri.getPath()).exists()) {
Log.i("Image does not exist");
return;
@@ -225,7 +253,11 @@ public class ImageSelectActivity extends AbstractActivity {
}
private static Uri getOutputImageFileUri() {
- return Uri.fromFile(getOutputImageFile());
+ final File file = getOutputImageFile();
+ if (file == null) {
+ return null;
+ }
+ return Uri.fromFile(file);
}
/** Create a File for saving an image or video */
diff --git a/main/src/cgeo/geocaching/ImagesActivity.java b/main/src/cgeo/geocaching/ImagesActivity.java
index 24f699e..07ff734 100644
--- a/main/src/cgeo/geocaching/ImagesActivity.java
+++ b/main/src/cgeo/geocaching/ImagesActivity.java
@@ -19,10 +19,6 @@ import java.util.List;
public class ImagesActivity extends AbstractActivity {
- private static final String EXTRAS_IMAGES = "images";
- private static final String EXTRAS_TYPE = "type";
- private static final String EXTRAS_GEOCODE = "geocode";
-
private boolean offline;
private ArrayList<Image> imageNames;
private ImagesList imagesList;
@@ -37,8 +33,8 @@ public class ImagesActivity extends AbstractActivity {
String geocode = null;
if (extras != null) {
- geocode = extras.getString(EXTRAS_GEOCODE);
- imgType = (ImageType) extras.getSerializable(EXTRAS_TYPE);
+ geocode = extras.getString(Intents.EXTRA_GEOCODE);
+ imgType = (ImageType) extras.getSerializable(Intents.EXTRA_TYPE);
}
if (extras == null || geocode == null) {
@@ -54,7 +50,7 @@ public class ImagesActivity extends AbstractActivity {
imagesList = new ImagesList(this, geocode);
- imageNames = extras.getParcelableArrayList(EXTRAS_IMAGES);
+ imageNames = extras.getParcelableArrayList(Intents.EXTRA_IMAGES);
if (CollectionUtils.isEmpty(imageNames)) {
showToast(res.getString(R.string.warn_load_images));
finish();
@@ -67,7 +63,7 @@ public class ImagesActivity extends AbstractActivity {
@Override
public void onStart() {
super.onStart();
- imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, imgType, offline);
+ imagesList.loadImages(findViewById(R.id.spoiler_list), imageNames, offline);
}
@Override
@@ -85,12 +81,12 @@ public class ImagesActivity extends AbstractActivity {
final Intent logImgIntent = new Intent(fromActivity, ImagesActivity.class);
// if resuming our app within this activity, finish it and return to the cache activity
logImgIntent.addFlags(Intent.FLAG_ACTIVITY_CLEAR_WHEN_TASK_RESET)
- .putExtra(EXTRAS_GEOCODE, geocode)
- .putExtra(EXTRAS_TYPE, imageType);
+ .putExtra(Intents.EXTRA_GEOCODE, geocode)
+ .putExtra(Intents.EXTRA_TYPE, imageType);
// avoid forcing the array list as parameter type
final ArrayList<Image> arrayList = new ArrayList<Image>(logImages);
- logImgIntent.putParcelableArrayListExtra(EXTRAS_IMAGES, arrayList);
+ logImgIntent.putParcelableArrayListExtra(Intents.EXTRA_IMAGES, arrayList);
fromActivity.startActivity(logImgIntent);
}
diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java
index 7f0a004..a700451 100644
--- a/main/src/cgeo/geocaching/Intents.java
+++ b/main/src/cgeo/geocaching/Intents.java
@@ -9,10 +9,11 @@ public class Intents {
private static final String PREFIX = "cgeo.geocaching.intent.extra.";
public static final String EXTRA_ADDRESS = PREFIX + "address";
- public static final String EXTRAS_COORDS = PREFIX + "coords";
+ public static final String EXTRA_COORDS = PREFIX + "coords";
public static final String EXTRA_COUNT = PREFIX + "count";
public static final String EXTRA_GEOCODE = PREFIX + "geocode";
public static final String EXTRA_GUID = PREFIX + "guid";
+ public static final String EXTRA_IMAGES = PREFIX + "images";
public static final String EXTRA_ID = PREFIX + "id";
public static final String EXTRA_KEYWORD = PREFIX + "keyword";
public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search";
@@ -23,6 +24,7 @@ public class Intents {
public static final String EXTRA_SEARCH = PREFIX + "search";
public static final String EXTRA_START_DIR = PREFIX + "start_dir";
public static final String EXTRA_TRACKING_CODE = PREFIX + "tracking_code";
+ public static final String EXTRA_TYPE = PREFIX + "type";
public static final String EXTRA_USERNAME = PREFIX + "username";
public static final String EXTRA_WAYPOINT_ID = PREFIX + "waypoint_id";
public static final String EXTRA_CACHELIST = PREFIX + "cache_list";
diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java
index 231555c..6fdff5a 100644
--- a/main/src/cgeo/geocaching/SearchActivity.java
+++ b/main/src/cgeo/geocaching/SearchActivity.java
@@ -291,7 +291,7 @@ public class SearchActivity extends AbstractActivity {
}
} else {
try {
- cgeocaches.startActivityCoordinates(this, new Geopoint(latText, lonText));
+ cgeocaches.startActivityCoordinates(this, new Geopoint(StringUtils.trim(latText), StringUtils.trim(lonText)));
} catch (Geopoint.ParseException e) {
showToast(res.getString(e.resource));
}
@@ -315,7 +315,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- cgeocaches.startActivityKeyword(this, keyText);
+ cgeocaches.startActivityKeyword(this, StringUtils.trim(keyText));
}
private class FindByAddressListener implements View.OnClickListener {
@@ -334,7 +334,7 @@ public class SearchActivity extends AbstractActivity {
}
final Intent addressesIntent = new Intent(this, AddressListActivity.class);
- addressesIntent.putExtra(Intents.EXTRA_KEYWORD, addText);
+ addressesIntent.putExtra(Intents.EXTRA_KEYWORD, StringUtils.trim(addText));
startActivity(addressesIntent);
}
@@ -354,7 +354,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- cgeocaches.startActivityUserName(this, usernameText);
+ cgeocaches.startActivityUserName(this, StringUtils.trim(usernameText));
}
private void findByOwnerFn() {
@@ -369,7 +369,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- cgeocaches.startActivityOwner(this, usernameText);
+ cgeocaches.startActivityOwner(this, StringUtils.trim(usernameText));
}
private class FindByGeocodeListener implements View.OnClickListener {
@@ -388,7 +388,7 @@ public class SearchActivity extends AbstractActivity {
return;
}
- CacheDetailActivity.startActivity(this, geocodeText);
+ CacheDetailActivity.startActivity(this, StringUtils.trim(geocodeText));
}
private class FindTrackableListener implements View.OnClickListener {
@@ -408,7 +408,7 @@ public class SearchActivity extends AbstractActivity {
}
final Intent trackablesIntent = new Intent(this, TrackableActivity.class);
- trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, trackableText.toUpperCase(Locale.US));
+ trackablesIntent.putExtra(Intents.EXTRA_GEOCODE, StringUtils.trim(trackableText).toUpperCase(Locale.US));
startActivity(trackablesIntent);
}
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 cd88071..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;
@@ -81,10 +80,6 @@ public class StaticMapsProvider {
}
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;
}
@@ -96,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);
}
@@ -167,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();
@@ -183,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,
@@ -245,7 +231,7 @@ public class StaticMapsProvider {
/**
* Check if at least one map file exists for the given cache.
- *
+ *
* @param cache
* @return <code>true</code> if at least one map file exists; <code>false</code> otherwise
*/
@@ -268,7 +254,7 @@ public class StaticMapsProvider {
/**
* Checks if at least one map file exists for the given geocode and waypoint ID.
- *
+ *
* @param geocode
* @param waypoint
* @return <code>true</code> if at least one map file exists; <code>false</code> otherwise
@@ -287,7 +273,7 @@ public class StaticMapsProvider {
/**
* Checks if all map files exist for the given geocode and waypoint ID.
- *
+ *
* @param geocode
* @param waypoint
* @return <code>true</code> if all map files exist; <code>false</code> otherwise
@@ -326,5 +312,4 @@ public class StaticMapsProvider {
}
return null;
}
-
}
diff --git a/main/src/cgeo/geocaching/StoredList.java b/main/src/cgeo/geocaching/StoredList.java
index 5a6f132..778c112 100644
--- a/main/src/cgeo/geocaching/StoredList.java
+++ b/main/src/cgeo/geocaching/StoredList.java
@@ -12,7 +12,10 @@ import android.content.res.Resources;
import android.view.View;
import android.widget.EditText;
+import java.text.Collator;
import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
import java.util.List;
public class StoredList {
@@ -69,7 +72,7 @@ public class StoredList {
}
public void promptForListSelection(final int titleId, final RunnableWithArgument<Integer> runAfterwards, final boolean onlyMoveTargets, final int exceptListId) {
- final List<StoredList> lists = cgData.getLists();
+ final List<StoredList> lists = getSortedLists();
if (lists == null) {
return;
@@ -115,6 +118,19 @@ public class StoredList {
builder.create().show();
}
+ private static List<StoredList> getSortedLists() {
+ final Collator collator = Collator.getInstance();
+ final List<StoredList> lists = cgData.getLists();
+ Collections.sort(lists, new Comparator<StoredList>() {
+
+ @Override
+ public int compare(StoredList lhs, StoredList rhs) {
+ return collator.compare(lhs.getTitle(), rhs.getTitle());
+ }
+ });
+ return lists;
+ }
+
public void promptForListCreation(final RunnableWithArgument<Integer> runAfterwards) {
handleListNameInput("", R.string.list_dialog_create_title, R.string.list_dialog_create, new RunnableWithArgument<String>() {
@@ -176,4 +192,13 @@ public class StoredList {
});
}
}
+
+ /**
+ * Get the list title. This method is not public by intention to make clients use the {@link UserInterface} class.
+ *
+ * @return
+ */
+ protected String getTitle() {
+ return title;
+ }
}
diff --git a/main/src/cgeo/geocaching/VisitCacheActivity.java b/main/src/cgeo/geocaching/VisitCacheActivity.java
index d95f6df..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,81 +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 class Poster extends AsyncTaskWithProgress<String, StatusCode> {
- private final Handler handler;
- private final String log;
-
- 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;
-
- try {
-
- final ImmutablePair<StatusCode, String> logResult = GCParser.postLog(geocode, cacheid, viewstates, typeSelected,
- date.get(Calendar.YEAR), (date.get(Calendar.MONTH) + 1), date.get(Calendar.DATE),
- log, trackables);
-
- result = logResult.left;
-
- if (logResult.left == StatusCode.NO_ERROR) {
- final LogEntry logNow = new LogEntry(date, typeSelected, log);
-
- cache.getLogs().add(0, logNow);
-
- if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) {
- cache.setFound(true);
+ 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);
+
+ if (postResult.left == StatusCode.NO_ERROR) {
+ final LogEntry logNow = new LogEntry(date, typeSelected, log);
+
+ cache.getLogs().add(0, logNow);
+
+ 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 (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;
+ }
}
- cgData.saveChangedCache(cache);
+ return postResult.left;
+ } catch (Exception e) {
+ Log.e("cgeovisit.postLogFn", e);
}
- if (logResult.left == StatusCode.NO_ERROR) {
- cgData.clearLogOffline(geocode);
- }
-
- if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isUseTwitter()
- && Settings.isTwitterLoginValid()
- && tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) {
- Twitter.postTweetCache(geocode);
- }
-
- if (logResult.left == StatusCode.NO_ERROR && typeSelected == LogType.FOUND_IT && Settings.isGCvoteLogin()) {
- GCVote.setRating(cache, rating);
- }
+ return StatusCode.LOG_POST_ERROR;
+ }
- if (logResult.left == StatusCode.NO_ERROR && StringUtils.isNotBlank(imageUri.getPath())) {
- result = GCParser.uploadLogImage(logResult.right, imageCaption, imageDescription, imageUri);
+ @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));
}
-
- return result;
- } catch (Exception e) {
- Log.e("cgeovisit.postLogFn", e);
}
-
- return StatusCode.LOG_POST_ERROR;
}
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/WaypointPopup.java b/main/src/cgeo/geocaching/WaypointPopup.java
index 766d43d..43a758f 100644
--- a/main/src/cgeo/geocaching/WaypointPopup.java
+++ b/main/src/cgeo/geocaching/WaypointPopup.java
@@ -2,6 +2,7 @@ package cgeo.geocaching;
import cgeo.geocaching.apps.cache.navi.NavigationAppFactory;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.geopoint.Units;
import cgeo.geocaching.ui.CacheDetailsCreator;
import cgeo.geocaching.utils.Log;
@@ -19,6 +20,7 @@ import android.widget.TextView;
public class WaypointPopup extends AbstractPopupActivity {
private int waypointId = 0;
private Waypoint waypoint = null;
+ private TextView waypointDistance = null;
public WaypointPopup() {
super("c:geo-waypoint-info", R.layout.waypoint_popup);
@@ -35,6 +37,14 @@ public class WaypointPopup extends AbstractPopupActivity {
}
@Override
+ public void onUpdateGeoData(IGeoData geo) {
+ if (geo.getCoords() != null && waypoint != null && waypoint.getCoords() != null) {
+ waypointDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(waypoint.getCoords())));
+ waypointDistance.bringToFront();
+ }
+ }
+
+ @Override
protected void init() {
super.init();
waypoint = cgData.loadWaypoint(waypointId);
@@ -53,6 +63,9 @@ public class WaypointPopup extends AbstractPopupActivity {
//Waypoint geocode
details.add(R.string.cache_geocode, waypoint.getPrefix() + waypoint.getGeocode().substring(2));
+ details.addDistance(waypoint, waypointDistance);
+ waypointDistance = details.getValueView();
+ details.add(R.string.waypoint_note, waypoint.getNote());
// Edit Button
final Button buttonEdit = (Button) findViewById(R.id.edit);
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 8fc61d4..e5176d5 100644
--- a/main/src/cgeo/geocaching/cgeocaches.java
+++ b/main/src/cgeo/geocaching/cgeocaches.java
@@ -39,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;
@@ -74,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;
@@ -373,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
@@ -428,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.EXTRA_COORDS);
}
else {
extras = new Bundle();
@@ -794,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()) {
@@ -804,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() {
@@ -1149,7 +1135,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();
}
@@ -1182,10 +1168,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();
}
});
@@ -1201,9 +1184,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()]));
}
/**
@@ -1388,24 +1371,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 {
@@ -1450,7 +1444,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;
}
@@ -1549,7 +1543,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));
@@ -1672,7 +1666,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
final Intent cachesIntent = new Intent(context, cgeocaches.class);
cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST);
- cachesIntent.putExtra(Intents.EXTRAS_COORDS, coordsNow);
+ cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow);
context.startActivity(cachesIntent);
}
@@ -1685,7 +1679,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
public static void startActivityAddress(final Context context, final Geopoint coords, final String address) {
final Intent addressIntent = new Intent(context, cgeocaches.class);
addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS);
- addressIntent.putExtra(Intents.EXTRAS_COORDS, coords);
+ addressIntent.putExtra(Intents.EXTRA_COORDS, coords);
addressIntent.putExtra(Intents.EXTRA_ADDRESS, address);
context.startActivity(addressIntent);
}
@@ -1696,7 +1690,7 @@ public class cgeocaches extends AbstractListActivity implements FilteredActivity
}
final Intent cachesIntent = new Intent(context, cgeocaches.class);
cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE);
- cachesIntent.putExtra(Intents.EXTRAS_COORDS, coords);
+ cachesIntent.putExtra(Intents.EXTRA_COORDS, coords);
context.startActivity(cachesIntent);
}
@@ -1730,11 +1724,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];
+ AbstractSearchLoader loader = null;
switch (enumType) {
case OFFLINE:
listId = Settings.getLastList();
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 9af9953..d566b2f 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -790,7 +790,7 @@ public abstract class GCParser {
insertCacheType(params, cacheType);
final String uri = "http://www.geocaching.com/seek/nearest.aspx";
- final String fullUri = uri + "?" + addFToParams(params, false, true);
+ final String fullUri = uri + "?" + addFToParams(params, my, true);
final String page = Login.getRequestLogged(uri, addFToParams(params, my, true));
if (StringUtils.isBlank(page)) {
@@ -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,18 +1088,23 @@ 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);
}
+ /**
+ * Post a log to GC.com.
+ *
+ * @return status code of the upload and ID of the log
+ */
public static StatusCode postLogTrackable(final String tbid, final String trackingCode, final String[] viewstates,
final LogType logType, final int year, final int month, final int day, final String log) {
if (Login.isEmpty(viewstates)) {
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|&nbsp;)*</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/AbstractExport.java b/main/src/cgeo/geocaching/export/AbstractExport.java
index 72ea544..e4ba5f0 100644
--- a/main/src/cgeo/geocaching/export/AbstractExport.java
+++ b/main/src/cgeo/geocaching/export/AbstractExport.java
@@ -1,5 +1,6 @@
package cgeo.geocaching.export;
+import cgeo.geocaching.R;
import cgeo.geocaching.cgeoapplication;
abstract class AbstractExport implements Export {
@@ -27,7 +28,7 @@ abstract class AbstractExport implements Export {
/**
* Generates a localized string from a resource id.
- *
+ *
* @param resourceId
* the resource id of the string
* @param params
@@ -43,4 +44,8 @@ abstract class AbstractExport implements Export {
// used in the array adapter of the dialog showing the exports
return getName();
}
+
+ protected String getProgressTitle() {
+ return getString(R.string.export) + ": " + getName();
+ }
}
diff --git a/main/src/cgeo/geocaching/export/FieldnoteExport.java b/main/src/cgeo/geocaching/export/FieldnoteExport.java
index 5e1805a..2900781 100644
--- a/main/src/cgeo/geocaching/export/FieldnoteExport.java
+++ b/main/src/cgeo/geocaching/export/FieldnoteExport.java
@@ -5,11 +5,11 @@ import cgeo.geocaching.LogEntry;
import cgeo.geocaching.R;
import cgeo.geocaching.cgData;
import cgeo.geocaching.activity.ActivityMixin;
-import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.connector.gc.Login;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.IOUtils;
import cgeo.geocaching.utils.Log;
@@ -20,7 +20,6 @@ import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
import android.content.DialogInterface;
-import android.os.AsyncTask;
import android.os.Environment;
import android.view.ContextThemeWrapper;
import android.view.View;
@@ -57,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
@@ -91,32 +91,27 @@ 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 AsyncTask<Void, Integer, 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;
- private final Progress progress = new Progress();
private File exportFile;
private static final int STATUS_UPLOAD = -1;
/**
- * Instantiates and configurates the task for exporting field notes.
+ * 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
@@ -124,22 +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) {
- this.caches = caches;
+ public ExportTask(final Activity activity, final boolean upload, final boolean onlyNew) {
+ super(activity, getProgressTitle(), getString(R.string.export_fieldnotes_creating));
this.activity = activity;
this.upload = upload;
this.onlyNew = onlyNew;
}
@Override
- protected void onPreExecute() {
- if (null != activity) {
- progress.show(activity, getString(R.string.export) + ": " + getName(), getString(R.string.export_fieldnotes_creating), true, null);
- }
- }
-
- @Override
- protected Boolean doInBackground(Void... params) {
+ protected Boolean doInBackgroundInternal(Geocache[] caches) {
final StringBuilder fieldNoteBuffer = new StringBuilder();
try {
int i = 0;
@@ -227,10 +215,8 @@ class FieldnoteExport extends AbstractExport {
}
@Override
- protected void onPostExecute(Boolean result) {
+ protected void onPostExecuteInternal(Boolean result) {
if (null != activity) {
- progress.dismiss();
-
if (result) {
// if (onlyNew) {
// // update last export time in settings when doing it ourself (currently we use the date check from gc.com)
@@ -248,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]) {
- progress.setMessage(getString(R.string.export_fieldnotes_uploading));
+ if (STATUS_UPLOAD == status) {
+ setMessage(getString(R.string.export_fieldnotes_uploading));
} else {
- progress.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 28ca646..3e58276 100644
--- a/main/src/cgeo/geocaching/export/GpxExport.java
+++ b/main/src/cgeo/geocaching/export/GpxExport.java
@@ -6,14 +6,16 @@ import cgeo.geocaching.R;
import cgeo.geocaching.Settings;
import cgeo.geocaching.Waypoint;
import cgeo.geocaching.cgData;
+import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.activity.ActivityMixin;
-import cgeo.geocaching.activity.Progress;
import cgeo.geocaching.enumerations.CacheAttribute;
import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.BaseUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.XmlUtils;
+import cgeo.org.kxml2.io.KXmlSerializer;
import org.apache.commons.lang3.StringUtils;
import org.xmlpull.v1.XmlSerializer;
@@ -21,13 +23,10 @@ import org.xmlpull.v1.XmlSerializer;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.Dialog;
-import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.net.Uri;
-import android.os.AsyncTask;
import android.os.Environment;
-import android.util.Xml;
import android.view.ContextThemeWrapper;
import android.view.View;
import android.widget.CheckBox;
@@ -38,9 +37,12 @@ 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.List;
import java.util.Locale;
+import java.util.Set;
class GpxExport extends AbstractExport {
private static final SimpleDateFormat dateFormatZ = new SimpleDateFormat("yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US);
@@ -48,24 +50,30 @@ class GpxExport extends AbstractExport {
public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0";
public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0";
+ /**
+ * During the export, only this number of geocaches is fully loaded into memory.
+ */
+ public static final int CACHES_PER_BATCH = 100;
+
protected GpxExport() {
super(getString(R.string.export_gpx));
}
@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
@@ -91,46 +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 AsyncTask<Void, Integer, File> {
- private final List<Geocache> caches;
+ 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()]);
+ }
+
+ protected class ExportTask extends AsyncTaskWithProgress<String, File> {
private final Activity activity;
- private final Progress progress = new Progress();
+ 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) {
- this.caches = caches;
+ public ExportTask(final Activity activity) {
+ super(activity, getProgressTitle());
this.activity = activity;
}
@Override
- protected void onPreExecute() {
- if (null != activity) {
- progress.show(activity, null, getString(R.string.export) + ": " + getName(), ProgressDialog.STYLE_HORIZONTAL, null);
- progress.setMaxProgressAndReset(caches.size());
- }
- }
-
- @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;
@@ -138,7 +147,7 @@ class GpxExport extends AbstractExport {
final File exportLocation = new File(Settings.getGpxExportDir());
exportLocation.mkdirs();
- final XmlSerializer gpx = Xml.newSerializer();
+ final XmlSerializer gpx = new KXmlSerializer();
writer = new FileWriter(exportFile);
gpx.setOutput(writer);
@@ -153,68 +162,17 @@ class GpxExport extends AbstractExport {
PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " +
PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd");
- for (int i = 0; i < caches.size(); i++) {
- final Geocache cache = cgData.loadCache(caches.get(i).getGeocode(), LoadFlags.LOAD_ALL_DB_ONLY);
-
- gpx.startTag(PREFIX_GPX, "wpt");
- gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude()));
- gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude()));
-
- final Date hiddenDate = cache.getHiddenDate();
- if (hiddenDate != null) {
- XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate));
- }
-
- XmlUtils.multipleTexts(gpx, PREFIX_GPX,
- "name", cache.getGeocode(),
- "desc", cache.getName(),
- "url", cache.getUrl(),
- "urlname", cache.getName(),
- "sym", cache.isFound() ? "Geocache Found" : "Geocache",
- "type", "Geocache|" + cache.getType().pattern);
-
- gpx.startTag(PREFIX_GROUNDSPEAK, "cache");
- gpx.attribute("", "id", cache.getCacheId());
- gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False");
- gpx.attribute("", "archives", cache.isArchived() ? "True" : "False");
-
- XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK,
- "name", cache.getName(),
- "placed_by", cache.getOwnerDisplayName(),
- "owner", cache.getOwnerUserId(),
- "type", cache.getType().pattern,
- "container", cache.getSize().id,
- "difficulty", Float.toString(cache.getDifficulty()),
- "terrain", Float.toString(cache.getTerrain()),
- "country", cache.getLocation(),
- "state", "",
- "encoded_hints", cache.getHint());
-
- writeAttributes(gpx, cache);
-
- gpx.startTag(PREFIX_GROUNDSPEAK, "short_description");
- gpx.attribute("", "html", BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False");
- gpx.text(cache.getShortDescription());
- gpx.endTag(PREFIX_GROUNDSPEAK, "short_description");
-
- gpx.startTag(PREFIX_GROUNDSPEAK, "long_description");
- gpx.attribute("", "html", BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False");
- gpx.text(cache.getDescription());
- gpx.endTag(PREFIX_GROUNDSPEAK, "long_description");
-
- writeLogs(gpx, cache);
-
- gpx.endTag(PREFIX_GROUNDSPEAK, "cache");
- gpx.endTag(PREFIX_GPX, "wpt");
-
- writeWaypoints(gpx, cache);
-
- publishProgress(i + 1);
+ // 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()) {
+ final List<String> batch = allGeocodes.subList(0, Math.min(CACHES_PER_BATCH, allGeocodes.size()));
+ exportBatch(gpx, batch);
+ batch.clear();
}
gpx.endTag(PREFIX_GPX, "gpx");
gpx.endDocument();
- } catch (final IOException e) {
+ } catch (final Exception e) {
Log.e("GpxExport.ExportTask export", e);
if (writer != null) {
@@ -235,6 +193,67 @@ class GpxExport extends AbstractExport {
return exportFile;
}
+ 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");
+ gpx.attribute("", "lat", Double.toString(cache.getCoords().getLatitude()));
+ gpx.attribute("", "lon", Double.toString(cache.getCoords().getLongitude()));
+
+ final Date hiddenDate = cache.getHiddenDate();
+ if (hiddenDate != null) {
+ XmlUtils.simpleText(gpx, PREFIX_GPX, "time", dateFormatZ.format(hiddenDate));
+ }
+
+ XmlUtils.multipleTexts(gpx, PREFIX_GPX,
+ "name", cache.getGeocode(),
+ "desc", cache.getName(),
+ "url", cache.getUrl(),
+ "urlname", cache.getName(),
+ "sym", cache.isFound() ? "Geocache Found" : "Geocache",
+ "type", "Geocache|" + cache.getType().pattern);
+
+ gpx.startTag(PREFIX_GROUNDSPEAK, "cache");
+ gpx.attribute("", "id", cache.getCacheId());
+ gpx.attribute("", "available", !cache.isDisabled() ? "True" : "False");
+ gpx.attribute("", "archives", cache.isArchived() ? "True" : "False");
+
+ XmlUtils.multipleTexts(gpx, PREFIX_GROUNDSPEAK,
+ "name", cache.getName(),
+ "placed_by", cache.getOwnerDisplayName(),
+ "owner", cache.getOwnerUserId(),
+ "type", cache.getType().pattern,
+ "container", cache.getSize().id,
+ "difficulty", Float.toString(cache.getDifficulty()),
+ "terrain", Float.toString(cache.getTerrain()),
+ "country", cache.getLocation(),
+ "state", "",
+ "encoded_hints", cache.getHint());
+
+ writeAttributes(gpx, cache);
+
+ gpx.startTag(PREFIX_GROUNDSPEAK, "short_description");
+ gpx.attribute("", "html", BaseUtils.containsHtml(cache.getShortDescription()) ? "True" : "False");
+ gpx.text(cache.getShortDescription());
+ gpx.endTag(PREFIX_GROUNDSPEAK, "short_description");
+
+ gpx.startTag(PREFIX_GROUNDSPEAK, "long_description");
+ gpx.attribute("", "html", BaseUtils.containsHtml(cache.getDescription()) ? "True" : "False");
+ gpx.text(cache.getDescription());
+ gpx.endTag(PREFIX_GROUNDSPEAK, "long_description");
+
+ writeLogs(gpx, cache);
+
+ gpx.endTag(PREFIX_GROUNDSPEAK, "cache");
+ gpx.endTag(PREFIX_GPX, "wpt");
+
+ writeWaypoints(gpx, cache);
+
+ countExported++;
+ publishProgress(countExported);
+ }
+ }
+
private void writeWaypoints(final XmlSerializer gpx, final Geocache cache) throws IOException {
List<Waypoint> waypoints = cache.getWaypoints();
List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size());
@@ -345,9 +364,8 @@ class GpxExport extends AbstractExport {
}
@Override
- protected void onPostExecute(final File exportFile) {
+ protected void onPostExecuteInternal(final File exportFile) {
if (null != activity) {
- progress.dismiss();
if (exportFile != null) {
ActivityMixin.showToast(activity, getName() + ' ' + getString(R.string.export_exportedto) + ": " + exportFile.toString());
if (Settings.getShareAfterExport()) {
@@ -363,11 +381,5 @@ class GpxExport extends AbstractExport {
}
}
- @Override
- protected void onProgressUpdate(Integer... status) {
- if (null != activity) {
- progress.setProgress(status[0]);
- }
- }
}
}
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/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
index 7520e2e..844287c 100644
--- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java
+++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
@@ -238,7 +238,7 @@ public class SimpleDirChooser extends ListActivity {
@Override
public boolean accept(File dir, String filename) {
File file = new File(dir, filename);
- return file.isDirectory();
+ return file.isDirectory() && file.canWrite();
}
}
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..e18128d 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 {
@@ -86,17 +87,41 @@ abstract class StateFilter extends AbstractFilter {
}
}
+ static class StateStoredFilter extends StateFilter {
+ public StateStoredFilter() {
+ super(res.getString(R.string.cache_status_stored));
+ }
+
+ @Override
+ public boolean accepts(Geocache cache) {
+ return cache.isOffline();
+ }
+ }
+
+ static class StateNotStoredFilter extends StateFilter {
+ public StateNotStoredFilter() {
+ super(res.getString(R.string.cache_status_not_stored));
+ }
+
+ @Override
+ public boolean accepts(Geocache cache) {
+ return !cache.isOffline();
+ }
+ }
+
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());
filters.add(new StatePremiumFilter());
filters.add(new StateNonPremiumFilter());
filters.add(new StateOfflineLogFilter());
+ filters.add(new StateStoredFilter());
+ filters.add(new StateNotStoredFilter());
Collections.sort(filters, new Comparator<StateFilter>() {
@@ -106,7 +131,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/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java
index a053f31..f6cfb84 100644
--- a/main/src/cgeo/geocaching/gcvote/GCVote.java
+++ b/main/src/cgeo/geocaching/gcvote/GCVote.java
@@ -173,12 +173,15 @@ public final class GCVote {
/**
* Transmit user vote to gcvote.com
- *
+ *
* @param cache
* @param vote
- * @return
+ * @return {@code true} if the rating was submitted successfully
*/
public static boolean setRating(Geocache cache, double vote) {
+ if (!Settings.isGCvoteLogin()) {
+ return false;
+ }
if (!cache.supportsGCVote()) {
return false;
}
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/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/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java
index a409750..b08d36d 100644
--- a/main/src/cgeo/geocaching/network/HtmlImage.java
+++ b/main/src/cgeo/geocaching/network/HtmlImage.java
@@ -193,7 +193,11 @@ public class HtmlImage implements Html.ImageGetter {
if (file.exists()) {
if (listId >= StoredList.STANDARD_LIST_ID || file.lastModified() > (new Date().getTime() - (24 * 60 * 60 * 1000)) || forceKeep) {
setSampleSize(file);
- return BitmapFactory.decodeFile(file.getPath(), bfOptions);
+ final Bitmap image = BitmapFactory.decodeFile(file.getPath(), bfOptions);
+ if (image == null) {
+ Log.e("Cannot decode bitmap from " + file.getPath());
+ }
+ return image;
}
}
return null;
diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java
index f30830e..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,49 +50,56 @@ 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) {
- 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;
+ if (!Settings.isUseTwitter()) {
+ return;
}
- 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) + '…';
- }
- status = "I found " + name + " (" + url + ")";
- status = appendHashTag(status, "cgeo");
- status = appendHashTag(status, "geocaching");
+ if (!Settings.isTwitterLoginValid()) {
+ return;
}
+ final Geocache cache = cgData.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB);
+ postTweet(cgeoapplication.getInstance(), getStatusMessage(cache), null);
+ }
+
+ static String getStatusMessage(Geocache cache) {
+ String name = cache.getName();
+ if (name.length() > 100) {
+ name = name.substring(0, 100) + '…';
+ }
+ 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/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
index e98bd77..80f01e2 100644
--- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
+++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.ui;
import cgeo.geocaching.Geocache;
import cgeo.geocaching.R;
+import cgeo.geocaching.Waypoint;
import cgeo.geocaching.cgeoapplication;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.Units;
@@ -154,4 +155,24 @@ public final class CacheDetailsCreator {
}
add(R.string.cache_distance, text);
}
+
+ public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) {
+ Float distance = null;
+ if (wpt.getCoords() != null) {
+ final Geopoint currentCoords = cgeoapplication.getInstance().currentGeo().getCoords();
+ if (currentCoords != null) {
+ distance = currentCoords.distanceTo(wpt);
+ }
+ }
+ String text = "--";
+ if (distance != null) {
+ text = Units.getDistanceFromKilometers(distance);
+ }
+ else if (waypointDistanceView != null) {
+ // if there is already a distance in waypointDistance, use it instead of resetting to default.
+ // this prevents displaying "--" while waiting for a new position update (See bug #1468)
+ text = waypointDistanceView.getText().toString();
+ }
+ add(R.string.cache_distance, text);
+ }
}
diff --git a/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java
new file mode 100644
index 0000000..afadb33
--- /dev/null
+++ b/main/src/cgeo/geocaching/ui/CoordinatesFormatSwitcher.java
@@ -0,0 +1,38 @@
+package cgeo.geocaching.ui;
+
+import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.geopoint.GeopointFormatter;
+
+import android.view.View;
+import android.view.View.OnClickListener;
+import android.widget.TextView;
+
+/**
+ * view click listener to automatically switch different coordinate formats
+ *
+ */
+public class CoordinatesFormatSwitcher implements OnClickListener {
+
+ private static GeopointFormatter.Format[] availableFormats = new GeopointFormatter.Format[] {
+ GeopointFormatter.Format.LAT_LON_DECMINUTE,
+ GeopointFormatter.Format.LAT_LON_DECSECOND,
+ GeopointFormatter.Format.LAT_LON_DECDEGREE
+ };
+
+ private int position = 0;
+
+ private final Geopoint coordinates;
+
+ public CoordinatesFormatSwitcher(final Geopoint coordinates) {
+ this.coordinates = coordinates;
+ }
+
+ @Override
+ public void onClick(View view) {
+ position = (position + 1) % availableFormats.length;
+ TextView textView = (TextView) view;
+ // rotate coordinate formats on click
+ textView.setText(coordinates.format(availableFormats[position]));
+ }
+
+} \ No newline at end of file
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/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java
index 9464114..b2c819c 100644
--- a/main/src/cgeo/geocaching/ui/ImagesList.java
+++ b/main/src/cgeo/geocaching/ui/ImagesList.java
@@ -11,7 +11,6 @@ import cgeo.geocaching.utils.Log;
import org.apache.commons.lang3.StringUtils;
import android.app.Activity;
-import android.app.ProgressDialog;
import android.content.Intent;
import android.content.res.Resources;
import android.graphics.Bitmap;
@@ -46,16 +45,14 @@ public class ImagesList {
private Image currentImage;
public enum ImageType {
- LogImages(R.string.cache_log_images_title, R.string.cache_log_images_loading),
- SpoilerImages(R.string.cache_spoiler_images_title, R.string.cache_spoiler_images_loading),
- AllImages(R.string.cache_images_title, R.string.cache_images_loading);
+ LogImages(R.string.cache_log_images_title),
+ SpoilerImages(R.string.cache_spoiler_images_title),
+ AllImages(R.string.cache_images_title);
private final int titleResId;
- private final int loadingResId;
- ImageType(final int title, final int loading) {
+ ImageType(final int title) {
this.titleResId = title;
- this.loadingResId = loading;
}
public int getTitle() {
@@ -64,9 +61,6 @@ public class ImagesList {
}
private LayoutInflater inflater = null;
- private ProgressDialog progressDialog = null;
- private int count = 0;
- private int countDone = 0;
private final Activity activity;
// We could use a Set here, but we will insert no duplicates, so there is no need to check for uniqueness.
private final Collection<Bitmap> bitmaps = new LinkedList<Bitmap>();
@@ -83,18 +77,10 @@ public class ImagesList {
inflater = activity.getLayoutInflater();
}
- public void loadImages(final View parentView, final List<Image> images, ImageType imageType, final boolean offline) {
+ public void loadImages(final View parentView, final List<Image> images, final boolean offline) {
imagesView = (LinearLayout) parentView.findViewById(R.id.spoiler_list);
- count = images.size();
- progressDialog = new ProgressDialog(activity);
- progressDialog.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
- progressDialog.setMessage(activity.getString(imageType.loadingResId));
- progressDialog.setCancelable(true);
- progressDialog.setMax(count);
- progressDialog.show();
-
for (final Image img : images) {
LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, null);
@@ -154,19 +140,12 @@ public class ImagesList {
imageView.setScaleType(ImageView.ScaleType.CENTER_CROP);
imageView.setLayoutParams(new LayoutParams(bounds.width(), bounds.height()));
+ view.findViewById(R.id.progress_bar).setVisibility(View.GONE);
view.addView(imageView);
imageView.setId(image.hashCode());
images.put(imageView.getId(), img);
}
-
- synchronized (activity) {
- countDone++;
- progressDialog.setProgress(countDone);
- if (progressDialog.getProgress() >= count) {
- progressDialog.dismiss();
- }
- }
}
}
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
new file mode 100644
index 0000000..7526d92
--- /dev/null
+++ b/main/src/cgeo/geocaching/utils/AsyncTaskWithProgress.java
@@ -0,0 +1,139 @@
+package cgeo.geocaching.utils;
+
+import cgeo.geocaching.activity.Progress;
+
+import android.app.Activity;
+import android.app.ProgressDialog;
+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. 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>
+ */
+public abstract class AsyncTaskWithProgress<Params, Result> extends AsyncTask<Params, Integer, Result> {
+
+ private final Progress progress = new Progress();
+ private final Activity activity;
+ private final String progressTitle;
+ private final String progressMessage;
+ private boolean indeterminate = false;
+
+ /**
+ * 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, 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.progressTitle = progressTitle;
+ this.progressMessage = progressMessage;
+ this.indeterminate = indeterminate;
+ }
+
+ /**
+ * Creates an AsyncTask with progress dialog.
+ *
+ * @param activity
+ * @param progressTitle
+ */
+ public AsyncTaskWithProgress(final Activity activity, final String progressTitle, boolean indeterminate) {
+ this(activity, progressTitle, null, indeterminate);
+ }
+
+ @Override
+ protected final void onPreExecute() {
+ if (null != activity) {
+ if (indeterminate) {
+ progress.show(activity, progressTitle, progressMessage, true, null);
+ }
+ else {
+ progress.show(activity, progressTitle, progressMessage, ProgressDialog.STYLE_HORIZONTAL, null);
+ }
+ }
+ onPreExecuteInternal();
+ }
+
+ /**
+ * This method should typically be overridden by sub classes instead of {@link #onPreExecute()}.
+ */
+ protected void onPreExecuteInternal() {
+ // empty by default
+ }
+
+ @Override
+ 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 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/main/src/cgeo/org/kxml2/io/KXmlSerializer.java b/main/src/cgeo/org/kxml2/io/KXmlSerializer.java
new file mode 100644
index 0000000..027ff53
--- /dev/null
+++ b/main/src/cgeo/org/kxml2/io/KXmlSerializer.java
@@ -0,0 +1,605 @@
+/*
+ * Copyright (c) 2002,2003, Stefan Haustein, Oberhausen, Rhld., Germany
+ *
+ * Permission is hereby granted, free of charge, to any person obtaining a copy
+ * of this software and associated documentation files (the "Software"), to deal
+ * in the Software without restriction, including without limitation the rights
+ * to use, copy, modify, merge, publish, distribute, sublicense, and/or
+ * sell copies of the Software, and to permit persons to whom the Software is
+ * furnished to do so, subject to the following conditions:
+ *
+ * The above copyright notice and this permission notice shall be included in
+ * all copies or substantial portions of the Software.
+ *
+ * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+ * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+ * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+ * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+ * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+ * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+ * IN THE SOFTWARE.
+ */
+
+package cgeo.org.kxml2.io;
+
+import org.apache.commons.lang3.StringUtils;
+import org.xmlpull.v1.XmlSerializer;
+
+import java.io.BufferedWriter;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.util.Locale;
+
+public class KXmlSerializer implements XmlSerializer {
+
+ // static final String UNDEFINED = ":";
+
+ // BEGIN android-added
+ /** size (in characters) for the write buffer */
+ private static final int WRITE_BUFFER_SIZE = 500;
+ // END android-added
+
+ // BEGIN android-changed
+ // (Guarantee that the writer is always buffered.)
+ private BufferedWriter writer;
+ // END android-changed
+
+ private boolean pending;
+ private int auto;
+ private int depth;
+
+ private String[] elementStack = new String[12];
+ //nsp/prefix/name
+ private int[] nspCounts = new int[4];
+ private String[] nspStack = new String[8];
+ //prefix/nsp; both empty are ""
+ private boolean[] indent = new boolean[4];
+ private boolean unicode;
+ private String encoding;
+
+ private final void check(boolean close) throws IOException {
+ if (!pending) {
+ return;
+ }
+
+ depth++;
+ pending = false;
+
+ if (indent.length <= depth) {
+ boolean[] hlp = new boolean[depth + 4];
+ System.arraycopy(indent, 0, hlp, 0, depth);
+ indent = hlp;
+ }
+ indent[depth] = indent[depth - 1];
+
+ for (int i = nspCounts[depth - 1]; i < nspCounts[depth]; i++) {
+ writer.write(' ');
+ writer.write("xmlns");
+ if (!StringUtils.isEmpty(nspStack[i * 2])) {
+ writer.write(':');
+ writer.write(nspStack[i * 2]);
+ }
+ else if (StringUtils.isEmpty(getNamespace()) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+ throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
+ }
+ writer.write("=\"");
+ writeEscaped(nspStack[i * 2 + 1], '"');
+ writer.write('"');
+ }
+
+ if (nspCounts.length <= depth + 1) {
+ int[] hlp = new int[depth + 8];
+ System.arraycopy(nspCounts, 0, hlp, 0, depth + 1);
+ nspCounts = hlp;
+ }
+
+ nspCounts[depth + 1] = nspCounts[depth];
+ // nspCounts[depth + 2] = nspCounts[depth];
+
+ writer.write(close ? " />" : ">");
+ }
+
+ private final void writeEscaped(String s, int quot) throws IOException {
+ for (int i = 0; i < s.length(); i++) {
+ char c = s.charAt(i);
+ switch (c) {
+ case '\n':
+ case '\r':
+ case '\t':
+ if (quot == -1) {
+ writer.write(c);
+ } else {
+ writer.write("&#"+((int) c)+';');
+ }
+ break;
+ case '&':
+ writer.write("&amp;");
+ break;
+ case '>':
+ writer.write("&gt;");
+ break;
+ case '<':
+ writer.write("&lt;");
+ break;
+ default:
+ if (c == quot) {
+ writer.write(c == '"' ? "&quot;" : "&apos;");
+ break;
+ }
+ // BEGIN android-changed: refuse to output invalid characters
+ // See http://www.w3.org/TR/REC-xml/#charsets for definition.
+ // Corrected for c:geo to handle utf-16 codepoint surrogates correctly
+ // See http://en.wikipedia.org/wiki/UTF-16#Code_points_U.2B10000_to_U.2B10FFFF
+ // Note: tab, newline, and carriage return have already been
+ // handled above.
+ // Check for lead surrogate
+ if (c >= 0xd800 && c <= 0xdbff) {
+
+ if (i + 1 < s.length()) {
+ writer.write(s.substring(i, i + 1));
+ i++;
+ break;
+ }
+ // if the lead surrogate is at the string end, it's not valid utf-16
+ reportInvalidCharacter(c);
+ }
+ boolean valid = (c >= 0x20 && c <= 0xd7ff) || (c >= 0xe000 && c <= 0xfffd);
+ if (!valid) {
+ reportInvalidCharacter(c);
+ }
+ if (unicode || c < 127) {
+ writer.write(c);
+ } else {
+ writer.write("&#" + ((int) c) + ";");
+ }
+ // END android-changed
+ }
+ }
+ }
+
+ // BEGIN android-added
+ private static void reportInvalidCharacter(char ch) {
+ throw new IllegalArgumentException("Illegal character (" + Integer.toHexString((int) ch) + ")");
+ }
+ // END android-added
+
+ /*
+ * private final void writeIndent() throws IOException {
+ * writer.write("\r\n");
+ * for (int i = 0; i < depth; i++)
+ * writer.write(' ');
+ * }
+ */
+
+ public void docdecl(String dd) throws IOException {
+ writer.write("<!DOCTYPE");
+ writer.write(dd);
+ writer.write(">");
+ }
+
+ public void endDocument() throws IOException {
+ while (depth > 0) {
+ endTag(elementStack[depth * 3 - 3], elementStack[depth * 3 - 1]);
+ }
+ flush();
+ }
+
+ public void entityRef(String name) throws IOException {
+ check(false);
+ writer.write('&');
+ writer.write(name);
+ writer.write(';');
+ }
+
+ public boolean getFeature(String name) {
+ //return false;
+ return ("http://xmlpull.org/v1/doc/features.html#indent-output"
+ .equals(
+ name))
+ ? indent[depth]
+ : false;
+ }
+
+ public String getPrefix(String namespace, boolean create) {
+ try {
+ return getPrefix(namespace, false, create);
+ } catch (IOException e) {
+ throw new RuntimeException(e.toString());
+ }
+ }
+
+ private final String getPrefix(
+ String namespace,
+ boolean includeDefault,
+ boolean create)
+ throws IOException {
+
+ for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
+ if (nspStack[i + 1].equals(namespace)
+ && (includeDefault || !StringUtils.isEmpty(nspStack[i]))) {
+ String cand = nspStack[i];
+ for (int j = i + 2; j < nspCounts[depth + 1] * 2; j++) {
+ if (nspStack[j].equals(cand)) {
+ cand = null;
+ break;
+ }
+ }
+ if (cand != null) {
+ return cand;
+ }
+ }
+ }
+
+ if (!create) {
+ return null;
+ }
+
+ String prefix;
+
+ if (StringUtils.isEmpty(namespace)) {
+ prefix = "";
+ } else {
+ do {
+ prefix = "n" + (auto++);
+ for (int i = nspCounts[depth + 1] * 2 - 2; i >= 0; i -= 2) {
+ if (prefix.equals(nspStack[i])) {
+ prefix = null;
+ break;
+ }
+ }
+ } while (prefix == null);
+ }
+
+ boolean p = pending;
+ pending = false;
+ setPrefix(prefix, namespace);
+ pending = p;
+ return prefix;
+ }
+
+ public Object getProperty(String name) {
+ throw new RuntimeException("Unsupported property");
+ }
+
+ public void ignorableWhitespace(String s)
+ throws IOException {
+ text(s);
+ }
+
+ public void setFeature(String name, boolean value) {
+ if ("http://xmlpull.org/v1/doc/features.html#indent-output"
+ .equals(name)) {
+ indent[depth] = value;
+ } else {
+ throw new RuntimeException("Unsupported Feature");
+ }
+ }
+
+ public void setProperty(String name, Object value) {
+ throw new RuntimeException(
+ "Unsupported Property:" + value);
+ }
+
+ public void setPrefix(String prefix, String namespace)
+ throws IOException {
+
+ check(false);
+ if (prefix == null) {
+ prefix = "";
+ }
+ if (namespace == null) {
+ namespace = "";
+ }
+
+ String defined = getPrefix(namespace, true, false);
+
+ // boil out if already defined
+
+ if (prefix.equals(defined)) {
+ return;
+ }
+
+ int pos = (nspCounts[depth + 1]++) << 1;
+
+ if (nspStack.length < pos + 1) {
+ String[] hlp = new String[nspStack.length + 16];
+ System.arraycopy(nspStack, 0, hlp, 0, pos);
+ nspStack = hlp;
+ }
+
+ nspStack[pos++] = prefix;
+ nspStack[pos] = namespace;
+ }
+
+ public void setOutput(Writer writer) {
+ // BEGIN android-changed
+ // Guarantee that the writer is always buffered.
+ if (writer instanceof BufferedWriter) {
+ this.writer = (BufferedWriter) writer;
+ } else {
+ this.writer = new BufferedWriter(writer, WRITE_BUFFER_SIZE);
+ }
+ // END android-changed
+
+ // elementStack = new String[12]; //nsp/prefix/name
+ //nspCounts = new int[4];
+ //nspStack = new String[8]; //prefix/nsp
+ //indent = new boolean[4];
+
+ nspCounts[0] = 2;
+ nspCounts[1] = 2;
+ nspStack[0] = "";
+ nspStack[1] = "";
+ nspStack[2] = "xml";
+ nspStack[3] = "http://www.w3.org/XML/1998/namespace";
+ pending = false;
+ auto = 0;
+ depth = 0;
+
+ unicode = false;
+ }
+
+ public void setOutput(OutputStream os, String encoding)
+ throws IOException {
+ if (os == null) {
+ throw new IllegalArgumentException("os == null");
+ }
+ setOutput(encoding == null
+ ? new OutputStreamWriter(os)
+ : new OutputStreamWriter(os, encoding));
+ this.encoding = encoding;
+ if (encoding != null && encoding.toLowerCase(Locale.US).startsWith("utf")) {
+ unicode = true;
+ }
+ }
+
+ public void startDocument(String encoding, Boolean standalone) throws IOException {
+ writer.write("<?xml version='1.0' ");
+
+ if (encoding != null) {
+ this.encoding = encoding;
+ if (encoding.toLowerCase(Locale.US).startsWith("utf")) {
+ unicode = true;
+ }
+ }
+
+ if (this.encoding != null) {
+ writer.write("encoding='");
+ writer.write(this.encoding);
+ writer.write("' ");
+ }
+
+ if (standalone != null) {
+ writer.write("standalone='");
+ writer.write(
+ standalone.booleanValue() ? "yes" : "no");
+ writer.write("' ");
+ }
+ writer.write("?>");
+ }
+
+ public XmlSerializer startTag(String namespace, String name)
+ throws IOException {
+ check(false);
+
+ // if (namespace == null)
+ // namespace = "";
+
+ if (indent[depth]) {
+ writer.write("\r\n");
+ for (int i = 0; i < depth; i++) {
+ writer.write(" ");
+ }
+ }
+
+ int esp = depth * 3;
+
+ if (elementStack.length < esp + 3) {
+ String[] hlp = new String[elementStack.length + 12];
+ System.arraycopy(elementStack, 0, hlp, 0, esp);
+ elementStack = hlp;
+ }
+
+ String prefix =
+ namespace == null
+ ? ""
+ : getPrefix(namespace, true, true);
+
+ if (namespace != null && StringUtils.isEmpty(namespace)) {
+ for (int i = nspCounts[depth]; i < nspCounts[depth + 1]; i++) {
+ if (StringUtils.isEmpty(nspStack[i * 2]) && !StringUtils.isEmpty(nspStack[i * 2 + 1])) {
+ throw new IllegalStateException("Cannot set default namespace for elements in no namespace");
+ }
+ }
+ }
+
+ elementStack[esp++] = namespace;
+ elementStack[esp++] = prefix;
+ elementStack[esp] = name;
+
+ writer.write('<');
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+
+ writer.write(name);
+
+ pending = true;
+
+ return this;
+ }
+
+ public XmlSerializer attribute(
+ String namespace,
+ String name,
+ String value)
+ throws IOException {
+ if (!pending) {
+ throw new IllegalStateException("illegal position for attribute");
+ }
+
+ // int cnt = nspCounts[depth];
+
+ if (namespace == null) {
+ namespace = "";
+ }
+
+ // depth--;
+ // pending = false;
+
+ String prefix =
+ StringUtils.isEmpty(namespace)
+ ? ""
+ : getPrefix(namespace, false, true);
+
+ // pending = true;
+ // depth++;
+
+ /*
+ * if (cnt != nspCounts[depth]) {
+ * writer.write(' ');
+ * writer.write("xmlns");
+ * if (nspStack[cnt * 2] != null) {
+ * writer.write(':');
+ * writer.write(nspStack[cnt * 2]);
+ * }
+ * writer.write("=\"");
+ * writeEscaped(nspStack[cnt * 2 + 1], '"');
+ * writer.write('"');
+ * }
+ */
+
+ writer.write(' ');
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+ writer.write(name);
+ writer.write('=');
+ char q = value.indexOf('"') == -1 ? '"' : '\'';
+ writer.write(q);
+ writeEscaped(value, q);
+ writer.write(q);
+
+ return this;
+ }
+
+ public void flush() throws IOException {
+ check(false);
+ writer.flush();
+ }
+
+ /*
+ * public void close() throws IOException {
+ * check();
+ * writer.close();
+ * }
+ */
+ public XmlSerializer endTag(String namespace, String name)
+ throws IOException {
+
+ if (!pending)
+ {
+ depth--;
+ // if (namespace == null)
+ // namespace = "";
+ }
+
+ if ((namespace == null
+ && elementStack[depth * 3] != null)
+ || (namespace != null
+ && !namespace.equals(elementStack[depth * 3]))
+ || !elementStack[depth * 3 + 2].equals(name)) {
+ throw new IllegalArgumentException("</{"+namespace+"}"+name+"> does not match start");
+ }
+
+ if (pending) {
+ check(true);
+ depth--;
+ }
+ else {
+ if (indent[depth + 1]) {
+ writer.write("\r\n");
+ for (int i = 0; i < depth; i++) {
+ writer.write(" ");
+ }
+ }
+
+ writer.write("</");
+ String prefix = elementStack[depth * 3 + 1];
+ if (!StringUtils.isEmpty(prefix)) {
+ writer.write(prefix);
+ writer.write(':');
+ }
+ writer.write(name);
+ writer.write('>');
+ }
+
+ nspCounts[depth + 1] = nspCounts[depth];
+ return this;
+ }
+
+ public String getNamespace() {
+ return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 3];
+ }
+
+ public String getName() {
+ return getDepth() == 0 ? null : elementStack[getDepth() * 3 - 1];
+ }
+
+ public int getDepth() {
+ return pending ? depth + 1 : depth;
+ }
+
+ public XmlSerializer text(String text) throws IOException {
+ check(false);
+ indent[depth] = false;
+ writeEscaped(text, -1);
+ return this;
+ }
+
+ public XmlSerializer text(char[] text, int start, int len)
+ throws IOException {
+ text(new String(text, start, len));
+ return this;
+ }
+
+ public void cdsect(String data) throws IOException {
+ check(false);
+ // BEGIN android-changed: ]]> is not allowed within a CDATA,
+ // so break and start a new one when necessary.
+ data = data.replace("]]>", "]]]]><![CDATA[>");
+ char[] chars = data.toCharArray();
+ // We also aren't allowed any invalid characters.
+ for (char ch : chars) {
+ boolean valid = (ch >= 0x20 && ch <= 0xd7ff) ||
+ (ch == '\t' || ch == '\n' || ch == '\r') ||
+ (ch >= 0xe000 && ch <= 0xfffd);
+ if (!valid) {
+ reportInvalidCharacter(ch);
+ }
+ }
+ writer.write("<![CDATA[");
+ writer.write(chars, 0, chars.length);
+ writer.write("]]>");
+ // END android-changed
+ }
+
+ public void comment(String comment) throws IOException {
+ check(false);
+ writer.write("<!--");
+ writer.write(comment);
+ writer.write("-->");
+ }
+
+ public void processingInstruction(String pi)
+ throws IOException {
+ check(false);
+ writer.write("<?");
+ writer.write(pi);
+ writer.write("?>");
+ }
+} \ No newline at end of file