aboutsummaryrefslogtreecommitdiffstats
path: root/main/src
diff options
context:
space:
mode:
Diffstat (limited to 'main/src')
-rw-r--r--main/src/cgeo/geocaching/AbstractDialogFragment.java2
-rw-r--r--main/src/cgeo/geocaching/AbstractLoggingActivity.java6
-rw-r--r--main/src/cgeo/geocaching/CacheDetailActivity.java40
-rw-r--r--main/src/cgeo/geocaching/CacheListActivity.java76
-rw-r--r--main/src/cgeo/geocaching/CachePopupFragment.java12
-rw-r--r--main/src/cgeo/geocaching/CgeoApplication.java131
-rw-r--r--main/src/cgeo/geocaching/CompassActivity.java109
-rw-r--r--main/src/cgeo/geocaching/CreateShortcutActivity.java110
-rw-r--r--main/src/cgeo/geocaching/DataStore.java138
-rw-r--r--main/src/cgeo/geocaching/EditWaypointActivity.java95
-rw-r--r--main/src/cgeo/geocaching/Geocache.java69
-rw-r--r--main/src/cgeo/geocaching/GpxFileListActivity.java126
-rw-r--r--main/src/cgeo/geocaching/ImageSelectActivity.java51
-rw-r--r--main/src/cgeo/geocaching/Intents.java48
-rw-r--r--main/src/cgeo/geocaching/LogCacheActivity.java54
-rw-r--r--main/src/cgeo/geocaching/LogTrackableActivity.java29
-rw-r--r--main/src/cgeo/geocaching/MainActivity.java73
-rw-r--r--main/src/cgeo/geocaching/SearchResult.java2
-rw-r--r--main/src/cgeo/geocaching/SelectMapfileActivity.java6
-rw-r--r--main/src/cgeo/geocaching/StatusFragment.java3
-rw-r--r--main/src/cgeo/geocaching/Trackable.java15
-rw-r--r--main/src/cgeo/geocaching/TrackableActivity.java268
-rw-r--r--main/src/cgeo/geocaching/Waypoint.java21
-rw-r--r--main/src/cgeo/geocaching/activity/AbstractActivity.java6
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java3
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java12
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java14
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java6
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java50
-rw-r--r--main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java4
-rw-r--r--main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java57
-rw-r--r--main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java25
-rw-r--r--main/src/cgeo/geocaching/connector/AbstractConnector.java6
-rw-r--r--main/src/cgeo/geocaching/connector/capability/ILogin.java5
-rw-r--r--main/src/cgeo/geocaching/connector/ec/ECApi.java51
-rw-r--r--main/src/cgeo/geocaching/connector/ec/ECLogin.java20
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConnector.java5
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCConstants.java1
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCLogin.java7
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCMap.java103
-rw-r--r--main/src/cgeo/geocaching/connector/gc/GCParser.java295
-rw-r--r--main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java6
-rw-r--r--main/src/cgeo/geocaching/connector/gc/Tile.java52
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OCApiConnector.java13
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OkapiClient.java296
-rw-r--r--main/src/cgeo/geocaching/connector/oc/OkapiError.java14
-rw-r--r--main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java6
-rw-r--r--main/src/cgeo/geocaching/enumerations/LocationProviderType.java2
-rw-r--r--main/src/cgeo/geocaching/enumerations/LogType.java16
-rw-r--r--main/src/cgeo/geocaching/files/AbstractFileListActivity.java28
-rw-r--r--main/src/cgeo/geocaching/files/FileTypeDetector.java3
-rw-r--r--main/src/cgeo/geocaching/files/GPXImporter.java45
-rw-r--r--main/src/cgeo/geocaching/files/GPXParser.java95
-rw-r--r--main/src/cgeo/geocaching/files/IFileSelectionView.java2
-rw-r--r--main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java190
-rw-r--r--main/src/cgeo/geocaching/files/LocParser.java2
-rw-r--r--main/src/cgeo/geocaching/files/SimpleDirChooser.java2
-rw-r--r--main/src/cgeo/geocaching/filter/PopularityFilter.java3
-rw-r--r--main/src/cgeo/geocaching/filter/PopularityRatioFilter.java11
-rw-r--r--main/src/cgeo/geocaching/gcvote/GCVote.java194
-rw-r--r--main/src/cgeo/geocaching/geopoint/Viewport.java16
-rw-r--r--main/src/cgeo/geocaching/list/AbstractList.java2
-rw-r--r--main/src/cgeo/geocaching/list/StoredList.java36
-rw-r--r--main/src/cgeo/geocaching/maps/CGeoMap.java157
-rw-r--r--main/src/cgeo/geocaching/maps/MapActivity.java17
-rw-r--r--main/src/cgeo/geocaching/maps/MapProviderFactory.java13
-rw-r--r--main/src/cgeo/geocaching/network/HtmlImage.java30
-rw-r--r--main/src/cgeo/geocaching/network/Network.java23
-rw-r--r--main/src/cgeo/geocaching/network/OAuth.java10
-rw-r--r--main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java58
-rw-r--r--main/src/cgeo/geocaching/network/OAuthTokens.java38
-rw-r--r--main/src/cgeo/geocaching/network/SmileyImage.java44
-rw-r--r--main/src/cgeo/geocaching/network/StatusUpdater.java23
-rw-r--r--main/src/cgeo/geocaching/playservices/LocationProvider.java157
-rw-r--r--main/src/cgeo/geocaching/sensors/DirectionProvider.java146
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoData.java87
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDataProvider.java255
-rw-r--r--main/src/cgeo/geocaching/sensors/GeoDirHandler.java11
-rw-r--r--main/src/cgeo/geocaching/sensors/GpsStatusProvider.java99
-rw-r--r--main/src/cgeo/geocaching/sensors/IGeoData.java5
-rw-r--r--main/src/cgeo/geocaching/sensors/OrientationProvider.java67
-rw-r--r--main/src/cgeo/geocaching/sensors/RotationProvider.java83
-rw-r--r--main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java2
-rw-r--r--main/src/cgeo/geocaching/settings/Settings.java154
-rw-r--r--main/src/cgeo/geocaching/settings/SettingsActivity.java38
-rw-r--r--main/src/cgeo/geocaching/sorting/DistanceComparator.java3
-rw-r--r--main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java3
-rw-r--r--main/src/cgeo/geocaching/sorting/SortActionProvider.java4
-rw-r--r--main/src/cgeo/geocaching/speech/SpeechService.java6
-rw-r--r--main/src/cgeo/geocaching/twitter/Twitter.java16
-rw-r--r--main/src/cgeo/geocaching/ui/CacheDetailsCreator.java16
-rw-r--r--main/src/cgeo/geocaching/ui/CacheListAdapter.java4
-rw-r--r--main/src/cgeo/geocaching/ui/CompassView.java16
-rw-r--r--main/src/cgeo/geocaching/ui/DecryptTextClickListener.java77
-rw-r--r--main/src/cgeo/geocaching/ui/ImagesList.java2
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/DateDialog.java6
-rw-r--r--main/src/cgeo/geocaching/ui/dialog/Dialogs.java47
-rw-r--r--main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java5
-rw-r--r--main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java13
-rw-r--r--main/src/cgeo/geocaching/utils/AngleUtils.java43
-rw-r--r--main/src/cgeo/geocaching/utils/ClipboardUtils.java2
-rw-r--r--main/src/cgeo/geocaching/utils/CryptUtils.java44
-rw-r--r--main/src/cgeo/geocaching/utils/Formatter.java42
-rw-r--r--main/src/cgeo/geocaching/utils/HtmlUtils.java28
-rw-r--r--main/src/cgeo/geocaching/utils/ImageUtils.java109
-rw-r--r--main/src/cgeo/geocaching/utils/JsonUtils.java20
-rw-r--r--main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java79
-rw-r--r--main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java7
-rw-r--r--main/src/cgeo/geocaching/utils/RxUtils.java99
-rw-r--r--main/src/cgeo/geocaching/utils/StartableHandlerThread.java80
-rw-r--r--main/src/cgeo/geocaching/utils/TextUtils.java68
111 files changed, 2979 insertions, 2565 deletions
diff --git a/main/src/cgeo/geocaching/AbstractDialogFragment.java b/main/src/cgeo/geocaching/AbstractDialogFragment.java
index 4025347..6f64146 100644
--- a/main/src/cgeo/geocaching/AbstractDialogFragment.java
+++ b/main/src/cgeo/geocaching/AbstractDialogFragment.java
@@ -42,7 +42,6 @@ import android.widget.ImageView;
import android.widget.TextView;
public abstract class AbstractDialogFragment extends DialogFragment implements CacheMenuHandler.ActivityInterface, PopupMenu.OnMenuItemClickListener, MenuItem.OnMenuItemClickListener {
- protected CgeoApplication app = null;
protected Resources res = null;
protected String geocode;
protected CacheDetailsCreator details;
@@ -61,7 +60,6 @@ public abstract class AbstractDialogFragment extends DialogFragment implements C
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
res = getResources();
- app = (CgeoApplication) getActivity().getApplication();
setHasOptionsMenu(true);
}
diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java
index 8448e45..15e8848 100644
--- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java
+++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java
@@ -2,6 +2,7 @@ package cgeo.geocaching;
import cgeo.geocaching.activity.AbstractActionBarActivity;
import cgeo.geocaching.activity.ActivityMixin;
+import cgeo.geocaching.activity.Keyboard;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.gc.GCConnector;
import cgeo.geocaching.connector.gc.GCSmiliesProvider;
@@ -77,4 +78,9 @@ public abstract class AbstractLoggingActivity extends AbstractActionBarActivity
final EditText log = (EditText) findViewById(R.id.log);
ActivityMixin.insertAtPosition(log, newText, moveCursor);
}
+
+ protected void requestKeyboardForLogging() {
+ new Keyboard(this).show(findViewById(R.id.log));
+ }
+
}
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java
index d3ab633..a59d9b9 100644
--- a/main/src/cgeo/geocaching/CacheDetailActivity.java
+++ b/main/src/cgeo/geocaching/CacheDetailActivity.java
@@ -66,10 +66,12 @@ import org.eclipse.jdt.annotation.Nullable;
import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
+import rx.Subscription;
import rx.android.observables.AndroidObservable;
import rx.functions.Action0;
import rx.functions.Action1;
import rx.subscriptions.CompositeSubscription;
+import rx.subscriptions.Subscriptions;
import android.R.color;
import android.app.AlertDialog;
@@ -171,6 +173,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
*/
private Waypoint selectedWaypoint;
+ private boolean requireGeodata;
+ private Subscription geoDataSubscription = Subscriptions.empty();
+
@Override
public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.cachedetail_activity);
@@ -235,7 +240,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
finish();
return;
}
- } else if (uriHost.contains("opencaching.de")) {
+ } else if (uriHost.contains("opencaching.de") || uriHost.contains("opencaching.fr")) {
if (StringUtils.startsWith(uriPath, "/oc")) {
geocode = uriPath.substring(1).toUpperCase(Locale.US);
} else {
@@ -293,8 +298,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (getPage(position) == Page.IMAGES) {
loadCacheImages();
}
+ requireGeodata = getPage(position) == Page.DETAILS;
+ startOrStopGeoDataListener();
}
});
+ requireGeodata = pageToOpen == 1;
final String realGeocode = geocode;
final String realGuid = guid;
@@ -323,9 +331,17 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
outState.putInt(STATE_PAGE_INDEX, getCurrentItem());
}
+ private void startOrStopGeoDataListener() {
+ geoDataSubscription.unsubscribe();
+ if (requireGeodata) {
+ geoDataSubscription = locationUpdater.start(GeoDirHandler.UPDATE_GEODATA);
+ }
+ }
+
@Override
public void onResume() {
- super.onResume(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA));
+ super.onResume();
+ startOrStopGeoDataListener();
if (refreshOnResume) {
notifyDataSetChanged();
@@ -334,6 +350,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
}
@Override
+ public void onPause() {
+ geoDataSubscription.unsubscribe();
+ super.onPause();
+ }
+
+ @Override
public void onDestroy() {
createSubscriptions.unsubscribe();
super.onDestroy();
@@ -910,9 +932,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
public void call(final Integer selectedListId) {
storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress));
}
- }, true, StoredList.TEMPORARY_LIST_ID);
+ }, true, StoredList.TEMPORARY_LIST.id);
} else {
- storeCache(StoredList.TEMPORARY_LIST_ID, new StoreCacheHandler(this, progress));
+ storeCache(StoredList.TEMPORARY_LIST.id, new StoreCacheHandler(this, progress));
}
}
@@ -2223,7 +2245,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) {
updateStatusMsg(R.string.cache_dialog_offline_save_message, (String) msg.obj);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
@@ -2239,7 +2261,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) {
updateStatusMsg(R.string.cache_dialog_refresh_message, (String) msg.obj);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
@@ -2252,7 +2274,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
@Override
public void handleMessage(final Message msg) {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
@@ -2267,12 +2289,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc
if (msg.what == MESSAGE_FAILED) {
super.handleMessage(msg);
} else {
- notifyDatasetChanged(activityRef);
+ notifyDataSetChanged(activityRef);
}
}
}
- private static void notifyDatasetChanged(final WeakReference<AbstractActivity> activityRef) {
+ private static void notifyDataSetChanged(final WeakReference<AbstractActivity> activityRef) {
final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get());
if (activity != null) {
activity.notifyDataSetChanged();
diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java
index 522004e..7b887aa 100644
--- a/main/src/cgeo/geocaching/CacheListActivity.java
+++ b/main/src/cgeo/geocaching/CacheListActivity.java
@@ -41,7 +41,6 @@ import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.network.Cookies;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
@@ -52,6 +51,7 @@ import cgeo.geocaching.ui.CacheListAdapter;
import cgeo.geocaching.ui.LoggingUI;
import cgeo.geocaching.ui.WeakReferenceHandler;
import cgeo.geocaching.ui.dialog.Dialogs;
+import cgeo.geocaching.utils.AngleUtils;
import cgeo.geocaching.utils.AsyncTaskWithProgress;
import cgeo.geocaching.utils.CancellableHandler;
import cgeo.geocaching.utils.DateUtils;
@@ -134,13 +134,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
private int detailTotal = 0;
private int detailProgress = 0;
private long detailProgressTime = 0L;
- private int listId = StoredList.TEMPORARY_LIST_ID; // Only meaningful for the OFFLINE type
+ private int listId = StoredList.TEMPORARY_LIST.id; // Only meaningful for the OFFLINE type
private final GeoDirHandler geoDirHandler = new GeoDirHandler() {
@Override
public void updateDirection(final float direction) {
if (Settings.isLiveList()) {
- adapter.setActualHeading(DirectionProvider.getDirectionNow(direction));
+ adapter.setActualHeading(AngleUtils.getDirectionNow(direction));
}
}
@@ -392,8 +392,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
// get parameters
Bundle extras = getIntent().getExtras();
if (extras != null) {
- final Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE);
- type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE;
+ type = Intents.getListType(getIntent());
coords = extras.getParcelable(Intents.EXTRA_COORDS);
}
else {
@@ -405,6 +404,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
coords = Geopoint.ZERO;
}
}
+ if (type == CacheListType.NEAREST) {
+ coords = CgeoApplication.getInstance().currentGeo().getCoords();
+ }
setTitle(title);
@@ -513,7 +515,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
public void onResume() {
super.onResume();
- resumeSubscription = geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.UPDATE_DIRECTION);
+ resumeSubscription = geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.UPDATE_DIRECTION | GeoDirHandler.LOW_POWER);
adapter.setSelectMode(false);
setAdapterCurrentCoordinates(true);
@@ -769,7 +771,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return new SearchResult(geocodes);
}
- public void deletePastEvents() {
+ private void deletePastEvents() {
final List<Geocache> deletion = new ArrayList<>();
for (final Geocache cache : adapter.getCheckedOrAllCaches()) {
if (DateUtils.isPastEvent(cache)) {
@@ -779,9 +781,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
new DropDetailsTask().execute(deletion.toArray(new Geocache[deletion.size()]));
}
- public void clearOfflineLogs() {
- progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage());
- new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
+ private void clearOfflineLogs() {
+ Dialogs.confirmYesNo(this, R.string.caches_clear_offlinelogs, R.string.caches_clear_offlinelogs_message, new OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ progress.show(CacheListActivity.this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage());
+ new ClearOfflineLogsThread(clearOfflineLogsHandler).start();
+ }
+ });
}
/**
@@ -1079,11 +1087,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
refreshStoredInternal(caches);
}
- }, true, StoredList.TEMPORARY_LIST_ID, newListName);
+ }, true, StoredList.TEMPORARY_LIST.id, newListName);
} else {
if (type != CacheListType.OFFLINE) {
for (final Geocache geocache : caches) {
- if (geocache.getListId() == StoredList.TEMPORARY_LIST_ID) {
+ if (geocache.getListId() == StoredList.TEMPORARY_LIST.id) {
geocache.setListId(StoredList.STANDARD_LIST_ID);
}
}
@@ -1126,7 +1134,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
});
}
- public void removeFromHistory() {
+ private void removeFromHistory() {
final List<Geocache> caches = adapter.getCheckedOrAllCaches();
final String[] geocodes = new String[caches.size()];
for (int i = 0; i < geocodes.length; i++) {
@@ -1137,7 +1145,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.getLoaderId(), b, this);
}
- public void importWeb() {
+ private void importWeb() {
// menu is also shown with no device connected
if (!Settings.isRegisteredForSend2cgeo()) {
Dialogs.confirm(this, R.string.web_import_title, R.string.init_sendToCgeo_description, new OnClickListener() {
@@ -1159,7 +1167,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
threadWeb.start();
}
- public void dropStored() {
+ private void dropStored() {
final int titleId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected : R.string.caches_remove_all;
final int messageId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected_confirm : R.string.caches_remove_all_confirm;
final String message = getString(messageId, adapter.getCheckedOrAllCount());
@@ -1355,13 +1363,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
};
}
- public void switchListById(final int id) {
+ private void switchListById(final int id) {
if (id < 0) {
return;
}
if (id == PseudoList.HISTORY_LIST.id) {
- CacheListActivity.startActivityHistory(this);
+ startActivity(CacheListActivity.getHistoryIntent(this));
finish();
return;
}
@@ -1452,7 +1460,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
public static void startActivityOffline(final Context context) {
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OFFLINE);
+ Intents.putListType(cachesIntent, CacheListType.OFFLINE);
context.startActivity(cachesIntent);
}
@@ -1461,7 +1469,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OWNER);
+ Intents.putListType(cachesIntent, CacheListType.OWNER);
cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
context.startActivity(cachesIntent);
}
@@ -1479,7 +1487,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.FINDER);
+ Intents.putListType(cachesIntent, CacheListType.FINDER);
cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName);
context.startActivity(cachesIntent);
}
@@ -1500,25 +1508,17 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
}
- public static void startActivityNearest(final AbstractActivity context, final Geopoint coordsNow) {
- if (!isValidCoords(context, coordsNow)) {
- return;
- }
- final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST);
- cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow);
- context.startActivity(cachesIntent);
+ public static Intent getNearestIntent(final Activity context) {
+ return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.NEAREST);
}
- public static void startActivityHistory(final Context context) {
- final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY);
- context.startActivity(cachesIntent);
+ public static Intent getHistoryIntent(final Context context) {
+ return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.HISTORY);
}
public static void startActivityAddress(final Context context, final Geopoint coords, final String address) {
final Intent addressIntent = new Intent(context, CacheListActivity.class);
- addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS);
+ Intents.putListType(addressIntent, CacheListType.ADDRESS);
addressIntent.putExtra(Intents.EXTRA_COORDS, coords);
addressIntent.putExtra(Intents.EXTRA_ADDRESS, address);
context.startActivity(addressIntent);
@@ -1529,7 +1529,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE);
+ Intents.putListType(cachesIntent, CacheListType.COORDINATE);
cachesIntent.putExtra(Intents.EXTRA_COORDS, coords);
context.startActivity(cachesIntent);
}
@@ -1548,15 +1548,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.KEYWORD);
+ Intents.putListType(cachesIntent, CacheListType.KEYWORD);
cachesIntent.putExtra(Intents.EXTRA_KEYWORD, keyword);
context.startActivity(cachesIntent);
}
public static void startActivityMap(final Context context, final SearchResult search) {
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.MAP);
cachesIntent.putExtra(Intents.EXTRA_SEARCH, search);
+ Intents.putListType(cachesIntent, CacheListType.MAP);
context.startActivity(cachesIntent);
}
@@ -1567,7 +1567,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
return;
}
final Intent cachesIntent = new Intent(context, CacheListActivity.class);
- cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.POCKET);
+ Intents.putListType(cachesIntent, CacheListType.POCKET);
cachesIntent.putExtra(Intents.EXTRA_NAME, pq.getName());
cachesIntent.putExtra(Intents.EXTRA_POCKET_GUID, guid);
context.startActivity(cachesIntent);
@@ -1592,7 +1592,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA
}
if (listId == PseudoList.ALL_LIST.id) {
title = res.getString(R.string.list_all_lists);
- } else if (listId <= StoredList.TEMPORARY_LIST_ID) {
+ } else if (listId <= StoredList.TEMPORARY_LIST.id) {
listId = StoredList.STANDARD_LIST_ID;
title = res.getString(R.string.stored_caches_button);
} else {
diff --git a/main/src/cgeo/geocaching/CachePopupFragment.java b/main/src/cgeo/geocaching/CachePopupFragment.java
index b2af12c..995fea2 100644
--- a/main/src/cgeo/geocaching/CachePopupFragment.java
+++ b/main/src/cgeo/geocaching/CachePopupFragment.java
@@ -17,8 +17,6 @@ import rx.functions.Action0;
import rx.functions.Action1;
import rx.schedulers.Schedulers;
-import android.content.Context;
-import android.content.Intent;
import android.content.res.Configuration;
import android.os.Bundle;
import android.os.Handler;
@@ -135,9 +133,9 @@ public class CachePopupFragment extends AbstractDialogFragment {
public void call(final Integer selectedListId) {
storeCache(selectedListId);
}
- }, true, StoredList.TEMPORARY_LIST_ID);
+ }, true, StoredList.TEMPORARY_LIST.id);
} else {
- storeCache(StoredList.TEMPORARY_LIST_ID);
+ storeCache(StoredList.TEMPORARY_LIST.id);
}
}
@@ -212,12 +210,6 @@ public class CachePopupFragment extends AbstractDialogFragment {
getActivity().finish();
}
- public static void startActivity(final Context context, final String geocode) {
- final Intent popupIntent = new Intent(context, CachePopup.class);
- popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode);
- context.startActivity(popupIntent);
- }
-
@Override
protected Geopoint getCoordinates() {
if (cache == null) {
diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java
index a81319d..34dab09 100644
--- a/main/src/cgeo/geocaching/CgeoApplication.java
+++ b/main/src/cgeo/geocaching/CgeoApplication.java
@@ -1,15 +1,26 @@
package cgeo.geocaching;
-import cgeo.geocaching.sensors.DirectionProvider;
+import cgeo.geocaching.playservices.LocationProvider;
+import cgeo.geocaching.sensors.GeoData;
import cgeo.geocaching.sensors.GeoDataProvider;
+import cgeo.geocaching.sensors.GpsStatusProvider;
+import cgeo.geocaching.sensors.GpsStatusProvider.Status;
import cgeo.geocaching.sensors.IGeoData;
+import cgeo.geocaching.sensors.OrientationProvider;
+import cgeo.geocaching.sensors.RotationProvider;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.OOMDumpingUncaughtExceptionHandler;
+import cgeo.geocaching.utils.RxUtils;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesUtil;
+
+import org.eclipse.jdt.annotation.NonNull;
import rx.Observable;
import rx.functions.Action1;
-import rx.observables.ConnectableObservable;
+import rx.functions.Func1;
import android.app.Application;
import android.view.ViewConfiguration;
@@ -23,9 +34,20 @@ public class CgeoApplication extends Application {
private boolean liveMapHintShownInThisSession = false; // livemap hint has been shown
private static CgeoApplication instance;
private Observable<IGeoData> geoDataObservable;
+ private Observable<IGeoData> geoDataObservableLowPower;
private Observable<Float> directionObservable;
- private volatile IGeoData currentGeo = null;
+ private Observable<Status> gpsStatusObservable;
+ @NonNull private volatile IGeoData currentGeo = GeoData.DUMMY_LOCATION;
+ private volatile boolean hasValidLocation = false;
private volatile float currentDirection = 0.0f;
+ private boolean isGooglePlayServicesAvailable = false;
+ private final Action1<IGeoData> rememberGeodataAction = new Action1<IGeoData>() {
+ @Override
+ public void call(final IGeoData geoData) {
+ currentGeo = geoData;
+ hasValidLocation = true;
+ }
+ };
public static void dumpOnOutOfMemory(final boolean enable) {
@@ -60,12 +82,7 @@ public class CgeoApplication extends Application {
final Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey");
menuKeyField.setAccessible(true);
menuKeyField.setBoolean(config, false);
- } catch (final IllegalArgumentException e) {
- // ignore
- } catch (final NoSuchFieldException e) {
- // ignore
- } catch (final IllegalAccessException e) {
- // ignore
+ } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException ignore) {
}
// Set language to English if the user decided so.
@@ -73,6 +90,52 @@ public class CgeoApplication extends Application {
// ensure initialization of lists
DataStore.getLists();
+
+ // Check if Google Play services is available
+ if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) {
+ isGooglePlayServicesAvailable = true;
+ }
+ Log.i("Google Play services are " + (isGooglePlayServicesAvailable ? "" : "not ") + "available");
+ setupGeoDataObservables(Settings.useGooglePlayServices(), Settings.useLowPowerMode());
+ setupDirectionObservable(Settings.useLowPowerMode());
+ gpsStatusObservable = GpsStatusProvider.create(this).replay(1).refCount();
+
+ // Attempt to acquire an initial location before any real activity happens.
+ geoDataObservableLowPower.subscribeOn(RxUtils.looperCallbacksScheduler).first().subscribe(rememberGeodataAction);
+ }
+
+ public void setupGeoDataObservables(final boolean useGooglePlayServices, final boolean useLowPowerLocation) {
+ if (useGooglePlayServices) {
+ geoDataObservable = LocationProvider.getMostPrecise(this).doOnNext(rememberGeodataAction);
+ if (useLowPowerLocation) {
+ geoDataObservableLowPower = LocationProvider.getLowPower(this, true).doOnNext(rememberGeodataAction);
+ } else {
+ geoDataObservableLowPower = geoDataObservable;
+ }
+ } else {
+ geoDataObservable = GeoDataProvider.create(this).replay(1).refCount().doOnNext(rememberGeodataAction);
+ geoDataObservableLowPower = geoDataObservable;
+ }
+ }
+
+ public void setupDirectionObservable(final boolean useLowPower) {
+ directionObservable = RotationProvider.create(this, useLowPower).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() {
+ @Override
+ public Observable<? extends Float> call(final Throwable throwable) {
+ return OrientationProvider.create(CgeoApplication.this);
+ }
+ }).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() {
+ @Override
+ public Observable<? extends Float> call(final Throwable throwable) {
+ Log.e("Device orientation will not be available as no suitable sensors were found");
+ return Observable.<Float>never().startWith(0.0f);
+ }
+ }).replay(1).refCount().doOnNext(new Action1<Float>() {
+ @Override
+ public void call(final Float direction) {
+ currentDirection = direction;
+ }
+ });
}
@Override
@@ -81,40 +144,32 @@ public class CgeoApplication extends Application {
DataStore.removeAllFromCache();
}
- public synchronized Observable<IGeoData> geoDataObservable() {
- if (geoDataObservable == null) {
- final ConnectableObservable<IGeoData> onDemand = GeoDataProvider.create(this).replay(1);
- onDemand.subscribe(new Action1<IGeoData>() {
- @Override
- public void call(final IGeoData geoData) {
- currentGeo = geoData;
- }
- });
- geoDataObservable = onDemand.refCount();
- }
- return geoDataObservable;
- }
-
- public synchronized Observable<Float> directionObservable() {
- if (directionObservable == null) {
- final ConnectableObservable<Float> onDemand = DirectionProvider.create(this).replay(1);
- onDemand.subscribe(new Action1<Float>() {
- @Override
- public void call(final Float direction) {
- currentDirection = direction;
- }
- });
- directionObservable = onDemand.refCount();
- }
+ public Observable<IGeoData> geoDataObservable(final boolean lowPower) {
+ return lowPower ? geoDataObservableLowPower : geoDataObservable;
+ }
+
+ public Observable<Float> directionObservable() {
return directionObservable;
}
+ public Observable<Status> gpsStatusObservable() {
+ if (gpsStatusObservable == null) {
+ gpsStatusObservable = GpsStatusProvider.create(this).share();
+ }
+ return gpsStatusObservable;
+ }
+
+ @NonNull
public IGeoData currentGeo() {
- return currentGeo != null ? currentGeo : geoDataObservable().toBlocking().first();
+ return currentGeo;
+ }
+
+ public boolean hasValidLocation() {
+ return hasValidLocation;
}
public Float distanceNonBlocking(final ICoordinates target) {
- if (currentGeo == null || target.getCoords() == null) {
+ if (target.getCoords() == null) {
return null;
}
return currentGeo.getCoords().distanceTo(target);
@@ -150,4 +205,8 @@ public class CgeoApplication extends Application {
forceRelog = true;
}
+ public boolean isGooglePlayServicesAvailable() {
+ return isGooglePlayServicesAvailable;
+ }
+
}
diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java
index 025d938..00fa790 100644
--- a/main/src/cgeo/geocaching/CompassActivity.java
+++ b/main/src/cgeo/geocaching/CompassActivity.java
@@ -8,19 +8,23 @@ import cgeo.geocaching.enumerations.LoadFlags;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.Units;
import cgeo.geocaching.maps.CGeoMap;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
+import cgeo.geocaching.sensors.GpsStatusProvider.Status;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.speech.SpeechService;
import cgeo.geocaching.ui.CompassView;
import cgeo.geocaching.ui.LoggingUI;
+import cgeo.geocaching.utils.AngleUtils;
import cgeo.geocaching.utils.Formatter;
import cgeo.geocaching.utils.Log;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action1;
+
import android.content.Context;
import android.content.Intent;
import android.content.res.Configuration;
@@ -34,14 +38,10 @@ import android.view.SubMenu;
import android.view.View;
import android.widget.TextView;
-import java.util.ArrayList;
-import java.util.Collection;
import java.util.List;
public class CompassActivity extends AbstractActionBarActivity {
- private static final int COORDINATES_OFFSET = 10;
-
@InjectView(R.id.nav_type) protected TextView navType;
@InjectView(R.id.nav_accuracy) protected TextView navAccuracy;
@InjectView(R.id.nav_satellites) protected TextView navSatellites;
@@ -56,7 +56,6 @@ public class CompassActivity extends AbstractActionBarActivity {
private static final String EXTRAS_NAME = "name";
private static final String EXTRAS_GEOCODE = "geocode";
private static final String EXTRAS_CACHE_INFO = "cacheinfo";
- private static final List<IWaypoint> coordinates = new ArrayList<>();
/**
* Destination of the compass, or null (if the compass is used for a waypoint only).
@@ -118,7 +117,9 @@ public class CompassActivity extends AbstractActionBarActivity {
@Override
public void onResume() {
- super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR));
+ super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR),
+ app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(gpsStatusHandler));
+ forceRefresh();
}
@Override
@@ -138,34 +139,42 @@ public class CompassActivity extends AbstractActionBarActivity {
setTitle();
setDestCoords();
setCacheInfo();
+ forceRefresh();
+ }
+ private void forceRefresh() {
// Force a refresh of location and direction when data is available.
final CgeoApplication app = CgeoApplication.getInstance();
final IGeoData geo = app.currentGeo();
- if (geo != null) {
- geoDirHandler.updateGeoDir(geo, app.currentDirection());
- }
+ geoDirHandler.updateGeoDir(geo, app.currentDirection());
}
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
getMenuInflater().inflate(R.menu.compass_activity_options, menu);
menu.findItem(R.id.menu_compass_sensor).setVisible(hasMagneticFieldSensor);
- final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu();
- if (coordinates.size() > 1) {
- for (int i = 0; i < coordinates.size(); i++) {
- final IWaypoint coordinate = coordinates.get(i);
- subMenu.add(0, COORDINATES_OFFSET + i, 0, coordinate.getName() + " (" + coordinate.getCoordType() + ")");
- }
- } else {
- menu.findItem(R.id.menu_select_destination).setVisible(false);
- }
if (cache != null) {
LoggingUI.addMenuItems(this, menu, cache);
}
+ addWaypointItems(menu);
return true;
}
+ private void addWaypointItems(final Menu menu) {
+ if (cache != null) {
+ final List<Waypoint> waypoints = cache.getWaypoints();
+ boolean visible = false;
+ final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu();
+ for (final Waypoint waypoint : waypoints) {
+ if (waypoint.getCoords() != null) {
+ subMenu.add(0, waypoint.getId(), 0, waypoint.getName());
+ visible = true;
+ }
+ }
+ menu.findItem(R.id.menu_select_destination).setVisible(visible);
+ }
+ }
+
@Override
public boolean onPrepareOptionsMenu(final Menu menu) {
super.onPrepareOptionsMenu(menu);
@@ -207,18 +216,19 @@ public class CompassActivity extends AbstractActionBarActivity {
if (LoggingUI.onMenuItemSelected(item, this, cache)) {
return true;
}
- final int coordinatesIndex = id - COORDINATES_OFFSET;
- if (coordinatesIndex >= 0 && coordinatesIndex < coordinates.size()) {
- final IWaypoint coordinate = coordinates.get(coordinatesIndex);
- title = coordinate.getName();
- dstCoords = coordinate.getCoords();
- setTitle();
- setDestCoords();
- setCacheInfo();
- updateDistanceInfo(app.currentGeo());
-
- Log.d("destination set: " + title + " (" + dstCoords + ")");
- return true;
+ if (cache != null) {
+ final Waypoint waypoint = cache.getWaypointById(id);
+ if (waypoint != null) {
+ title = waypoint.getName();
+ dstCoords = waypoint.getCoords();
+ setTitle();
+ setDestCoords();
+ setCacheInfo();
+ updateDistanceInfo(app.currentGeo());
+
+ Log.d("destination set: " + title + " (" + dstCoords + ")");
+ return true;
+ }
}
}
return super.onOptionsItemSelected(item);
@@ -259,16 +269,22 @@ public class CompassActivity extends AbstractActionBarActivity {
headingView.setText(Math.round(cacheHeading) + "°");
}
+ private final Action1<Status> gpsStatusHandler = new Action1<Status>() {
+ @Override
+ public void call(final Status gpsStatus) {
+ if (gpsStatus.satellitesVisible >= 0) {
+ navSatellites.setText(res.getString(R.string.loc_sat) + ": " + gpsStatus.satellitesFixed + "/" + gpsStatus.satellitesVisible);
+ } else {
+ navSatellites.setText("");
+ }
+ }
+ };
+
private final GeoDirHandler geoDirHandler = new GeoDirHandler() {
@Override
public void updateGeoDir(final IGeoData geo, final float dir) {
try {
if (geo.getCoords() != null) {
- if (geo.getSatellitesVisible() >= 0) {
- navSatellites.setText(res.getString(R.string.loc_sat) + ": " + geo.getSatellitesFixed() + "/" + geo.getSatellitesVisible());
- } else {
- navSatellites.setText("");
- }
navType.setText(res.getString(geo.getLocationProvider().resourceId));
if (geo.getAccuracy() >= 0) {
@@ -286,7 +302,7 @@ public class CompassActivity extends AbstractActionBarActivity {
navLocation.setText(res.getString(R.string.loc_trying));
}
- updateNorthHeading(DirectionProvider.getDirectionNow(dir));
+ updateNorthHeading(AngleUtils.getDirectionNow(dir));
} catch (final RuntimeException e) {
Log.w("Failed to LocationUpdater location.");
}
@@ -299,17 +315,8 @@ public class CompassActivity extends AbstractActionBarActivity {
}
}
- public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType,
+ public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords,
final String info) {
- coordinates.clear();
- if (coordinatesWithType != null) {
- for (final IWaypoint coordinate : coordinatesWithType) {
- if (coordinate != null) {
- coordinates.add(coordinate);
- }
- }
- }
-
final Intent navigateIntent = new Intent(context, CompassActivity.class);
navigateIntent.putExtra(EXTRAS_COORDS, coords);
navigateIntent.putExtra(EXTRAS_GEOCODE, geocode);
@@ -320,12 +327,12 @@ public class CompassActivity extends AbstractActionBarActivity {
context.startActivity(navigateIntent);
}
- public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType) {
- CompassActivity.startActivity(context, geocode, displayedName, coords, coordinatesWithType, null);
+ public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords) {
+ startActivity(context, geocode, displayedName, coords, null);
}
- public static void startActivity(final Context context, final Geocache cache) {
- startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(), null,
+ public static void startActivityCache(final Context context, final Geocache cache) {
+ startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(),
Formatter.formatCacheInfoShort(cache));
}
diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java
index ffcf81b..cf6d486 100644
--- a/main/src/cgeo/geocaching/CreateShortcutActivity.java
+++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java
@@ -1,19 +1,54 @@
package cgeo.geocaching;
import cgeo.geocaching.activity.AbstractActionBarActivity;
-import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.maps.MapActivity;
+import cgeo.geocaching.ui.dialog.Dialogs;
+import cgeo.geocaching.ui.dialog.Dialogs.ItemWithIcon;
+import cgeo.geocaching.utils.ImageUtils;
import rx.functions.Action1;
import android.content.Intent;
import android.content.Intent.ShortcutIconResource;
+import android.graphics.Bitmap;
+import android.graphics.drawable.Drawable;
+import android.graphics.drawable.LayerDrawable;
import android.os.Bundle;
+import java.util.ArrayList;
+import java.util.List;
+
public class CreateShortcutActivity extends AbstractActionBarActivity {
+ private static class Shortcut implements ItemWithIcon {
+
+ private final int titleResourceId;
+ private final int drawableResourceId;
+ private final Intent intent;
+
+ /**
+ * shortcut with a separate icon
+ */
+ public Shortcut(final int titleResourceId, final int drawableResourceId, final Intent intent) {
+ this.titleResourceId = titleResourceId;
+ this.drawableResourceId = drawableResourceId;
+ this.intent = intent;
+ }
+
+ @Override
+ public int getIcon() {
+ return drawableResourceId;
+ }
+
+ @Override
+ public String toString() {
+ return CgeoApplication.getInstance().getString(titleResourceId);
+ }
+ }
+
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// init
@@ -23,35 +58,82 @@ public class CreateShortcutActivity extends AbstractActionBarActivity {
}
private void promptForShortcut() {
+ final List<Shortcut> shortcuts = new ArrayList<>();
+
+ shortcuts.add(new Shortcut(R.string.live_map_button, R.drawable.main_live, new Intent(this, MapActivity.class)));
+ shortcuts.add(new Shortcut(R.string.caches_nearby_button, R.drawable.main_nearby, CacheListActivity.getNearestIntent(this)));
+
+ // TODO: make logging activities ask for cache/trackable when being invoked externally
+ // shortcuts.add(new Shortcut(R.string.cache_menu_visit, new Intent(this, LogCacheActivity.class)));
+ // shortcuts.add(new Shortcut(R.string.trackable_log_touch, new Intent(this, LogTrackableActivity.class)));
+
+ final Shortcut offlineShortcut = new Shortcut(R.string.stored_caches_button, R.drawable.main_stored, null);
+ shortcuts.add(offlineShortcut);
+ shortcuts.add(new Shortcut(R.string.advanced_search_button, R.drawable.main_search, new Intent(this, SearchActivity.class)));
+ shortcuts.add(new Shortcut(R.string.any_button, R.drawable.main_any, new Intent(this, NavigateAnyPointActivity.class)));
+ shortcuts.add(new Shortcut(R.string.menu_history, R.drawable.main_stored, CacheListActivity.getHistoryIntent(this)));
+
+ Dialogs.select(this, getString(R.string.create_shortcut), shortcuts, new Action1<Shortcut>() {
+
+ @Override
+ public void call(final Shortcut shortcut) {
+ if (shortcut == offlineShortcut) {
+ promptForListShortcut();
+ }
+ else {
+ createShortcutAndFinish(shortcut.toString(), shortcut.intent, shortcut.drawableResourceId);
+ }
+ }
+ });
+ }
+
+ protected void promptForListShortcut() {
new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new Action1<Integer>() {
@Override
public void call(final Integer listId) {
- final Intent shortcut = createShortcut(listId);
- setResult(RESULT_OK, shortcut);
-
- // finish activity to return the shortcut
- finish();
+ createOfflineListShortcut(listId.intValue());
}
- }, false, PseudoList.HISTORY_LIST.id);
+ }, true, -1);
}
- protected Intent createShortcut(int listId) {
+ protected void createOfflineListShortcut(final int listId) {
final StoredList list = DataStore.getList(listId);
if (list == null) {
- return null;
+ return;
}
// target to be executed by the shortcut
final Intent targetIntent = new Intent(this, CacheListActivity.class);
targetIntent.putExtra(Intents.EXTRA_LIST_ID, list.id);
- final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, R.drawable.cgeo);
// shortcut to be returned
+ createShortcutAndFinish(list.title, targetIntent, R.drawable.main_stored);
+ }
+
+ private void createShortcutAndFinish(final String title, final Intent targetIntent, final int iconResourceId) {
final Intent intent = new Intent();
intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent);
- intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, list.title);
- intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
- return intent;
+ intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title);
+ if (iconResourceId == R.drawable.cgeo) {
+ final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, iconResourceId);
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource);
+ }
+ else {
+ intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createOverlay(iconResourceId));
+ }
+
+ setResult(RESULT_OK, intent);
+
+ // finish activity to return the shortcut
+ finish();
+ }
+
+ private Bitmap createOverlay(final int drawableResourceId) {
+ final LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] {
+ res.getDrawable(drawableResourceId), res.getDrawable(R.drawable.cgeo) });
+ layerDrawable.setLayerInset(0, 0, 0, 10, 10);
+ layerDrawable.setLayerInset(1, 50, 50, 0, 0);
+ return ImageUtils.convertToBitmap(layerDrawable);
}
}
diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java
index e404b22..9ee588b 100644
--- a/main/src/cgeo/geocaching/DataStore.java
+++ b/main/src/cgeo/geocaching/DataStore.java
@@ -58,6 +58,7 @@ import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Date;
+import java.util.EnumMap;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
@@ -111,35 +112,32 @@ public class DataStore {
"cg_caches.direction," + // 16
"cg_caches.distance," + // 17
"cg_caches.terrain," + // 18
- "cg_caches.latlon," + // 19
- "cg_caches.location," + // 20
- "cg_caches.personal_note," + // 21
- "cg_caches.shortdesc," + // 22
- "cg_caches.favourite_cnt," + // 23
- "cg_caches.rating," + // 24
- "cg_caches.votes," + // 25
- "cg_caches.myvote," + // 26
- "cg_caches.disabled," + // 27
- "cg_caches.archived," + // 28
- "cg_caches.members," + // 29
- "cg_caches.found," + // 30
- "cg_caches.favourite," + // 31
- "cg_caches.inventoryunknown," + // 32
- "cg_caches.onWatchlist," + // 33
- "cg_caches.reliable_latlon," + // 34
- "cg_caches.coordsChanged," + // 35
- "cg_caches.latitude," + // 36
- "cg_caches.longitude," + // 37
- "cg_caches.finalDefined," + // 38
- "cg_caches._id," + // 39
- "cg_caches.inventorycoins," + // 40
- "cg_caches.inventorytags," + // 41
- "cg_caches.logPasswordRequired"; // 42
-
- //TODO: remove "latlon" field from cache table
+ "cg_caches.location," + // 19
+ "cg_caches.personal_note," + // 20
+ "cg_caches.shortdesc," + // 21
+ "cg_caches.favourite_cnt," + // 22
+ "cg_caches.rating," + // 23
+ "cg_caches.votes," + // 24
+ "cg_caches.myvote," + // 25
+ "cg_caches.disabled," + // 26
+ "cg_caches.archived," + // 27
+ "cg_caches.members," + // 28
+ "cg_caches.found," + // 29
+ "cg_caches.favourite," + // 30
+ "cg_caches.inventoryunknown," + // 31
+ "cg_caches.onWatchlist," + // 32
+ "cg_caches.reliable_latlon," + // 33
+ "cg_caches.coordsChanged," + // 34
+ "cg_caches.latitude," + // 35
+ "cg_caches.longitude," + // 36
+ "cg_caches.finalDefined," + // 37
+ "cg_caches._id," + // 38
+ "cg_caches.inventorycoins," + // 39
+ "cg_caches.inventorytags," + // 40
+ "cg_caches.logPasswordRequired"; // 41
/** The list of fields needed for mapping. */
- private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own", "visited" };
+ private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latitude", "longitude", "note", "own", "visited" };
/** Number of days (as ms) after temporarily saved caches are deleted */
private final static long DAYS_AFTER_CACHE_IS_DELETED = 3 * 24 * 60 * 60 * 1000;
@@ -183,7 +181,6 @@ public class DataStore {
+ "size text, "
+ "difficulty float, "
+ "terrain float, "
- + "latlon text, "
+ "location text, "
+ "direction double, "
+ "distance double, "
@@ -214,9 +211,7 @@ public class DataStore {
+ "create table " + dbTableLists + " ("
+ "_id integer primary key autoincrement, "
+ "title text not null, "
- + "updated long not null, "
- + "latitude double, "
- + "longitude double "
+ + "updated long not null"
+ "); ";
private static final String dbCreateAttributes = ""
+ "create table " + dbTableAttributes + " ("
@@ -235,7 +230,6 @@ public class DataStore {
+ "prefix text, "
+ "lookup text, "
+ "name text, "
- + "latlon text, "
+ "latitude double, "
+ "longitude double, "
+ "note text, "
@@ -638,7 +632,6 @@ public class DataStore {
+ "size text, "
+ "difficulty float, "
+ "terrain float, "
- + "latlon text, "
+ "location text, "
+ "direction double, "
+ "distance double, "
@@ -665,7 +658,7 @@ public class DataStore {
db.execSQL(dbCreateCachesTemp);
db.execSQL("insert into " + dbTableCachesTemp + " select _id,updated,detailed,detailedupdate,visiteddate,geocode,reason,cacheid,guid,type,name,own,owner,owner_real," +
- "hidden,hint,size,difficulty,terrain,latlon,location,direction,distance,latitude,longitude, 0," +
+ "hidden,hint,size,difficulty,terrain,location,direction,distance,latitude,longitude, 0," +
"personal_note,shortdesc,description,favourite_cnt,rating,votes,myvote,disabled,archived,members,found,favourite,inventorycoins," +
"inventorytags,inventoryunknown,onWatchlist from " + dbTableCaches);
db.execSQL("drop table " + dbTableCaches);
@@ -681,13 +674,12 @@ public class DataStore {
+ "prefix text, "
+ "lookup text, "
+ "name text, "
- + "latlon text, "
+ "latitude double, "
+ "longitude double, "
+ "note text "
+ "); ";
db.execSQL(dbCreateWaypointsTemp);
- db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latlon, latitude, longitude, note from " + dbTableWaypoints);
+ db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latitude, longitude, note from " + dbTableWaypoints);
db.execSQL("drop table " + dbTableWaypoints);
db.execSQL("alter table " + dbTableWaypointsTemp + " rename to " + dbTableWaypoints);
@@ -1009,7 +1001,7 @@ public class DataStore {
}
synchronized (listId) {
listId.bindString(1, value);
- return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST_ID;
+ return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST.id;
}
} catch (final SQLiteDoneException e) {
// Do nothing, it only means we have no information on the cache
@@ -1041,27 +1033,6 @@ public class DataStore {
return null;
}
- public static String getCacheidForGeocode(final String geocode) {
- if (StringUtils.isBlank(geocode)) {
- return null;
- }
- init();
-
- try {
- final SQLiteStatement description = PreparedStatements.getCacheIdOfGeocode();
- synchronized (description) {
- description.bindString(1, geocode);
- return description.simpleQueryForString();
- }
- } catch (final SQLiteDoneException e) {
- // Do nothing, it only means we have no information on the cache
- } catch (final Exception e) {
- Log.e("DataStore.getCacheidForGeocode", e);
- }
-
- return null;
- }
-
/**
* Save/store a cache to the CacheCache
*
@@ -1294,7 +1265,6 @@ public class DataStore {
values.put("prefix", oneWaypoint.getPrefix());
values.put("lookup", oneWaypoint.getLookup());
values.put("name", oneWaypoint.getName());
- values.put("latlon", oneWaypoint.getLatlon());
putCoords(values, oneWaypoint.getCoords());
values.put("note", oneWaypoint.getNote());
values.put("own", oneWaypoint.isUserDefined() ? 1 : 0);
@@ -1373,7 +1343,6 @@ public class DataStore {
values.put("prefix", waypoint.getPrefix());
values.put("lookup", waypoint.getLookup());
values.put("name", waypoint.getName());
- values.put("latlon", waypoint.getLatlon());
putCoords(values, waypoint.getCoords());
values.put("note", waypoint.getNote());
values.put("own", waypoint.isUserDefined() ? 1 : 0);
@@ -1562,7 +1531,7 @@ public class DataStore {
return new HashSet<>();
}
- final Set<Geocache> result = new HashSet<>();
+ final Set<Geocache> result = new HashSet<>(geocodes.size());
final Set<String> remaining = new HashSet<>(geocodes);
if (loadFlags.contains(LoadFlag.CACHE_BEFORE)) {
@@ -1640,7 +1609,7 @@ public class DataStore {
int logIndex = -1;
while (cursor.moveToNext()) {
- final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor);
+ final Geocache cache = createCacheFromDatabaseContent(cursor);
if (loadFlags.contains(LoadFlag.ATTRIBUTES)) {
cache.setAttributes(loadAttributes(cache.getGeocode()));
@@ -1750,25 +1719,25 @@ public class DataStore {
}
cache.setTerrain(cursor.getFloat(18));
// do not set cache.location
- cache.setCoords(getCoords(cursor, 36, 37));
- cache.setPersonalNote(cursor.getString(21));
+ cache.setPersonalNote(cursor.getString(20));
// do not set cache.shortdesc
// do not set cache.description
- cache.setFavoritePoints(cursor.getInt(23));
- cache.setRating(cursor.getFloat(24));
- cache.setVotes(cursor.getInt(25));
- cache.setMyVote(cursor.getFloat(26));
- cache.setDisabled(cursor.getInt(27) == 1);
- cache.setArchived(cursor.getInt(28) == 1);
- cache.setPremiumMembersOnly(cursor.getInt(29) == 1);
- cache.setFound(cursor.getInt(30) == 1);
- cache.setFavorite(cursor.getInt(31) == 1);
- cache.setInventoryItems(cursor.getInt(32));
- cache.setOnWatchlist(cursor.getInt(33) == 1);
- cache.setReliableLatLon(cursor.getInt(34) > 0);
- cache.setUserModifiedCoords(cursor.getInt(35) > 0);
- cache.setFinalDefined(cursor.getInt(38) > 0);
- cache.setLogPasswordRequired(cursor.getInt(42) > 0);
+ cache.setFavoritePoints(cursor.getInt(22));
+ cache.setRating(cursor.getFloat(23));
+ cache.setVotes(cursor.getInt(24));
+ cache.setMyVote(cursor.getFloat(25));
+ cache.setDisabled(cursor.getInt(26) == 1);
+ cache.setArchived(cursor.getInt(27) == 1);
+ cache.setPremiumMembersOnly(cursor.getInt(28) == 1);
+ cache.setFound(cursor.getInt(29) == 1);
+ cache.setFavorite(cursor.getInt(30) == 1);
+ cache.setInventoryItems(cursor.getInt(31));
+ cache.setOnWatchlist(cursor.getInt(32) == 1);
+ cache.setReliableLatLon(cursor.getInt(33) > 0);
+ cache.setUserModifiedCoords(cursor.getInt(34) > 0);
+ cache.setCoords(getCoords(cursor, 35, 36));
+ cache.setFinalDefined(cursor.getInt(37) > 0);
+ cache.setLogPasswordRequired(cursor.getInt(41) > 0);
Log.d("Loading " + cache.toString() + " (" + cache.getListId() + ") from DB");
@@ -1850,7 +1819,6 @@ public class DataStore {
waypoint.setGeocode(cursor.getString(cursor.getColumnIndex("geocode")));
waypoint.setPrefix(cursor.getString(cursor.getColumnIndex("prefix")));
waypoint.setLookup(cursor.getString(cursor.getColumnIndex("lookup")));
- waypoint.setLatlon(cursor.getString(cursor.getColumnIndex("latlon")));
waypoint.setCoords(getCoords(cursor, cursor.getColumnIndex("latitude"), cursor.getColumnIndex("longitude")));
waypoint.setNote(cursor.getString(cursor.getColumnIndex("note")));
@@ -1970,7 +1938,7 @@ public class DataStore {
init();
- final Map<LogType, Integer> logCounts = new HashMap<>();
+ final Map<LogType, Integer> logCounts = new EnumMap<>(LogType.class);
final Cursor cursor = database.query(
dbTableLogCount,
@@ -2866,7 +2834,7 @@ public class DataStore {
final StringBuilder whereExpr = new StringBuilder("geocode in (");
final Iterator<String> iterator = geocodes.iterator();
while (true) {
- whereExpr.append(DatabaseUtils.sqlEscapeString(StringUtils.upperCase(iterator.next())));
+ DatabaseUtils.appendEscapedSQLString(whereExpr, StringUtils.upperCase(iterator.next()));
if (!iterator.hasNext()) {
break;
}
@@ -3003,10 +2971,6 @@ public class DataStore {
return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE guid = ?");
}
- private static SQLiteStatement getCacheIdOfGeocode() {
- return getStatement("cacheIdFromGeocode", "SELECT cacheid FROM " + dbTableCaches + " WHERE geocode = ?");
- }
-
private static SQLiteStatement getGeocodeOfGuid() {
return getStatement("geocodeFromGuid", "SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?");
}
@@ -3018,7 +2982,7 @@ public class DataStore {
}
public static void markDropped(final List<Geocache> caches) {
- moveToList(caches, StoredList.TEMPORARY_LIST_ID);
+ moveToList(caches, StoredList.TEMPORARY_LIST.id);
}
public static Viewport getBounds(final String geocode) {
diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java
index 73cb477..5dccad8 100644
--- a/main/src/cgeo/geocaching/EditWaypointActivity.java
+++ b/main/src/cgeo/geocaching/EditWaypointActivity.java
@@ -89,10 +89,10 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
*/
private Geocache cache;
- private Handler loadWaypointHandler = new Handler() {
+ private final Handler loadWaypointHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
try {
if (waypoint == null) {
Log.d("No waypoint loaded to edit. id= " + id);
@@ -126,7 +126,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
if (own) {
initializeWaypointTypeSelector();
}
- } catch (RuntimeException e) {
+ } catch (final RuntimeException e) {
Log.e("EditWaypointActivity.loadWaypointHandler", e);
} finally {
if (waitDialog != null) {
@@ -138,7 +138,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
};
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.editwaypoint_activity);
if (StringUtils.isBlank(geocode) && id <= 0) {
@@ -159,11 +159,11 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
addWaypoint.setOnClickListener(new SaveWaypointListener());
- List<String> wayPointNames = new ArrayList<>();
- for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) {
+ final List<String> wayPointNames = new ArrayList<>();
+ for (final WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) {
wayPointNames.add(wpt.getL10n());
}
- ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames);
+ final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames);
waypointName.setAdapter(adapter);
if (savedInstanceState != null) {
@@ -189,7 +189,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
disableSuggestions(distanceView);
}
- private void setCoordsModificationVisibility(IConnector con, Geocache cache) {
+ private void setCoordsModificationVisibility(final IConnector con, final Geocache cache) {
if (cache != null && (cache.getType() == CacheType.MYSTERY || cache.getType() == CacheType.MULTI)) {
coordinatesGroup.setVisibility(View.VISIBLE);
modifyBoth.setVisibility(con.supportsOwnCoordinates() ? View.VISIBLE : View.GONE);
@@ -205,18 +205,23 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
}
private void initializeWaypointTypeSelector() {
- ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()]));
+ final ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()]));
wpAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
waypointTypeSelector.setAdapter(wpAdapter);
waypointTypeSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@Override
- public void onItemSelected(AdapterView<?> parent, View v, int pos, long id) {
+ public void onItemSelected(final AdapterView<?> parent, final View v, final int pos, final long id) {
+ final String oldDefaultName = waypointTypeSelectorPosition >= 0 ? getDefaultWaypointName(POSSIBLE_WAYPOINT_TYPES.get(waypointTypeSelectorPosition)) : StringUtils.EMPTY;
waypointTypeSelectorPosition = pos;
+ final String currentName = waypointName.getText().toString().trim();
+ if (StringUtils.isBlank(currentName) || oldDefaultName.equals(currentName)) {
+ waypointName.setText(getDefaultWaypointName(getSelectedWaypointType()));
+ }
}
@Override
- public void onNothingSelected(AdapterView<?> parent) {
+ public void onNothingSelected(final AdapterView<?> parent) {
}
});
@@ -277,7 +282,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
waypoint = DataStore.loadWaypoint(id);
loadWaypointHandler.sendMessage(Message.obtain());
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("EditWaypointActivity.loadWaypoint.run", e);
}
}
@@ -286,15 +291,15 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
private class CoordDialogListener implements View.OnClickListener {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
Geopoint gp = null;
try {
gp = new Geopoint(buttonLat.getText().toString(), buttonLon.getText().toString());
- } catch (Geopoint.ParseException e) {
+ } catch (final Geopoint.ParseException e) {
// button text is blank when creating new waypoint
}
- Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
- CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo());
+ final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ final CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo());
coordsDialog.setCancelable(true);
coordsDialog.show(getSupportFragmentManager(),"wpeditdialog");
}
@@ -303,11 +308,43 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
}
@Override
- public void updateCoordinates(Geopoint gp) {
+ public void updateCoordinates(final Geopoint gp) {
buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE));
buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE));
}
+ /**
+ * Suffix the waypoint type with a running number to get a default name.
+ *
+ * @param type
+ * type to create a new default name for
+ *
+ * @return
+ */
+ private String getDefaultWaypointName(final WaypointType type) {
+ final ArrayList<String> wpNames = new ArrayList<>();
+ for (final Waypoint waypoint : cache.getWaypoints()) {
+ wpNames.add(waypoint.getName());
+ }
+ // try final and trailhead without index
+ if (type == WaypointType.FINAL || type == WaypointType.TRAILHEAD) {
+ if (!wpNames.contains(type.getL10n())) {
+ return type.getL10n();
+ }
+ }
+ // for other types add an index by default
+ int index = 1;
+ while (wpNames.contains(type.getL10n() + " " + index)) {
+ index++;
+ }
+ return type.getL10n() + " " + index;
+ }
+
+ private WaypointType getSelectedWaypointType() {
+ final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition();
+ return selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType();
+ }
+
public static final int SUCCESS = 0;
public static final int UPLOAD_START = 1;
public static final int UPLOAD_ERROR = 2;
@@ -318,7 +355,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
private class SaveWaypointListener implements View.OnClickListener {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
final String bearingText = bearing.getText().toString();
// combine distance from EditText and distanceUnit saved from Spinner
final String distanceText = distanceView.getText().toString() + distanceUnits.get(distanceUnitSelector.getSelectedItemPosition());
@@ -336,7 +373,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) {
try {
coords = new Geopoint(latText, lonText);
- } catch (Geopoint.ParseException e) {
+ } catch (final Geopoint.ParseException e) {
showToast(res.getString(e.resource));
return;
}
@@ -354,7 +391,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
double bearing;
try {
bearing = Double.parseDouble(bearingText);
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
Dialogs.message(EditWaypointActivity.this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist);
return;
}
@@ -363,7 +400,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
try {
distance = DistanceParser.parseDistance(distanceText,
!Settings.isUseImperialUnits());
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
showToast(res.getString(R.string.err_parse_dist));
return;
}
@@ -371,19 +408,17 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
coords = coords.project(bearing, distance);
}
- // if no name is given, just give the waypoint its number as name
final String givenName = waypointName.getText().toString().trim();
- final String name = StringUtils.isNotEmpty(givenName) ? givenName : res.getString(R.string.waypoint) + " " + (wpCount + 1);
+ final String name = StringUtils.defaultIfBlank(givenName, getDefaultWaypointName(getSelectedWaypointType()));
final String noteText = note.getText().toString().trim();
final Geopoint coordsToSave = coords;
- final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition();
- final WaypointType type = selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType();
+ final WaypointType type = getSelectedWaypointType();
final boolean visited = visitedCheckBox.isChecked();
final ProgressDialog progress = ProgressDialog.show(EditWaypointActivity.this, getString(R.string.cache), getString(R.string.waypoint_being_saved), true);
final Handler finishHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
// TODO: The order of showToast, progress.dismiss and finish is different in these cases. Why?
switch (msg.what) {
case UPLOAD_SUCCESS:
@@ -422,7 +457,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
class SaveWptTask extends AsyncTask<Void, Void, Void> {
@Override
- protected Void doInBackground(Void... params) {
+ protected Void doInBackground(final Void... params) {
final Waypoint waypoint = new Waypoint(name, type, own);
waypoint.setGeocode(geocode);
waypoint.setPrefix(prefix);
@@ -432,12 +467,12 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
waypoint.setVisited(visited);
waypoint.setId(id);
- Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
+ final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS);
if (cache == null) {
finishHandler.sendEmptyMessage(SAVE_ERROR);
return null;
}
- Waypoint oldWaypoint = cache.getWaypointById(id);
+ final Waypoint oldWaypoint = cache.getWaypointById(id);
if (cache.addOrChangeWaypoint(waypoint, true)) {
DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
if (!StaticMapsProvider.hasAllStaticMapsForWaypoint(geocode, waypoint)) {
@@ -460,7 +495,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C
finishHandler.sendEmptyMessage(UPLOAD_START);
if (cache.supportsOwnCoordinates()) {
- boolean result = uploadModifiedCoords(cache, waypoint.getCoords());
+ final boolean result = uploadModifiedCoords(cache, waypoint.getCoords());
finishHandler.sendEmptyMessage(result ? SUCCESS : UPLOAD_ERROR);
} else {
showToast(getString(R.string.waypoint_coordinates_couldnt_be_modified_on_website));
diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java
index d9b2856..8bf64dc 100644
--- a/main/src/cgeo/geocaching/Geocache.java
+++ b/main/src/cgeo/geocaching/Geocache.java
@@ -12,11 +12,9 @@ import cgeo.geocaching.connector.gc.GCConnector;
import cgeo.geocaching.connector.gc.GCConstants;
import cgeo.geocaching.connector.gc.Tile;
import cgeo.geocaching.connector.gc.UncertainProperty;
-import cgeo.geocaching.enumerations.CacheAttribute;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
-import cgeo.geocaching.enumerations.LoadFlags.LoadFlag;
import cgeo.geocaching.enumerations.LoadFlags.RemoveFlag;
import cgeo.geocaching.enumerations.LoadFlags.SaveFlag;
import cgeo.geocaching.enumerations.LogType;
@@ -54,14 +52,12 @@ import rx.functions.Action0;
import android.app.Activity;
import android.content.Intent;
import android.content.res.Resources;
-import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Environment;
import android.os.Handler;
import android.os.Message;
import android.os.Parcelable;
import android.text.Html;
-import android.text.Html.ImageGetter;
import java.io.File;
import java.util.ArrayList;
@@ -69,10 +65,9 @@ import java.util.Calendar;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
+import java.util.EnumMap;
import java.util.EnumSet;
-import java.util.HashMap;
import java.util.HashSet;
-import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
@@ -88,7 +83,7 @@ public class Geocache implements ICache, IWaypoint {
private long updated = 0;
private long detailedUpdate = 0;
private long visitedDate = 0;
- private int listId = StoredList.TEMPORARY_LIST_ID;
+ private int listId = StoredList.TEMPORARY_LIST.id;
private boolean detailed = false;
private String geocode = "";
private String cacheId = "";
@@ -149,7 +144,7 @@ public class Geocache implements ICache, IWaypoint {
private List<Image> spoilers = null;
private List<Trackable> inventory = null;
- private Map<LogType, Integer> logCounts = new HashMap<>();
+ private Map<LogType, Integer> logCounts = new EnumMap<>(LogType.class);
private boolean userModifiedCoords = false;
// temporary values
private boolean statusChecked = false;
@@ -163,10 +158,6 @@ public class Geocache implements ICache, IWaypoint {
private Handler changeNotificationHandler = null;
- // Images whose URL contains one of those patterns will not be available on the Images tab
- // for opening into an external application.
- private final String[] NO_EXTERNAL = new String[]{"geocheck.org"};
-
/**
* Create a new cache. To be used everywhere except for the GPX parser
*/
@@ -251,7 +242,7 @@ public class Geocache implements ICache, IWaypoint {
if (visitedDate == 0) {
visitedDate = other.visitedDate;
}
- if (listId == StoredList.TEMPORARY_LIST_ID) {
+ if (listId == StoredList.TEMPORARY_LIST.id) {
listId = other.listId;
}
if (StringUtils.isBlank(geocode)) {
@@ -455,11 +446,7 @@ public class Geocache implements ICache, IWaypoint {
ActivityMixin.showToast(fromActivity, fromActivity.getResources().getString(R.string.err_cannot_log_visit));
return;
}
- final Intent logVisitIntent = new Intent(fromActivity, LogCacheActivity.class);
- logVisitIntent.putExtra(LogCacheActivity.EXTRAS_ID, cacheId);
- logVisitIntent.putExtra(LogCacheActivity.EXTRAS_GEOCODE, geocode);
-
- fromActivity.startActivity(logVisitIntent);
+ fromActivity.startActivity(LogCacheActivity.getLogCacheIntent(fromActivity, cacheId, geocode));
}
public void logOffline(final Activity fromActivity, final LogType logType) {
@@ -1432,7 +1419,7 @@ public class Geocache implements ICache, IWaypoint {
}
public void store(final CancellableHandler handler) {
- store(StoredList.TEMPORARY_LIST_ID, handler);
+ store(StoredList.TEMPORARY_LIST.id, handler);
}
public void store(final int listId, final CancellableHandler handler) {
@@ -1523,7 +1510,6 @@ public class Geocache implements ICache, IWaypoint {
@Override
public void call() {
refreshSynchronous(handler);
- handler.sendEmptyMessage(CancellableHandler.DONE);
}
});
}
@@ -1614,7 +1600,7 @@ public class Geocache implements ICache, IWaypoint {
RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache), imgGetter.waitForEndObservable(handler));
if (handler != null) {
- handler.sendMessage(Message.obtain());
+ handler.sendEmptyMessage(CancellableHandler.DONE);
}
} catch (final Exception e) {
Log.e("Geocache.storeCache", e);
@@ -1627,7 +1613,7 @@ public class Geocache implements ICache, IWaypoint {
return null;
}
- if (!forceReload && listId == StoredList.TEMPORARY_LIST_ID && (DataStore.isOffline(geocode, guid) || DataStore.isThere(geocode, guid, true, true))) {
+ if (!forceReload && listId == StoredList.TEMPORARY_LIST.id && (DataStore.isOffline(geocode, guid) || DataStore.isThere(geocode, guid, true, true))) {
final SearchResult search = new SearchResult();
final String realGeocode = StringUtils.isNotBlank(geocode) ? geocode : DataStore.getGeocodeForGuid(guid);
search.addGeocode(realGeocode);
@@ -1693,22 +1679,6 @@ public class Geocache implements ICache, IWaypoint {
return null;
}
- /**
- * check whether the cache has a given attribute
- *
- * @param attribute
- * @param yes
- * true if we are looking for the attribute_yes version, false for the attribute_no version
- * @return
- */
- public boolean hasAttribute(final CacheAttribute attribute, final boolean yes) {
- Geocache fullCache = DataStore.loadCache(getGeocode(), EnumSet.of(LoadFlag.ATTRIBUTES));
- if (fullCache == null) {
- fullCache = this;
- }
- return fullCache.getAttributes().contains(attribute.getAttributeName(yes));
- }
-
public boolean hasStaticMap() {
return StaticMapsProvider.hasStaticMap(this);
}
@@ -1720,23 +1690,6 @@ public class Geocache implements ICache, IWaypoint {
}
};
- private void addDescriptionImagesUrls(final Collection<Image> images) {
- final Set<String> urls = new LinkedHashSet<>();
- for (final Image image : images) {
- urls.add(image.getUrl());
- }
- Html.fromHtml(getDescription(), new ImageGetter() {
- @Override
- public Drawable getDrawable(final String source) {
- if (!urls.contains(source) && !ImageUtils.containsPattern(source, NO_EXTERNAL)) {
- images.add(new Image(source, geocode));
- urls.add(source);
- }
- return null;
- }
- }, null);
- }
-
public Collection<Image> getImages() {
final LinkedList<Image> result = new LinkedList<>();
result.addAll(getSpoilers());
@@ -1744,11 +1697,7 @@ public class Geocache implements ICache, IWaypoint {
for (final LogEntry log : getLogs()) {
result.addAll(log.getLogImages());
}
- final Set<String> urls = new HashSet<>(result.size());
- for (final Image image : result) {
- urls.add(image.getUrl());
- }
- addDescriptionImagesUrls(result);
+ ImageUtils.addImagesFromHtml(result, getDescription(), geocode);
return result;
}
diff --git a/main/src/cgeo/geocaching/GpxFileListActivity.java b/main/src/cgeo/geocaching/GpxFileListActivity.java
index dae52c4..3da4927 100644
--- a/main/src/cgeo/geocaching/GpxFileListActivity.java
+++ b/main/src/cgeo/geocaching/GpxFileListActivity.java
@@ -1,63 +1,63 @@
-package cgeo.geocaching;
-
-import cgeo.geocaching.connector.ConnectorFactory;
-import cgeo.geocaching.connector.IConnector;
-import cgeo.geocaching.files.AbstractFileListActivity;
-import cgeo.geocaching.files.GPXImporter;
-import cgeo.geocaching.list.StoredList;
-import cgeo.geocaching.settings.Settings;
-import cgeo.geocaching.ui.GPXListAdapter;
-
-import org.apache.commons.lang3.StringUtils;
-
-import android.app.Activity;
-import android.content.Intent;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter> {
-
- public GpxFileListActivity() {
- super(new String[] { "gpx", "loc", "zip" });
- }
-
- @Override
- protected GPXListAdapter getAdapter(List<File> files) {
- return new GPXListAdapter(this, files);
- }
-
- @Override
- protected List<File> getBaseFolders() {
- return Collections.singletonList(new File(Settings.getGpxImportDir()));
- }
-
- public static void startSubActivity(Activity fromActivity, int listId) {
- final Intent intent = new Intent(fromActivity, GpxFileListActivity.class);
- intent.putExtra(Intents.EXTRA_LIST_ID, StoredList.getConcreteList(listId));
- fromActivity.startActivityForResult(intent, 0);
- }
-
- @Override
- protected boolean filenameBelongsToList(final String filename) {
- if (super.filenameBelongsToList(filename)) {
- if (StringUtils.endsWithIgnoreCase(filename, GPXImporter.ZIP_FILE_EXTENSION)) {
- for (IConnector connector : ConnectorFactory.getConnectors()) {
- if (connector.isZippedGPXFile(filename)) {
- return true;
- }
- }
- return false;
- }
- // filter out waypoint files
- return !StringUtils.containsIgnoreCase(filename, GPXImporter.WAYPOINTS_FILE_SUFFIX);
- }
- return false;
- }
-
- public int getListId() {
- return listId;
- }
-
-}
+package cgeo.geocaching;
+
+import cgeo.geocaching.connector.ConnectorFactory;
+import cgeo.geocaching.connector.IConnector;
+import cgeo.geocaching.files.AbstractFileListActivity;
+import cgeo.geocaching.files.GPXImporter;
+import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.ui.GPXListAdapter;
+
+import org.apache.commons.lang3.StringUtils;
+
+import android.app.Activity;
+import android.content.Intent;
+
+import java.io.File;
+import java.util.Collections;
+import java.util.List;
+
+public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter> {
+
+ public GpxFileListActivity() {
+ super(new String[] { "gpx", "loc", "zip" });
+ }
+
+ @Override
+ protected GPXListAdapter getAdapter(List<File> files) {
+ return new GPXListAdapter(this, files);
+ }
+
+ @Override
+ protected List<File> getBaseFolders() {
+ return Collections.singletonList(new File(Settings.getGpxImportDir()));
+ }
+
+ public static void startSubActivity(Activity fromActivity, int listId) {
+ final Intent intent = new Intent(fromActivity, GpxFileListActivity.class);
+ intent.putExtra(Intents.EXTRA_LIST_ID, StoredList.getConcreteList(listId));
+ fromActivity.startActivityForResult(intent, 0);
+ }
+
+ @Override
+ protected boolean filenameBelongsToList(final String filename) {
+ if (super.filenameBelongsToList(filename)) {
+ if (StringUtils.endsWithIgnoreCase(filename, GPXImporter.ZIP_FILE_EXTENSION)) {
+ for (IConnector connector : ConnectorFactory.getConnectors()) {
+ if (connector.isZippedGPXFile(filename)) {
+ return true;
+ }
+ }
+ return false;
+ }
+ // filter out waypoint files
+ return !StringUtils.containsIgnoreCase(filename, GPXImporter.WAYPOINTS_FILE_SUFFIX);
+ }
+ return false;
+ }
+
+ public int getListId() {
+ return listId;
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java
index a64b4cf..3b4039e 100644
--- a/main/src/cgeo/geocaching/ImageSelectActivity.java
+++ b/main/src/cgeo/geocaching/ImageSelectActivity.java
@@ -48,11 +48,6 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
@InjectView(R.id.cancel) protected Button clearButton;
@InjectView(R.id.image_preview) protected ImageView imagePreview;
- static final String EXTRAS_CAPTION = "caption";
- static final String EXTRAS_DESCRIPTION = "description";
- static final String EXTRAS_URI_AS_STRING = "uri";
- static final String EXTRAS_SCALE = "scale";
-
private static final String SAVED_STATE_IMAGE_CAPTION = "cgeo.geocaching.saved_state_image_caption";
private static final String SAVED_STATE_IMAGE_DESCRIPTION = "cgeo.geocaching.saved_state_image_description";
private static final String SAVED_STATE_IMAGE_URI = "cgeo.geocaching.saved_state_image_uri";
@@ -80,10 +75,10 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
// Get parameters from intent and basic cache information from database
final Bundle extras = getIntent().getExtras();
if (extras != null) {
- imageCaption = extras.getString(EXTRAS_CAPTION);
- imageDescription = extras.getString(EXTRAS_DESCRIPTION);
- imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING));
- scaleChoiceIndex = extras.getInt(EXTRAS_SCALE, scaleChoiceIndex);
+ imageCaption = extras.getString(Intents.EXTRA_CAPTION);
+ imageDescription = extras.getString(Intents.EXTRA_DESCRIPTION);
+ imageUri = Uri.parse(extras.getString(Intents.EXTRA_URI_AS_STRING));
+ scaleChoiceIndex = extras.getInt(Intents.EXTRA_SCALE, scaleChoiceIndex);
}
// Restore previous state
@@ -97,7 +92,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
cameraButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
selectImageFromCamera();
}
});
@@ -105,7 +100,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
storedButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View view) {
+ public void onClick(final View view) {
selectImageFromStorage();
}
});
@@ -123,20 +118,20 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
scaleView.setSelection(scaleChoiceIndex);
scaleView.setOnItemSelectedListener(new OnItemSelectedListener() {
@Override
- public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
+ public void onItemSelected(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) {
scaleChoiceIndex = scaleView.getSelectedItemPosition();
Settings.setLogImageScale(scaleChoiceIndex);
}
@Override
- public void onNothingSelected(AdapterView<?> arg0) {
+ public void onNothingSelected(final AdapterView<?> arg0) {
}
});
saveButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
saveImageInfo(true);
}
});
@@ -144,7 +139,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
clearButton.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View v) {
+ public void onClick(final View v) {
saveImageInfo(false);
}
});
@@ -162,17 +157,17 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
outState.putInt(SAVED_STATE_IMAGE_SCALE, scaleChoiceIndex);
}
- public void saveImageInfo(boolean saveInfo) {
+ public void saveImageInfo(final boolean saveInfo) {
if (saveInfo) {
final String filename = writeScaledImage(imageUri.getPath());
if (filename != null) {
imageUri = Uri.parse(filename);
final Intent intent = new Intent();
syncEditTexts();
- intent.putExtra(EXTRAS_CAPTION, imageCaption);
- intent.putExtra(EXTRAS_DESCRIPTION, imageDescription);
- intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString());
- intent.putExtra(EXTRAS_SCALE, scaleChoiceIndex);
+ intent.putExtra(Intents.EXTRA_CAPTION, imageCaption);
+ intent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription);
+ intent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString());
+ intent.putExtra(Intents.EXTRA_SCALE, scaleChoiceIndex);
setResult(RESULT_OK, intent);
} else {
showToast(res.getString(R.string.err_select_logimage_failed));
@@ -193,7 +188,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
private void selectImageFromCamera() {
// create Intent to take a picture and return control to the calling application
- Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
+ final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE);
imageUri = ImageUtils.getOutputImageFileUri(); // create a file to save the image
if (imageUri == null) {
@@ -207,14 +202,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
}
private void selectImageFromStorage() {
- Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
+ final Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
intent.setType("image/jpeg");
startActivityForResult(Intent.createChooser(intent, "Select Image"), SELECT_STORED_IMAGE);
}
@Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
+ protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (resultCode == RESULT_CANCELED) {
// User cancelled the image capture
showToast(getResources().getString(R.string.info_select_logimage_cancelled));
@@ -232,7 +227,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
if (data != null) {
final Uri selectedImage = data.getData();
if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) {
- String[] filePathColumn = { MediaColumns.DATA };
+ final String[] filePathColumn = { MediaColumns.DATA };
Cursor cursor = null;
try {
@@ -243,14 +238,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
}
cursor.moveToFirst();
- int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
- String filePath = cursor.getString(columnIndex);
+ final int columnIndex = cursor.getColumnIndex(filePathColumn[0]);
+ final String filePath = cursor.getString(columnIndex);
if (StringUtils.isBlank(filePath)) {
showFailure();
return;
}
imageUri = Uri.parse(filePath);
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("ImageSelectActivity.onActivityResult", e);
showFailure();
} finally {
@@ -269,7 +264,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity {
output = new FileOutputStream(outputFile);
LocalStorage.copy(input, output);
imageUri = Uri.fromFile(outputFile);
- } catch (FileNotFoundException e) {
+ } catch (final FileNotFoundException e) {
Log.e("ImageSelectActivity.onStartResult", e);
} finally {
IOUtils.closeQuietly(input);
diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java
index a55c22a..e2b204e 100644
--- a/main/src/cgeo/geocaching/Intents.java
+++ b/main/src/cgeo/geocaching/Intents.java
@@ -1,5 +1,13 @@
package cgeo.geocaching;
+import cgeo.geocaching.enumerations.CacheListType;
+
+import org.apache.commons.lang3.StringUtils;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.content.Intent;
+import android.os.Bundle;
+
public class Intents {
private Intents() {
@@ -18,7 +26,22 @@ public class Intents {
public static final String EXTRA_KEYWORD = PREFIX + "keyword";
public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search";
public static final String EXTRA_LIST_ID = PREFIX + "list_id";
- public static final String EXTRA_LIST_TYPE = PREFIX + "list_type";
+ public static final String EXTRA_CAPTION = PREFIX + "caption";
+ public static final String EXTRA_DESCRIPTION = PREFIX + "description";
+ public static final String EXTRA_URI_AS_STRING = PREFIX + "uri";
+ public static final String EXTRA_SCALE = PREFIX + "scale";
+
+ public static final String EXTRA_WPTTYPE = PREFIX + "wpttype";
+ public static final String EXTRA_MAPSTATE = PREFIX + "mapstate";
+ public static final String EXTRA_MAP_TITLE = PREFIX + "mapTitle";
+ public static final String EXTRA_MAP_MODE = PREFIX + "mapMode";
+ public static final String EXTRA_LIVE_ENABLED = PREFIX + "liveEnabled";
+
+ /**
+ * list type to be used with the cache list activity. Be aware to use the String representation of the corresponding
+ * enum.
+ */
+ private static final String EXTRA_LIST_TYPE = PREFIX + "list_type";
public static final String EXTRA_MAP_FILE = PREFIX + "map_file";
public static final String EXTRA_NAME = PREFIX + "name";
public static final String EXTRA_SEARCH = PREFIX + "search";
@@ -49,4 +72,27 @@ public class Intents {
public static final String EXTRA_OAUTH_TEMP_TOKEN_SECRET_PREF = PREFIX_OAUTH + "tempSecretPref";
public static final String EXTRA_OAUTH_TOKEN_PUBLIC_KEY = PREFIX_OAUTH + "publicTokenPref";
public static final String EXTRA_OAUTH_TOKEN_SECRET_KEY = PREFIX_OAUTH + "secretTokenPref";
+
+ public static Intent putListType(final Intent intent, final @NonNull CacheListType listType) {
+ intent.putExtra(Intents.EXTRA_LIST_TYPE, listType.name());
+ return intent;
+ }
+
+ public static @NonNull CacheListType getListType(final Intent intent) {
+ final Bundle extras = intent.getExtras();
+ if (extras == null) {
+ return CacheListType.OFFLINE;
+ }
+ final String typeName = extras.getString(Intents.EXTRA_LIST_TYPE);
+ if (StringUtils.isBlank(typeName)) {
+ return CacheListType.OFFLINE;
+ }
+ CacheListType listType;
+ try {
+ listType = CacheListType.valueOf(typeName);
+ } catch (final IllegalArgumentException e) {
+ return CacheListType.OFFLINE;
+ }
+ return (listType != null) ? listType : CacheListType.OFFLINE;
+ }
}
diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java
index bc87525..3558b3c 100644
--- a/main/src/cgeo/geocaching/LogCacheActivity.java
+++ b/main/src/cgeo/geocaching/LogCacheActivity.java
@@ -54,8 +54,6 @@ import java.util.Date;
import java.util.List;
public class LogCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent {
- static final String EXTRAS_GEOCODE = "geocode";
- static final String EXTRAS_ID = "id";
private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating";
private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type";
@@ -208,9 +206,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
// Get parameters from intent and basic cache information from database
final Bundle extras = getIntent().getExtras();
if (extras != null) {
- geocode = extras.getString(EXTRAS_GEOCODE);
+ geocode = extras.getString(Intents.EXTRA_GEOCODE);
if (StringUtils.isBlank(geocode)) {
- final String cacheid = extras.getString(EXTRAS_ID);
+ final String cacheid = extras.getString(Intents.EXTRA_ID);
if (StringUtils.isNotBlank(cacheid)) {
geocode = DataStore.getGeocodeForGuid(cacheid);
}
@@ -288,6 +286,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
loggingManager = cache.getLoggingManager(this);
loggingManager.init();
+ requestKeyboardForLogging();
}
private void initializeRatingBar(final RatingBar ratingBar) {
@@ -423,7 +422,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
if (logResult.getPostLogResult() == StatusCode.NO_ERROR) {
// update geocache in DB
- if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED || typeSelected == LogType.WEBCAM_PHOTO_TAKEN) {
+ if (typeSelected.isFoundLog()) {
cache.setFound(true);
cache.setVisitedDate(new Date().getTime());
}
@@ -578,9 +577,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
private void selectImage() {
final Intent selectImageIntent = new Intent(this, ImageSelectActivity.class);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription);
- selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString());
+ selectImageIntent.putExtra(Intents.EXTRA_CAPTION, imageCaption);
+ selectImageIntent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription);
+ selectImageIntent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString());
startActivityForResult(selectImageIntent, SELECT_IMAGE);
}
@@ -589,9 +588,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) {
if (requestCode == SELECT_IMAGE) {
if (resultCode == RESULT_OK) {
- imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION);
- imageDescription = data.getStringExtra(ImageSelectActivity.EXTRAS_DESCRIPTION);
- imageUri = Uri.parse(data.getStringExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING));
+ imageCaption = data.getStringExtra(Intents.EXTRA_CAPTION);
+ imageDescription = data.getStringExtra(Intents.EXTRA_DESCRIPTION);
+ imageUri = Uri.parse(data.getStringExtra(Intents.EXTRA_URI_AS_STRING));
} else if (resultCode != RESULT_CANCELED) {
// Image capture failed, advise user
showToast(getResources().getString(R.string.err_select_logimage_failed));
@@ -603,7 +602,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
public boolean onOptionsItemSelected(final MenuItem item) {
switch (item.getItemId()) {
case R.id.menu_send:
- sendLog();
+ sendLogAndConfirm();
return true;
case R.id.menu_image:
selectImage();
@@ -622,18 +621,32 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
return super.onOptionsItemSelected(item);
}
- private void sendLog() {
+ private void sendLogAndConfirm() {
if (!sendButtonEnabled) {
Dialogs.message(this, R.string.log_post_not_possible);
+ return;
+ }
+ if (typeSelected.mustConfirmLog()) {
+ Dialogs.confirm(this, R.string.confirm_log_title, res.getString(R.string.confirm_log_message, typeSelected.getL10n()), new OnClickListener() {
+
+ @Override
+ public void onClick(final DialogInterface dialog, final int which) {
+ sendLogInternal();
+ }
+ });
}
else {
- final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ?
- R.string.log_saving :
- R.string.log_saving_and_uploading);
- new Poster(this, message).execute(currentLogText(), currentLogPassword());
+ sendLogInternal();
}
}
+ private void sendLogInternal() {
+ final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ?
+ R.string.log_saving :
+ R.string.log_saving_and_uploading);
+ new Poster(this, message).execute(currentLogText(), currentLogPassword());
+ }
+
@Override
public boolean onCreateOptionsMenu(final Menu menu) {
super.onCreateOptionsMenu(menu);
@@ -650,4 +663,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia
.setTarget(new ActionItemTarget(this, R.id.menu_send))
.setContent(R.string.showcase_logcache_title, R.string.showcase_logcache_text);
}
+
+ public static Intent getLogCacheIntent(final Activity context, final String cacheId, final String geocode) {
+ final Intent logVisitIntent = new Intent(context, LogCacheActivity.class);
+ logVisitIntent.putExtra(Intents.EXTRA_ID, cacheId);
+ logVisitIntent.putExtra(Intents.EXTRA_GEOCODE, geocode);
+ return logVisitIntent;
+ }
}
diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java
index 26f7c84..5c6d0f5 100644
--- a/main/src/cgeo/geocaching/LogTrackableActivity.java
+++ b/main/src/cgeo/geocaching/LogTrackableActivity.java
@@ -3,6 +3,7 @@ package cgeo.geocaching;
import butterknife.ButterKnife;
import butterknife.InjectView;
+import cgeo.geocaching.activity.Keyboard;
import cgeo.geocaching.connector.gc.GCLogin;
import cgeo.geocaching.connector.gc.GCParser;
import cgeo.geocaching.enumerations.LogType;
@@ -146,6 +147,17 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
}
init();
+ requestKeyboardForLogging();
+ }
+
+ @Override
+ protected void requestKeyboardForLogging() {
+ if (StringUtils.isBlank(trackingEditText.getText())) {
+ new Keyboard(this).show(trackingEditText);
+ }
+ else {
+ super.requestKeyboardForLogging();
+ }
}
@Override
@@ -309,6 +321,9 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) {
Twitter.postTweetTrackable(geocode, new LogEntry(0, typeSelected, log));
}
+ if (status == StatusCode.NO_ERROR) {
+ addLocalTrackableLog(log);
+ }
return status;
} catch (final Exception e) {
@@ -318,6 +333,20 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat
return StatusCode.LOG_POST_ERROR;
}
+ /**
+ * Adds the new log to the list of log entries for this trackable to be able to show it in the trackable activity.
+ *
+ *
+ * @param logText
+ */
+ private void addLocalTrackableLog(final String logText) {
+ final LogEntry logEntry = new LogEntry(Calendar.getInstance().getTimeInMillis(), typeSelected, logText);
+ final ArrayList<LogEntry> modifiedLogs = new ArrayList<>(trackable.getLogs());
+ modifiedLogs.add(0, logEntry);
+ trackable.setLogs(modifiedLogs);
+ DataStore.saveTrackable(trackable);
+ }
+
public static void startActivity(final Context context, final Trackable trackable) {
final Intent logTouchIntent = new Intent(context, LogTrackableActivity.class);
logTouchIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.getGeocode());
diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java
index c0c6712..40504fb 100644
--- a/main/src/cgeo/geocaching/MainActivity.java
+++ b/main/src/cgeo/geocaching/MainActivity.java
@@ -7,8 +7,6 @@ import cgeo.geocaching.activity.AbstractActionBarActivity;
import cgeo.geocaching.activity.ShowcaseViewBuilder;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.capability.ILogin;
-import cgeo.geocaching.connector.gc.GCConnector;
-import cgeo.geocaching.connector.gc.GCLogin;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.geopoint.Geopoint;
@@ -17,6 +15,8 @@ import cgeo.geocaching.list.PseudoList;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.maps.CGeoMap;
import cgeo.geocaching.sensors.GeoDirHandler;
+import cgeo.geocaching.sensors.GpsStatusProvider;
+import cgeo.geocaching.sensors.GpsStatusProvider.Status;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.settings.SettingsActivity;
@@ -38,8 +38,8 @@ import rx.Observable;
import rx.Observable.OnSubscribe;
import rx.Subscriber;
import rx.android.observables.AndroidObservable;
+import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action1;
-import rx.subscriptions.Subscriptions;
import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
@@ -147,37 +147,16 @@ public class MainActivity extends AbstractActionBarActivity {
return StringUtils.join(addressParts, ", ");
}
- private class SatellitesHandler extends GeoDirHandler {
-
- private boolean gpsEnabled = false;
- private int satellitesFixed = 0;
- private int satellitesVisible = 0;
-
+ private final Action1<GpsStatusProvider.Status> satellitesHandler = new Action1<Status>() {
@Override
- public void updateGeoData(final IGeoData data) {
- if (data.getGpsEnabled() == gpsEnabled &&
- data.getSatellitesFixed() == satellitesFixed &&
- data.getSatellitesVisible() == satellitesVisible) {
- return;
- }
- gpsEnabled = data.getGpsEnabled();
- satellitesFixed = data.getSatellitesFixed();
- satellitesVisible = data.getSatellitesVisible();
-
- if (gpsEnabled) {
- if (satellitesFixed > 0) {
- navSatellites.setText(res.getString(R.string.loc_sat) + ": " + satellitesFixed + '/' + satellitesVisible);
- } else if (satellitesVisible >= 0) {
- navSatellites.setText(res.getString(R.string.loc_sat) + ": 0/" + satellitesVisible);
- }
+ public void call(final Status gpsStatus) {
+ if (gpsStatus.gpsEnabled) {
+ navSatellites.setText(res.getString(R.string.loc_sat) + ": " + gpsStatus.satellitesFixed + '/' + gpsStatus.satellitesVisible);
} else {
navSatellites.setText(res.getString(R.string.loc_gps_disabled));
}
}
-
- }
-
- private final SatellitesHandler satellitesHandler = new SatellitesHandler();
+ };
private final Handler firstLoginHandler = new Handler() {
@@ -231,8 +210,12 @@ public class MainActivity extends AbstractActionBarActivity {
@Override
public void onResume() {
- super.onResume(Subscriptions.from(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA), satellitesHandler.start(GeoDirHandler.UPDATE_GEODATA)));
+ super.onResume(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.LOW_POWER),
+ app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(satellitesHandler));
updateUserInfoHandler.sendEmptyMessage(-1);
+ if (app.hasValidLocation()) {
+ locationUpdater.updateGeoData(app.currentGeo());
+ }
startBackgroundLogin();
init();
}
@@ -247,9 +230,9 @@ public class MainActivity extends AbstractActionBarActivity {
new Thread() {
@Override
public void run() {
- if (mustLogin && conn == GCConnector.getInstance()) {
+ if (mustLogin) {
// Properly log out from geocaching.com
- GCLogin.getInstance().logout();
+ conn.logout();
}
conn.login(firstLoginHandler, MainActivity.this);
updateUserInfoHandler.sendEmptyMessage(-1);
@@ -315,7 +298,7 @@ public class MainActivity extends AbstractActionBarActivity {
startActivity(new Intent(this, SettingsActivity.class));
return true;
case R.id.menu_history:
- CacheListActivity.startActivityHistory(this);
+ startActivity(CacheListActivity.getHistoryIntent(this));
return true;
case R.id.menu_scan:
startScannerApplication();
@@ -578,7 +561,7 @@ public class MainActivity extends AbstractActionBarActivity {
}
}
});
- AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.from(geo.getCoords().toString())))
+ AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.just(geo.getCoords().toString())))
.subscribeOn(RxUtils.networkScheduler)
.subscribe(new Action1<String>() {
@Override
@@ -599,7 +582,7 @@ public class MainActivity extends AbstractActionBarActivity {
*/
public void cgeoFindOnMap(final View v) {
findOnMap.setPressed(true);
- CGeoMap.startActivityLiveMap(this);
+ startActivity(CGeoMap.getLiveMapIntent(this));
}
/**
@@ -612,7 +595,7 @@ public class MainActivity extends AbstractActionBarActivity {
}
nearestView.setPressed(true);
- CacheListActivity.startActivityNearest(this, app.currentGeo().getCoords());
+ startActivity(CacheListActivity.getNearestIntent(this));
}
/**
@@ -733,12 +716,18 @@ public class MainActivity extends AbstractActionBarActivity {
}
private void checkShowChangelog() {
- final long lastChecksum = Settings.getLastChangelogChecksum();
- final long checksum = TextUtils.checksum(getString(R.string.changelog_master) + getString(R.string.changelog_release));
- Settings.setLastChangelogChecksum(checksum);
- // don't show change log after new install...
- if (lastChecksum > 0 && lastChecksum != checksum) {
- AboutActivity.showChangeLog(this);
+ // temporary workaround for #4143
+ //TODO: understand and avoid if possible
+ try {
+ final long lastChecksum = Settings.getLastChangelogChecksum();
+ final long checksum = TextUtils.checksum(getString(R.string.changelog_master) + getString(R.string.changelog_release));
+ Settings.setLastChangelogChecksum(checksum);
+ // don't show change log after new install...
+ if (lastChecksum > 0 && lastChecksum != checksum) {
+ AboutActivity.showChangeLog(this);
+ }
+ } catch (final Exception ex) {
+ Log.e("Error checking/showing changelog!", ex);
}
}
diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java
index 74cc59d..6015872 100644
--- a/main/src/cgeo/geocaching/SearchResult.java
+++ b/main/src/cgeo/geocaching/SearchResult.java
@@ -320,7 +320,7 @@ public class SearchResult implements Parcelable {
return cObservable.flatMap(new Func1<C, Observable<? extends SearchResult>>() {
@Override
public Observable<? extends SearchResult> call(final C c) {
- return c.isActive() ? Observable.from(func.call(c)) : Observable.<SearchResult>empty();
+ return c.isActive() ? Observable.just(func.call(c)) : Observable.<SearchResult>empty();
}
});
}
diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java
index dc898d7..da41250 100644
--- a/main/src/cgeo/geocaching/SelectMapfileActivity.java
+++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java
@@ -33,7 +33,7 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio
private String mapFile;
- private static int REQUEST_DIRECTORY = 1;
+ private final static int REQUEST_DIRECTORY = 1;
@Override
public void onCreate(Bundle savedInstanceState) {
@@ -98,8 +98,8 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio
}
@Override
- public void setCurrentFile(String newFile) {
- mapFile = newFile;
+ public void setCurrentFile(String name) {
+ mapFile = name;
}
@Override
diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java
index a228363..3fd4434 100644
--- a/main/src/cgeo/geocaching/StatusFragment.java
+++ b/main/src/cgeo/geocaching/StatusFragment.java
@@ -9,7 +9,6 @@ import cgeo.geocaching.utils.Log;
import rx.Subscription;
import rx.android.observables.AndroidObservable;
import rx.functions.Action1;
-import rx.schedulers.Schedulers;
import rx.subscriptions.Subscriptions;
import android.content.Intent;
@@ -34,7 +33,7 @@ public class StatusFragment extends Fragment {
final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false);
final ImageView statusIcon = ButterKnife.findById(statusGroup, R.id.status_icon);
final TextView statusMessage = ButterKnife.findById(statusGroup, R.id.status_message);
- statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribeOn(Schedulers.io())
+ statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus)
.subscribe(new Action1<Status>() {
@Override
public void call(final Status status) {
diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java
index 9c2b044..fe53109 100644
--- a/main/src/cgeo/geocaching/Trackable.java
+++ b/main/src/cgeo/geocaching/Trackable.java
@@ -3,13 +3,16 @@ package cgeo.geocaching;
import cgeo.geocaching.connector.ConnectorFactory;
import cgeo.geocaching.connector.trackable.TrackableConnector;
import cgeo.geocaching.enumerations.LogType;
+import cgeo.geocaching.utils.ImageUtils;
import org.apache.commons.lang3.StringUtils;
import android.text.Html;
import java.util.ArrayList;
+import java.util.Collection;
import java.util.Date;
+import java.util.LinkedList;
import java.util.List;
public class Trackable implements ILogable {
@@ -214,6 +217,18 @@ public class Trackable implements ILogable {
this.trackingcode = trackingcode;
}
+ public Collection<Image> getImages() {
+ final List<Image> images = new LinkedList<>();
+ if (StringUtils.isNotBlank(image)) {
+ images.add(new Image(image, StringUtils.defaultIfBlank(name, geocode)));
+ }
+ ImageUtils.addImagesFromHtml(images, getDetails(), geocode);
+ for (final LogEntry log : getLogs()) {
+ images.addAll(log.getLogImages());
+ }
+ return images;
+ }
+
static public List<LogType> getPossibleLogTypes() {
final List<LogType> logTypes = new ArrayList<>();
logTypes.add(LogType.RETRIEVED_IT);
diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java
index 41b2d92..81516c3 100644
--- a/main/src/cgeo/geocaching/TrackableActivity.java
+++ b/main/src/cgeo/geocaching/TrackableActivity.java
@@ -15,29 +15,33 @@ import cgeo.geocaching.network.HtmlImage;
import cgeo.geocaching.ui.AbstractCachingPageViewCreator;
import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod;
import cgeo.geocaching.ui.CacheDetailsCreator;
+import cgeo.geocaching.ui.ImagesList;
import cgeo.geocaching.ui.UserActionsClickListener;
import cgeo.geocaching.ui.UserNameClickListener;
import cgeo.geocaching.ui.logs.TrackableLogsViewCreator;
import cgeo.geocaching.utils.Formatter;
import cgeo.geocaching.utils.HtmlUtils;
import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
import cgeo.geocaching.utils.UnknownTagsHandler;
+import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.apache.commons.lang3.tuple.Pair;
+import rx.Observable;
import rx.android.observables.AndroidObservable;
import rx.android.observables.ViewObservable;
import rx.functions.Action1;
+import rx.functions.Func0;
+import rx.subscriptions.CompositeSubscription;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.drawable.BitmapDrawable;
import android.net.Uri;
import android.os.Bundle;
-import android.os.Handler;
-import android.os.Message;
import android.support.v7.app.ActionBar;
import android.support.v7.view.ActionMode;
import android.text.Html;
@@ -59,9 +63,12 @@ import java.util.Locale;
public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivity.Page> implements ActivitySharingInterface {
+ private CompositeSubscription createSubscriptions;
+
public enum Page {
DETAILS(R.string.detail),
- LOGS(R.string.cache_logs);
+ LOGS(R.string.cache_logs),
+ IMAGES(R.string.cache_images);
private final int resId;
@@ -77,50 +84,9 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
private String id = null;
private LayoutInflater inflater = null;
private ProgressDialog waitDialog = null;
- private final Handler loadTrackableHandler = new Handler() {
-
- @Override
- public void handleMessage(final Message msg) {
- if (trackable == null) {
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- if (StringUtils.isNotBlank(geocode)) {
- showToast(res.getString(R.string.err_tb_find) + " " + geocode + ".");
- } else {
- showToast(res.getString(R.string.err_tb_find_that));
- }
-
- finish();
- return;
- }
-
- try {
- inflater = getLayoutInflater();
- geocode = trackable.getGeocode();
-
- if (StringUtils.isNotBlank(trackable.getName())) {
- setTitle(Html.fromHtml(trackable.getName()).toString());
- } else {
- setTitle(trackable.getName());
- }
-
- invalidateOptionsMenuCompatible();
- reinitializeViewPager();
-
- } catch (final Exception e) {
- Log.e("TrackableActivity.loadTrackableHandler: ", e);
- }
-
- if (waitDialog != null) {
- waitDialog.dismiss();
- }
-
- }
- };
-
private CharSequence clickedItemText = null;
+ private ImagesList imagesList = null;
+
/**
* Action mode of the current contextual action bar (e.g. for copy and share actions).
*/
@@ -201,15 +167,28 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
} else {
message = res.getString(R.string.trackable);
}
- waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true);
-
// If we have a newer Android device setup Android Beam for easy cache sharing
initializeAndroidBeam(this);
- createViewPager(0, null);
- final LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id);
- thread.start();
+ createViewPager(0, new OnPageSelectedListener() {
+ @Override
+ public void onPageSelected(final int position) {
+ // Lazy loading of trackable images
+ if (getPage(position) == Page.IMAGES) {
+ loadTrackableImages();
+ }
+ }
+ });
+ waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true);
+ createSubscriptions = new CompositeSubscription();
+ createSubscriptions.add(AndroidObservable.bindActivity(this, loadTrackable(geocode, guid, id)).singleOrDefault(null).subscribe(new Action1<Trackable>() {
+ @Override
+ public void call(final Trackable trackable) {
+ TrackableActivity.this.trackable = trackable;
+ displayTrackable();
+ }
+ }));
}
@Override
@@ -245,87 +224,85 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
return super.onPrepareOptionsMenu(menu);
}
- private class LoadTrackableThread extends Thread {
- final private Handler handler;
- final private String geocode;
- final private String guid;
- final private String id;
-
- public LoadTrackableThread(final Handler handlerIn, final String geocodeIn, final String guidIn, final String idIn) {
- handler = handlerIn;
- geocode = geocodeIn;
- guid = guidIn;
- id = idIn;
- }
-
- @Override
- public void run() {
- if (StringUtils.isNotEmpty(geocode)) {
-
- // iterate over the connectors as some codes may be handled by multiple connectors
- for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) {
- if (trackableConnector.canHandleTrackable(geocode)) {
- trackable = trackableConnector.searchTrackable(geocode, guid, id);
- if (trackable != null) {
- break;
+ private static Observable<Trackable> loadTrackable(final String geocode, final String guid, final String id) {
+ return Observable.defer(new Func0<Observable<Trackable>>() {
+ @Override
+ public Observable<Trackable> call() {
+ if (StringUtils.isNotEmpty(geocode)) {
+ // iterate over the connectors as some codes may be handled by multiple connectors
+ for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) {
+ if (trackableConnector.canHandleTrackable(geocode)) {
+ final Trackable trackable = trackableConnector.searchTrackable(geocode, guid, id);
+ if (trackable != null) {
+ return Observable.just(trackable);
+ }
}
}
+ // Check local storage (offline case)
+ final Trackable trackable = DataStore.loadTrackable(geocode);
+ if (trackable != null) {
+ return Observable.just(trackable);
+ }
}
- // Check local storage (offline case)
- if (trackable == null) {
- trackable = DataStore.loadTrackable(geocode);
- }
- }
- // fall back to GC search by GUID
- if (trackable == null) {
- trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id);
+
+ // Fall back to GC search by GUID
+ final Trackable trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id);
+ return trackable != null ? Observable.just(trackable) : Observable.<Trackable>empty();
}
- handler.sendMessage(Message.obtain());
- }
+ }).subscribeOn(RxUtils.networkScheduler);
}
- private class TrackableIconThread extends Thread {
- final private String url;
- final private Handler handler;
+ public void displayTrackable() {
+ if (trackable == null) {
+ if (waitDialog != null) {
+ waitDialog.dismiss();
+ }
+
+ if (StringUtils.isNotBlank(geocode)) {
+ showToast(res.getString(R.string.err_tb_find) + " " + geocode + ".");
+ } else {
+ showToast(res.getString(R.string.err_tb_find_that));
+ }
- public TrackableIconThread(final String urlIn, final Handler handlerIn) {
- url = urlIn;
- handler = handlerIn;
+ finish();
+ return;
}
- @Override
- public void run() {
- if (url == null || handler == null) {
- return;
+ try {
+ inflater = getLayoutInflater();
+ geocode = trackable.getGeocode();
+
+ if (StringUtils.isNotBlank(trackable.getName())) {
+ setTitle(Html.fromHtml(trackable.getName()).toString());
+ } else {
+ setTitle(trackable.getName());
}
- try {
- final HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false);
+ invalidateOptionsMenuCompatible();
+ reinitializeViewPager();
- final BitmapDrawable image = imgGetter.getDrawable(url);
- final Message message = handler.obtainMessage(0, image);
- handler.sendMessage(message);
- } catch (final Exception e) {
- Log.e("TrackableActivity.TrackableIconThread.run: ", e);
- }
+ } catch (final Exception e) {
+ Log.e("TrackableActivity.loadTrackableHandler: ", e);
}
- }
-
- private static class TrackableIconHandler extends Handler {
- final private ActionBar view;
- public TrackableIconHandler(final ActionBar viewIn) {
- view = viewIn;
+ if (waitDialog != null) {
+ waitDialog.dismiss();
}
- @Override
- public void handleMessage(final Message message) {
- final BitmapDrawable image = (BitmapDrawable) message.obj;
- if (image != null && view != null) {
- image.setBounds(0, 0, view.getHeight(), view.getHeight());
- view.setIcon(image);
+ }
+
+ private void setupIcon(final ActionBar actionBar, final String url) {
+ final HtmlImage imgGetter = new HtmlImage(HtmlImage.SHARED, false, 0, false);
+ AndroidObservable.bindActivity(this, imgGetter.fetchDrawable(url)).subscribe(new Action1<BitmapDrawable>() {
+ @Override
+ public void call(final BitmapDrawable image) {
+ if (actionBar != null) {
+ final int height = actionBar.getHeight();
+ image.setBounds(0, 0, height, height);
+ actionBar.setIcon(image);
+ }
}
- }
+ });
}
public static void startActivity(final AbstractActivity fromContext,
@@ -343,11 +320,38 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
case DETAILS:
return new DetailsViewCreator();
case LOGS:
- return new TrackableLogsViewCreator(this, trackable);
+ return new TrackableLogsViewCreator(this);
+ case IMAGES:
+ return new ImagesViewCreator();
}
throw new IllegalStateException(); // cannot happen as long as switch case is enum complete
}
+ private class ImagesViewCreator extends AbstractCachingPageViewCreator<View> {
+
+ @Override
+ public View getDispatchedView(final ViewGroup parentView) {
+ view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, parentView, false);
+ return view;
+ }
+ }
+
+ private void loadTrackableImages() {
+ if (imagesList != null) {
+ return;
+ }
+ final PageViewCreator creator = getViewCreator(Page.IMAGES);
+ if (creator == null) {
+ return;
+ }
+ final View imageView = creator.getView(null);
+ if (imageView == null) {
+ return;
+ }
+ imagesList = new ImagesList(this, trackable.getGeocode());
+ createSubscriptions.add(imagesList.loadImages(imageView, trackable.getImages(), false));
+ }
+
@Override
protected String getTitle(final Page page) {
return res.getString(page.resId);
@@ -357,9 +361,12 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
protected Pair<List<? extends Page>, Integer> getOrderedPages() {
final List<Page> pages = new ArrayList<>();
pages.add(Page.DETAILS);
- if (!trackable.getLogs().isEmpty()) {
+ if (CollectionUtils.isNotEmpty(trackable.getLogs())) {
pages.add(Page.LOGS);
}
+ if (CollectionUtils.isNotEmpty(trackable.getImages())) {
+ pages.add(Page.IMAGES);
+ }
return new ImmutablePair<List<? extends Page>, Integer>(pages, 0);
}
@@ -382,9 +389,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
// action bar icon
if (StringUtils.isNotBlank(trackable.getIconUrl())) {
- final TrackableIconHandler iconHandler = new TrackableIconHandler(getSupportActionBar());
- final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler);
- iconThread.start();
+ setupIcon(getSupportActionBar(), trackable.getIconUrl());
}
// trackable name
@@ -433,8 +438,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
if (showTimeSpan && trackable.getLogs() != null) {
for (final LogEntry log : trackable.getLogs()) {
if (log.type == LogType.RETRIEVED_IT || log.type == LogType.GRABBED_IT || log.type == LogType.DISCOVERED_IT || log.type == LogType.PLACED_IT) {
- final int days = log.daysSinceLog();
- text.append(" (").append(res.getQuantityString(R.plurals.days_ago, days, days)).append(')');
+ text.append(" (").append(Formatter.formatDaysAgo(log.date)).append(')');
break;
}
}
@@ -595,4 +599,24 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi
return false;
}
+ @Override
+ protected void onResume() {
+ super.onResume();
+ // refresh the logs view after coming back from logging a trackable
+ if (trackable != null) {
+ final Trackable updatedTrackable = DataStore.loadTrackable(trackable.getGeocode());
+ trackable.setLogs(updatedTrackable.getLogs());
+ reinitializeViewPager();
+ }
+ }
+
+ @Override
+ protected void onDestroy() {
+ createSubscriptions.unsubscribe();
+ super.onDestroy();
+ }
+
+ public Trackable getTrackable() {
+ return trackable;
+ }
}
diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java
index 7381aab..b2c5305 100644
--- a/main/src/cgeo/geocaching/Waypoint.java
+++ b/main/src/cgeo/geocaching/Waypoint.java
@@ -21,13 +21,13 @@ public class Waypoint implements IWaypoint {
public static final String PREFIX_OWN = "OWN";
private static final int ORDER_UNDEFINED = -2;
+ private static final Pattern PATTERN_COORDS = Pattern.compile("\\b[nNsS]\\s*\\d");
private int id = -1;
private String geocode = "geocode";
private WaypointType waypointType = WaypointType.WAYPOINT;
private String prefix = "";
private String lookup = "";
private String name = "";
- private String latlon = "";
private Geopoint coords = null;
private String note = "";
private int cachedOrder = ORDER_UNDEFINED;
@@ -67,9 +67,6 @@ public class Waypoint implements IWaypoint {
if (StringUtils.isBlank(name)) {
setName(old.name);
}
- if (StringUtils.isBlank(latlon) || latlon.startsWith("?")) { // there are waypoints containing "???"
- latlon = old.latlon;
- }
if (coords == null) {
coords = old.coords;
}
@@ -204,14 +201,6 @@ public class Waypoint implements IWaypoint {
this.name = name;
}
- public String getLatlon() {
- return latlon;
- }
-
- public void setLatlon(final String latlon) {
- this.latlon = latlon;
- }
-
@Override
public Geopoint getCoords() {
return coords;
@@ -303,10 +292,9 @@ public class Waypoint implements IWaypoint {
*/
public static Collection<Waypoint> parseWaypointsFromNote(@NonNull final String initialNote) {
final List<Waypoint> waypoints = new LinkedList<>();
- final Pattern COORDPATTERN = Pattern.compile("\\b[nNsS]{1}\\s*\\d"); // begin of coordinates
String note = initialNote;
- MatcherWrapper matcher = new MatcherWrapper(COORDPATTERN, note);
+ MatcherWrapper matcher = new MatcherWrapper(PATTERN_COORDS, note);
int count = 1;
while (matcher.find()) {
try {
@@ -321,12 +309,11 @@ public class Waypoint implements IWaypoint {
waypoints.add(waypoint);
count++;
}
- } catch (final Geopoint.ParseException e) {
- // ignore
+ } catch (final Geopoint.ParseException ignore) {
}
note = note.substring(matcher.start() + 1);
- matcher = new MatcherWrapper(COORDPATTERN, note);
+ matcher = new MatcherWrapper(PATTERN_COORDS, note);
}
return waypoints;
}
diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java
index 4fe750a..603211e 100644
--- a/main/src/cgeo/geocaching/activity/AbstractActivity.java
+++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java
@@ -96,9 +96,9 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
return super.onOptionsItemSelected(item);
}
- public void onResume(final Subscription resumeSubscription) {
+ public void onResume(final Subscription... resumeSubscriptions) {
super.onResume();
- this.resumeSubscription = resumeSubscription;
+ this.resumeSubscription = Subscriptions.from(resumeSubscriptions);
}
@Override
@@ -216,7 +216,7 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs
}
protected void initializeAndroidBeam(final ActivitySharingInterface sharingInterface) {
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
+ if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) {
initializeICSAndroidBeam(sharingInterface);
}
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
index 6c6ffda..f31d175 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java
@@ -9,6 +9,9 @@ import android.content.Intent;
public abstract class AbstractRadarApp extends AbstractPointNavigationApp {
+ protected static final String RADAR_EXTRA_LONGITUDE = "longitude";
+ protected static final String RADAR_EXTRA_LATITUDE = "latitude";
+
private final String intentAction;
protected AbstractRadarApp(final String name, final int id, final String intent, final String packageName) {
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
index 03d2220..743ce1f 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java
@@ -20,19 +20,19 @@ class CompassApp extends AbstractPointNavigationApp {
}
@Override
- public void navigate(Activity activity, Geopoint coords) {
- CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords, null);
+ public void navigate(final Activity activity, final Geopoint coords) {
+ CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords);
}
@Override
- public void navigate(Activity activity, Waypoint waypoint) {
- CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null,
+ public void navigate(final Activity activity, final Waypoint waypoint) {
+ CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(),
waypoint.getWaypointType().getL10n());
}
@Override
- public void navigate(Activity activity, Geocache cache) {
- CompassActivity.startActivity(activity, cache);
+ public void navigate(final Activity activity, final Geocache cache) {
+ CompassActivity.startActivityCache(activity, cache);
}
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
index 4924786..f5ccef4 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java
@@ -1,10 +1,10 @@
package cgeo.geocaching.apps.cache.navi;
import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.R;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.maps.MapProviderFactory;
+import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.utils.Log;
import android.app.Activity;
@@ -23,15 +23,13 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp {
}
@Override
- public void navigate(Activity activity, Geopoint coords) {
+ public void navigate(final Activity activity, final Geopoint coords) {
try {
- IGeoData geo = CgeoApplication.getInstance().currentGeo();
- final Geopoint coordsNow = geo == null ? null : geo.getCoords();
-
- if (coordsNow != null) {
+ final IGeoData geo = CgeoApplication.getInstance().currentGeo();
+ if (geo.getCoords() != null) {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
.parse("http://maps.google.com/maps?f=d&saddr="
- + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr="
+ + geo.getCoords().getLatitude() + "," + geo.getCoords().getLongitude() + "&daddr="
+ coords.getLatitude() + "," + coords.getLongitude())));
} else {
activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri
@@ -39,7 +37,7 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp {
+ coords.getLatitude() + "," + coords.getLongitude())));
}
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.i("GoogleMapsDirectionApp: application not available.", e);
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
index 5d645f7..4dbfadd 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java
@@ -8,6 +8,8 @@ import android.content.Intent;
class OruxMapsApp extends AbstractPointNavigationApp {
+ private static final String ORUXMAPS_EXTRA_LONGITUDE = "longitude";
+ private static final String ORUXMAPS_EXTRA_LATITUDE = "latitude";
private static final String INTENT = "com.oruxmaps.VIEW_MAP_ONLINE";
OruxMapsApp() {
@@ -17,8 +19,8 @@ class OruxMapsApp extends AbstractPointNavigationApp {
@Override
public void navigate(Activity activity, Geopoint point) {
final Intent intent = new Intent(INTENT);
- intent.putExtra("latitude", point.getLatitude());//latitude, wgs84 datum
- intent.putExtra("longitude", point.getLongitude());//longitude, wgs84 datum
+ intent.putExtra(ORUXMAPS_EXTRA_LATITUDE, point.getLatitude());//latitude, wgs84 datum
+ intent.putExtra(ORUXMAPS_EXTRA_LONGITUDE, point.getLongitude());//longitude, wgs84 datum
activity.startActivity(intent);
}
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
index ac83085..a12a38e 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java
@@ -1,26 +1,26 @@
-package cgeo.geocaching.apps.cache.navi;
-
-import cgeo.geocaching.R;
-import cgeo.geocaching.geopoint.Geopoint;
-
-import android.content.Intent;
-
-/**
- * Application for communication with the Pebble watch.
- *
- */
-class PebbleApp extends AbstractRadarApp {
-
- private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO";
- private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc";
-
- PebbleApp() {
- super(getString(R.string.cache_menu_pebble), R.id.cache_app_pebble, INTENT, PACKAGE_NAME);
- }
-
- @Override
- protected void addCoordinates(final Intent intent, final Geopoint coords) {
- intent.putExtra("latitude", coords.getLatitude());
- intent.putExtra("longitude", coords.getLongitude());
- }
+package cgeo.geocaching.apps.cache.navi;
+
+import cgeo.geocaching.R;
+import cgeo.geocaching.geopoint.Geopoint;
+
+import android.content.Intent;
+
+/**
+ * Application for communication with the Pebble watch.
+ *
+ */
+class PebbleApp extends AbstractRadarApp {
+
+ private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO";
+ private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc";
+
+ PebbleApp() {
+ super(getString(R.string.cache_menu_pebble), R.id.cache_app_pebble, INTENT, PACKAGE_NAME);
+ }
+
+ @Override
+ protected void addCoordinates(final Intent intent, final Geopoint coords) {
+ intent.putExtra(RADAR_EXTRA_LATITUDE, coords.getLatitude());
+ intent.putExtra(RADAR_EXTRA_LONGITUDE, coords.getLongitude());
+ }
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
index 41cf2d8..0ee512b 100644
--- a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
+++ b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java
@@ -16,8 +16,8 @@ class RadarApp extends AbstractRadarApp {
@Override
protected void addCoordinates(final Intent intent, final Geopoint coords) {
- intent.putExtra("latitude", (float) coords.getLatitude());
- intent.putExtra("longitude", (float) coords.getLongitude());
+ intent.putExtra(RADAR_EXTRA_LATITUDE, (float) coords.getLatitude());
+ intent.putExtra(RADAR_EXTRA_LONGITUDE, (float) coords.getLongitude());
}
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java
deleted file mode 100644
index a6d7e9b..0000000
--- a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java
+++ /dev/null
@@ -1,57 +0,0 @@
-package cgeo.geocaching.concurrent;
-
-
-import java.util.concurrent.ArrayBlockingQueue;
-import java.util.concurrent.BlockingQueue;
-import java.util.concurrent.ThreadFactory;
-import java.util.concurrent.ThreadPoolExecutor;
-import java.util.concurrent.TimeUnit;
-
-/**
- * BlockingThreadPool restricts the amount of parallel threads executing Runnables.
- */
-public class BlockingThreadPool {
- /** The queue holding the Runnable. **/
- private BlockingQueue<Runnable> queue = null;
- /** The Executor. **/
- private ThreadPoolExecutor executor;
-
- /**
- * Creates a ThreadPool with a given maximum of parallel threads running.
- * Idle threads will be stopped until new threads are added.
- *
- * @param poolSize
- * Maximum amout of parallel Threads
- * @param priority
- * The Thread priority e.g. Thread.MIN_PRIORITY
- */
- public BlockingThreadPool(int poolSize, int priority) {
- ThreadFactory threadFactory = new PriorityThreadFactory(priority);
- this.queue = new ArrayBlockingQueue<>(poolSize, true);
- this.executor = new ThreadPoolExecutor(0, poolSize, 5, TimeUnit.SECONDS, this.queue);
- this.executor.setThreadFactory(threadFactory);
- }
-
- /**
- * Add a runnable to the pool. This will start the core threads in the underlying
- * executor and try to add the Runnable to the pool. This method waits until timeout
- * if no free thread is available.
- *
- * @param task
- * The Runnable to add to the pool
- * @param timeout
- * The timeout to wait for a free thread
- * @param unit
- * The timeout unit
- * @return true/false successful added
- * @throws InterruptedException
- * Operation was interrupted
- */
- public boolean add(Runnable task, int timeout, TimeUnit unit) throws InterruptedException {
- this.executor.setCorePoolSize(this.executor.getMaximumPoolSize());
- this.executor.prestartAllCoreThreads();
- boolean successfull = this.queue.offer(task, timeout, unit);
- this.executor.setCorePoolSize(0);
- return successfull;
- }
-}
diff --git a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java
deleted file mode 100644
index 0da198b..0000000
--- a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java
+++ /dev/null
@@ -1,25 +0,0 @@
-package cgeo.geocaching.concurrent;
-
-import org.eclipse.jdt.annotation.NonNull;
-
-import java.util.concurrent.ThreadFactory;
-
-/**
- * Helper class for setting Thread priority in ThreadPool.
- */
-public class PriorityThreadFactory implements ThreadFactory {
- private int priority;
-
- public PriorityThreadFactory(int priority) {
- this.priority = priority;
- }
-
- @NonNull
- @Override
- public Thread newThread(Runnable r) {
- Thread result = new Thread(r);
- result.setPriority(this.priority);
- return result;
- }
-
-}
diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java
index 9729e06..a929e2b 100644
--- a/main/src/cgeo/geocaching/connector/AbstractConnector.java
+++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java
@@ -105,9 +105,9 @@ public abstract class AbstractConnector implements IConnector {
return null;
}
- protected static boolean isNumericId(final String string) {
+ protected static boolean isNumericId(final String str) {
try {
- return Integer.parseInt(string) > 0;
+ return Integer.parseInt(str) > 0;
} catch (NumberFormatException e) {
}
return false;
@@ -295,4 +295,6 @@ public abstract class AbstractConnector implements IConnector {
return actions;
}
+ public void logout() {
+ }
}
diff --git a/main/src/cgeo/geocaching/connector/capability/ILogin.java b/main/src/cgeo/geocaching/connector/capability/ILogin.java
index 4a839c8..b8b4975 100644
--- a/main/src/cgeo/geocaching/connector/capability/ILogin.java
+++ b/main/src/cgeo/geocaching/connector/capability/ILogin.java
@@ -23,6 +23,11 @@ public interface ILogin extends IConnector {
boolean login(Handler handler, Context fromActivity);
/**
+ * Log out of the connector if possible.
+ */
+ void logout();
+
+ /**
* Returns the status of the last {@link}login() request
*
* @return
diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java
index 421d112..3e7472e 100644
--- a/main/src/cgeo/geocaching/connector/ec/ECApi.java
+++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java
@@ -14,17 +14,18 @@ import cgeo.geocaching.geopoint.Viewport;
import cgeo.geocaching.list.StoredList;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.SynchronizedDateFormat;
import ch.boye.httpclientandroidlib.HttpResponse;
+import com.fasterxml.jackson.databind.JsonNode;
+
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
+import java.io.IOException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collection;
@@ -161,7 +162,7 @@ public class ECApi {
}
try {
- return new GPX10Parser(StoredList.TEMPORARY_LIST_ID).parse(response.getEntity().getContent(), null);
+ return new GPX10Parser(StoredList.TEMPORARY_LIST.id).parse(response.getEntity().getContent(), null);
} catch (Exception e) {
Log.e("Error importing gpx from extremcaching.com", e);
return Collections.emptyList();
@@ -171,46 +172,44 @@ public class ECApi {
private static List<Geocache> importCachesFromJSON(final HttpResponse response) {
if (response != null) {
try {
- final String data = Network.getResponseDataAlways(response);
- if (StringUtils.isBlank(data) || StringUtils.equals(data, "[]")) {
+ final JsonNode json = JsonUtils.reader.readTree(Network.getResponseDataAlways(response));
+ if (!json.isArray()) {
return Collections.emptyList();
}
- final JSONArray json = new JSONArray(data);
- final int len = json.length();
- final List<Geocache> caches = new ArrayList<>(len);
- for (int i = 0; i < len; i++) {
- final Geocache cache = parseCache(json.getJSONObject(i));
+ final List<Geocache> caches = new ArrayList<>(json.size());
+ for (final JsonNode node: json) {
+ final Geocache cache = parseCache(node);
if (cache != null) {
caches.add(cache);
}
}
return caches;
- } catch (final JSONException e) {
- Log.w("JSONResult", e);
+ } catch (IOException | ClassCastException e) {
+ Log.w("importCachesFromJSON", e);
}
}
return Collections.emptyList();
}
- private static Geocache parseCache(final JSONObject response) {
- final Geocache cache = new Geocache();
- cache.setReliableLatLon(true);
+ private static Geocache parseCache(final JsonNode response) {
try {
- cache.setGeocode("EC" + response.getString("cache_id"));
- cache.setName(response.getString("title"));
- cache.setCoords(new Geopoint(response.getString("lat"), response.getString("lon")));
- cache.setType(getCacheType(response.getString("type")));
- cache.setDifficulty((float) response.getDouble("difficulty"));
- cache.setTerrain((float) response.getDouble("terrain"));
- cache.setSize(CacheSize.getById(response.getString("size")));
- cache.setFound(response.getInt("found") == 1);
+ final Geocache cache = new Geocache();
+ cache.setReliableLatLon(true);
+ cache.setGeocode("EC" + response.get("cache_id").asText());
+ cache.setName(response.get("title").asText());
+ cache.setCoords(new Geopoint(response.get("lat").asText(), response.get("lon").asText()));
+ cache.setType(getCacheType(response.get("type").asText()));
+ cache.setDifficulty((float) response.get("difficulty").asDouble());
+ cache.setTerrain((float) response.get("terrain").asDouble());
+ cache.setSize(CacheSize.getById(response.get("size").asText()));
+ cache.setFound(response.get("found").asInt() == 1);
DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE));
- } catch (final JSONException e) {
+ return cache;
+ } catch (final NullPointerException e) {
Log.e("ECApi.parseCache", e);
return null;
}
- return cache;
}
private static CacheType getCacheType(final String cacheType) {
diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java
index 012bdc9..35c2db4 100644
--- a/main/src/cgeo/geocaching/connector/ec/ECLogin.java
+++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java
@@ -7,14 +7,18 @@ import cgeo.geocaching.enumerations.StatusCode;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import ch.boye.httpclientandroidlib.HttpResponse;
+
+import com.fasterxml.jackson.databind.JsonNode;
+
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
+
+import java.io.IOException;
public class ECLogin extends AbstractLogin {
@@ -26,7 +30,7 @@ public class ECLogin extends AbstractLogin {
}
private static class SingletonHolder {
- private static ECLogin INSTANCE = new ECLogin();
+ private final static ECLogin INSTANCE = new ECLogin();
}
public static ECLogin getInstance() {
@@ -93,18 +97,18 @@ public class ECLogin extends AbstractLogin {
setActualStatus(app.getString(R.string.init_login_popup_ok));
try {
- final JSONObject json = new JSONObject(data);
+ final JsonNode json = JsonUtils.reader.readTree(data);
- final String sid = json.getString("sid");
+ final String sid = json.get("sid").asText();
if (!StringUtils.isBlank(sid)) {
sessionId = sid;
setActualLoginStatus(true);
- setActualUserName(json.getString("username"));
- setActualCachesFound(json.getInt("found"));
+ setActualUserName(json.get("username").asText());
+ setActualCachesFound(json.get("found").asInt());
return true;
}
resetLoginStatus();
- } catch (final JSONException e) {
+ } catch (IOException | NullPointerException e) {
Log.e("ECLogin.getLoginStatus", e);
}
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
index 3b7c31e..4512979 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java
@@ -344,6 +344,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode,
}
@Override
+ public void logout() {
+ GCLogin.getInstance().logout();
+ }
+
+ @Override
public String getUserName() {
return GCLogin.getInstance().getActualUserName();
}
diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
index 3f16156..c2021bb 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java
@@ -45,7 +45,6 @@ public final class GCConstants {
public final static Pattern PATTERN_OWNER_USERID = Pattern.compile("other caches <a href=\"/seek/nearest\\.aspx\\?u=(.*?)\">hidden</a> or");
public final static Pattern PATTERN_FOUND = Pattern.compile("ctl00_ContentBody_GeoNav_logText\">(Found It|Attended)");
public final static Pattern PATTERN_FOUND_ALTERNATIVE = Pattern.compile("<div class=\"StatusInformationWidget FavoriteWidget\"");
- public final static Pattern PATTERN_FOUND_DATE = Pattern.compile(">Logged on: ([^<]+?)<");
public final static Pattern PATTERN_OWNER_DISPLAYNAME = Pattern.compile("<div id=\"ctl00_ContentBody_mcd1\">[^<]+<a href=\"[^\"]+\">([^<]+)</a>");
public final static Pattern PATTERN_TYPE = Pattern.compile("<a href=\"/seek/nearest.aspx\\?tx=([0-9a-f-]+)");
public final static Pattern PATTERN_HIDDEN = Pattern.compile("<div id=\"ctl00_ContentBody_mcd2\">\\W*Hidden[\\s:]*([^<]+?)</div>");
diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
index 9f430c0..16f20b8 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java
@@ -175,6 +175,9 @@ public class GCLogin extends AbstractLogin {
return StatusCode.NO_ERROR;
}
+ private static String removeDotAndComma(final String str) {
+ return StringUtils.replaceChars(str, ".,", null);
+ }
/**
* Check if the user has been logged in when he retrieved the data.
@@ -201,7 +204,7 @@ public class GCLogin extends AbstractLogin {
setActualUserName(TextUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???"));
int cachesCount = 0;
try {
- cachesCount = Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", ""));
+ cachesCount = Integer.parseInt(removeDotAndComma(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0")));
} catch (final NumberFormatException e) {
Log.e("getLoginStatus: bad cache count", e);
}
@@ -266,7 +269,7 @@ public class GCLogin extends AbstractLogin {
Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM);
}
- setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", "")));
+ setActualCachesFound(Integer.parseInt(removeDotAndComma(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1"))));
final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null);
if (avatarURL != null) {
diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java
index 27ce06e..bacaddb 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCMap.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java
@@ -9,6 +9,7 @@ import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy;
import cgeo.geocaching.enumerations.LiveMapStrategy.StrategyFlag;
import cgeo.geocaching.enumerations.StatusCode;
+import cgeo.geocaching.files.ParserException;
import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.GeopointFormatter.Format;
import cgeo.geocaching.geopoint.Units;
@@ -16,20 +17,23 @@ import cgeo.geocaching.geopoint.Viewport;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Formatter;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.LeastRecentlyUsedMap;
import cgeo.geocaching.utils.Log;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
import rx.Observable;
import rx.functions.Func2;
import android.graphics.Bitmap;
+import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Arrays;
@@ -60,53 +64,41 @@ public class GCMap {
// {"name":"HP: Hannover - Sahlkamp","gc":"GC2Q97X","g":"a09149ca-00e0-4aa2-b332-db2b4dfb18d2","available":true,"archived":false,"subrOnly":false,"li":false,"fp":"0","difficulty":{"text":1.0,"value":"1"},"terrain":{"text":1.5,"value":"1_5"},"hidden":"5/29/2011","container":{"text":"Small","value":"small.gif"},"type":{"text":"Traditional Cache","value":2},"owner":{"text":"GeoM@n","value":"1deaa69e-6bcc-421d-95a1-7d32b468cb82"}}]
// }
- final JSONObject json = new JSONObject(data);
- final String status = json.getString("status");
+ final ObjectNode json = (ObjectNode) JsonUtils.reader.readTree(data);
+ final String status = json.path("status").asText();
if (StringUtils.isBlank(status)) {
-
- throw new JSONException("No status inside JSON");
+ throw new ParserException("No status inside JSON");
}
if ("success".compareTo(status) != 0) {
- throw new JSONException("Wrong status inside JSON");
+ throw new ParserException("Wrong status inside JSON");
}
- final JSONArray dataArray = json.getJSONArray("data");
+ final ArrayNode dataArray = (ArrayNode) json.get("data");
if (dataArray == null) {
- throw new JSONException("No data inside JSON");
+ throw new ParserException("No data inside JSON");
}
final ArrayList<Geocache> caches = new ArrayList<>();
- for (int j = 0; j < dataArray.length(); j++) {
+ for (final JsonNode dataObject: dataArray) {
final Geocache cache = new Geocache();
-
- JSONObject dataObject = dataArray.getJSONObject(j);
- cache.setName(dataObject.getString("name"));
- cache.setGeocode(dataObject.getString("gc"));
- cache.setGuid(dataObject.getString("g")); // 34c2e609-5246-4f91-9029-d6c02b0f2a82"
- cache.setDisabled(!dataObject.getBoolean("available"));
- cache.setArchived(dataObject.getBoolean("archived"));
- cache.setPremiumMembersOnly(dataObject.getBoolean("subrOnly"));
+ cache.setName(dataObject.path("name").asText());
+ cache.setGeocode(dataObject.path("gc").asText());
+ cache.setGuid(dataObject.path("g").asText()); // 34c2e609-5246-4f91-9029-d6c02b0f2a82"
+ cache.setDisabled(!dataObject.path("available").asBoolean());
+ cache.setArchived(dataObject.path("archived").asBoolean());
+ cache.setPremiumMembersOnly(dataObject.path("subrOnly").asBoolean());
// "li" seems to be "false" always
- cache.setFavoritePoints(Integer.parseInt(dataObject.getString("fp")));
- JSONObject difficultyObj = dataObject.getJSONObject("difficulty");
- cache.setDifficulty(Float.parseFloat(difficultyObj.getString("text"))); // 3.5
- JSONObject terrainObj = dataObject.getJSONObject("terrain");
- cache.setTerrain(Float.parseFloat(terrainObj.getString("text"))); // 1.5
- cache.setHidden(GCLogin.parseGcCustomDate(dataObject.getString("hidden"), "MM/dd/yyyy")); // 7/23/2001
- JSONObject containerObj = dataObject.getJSONObject("container");
- cache.setSize(CacheSize.getById(containerObj.getString("text"))); // Regular
- JSONObject typeObj = dataObject.getJSONObject("type");
- cache.setType(CacheType.getByPattern(typeObj.getString("text"))); // Traditional Cache
- JSONObject ownerObj = dataObject.getJSONObject("owner");
- cache.setOwnerDisplayName(ownerObj.getString("text"));
+ cache.setFavoritePoints(Integer.parseInt(dataObject.path("fp").asText()));
+ cache.setDifficulty(Float.parseFloat(dataObject.path("difficulty").path("text").asText())); // 3.5
+ cache.setTerrain(Float.parseFloat(dataObject.path("terrain").path("text").asText())); // 1.5
+ cache.setHidden(GCLogin.parseGcCustomDate(dataObject.path("hidden").asText(), "MM/dd/yyyy")); // 7/23/2001
+ cache.setSize(CacheSize.getById(dataObject.path("container").path("text").asText())); // Regular
+ cache.setType(CacheType.getByPattern(dataObject.path("type").path("text").asText())); // Traditional Cache
+ cache.setOwnerDisplayName(dataObject.path("owner").path("text").asText());
caches.add(cache);
}
result.addAndPutInCache(caches);
- } catch (JSONException e) {
- result.setError(StatusCode.UNKNOWN_ERROR);
- } catch (ParseException e) {
- result.setError(StatusCode.UNKNOWN_ERROR);
- } catch (NumberFormatException e) {
+ } catch (ParserException | ParseException | IOException | NumberFormatException ignore) {
result.setError(StatusCode.UNKNOWN_ERROR);
}
return result;
@@ -125,7 +117,7 @@ public class GCMap {
final LeastRecentlyUsedMap<String, String> nameCache = new LeastRecentlyUsedMap.LruCache<>(2000); // JSON id, cache name
if (StringUtils.isEmpty(data)) {
- throw new JSONException("No page given");
+ throw new ParserException("No page given");
}
// Example JSON information
@@ -134,34 +126,33 @@ public class GCMap {
// "data":{"55_55":[{"i":"gEaR","n":"Spiel & Sport"}],"55_54":[{"i":"gEaR","n":"Spiel & Sport"}],"17_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"55_53":[{"i":"gEaR","n":"Spiel & Sport"}],"17_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"17_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"57_53":[{"i":"gEaR","n":"Spiel & Sport"}],"57_55":[{"i":"gEaR","n":"Spiel & Sport"}],"3_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"3_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"57_54":[{"i":"gEaR","n":"Spiel & Sport"}],"3_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"15_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"15_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"15_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"4_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"4_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"4_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"16_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"16_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"16_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"2_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"2_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"2_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"56_53":[{"i":"gEaR","n":"Spiel & Sport"}],"56_54":[{"i":"gEaR","n":"Spiel & Sport"}],"56_55":[{"i":"gEaR","n":"Spiel & Sport"}]}
// }
- final JSONObject json = new JSONObject(data);
+ final ObjectNode json = (ObjectNode) JsonUtils.reader.readTree(data);
- final JSONArray grid = json.getJSONArray("grid");
- if (grid == null || grid.length() != (UTFGrid.GRID_MAXY + 1)) {
- throw new JSONException("No grid inside JSON");
+ final ArrayNode grid = (ArrayNode) json.get("grid");
+ if (grid == null || grid.size() != (UTFGrid.GRID_MAXY + 1)) {
+ throw new ParserException("No grid inside JSON");
}
- final JSONArray keys = json.getJSONArray("keys");
+ final ArrayNode keys = (ArrayNode) json.get("keys");
if (keys == null) {
- throw new JSONException("No keys inside JSON");
+ throw new ParserException("No keys inside JSON");
}
- final JSONObject dataObject = json.getJSONObject("data");
+ final ObjectNode dataObject = (ObjectNode) json.get("data");
if (dataObject == null) {
- throw new JSONException("No data inside JSON");
+ throw new ParserException("No data inside JSON");
}
// iterate over the data and construct all caches in this tile
Map<String, List<UTFGridPosition>> positions = new HashMap<>(); // JSON id as key
Map<String, List<UTFGridPosition>> singlePositions = new HashMap<>(); // JSON id as key
- for (int i = 1; i < keys.length(); i++) { // index 0 is empty
- String key = keys.getString(i);
- if (StringUtils.isNotBlank(key)) {
+ for (final JsonNode rawKey: keys) {
+ final String key = rawKey.asText();
+ if (StringUtils.isNotBlank(key)) { // index 0 is empty
UTFGridPosition pos = UTFGridPosition.fromString(key);
- JSONArray dataForKey = dataObject.getJSONArray(key);
- for (int j = 0; j < dataForKey.length(); j++) {
- JSONObject cacheInfo = dataForKey.getJSONObject(j);
- String id = cacheInfo.getString("i");
- nameCache.put(id, cacheInfo.getString("n"));
+ final ArrayNode dataForKey = (ArrayNode) dataObject.get(key);
+ for (final JsonNode cacheInfo: dataForKey) {
+ final String id = cacheInfo.get("i").asText();
+ nameCache.put(id, cacheInfo.get("n").asText());
List<UTFGridPosition> listOfPositions = positions.get(id);
List<UTFGridPosition> singleListOfPositions = singlePositions.get(id);
@@ -174,7 +165,7 @@ public class GCMap {
}
listOfPositions.add(pos);
- if (dataForKey.length() == 1) {
+ if (dataForKey.size() == 1) {
singleListOfPositions.add(pos);
}
@@ -220,9 +211,7 @@ public class GCMap {
searchResult.addAndPutInCache(caches);
Log.d("Retrieved " + searchResult.getCount() + " caches for tile " + tile.toString());
- } catch (RuntimeException e) {
- Log.e("GCMap.parseMapJSON", e);
- } catch (JSONException e) {
+ } catch (RuntimeException | ParserException | IOException e) {
Log.e("GCMap.parseMapJSON", e);
}
diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java
index c771049..6919173 100644
--- a/main/src/cgeo/geocaching/connector/gc/GCParser.java
+++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java
@@ -30,6 +30,8 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.ui.DirectionImage;
import cgeo.geocaching.utils.CancellableHandler;
+import cgeo.geocaching.utils.HtmlUtils;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.MatcherWrapper;
import cgeo.geocaching.utils.RxUtils;
@@ -38,15 +40,16 @@ import cgeo.geocaching.utils.TextUtils;
import ch.boye.httpclientandroidlib.HttpResponse;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringEscapeUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
import rx.Observable;
import rx.Observable.OnSubscribe;
@@ -59,6 +62,7 @@ import android.net.Uri;
import android.text.Html;
import java.io.File;
+import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
@@ -75,6 +79,7 @@ import java.util.regex.Pattern;
public abstract class GCParser {
private final static SynchronizedDateFormat dateTbIn1 = new SynchronizedDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009
private final static SynchronizedDateFormat dateTbIn2 = new SynchronizedDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009
+ private final static ImmutablePair<StatusCode, Geocache> UNKNOWN_PARSE_ERROR = ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(pageContent)) {
@@ -125,12 +130,12 @@ public abstract class GCParser {
page = page.substring(startPos + 1, endPos - startPos + 1); // cut between <table> and </table>
- final String[] rows = page.split("<tr class=");
- final int rows_count = rows.length;
+ final String[] rows = StringUtils.splitByWholeSeparator(page, "<tr class=");
+ final int rowsCount = rows.length;
int excludedCaches = 0;
final ArrayList<Geocache> caches = new ArrayList<>();
- for (int z = 1; z < rows_count; z++) {
+ for (int z = 1; z < rowsCount; z++) {
final Geocache cache = new Geocache();
final String row = rows[z];
@@ -161,7 +166,7 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse GUID and/or Disabled
- Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data");
+ Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data", e);
}
if (Settings.isExcludeDisabledCaches() && (cache.isDisabled() || cache.isArchived())) {
@@ -173,11 +178,11 @@ public abstract class GCParser {
cache.setGeocode(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true));
// cache type
- cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true)));
+ cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, null)));
// cache direction - image
if (Settings.getLoadDirImg()) {
- final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false);
+ final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, null);
if (direction != null) {
cache.setDirectionImg(direction);
}
@@ -204,19 +209,19 @@ public abstract class GCParser {
}
// size
- final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false);
+ final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, null);
cache.setSize(CacheSize.getById(container));
// date hidden, makes sorting event caches easier
- final String dateHidden = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_HIDDEN_DATE, false, 1, null, false);
+ final String dateHidden = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_HIDDEN_DATE, false, null);
if (StringUtils.isNotBlank(dateHidden)) {
try {
- Date date = GCLogin.parseGcCustomDate(dateHidden);
+ final Date date = GCLogin.parseGcCustomDate(dateHidden);
if (date != null) {
cache.setHidden(date);
}
- } catch (ParseException e) {
- Log.e("Error parsing event date from search");
+ } catch (final ParseException e) {
+ Log.e("Error parsing event date from search", e);
}
}
@@ -266,7 +271,7 @@ public abstract class GCParser {
cache.setFavoritePoints(Integer.parseInt(result));
}
} catch (final NumberFormatException e) {
- Log.w("GCParser.parseSearch: Failed to parse favorite count");
+ Log.w("GCParser.parseSearch: Failed to parse favorite count", e);
}
caches.add(cache);
@@ -280,7 +285,7 @@ public abstract class GCParser {
searchResult.setTotalCountGC(Integer.parseInt(result) - excludedCaches);
}
} catch (final NumberFormatException e) {
- Log.w("GCParser.parseSearch: Failed to parse cache count");
+ Log.w("GCParser.parseSearch: Failed to parse cache count", e);
}
String recaptchaText = null;
@@ -380,17 +385,20 @@ public abstract class GCParser {
* Parse cache from text and return either an error code or a cache object in a pair. Note that inline logs are
* not parsed nor saved, while the cache itself is.
*
- * @param pageIn the page text to parse
- * @param handler the handler to send the progress notifications to
- * @return a pair, with a {@link StatusCode} on the left, and a non-nulll cache objet on the right
- * iff the status code is {@link StatusCode.NO_ERROR}.
+ * @param pageIn
+ * the page text to parse
+ * @param handler
+ * the handler to send the progress notifications to
+ * @return a pair, with a {@link StatusCode} on the left, and a non-null cache object on the right
+ * iff the status code is {@link StatusCode.NO_ERROR}.
*/
+ @NonNull
static private ImmutablePair<StatusCode, Geocache> parseCacheFromText(final String pageIn, @Nullable final CancellableHandler handler) {
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_details);
if (StringUtils.isBlank(pageIn)) {
Log.e("GCParser.parseCache: No page given");
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) {
@@ -403,12 +411,12 @@ public abstract class GCParser {
final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString();
if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) {
- return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
+ return UNKNOWN_PARSE_ERROR;
}
// first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields
String personalNoteWithLineBreaks = "";
- MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_PERSONALNOTE, pageIn);
+ final MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_PERSONALNOTE, pageIn);
if (matcher.find()) {
personalNoteWithLineBreaks = matcher.group(1).trim();
}
@@ -446,7 +454,7 @@ public abstract class GCParser {
final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS);
if (pos == -1) {
Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page");
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
tableInside = tableInside.substring(pos);
@@ -490,7 +498,7 @@ public abstract class GCParser {
}
} catch (final ParseException e) {
// failed to parse cache hidden date
- Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date");
+ Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date", e);
}
// favorite
@@ -579,13 +587,13 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse cache attributes
- Log.w("GCParser.parseCache: Failed to parse cache attributes");
+ Log.w("GCParser.parseCache: Failed to parse cache attributes", e);
}
// cache spoilers
try {
if (CancellableHandler.isCancelled(handler)) {
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_spoilers);
@@ -608,7 +616,7 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse cache spoilers
- Log.w("GCParser.parseCache: Failed to parse cache spoilers");
+ Log.w("GCParser.parseCache: Failed to parse cache spoilers", e);
}
// cache inventory
@@ -642,7 +650,7 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse cache inventory
- Log.w("GCParser.parseCache: Failed to parse cache inventory (2)");
+ Log.w("GCParser.parseCache: Failed to parse cache inventory (2)", e);
}
// cache logs counts
@@ -664,7 +672,7 @@ public abstract class GCParser {
}
} catch (final NumberFormatException e) {
// failed to parse logs
- Log.w("GCParser.parseCache: Failed to parse cache log count");
+ Log.w("GCParser.parseCache: Failed to parse cache log count", e);
}
// waypoints - reset collection
@@ -680,13 +688,13 @@ public abstract class GCParser {
cache.addOrChangeWaypoint(waypoint, false);
cache.setUserModifiedCoords(true);
}
- } catch (final Geopoint.GeopointException e) {
+ } catch (final Geopoint.GeopointException ignored) {
}
int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">");
if (wpBegin != -1) { // parse waypoints
if (CancellableHandler.isCancelled(handler)) {
- return null;
+ return UNKNOWN_PARSE_ERROR;
}
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_waypoints);
@@ -707,10 +715,10 @@ public abstract class GCParser {
wpList = wpList.substring(wpBegin + 7, wpEnd);
}
- final String[] wpItems = wpList.split("<tr");
+ final String[] wpItems = StringUtils.splitByWholeSeparator(wpList, "<tr");
for (int j = 1; j < wpItems.length; j++) {
- String[] wp = wpItems[j].split("<td");
+ String[] wp = StringUtils.splitByWholeSeparator(wpItems[j], "<td");
// waypoint name
// res is null during the unit tests
@@ -730,13 +738,12 @@ public abstract class GCParser {
// waypoint latitude and longitude
latlon = Html.fromHtml(TextUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim();
if (!StringUtils.startsWith(latlon, "???")) {
- waypoint.setLatlon(latlon);
waypoint.setCoords(new Geopoint(latlon));
}
j++;
if (wpItems.length > j) {
- wp = wpItems[j].split("<td");
+ wp = StringUtils.splitByWholeSeparator(wpItems[j], "<td");
}
// waypoint note
@@ -751,7 +758,7 @@ public abstract class GCParser {
// last check for necessary cache conditions
if (StringUtils.isBlank(cache.getGeocode())) {
- return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null);
+ return UNKNOWN_PARSE_ERROR;
}
cache.setDetailedUpdatedNow();
@@ -762,7 +769,7 @@ public abstract class GCParser {
return StringUtils.replaceChars(numberWithPunctuation, ".,", "");
}
- public static SearchResult searchByNextPage(final SearchResult search, boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByNextPage(final SearchResult search, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (search == null) {
return null;
}
@@ -845,7 +852,7 @@ public abstract class GCParser {
* @return
*/
@Nullable
- private static SearchResult searchByAny(final CacheType cacheType, final boolean my, final boolean showCaptcha, final Parameters params, RecaptchaReceiver recaptchaReceiver) {
+ private static SearchResult searchByAny(final CacheType cacheType, final boolean my, final boolean showCaptcha, final Parameters params, final RecaptchaReceiver recaptchaReceiver) {
insertCacheType(params, cacheType);
final String uri = "http://www.geocaching.com/seek/nearest.aspx";
@@ -871,12 +878,12 @@ public abstract class GCParser {
return search;
}
- public static SearchResult searchByCoords(final @NonNull Geopoint coords, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByCoords(final @NonNull Geopoint coords, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
final Parameters params = new Parameters("lat", Double.toString(coords.getLatitude()), "lng", Double.toString(coords.getLongitude()));
return searchByAny(cacheType, false, showCaptcha, params, recaptchaReceiver);
}
- public static SearchResult searchByKeyword(final @NonNull String keyword, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByKeyword(final @NonNull String keyword, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(keyword)) {
Log.e("GCParser.searchByKeyword: No keyword given");
return null;
@@ -894,7 +901,7 @@ public abstract class GCParser {
return false;
}
- public static SearchResult searchByUsername(final String userName, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByUsername(final String userName, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(userName)) {
Log.e("GCParser.searchByUsername: No user name given");
return null;
@@ -905,7 +912,7 @@ public abstract class GCParser {
return searchByAny(cacheType, isSearchForMyCaches(userName), showCaptcha, params, recaptchaReceiver);
}
- public static SearchResult searchByPocketQuery(final String pocketGuid, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByPocketQuery(final String pocketGuid, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(pocketGuid)) {
Log.e("GCParser.searchByPocket: No guid name given");
return null;
@@ -916,7 +923,7 @@ public abstract class GCParser {
return searchByAny(cacheType, false, showCaptcha, params, recaptchaReceiver);
}
- public static SearchResult searchByOwner(final String userName, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByOwner(final String userName, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(userName)) {
Log.e("GCParser.searchByOwner: No user name given");
return null;
@@ -926,32 +933,28 @@ public abstract class GCParser {
return searchByAny(cacheType, isSearchForMyCaches(userName), showCaptcha, params, recaptchaReceiver);
}
- public static SearchResult searchByAddress(final String address, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) {
+ public static SearchResult searchByAddress(final String address, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) {
if (StringUtils.isBlank(address)) {
Log.e("GCParser.searchByAddress: No address given");
return null;
}
- try {
- final JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address));
- if (response == null) {
- return null;
- }
- if (!StringUtils.equalsIgnoreCase(response.getString("status"), "success")) {
- return null;
- }
- if (!response.has("data")) {
- return null;
- }
- final JSONObject data = response.getJSONObject("data");
- if (data == null) {
- return null;
- }
- return searchByCoords(new Geopoint(data.getDouble("lat"), data.getDouble("lng")), cacheType, showCaptcha, recaptchaReceiver);
- } catch (final JSONException e) {
- Log.w("GCParser.searchByAddress", e);
+
+ final ObjectNode response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address));
+ if (response == null) {
+ return null;
}
- return null;
+ if (!StringUtils.equalsIgnoreCase(response.path("status").asText(), "success")) {
+ return null;
+ }
+
+ final JsonNode data = response.path("data");
+ final JsonNode latNode = data.get("lat");
+ final JsonNode lngNode = data.get("lng");
+ if (latNode == null || lngNode == null) {
+ return null;
+ }
+ return searchByCoords(new Geopoint(latNode.asDouble(), lngNode.asDouble()), cacheType, showCaptcha, recaptchaReceiver);
}
@Nullable
@@ -1001,13 +1004,13 @@ public abstract class GCParser {
return null;
}
- String subPage = StringUtils.substringAfter(page, "class=\"PocketQueryListTable");
+ final String subPage = StringUtils.substringAfter(page, "class=\"PocketQueryListTable");
if (StringUtils.isEmpty(subPage)) {
Log.e("GCParser.searchPocketQueryList: class \"PocketQueryListTable\" not found on page");
return Collections.emptyList();
}
- List<PocketQueryList> list = new ArrayList<>();
+ final List<PocketQueryList> list = new ArrayList<>();
final MatcherWrapper matcherPocket = new MatcherWrapper(GCConstants.PATTERN_LIST_PQ, subPage);
@@ -1015,7 +1018,7 @@ public abstract class GCParser {
int maxCaches;
try {
maxCaches = Integer.parseInt(matcherPocket.group(1));
- } catch (NumberFormatException e) {
+ } catch (final NumberFormatException e) {
maxCaches = 0;
Log.e("GCParser.searchPocketQueryList: Unable to parse max caches", e);
}
@@ -1029,7 +1032,7 @@ public abstract class GCParser {
Collections.sort(list, new Comparator<PocketQueryList>() {
@Override
- public int compare(PocketQueryList left, PocketQueryList right) {
+ public int compare(final PocketQueryList left, final PocketQueryList right) {
return String.CASE_INSENSITIVE_ORDER.compare(left.getName(), right.getName());
}
});
@@ -1483,7 +1486,7 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse trackable owner name
- Log.w("GCParser.parseTrackable: Failed to parse trackable owner name");
+ Log.w("GCParser.parseTrackable: Failed to parse trackable owner name", e);
}
// trackable origin
@@ -1514,7 +1517,7 @@ public abstract class GCParser {
}
} catch (final RuntimeException e) {
// failed to parse trackable last known place
- Log.w("GCParser.parseTrackable: Failed to parse trackable last known place");
+ Log.w("GCParser.parseTrackable: Failed to parse trackable last known place", e);
}
// released date - can be missing on the page
@@ -1522,12 +1525,12 @@ public abstract class GCParser {
if (releaseString != null) {
try {
trackable.setReleased(dateTbIn1.parse(releaseString));
- } catch (ParseException e) {
+ } catch (final ParseException ignore) {
if (trackable.getReleased() == null) {
try {
trackable.setReleased(dateTbIn2.parse(releaseString));
- } catch (ParseException e1) {
- Log.e("Could not parse trackable release " + releaseString);
+ } catch (final ParseException e) {
+ Log.e("Could not parse trackable release " + releaseString, e);
}
}
}
@@ -1545,7 +1548,7 @@ public abstract class GCParser {
}
// trackable goal
- trackable.setGoal(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal())));
+ trackable.setGoal(HtmlUtils.removeExtraParagraph(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))));
// trackable details & image
try {
@@ -1558,12 +1561,12 @@ public abstract class GCParser {
trackable.setImage(StringUtils.replace(image, "/display/", "/large/"));
}
if (StringUtils.isNotEmpty(details) && !StringUtils.equals(details, "No additional details available.")) {
- trackable.setDetails(convertLinks(details));
+ trackable.setDetails(HtmlUtils.removeExtraParagraph(convertLinks(details)));
}
}
} catch (final RuntimeException e) {
// failed to parse trackable details & image
- Log.w("GCParser.parseTrackable: Failed to parse trackable details & image");
+ Log.w("GCParser.parseTrackable: Failed to parse trackable details & image", e);
}
if (StringUtils.isEmpty(trackable.getDetails()) && page.contains(GCConstants.ERROR_TB_NOT_ACTIVATED)) {
trackable.setDetails(CgeoApplication.getInstance().getString(R.string.trackable_not_activated));
@@ -1585,7 +1588,7 @@ public abstract class GCParser {
long date = 0;
try {
date = GCLogin.parseGcCustomDate(matcherLogs.group(2)).getTime();
- } catch (final ParseException e) {
+ } catch (final ParseException ignore) {
}
final LogEntry logDone = new LogEntry(
@@ -1630,7 +1633,7 @@ public abstract class GCParser {
return trackable;
}
- private static String convertLinks(String input) {
+ private static String convertLinks(final String input) {
if (input == null) {
return null;
}
@@ -1691,7 +1694,7 @@ public abstract class GCParser {
Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information");
return Observable.empty();
}
- String rawResponse = Network.getResponseData(response);
+ final String rawResponse = Network.getResponseData(response);
if (rawResponse == null) {
Log.e("GCParser.loadLogsFromDetails: unable to read whole response");
return Observable.empty();
@@ -1712,56 +1715,51 @@ public abstract class GCParser {
}
try {
- final JSONObject resp = new JSONObject(rawResponse);
- if (!resp.getString("status").equals("success")) {
- Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status"));
+ final ObjectNode resp = (ObjectNode) JsonUtils.reader.readTree(rawResponse);
+ if (!resp.path("status").asText().equals("success")) {
+ Log.e("GCParser.loadLogsFromDetails: status is " + resp.path("status").asText("[absent]"));
subscriber.onCompleted();
return;
}
- final JSONArray data = resp.getJSONArray("data");
-
- for (int index = 0; index < data.length(); index++) {
- final JSONObject entry = data.getJSONObject(index);
-
+ final ArrayNode data = (ArrayNode) resp.get("data");
+ for (final JsonNode entry: data) {
// FIXME: use the "LogType" field instead of the "LogTypeImage" one.
- final String logIconNameExt = entry.optString("LogTypeImage", ".gif");
+ final String logIconNameExt = entry.path("LogTypeImage").asText(".gif");
final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4);
long date = 0;
try {
- date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime();
- } catch (final ParseException e) {
- Log.e("GCParser.loadLogsFromDetails: failed to parse log date.");
+ date = GCLogin.parseGcCustomDate(entry.get("Visited").asText()).getTime();
+ } catch (ParseException | NullPointerException e) {
+ Log.e("GCParser.loadLogsFromDetails: failed to parse log date", e);
}
// TODO: we should update our log data structure to be able to record
// proper coordinates, and make them clickable. In the meantime, it is
// better to integrate those coordinates into the text rather than not
// display them at all.
- final String latLon = entry.getString("LatLonString");
- final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.getString("LogText"));
+ final String latLon = entry.path("LatLonString").asText();
+ final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.path("LogText").asText());
final LogEntry logDone = new LogEntry(
- TextUtils.removeControlCharacters(entry.getString("UserName")),
+ TextUtils.removeControlCharacters(entry.path("UserName").asText()),
date,
LogType.getByIconName(logIconName),
logText);
- logDone.found = entry.getInt("GeocacheFindCount");
+ logDone.found = entry.path("GeocacheFindCount").asInt();
logDone.friend = markAsFriendsLog;
- final JSONArray images = entry.getJSONArray("Images");
- for (int i = 0; i < images.length(); i++) {
- final JSONObject image = images.getJSONObject(i);
- final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.getString("FileName");
- final String title = TextUtils.removeControlCharacters(image.getString("Name"));
+ final ArrayNode images = (ArrayNode) entry.get("Images");
+ for (final JsonNode image: images) {
+ final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.path("FileName").asText();
+ final String title = TextUtils.removeControlCharacters(image.path("Name").asText());
final Image logImage = new Image(url, title);
logDone.addLogImage(logImage);
}
subscriber.onNext(logDone);
}
- } catch (final JSONException e) {
- // failed to parse logs
+ } catch (final IOException e) {
Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e);
}
subscriber.onCompleted();
@@ -1770,7 +1768,7 @@ public abstract class GCParser {
}
@NonNull
- public static List<LogType> parseTypes(String page) {
+ public static List<LogType> parseTypes(final String page) {
if (StringUtils.isEmpty(page)) {
return Collections.emptyList();
}
@@ -1865,16 +1863,11 @@ public abstract class GCParser {
return;
}
+ CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
final Observable<LogEntry> logs = getLogs(page, Logs.ALL);
- Observable<LogEntry> specialLogs;
- if (Settings.isFriendLogsWanted()) {
- CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
- specialLogs = Observable.merge(getLogs(page, Logs.FRIENDS),
- getLogs(page, Logs.OWN));
- } else {
- CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs);
- specialLogs = Observable.empty();
- }
+ final Observable<LogEntry> ownLogs = getLogs(page, Logs.OWN).cache();
+ final Observable<LogEntry> specialLogs = Settings.isFriendLogsWanted() ?
+ Observable.merge(getLogs(page, Logs.FRIENDS), ownLogs) : Observable.<LogEntry>empty();
final Observable<List<LogEntry>> mergedLogs = Observable.zip(logs.toList(), specialLogs.toList(),
new Func2<List<LogEntry>, List<LogEntry>, List<LogEntry>>() {
@Override
@@ -1889,6 +1882,16 @@ public abstract class GCParser {
DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logEntries);
}
});
+ if (cache.isFound() && cache.getVisitedDate() == 0) {
+ ownLogs.subscribe(new Action1<LogEntry>() {
+ @Override
+ public void call(final LogEntry logEntry) {
+ if (logEntry.type == LogType.FOUND_IT) {
+ cache.setVisitedDate(logEntry.date);
+ }
+ }
+ });
+ }
if (Settings.isRatingWanted() && !CancellableHandler.isCancelled(handler)) {
CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_gcvote);
@@ -1923,75 +1926,61 @@ public abstract class GCParser {
}
}
- public static boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) {
+ public static boolean uploadModifiedCoordinates(final Geocache cache, final Geopoint wpt) {
return editModifiedCoordinates(cache, wpt);
}
- public static boolean deleteModifiedCoordinates(Geocache cache) {
+ public static boolean deleteModifiedCoordinates(final Geocache cache) {
return editModifiedCoordinates(cache, null);
}
- public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) {
+ public static boolean editModifiedCoordinates(final Geocache cache, final Geopoint wpt) {
final String userToken = getUserToken(cache);
if (StringUtils.isEmpty(userToken)) {
return false;
}
- try {
- JSONObject jo;
- if (wpt != null) {
- jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken)
- .put("data", new JSONObject()
- .put("lat", wpt.getLatitudeE6() / 1E6)
- .put("lng", wpt.getLongitudeE6() / 1E6))));
- } else {
- jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken)));
- }
-
- final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate";
+ final ObjectNode jo = new ObjectNode(JsonUtils.factory);
+ final ObjectNode dto = jo.putObject("dto").put("ut", userToken);
+ if (wpt != null) {
+ dto.putObject("data").put("lat", wpt.getLatitudeE6() / 1E6).put("lng", wpt.getLongitudeE6() / 1E6);
+ }
- final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/";
- final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo);
- Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString());
+ final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate";
- if (response != null && response.getStatusLine().getStatusCode() == 200) {
- Log.i("GCParser.editModifiedCoordinates - edited on GC.com");
- return true;
- }
+ final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/";
+ final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo);
+ Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString());
- } catch (final JSONException e) {
- Log.e("Unknown exception with json wrap code", e);
+ if (response != null && response.getStatusLine().getStatusCode() == 200) {
+ Log.i("GCParser.editModifiedCoordinates - edited on GC.com");
+ return true;
}
+
Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords");
return false;
}
- public static boolean uploadPersonalNote(Geocache cache) {
+ public static boolean uploadPersonalNote(final Geocache cache) {
final String userToken = getUserToken(cache);
if (StringUtils.isEmpty(userToken)) {
return false;
}
- try {
- final JSONObject jo = new JSONObject()
- .put("dto", (new JSONObject()
- .put("et", StringUtils.defaultString(cache.getPersonalNote()))
- .put("ut", userToken)));
-
- final String uriSuffix = "SetUserCacheNote";
+ final ObjectNode jo = new ObjectNode(JsonUtils.factory);
+ jo.putObject("dto").put("et", StringUtils.defaultString(cache.getPersonalNote())).put("ut", userToken);
- final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/";
- final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo);
- Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString());
+ final String uriSuffix = "SetUserCacheNote";
- if (response != null && response.getStatusLine().getStatusCode() == 200) {
- Log.i("GCParser.uploadPersonalNote - uploaded to GC.com");
- return true;
- }
+ final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/";
+ final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo);
+ Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString());
- } catch (final JSONException e) {
- Log.e("Unknown exception with json wrap code", e);
+ if (response != null && response.getStatusLine().getStatusCode() == 200) {
+ Log.i("GCParser.uploadPersonalNote - uploaded to GC.com");
+ return true;
}
+
Log.e("GCParser.uploadPersonalNote - cannot upload personal note");
return false;
}
diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
index 6095514..affeb7d 100644
--- a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
+++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java
@@ -42,15 +42,15 @@ public class RecaptchaHandler extends Handler {
}
private void loadChallenge(final ImageView imageView, final View reloadButton) {
- final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<? extends Bitmap>>() {
+ final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<Bitmap>>() {
@Override
- public Observable<? extends Bitmap> call() {
+ public Observable<Bitmap> call() {
final String url = "http://www.google.com/recaptcha/api/image?c=" + recaptchaReceiver.getChallenge();
final InputStream is = Network.getResponseStream(Network.getRequest(url));
if (is != null) {
try {
final Bitmap img = BitmapFactory.decodeStream(is);
- return Observable.from(img);
+ return Observable.just(img);
} catch (final Exception e) {
Log.e("RecaptchaHandler.getCaptcha", e);
return Observable.error(e);
diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java
index 18fe65c..ff13fe3 100644
--- a/main/src/cgeo/geocaching/connector/gc/Tile.java
+++ b/main/src/cgeo/geocaching/connector/gc/Tile.java
@@ -56,11 +56,11 @@ public class Tile {
private final int zoomLevel;
private final Viewport viewPort;
- public Tile(Geopoint origin, int zoomlevel) {
+ public Tile(final Geopoint origin, final int zoomlevel) {
this(calcX(origin, clippedZoomlevel(zoomlevel)), calcY(origin, clippedZoomlevel(zoomlevel)), clippedZoomlevel(zoomlevel));
}
- private Tile(int tileX, int tileY, int zoomlevel) {
+ private Tile(final int tileX, final int tileY, final int zoomlevel) {
this.zoomLevel = clippedZoomlevel(zoomlevel);
@@ -74,7 +74,7 @@ public class Tile {
return zoomLevel;
}
- private static int clippedZoomlevel(int zoomlevel) {
+ private static int clippedZoomlevel(final int zoomlevel) {
return Math.max(Math.min(zoomlevel, ZOOMLEVEL_MAX), ZOOMLEVEL_MIN);
}
@@ -95,7 +95,7 @@ public class Tile {
*/
private static int calcY(final Geopoint origin, final int zoomlevel) {
// Optimization from Bing
- double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude()));
+ final double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude()));
// The cut of the fractional part instead of rounding to the nearest integer is intentional and part of the algorithm
return (int) ((0.5 - Math.log((1 + sinLatRad) / (1 - sinLatRad)) / (4 * Math.PI)) * NUMBER_OF_TILES[zoomlevel]);
}
@@ -115,13 +115,13 @@ public class Tile {
* href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a>
*/
@NonNull
- public Geopoint getCoord(UTFGridPosition pos) {
+ public Geopoint getCoord(final UTFGridPosition pos) {
- double pixX = tileX * TILE_SIZE + pos.x * 4;
- double pixY = tileY * TILE_SIZE + pos.y * 4;
+ final double pixX = tileX * TILE_SIZE + pos.x * 4;
+ final double pixY = tileY * TILE_SIZE + pos.y * 4;
- double lonDeg = ((360.0 * pixX) / NUMBER_OF_PIXELS[this.zoomLevel]) - 180.0;
- double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * pixY / NUMBER_OF_PIXELS[this.zoomLevel])));
+ final double lonDeg = ((360.0 * pixX) / NUMBER_OF_PIXELS[this.zoomLevel]) - 180.0;
+ final double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * pixY / NUMBER_OF_PIXELS[this.zoomLevel])));
return new Geopoint(Math.toDegrees(latRad), lonDeg);
}
@@ -153,8 +153,8 @@ public class Tile {
/ Math.log(2)
);
- Tile tileLeft = new Tile(left, zoom);
- Tile tileRight = new Tile(right, zoom);
+ final Tile tileLeft = new Tile(left, zoom);
+ final Tile tileRight = new Tile(right, zoom);
if (Math.abs(tileLeft.tileX - tileRight.tileX) < (numberOfTiles - 1)) {
zoom += 1;
@@ -190,8 +190,8 @@ public class Tile {
) / Math.log(2)
);
- Tile tileBottom = new Tile(bottom, zoom);
- Tile tileTop = new Tile(top, zoom);
+ final Tile tileBottom = new Tile(bottom, zoom);
+ final Tile tileTop = new Tile(top, zoom);
if (Math.abs(tileBottom.tileY - tileTop.tileY) > (numberOfTiles - 1)) {
zoom -= 1;
@@ -200,7 +200,7 @@ public class Tile {
return Math.min(zoom, ZOOMLEVEL_MAX);
}
- private static double tanGrad(double angleGrad) {
+ private static double tanGrad(final double angleGrad) {
return Math.tan(angleGrad / 180.0 * Math.PI);
}
@@ -211,12 +211,12 @@ public class Tile {
* @param x
* @return
*/
- private static double asinh(double x) {
+ private static double asinh(final double x) {
return Math.log(x + Math.sqrt(x * x + 1.0));
}
@Override
- public boolean equals(Object o) {
+ public boolean equals(final Object o) {
if (this == o) {
return true;
}
@@ -260,7 +260,7 @@ public class Tile {
public Bitmap call() {
try {
return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null;
- } catch (IOException e) {
+ } catch (final IOException e) {
Log.e("Tile.requestMapTile() ", e);
return null;
}
@@ -298,20 +298,20 @@ public class Tile {
* @return
*/
protected static Set<Tile> getTilesForViewport(final Viewport viewport, final int tilesOnAxis, final int minZoom) {
- Set<Tile> tiles = new HashSet<>();
- int zoom = Math.max(
+ final Set<Tile> tiles = new HashSet<>();
+ final int zoom = Math.max(
Math.min(Tile.calcZoomLon(viewport.bottomLeft, viewport.topRight, tilesOnAxis),
Tile.calcZoomLat(viewport.bottomLeft, viewport.topRight, tilesOnAxis)),
minZoom);
- Tile tileBottomLeft = new Tile(viewport.bottomLeft, zoom);
- Tile tileTopRight = new Tile(viewport.topRight, zoom);
+ final Tile tileBottomLeft = new Tile(viewport.bottomLeft, zoom);
+ final Tile tileTopRight = new Tile(viewport.topRight, zoom);
- int xLow = Math.min(tileBottomLeft.getX(), tileTopRight.getX());
- int xHigh = Math.max(tileBottomLeft.getX(), tileTopRight.getX());
+ final int xLow = Math.min(tileBottomLeft.getX(), tileTopRight.getX());
+ final int xHigh = Math.max(tileBottomLeft.getX(), tileTopRight.getX());
- int yLow = Math.min(tileBottomLeft.getY(), tileTopRight.getY());
- int yHigh = Math.max(tileBottomLeft.getY(), tileTopRight.getY());
+ final int yLow = Math.min(tileBottomLeft.getY(), tileTopRight.getY());
+ final int yHigh = Math.max(tileBottomLeft.getY(), tileTopRight.getY());
for (int xNum = xLow; xNum <= xHigh; xNum++) {
for (int yNum = yLow; yNum <= yHigh; yNum++) {
@@ -324,8 +324,6 @@ public class Tile {
public static class TileCache extends LeastRecentlyUsedSet<Tile> {
- private static final long serialVersionUID = -1942301031192719547L;
-
public TileCache() {
super(64);
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
index 284234e..9e9ec7f 100644
--- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
+++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java
@@ -31,7 +31,7 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode {
private final ApiSupport apiSupport;
private final String licenseString;
- public OCApiConnector(String name, String host, String prefix, String cK, String licenseString, ApiSupport apiSupport) {
+ public OCApiConnector(final String name, final String host, final String prefix, final String cK, final String licenseString, final ApiSupport apiSupport) {
super(name, host, prefix);
this.cK = cK;
this.apiSupport = apiSupport;
@@ -39,7 +39,12 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode {
}
public void addAuthentication(final Parameters params) {
- params.put(CryptUtils.rot13("pbafhzre_xrl"), CryptUtils.rot13(cK));
+ final String rotCK = CryptUtils.rot13(cK);
+ // check that developers are not using the Ant defined properties without any values
+ if (StringUtils.startsWith(rotCK, "${")) {
+ throw new IllegalStateException("invalid OKAPI OAuth token " + rotCK);
+ }
+ params.put(CryptUtils.rot13("pbafhzre_xrl"), rotCK);
}
@Override
@@ -93,13 +98,13 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode {
/**
* Checks if a search based on a user name targets the current user
- *
+ *
* @param username
* Name of the user the query is searching after
* @return True - search target and current is same, False - current user not known or not the same as username
*/
@SuppressWarnings("static-method")
- public boolean isSearchForMyCaches(String username) {
+ public boolean isSearchForMyCaches(final String username) {
return false;
}
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
index bd87042..f665a1a 100644
--- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
+++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java
@@ -27,36 +27,40 @@ import cgeo.geocaching.geopoint.GeopointFormatter;
import cgeo.geocaching.geopoint.Viewport;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.OAuth;
+import cgeo.geocaching.network.OAuthTokens;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.SynchronizedDateFormat;
import ch.boye.httpclientandroidlib.HttpResponse;
+import com.fasterxml.jackson.core.JsonProcessingException;
+import com.fasterxml.jackson.databind.JsonNode;
+import com.fasterxml.jackson.databind.node.ArrayNode;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import org.apache.commons.lang3.StringUtils;
-import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
import android.net.Uri;
+import java.io.IOException;
import java.text.ParseException;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Collections;
import java.util.Date;
import java.util.EnumSet;
-import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.TimeZone;
+import java.util.regex.Pattern;
final class OkapiClient {
@@ -132,6 +136,8 @@ final class OkapiClient {
private static final String METHOD_SEARCH_NEAREST = "services/caches/search/nearest";
private static final String METHOD_RETRIEVE_CACHES = "services/caches/geocaches";
+ private static final Pattern PATTERN_TIMEZONE = Pattern.compile("([+-][01][0-9]):([03])0");
+
public static Geocache getCache(final String geoCode) {
final Parameters params = new Parameters("cache_code", geoCode);
final IConnector connector = ConnectorFactory.getConnector(geoCode);
@@ -209,10 +215,15 @@ final class OkapiClient {
}
addFilterParams(valueMap, connector, my);
- params.add("search_params", new JSONObject(valueMap).toString());
+ try {
+ params.add("search_params", JsonUtils.writer.writeValueAsString(valueMap));
+ } catch (final JsonProcessingException e) {
+ Log.e("requestCaches", e);
+ return Collections.emptyList();
+ }
addRetrieveParams(params, connector);
- final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params).data;
+ final ObjectNode data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params).data;
if (data == null) {
return Collections.emptyList();
@@ -245,7 +256,7 @@ final class OkapiClient {
final Parameters params = new Parameters("cache_code", cache.getGeocode());
params.add("watched", watched ? "true" : "false");
- final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params).data;
+ final ObjectNode data = request(connector, OkapiService.SERVICE_MARK_CACHE, params).data;
if (data == null) {
return false;
@@ -269,68 +280,58 @@ final class OkapiClient {
params.add("password", logPassword);
}
- final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params).data;
+ final ObjectNode data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params).data;
if (data == null) {
return new LogResult(StatusCode.LOG_POST_ERROR, "");
}
try {
- if (data.getBoolean("success")) {
- return new LogResult(StatusCode.NO_ERROR, data.getString("log_uuid"));
+ if (data.get("success").asBoolean()) {
+ return new LogResult(StatusCode.NO_ERROR, data.get("log_uuid").asText());
}
return new LogResult(StatusCode.LOG_POST_ERROR, "");
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.postLog", e);
}
return new LogResult(StatusCode.LOG_POST_ERROR, "");
}
- private static List<Geocache> parseCaches(final JSONObject response) {
+ private static List<Geocache> parseCaches(final ObjectNode response) {
try {
// Check for empty result
- final String result = response.getString("results");
- if (StringUtils.isBlank(result) || StringUtils.equals(result, "[]")) {
+ final JsonNode results = response.path("results");
+ if (!results.isObject()) {
return Collections.emptyList();
}
// Get and iterate result list
- final JSONObject cachesResponse = response.getJSONObject("results");
- if (cachesResponse != null) {
- final List<Geocache> caches = new ArrayList<>(cachesResponse.length());
- final Iterator<?> keys = cachesResponse.keys();
- while (keys.hasNext()) {
- final Object next = keys.next();
- if (next instanceof String) {
- final String key = (String) next;
- final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key));
- caches.add(cache);
- }
- }
- return caches;
+ final List<Geocache> caches = new ArrayList<>(results.size());
+ for (final JsonNode cache: results) {
+ caches.add(parseSmallCache((ObjectNode) cache));
}
- } catch (final JSONException e) {
+ return caches;
+ } catch (ClassCastException | NullPointerException e) {
Log.e("OkapiClient.parseCachesResult", e);
}
return Collections.emptyList();
}
- private static Geocache parseSmallCache(final JSONObject response) {
+ private static Geocache parseSmallCache(final ObjectNode response) {
final Geocache cache = new Geocache();
cache.setReliableLatLon(true);
try {
-
parseCoreCache(response, cache);
-
DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE));
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
+ // FIXME: here we may return a partially filled cache
Log.e("OkapiClient.parseSmallCache", e);
}
return cache;
}
- private static Geocache parseCache(final JSONObject response) {
+ private static Geocache parseCache(final ObjectNode response) {
final Geocache cache = new Geocache();
cache.setReliableLatLon(true);
try {
@@ -338,28 +339,27 @@ final class OkapiClient {
parseCoreCache(response, cache);
// not used: url
- final JSONObject ownerObject = response.getJSONObject(CACHE_OWNER);
- final String owner = parseUser(ownerObject);
+ final String owner = parseUser(response.get(CACHE_OWNER));
cache.setOwnerDisplayName(owner);
// OpenCaching has no distinction between user id and user display name. Set the ID anyway to simplify c:geo workflows.
cache.setOwnerUserId(owner);
- cache.getLogCounts().put(LogType.FOUND_IT, response.getInt(CACHE_FOUNDS));
- cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.getInt(CACHE_NOTFOUNDS));
+ cache.getLogCounts().put(LogType.FOUND_IT, response.get(CACHE_FOUNDS).asInt());
+ cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.get(CACHE_NOTFOUNDS).asInt());
// only current Api
- cache.getLogCounts().put(LogType.WILL_ATTEND, response.optInt(CACHE_WILLATTENDS));
+ cache.getLogCounts().put(LogType.WILL_ATTEND, response.path(CACHE_WILLATTENDS).asInt());
- if (!response.isNull(CACHE_RATING)) {
- cache.setRating((float) response.getDouble(CACHE_RATING));
+ if (response.has(CACHE_RATING)) {
+ cache.setRating((float) response.get(CACHE_RATING).asDouble());
}
- cache.setVotes(response.getInt(CACHE_VOTES));
+ cache.setVotes(response.get(CACHE_VOTES).asInt());
- cache.setFavoritePoints(response.getInt(CACHE_RECOMMENDATIONS));
+ cache.setFavoritePoints(response.get(CACHE_RECOMMENDATIONS).asInt());
// not used: req_password
// Prepend gc-link to description if available
final StringBuilder description = new StringBuilder(500);
- if (!response.isNull("gc_code")) {
- final String gccode = response.getString("gc_code");
+ if (response.hasNonNull("gc_code")) {
+ final String gccode = response.get("gc_code").asText();
description.append(CgeoApplication.getInstance().getResources()
.getString(R.string.cache_listed_on, GCConnector.getInstance().getName()))
.append(": <a href=\"http://coord.info/")
@@ -368,71 +368,70 @@ final class OkapiClient {
.append(gccode)
.append("</a><br /><br />");
}
- description.append(response.getString(CACHE_DESCRIPTION));
+ description.append(response.get(CACHE_DESCRIPTION).asText());
cache.setDescription(description.toString());
// currently the hint is delivered as HTML (contrary to OKAPI documentation), so we can store it directly
- cache.setHint(response.getString(CACHE_HINT));
+ cache.setHint(response.get(CACHE_HINT).asText());
// not used: hints
- final JSONArray images = response.getJSONArray(CACHE_IMAGES);
+ final ArrayNode images = (ArrayNode) response.get(CACHE_IMAGES);
if (images != null) {
- for (int i = 0; i < images.length(); i++) {
- final JSONObject imageResponse = images.getJSONObject(i);
- final String title = imageResponse.getString(CACHE_IMAGE_CAPTION);
- final String url = absoluteUrl(imageResponse.getString(CACHE_IMAGE_URL), cache.getGeocode());
+ for (final JsonNode imageResponse: images) {
+ final String title = imageResponse.get(CACHE_IMAGE_CAPTION).asText();
+ final String url = absoluteUrl(imageResponse.get(CACHE_IMAGE_URL).asText(), cache.getGeocode());
// all images are added as spoiler images, although OKAPI has spoiler and non spoiler images
cache.addSpoiler(new Image(url, title));
}
}
- cache.setAttributes(parseAttributes(response.getJSONArray(CACHE_ATTRNAMES), response.optJSONArray(CACHE_ATTR_ACODES)));
+ cache.setAttributes(parseAttributes((ArrayNode) response.path(CACHE_ATTRNAMES), (ArrayNode) response.get(CACHE_ATTR_ACODES)));
//TODO: Store license per cache
//cache.setLicense(response.getString("attribution_note"));
- cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false);
+ cache.setWaypoints(parseWaypoints((ArrayNode) response.path(CACHE_WPTS)), false);
- cache.setInventory(parseTrackables(response.getJSONArray(CACHE_TRACKABLES)));
+ cache.setInventory(parseTrackables((ArrayNode) response.path(CACHE_TRACKABLES)));
- if (!response.isNull(CACHE_IS_WATCHED)) {
- cache.setOnWatchlist(response.getBoolean(CACHE_IS_WATCHED));
+ if (response.has(CACHE_IS_WATCHED)) {
+ cache.setOnWatchlist(response.get(CACHE_IS_WATCHED).asBoolean());
}
- if (!response.isNull(CACHE_MY_NOTES)) {
- cache.setPersonalNote(response.getString(CACHE_MY_NOTES));
+ if (response.hasNonNull(CACHE_MY_NOTES)) {
+ cache.setPersonalNote(response.get(CACHE_MY_NOTES).asText());
cache.parseWaypointsFromNote();
}
- cache.setLogPasswordRequired(response.getBoolean(CACHE_REQ_PASSWORD));
+ cache.setLogPasswordRequired(response.get(CACHE_REQ_PASSWORD).asBoolean());
cache.setDetailedUpdatedNow();
// save full detailed caches
DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB));
- DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs(response.getJSONArray(CACHE_LATEST_LOGS)));
- } catch (final JSONException e) {
+ DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs((ArrayNode) response.path(CACHE_LATEST_LOGS)));
+ } catch (ClassCastException | NullPointerException e) {
Log.e("OkapiClient.parseCache", e);
}
return cache;
}
- private static void parseCoreCache(final JSONObject response, final Geocache cache) throws JSONException {
- cache.setGeocode(response.getString(CACHE_CODE));
- cache.setName(response.getString(CACHE_NAME));
+ private static void parseCoreCache(final ObjectNode response, final Geocache cache) {
+ cache.setGeocode(response.get(CACHE_CODE).asText());
+ cache.setName(response.get(CACHE_NAME).asText());
// not used: names
- setLocation(cache, response.getString(CACHE_LOCATION));
- cache.setType(getCacheType(response.getString(CACHE_TYPE)));
+ setLocation(cache, response.get(CACHE_LOCATION).asText());
+ cache.setType(getCacheType(response.get(CACHE_TYPE).asText()));
- final String status = response.getString(CACHE_STATUS);
+ final String status = response.get(CACHE_STATUS).asText();
cache.setDisabled(status.equalsIgnoreCase(CACHE_STATUS_DISABLED));
cache.setArchived(status.equalsIgnoreCase(CACHE_STATUS_ARCHIVED));
cache.setSize(getCacheSize(response));
- cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY));
- cache.setTerrain((float) response.getDouble(CACHE_TERRAIN));
+ cache.setDifficulty((float) response.get(CACHE_DIFFICULTY).asDouble());
+ cache.setTerrain((float) response.get(CACHE_TERRAIN).asDouble());
- cache.setInventoryItems(response.getInt(CACHE_TRACKABLES_COUNT));
+ cache.setInventoryItems(response.get(CACHE_TRACKABLES_COUNT).asInt());
- if (!response.isNull(CACHE_IS_FOUND)) {
- cache.setFound(response.getBoolean(CACHE_IS_FOUND));
+ if (response.has(CACHE_IS_FOUND)) {
+ cache.setFound(response.get(CACHE_IS_FOUND).asBoolean());
}
- cache.setHidden(parseDate(response.getString(CACHE_HIDDEN)));
+ cache.setHidden(parseDate(response.get(CACHE_HIDDEN).asText()));
}
private static String absoluteUrl(final String url, final String geocode) {
@@ -448,38 +447,36 @@ final class OkapiClient {
return url;
}
- private static String parseUser(final JSONObject user) throws JSONException {
- return user.getString(USER_USERNAME);
+ private static String parseUser(final JsonNode user) {
+ return user.get(USER_USERNAME).asText();
}
- private static List<LogEntry> parseLogs(final JSONArray logsJSON) {
+ private static List<LogEntry> parseLogs(final ArrayNode logsJSON) {
final List<LogEntry> result = new LinkedList<>();
- for (int i = 0; i < logsJSON.length(); i++) {
+ for (final JsonNode logResponse: logsJSON) {
try {
- final JSONObject logResponse = logsJSON.getJSONObject(i);
final LogEntry log = new LogEntry(
- parseUser(logResponse.getJSONObject(LOG_USER)),
- parseDate(logResponse.getString(LOG_DATE)).getTime(),
- parseLogType(logResponse.getString(LOG_TYPE)),
- logResponse.getString(LOG_COMMENT).trim());
+ parseUser(logResponse.get(LOG_USER)),
+ parseDate(logResponse.get(LOG_DATE).asText()).getTime(),
+ parseLogType(logResponse.get(LOG_TYPE).asText()),
+ logResponse.get(LOG_COMMENT).asText().trim());
result.add(log);
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.parseLogs", e);
}
}
return result;
}
- private static List<Waypoint> parseWaypoints(final JSONArray wptsJson) {
+ private static List<Waypoint> parseWaypoints(final ArrayNode wptsJson) {
List<Waypoint> result = null;
- for (int i = 0; i < wptsJson.length(); i++) {
+ for (final JsonNode wptResponse: wptsJson) {
try {
- final JSONObject wptResponse = wptsJson.getJSONObject(i);
- final Waypoint wpt = new Waypoint(wptResponse.getString(WPT_NAME),
- parseWptType(wptResponse.getString(WPT_TYPE)),
+ final Waypoint wpt = new Waypoint(wptResponse.get(WPT_NAME).asText(),
+ parseWptType(wptResponse.get(WPT_TYPE).asText()),
false);
- wpt.setNote(wptResponse.getString(WPT_DESCRIPTION));
- final Geopoint pt = parseCoords(wptResponse.getString(WPT_LOCATION));
+ wpt.setNote(wptResponse.get(WPT_DESCRIPTION).asText());
+ final Geopoint pt = parseCoords(wptResponse.get(WPT_LOCATION).asText());
if (pt != null) {
wpt.setCoords(pt);
}
@@ -488,26 +485,25 @@ final class OkapiClient {
}
wpt.setPrefix(wpt.getName());
result.add(wpt);
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.parseWaypoints", e);
}
}
return result;
}
- private static List<Trackable> parseTrackables(final JSONArray trackablesJson) {
- if (trackablesJson.length() == 0) {
+ private static List<Trackable> parseTrackables(final ArrayNode trackablesJson) {
+ if (trackablesJson.size() == 0) {
return Collections.emptyList();
}
final List<Trackable> result = new ArrayList<>();
- for (int i = 0; i < trackablesJson.length(); i++) {
+ for (final JsonNode trackableResponse: trackablesJson) {
try {
- final JSONObject trackableResponse = trackablesJson.getJSONObject(i);
final Trackable trk = new Trackable();
- trk.setGeocode(trackableResponse.getString(TRK_GEOCODE));
- trk.setName(trackableResponse.getString(TRK_NAME));
+ trk.setGeocode(trackableResponse.get(TRK_GEOCODE).asText());
+ trk.setName(trackableResponse.get(TRK_NAME).asText());
result.add(trk);
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.parseWaypoints", e);
// Don't overwrite internal state with possibly partial result
return null;
@@ -576,7 +572,7 @@ final class OkapiClient {
}
private static Date parseDate(final String date) {
- final String strippedDate = date.replaceAll("\\+0([0-9]){1}\\:00", "+0$100");
+ final String strippedDate = PATTERN_TIMEZONE.matcher(date).replaceAll("$1$20");
try {
return ISO8601DATEFORMAT.parse(strippedDate);
} catch (final ParseException e) {
@@ -595,14 +591,14 @@ final class OkapiClient {
return null;
}
- private static List<String> parseAttributes(final JSONArray nameList, final JSONArray acodeList) {
+ private static List<String> parseAttributes(final ArrayNode nameList, final ArrayNode acodeList) {
final List<String> result = new ArrayList<>();
- for (int i = 0; i < nameList.length(); i++) {
+ for (int i = 0; i < nameList.size(); i++) {
try {
- final String name = nameList.getString(i);
- final int acode = acodeList != null ? Integer.parseInt(acodeList.getString(i).substring(1)) : CacheAttribute.NO_ID;
+ final String name = nameList.get(i).asText();
+ final int acode = acodeList != null ? Integer.parseInt(acodeList.get(i).asText().substring(1)) : CacheAttribute.NO_ID;
final CacheAttribute attr = CacheAttribute.getByOcACode(acode);
if (attr != null) {
@@ -610,7 +606,7 @@ final class OkapiClient {
} else {
result.add(name);
}
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.parseAttributes", e);
}
}
@@ -624,27 +620,27 @@ final class OkapiClient {
cache.setCoords(new Geopoint(latitude, longitude));
}
- private static CacheSize getCacheSize(final JSONObject response) {
- if (response.isNull(CACHE_SIZE2)) {
+ private static CacheSize getCacheSize(final ObjectNode response) {
+ if (!response.has(CACHE_SIZE2)) {
return getCacheSizeDeprecated(response);
}
try {
- final String size = response.getString(CACHE_SIZE2);
+ final String size = response.get(CACHE_SIZE2).asText();
return CacheSize.getById(size);
- } catch (final JSONException e) {
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.getCacheSize", e);
return getCacheSizeDeprecated(response);
}
}
- private static CacheSize getCacheSizeDeprecated(final JSONObject response) {
- if (response.isNull(CACHE_SIZE_DEPRECATED)) {
+ private static CacheSize getCacheSizeDeprecated(final ObjectNode response) {
+ if (!response.has(CACHE_SIZE_DEPRECATED)) {
return CacheSize.NOT_CHOSEN;
}
double size = 0;
try {
- size = response.getDouble(CACHE_SIZE_DEPRECATED);
- } catch (final JSONException e) {
+ size = response.get(CACHE_SIZE_DEPRECATED).asDouble();
+ } catch (final NullPointerException e) {
Log.e("OkapiClient.getCacheSize", e);
}
switch ((int) Math.round(size)) {
@@ -732,19 +728,22 @@ final class OkapiClient {
@NonNull
private static JSONResult request(final OCApiConnector connector, final OkapiService service, final Parameters params) {
if (connector == null) {
- return new JSONResult(null);
+ return new JSONResult("unknown OKAPI connector");
}
final String host = connector.getHost();
if (StringUtils.isBlank(host)) {
- return new JSONResult(null);
+ return new JSONResult("unknown OKAPI connector host");
}
params.add("langpref", getPreferredLanguage());
if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) {
- final ImmutablePair<String, String> tokens = Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId());
- OAuth.signOAuth(host, service.methodName, "GET", false, params, tokens.left, tokens.right, connector.getCK(), connector.getCS());
+ final OAuthTokens tokens = new OAuthTokens(connector);
+ if (!tokens.isValid()) {
+ return new JSONResult("invalid oauth tokens");
+ }
+ OAuth.signOAuth(host, service.methodName, "GET", false, params, tokens, connector.getCK(), connector.getCS());
} else {
connector.addAuthentication(params);
}
@@ -810,16 +809,7 @@ final class OkapiClient {
return null;
}
- final JSONObject data = result.data;
- if (!data.isNull(USER_UUID)) {
- try {
- return data.getString(USER_UUID);
- } catch (final JSONException e) {
- Log.e("OkapiClient.getUserUUID - uuid", e);
- }
- }
-
- return null;
+ return result.data.path(USER_UUID).asText(null);
}
public static UserInfo getUserInfo(final OCApiLiveConnector connector) {
@@ -833,31 +823,11 @@ final class OkapiClient {
return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.getFromOkapiError(error.getResult()));
}
- final JSONObject data = result.data;
-
- String name = StringUtils.EMPTY;
- boolean successUserName = false;
-
- if (!data.isNull(USER_USERNAME)) {
- try {
- name = data.getString(USER_USERNAME);
- successUserName = true;
- } catch (final JSONException e) {
- Log.e("OkapiClient.getUserInfo - name", e);
- }
- }
-
- int finds = 0;
- boolean successFinds = false;
-
- if (!data.isNull(USER_CACHES_FOUND)) {
- try {
- finds = data.getInt(USER_CACHES_FOUND);
- successFinds = true;
- } catch (final JSONException e) {
- Log.e("OkapiClient.getUserInfo - finds", e);
- }
- }
+ final ObjectNode data = result.data;
+ final boolean successUserName = data.has(USER_USERNAME);
+ final String name = data.path(USER_USERNAME).asText();
+ final boolean successFinds = data.has(USER_CACHES_FOUND);
+ final int finds = data.path(USER_CACHES_FOUND).asInt();
return new UserInfo(name, finds, successUserName && successFinds ? UserInfoStatus.SUCCESSFUL : UserInfoStatus.FAILED);
}
@@ -874,7 +844,7 @@ final class OkapiClient {
if (!result.isSuccess) {
return new OkapiError(result.data);
}
- return new OkapiError(new JSONObject());
+ return new OkapiError(new ObjectNode(JsonUtils.factory));
}
/**
@@ -884,21 +854,27 @@ final class OkapiClient {
private static class JSONResult {
public final boolean isSuccess;
- public final JSONObject data;
+ public final ObjectNode data;
public JSONResult(final @Nullable HttpResponse response) {
- final boolean isSuccess = Network.isSuccess(response);
+ final boolean isRequestSuccessful = Network.isSuccess(response);
final String responseData = Network.getResponseDataAlways(response);
- JSONObject data = null;
+ ObjectNode tempData = null;
if (responseData != null) {
try {
- data = new JSONObject(responseData);
- } catch (final JSONException e) {
+ tempData = (ObjectNode) JsonUtils.reader.readTree(responseData);
+ } catch (IOException | ClassCastException e) {
Log.w("JSONResult", e);
}
}
- this.data = data;
- this.isSuccess = isSuccess && data != null;
+ data = tempData;
+ isSuccess = isRequestSuccessful && tempData != null;
+ }
+
+ public JSONResult(final @NonNull String errorMessage) {
+ isSuccess = false;
+ data = new ObjectNode(JsonUtils.factory);
+ data.putObject("error").put("developer_message", errorMessage);
}
}
}
diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiError.java b/main/src/cgeo/geocaching/connector/oc/OkapiError.java
index 7faf2c7..b847207 100644
--- a/main/src/cgeo/geocaching/connector/oc/OkapiError.java
+++ b/main/src/cgeo/geocaching/connector/oc/OkapiError.java
@@ -2,11 +2,11 @@ package cgeo.geocaching.connector.oc;
import cgeo.geocaching.utils.Log;
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
/**
* Handles the JSON error response from OKAPI
@@ -26,7 +26,7 @@ public class OkapiError {
@NonNull private final OkapiErrors state;
@NonNull private final String message;
- public OkapiError(@Nullable JSONObject data) {
+ public OkapiError(@Nullable ObjectNode data) {
// A null-response is by definition an error (some exception occurred somewhere in the flow)
if (data == null) {
@@ -39,10 +39,10 @@ public class OkapiError {
String localmessage = null;
OkapiErrors localstate = OkapiErrors.UNSPECIFIED;
try {
- JSONObject error = data.getJSONObject("error");
+ final ObjectNode error = (ObjectNode) data.get("error");
// Check reason_stack element to look for the specific oauth problems we want to report back
if (error.has("reason_stack")) {
- String reason = error.getString("reason_stack");
+ final String reason = error.get("reason_stack").asText();
if (StringUtils.contains(reason, "invalid_oauth_request")) {
if (StringUtils.contains(reason, "invalid_timestamp")) {
localstate = OkapiErrors.INVALID_TIMESTAMP;
@@ -53,10 +53,10 @@ public class OkapiError {
}
// Check if we can extract a message as well
if (error.has("developer_message")) {
- localmessage = error.getString("developer_message");
+ localmessage = error.get("developer_message").asText();
assert localmessage != null; // by virtue of defaultString
}
- } catch (JSONException ex) {
+ } catch (ClassCastException | NullPointerException ex) {
Log.d("OkapiError: Failed to parse JSON", ex);
localstate = OkapiErrors.UNSPECIFIED;
}
diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java
index b2afff5..21207ec 100644
--- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java
+++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java
@@ -39,9 +39,9 @@ public class OpenCachingApi {
return null;
}
- private static HttpResponse getRequest(String string, Parameters parameters) {
+ private static HttpResponse getRequest(final String uri, final Parameters parameters) {
parameters.add("Authorization", DEV_KEY);
- return Network.getRequest(string, parameters);
+ return Network.getRequest(uri, parameters);
}
private static Collection<Geocache> importCachesFromResponse(final HttpResponse response, final boolean isDetailed) {
@@ -50,7 +50,7 @@ public class OpenCachingApi {
}
Collection<Geocache> caches;
try {
- caches = new OXGPXParser(StoredList.TEMPORARY_LIST_ID, isDetailed).parse(response.getEntity().getContent(), null);
+ caches = new OXGPXParser(StoredList.TEMPORARY_LIST.id, isDetailed).parse(response.getEntity().getContent(), null);
} catch (Exception e) {
Log.e("Error importing from OpenCaching.com", e);
return Collections.emptyList();
diff --git a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java
index f2c79fe..a6f0114 100644
--- a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java
+++ b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java
@@ -5,6 +5,8 @@ import cgeo.geocaching.R;
public enum LocationProviderType {
GPS(R.string.loc_gps),
NETWORK(R.string.loc_net),
+ FUSED(R.string.loc_fused),
+ LOW_POWER(R.string.loc_low_power),
LAST(R.string.loc_last);
public final int resourceId;
diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java
index 84ab7b9..5345611 100644
--- a/main/src/cgeo/geocaching/enumerations/LogType.java
+++ b/main/src/cgeo/geocaching/enumerations/LogType.java
@@ -52,7 +52,7 @@ public enum LogType {
private final int stringId;
public final int markerId;
- LogType(int id, String iconName, String type, String oc_type, int stringId, int markerId) {
+ LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId, final int markerId) {
this.id = id;
this.iconName = iconName;
this.type = type;
@@ -61,7 +61,7 @@ public enum LogType {
this.markerId = markerId;
}
- LogType(int id, String iconName, String type, String oc_type, int stringId) {
+ LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId) {
this(id, iconName, type, oc_type, stringId, R.drawable.mark_gray);
}
@@ -70,7 +70,7 @@ public enum LogType {
static {
final HashMap<String, LogType> mappingPattern = new HashMap<>();
final HashMap<String, LogType> mappingType = new HashMap<>();
- for (LogType lt : values()) {
+ for (final LogType lt : values()) {
if (lt.iconName != null) {
mappingPattern.put(lt.iconName, lt);
}
@@ -81,7 +81,7 @@ public enum LogType {
}
public static LogType getById(final int id) {
- for (LogType logType : values()) {
+ for (final LogType logType : values()) {
if (logType.id == id) {
return logType;
}
@@ -113,4 +113,12 @@ public enum LogType {
public final String getL10n() {
return CgeoApplication.getInstance().getBaseContext().getResources().getString(stringId);
}
+
+ public final boolean isFoundLog() {
+ return this == LogType.FOUND_IT || this == LogType.ATTENDED || this == LogType.WEBCAM_PHOTO_TAKEN;
+ }
+
+ public boolean mustConfirmLog() {
+ return this == ARCHIVE || this == NEEDS_ARCHIVE;
+ }
}
diff --git a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java
index 2a05cbc..fa84df9 100644
--- a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java
+++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java
@@ -38,7 +38,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
private String searchInfo;
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
if (msg.obj != null && waitDialog != null) {
if (searchInfo == null) {
searchInfo = res.getString(R.string.file_searching_in) + " ";
@@ -52,7 +52,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
private String getDefaultFolders() {
final ArrayList<String> names = new ArrayList<>();
- for (File dir : getExistingBaseFolders()) {
+ for (final File dir : getExistingBaseFolders()) {
names.add(dir.getPath());
}
return StringUtils.join(names, '\n');
@@ -62,7 +62,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
final private Handler loadFilesHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
if (waitDialog != null) {
waitDialog.dismiss();
}
@@ -76,17 +76,17 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
};
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setTheme();
setContentView(R.layout.gpx);
- Bundle extras = getIntent().getExtras();
+ final Bundle extras = getIntent().getExtras();
if (extras != null) {
listId = extras.getInt(Intents.EXTRA_LIST_ID);
}
- if (listId <= StoredList.TEMPORARY_LIST_ID) {
+ if (listId <= StoredList.TEMPORARY_LIST.id) {
listId = StoredList.STANDARD_LIST_ID;
}
@@ -100,7 +100,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
true,
new DialogInterface.OnCancelListener() {
@Override
- public void onCancel(DialogInterface arg0) {
+ public void onCancel(final DialogInterface arg0) {
if (searchingThread != null && searchingThread.isAlive()) {
searchingThread.notifyEnd();
}
@@ -171,7 +171,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
} else {
Log.w("No external media mounted.");
}
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("AbstractFileListActivity.loadFiles.run", e);
}
@@ -181,7 +181,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
Collections.sort(files, new Comparator<File>() {
@Override
- public int compare(File lhs, File rhs) {
+ public int compare(final File lhs, final File rhs) {
return lhs.getName().compareToIgnoreCase(rhs.getName());
}
});
@@ -189,7 +189,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
loadFilesHandler.sendMessage(Message.obtain(loadFilesHandler));
}
- private void listDirs(List<File> list, List<File> directories, FileListSelector selector, Handler feedbackHandler) {
+ private void listDirs(final List<File> list, final List<File> directories, final FileListSelector selector, final Handler feedbackHandler) {
for (final File dir : directories) {
FileUtils.listDir(list, dir, selector, feedbackHandler);
}
@@ -204,7 +204,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
* @return <code>true</code> if the filename belongs to the list
*/
protected boolean filenameBelongsToList(final String filename) {
- for (String ext : extensions) {
+ for (final String ext : extensions) {
if (StringUtils.endsWithIgnoreCase(filename, ext)) {
return true;
}
@@ -213,7 +213,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
}
protected List<File> getExistingBaseFolders() {
- ArrayList<File> result = new ArrayList<>();
+ final ArrayList<File> result = new ArrayList<>();
for (final File dir : getBaseFolders()) {
if (dir.exists() && dir.isDirectory()) {
result.add(dir);
@@ -245,7 +245,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
boolean shouldEnd = false;
@Override
- public boolean isSelected(File file) {
+ public boolean isSelected(final File file) {
return filenameBelongsToList(file.getName());
}
@@ -254,7 +254,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext
return shouldEnd;
}
- public synchronized void setShouldEnd(boolean shouldEnd) {
+ public synchronized void setShouldEnd(final boolean shouldEnd) {
this.shouldEnd = shouldEnd;
}
}
diff --git a/main/src/cgeo/geocaching/files/FileTypeDetector.java b/main/src/cgeo/geocaching/files/FileTypeDetector.java
index 389b83a..ab0f032 100644
--- a/main/src/cgeo/geocaching/files/FileTypeDetector.java
+++ b/main/src/cgeo/geocaching/files/FileTypeDetector.java
@@ -10,7 +10,6 @@ import android.content.ContentResolver;
import android.net.Uri;
import java.io.BufferedReader;
-import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
@@ -37,8 +36,6 @@ public class FileTypeDetector {
reader = new BufferedReader(new InputStreamReader(is));
type = detectHeader(reader);
reader.close();
- } catch (FileNotFoundException e) {
- Log.e("FileTypeDetector", e);
} catch (IOException e) {
Log.e("FileTypeDetector", e);
} finally {
diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java
index 52f68e1..4f1d391 100644
--- a/main/src/cgeo/geocaching/files/GPXImporter.java
+++ b/main/src/cgeo/geocaching/files/GPXImporter.java
@@ -113,7 +113,7 @@ public class GPXImporter {
fileType = getFileTypeFromMimeType(mimeType);
}
- ImportThread importer = getImporterFromFileType(uri, contentResolver,
+ final ImportThread importer = getImporterFromFileType(uri, contentResolver,
fileType);
if (importer != null) {
@@ -139,14 +139,15 @@ public class GPXImporter {
final String mimeType) {
if (GPX_MIME_TYPES.contains(mimeType)) {
return FileType.GPX;
- } else if (ZIP_MIME_TYPES.contains(mimeType)) {
+ }
+ if (ZIP_MIME_TYPES.contains(mimeType)) {
return FileType.ZIP;
}
- return FileType.UNKNOWN;
+ return FileType.UNKNOWN;
}
- private ImportThread getImporterFromFileType(Uri uri,
- ContentResolver contentResolver, FileType fileType) {
+ private ImportThread getImporterFromFileType(final Uri uri,
+ final ContentResolver contentResolver, final FileType fileType) {
switch (fileType) {
case ZIP:
return new ImportGpxZipAttachmentThread(uri, contentResolver,
@@ -178,7 +179,7 @@ public class GPXImporter {
final Handler importStepHandler;
final CancellableHandler progressHandler;
- protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
this.listId = listId;
this.importStepHandler = importStepHandler;
this.progressHandler = progressHandler;
@@ -244,7 +245,7 @@ public class GPXImporter {
static class ImportLocFileThread extends ImportThread {
private final File file;
- public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportLocFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.file = file;
}
@@ -262,7 +263,7 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportLocAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportLocAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
@@ -284,7 +285,7 @@ public class GPXImporter {
static abstract class ImportGpxThread extends ImportThread {
- protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@@ -305,13 +306,13 @@ public class GPXImporter {
static class ImportGpxFileThread extends ImportGpxThread {
private final File cacheFile;
- public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.cacheFile = file;
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final 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);
@@ -333,17 +334,21 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final 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));
final InputStream is = contentResolver.openInputStream(uri);
+ int streamSize = is.available();
+ if (streamSize == 0) {
+ streamSize = -1;
+ }
+ importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, streamSize));
try {
return parser.parse(is, progressHandler);
} finally {
@@ -354,12 +359,12 @@ public class GPXImporter {
static abstract class ImportGpxZipThread extends ImportGpxThread {
- protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ protected ImportGpxZipThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
}
@Override
- protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException {
+ protected Collection<Geocache> doImport(final 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
@@ -403,7 +408,7 @@ public class GPXImporter {
static class ImportGpxZipFileThread extends ImportGpxZipThread {
private final File cacheFile;
- public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxZipFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.cacheFile = file;
Log.i("Import zipped GPX: " + file);
@@ -419,7 +424,7 @@ public class GPXImporter {
private final Uri uri;
private final ContentResolver contentResolver;
- public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) {
+ public ImportGpxZipAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) {
super(listId, importStepHandler, progressHandler);
this.uri = uri;
this.contentResolver = contentResolver;
@@ -434,14 +439,14 @@ public class GPXImporter {
final private CancellableHandler progressHandler = new CancellableHandler() {
@Override
- public void handleRegularMessage(Message msg) {
+ public void handleRegularMessage(final Message msg) {
progress.setProgress(msg.arg1);
}
};
final private Handler importStepHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
switch (msg.what) {
case IMPORT_STEP_START:
final Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL);
diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java
index 89ee887..ccc265e 100644
--- a/main/src/cgeo/geocaching/files/GPXParser.java
+++ b/main/src/cgeo/geocaching/files/GPXParser.java
@@ -8,6 +8,8 @@ import cgeo.geocaching.R;
import cgeo.geocaching.Trackable;
import cgeo.geocaching.Waypoint;
import cgeo.geocaching.connector.ConnectorFactory;
+import cgeo.geocaching.connector.IConnector;
+import cgeo.geocaching.connector.capability.ILogin;
import cgeo.geocaching.enumerations.CacheSize;
import cgeo.geocaching.enumerations.CacheType;
import cgeo.geocaching.enumerations.LoadFlags;
@@ -120,12 +122,12 @@ public abstract class GPXParser extends FileParser {
private final class UserDataListener implements EndTextElementListener {
private final int index;
- public UserDataListener(int index) {
+ public UserDataListener(final int index) {
this.index = index;
}
@Override
- public void end(String user) {
+ public void end(final String user) {
userData[index] = validate(user);
}
}
@@ -250,13 +252,13 @@ public abstract class GPXParser extends FileParser {
}
}
- protected GPXParser(int listIdIn, String namespaceIn, String versionIn) {
+ protected GPXParser(final int listIdIn, final String namespaceIn, final String versionIn) {
listId = listIdIn;
namespace = namespaceIn;
version = versionIn;
}
- static Date parseDate(String inputUntrimmed) throws ParseException {
+ static Date parseDate(final String inputUntrimmed) throws ParseException {
String input = inputUntrimmed.trim();
// remove milliseconds to reduce number of needed patterns
final MatcherWrapper matcher = new MatcherWrapper(PATTERN_MILLISECONDS, input);
@@ -280,7 +282,7 @@ public abstract class GPXParser extends FileParser {
root.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
scriptUrl = body;
}
});
@@ -289,7 +291,7 @@ public abstract class GPXParser extends FileParser {
waypoint.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("lat") > -1 && attrs.getIndex("lon") > -1) {
final String latitude = attrs.getValue("lat");
@@ -399,7 +401,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "time").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setHidden(parseDate(body));
} catch (final Exception e) {
@@ -412,7 +414,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
name = body;
String content = body.trim();
@@ -431,7 +433,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "desc").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
desc = body;
cache.setShortDescription(validate(body));
@@ -442,7 +444,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "cmt").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cmt = body;
cache.setDescription(validate(body));
@@ -453,8 +455,8 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
- final String[] content = body.split("\\|");
+ public void end(final String body) {
+ final String[] content = StringUtils.split(body, '|');
if (content.length > 0) {
type = content[0].toLowerCase(Locale.US).trim();
}
@@ -477,7 +479,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String url) {
+ public void end(final String url) {
final MatcherWrapper matcher = new MatcherWrapper(PATTERN_GUID, url);
if (matcher.matches()) {
final String guid = matcher.group(1);
@@ -497,7 +499,7 @@ public abstract class GPXParser extends FileParser {
waypoint.getChild(namespace, "urlname").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String urlName) {
+ public void end(final String urlName) {
if (cache.getName().equals(cache.getGeocode()) && StringUtils.startsWith(cache.getGeocode(), "WM")) {
cache.setName(StringUtils.trim(urlName));
}
@@ -520,7 +522,7 @@ public abstract class GPXParser extends FileParser {
gcCache.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("id") > -1) {
cache.setCacheId(attrs.getValue("id"));
@@ -541,7 +543,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String cacheName) {
+ public void end(final String cacheName) {
cache.setName(validate(cacheName));
}
});
@@ -550,7 +552,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "owner").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String ownerUserId) {
+ public void end(final String ownerUserId) {
cache.setOwnerUserId(validate(ownerUserId));
}
});
@@ -559,7 +561,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "placed_by").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String ownerDisplayName) {
+ public void end(final String ownerDisplayName) {
cache.setOwnerDisplayName(validate(ownerDisplayName));
}
});
@@ -568,7 +570,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cache.setType(CacheType.getByPattern(validate(body)));
}
});
@@ -577,7 +579,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "container").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
cache.setSize(CacheSize.getById(validate(body)));
}
});
@@ -597,7 +599,7 @@ public abstract class GPXParser extends FileParser {
gcAttribute.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
try {
if (attrs.getIndex("id") > -1 && attrs.getIndex("inc") > -1) {
final int attributeId = Integer.parseInt(attrs.getValue("id"));
@@ -617,7 +619,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "difficulty").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setDifficulty(Float.parseFloat(body));
} catch (final NumberFormatException e) {
@@ -630,7 +632,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "terrain").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
cache.setTerrain(Float.parseFloat(body));
} catch (final NumberFormatException e) {
@@ -643,7 +645,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "country").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String country) {
+ public void end(final String country) {
if (StringUtils.isBlank(cache.getLocation())) {
cache.setLocation(validate(country));
} else {
@@ -656,7 +658,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "state").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String state) {
+ public void end(final String state) {
final String trimmedState = state.trim();
if (StringUtils.isNotEmpty(trimmedState)) { // state can be completely empty
if (StringUtils.isBlank(cache.getLocation())) {
@@ -672,7 +674,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "encoded_hints").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String encoded) {
+ public void end(final String encoded) {
cache.setHint(validate(encoded));
}
});
@@ -680,7 +682,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "short_description").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String shortDesc) {
+ public void end(final String shortDesc) {
cache.setShortDescription(validate(shortDesc));
}
});
@@ -688,7 +690,7 @@ public abstract class GPXParser extends FileParser {
gcCache.getChild(nsGC, "long_description").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String desc) {
+ public void end(final String desc) {
cache.setDescription(validate(desc));
}
});
@@ -703,7 +705,7 @@ public abstract class GPXParser extends FileParser {
gcTB.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
trackable = new Trackable();
try {
@@ -733,7 +735,7 @@ public abstract class GPXParser extends FileParser {
gcTB.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String tbName) {
+ public void end(final String tbName) {
trackable.setName(validate(tbName));
}
});
@@ -747,7 +749,7 @@ public abstract class GPXParser extends FileParser {
gcLog.setStartElementListener(new StartElementListener() {
@Override
- public void start(Attributes attrs) {
+ public void start(final Attributes attrs) {
log = new LogEntry("", 0, LogType.UNKNOWN, "");
try {
@@ -765,6 +767,13 @@ public abstract class GPXParser extends FileParser {
@Override
public void end() {
if (log.type != LogType.UNKNOWN) {
+ if (log.type.isFoundLog() && StringUtils.isNotBlank(log.author)) {
+ final IConnector connector = ConnectorFactory.getConnector(cache);
+ if (connector instanceof ILogin && StringUtils.equals(log.author, ((ILogin) connector).getUserName())) {
+ cache.setFound(true);
+ cache.setVisitedDate(log.date);
+ }
+ }
logs.add(log);
}
}
@@ -774,7 +783,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "date").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
try {
log.date = parseDate(body).getTime();
} catch (final Exception e) {
@@ -787,7 +796,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
final String logType = validate(body);
log.type = LogType.getByType(logType);
}
@@ -797,7 +806,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "finder").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String finderName) {
+ public void end(final String finderName) {
log.author = validate(finderName);
}
});
@@ -806,7 +815,7 @@ public abstract class GPXParser extends FileParser {
gcLog.getChild(nsGC, "text").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String logText) {
+ public void end(final String logText) {
log.log = validate(logText);
}
});
@@ -814,7 +823,7 @@ public abstract class GPXParser extends FileParser {
try {
progressStream = new ProgressInputStream(stream);
- BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8));
+ final BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8));
Xml.parse(new InvalidXMLCharacterFilterReader(reader), root.getContentHandler());
return DataStore.loadCaches(result, EnumSet.of(LoadFlag.DB_MINIMAL));
} catch (final SAXException e) {
@@ -833,7 +842,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String watchList) {
+ public void end(final String watchList) {
cache.setOnWatchlist(Boolean.valueOf(watchList.trim()));
}
});
@@ -847,7 +856,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "Parent").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String body) {
+ public void end(final String body) {
parentCacheCode = body;
}
});
@@ -855,7 +864,7 @@ public abstract class GPXParser extends FileParser {
gsak.getChild(gsakNamespace, "FavPoints").setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String favoritePoints) {
+ public void end(final String favoritePoints) {
try {
cache.setFavoritePoints(Integer.parseInt(favoritePoints));
}
@@ -894,7 +903,7 @@ public abstract class GPXParser extends FileParser {
cgeoVisited.setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String visited) {
+ public void end(final String visited) {
wptVisited = Boolean.valueOf(visited.trim());
}
});
@@ -904,7 +913,7 @@ public abstract class GPXParser extends FileParser {
cgeoUserDefined.setEndTextElementListener(new EndTextElementListener() {
@Override
- public void end(String userDefined) {
+ public void end(final String userDefined) {
wptUserDefined = Boolean.valueOf(userDefined.trim());
}
});
@@ -917,7 +926,7 @@ public abstract class GPXParser extends FileParser {
* @param cache
* currently imported cache
*/
- protected void afterParsing(Geocache cache) {
+ protected void afterParsing(final Geocache cache) {
// can be overridden by sub classes
}
@@ -930,7 +939,7 @@ public abstract class GPXParser extends FileParser {
*/
protected abstract Element getCacheParent(Element waypoint);
- protected static String validate(String input) {
+ protected static String validate(final String input) {
if ("nil".equalsIgnoreCase(input)) {
return "";
}
diff --git a/main/src/cgeo/geocaching/files/IFileSelectionView.java b/main/src/cgeo/geocaching/files/IFileSelectionView.java
index 5bbc1b2..0407ee4 100644
--- a/main/src/cgeo/geocaching/files/IFileSelectionView.java
+++ b/main/src/cgeo/geocaching/files/IFileSelectionView.java
@@ -8,7 +8,7 @@ public interface IFileSelectionView {
String getCurrentFile();
- void setCurrentFile(String string);
+ void setCurrentFile(final String name);
void close();
diff --git a/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java b/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java
index a7a3e1b..8a089d2 100644
--- a/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java
+++ b/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java
@@ -1,98 +1,94 @@
-package cgeo.geocaching.files;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.FilterReader;
-import java.io.IOException;
-import java.io.Reader;
-
-/**
- * Filter reader which can filter out invalid XML characters and character references.
- *
- */
-public class InvalidXMLCharacterFilterReader extends FilterReader
-{
-
- public InvalidXMLCharacterFilterReader(Reader in) {
- super(in);
- }
-
- /**
- * Every overload of {@link Reader#read()} method delegates to this one so
- * it is enough to override only this one. <br />
- * To skip invalid characters this method shifts only valid chars to left
- * and returns decreased value of the original read method. So after last
- * valid character there will be some unused chars in the buffer.
- *
- * @return Number of read valid characters or <code>-1</code> if end of the
- * underling reader was reached.
- */
- @Override
- public int read(char[] cbuf, int off, int len) throws IOException {
- int read = super.read(cbuf, off, len);
- // check for end
- if (read == -1) {
- return -1;
- }
- // target position
- int pos = off - 1;
-
- int entityStart = -1;
- for (int readPos = off; readPos < off + read; readPos++) {
- boolean useChar = true;
- switch (cbuf[readPos]) {
- case '&':
- pos++;
- entityStart = readPos;
- break;
- case ';':
- pos++;
- if (entityStart >= 0) {
- int entityLength = readPos - entityStart + 1;
- if (entityLength <= 5) {
- String entity = new String(cbuf, entityStart, entityLength);
- if (StringUtils.startsWith(entity, "&#")) {
- String numberString = StringUtils.substringBetween(entity, "&#", ";");
- final int value;
- if (StringUtils.startsWith(numberString, "x")) {
- value = Integer.parseInt(numberString.substring(1), 16);
- }
- else {
- value = Integer.parseInt(numberString);
- }
- if (!isValidXMLChar((char) value)) {
- pos -= entityLength;
- useChar = false;
- }
- }
- }
- }
- break;
- default:
- if (isValidXMLChar(cbuf[readPos])) {
- pos++;
- } else {
- continue;
- }
- }
- // copy, and skip unwanted characters
- if (pos < readPos && useChar) {
- cbuf[pos] = cbuf[readPos];
- }
- }
- return pos - off + 1;
- }
-
- private static boolean isValidXMLChar(char c) {
- if ((c == 0x9) ||
- (c == 0xA) ||
- (c == 0xD) ||
- ((c >= 0x20) && (c <= 0xD7FF)) ||
- ((c >= 0xE000) && (c <= 0xFFFD)) ||
- ((c >= 0x10000) && (c <= 0x10FFFF)))
- {
- return true;
- }
- return false;
- }
+package cgeo.geocaching.files;
+
+import org.apache.commons.lang3.StringUtils;
+
+import java.io.FilterReader;
+import java.io.IOException;
+import java.io.Reader;
+
+/**
+ * Filter reader which can filter out invalid XML characters and character references.
+ *
+ */
+public class InvalidXMLCharacterFilterReader extends FilterReader
+{
+
+ public InvalidXMLCharacterFilterReader(Reader in) {
+ super(in);
+ }
+
+ /**
+ * Every overload of {@link Reader#read()} method delegates to this one so
+ * it is enough to override only this one. <br />
+ * To skip invalid characters this method shifts only valid chars to left
+ * and returns decreased value of the original read method. So after last
+ * valid character there will be some unused chars in the buffer.
+ *
+ * @return Number of read valid characters or <code>-1</code> if end of the
+ * underling reader was reached.
+ */
+ @Override
+ public int read(char[] cbuf, int off, int len) throws IOException {
+ int read = super.read(cbuf, off, len);
+ // check for end
+ if (read == -1) {
+ return -1;
+ }
+ // target position
+ int pos = off - 1;
+
+ int entityStart = -1;
+ for (int readPos = off; readPos < off + read; readPos++) {
+ boolean useChar = true;
+ switch (cbuf[readPos]) {
+ case '&':
+ pos++;
+ entityStart = readPos;
+ break;
+ case ';':
+ pos++;
+ if (entityStart >= 0) {
+ int entityLength = readPos - entityStart + 1;
+ if (entityLength <= 5) {
+ String entity = new String(cbuf, entityStart, entityLength);
+ if (StringUtils.startsWith(entity, "&#")) {
+ String numberString = StringUtils.substringBetween(entity, "&#", ";");
+ final int value;
+ if (StringUtils.startsWith(numberString, "x")) {
+ value = Integer.parseInt(numberString.substring(1), 16);
+ }
+ else {
+ value = Integer.parseInt(numberString);
+ }
+ if (!isValidXMLChar((char) value)) {
+ pos -= entityLength;
+ useChar = false;
+ }
+ }
+ }
+ }
+ break;
+ default:
+ if (isValidXMLChar(cbuf[readPos])) {
+ pos++;
+ } else {
+ continue;
+ }
+ }
+ // copy, and skip unwanted characters
+ if (pos < readPos && useChar) {
+ cbuf[pos] = cbuf[readPos];
+ }
+ }
+ return pos - off + 1;
+ }
+
+ private static boolean isValidXMLChar(char c) {
+ return (c == 0x9) ||
+ (c == 0xA) ||
+ (c == 0xD) ||
+ ((c >= 0x20) && (c <= 0xD7FF)) ||
+ ((c >= 0xE000) && (c <= 0xFFFD)) ||
+ ((c >= 0x10000) && (c <= 0x10FFFF));
+ }
} \ No newline at end of file
diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java
index 2871d77..13f8cca 100644
--- a/main/src/cgeo/geocaching/files/LocParser.java
+++ b/main/src/cgeo/geocaching/files/LocParser.java
@@ -88,7 +88,7 @@ public final class LocParser extends FileParser {
}
// >> premium only
- final String[] points = fileContent.split("<waypoint>");
+ final String[] points = StringUtils.splitByWholeSeparator(fileContent, "<waypoint>");
// parse coordinates
for (String pointString : points) {
diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
index 2aadf16..0139206 100644
--- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java
+++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java
@@ -263,7 +263,7 @@ public class SimpleDirChooser extends AbstractListActivity {
private boolean checked = false;
private boolean writeable = false;
- private static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() {
+ private final static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() {
@Override
public int compare(final Option lhs, final Option rhs) {
diff --git a/main/src/cgeo/geocaching/filter/PopularityFilter.java b/main/src/cgeo/geocaching/filter/PopularityFilter.java
index a0244b9..0fc807d 100644
--- a/main/src/cgeo/geocaching/filter/PopularityFilter.java
+++ b/main/src/cgeo/geocaching/filter/PopularityFilter.java
@@ -29,8 +29,7 @@ class PopularityFilter extends AbstractFilter {
@Override
public List<IFilter> getFilters() {
final List<IFilter> filters = new ArrayList<>(FAVORITES.length);
- for (int i = 0; i < FAVORITES.length; i++) {
- final int minRange = FAVORITES[i];
+ for (final int minRange : FAVORITES) {
final int maxRange = Integer.MAX_VALUE;
final String range = "> " + minRange;
final String name = CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.favorite_points, minRange, range);
diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
index a04f219..f7ac4db 100644
--- a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
+++ b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java
@@ -16,7 +16,7 @@ class PopularityRatioFilter extends AbstractFilter {
private final int minRatio;
private final int maxRatio;
- public PopularityRatioFilter(String name, final int minRatio, final int maxRatio) {
+ public PopularityRatioFilter(final String name, final int minRatio, final int maxRatio) {
super(name);
this.minRatio = minRatio;
this.maxRatio = maxRatio;
@@ -35,11 +35,11 @@ class PopularityRatioFilter extends AbstractFilter {
return ratio > minRatio && ratio <= maxRatio;
}
- private static int getFindsCount(Geocache cache) {
+ private static int getFindsCount(final Geocache cache) {
if (cache.getLogCounts().isEmpty()) {
cache.setLogCounts(DataStore.loadLogCounts(cache.getGeocode()));
}
- Integer logged = cache.getLogCounts().get(LogType.FOUND_IT);
+ final Integer logged = cache.getLogCounts().get(LogType.FOUND_IT);
if (logged != null) {
return logged;
}
@@ -53,10 +53,9 @@ class PopularityRatioFilter extends AbstractFilter {
@Override
public List<IFilter> getFilters() {
final List<IFilter> filters = new ArrayList<>(RATIOS.length);
- for (int i = 0; i < RATIOS.length; i++) {
- final int minRange = RATIOS[i];
+ for (final int minRange : RATIOS) {
final int maxRange = Integer.MAX_VALUE;
- final String name = "> " + minRange + " " + CgeoApplication.getInstance().getResources().getString(R.string.percent_favorite_points);
+ final String name = CgeoApplication.getInstance().getResources().getString(R.string.more_than_percent_favorite_points, minRange);
filters.add(new PopularityRatioFilter(name, minRange, maxRange));
}
return filters;
diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java
index 8de3edc..a5b31f0 100644
--- a/main/src/cgeo/geocaching/gcvote/GCVote.java
+++ b/main/src/cgeo/geocaching/gcvote/GCVote.java
@@ -8,31 +8,27 @@ import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.LeastRecentlyUsedMap;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.MatcherWrapper;
+import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.collections4.MapUtils;
+import org.apache.commons.io.Charsets;
+import org.apache.commons.io.IOUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
import org.eclipse.jdt.annotation.NonNull;
+import org.xmlpull.v1.XmlPullParser;
+import org.xmlpull.v1.XmlPullParserFactory;
+import java.io.InputStream;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
import java.util.Map;
-import java.util.regex.Pattern;
public final class GCVote {
public static final float NO_RATING = 0;
- private static final Pattern PATTERN_LOG_IN = Pattern.compile("loggedIn='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_GUID = Pattern.compile("cacheId='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_WAYPOINT = Pattern.compile("waypoint='([^']+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_RATING = Pattern.compile("voteAvg='([0-9.]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTES = Pattern.compile("voteCnt='([0-9]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTE = Pattern.compile("voteUser='([0-9.]+)'", Pattern.CASE_INSENSITIVE);
- private static final Pattern PATTERN_VOTE_ELEMENT = Pattern.compile("<vote ([^>]+)>", Pattern.CASE_INSENSITIVE);
private static final int MAX_CACHED_RATINGS = 1000;
private static final LeastRecentlyUsedMap<String, GCVoteRating> RATINGS_CACHE = new LeastRecentlyUsedMap.LruCache<>(MAX_CACHED_RATINGS);
@@ -71,124 +67,68 @@ public final class GCVote {
* @param geocodes
* @return
*/
+ @NonNull
private static Map<String, GCVoteRating> getRating(final List<String> guids, final List<String> geocodes) {
if (guids == null && geocodes == null) {
- return null;
+ return Collections.emptyMap();
}
- final Map<String, GCVoteRating> ratings = new HashMap<>();
+ final Parameters params = new Parameters("version", "cgeo");
+ final ImmutablePair<String, String> login = Settings.getGCvoteLogin();
+ if (login != null) {
+ params.put("userName", login.left, "password", login.right);
+ }
+ // use guid or gccode for lookup
+ final boolean requestByGuids = CollectionUtils.isNotEmpty(guids);
+ if (requestByGuids) {
+ params.put("cacheIds", StringUtils.join(guids, ','));
+ } else {
+ params.put("waypoints", StringUtils.join(geocodes, ','));
+ }
+ final InputStream response = Network.getResponseStream(Network.getRequest("http://gcvote.com/getVotes.php", params));
+ if (response == null) {
+ return Collections.emptyMap();
+ }
try {
- final Parameters params = new Parameters();
- if (Settings.isLogin()) {
- final ImmutablePair<String, String> login = Settings.getGCvoteLogin();
- if (login != null) {
- params.put("userName", login.left, "password", login.right);
- }
- }
- // use guid or gccode for lookup
- boolean requestByGuids = true;
- if (guids != null && !guids.isEmpty()) {
- params.put("cacheIds", StringUtils.join(guids.toArray(), ','));
- } else {
- params.put("waypoints", StringUtils.join(geocodes.toArray(), ','));
- requestByGuids = false;
- }
- params.put("version", "cgeo");
- final String page = Network.getResponseData(Network.getRequest("http://gcvote.com/getVotes.php", params));
- if (page == null) {
- return null;
- }
-
- final MatcherWrapper matcherVoteElement = new MatcherWrapper(PATTERN_VOTE_ELEMENT, page);
- while (matcherVoteElement.find()) {
- String voteData = matcherVoteElement.group(1);
- if (voteData == null) {
- continue;
- }
-
- String id = null;
- String guid = null;
- final MatcherWrapper matcherGuid = new MatcherWrapper(PATTERN_GUID, voteData);
- if (matcherGuid.find()) {
- if (matcherGuid.groupCount() > 0) {
- guid = matcherGuid.group(1);
- if (requestByGuids) {
- id = guid;
- }
- }
- }
- if (!requestByGuids) {
- final MatcherWrapper matcherWp = new MatcherWrapper(PATTERN_WAYPOINT, voteData);
- if (matcherWp.find()) {
- if (matcherWp.groupCount() > 0) {
- id = matcherWp.group(1);
- }
- }
- }
- if (id == null) {
- continue;
- }
-
- boolean loggedIn = false;
- final MatcherWrapper matcherLoggedIn = new MatcherWrapper(PATTERN_LOG_IN, page);
- if (matcherLoggedIn.find()) {
- if (matcherLoggedIn.groupCount() > 0) {
- if (matcherLoggedIn.group(1).equalsIgnoreCase("true")) {
- loggedIn = true;
- }
- }
- }
-
- float rating = NO_RATING;
- try {
- final MatcherWrapper matcherRating = new MatcherWrapper(PATTERN_RATING, voteData);
- if (matcherRating.find()) {
- rating = Float.parseFloat(matcherRating.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse rating");
- }
- if (!isValidRating(rating)) {
- continue;
- }
-
- int votes = -1;
- try {
- final MatcherWrapper matcherVotes = new MatcherWrapper(PATTERN_VOTES, voteData);
- if (matcherVotes.find()) {
- votes = Integer.parseInt(matcherVotes.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse vote count");
- }
- if (votes < 0) {
- continue;
- }
+ return getRatingsFromXMLResponse(response, requestByGuids);
+ } finally {
+ IOUtils.closeQuietly(response);
+ }
+ }
- float myVote = NO_RATING;
- if (loggedIn) {
- try {
- final MatcherWrapper matcherVote = new MatcherWrapper(PATTERN_VOTE, voteData);
- if (matcherVote.find()) {
- myVote = Float.parseFloat(matcherVote.group(1));
- }
- } catch (NumberFormatException e) {
- Log.w("GCVote.getRating: Failed to parse user's vote");
+ static Map<String, GCVoteRating> getRatingsFromXMLResponse(@NonNull final InputStream response, final boolean requestByGuids) {
+ try {
+ final XmlPullParserFactory factory = XmlPullParserFactory.newInstance();
+ final XmlPullParser xpp = factory.newPullParser();
+ xpp.setInput(response, Charsets.UTF_8.name());
+ boolean loggedIn = false;
+ final Map<String, GCVoteRating> ratings = new HashMap<>();
+ int eventType = xpp.getEventType();
+ while (eventType != XmlPullParser.END_DOCUMENT) {
+ if (eventType == XmlPullParser.START_TAG) {
+ final String tagName = xpp.getName();
+ if (StringUtils.equals(tagName, "vote")) {
+ final String guid = xpp.getAttributeValue(null, "cacheId");
+ final String id = requestByGuids ? guid : xpp.getAttributeValue(null, "waypoint");
+ final float myVote = loggedIn ? Float.parseFloat(xpp.getAttributeValue(null, "voteUser")) : 0;
+ final GCVoteRating voteRating = new GCVoteRating(Float.parseFloat(xpp.getAttributeValue(null, "voteAvg")),
+ Integer.parseInt(xpp.getAttributeValue(null, "voteCnt")),
+ myVote);
+ ratings.put(id, voteRating);
+ } else if (StringUtils.equals(tagName, "votes")) {
+ loggedIn = StringUtils.equals(xpp.getAttributeValue(null, "loggedIn"), "true");
}
}
-
- if (StringUtils.isNotBlank(id)) {
- GCVoteRating gcvoteRating = new GCVoteRating(rating, votes, myVote);
- ratings.put(id, gcvoteRating);
- RATINGS_CACHE.put(guid, gcvoteRating);
- }
+ eventType = xpp.next();
}
- } catch (RuntimeException e) {
- Log.e("GCVote.getRating", e);
- }
+ RATINGS_CACHE.putAll(ratings);
+ return ratings;
+ } catch (final Exception e) {
+ Log.e("Cannot parse GC vote result", e);
+ return Collections.emptyMap();
- return ratings;
+ }
}
/**
@@ -236,16 +176,14 @@ public final class GCVote {
try {
final Map<String, GCVoteRating> ratings = GCVote.getRating(null, geocodes);
- if (MapUtils.isNotEmpty(ratings)) {
- // save found cache coordinates
- for (Geocache cache : caches) {
- if (ratings.containsKey(cache.getGeocode())) {
- GCVoteRating rating = ratings.get(cache.getGeocode());
+ // save found cache coordinates
+ for (Geocache cache : caches) {
+ if (ratings.containsKey(cache.getGeocode())) {
+ GCVoteRating rating = ratings.get(cache.getGeocode());
- cache.setRating(rating.getRating());
- cache.setVotes(rating.getVotes());
- cache.setMyVote(rating.getMyVote());
- }
+ cache.setRating(rating.getRating());
+ cache.setVotes(rating.getVotes());
+ cache.setMyVote(rating.getMyVote());
}
}
} catch (Exception e) {
@@ -275,10 +213,6 @@ public final class GCVote {
return rating >= MIN_RATING && rating <= MAX_RATING;
}
- public static String getRatingText(final float rating) {
- return String.format(Locale.getDefault(), "%.1f", rating);
- }
-
public static boolean isVotingPossible(final Geocache cache) {
return Settings.isGCvoteLogin() && StringUtils.isNotBlank(cache.getGuid()) && cache.supportsGCVote();
}
diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java
index ba0e040..a48b0a1 100644
--- a/main/src/cgeo/geocaching/geopoint/Viewport.java
+++ b/main/src/cgeo/geocaching/geopoint/Viewport.java
@@ -79,6 +79,22 @@ public final class Viewport {
&& coords.getLatitudeE6() <= topRight.getLatitudeE6();
}
+ /**
+ * Count the number of points present in the viewport.
+ *
+ * @param points a collection of (possibly null) points
+ * @return the number of non-null points in the viewport
+ */
+ public int count(final @NonNull Collection<? extends ICoordinates> points) {
+ int total = 0;
+ for (ICoordinates point: points) {
+ if (point != null && contains(point)) {
+ total += 1;
+ }
+ }
+ return total;
+ }
+
@Override
public String toString() {
return "(" + bottomLeft.toString() + "," + topRight.toString() + ")";
diff --git a/main/src/cgeo/geocaching/list/AbstractList.java b/main/src/cgeo/geocaching/list/AbstractList.java
index 9b57b3a..5a20b9d 100644
--- a/main/src/cgeo/geocaching/list/AbstractList.java
+++ b/main/src/cgeo/geocaching/list/AbstractList.java
@@ -8,7 +8,7 @@ public abstract class AbstractList {
public final int id;
public final String title;
- private static SparseArray<AbstractList> LISTS = new SparseArray<>();
+ private final static SparseArray<AbstractList> LISTS = new SparseArray<>();
public AbstractList(final int id, final String title) {
this.id = id;
diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java
index 53632a0..abb6af1 100644
--- a/main/src/cgeo/geocaching/list/StoredList.java
+++ b/main/src/cgeo/geocaching/list/StoredList.java
@@ -24,12 +24,12 @@ import java.util.Comparator;
import java.util.List;
public final class StoredList extends AbstractList {
- public static final int TEMPORARY_LIST_ID = 0;
- public static final StoredList TEMPORARY_LIST = new StoredList(TEMPORARY_LIST_ID, "<temporary>", 0); // Never displayed
+ private static final int TEMPORARY_LIST_ID = 0;
+ public static final StoredList TEMPORARY_LIST = new StoredList(TEMPORARY_LIST_ID, "<temporary>", 0); // Never displayed
public static final int STANDARD_LIST_ID = 1;
private final int count; // this value is only valid as long as the list is not changed by other database operations
- public StoredList(int id, String title, int count) {
+ public StoredList(final int id, final String title, final int count) {
super(id, title);
this.count = count;
}
@@ -48,7 +48,7 @@ public final class StoredList extends AbstractList {
}
@Override
- public boolean equals(Object obj) {
+ public boolean equals(final Object obj) {
if (this == obj) {
return true;
}
@@ -69,10 +69,6 @@ public final class StoredList extends AbstractList {
res = app.getResources();
}
- public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards) {
- promptForListSelection(titleId, runAfterwards, false, -1);
- }
-
public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) {
promptForListSelection(titleId, runAfterwards, onlyConcreteLists, exceptListId, StringUtils.EMPTY);
}
@@ -81,18 +77,18 @@ public final class StoredList extends AbstractList {
final List<AbstractList> lists = getMenuLists(onlyConcreteLists, exceptListId);
final List<CharSequence> listsTitle = new ArrayList<>();
- for (AbstractList list : lists) {
+ for (final AbstractList list : lists) {
listsTitle.add(list.getTitleAndCount());
}
final CharSequence[] items = new CharSequence[listsTitle.size()];
final Activity activity = activityRef.get();
- AlertDialog.Builder builder = new AlertDialog.Builder(activity);
+ final AlertDialog.Builder builder = new AlertDialog.Builder(activity);
builder.setTitle(res.getString(titleId));
builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() {
@Override
- public void onClick(DialogInterface dialogInterface, int itemId) {
+ public void onClick(final DialogInterface dialogInterface, final int itemId) {
final AbstractList list = lists.get(itemId);
if (list == PseudoList.NEW_LIST) {
// create new list on the fly
@@ -106,12 +102,12 @@ public final class StoredList extends AbstractList {
builder.create().show();
}
- public static List<AbstractList> getMenuLists(boolean onlyConcreteLists, int exceptListId) {
+ public static List<AbstractList> getMenuLists(final boolean onlyConcreteLists, final int exceptListId) {
final List<AbstractList> lists = new ArrayList<>();
lists.addAll(getSortedLists());
- if (exceptListId > StoredList.TEMPORARY_LIST_ID) {
- StoredList exceptList = DataStore.getList(exceptListId);
+ if (exceptListId > StoredList.TEMPORARY_LIST.id) {
+ final StoredList exceptList = DataStore.getList(exceptListId);
if (exceptList != null) {
lists.remove(exceptList);
}
@@ -138,7 +134,7 @@ public final class StoredList extends AbstractList {
Collections.sort(lists, new Comparator<StoredList>() {
@Override
- public int compare(StoredList lhs, StoredList rhs) {
+ public int compare(final StoredList lhs, final StoredList rhs) {
// have the standard list at the top
if (lhs.id == STANDARD_LIST_ID) {
return -1;
@@ -153,7 +149,7 @@ public final class StoredList extends AbstractList {
return lists;
}
- public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, String newListName) {
+ public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, final String newListName) {
handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new Action1<String>() {
// We need to update the list cache by creating a new StoredList object here.
@@ -177,7 +173,7 @@ public final class StoredList extends AbstractList {
});
}
- private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) {
+ private void handleListNameInput(final String defaultValue, final int dialogTitle, final int buttonTitle, final Action1<String> runnable) {
final Activity activity = activityRef.get();
if (activity == null) {
return;
@@ -187,7 +183,7 @@ public final class StoredList extends AbstractList {
@Override
public void call(final String input) {
// remove whitespaces added by autocompletion of Android keyboard
- String listName = StringUtils.trim(input);
+ final String listName = StringUtils.trim(input);
if (StringUtils.isNotBlank(listName)) {
runnable.call(listName);
}
@@ -225,8 +221,8 @@ public final class StoredList extends AbstractList {
/**
* Return the given list, if it is a concrete list. Return the default list otherwise.
*/
- public static int getConcreteList(int listId) {
- if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID || listId == PseudoList.HISTORY_LIST.id) {
+ public static int getConcreteList(final int listId) {
+ if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST.id || listId == PseudoList.HISTORY_LIST.id) {
return STANDARD_LIST_ID;
}
return listId;
diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java
index 8aae0cc..d257cc6 100644
--- a/main/src/cgeo/geocaching/maps/CGeoMap.java
+++ b/main/src/cgeo/geocaching/maps/CGeoMap.java
@@ -6,6 +6,7 @@ import cgeo.geocaching.CacheListActivity;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.DataStore;
import cgeo.geocaching.Geocache;
+import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.SearchResult;
import cgeo.geocaching.Waypoint;
@@ -31,7 +32,6 @@ import cgeo.geocaching.maps.interfaces.MapProvider;
import cgeo.geocaching.maps.interfaces.MapSource;
import cgeo.geocaching.maps.interfaces.MapViewImpl;
import cgeo.geocaching.maps.interfaces.OnMapDragListener;
-import cgeo.geocaching.sensors.DirectionProvider;
import cgeo.geocaching.sensors.GeoDirHandler;
import cgeo.geocaching.sensors.IGeoData;
import cgeo.geocaching.settings.Settings;
@@ -125,16 +125,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
private static final int UPDATE_PROGRESS = 0;
private static final int FINISHED_LOADING_DETAILS = 1;
- //Menu
- private static final String EXTRAS_GEOCODE = "geocode";
- private static final String EXTRAS_COORDS = "coords";
- private static final String EXTRAS_WPTTYPE = "wpttype";
- private static final String EXTRAS_MAPSTATE = "mapstate";
- private static final String EXTRAS_SEARCH = "search";
- private static final String EXTRAS_MAP_TITLE = "mapTitle";
- private static final String EXTRAS_MAP_MODE = "mapMode";
- private static final String EXTRAS_LIVE_ENABLED = "liveEnabled";
-
private static final String BUNDLE_MAP_SOURCE = "mapSource";
private static final String BUNDLE_MAP_STATE = "mapState";
private static final String BUNDLE_LIVE_ENABLED = "liveEnabled";
@@ -204,7 +194,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
private static BlockingQueue<Runnable> downloadQueue = new ArrayBlockingQueue<>(1);
private static ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, downloadQueue, new ThreadPoolExecutor.DiscardOldestPolicy());
private static BlockingQueue<Runnable> loadQueue = new ArrayBlockingQueue<>(1);
-
private static ThreadPoolExecutor loadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, loadQueue, new ThreadPoolExecutor.DiscardOldestPolicy());
// handlers
/** Updates the titles */
@@ -373,21 +362,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
}
protected void countVisibleCaches() {
- final List<Geocache> protectedCaches = caches.getAsList();
-
- int count = 0;
- if (!protectedCaches.isEmpty()) {
- final Viewport viewport = mapView.getViewport();
-
- for (final Geocache cache : protectedCaches) {
- if (cache != null && cache.getCoords() != null) {
- if (viewport.contains(cache)) {
- count++;
- }
- }
- }
- }
- cachesCnt = count;
+ cachesCnt = mapView.getViewport().count(caches.getAsList());
}
@Override
@@ -417,14 +392,14 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// Get parameters from the intent
final Bundle extras = activity.getIntent().getExtras();
if (extras != null) {
- mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE);
- isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false);
- searchIntent = extras.getParcelable(EXTRAS_SEARCH);
- geocodeIntent = extras.getString(EXTRAS_GEOCODE);
- coordsIntent = extras.getParcelable(EXTRAS_COORDS);
- waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE));
- mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE);
- mapTitle = extras.getString(EXTRAS_MAP_TITLE);
+ mapMode = (MapMode) extras.get(Intents.EXTRA_MAP_MODE);
+ isLiveEnabled = extras.getBoolean(Intents.EXTRA_LIVE_ENABLED, false);
+ searchIntent = extras.getParcelable(Intents.EXTRA_SEARCH);
+ geocodeIntent = extras.getString(Intents.EXTRA_GEOCODE);
+ coordsIntent = extras.getParcelable(Intents.EXTRA_COORDS);
+ waypointTypeIntent = WaypointType.findById(extras.getString(Intents.EXTRA_WPTTYPE));
+ mapStateIntent = extras.getIntArray(Intents.EXTRA_MAPSTATE);
+ mapTitle = extras.getString(Intents.EXTRA_MAP_TITLE);
}
else {
mapMode = MapMode.LIVE;
@@ -506,7 +481,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
}
prepareFilterBar();
- if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) {
+ if (!app.isLiveMapHintShownInThisSession() && Settings.getLiveMapHintShowCount() <= 3) {
LiveMapInfoDialogBuilder.create(activity).show();
}
}
@@ -716,7 +691,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
public void call(final Integer selectedListId) {
storeCaches(geocodes, selectedListId);
}
- }, true, StoredList.TEMPORARY_LIST_ID);
+ }, true, StoredList.TEMPORARY_LIST.id);
} else {
storeCaches(geocodes, StoredList.STANDARD_LIST_ID);
}
@@ -872,19 +847,19 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// prepare information to restart a similar view
final Intent mapIntent = new Intent(activity, Settings.getMapProvider().getMapClass());
- mapIntent.putExtra(EXTRAS_SEARCH, searchIntent);
- mapIntent.putExtra(EXTRAS_GEOCODE, geocodeIntent);
+ mapIntent.putExtra(Intents.EXTRA_SEARCH, searchIntent);
+ mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocodeIntent);
if (coordsIntent != null) {
- mapIntent.putExtra(EXTRAS_COORDS, coordsIntent);
+ mapIntent.putExtra(Intents.EXTRA_COORDS, coordsIntent);
}
- mapIntent.putExtra(EXTRAS_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null);
- mapIntent.putExtra(EXTRAS_MAP_TITLE, mapTitle);
- mapIntent.putExtra(EXTRAS_MAP_MODE, mapMode);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, isLiveEnabled);
+ mapIntent.putExtra(Intents.EXTRA_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, mapTitle);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, mapMode);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, isLiveEnabled);
final int[] mapState = currentMapState();
if (mapState != null) {
- mapIntent.putExtra(EXTRAS_MAPSTATE, mapState);
+ mapIntent.putExtra(Intents.EXTRA_MAPSTATE, mapState);
}
// start the new map
@@ -917,7 +892,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// Set center of map to my location if appropriate.
private void myLocationInMiddle(final IGeoData geo) {
- if (followMyLocation && !geo.isPseudoLocation()) {
+ if (followMyLocation) {
centerMap(geo.getCoords());
}
}
@@ -933,8 +908,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
// minimum change of location in fraction of map width/height (whatever is smaller) for position overlay update
private static final float MIN_LOCATION_DELTA = 0.01f;
- Location currentLocation = new Location("");
- boolean locationValid = false;
+ Location currentLocation = CgeoApplication.getInstance().currentGeo().getLocation();
float currentHeading;
private long timeLastPositionOverlayCalculation = 0;
@@ -949,15 +923,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
@Override
public void updateGeoDir(final IGeoData geo, final float dir) {
- if (geo.isPseudoLocation()) {
- locationValid = false;
- } else {
- locationValid = true;
-
- currentLocation = geo.getLocation();
- currentHeading = DirectionProvider.getDirectionNow(dir);
- repaintPositionOverlay();
- }
+ currentLocation = geo.getLocation();
+ currentHeading = AngleUtils.getDirectionNow(dir);
+ repaintPositionOverlay();
}
/**
@@ -971,16 +939,16 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
try {
final CGeoMap map = mapRef.get();
if (map != null) {
- final boolean needsRepaintForDistance = needsRepaintForDistance();
+ final boolean needsRepaintForDistanceOrAccuracy = needsRepaintForDistanceOrAccuracy();
final boolean needsRepaintForHeading = needsRepaintForHeading();
- if (needsRepaintForDistance) {
+ if (needsRepaintForDistanceOrAccuracy) {
if (map.followMyLocation) {
map.centerMap(new Geopoint(currentLocation));
}
}
- if (needsRepaintForDistance || needsRepaintForHeading) {
+ if (needsRepaintForDistanceOrAccuracy || needsRepaintForHeading) {
map.overlayPositionAndScale.setCoordinates(currentLocation);
map.overlayPositionAndScale.setHeading(currentHeading);
map.mapView.repaintRequired(map.overlayPositionAndScale);
@@ -1000,11 +968,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
return Math.abs(AngleUtils.difference(currentHeading, map.overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA;
}
- boolean needsRepaintForDistance() {
- if (!locationValid) {
- return false;
- }
-
+ boolean needsRepaintForDistanceOrAccuracy() {
final CGeoMap map = mapRef.get();
if (map == null) {
return false;
@@ -1013,6 +977,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
float dist = Float.MAX_VALUE;
if (lastLocation != null) {
+ if (lastLocation.getAccuracy() != currentLocation.getAccuracy()) {
+ return true;
+ }
dist = currentLocation.distanceTo(lastLocation);
}
@@ -1039,7 +1006,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
displayPoint(coordsIntent);
loadTimer = Subscriptions.empty();
} else {
- loadTimer = startLoadTimer();
+ loadTimer = Schedulers.newThread().createWorker().schedulePeriodically(new LoadTimerAction(this), 0, 250, TimeUnit.MILLISECONDS);
}
return loadTimer;
}
@@ -1096,13 +1063,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
}
/**
- * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}.
- */
- private Subscription startLoadTimer() {
- return Schedulers.newThread().createWorker().schedulePeriodically(new LoadTimerAction(this), 0, 250, TimeUnit.MILLISECONDS);
- }
-
- /**
* get if map is loading something
*
* @return
@@ -1252,9 +1212,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
//render
displayExecutor.execute(new DisplayRunnable(this));
- } catch (final ThreadDeath e) {
- Log.d("DownloadThread stopped");
- displayHandler.sendEmptyMessage(UPDATE_TITLE);
} finally {
showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress
}
@@ -1304,19 +1261,14 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
}
itemsToDisplay.add(getCacheItem(cache));
}
-
- overlayCaches.updateItems(itemsToDisplay);
- displayHandler.sendEmptyMessage(INVALIDATE_MAP);
-
- } else {
+ }
+ // don't add other waypoints to overlayCaches if just one point should be displayed
+ if (coordsIntent == null) {
overlayCaches.updateItems(itemsToDisplay);
- displayHandler.sendEmptyMessage(INVALIDATE_MAP);
}
+ displayHandler.sendEmptyMessage(INVALIDATE_MAP);
displayHandler.sendEmptyMessage(UPDATE_TITLE);
- } catch (final ThreadDeath e) {
- Log.d("DisplayThread stopped");
- displayHandler.sendEmptyMessage(UPDATE_TITLE);
} finally {
showProgressHandler.sendEmptyMessage(HIDE_PROGRESS);
}
@@ -1612,42 +1564,41 @@ public class CGeoMap extends AbstractMap implements ViewFactory {
public static void startActivitySearch(final Activity fromActivity, final SearchResult search, final String title) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_SEARCH, search);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIST);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_SEARCH, search);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIST);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
if (StringUtils.isNotBlank(title)) {
- mapIntent.putExtra(CGeoMap.EXTRAS_MAP_TITLE, title);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title);
}
fromActivity.startActivity(mapIntent);
}
- public static void startActivityLiveMap(final Activity fromActivity) {
- final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIVE);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, Settings.isLiveMap());
- fromActivity.startActivity(mapIntent);
+ public static Intent getLiveMapIntent(final Activity fromActivity) {
+ return newIntent(fromActivity)
+ .putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIVE)
+ .putExtra(Intents.EXTRA_LIVE_ENABLED, Settings.isLiveMap());
}
public static void startActivityCoords(final Activity fromActivity, final Geopoint coords, final WaypointType type, final String title) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.COORDS);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
- mapIntent.putExtra(EXTRAS_COORDS, coords);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.COORDS);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_COORDS, coords);
if (type != null) {
- mapIntent.putExtra(EXTRAS_WPTTYPE, type.id);
+ mapIntent.putExtra(Intents.EXTRA_WPTTYPE, type.id);
}
if (StringUtils.isNotBlank(title)) {
- mapIntent.putExtra(EXTRAS_MAP_TITLE, title);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title);
}
fromActivity.startActivity(mapIntent);
}
public static void startActivityGeoCode(final Activity fromActivity, final String geocode) {
final Intent mapIntent = newIntent(fromActivity);
- mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.SINGLE);
- mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false);
- mapIntent.putExtra(EXTRAS_GEOCODE, geocode);
- mapIntent.putExtra(EXTRAS_MAP_TITLE, geocode);
+ mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.SINGLE);
+ mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false);
+ mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocode);
+ mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, geocode);
fromActivity.startActivity(mapIntent);
}
diff --git a/main/src/cgeo/geocaching/maps/MapActivity.java b/main/src/cgeo/geocaching/maps/MapActivity.java
new file mode 100644
index 0000000..28668ca
--- /dev/null
+++ b/main/src/cgeo/geocaching/maps/MapActivity.java
@@ -0,0 +1,17 @@
+package cgeo.geocaching.maps;
+
+import android.app.Activity;
+import android.os.Bundle;
+
+/**
+ * This activity provides an entry point for external intent calls, and then forwards to the currently used map activity
+ * implementation.
+ */
+public class MapActivity extends Activity {
+ @Override
+ protected void onCreate(final Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+ startActivity(CGeoMap.getLiveMapIntent(this));
+ finish();
+ }
+}
diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java
index dd4ff0f..212188d 100644
--- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java
+++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java
@@ -33,7 +33,8 @@ public class MapProviderFactory {
public static boolean isGoogleMapsInstalled() {
// Check if API key is available
- if (StringUtils.isBlank(CgeoApplication.getInstance().getString(R.string.maps_api_key))) {
+ final String mapsKey = CgeoApplication.getInstance().getString(R.string.maps_api_key);
+ if (StringUtils.length(mapsKey) < 30 || StringUtils.contains(mapsKey, "key")) {
Log.w("No Google API key available.");
return false;
}
@@ -41,7 +42,7 @@ public class MapProviderFactory {
// Check if API is available
try {
Class.forName("com.google.android.maps.MapActivity");
- } catch (ClassNotFoundException e) {
+ } catch (final ClassNotFoundException e) {
return false;
}
@@ -59,7 +60,7 @@ public class MapProviderFactory {
return provider1 == provider2 && provider1.isSameActivity(source1, source2);
}
- public static void addMapviewMenuItems(Menu menu) {
+ public static void addMapviewMenuItems(final Menu menu) {
final SubMenu parentMenu = menu.findItem(R.id.menu_select_mapview).getSubMenu();
final int currentSource = Settings.getMapSource().getNumericalId();
@@ -78,8 +79,8 @@ public class MapProviderFactory {
* @return the map source, or <tt>null</tt> if <tt>id</tt> does not correspond to a registered map source
*/
@Nullable
- public static MapSource getMapSource(int id) {
- for (MapSource mapSource : mapSources) {
+ public static MapSource getMapSource(final int id) {
+ for (final MapSource mapSource : mapSources) {
if (mapSource.getNumericalId() == id) {
return mapSource;
}
@@ -109,7 +110,7 @@ public class MapProviderFactory {
*/
public static void deleteOfflineMapSources() {
final ArrayList<MapSource> deletion = new ArrayList<>();
- for (MapSource mapSource : mapSources) {
+ for (final MapSource mapSource : mapSources) {
if (mapSource instanceof MapsforgeMapProvider.OfflineMapSource) {
deletion.add(mapSource);
}
diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java
index 31edc9f..ab902d2 100644
--- a/main/src/cgeo/geocaching/network/HtmlImage.java
+++ b/main/src/cgeo/geocaching/network/HtmlImage.java
@@ -89,7 +89,7 @@ public class HtmlImage implements Html.ImageGetter {
final private int maxWidth;
final private int maxHeight;
final private Resources resources;
- final private TextView view;
+ protected final TextView view;
// Background loading
final private PublishSubject<Observable<String>> loading = PublishSubject.create();
@@ -154,6 +154,10 @@ public class HtmlImage implements Html.ImageGetter {
if (view == null) {
return drawable.toBlocking().lastOrDefault(null);
}
+ return getContainerDrawable(drawable);
+ }
+
+ protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) {
return new ContainerDrawable(view, drawable);
}
@@ -162,17 +166,17 @@ public class HtmlImage implements Html.ImageGetter {
public Observable<BitmapDrawable> fetchDrawable(final String url) {
if (StringUtils.isBlank(url) || ImageUtils.containsPattern(url, BLOCKED)) {
- return Observable.from(ImageUtils.getTransparent1x1Drawable(resources));
+ return Observable.just(ImageUtils.getTransparent1x1Drawable(resources));
}
// Explicit local file URLs are loaded from the filesystem regardless of their age. The IO part is short
// enough to make the whole operation on the computation scheduler.
if (FileUtils.isFileUrl(url)) {
- return Observable.defer(new Func0<Observable<? extends BitmapDrawable>>() {
+ return Observable.defer(new Func0<Observable<BitmapDrawable>>() {
@Override
- public Observable<? extends BitmapDrawable> call() {
+ public Observable<BitmapDrawable> call() {
final Bitmap bitmap = loadCachedImage(FileUtils.urlToFile(url), true).getLeft();
- return bitmap != null ? Observable.from(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty();
+ return bitmap != null ? Observable.just(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty();
}
}).subscribeOn(RxUtils.computationScheduler);
}
@@ -208,12 +212,7 @@ public class HtmlImage implements Html.ImageGetter {
private Pair<BitmapDrawable, Boolean> loadFromDisk() {
final Pair<Bitmap, Boolean> loadResult = loadImageFromStorage(url, pseudoGeocode, shared);
- final Bitmap bitmap = loadResult.getLeft();
- return new ImmutablePair<>(bitmap != null ?
- ImageUtils.scaleBitmapToFitDisplay(bitmap) :
- null,
- loadResult.getRight()
- );
+ return scaleImage(loadResult);
}
private void downloadAndSave(final Subscriber<? super BitmapDrawable> subscriber) {
@@ -254,6 +253,15 @@ public class HtmlImage implements Html.ImageGetter {
});
}
+ @SuppressWarnings("static-method")
+ protected Pair<BitmapDrawable, Boolean> scaleImage(final Pair<Bitmap, Boolean> loadResult) {
+ final Bitmap bitmap = loadResult.getLeft();
+ return new ImmutablePair<>(bitmap != null ?
+ ImageUtils.scaleBitmapToFitDisplay(bitmap) :
+ null,
+ loadResult.getRight());
+ }
+
public Observable<String> waitForEndObservable(@Nullable final CancellableHandler handler) {
if (handler != null) {
handler.unsubscribeIfCancelled(subscription);
diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java
index a49b302..e8c2b28 100644
--- a/main/src/cgeo/geocaching/network/Network.java
+++ b/main/src/cgeo/geocaching/network/Network.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.network;
import cgeo.geocaching.files.LocalStorage;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.JsonUtils;
import cgeo.geocaching.utils.Log;
import cgeo.geocaching.utils.TextUtils;
@@ -26,11 +27,12 @@ import ch.boye.httpclientandroidlib.params.CoreConnectionPNames;
import ch.boye.httpclientandroidlib.params.CoreProtocolPNames;
import ch.boye.httpclientandroidlib.params.HttpParams;
import ch.boye.httpclientandroidlib.util.EntityUtils;
+
+import com.fasterxml.jackson.databind.node.ObjectNode;
+
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.Nullable;
-import org.json.JSONException;
-import org.json.JSONObject;
import android.content.Context;
import android.net.ConnectivityManager;
@@ -43,6 +45,7 @@ import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.URLDecoder;
import java.net.URLEncoder;
+import java.util.regex.Pattern;
public abstract class Network {
@@ -51,7 +54,7 @@ public abstract class Network {
/** Native user agent, taken from a Android 2.2 Nexus **/
private final static String NATIVE_USER_AGENT = "Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1";
- private static final String PATTERN_PASSWORD = "(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+";
+ private static final Pattern PATTERN_PASSWORD = Pattern.compile("(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+");
private final static HttpParams clientParams = new BasicHttpParams();
@@ -63,7 +66,7 @@ public abstract class Network {
}
private static String hidePassword(final String message) {
- return message.replaceAll(PATTERN_PASSWORD, "password=***");
+ return PATTERN_PASSWORD.matcher(message).replaceAll("password=***");
}
private static HttpClient getHttpClient() {
@@ -107,14 +110,14 @@ public abstract class Network {
* @return the HTTP response, or null in case of an encoding error params
*/
@Nullable
- public static HttpResponse postJsonRequest(final String uri, final JSONObject json) {
+ public static HttpResponse postJsonRequest(final String uri, final ObjectNode json) {
HttpPost request = new HttpPost(uri);
request.addHeader("Content-Type", "application/json; charset=utf-8");
if (json != null) {
try {
request.setEntity(new StringEntity(json.toString(), CharEncoding.UTF_8));
} catch (UnsupportedEncodingException e) {
- Log.e("postJsonRequest:JSON Entity: UnsupportedEncodingException");
+ Log.e("postJsonRequest:JSON Entity: UnsupportedEncodingException", e);
return null;
}
}
@@ -344,14 +347,14 @@ public abstract class Network {
* @return a JSON object if the request was successful and the body could be decoded, <code>null</code> otherwise
*/
@Nullable
- public static JSONObject requestJSON(final String uri, @Nullable final Parameters params) {
+ public static ObjectNode requestJSON(final String uri, @Nullable final Parameters params) {
final HttpResponse response = request("GET", uri, params, new Parameters("Accept", "application/json, text/javascript, */*; q=0.01"), null);
final String responseData = getResponseData(response, false);
if (responseData != null) {
try {
- return new JSONObject(responseData);
- } catch (final JSONException e) {
- Log.w("Network.requestJSON", e);
+ return (ObjectNode) JsonUtils.reader.readTree(responseData);
+ } catch (final IOException e) {
+ Log.w("requestJSON", e);
}
}
diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java
index cfc62fc..c23ffbf 100644
--- a/main/src/cgeo/geocaching/network/OAuth.java
+++ b/main/src/cgeo/geocaching/network/OAuth.java
@@ -6,7 +6,6 @@ import ch.boye.httpclientandroidlib.NameValuePair;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
-import org.eclipse.jdt.annotation.Nullable;
import java.util.ArrayList;
import java.util.Date;
@@ -18,8 +17,7 @@ public class OAuth {
final String method,
final boolean https,
final Parameters params,
- @Nullable final String token,
- @Nullable final String tokenSecret,
+ final OAuthTokens tokens,
final String consumerKey,
final String consumerSecret) {
params.put(
@@ -27,7 +25,7 @@ public class OAuth {
"oauth_nonce", CryptUtils.md5(Long.toString(System.currentTimeMillis())),
"oauth_signature_method", "HMAC-SHA1",
"oauth_timestamp", Long.toString(new Date().getTime() / 1000),
- "oauth_token", StringUtils.defaultString(token),
+ "oauth_token", StringUtils.defaultString(tokens.getTokenPublic()),
"oauth_version", "1.0");
params.sort();
@@ -36,7 +34,7 @@ public class OAuth {
paramsEncoded.add(nameValue.getName() + "=" + OAuth.percentEncode(nameValue.getValue()));
}
- final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them!
+ final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokens.getTokenSecret()); // both even if empty some of them!
final @NonNull String joinedParams = StringUtils.join(paramsEncoded.toArray(), '&');
final String requestPacked = method + "&" + OAuth.percentEncode((https ? "https" : "http") + "://" + host + path) + "&" + OAuth.percentEncode(joinedParams);
params.put("oauth_signature", CryptUtils.base64Encode(CryptUtils.hashHmac(requestPacked, keysPacked)));
@@ -48,7 +46,7 @@ public class OAuth {
* @param url
* @return
*/
- static String percentEncode(@NonNull String url) {
+ static String percentEncode(@NonNull final String url) {
return StringUtils.replace(Network.rfc3986URLEncode(url), "*", "%2A");
}
}
diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
index eb56f0b..5efea02 100644
--- a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
+++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java
@@ -40,6 +40,8 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
private static final int STATUS_ERROR = 0;
private static final int STATUS_SUCCESS = 1;
private static final int STATUS_ERROR_EXT_MSG = 2;
+ private static final Pattern PARAMS_PATTERN_1 = Pattern.compile("oauth_token=([\\w_.-]+)");
+ private static final Pattern PARAMS_PATTERN_2 = Pattern.compile("oauth_token_secret=([\\w_.-]+)");
@NonNull private String host = StringUtils.EMPTY;
@NonNull private String pathRequest = StringUtils.EMPTY;
@@ -51,18 +53,16 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
@NonNull private String callback = StringUtils.EMPTY;
private String OAtoken = null;
private String OAtokenSecret = null;
- private final Pattern paramsPattern1 = Pattern.compile("oauth_token=([a-zA-Z0-9\\-\\_.]+)");
- private final Pattern paramsPattern2 = Pattern.compile("oauth_token_secret=([a-zA-Z0-9\\-\\_.]+)");
@InjectView(R.id.start) protected Button startButton;
@InjectView(R.id.auth_1) protected TextView auth_1;
@InjectView(R.id.auth_2) protected TextView auth_2;
private ProgressDialog requestTokenDialog = null;
private ProgressDialog changeTokensDialog = null;
- private Handler requestTokenHandler = new Handler() {
+ private final Handler requestTokenHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
if (requestTokenDialog != null && requestTokenDialog.isShowing()) {
requestTokenDialog.dismiss();
}
@@ -85,10 +85,10 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
};
- private Handler changeTokensHandler = new Handler() {
+ private final Handler changeTokensHandler = new Handler() {
@Override
- public void handleMessage(Message msg) {
+ public void handleMessage(final Message msg) {
if (changeTokensDialog != null && changeTokensDialog.isShowing()) {
changeTokensDialog.dismiss();
}
@@ -105,10 +105,10 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
};
@Override
- public void onCreate(Bundle savedInstanceState) {
+ public void onCreate(final Bundle savedInstanceState) {
super.onCreate(savedInstanceState, R.layout.authorization_activity);
- Bundle extras = getIntent().getExtras();
+ final Bundle extras = getIntent().getExtras();
if (extras != null) {
host = BundleUtils.getString(extras, Intents.EXTRA_OAUTH_HOST, host);
pathRequest = BundleUtils.getString(extras, Intents.EXTRA_OAUTH_PATH_REQUEST, pathRequest);
@@ -125,7 +125,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
auth_1.setText(getAuthExplainShort());
auth_2.setText(getAuthExplainLong());
- ImmutablePair<String, String> tempToken = getTempTokens();
+ final ImmutablePair<String, String> tempToken = getTempTokens();
OAtoken = tempToken.left;
OAtokenSecret = tempToken.right;
@@ -167,7 +167,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
final Parameters params = new Parameters();
params.put("oauth_callback", callback);
final String method = "GET";
- OAuth.signOAuth(host, pathRequest, method, https, params, null, null, consumerKey, consumerSecret);
+ OAuth.signOAuth(host, pathRequest, method, https, params, new OAuthTokens(null, null), consumerKey, consumerSecret);
final HttpResponse response = Network.getRequest(getUrlPrefix() + host + pathRequest, params);
if (Network.isSuccess(response)) {
@@ -176,11 +176,11 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
int status = STATUS_ERROR;
if (StringUtils.isNotBlank(line)) {
assert line != null;
- final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line);
+ final MatcherWrapper paramsMatcher1 = new MatcherWrapper(PARAMS_PATTERN_1, line);
if (paramsMatcher1.find()) {
OAtoken = paramsMatcher1.group(1);
}
- final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line);
+ final MatcherWrapper paramsMatcher2 = new MatcherWrapper(PARAMS_PATTERN_2, line);
if (paramsMatcher2.find()) {
OAtokenSecret = paramsMatcher2.group(1);
}
@@ -193,9 +193,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser));
startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getUrlPrefix() + host + pathAuthorize + "?" + encodedParams)));
status = STATUS_SUCCESS;
- } catch (ParseException e) {
- Log.e("OAuthAuthorizationActivity.requestToken", e);
- } catch (IOException e) {
+ } catch (ParseException | IOException e) {
Log.e("OAuthAuthorizationActivity.requestToken", e);
}
}
@@ -221,17 +219,17 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
final Parameters params = new Parameters("oauth_verifier", verifier);
final String method = "POST";
- OAuth.signOAuth(host, pathAccess, method, https, params, OAtoken, OAtokenSecret, consumerKey, consumerSecret);
+ OAuth.signOAuth(host, pathAccess, method, https, params, new OAuthTokens(OAtoken, OAtokenSecret), consumerKey, consumerSecret);
final String line = StringUtils.defaultString(Network.getResponseData(Network.postRequest(getUrlPrefix() + host + pathAccess, params)));
OAtoken = "";
OAtokenSecret = "";
- final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line);
+ final MatcherWrapper paramsMatcher1 = new MatcherWrapper(PARAMS_PATTERN_1, line);
if (paramsMatcher1.find()) {
OAtoken = paramsMatcher1.group(1);
}
- final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line);
+ final MatcherWrapper paramsMatcher2 = new MatcherWrapper(PARAMS_PATTERN_2, line);
if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) {
OAtokenSecret = paramsMatcher2.group(1);
}
@@ -244,7 +242,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
setTokens(OAtoken, OAtokenSecret, true);
status = AUTHENTICATED;
}
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("OAuthAuthorizationActivity.changeToken", e);
}
@@ -258,7 +256,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
private class StartListener implements View.OnClickListener {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
if (requestTokenDialog == null) {
requestTokenDialog = new ProgressDialog(OAuthAuthorizationActivity.this);
requestTokenDialog.setCancelable(false);
@@ -333,7 +331,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
* @return String with a more detailed error message (user-facing, localized), can be empty
*/
@SuppressWarnings("static-method")
- protected String getExtendedErrorMsg(HttpResponse response) {
+ protected String getExtendedErrorMsg(final HttpResponse response) {
return StringUtils.EMPTY;
}
@@ -363,14 +361,14 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
@NonNull public final String consumerSecret;
@NonNull public final String callback;
- public OAuthParameters(@NonNull String host,
- @NonNull String pathRequest,
- @NonNull String pathAuthorize,
- @NonNull String pathAccess,
- boolean https,
- @NonNull String consumerKey,
- @NonNull String consumerSecret,
- @NonNull String callback) {
+ public OAuthParameters(@NonNull final String host,
+ @NonNull final String pathRequest,
+ @NonNull final String pathAuthorize,
+ @NonNull final String pathAccess,
+ final boolean https,
+ @NonNull final String consumerKey,
+ @NonNull final String consumerSecret,
+ @NonNull final String callback) {
this.host = host;
this.pathRequest = pathRequest;
this.pathAuthorize = pathAuthorize;
@@ -381,7 +379,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity {
this.callback = callback;
}
- public void setOAuthExtras(Intent intent) {
+ public void setOAuthExtras(final Intent intent) {
if (intent != null) {
intent.putExtra(Intents.EXTRA_OAUTH_HOST, host);
intent.putExtra(Intents.EXTRA_OAUTH_PATH_REQUEST, pathRequest);
diff --git a/main/src/cgeo/geocaching/network/OAuthTokens.java b/main/src/cgeo/geocaching/network/OAuthTokens.java
new file mode 100644
index 0000000..9f45e7f
--- /dev/null
+++ b/main/src/cgeo/geocaching/network/OAuthTokens.java
@@ -0,0 +1,38 @@
+package cgeo.geocaching.network;
+
+import cgeo.geocaching.connector.oc.OCApiConnector;
+import cgeo.geocaching.settings.Settings;
+
+import org.apache.commons.lang3.StringUtils;
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.util.Pair;
+
+public class OAuthTokens extends Pair<String, String> {
+
+ public OAuthTokens(@NonNull final OCApiConnector connector) {
+ this(Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId()));
+ }
+
+ public OAuthTokens(final ImmutablePair<String, String> tokenPair) {
+ this(tokenPair.left, tokenPair.right);
+ }
+
+ public OAuthTokens(final String pub, final String secret) {
+ super(pub, secret);
+ }
+
+ public boolean isValid() {
+ return StringUtils.isNotBlank(getTokenPublic()) && StringUtils.isNotBlank(getTokenSecret());
+ }
+
+ public String getTokenPublic() {
+ return first;
+ }
+
+ public String getTokenSecret() {
+ return second;
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/network/SmileyImage.java b/main/src/cgeo/geocaching/network/SmileyImage.java
new file mode 100644
index 0000000..86baeaa
--- /dev/null
+++ b/main/src/cgeo/geocaching/network/SmileyImage.java
@@ -0,0 +1,44 @@
+package cgeo.geocaching.network;
+
+import cgeo.geocaching.list.StoredList;
+import cgeo.geocaching.utils.ImageUtils;
+import cgeo.geocaching.utils.ImageUtils.LineHeightContainerDrawable;
+
+import org.apache.commons.lang3.tuple.ImmutablePair;
+import org.apache.commons.lang3.tuple.Pair;
+
+import rx.Observable;
+
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
+import android.widget.TextView;
+
+/**
+ * Specialized image class for fetching and displaying smileys in the log book.
+ */
+public class SmileyImage extends HtmlImage {
+
+ public SmileyImage(final String geocode, final TextView view) {
+ super(geocode, false, StoredList.STANDARD_LIST_ID, false, view);
+ }
+
+ @Override
+ protected Pair<BitmapDrawable, Boolean> scaleImage(final Pair<Bitmap, Boolean> loadResult) {
+ final Bitmap bitmap = loadResult.getLeft();
+ BitmapDrawable drawable;
+ if (bitmap != null) {
+ drawable = new BitmapDrawable(view.getResources(), bitmap);
+ drawable.setBounds(ImageUtils.scaleImageToLineHeight(drawable, view));
+ }
+ else {
+ drawable = null;
+ }
+ return new ImmutablePair<>(drawable, loadResult.getRight());
+ }
+
+ @Override
+ protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) {
+ return new LineHeightContainerDrawable(view, drawable);
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java
index 82650d1..bc4a5db 100644
--- a/main/src/cgeo/geocaching/network/StatusUpdater.java
+++ b/main/src/cgeo/geocaching/network/StatusUpdater.java
@@ -4,8 +4,7 @@ import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.utils.RxUtils;
import cgeo.geocaching.utils.Version;
-import org.json.JSONException;
-import org.json.JSONObject;
+import com.fasterxml.jackson.databind.node.ObjectNode;
import rx.functions.Action0;
import rx.subjects.BehaviorSubject;
@@ -31,11 +30,11 @@ public class StatusUpdater {
this.url = url;
}
- Status(final JSONObject response) {
- message = get(response, "message");
- messageId = get(response, "message_id");
- icon = get(response, "icon");
- url = get(response, "url");
+ Status(final ObjectNode response) {
+ message = response.path("message").asText(null);
+ messageId = response.path("message_id").asText(null);
+ icon = response.path("icon").asText(null);
+ url = response.path("url").asText(null);
}
final static public Status closeoutStatus =
@@ -55,7 +54,7 @@ public class StatusUpdater {
RxUtils.networkScheduler.createWorker().schedulePeriodically(new Action0() {
@Override
public void call() {
- final JSONObject response =
+ final ObjectNode response =
Network.requestJSON("http://status.cgeo.org/api/status.json",
new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())),
"version_name", Version.getVersionName(CgeoApplication.getInstance()),
@@ -67,12 +66,4 @@ public class StatusUpdater {
}, 0, 1800, TimeUnit.SECONDS);
}
- private static String get(final JSONObject json, final String key) {
- try {
- return json.getString(key);
- } catch (final JSONException e) {
- return null;
- }
- }
-
}
diff --git a/main/src/cgeo/geocaching/playservices/LocationProvider.java b/main/src/cgeo/geocaching/playservices/LocationProvider.java
new file mode 100644
index 0000000..f235a3b
--- /dev/null
+++ b/main/src/cgeo/geocaching/playservices/LocationProvider.java
@@ -0,0 +1,157 @@
+package cgeo.geocaching.playservices;
+
+import cgeo.geocaching.sensors.GeoData;
+import cgeo.geocaching.sensors.IGeoData;
+import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils;
+
+import com.google.android.gms.common.ConnectionResult;
+import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks;
+import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener;
+import com.google.android.gms.location.LocationClient;
+import com.google.android.gms.location.LocationListener;
+import com.google.android.gms.location.LocationRequest;
+
+import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Subscriber;
+import rx.functions.Action0;
+import rx.functions.Action1;
+import rx.functions.Func1;
+import rx.subjects.ReplaySubject;
+import rx.subscriptions.Subscriptions;
+
+import android.content.Context;
+import android.location.Location;
+import android.os.Bundle;
+
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+
+public class LocationProvider implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener {
+
+ private static final LocationRequest LOCATION_REQUEST =
+ LocationRequest.create().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(2000).setFastestInterval(250);
+ private static final LocationRequest LOCATION_REQUEST_LOW_POWER =
+ LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000);
+ private static final AtomicInteger mostPreciseCount = new AtomicInteger(0);
+ private static final AtomicInteger lowPowerCount = new AtomicInteger(0);
+ private static LocationProvider instance = null;
+ private static ReplaySubject<IGeoData> subject = ReplaySubject.createWithSize(1);
+ private final LocationClient locationClient;
+
+ private static synchronized LocationProvider getInstance(final Context context) {
+ if (instance == null) {
+ instance = new LocationProvider(context);
+ }
+ return instance;
+ }
+
+ private synchronized void updateRequest() {
+ if (locationClient.isConnected()) {
+ if (mostPreciseCount.get() > 0) {
+ Log.d("LocationProvider: requesting most precise locations");
+ locationClient.requestLocationUpdates(LOCATION_REQUEST, this, RxUtils.looperCallbacksLooper);
+ } else if (lowPowerCount.get() > 0) {
+ Log.d("LocationProvider: requesting low-power locations");
+ locationClient.requestLocationUpdates(LOCATION_REQUEST_LOW_POWER, this, RxUtils.looperCallbacksLooper);
+ } else {
+ Log.d("LocationProvider: stopping location requests");
+ locationClient.removeLocationUpdates(this);
+ }
+ }
+ }
+
+ private static Observable<IGeoData> get(final Context context, final AtomicInteger reference) {
+ final LocationProvider instance = getInstance(context);
+ return Observable.create(new OnSubscribe<IGeoData>() {
+ @Override
+ public void call(final Subscriber<? super IGeoData> subscriber) {
+ if (reference.incrementAndGet() == 1) {
+ instance.updateRequest();
+ }
+ subscriber.add(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ RxUtils.looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (reference.decrementAndGet() == 0) {
+ instance.updateRequest();
+ }
+ }
+ }, 2500, TimeUnit.MILLISECONDS);
+ }
+ }));
+ subscriber.add(subject.subscribe(new Action1<IGeoData>() {
+ @Override
+ public void call(final IGeoData geoData) {
+ subscriber.onNext(geoData);
+ }
+ }));
+ }
+ });
+ }
+
+ private static Observable<IGeoData> getInitialLocation(final Context context, final boolean lowPower) {
+ return get(context, lowPower ? lowPowerCount : mostPreciseCount).first();
+ }
+
+ public static Observable<IGeoData> getMostPrecise(final Context context) {
+ return get(context, mostPreciseCount);
+ }
+
+ public static Observable<IGeoData> getLowPower(Context context, boolean withInitialLocation) {
+ final Observable<IGeoData> initialLocationObservable = withInitialLocation ? getInitialLocation(context, true) : Observable.<IGeoData>empty();
+ final Observable<IGeoData> lowPowerObservable = get(context, lowPowerCount).skip(1);
+ final Observable<IGeoData> gpsFixObservable = get(context, mostPreciseCount).skip(1).lift(RxUtils.operatorTakeUntil(new Func1<IGeoData, Boolean>() {
+ @Override
+ public Boolean call(final IGeoData geoData) {
+ return geoData.getAccuracy() < 20;
+ }
+ }));
+ return initialLocationObservable.concatWith(lowPowerObservable.ambWith(gpsFixObservable.delaySubscription(6, TimeUnit.SECONDS)).first()
+ .concatWith(lowPowerObservable).timeout(25, TimeUnit.SECONDS).retry());
+ }
+
+ /**
+ * Build a new geo data provider object.
+ * <p/>
+ * There is no need to instantiate more than one such object in an application, as observers can be added
+ * at will.
+ *
+ * @param context the context used to retrieve the system services
+ */
+ private LocationProvider(final Context context) {
+ final IGeoData initialLocation = GeoData.getInitialLocation(context);
+ if (initialLocation != null) {
+ subject.onNext(initialLocation);
+ }
+ locationClient = new LocationClient(context, this, this);
+ locationClient.connect();
+ }
+
+ @Override
+ public void onConnected(final Bundle bundle) {
+ updateRequest();
+ }
+
+ @Override
+ public void onDisconnected() {
+ }
+
+ @Override
+ public void onConnectionFailed(final ConnectionResult connectionResult) {
+ Log.e("cannot connect to Google Play location service: " + connectionResult);
+ subject.onError(new RuntimeException("Connection failed: " + connectionResult));
+ }
+
+ @Override
+ public void onLocationChanged(final Location location) {
+ if (Settings.useLowPowerMode()) {
+ location.setProvider(GeoData.LOW_POWER_PROVIDER);
+ }
+ subject.onNext(new GeoData(location));
+ }
+}
diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java
deleted file mode 100644
index ed5d76a..0000000
--- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java
+++ /dev/null
@@ -1,146 +0,0 @@
-package cgeo.geocaching.sensors;
-
-import cgeo.geocaching.CgeoApplication;
-import cgeo.geocaching.utils.AngleUtils;
-import cgeo.geocaching.utils.StartableHandlerThread;
-
-import rx.Observable;
-import rx.Observable.OnSubscribe;
-import rx.Subscriber;
-import rx.subjects.BehaviorSubject;
-
-import android.content.Context;
-import android.hardware.Sensor;
-import android.hardware.SensorEvent;
-import android.hardware.SensorEventListener;
-import android.hardware.SensorManager;
-import android.os.Handler;
-import android.os.Process;
-import android.view.Surface;
-import android.view.WindowManager;
-
-public class DirectionProvider {
-
- private static final BehaviorSubject<Float> SUBJECT = BehaviorSubject.create(0.0f);
-
- private static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
-
- private DirectionProvider() {
- // utility class
- }
-
- static class Listener implements SensorEventListener, StartableHandlerThread.Callback {
-
- private int count = 0;
-
- private SensorManager sensorManager;
-
- @Override
- public void onSensorChanged(final SensorEvent event) {
- SUBJECT.onNext(event.values[0]);
- }
-
- @Override
- public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
- /*
- * There is a bug in Android, which apparently causes this method to be called every
- * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here.
- *
- * See for example https://code.google.com/p/android/issues/detail?id=14792
- */
- }
-
- @Override
- public void start(final Context context, final Handler handler) {
- if (!hasSensor(context)) {
- return;
- }
- if (++count == 1) {
- Sensor orientationSensor = getOrientationSensor(context);
- sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL, handler);
- }
- }
-
- @Override
- public void stop() {
- if (!hasSensor) {
- return;
- }
- if (--count == 0) {
- sensorManager.unregisterListener(this);
- }
- }
-
- /**
- * Assume that there is an orientation sensor, unless we have really checked that
- */
- private boolean hasSensor = true;
-
- /**
- * Flag for one time check if there is a sensor.
- */
- private boolean hasSensorChecked = false;
-
- public boolean hasSensor(Context context) {
- if (!hasSensorChecked) {
- hasSensor = getOrientationSensor(context) != null;
- hasSensorChecked = true;
- }
- return hasSensor;
- }
-
- // This will be removed when using a new location service. Until then, it is okay to be used.
- @SuppressWarnings("deprecation")
- private Sensor getOrientationSensor(final Context context) {
- sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
- return sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
- }
-
- }
-
- private static final StartableHandlerThread HANDLER_THREAD =
- new StartableHandlerThread("DirectionProvider thread", Process.THREAD_PRIORITY_BACKGROUND, new Listener());
-
- static {
- HANDLER_THREAD.start();
- }
-
- public static Observable<Float> create(final Context context) {
- return Observable.create(new OnSubscribe<Float>() {
- @Override
- public void call(final Subscriber<? super Float> subscriber) {
- HANDLER_THREAD.start(subscriber, context);
- SUBJECT.subscribe(subscriber);
- }
- });
- }
-
- /**
- * Take the phone rotation (through a given activity) in account and adjust the direction.
- *
- * @param direction the unadjusted direction in degrees, in the [0, 360[ range
- * @return the adjusted direction in degrees, in the [0, 360[ range
- */
-
- public static float getDirectionNow(final float direction) {
- return AngleUtils.normalize(direction + getRotationOffset());
- }
-
- static float reverseDirectionNow(final float direction) {
- return AngleUtils.normalize(direction - getRotationOffset());
- }
-
- private static int getRotationOffset() {
- switch (WINDOW_MANAGER.getDefaultDisplay().getRotation()) {
- case Surface.ROTATION_90:
- return 90;
- case Surface.ROTATION_180:
- return 180;
- case Surface.ROTATION_270:
- return 270;
- default:
- return 0;
- }
- }
-
-}
diff --git a/main/src/cgeo/geocaching/sensors/GeoData.java b/main/src/cgeo/geocaching/sensors/GeoData.java
index c0b3974..561c09f 100644
--- a/main/src/cgeo/geocaching/sensors/GeoData.java
+++ b/main/src/cgeo/geocaching/sensors/GeoData.java
@@ -2,22 +2,42 @@ package cgeo.geocaching.sensors;
import cgeo.geocaching.enumerations.LocationProviderType;
import cgeo.geocaching.geopoint.Geopoint;
+import cgeo.geocaching.utils.Log;
+import android.content.Context;
import android.location.Location;
import android.location.LocationManager;
-class GeoData extends Location implements IGeoData {
- private final boolean gpsEnabled;
- private final int satellitesVisible;
- private final int satellitesFixed;
- private final boolean pseudoLocation;
+import javax.annotation.Nullable;
- GeoData(final Location location, final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed, final boolean pseudoLocation) {
+public class GeoData extends Location implements IGeoData {
+
+ public static final String INITIAL_PROVIDER = "initial";
+ public static final String FUSED_PROVIDER = "fused";
+ public static final String LOW_POWER_PROVIDER = "low-power";
+
+ // Some devices will not have the last position available (for example the emulator). In this case,
+ // rather than waiting forever for a position update which might never come, we emulate it by placing
+ // the user arbitrarly at Paris Notre-Dame, one of the most visited free tourist attractions in the world.
+ final public static GeoData DUMMY_LOCATION = new GeoData(new Location(INITIAL_PROVIDER));
+ static {
+ DUMMY_LOCATION.setLatitude(48.85308);
+ DUMMY_LOCATION.setLongitude(2.34962);
+ }
+
+ public GeoData(final Location location) {
super(location);
- this.gpsEnabled = gpsEnabled;
- this.satellitesVisible = satellitesVisible;
- this.satellitesFixed = satellitesFixed;
- this.pseudoLocation = pseudoLocation;
+ }
+
+ @Nullable
+ static Location best(@Nullable final Location gpsLocation, @Nullable final Location netLocation) {
+ if (isRecent(gpsLocation) || !(netLocation != null)) {
+ return gpsLocation;
+ }
+ if (!(gpsLocation != null)) {
+ return netLocation;
+ }
+ return gpsLocation.getTime() >= netLocation.getTime() ? gpsLocation : netLocation;
}
@Override
@@ -32,6 +52,13 @@ class GeoData extends Location implements IGeoData {
if (provider.equals(LocationManager.NETWORK_PROVIDER)) {
return LocationProviderType.NETWORK;
}
+ // LocationManager.FUSED_PROVIDER constant is not available at API level 9
+ if (provider.equals(FUSED_PROVIDER)) {
+ return LocationProviderType.FUSED;
+ }
+ if (provider.equals(LOW_POWER_PROVIDER)) {
+ return LocationProviderType.LOW_POWER;
+ }
return LocationProviderType.LAST;
}
@@ -45,23 +72,35 @@ class GeoData extends Location implements IGeoData {
return new Geopoint(this);
}
- @Override
- public boolean getGpsEnabled() {
- return gpsEnabled;
+ @Nullable public static GeoData getInitialLocation(final Context context) {
+ final LocationManager geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ if (geoManager == null) {
+ Log.w("No LocationManager available");
+ return null;
+ }
+ try {
+ // Try to find a sensible initial location from the last locations known to Android.
+ final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
+ final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
+ final Location bestLocation = best(lastGpsLocation, lastNetworkLocation);
+ if (bestLocation != null) {
+ bestLocation.setProvider(INITIAL_PROVIDER);
+ return new GeoData(bestLocation);
+ }
+ Log.i("No last known location available");
+ return null;
+ } catch (final Exception e) {
+ // This error is non-fatal as its only consequence is that we will start with a dummy location
+ // instead of a previously known one.
+ Log.e("Error when retrieving last known location", e);
+ return null;
+ }
}
- @Override
- public int getSatellitesVisible() {
- return satellitesVisible;
- }
- @Override
- public int getSatellitesFixed() {
- return satellitesFixed;
- }
- @Override
- public boolean isPseudoLocation() {
- return pseudoLocation;
+ public static boolean isRecent(@Nullable final Location location) {
+ return location != null && System.currentTimeMillis() <= location.getTime() + 30000;
}
+
}
diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
index a4799cb..faecbe3 100644
--- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
+++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java
@@ -1,25 +1,13 @@
package cgeo.geocaching.sensors;
import cgeo.geocaching.utils.Log;
-import cgeo.geocaching.utils.StartableHandlerThread;
+import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
import org.apache.commons.lang3.StringUtils;
import rx.Observable;
-import rx.Observable.OnSubscribe;
-import rx.Subscriber;
-import rx.Subscription;
-import rx.android.schedulers.AndroidSchedulers;
-import rx.functions.Action0;
-import rx.functions.Action1;
-import rx.observables.ConnectableObservable;
-import rx.subjects.BehaviorSubject;
-import rx.subscriptions.CompositeSubscription;
-import rx.subscriptions.Subscriptions;
import android.content.Context;
-import android.location.GpsSatellite;
-import android.location.GpsStatus;
import android.location.Location;
import android.location.LocationListener;
import android.location.LocationManager;
@@ -27,40 +15,13 @@ import android.os.Bundle;
import java.util.concurrent.TimeUnit;
-public class GeoDataProvider implements OnSubscribe<IGeoData> {
+public class GeoDataProvider extends LooperCallbacks<IGeoData> {
- private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last";
+ private final Context context;
private final LocationManager geoManager;
- private final LocationData gpsLocation = new LocationData();
- private final LocationData netLocation = new LocationData();
- private final BehaviorSubject<IGeoData> subject;
- private static final StartableHandlerThread handlerThread =
- new StartableHandlerThread("GeoDataProvider thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
- static {
- handlerThread.start();
- }
-
- public boolean gpsEnabled = false;
- public int satellitesVisible = 0;
- public int satellitesFixed = 0;
-
- private static class LocationData {
- public Location location;
- public long timestamp = 0;
-
- public void update(final Location location) {
- this.location = location;
- timestamp = System.currentTimeMillis();
- }
-
- public boolean isRecent() {
- return isValid() && System.currentTimeMillis() < timestamp + 30000;
- }
-
- public boolean isValid() {
- return location != null;
- }
- }
+ private Location latestGPSLocation = null;
+ private final Listener networkListener = new Listener();
+ private final Listener gpsListener = new Listener();
/**
* Build a new geo data provider object.
@@ -71,117 +32,51 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> {
* @param context the context used to retrieve the system services
*/
protected GeoDataProvider(final Context context) {
- geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
- subject = BehaviorSubject.create(findInitialLocation());
+ super(2500, TimeUnit.MILLISECONDS);
+ this.context = context.getApplicationContext();
+ geoManager = (LocationManager) this.context.getSystemService(Context.LOCATION_SERVICE);
}
public static Observable<IGeoData> create(final Context context) {
- final GeoDataProvider provider = new GeoDataProvider(context);
- return provider.worker.refCount();
+ return Observable.create(new GeoDataProvider(context));
}
@Override
- public void call(final Subscriber<? super IGeoData> subscriber) {
- subject.subscribe(subscriber);
- }
-
- final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) {
- private int debugSessionCounter = 0;
-
- private final Object lock = new Object();
- private int count = 0;
-
- final private GpsStatus.Listener gpsStatusListener = new GpsStatusListener();
- final private Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation);
- final private Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation);
-
- @Override
- public void connect(Action1<? super Subscription> connection) {
- final CompositeSubscription subscription = new CompositeSubscription();
- AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() {
- @Override
- public void call() {
- synchronized(lock) {
- if (count++ == 0) {
- Log.d("GeoDataProvider: starting the GPS and network listeners" + " (" + ++debugSessionCounter + ")");
- geoManager.addGpsStatusListener(gpsStatusListener);
- for (final Listener listener : new Listener[] { networkListener, gpsListener }) {
- try {
- geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener);
- } catch (final Exception e) {
- Log.w("There is no location provider " + listener.locationProvider);
- }
- }
- }
- }
-
- subscription.add(Subscriptions.create(new Action0() {
- @Override
- public void call() {
- AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() {
- @Override
- public void call() {
- synchronized (lock) {
- if (--count == 0) {
- Log.d("GeoDataProvider: stopping the GPS and network listeners" + " (" + debugSessionCounter + ")");
- geoManager.removeUpdates(networkListener);
- geoManager.removeUpdates(gpsListener);
- geoManager.removeGpsStatusListener(gpsStatusListener);
- }
- }
- }
- }, 2500, TimeUnit.MILLISECONDS);
- }
- }));
- }
- });
- connection.call(subscription);
+ public void onStart() {
+ final IGeoData initialLocation = GeoData.getInitialLocation(context);
+ if (initialLocation != null) {
+ subscriber.onNext(initialLocation);
}
- };
-
- private IGeoData findInitialLocation() {
- final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER);
+ Log.d("GeoDataProvider: starting the GPS and network listeners");
try {
- // Try to find a sensible initial location from the last locations known to Android.
- final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER);
- final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER);
-
- // If both providers are non-null, take the most recent one
- if (lastGpsLocation != null && lastNetworkLocation != null) {
- if (lastGpsLocation.getTime() >= lastNetworkLocation.getTime()) {
- copyCoords(initialLocation, lastGpsLocation);
- } else {
- copyCoords(initialLocation, lastNetworkLocation);
- }
- } else if (lastGpsLocation != null) {
- copyCoords(initialLocation, lastGpsLocation);
- } else if (lastNetworkLocation != null) {
- copyCoords(initialLocation, lastNetworkLocation);
- } else {
- Log.i("GeoDataProvider: no last known location available");
- }
+ geoManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener);
+ } catch (final Exception e) {
+ Log.w("Unable to create GPS location provider: " + e.getMessage());
+ }
+ try {
+ geoManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, networkListener);
} catch (final Exception e) {
- // This error is non-fatal as its only consequence is that we will start with a dummy location
- // instead of a previously known one.
- Log.e("GeoDataProvider: error when retrieving last known location", e);
+ Log.w("Unable to create network location provider: " + e.getMessage());
}
- // Start with an historical GeoData just in case someone queries it before we get
- // a chance to get any information.
- return new GeoData(initialLocation, false, 0, 0, true);
}
- private static void copyCoords(final Location target, final Location source) {
- target.setLatitude(source.getLatitude());
- target.setLongitude(source.getLongitude());
+ @Override
+ protected void onStop() {
+ Log.d("GeoDataProvider: stopping the GPS and network listeners");
+ geoManager.removeUpdates(networkListener);
+ geoManager.removeUpdates(gpsListener);
}
private class Listener implements LocationListener {
- private final String locationProvider;
- private final LocationData locationData;
- Listener(final String locationProvider, final LocationData locationData) {
- this.locationProvider = locationProvider;
- this.locationData = locationData;
+ @Override
+ public void onLocationChanged(final Location location) {
+ if (StringUtils.equals(location.getProvider(), LocationManager.GPS_PROVIDER)) {
+ latestGPSLocation = location;
+ assign(latestGPSLocation);
+ } else {
+ assign(GeoData.best(latestGPSLocation, location));
+ }
}
@Override
@@ -198,86 +93,12 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> {
public void onProviderEnabled(final String provider) {
// nothing
}
-
- @Override
- public void onLocationChanged(final Location location) {
- locationData.update(location);
- selectBest();
- }
- }
-
- private final class GpsStatusListener implements GpsStatus.Listener {
-
- @Override
- public void onGpsStatusChanged(final int event) {
- boolean changed = false;
- switch (event) {
- case GpsStatus.GPS_EVENT_FIRST_FIX:
- case GpsStatus.GPS_EVENT_SATELLITE_STATUS: {
- final GpsStatus status = geoManager.getGpsStatus(null);
- int visible = 0;
- int fixed = 0;
- for (final GpsSatellite satellite : status.getSatellites()) {
- if (satellite.usedInFix()) {
- fixed++;
- }
- visible++;
- }
- if (visible != satellitesVisible || fixed != satellitesFixed) {
- satellitesVisible = visible;
- satellitesFixed = fixed;
- changed = true;
- }
- break;
- }
- case GpsStatus.GPS_EVENT_STARTED:
- if (!gpsEnabled) {
- gpsEnabled = true;
- changed = true;
- }
- break;
- case GpsStatus.GPS_EVENT_STOPPED:
- if (gpsEnabled) {
- gpsEnabled = false;
- satellitesFixed = 0;
- satellitesVisible = 0;
- changed = true;
- }
- break;
- default:
- throw new IllegalStateException();
- }
-
- if (changed) {
- selectBest();
- }
- }
- }
-
- private LocationData best() {
- if (gpsLocation.isRecent() || !netLocation.isValid()) {
- return gpsLocation.isValid() ? gpsLocation : null;
- }
- if (!gpsLocation.isValid()) {
- return netLocation;
- }
- return gpsLocation.timestamp > netLocation.timestamp ? gpsLocation : netLocation;
- }
-
- private void selectBest() {
- assign(best());
}
- private void assign(final LocationData locationData) {
- if (locationData == null) {
- return;
- }
-
+ private void assign(final Location location) {
// We do not necessarily get signalled when satellites go to 0/0.
- final int visible = gpsLocation.isRecent() ? satellitesVisible : 0;
- final boolean pseudoLocation = StringUtils.equals(locationData.location.getProvider(), LAST_LOCATION_PSEUDO_PROVIDER);
- final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed, pseudoLocation);
- subject.onNext(current);
+ final IGeoData current = new GeoData(location);
+ subscriber.onNext(current);
}
}
diff --git a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
index 0f30142..d127784 100644
--- a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
+++ b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java
@@ -2,6 +2,7 @@ package cgeo.geocaching.sensors;
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.settings.Settings;
+import cgeo.geocaching.utils.AngleUtils;
import org.apache.commons.lang3.tuple.ImmutablePair;
@@ -21,7 +22,7 @@ import rx.subscriptions.CompositeSubscription;
* accordingly in {@code onPause}.
*
* The direction is always relative to the top of the device (natural direction), and that it must
- * be fixed using {@link DirectionProvider#getDirectionNow(float)}. When the direction is derived from the GPS,
+ * be fixed using {@link cgeo.geocaching.utils.AngleUtils#getDirectionNow(float)}. When the direction is derived from the GPS,
* it is altered so that the fix can still be applied as if the information came from the compass.
*/
public abstract class GeoDirHandler {
@@ -29,6 +30,7 @@ public abstract class GeoDirHandler {
public static final int UPDATE_GEODATA = 1 << 1;
public static final int UPDATE_DIRECTION = 1 << 2;
public static final int UPDATE_GEODIR = 1 << 3;
+ public static final int LOW_POWER = 1 << 4;
private static final CgeoApplication app = CgeoApplication.getInstance();
@@ -76,7 +78,7 @@ public abstract class GeoDirHandler {
private static float fixDirection(final IGeoData geoData, final float direction) {
final boolean useGPSBearing = !Settings.isUseCompass() || geoData.getSpeed() > 5;
- return useGPSBearing ? DirectionProvider.reverseDirectionNow(geoData.getBearing()) : direction;
+ return useGPSBearing ? AngleUtils.reverseDirectionNow(geoData.getBearing()) : direction;
}
/**
@@ -85,8 +87,9 @@ public abstract class GeoDirHandler {
*/
public Subscription start(final int flags) {
final CompositeSubscription subscriptions = new CompositeSubscription();
+ final boolean lowPower = (flags & LOW_POWER) != 0;
if ((flags & UPDATE_GEODATA) != 0) {
- subscriptions.add(app.geoDataObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() {
+ subscriptions.add(app.geoDataObservable(lowPower).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() {
@Override
public void call(final IGeoData geoData) {
updateGeoData(geoData);
@@ -102,7 +105,7 @@ public abstract class GeoDirHandler {
}));
}
if ((flags & UPDATE_GEODIR) != 0) {
- subscriptions.add(Observable.combineLatest(app.geoDataObservable(), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() {
+ subscriptions.add(Observable.combineLatest(app.geoDataObservable(lowPower), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() {
@Override
public ImmutablePair<IGeoData, Float> call(final IGeoData geoData, final Float direction) {
return ImmutablePair.of(geoData, fixDirection(geoData, direction));
diff --git a/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java
new file mode 100644
index 0000000..5f12e99
--- /dev/null
+++ b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java
@@ -0,0 +1,99 @@
+package cgeo.geocaching.sensors;
+
+import cgeo.geocaching.sensors.GpsStatusProvider.Status;
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+
+import rx.Observable;
+
+import android.content.Context;
+import android.location.GpsSatellite;
+import android.location.GpsStatus;
+import android.location.LocationManager;
+
+public class GpsStatusProvider extends LooperCallbacks<Status> {
+
+ public static class Status {
+ final public boolean gpsEnabled;
+ final public int satellitesVisible;
+ final public int satellitesFixed;
+
+ public Status(final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed) {
+ this.gpsEnabled = gpsEnabled;
+ this.satellitesVisible = satellitesVisible;
+ this.satellitesFixed = satellitesFixed;
+ }
+ }
+
+ private final LocationManager geoManager;
+ private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener();
+ private Status latest = new Status(false, 0, 0);
+
+ private static final Status NO_GPS = new Status(false, 0, 0);
+
+ /**
+ * Build a new gps status provider object.
+ * <p/>
+ * There is no need to instantiate more than one such object in an application, as observers can be added
+ * at will.
+ *
+ * @param context the context used to retrieve the system services
+ */
+ protected GpsStatusProvider(final Context context) {
+ geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
+ }
+
+ public static Observable<Status> create(final Context context) {
+ return Observable.create(new GpsStatusProvider(context));
+ }
+
+ @Override
+ protected void onStart() {
+ Log.d("GpsStatusProvider: starting the GPS status listener");
+ subscriber.onNext(NO_GPS);
+ geoManager.addGpsStatusListener(gpsStatusListener);
+ }
+
+ @Override
+ protected void onStop() {
+ Log.d("GpsStatusProvider: stopping the GPS status listener");
+ geoManager.removeGpsStatusListener(gpsStatusListener);
+ }
+
+ private final class GpsStatusListener implements GpsStatus.Listener {
+
+ @Override
+ public void onGpsStatusChanged(final int event) {
+ switch (event) {
+ case GpsStatus.GPS_EVENT_FIRST_FIX:
+ case GpsStatus.GPS_EVENT_SATELLITE_STATUS: {
+ final GpsStatus status = geoManager.getGpsStatus(null);
+ int visible = 0;
+ int fixed = 0;
+ for (final GpsSatellite satellite : status.getSatellites()) {
+ if (satellite.usedInFix()) {
+ fixed++;
+ }
+ visible++;
+ }
+ if (visible == latest.satellitesVisible && fixed == latest.satellitesFixed) {
+ return;
+ }
+ latest = new Status(true, visible, fixed);
+ break;
+ }
+ case GpsStatus.GPS_EVENT_STARTED:
+ latest = new Status(true, 0, 0);
+ break;
+ case GpsStatus.GPS_EVENT_STOPPED:
+ latest = new Status(false, 0, 0);
+ break;
+ default:
+ throw new IllegalStateException();
+ }
+
+ subscriber.onNext(latest);
+ }
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/sensors/IGeoData.java b/main/src/cgeo/geocaching/sensors/IGeoData.java
index 5b4f046..b78b805 100644
--- a/main/src/cgeo/geocaching/sensors/IGeoData.java
+++ b/main/src/cgeo/geocaching/sensors/IGeoData.java
@@ -10,13 +10,8 @@ public interface IGeoData {
public Location getLocation();
public LocationProviderType getLocationProvider();
- public boolean isPseudoLocation();
-
public Geopoint getCoords();
public float getBearing();
public float getSpeed();
public float getAccuracy();
- public boolean getGpsEnabled();
- public int getSatellitesVisible();
- public int getSatellitesFixed();
}
diff --git a/main/src/cgeo/geocaching/sensors/OrientationProvider.java b/main/src/cgeo/geocaching/sensors/OrientationProvider.java
new file mode 100644
index 0000000..83e0638
--- /dev/null
+++ b/main/src/cgeo/geocaching/sensors/OrientationProvider.java
@@ -0,0 +1,67 @@
+package cgeo.geocaching.sensors;
+
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+
+import rx.Observable;
+
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class OrientationProvider extends LooperCallbacks<Float> implements SensorEventListener {
+
+ private final SensorManager sensorManager;
+ private final Sensor orientationSensor;
+
+ @SuppressWarnings("deprecation")
+ protected OrientationProvider(final Context context) {
+ sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION);
+ if (orientationSensor != null) {
+ Log.d("OrientationProvider: sensor found");
+ } else {
+ Log.w("OrientationProvider: no orientation sensor on this device");
+ }
+ }
+
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ subscriber.onNext(event.values[0]);
+ }
+
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
+ /*
+ * There is a bug in Android, which apparently causes this method to be called every
+ * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here.
+ *
+ * See for example https://code.google.com/p/android/issues/detail?id=14792
+ */
+ }
+
+ @Override
+ public void onStart() {
+ if (orientationSensor != null) {
+ Log.d("OrientationProvider: starting the orientation provider");
+ sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ subscriber.onError(new RuntimeException("orientation sensor is absent on this device"));
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (orientationSensor != null) {
+ Log.d("OrientationProvider: stopping the orientation provider");
+ sensorManager.unregisterListener(this);
+ }
+ }
+
+ public static Observable<Float> create(final Context context) {
+ return Observable.create(new OrientationProvider(context));
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/sensors/RotationProvider.java b/main/src/cgeo/geocaching/sensors/RotationProvider.java
new file mode 100644
index 0000000..40e2c3c
--- /dev/null
+++ b/main/src/cgeo/geocaching/sensors/RotationProvider.java
@@ -0,0 +1,83 @@
+package cgeo.geocaching.sensors;
+
+import cgeo.geocaching.utils.Log;
+import cgeo.geocaching.utils.RxUtils.LooperCallbacks;
+
+import rx.Observable;
+
+import android.annotation.TargetApi;
+import android.content.Context;
+import android.hardware.Sensor;
+import android.hardware.SensorEvent;
+import android.hardware.SensorEventListener;
+import android.hardware.SensorManager;
+
+public class RotationProvider extends LooperCallbacks<Float> implements SensorEventListener {
+
+ private final SensorManager sensorManager;
+ private final Sensor rotationSensor;
+ private final float[] rotationMatrix = new float[16];
+ private final float[] orientation = new float[4];
+ private final float[] values = new float[4];
+
+ @TargetApi(19)
+ protected RotationProvider(final Context context, final boolean lowPower) {
+ sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE);
+ // The geomagnetic rotation vector introduced in Android 4.4 (API 19) requires less power. Favour it
+ // even if it is more sensible to noise in low-power settings.
+ final Sensor sensor = lowPower ? sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR) : null;
+ if (sensor != null) {
+ rotationSensor = sensor;
+ Log.d("RotationProvider: geomagnetic (low-power) sensor found");
+ } else {
+ rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR);
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: sensor found");
+ } else {
+ Log.w("RotationProvider: no rotation sensor on this device");
+ }
+ }
+ }
+
+ @Override
+ public void onSensorChanged(final SensorEvent event) {
+ // On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation
+ // vector has more than 4 elements. Since only the four first elements are used, we can truncate the vector
+ // without losing precision.
+ if (event.values.length > 4) {
+ System.arraycopy(event.values, 0, values, 0, 4);
+ SensorManager.getRotationMatrixFromVector(rotationMatrix, values);
+ } else {
+ SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values);
+ }
+ SensorManager.getOrientation(rotationMatrix, orientation);
+ subscriber.onNext((float) (orientation[0] * 180 / Math.PI));
+ }
+
+ @Override
+ public void onAccuracyChanged(final Sensor sensor, final int accuracy) {
+ }
+
+ @Override
+ public void onStart() {
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: starting the rotation provider");
+ sensorManager.registerListener(this, rotationSensor, SensorManager.SENSOR_DELAY_NORMAL);
+ } else {
+ subscriber.onError(new RuntimeException("rotation sensor is absent on this device"));
+ }
+ }
+
+ @Override
+ public void onStop() {
+ if (rotationSensor != null) {
+ Log.d("RotationProvider: stopping the rotation provider");
+ sensorManager.unregisterListener(this);
+ }
+ }
+
+ public static Observable<Float> create(final Context context, final boolean lowPower) {
+ return Observable.create(new RotationProvider(context, lowPower));
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
index 84c343a..93480ee 100644
--- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
+++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java
@@ -69,7 +69,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference {
final String[] strings = StringUtils.split(Network.getResponseData(response), ',');
Settings.setWebNameCode(nam, strings[0]);
try {
- return Observable.from(Integer.parseInt(strings[1].trim()));
+ return Observable.just(Integer.parseInt(strings[1].trim()));
} catch (final Exception e) {
Log.e("RegisterSend2CgeoPreference", e);
}
diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java
index c4c1ae4..4dd959b 100644
--- a/main/src/cgeo/geocaching/settings/Settings.java
+++ b/main/src/cgeo/geocaching/settings/Settings.java
@@ -108,75 +108,89 @@ public class Settings {
}
private static void migrateSettings() {
- // migrate from non standard file location and integer based boolean types
- final int oldVersion = getInt(R.string.pref_settingsversion, 0);
- if (oldVersion < 1) {
- final String oldPreferencesName = "cgeo.pref";
- final SharedPreferences old = CgeoApplication.getInstance().getSharedPreferences(oldPreferencesName, Context.MODE_PRIVATE);
+ final int LATEST_PREFERENCES_VERSION = 2;
+ final int currentVersion = getInt(R.string.pref_settingsversion, 0);
+
+ // No need to migrate if we are up to date.
+ if (currentVersion == LATEST_PREFERENCES_VERSION) {
+ return;
+ }
+
+ // No need to migrate if we don't have older settings, defaults will be used instead.
+ final String preferencesNameV0 = "cgeo.pref";
+ final SharedPreferences prefsV0 = CgeoApplication.getInstance().getSharedPreferences(preferencesNameV0, Context.MODE_PRIVATE);
+ if (currentVersion == 0 && prefsV0.getAll().isEmpty()) {
+ final Editor e = sharedPrefs.edit();
+ e.putInt(getKey(R.string.pref_settingsversion), LATEST_PREFERENCES_VERSION);
+ e.commit();
+ return;
+ }
+
+ if (currentVersion < 1) {
+ // migrate from non standard file location and integer based boolean types
final Editor e = sharedPrefs.edit();
- e.putString(getKey(R.string.pref_temp_twitter_token_secret), old.getString(getKey(R.string.pref_temp_twitter_token_secret), null));
- e.putString(getKey(R.string.pref_temp_twitter_token_public), old.getString(getKey(R.string.pref_temp_twitter_token_public), null));
- e.putBoolean(getKey(R.string.pref_help_shown), old.getInt(getKey(R.string.pref_help_shown), 0) != 0);
- e.putFloat(getKey(R.string.pref_anylongitude), old.getFloat(getKey(R.string.pref_anylongitude), 0));
- e.putFloat(getKey(R.string.pref_anylatitude), old.getFloat(getKey(R.string.pref_anylatitude), 0));
- e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != old.getInt(getKey(R.string.pref_offlinemaps), 1));
- e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != old.getInt(getKey(R.string.pref_offlinewpmaps), 0));
- e.putString(getKey(R.string.pref_webDeviceCode), old.getString(getKey(R.string.pref_webDeviceCode), null));
- e.putString(getKey(R.string.pref_webDeviceName), old.getString(getKey(R.string.pref_webDeviceName), null));
- e.putBoolean(getKey(R.string.pref_maplive), old.getInt(getKey(R.string.pref_maplive), 1) != 0);
- e.putInt(getKey(R.string.pref_mapsource), old.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT));
- e.putBoolean(getKey(R.string.pref_twitter), 0 != old.getInt(getKey(R.string.pref_twitter), 0));
- e.putBoolean(getKey(R.string.pref_showaddress), 0 != old.getInt(getKey(R.string.pref_showaddress), 1));
- e.putBoolean(getKey(R.string.pref_showcaptcha), old.getBoolean(getKey(R.string.pref_showcaptcha), false));
- e.putBoolean(getKey(R.string.pref_maptrail), old.getInt(getKey(R.string.pref_maptrail), 1) != 0);
- e.putInt(getKey(R.string.pref_lastmapzoom), old.getInt(getKey(R.string.pref_lastmapzoom), 14));
- e.putBoolean(getKey(R.string.pref_livelist), 0 != old.getInt(getKey(R.string.pref_livelist), 1));
- e.putBoolean(getKey(R.string.pref_units), old.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric);
- e.putBoolean(getKey(R.string.pref_skin), old.getInt(getKey(R.string.pref_skin), 0) != 0);
- e.putInt(getKey(R.string.pref_lastusedlist), old.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID));
- e.putString(getKey(R.string.pref_cachetype), old.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id));
- e.putString(getKey(R.string.pref_twitter_token_secret), old.getString(getKey(R.string.pref_twitter_token_secret), null));
- e.putString(getKey(R.string.pref_twitter_token_public), old.getString(getKey(R.string.pref_twitter_token_public), null));
- e.putInt(getKey(R.string.pref_version), old.getInt(getKey(R.string.pref_version), 0));
- e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != old.getInt(getKey(R.string.pref_autoloaddesc), 1));
- e.putBoolean(getKey(R.string.pref_ratingwanted), old.getBoolean(getKey(R.string.pref_ratingwanted), true));
- e.putBoolean(getKey(R.string.pref_friendlogswanted), old.getBoolean(getKey(R.string.pref_friendlogswanted), true));
- e.putBoolean(getKey(R.string.pref_useenglish), old.getBoolean(getKey(R.string.pref_useenglish), false));
- e.putBoolean(getKey(R.string.pref_usecompass), 0 != old.getInt(getKey(R.string.pref_usecompass), 1));
- e.putBoolean(getKey(R.string.pref_trackautovisit), old.getBoolean(getKey(R.string.pref_trackautovisit), false));
- e.putBoolean(getKey(R.string.pref_sigautoinsert), old.getBoolean(getKey(R.string.pref_sigautoinsert), false));
- e.putBoolean(getKey(R.string.pref_logimages), old.getBoolean(getKey(R.string.pref_logimages), false));
- e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != old.getInt(getKey(R.string.pref_excludedisabled), 0));
- e.putBoolean(getKey(R.string.pref_excludemine), 0 != old.getInt(getKey(R.string.pref_excludemine), 0));
- e.putString(getKey(R.string.pref_mapfile), old.getString(getKey(R.string.pref_mapfile), null));
- e.putString(getKey(R.string.pref_signature), old.getString(getKey(R.string.pref_signature), null));
- e.putString(getKey(R.string.pref_pass_vote), old.getString(getKey(R.string.pref_pass_vote), null));
- e.putString(getKey(R.string.pref_password), old.getString(getKey(R.string.pref_password), null));
- e.putString(getKey(R.string.pref_username), old.getString(getKey(R.string.pref_username), null));
- e.putString(getKey(R.string.pref_memberstatus), old.getString(getKey(R.string.pref_memberstatus), ""));
- e.putInt(getKey(R.string.pref_coordinputformat), old.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE));
- e.putBoolean(getKey(R.string.pref_log_offline), old.getBoolean(getKey(R.string.pref_log_offline), false));
- e.putBoolean(getKey(R.string.pref_choose_list), old.getBoolean(getKey(R.string.pref_choose_list), true));
- e.putBoolean(getKey(R.string.pref_loaddirectionimg), old.getBoolean(getKey(R.string.pref_loaddirectionimg), true));
- e.putString(getKey(R.string.pref_gccustomdate), old.getString(getKey(R.string.pref_gccustomdate), null));
- e.putInt(getKey(R.string.pref_showwaypointsthreshold), old.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT));
- e.putString(getKey(R.string.pref_cookiestore), old.getString(getKey(R.string.pref_cookiestore), null));
- e.putBoolean(getKey(R.string.pref_opendetailslastpage), old.getBoolean(getKey(R.string.pref_opendetailslastpage), false));
- e.putInt(getKey(R.string.pref_lastdetailspage), old.getInt(getKey(R.string.pref_lastdetailspage), 1));
- e.putInt(getKey(R.string.pref_defaultNavigationTool), old.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id));
- e.putInt(getKey(R.string.pref_defaultNavigationTool2), old.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id));
- e.putInt(getKey(R.string.pref_livemapstrategy), old.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id));
- e.putBoolean(getKey(R.string.pref_debug), old.getBoolean(getKey(R.string.pref_debug), false));
- e.putBoolean(getKey(R.string.pref_hidelivemaphint), old.getInt(getKey(R.string.pref_hidelivemaphint), 0) != 0);
- e.putInt(getKey(R.string.pref_livemaphintshowcount), old.getInt(getKey(R.string.pref_livemaphintshowcount), 0));
+ e.putString(getKey(R.string.pref_temp_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_temp_twitter_token_public), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_public), null));
+ e.putBoolean(getKey(R.string.pref_help_shown), prefsV0.getInt(getKey(R.string.pref_help_shown), 0) != 0);
+ e.putFloat(getKey(R.string.pref_anylongitude), prefsV0.getFloat(getKey(R.string.pref_anylongitude), 0));
+ e.putFloat(getKey(R.string.pref_anylatitude), prefsV0.getFloat(getKey(R.string.pref_anylatitude), 0));
+ e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinemaps), 1));
+ e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinewpmaps), 0));
+ e.putString(getKey(R.string.pref_webDeviceCode), prefsV0.getString(getKey(R.string.pref_webDeviceCode), null));
+ e.putString(getKey(R.string.pref_webDeviceName), prefsV0.getString(getKey(R.string.pref_webDeviceName), null));
+ e.putBoolean(getKey(R.string.pref_maplive), prefsV0.getInt(getKey(R.string.pref_maplive), 1) != 0);
+ e.putInt(getKey(R.string.pref_mapsource), prefsV0.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT));
+ e.putBoolean(getKey(R.string.pref_twitter), 0 != prefsV0.getInt(getKey(R.string.pref_twitter), 0));
+ e.putBoolean(getKey(R.string.pref_showaddress), 0 != prefsV0.getInt(getKey(R.string.pref_showaddress), 1));
+ e.putBoolean(getKey(R.string.pref_showcaptcha), prefsV0.getBoolean(getKey(R.string.pref_showcaptcha), false));
+ e.putBoolean(getKey(R.string.pref_maptrail), prefsV0.getInt(getKey(R.string.pref_maptrail), 1) != 0);
+ e.putInt(getKey(R.string.pref_lastmapzoom), prefsV0.getInt(getKey(R.string.pref_lastmapzoom), 14));
+ e.putBoolean(getKey(R.string.pref_livelist), 0 != prefsV0.getInt(getKey(R.string.pref_livelist), 1));
+ e.putBoolean(getKey(R.string.pref_units), prefsV0.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric);
+ e.putBoolean(getKey(R.string.pref_skin), prefsV0.getInt(getKey(R.string.pref_skin), 0) != 0);
+ e.putInt(getKey(R.string.pref_lastusedlist), prefsV0.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID));
+ e.putString(getKey(R.string.pref_cachetype), prefsV0.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id));
+ e.putString(getKey(R.string.pref_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_twitter_token_secret), null));
+ e.putString(getKey(R.string.pref_twitter_token_public), prefsV0.getString(getKey(R.string.pref_twitter_token_public), null));
+ e.putInt(getKey(R.string.pref_version), prefsV0.getInt(getKey(R.string.pref_version), 0));
+ e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != prefsV0.getInt(getKey(R.string.pref_autoloaddesc), 1));
+ e.putBoolean(getKey(R.string.pref_ratingwanted), prefsV0.getBoolean(getKey(R.string.pref_ratingwanted), true));
+ e.putBoolean(getKey(R.string.pref_friendlogswanted), prefsV0.getBoolean(getKey(R.string.pref_friendlogswanted), true));
+ e.putBoolean(getKey(R.string.pref_useenglish), prefsV0.getBoolean(getKey(R.string.pref_useenglish), false));
+ e.putBoolean(getKey(R.string.pref_usecompass), 0 != prefsV0.getInt(getKey(R.string.pref_usecompass), 1));
+ e.putBoolean(getKey(R.string.pref_trackautovisit), prefsV0.getBoolean(getKey(R.string.pref_trackautovisit), false));
+ e.putBoolean(getKey(R.string.pref_sigautoinsert), prefsV0.getBoolean(getKey(R.string.pref_sigautoinsert), false));
+ e.putBoolean(getKey(R.string.pref_logimages), prefsV0.getBoolean(getKey(R.string.pref_logimages), false));
+ e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != prefsV0.getInt(getKey(R.string.pref_excludedisabled), 0));
+ e.putBoolean(getKey(R.string.pref_excludemine), 0 != prefsV0.getInt(getKey(R.string.pref_excludemine), 0));
+ e.putString(getKey(R.string.pref_mapfile), prefsV0.getString(getKey(R.string.pref_mapfile), null));
+ e.putString(getKey(R.string.pref_signature), prefsV0.getString(getKey(R.string.pref_signature), null));
+ e.putString(getKey(R.string.pref_pass_vote), prefsV0.getString(getKey(R.string.pref_pass_vote), null));
+ e.putString(getKey(R.string.pref_password), prefsV0.getString(getKey(R.string.pref_password), null));
+ e.putString(getKey(R.string.pref_username), prefsV0.getString(getKey(R.string.pref_username), null));
+ e.putString(getKey(R.string.pref_memberstatus), prefsV0.getString(getKey(R.string.pref_memberstatus), ""));
+ e.putInt(getKey(R.string.pref_coordinputformat), prefsV0.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE));
+ e.putBoolean(getKey(R.string.pref_log_offline), prefsV0.getBoolean(getKey(R.string.pref_log_offline), false));
+ e.putBoolean(getKey(R.string.pref_choose_list), prefsV0.getBoolean(getKey(R.string.pref_choose_list), true));
+ e.putBoolean(getKey(R.string.pref_loaddirectionimg), prefsV0.getBoolean(getKey(R.string.pref_loaddirectionimg), true));
+ e.putString(getKey(R.string.pref_gccustomdate), prefsV0.getString(getKey(R.string.pref_gccustomdate), null));
+ e.putInt(getKey(R.string.pref_showwaypointsthreshold), prefsV0.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT));
+ e.putString(getKey(R.string.pref_cookiestore), prefsV0.getString(getKey(R.string.pref_cookiestore), null));
+ e.putBoolean(getKey(R.string.pref_opendetailslastpage), prefsV0.getBoolean(getKey(R.string.pref_opendetailslastpage), false));
+ e.putInt(getKey(R.string.pref_lastdetailspage), prefsV0.getInt(getKey(R.string.pref_lastdetailspage), 1));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id));
+ e.putInt(getKey(R.string.pref_defaultNavigationTool2), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id));
+ e.putInt(getKey(R.string.pref_livemapstrategy), prefsV0.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id));
+ e.putBoolean(getKey(R.string.pref_debug), prefsV0.getBoolean(getKey(R.string.pref_debug), false));
+ e.putInt(getKey(R.string.pref_livemaphintshowcount), prefsV0.getInt(getKey(R.string.pref_livemaphintshowcount), 0));
e.putInt(getKey(R.string.pref_settingsversion), 1); // mark migrated
e.commit();
}
// changes for new settings dialog
- if (oldVersion < 2) {
+ if (currentVersion < 2) {
final Editor e = sharedPrefs.edit();
e.putBoolean(getKey(R.string.pref_units), !isUseImperialUnits());
@@ -408,6 +422,14 @@ public class Settings {
return getString(R.string.pref_cookiestore, null);
}
+ public static boolean useGooglePlayServices() {
+ return CgeoApplication.getInstance().isGooglePlayServicesAvailable() && getBoolean(R.string.pref_googleplayservices, true);
+ }
+
+ public static boolean useLowPowerMode() {
+ return getBoolean(R.string.pref_lowpowermode, false);
+ }
+
/**
* @param cacheType
* The cache type used for future filtering
@@ -626,7 +648,7 @@ public class Settings {
mapSource = MapProviderFactory.getMapSource(id);
if (mapSource != null) {
// don't use offline maps if the map file is not valid
- if ((!(mapSource instanceof OfflineMapSource)) || (isValidMapFile())) {
+ if (!(mapSource instanceof OfflineMapSource) || isValidMapFile()) {
return mapSource;
}
}
@@ -849,14 +871,6 @@ public class Settings {
return Log.isDebug();
}
- public static boolean getHideLiveMapHint() {
- return getBoolean(R.string.pref_hidelivemaphint, false);
- }
-
- public static void setHideLiveHint(final boolean hide) {
- putBoolean(R.string.pref_hidelivemaphint, hide);
- }
-
public static int getLiveMapHintShowCount() {
return getInt(R.string.pref_livemaphintshowcount, 0);
}
diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java
index df6e680..b5d7b68 100644
--- a/main/src/cgeo/geocaching/settings/SettingsActivity.java
+++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java
@@ -1,7 +1,5 @@
package cgeo.geocaching.settings;
-import butterknife.ButterKnife;
-
import cgeo.geocaching.CgeoApplication;
import cgeo.geocaching.DataStore;
import cgeo.geocaching.Intents;
@@ -126,6 +124,7 @@ public class SettingsActivity extends PreferenceActivity {
initDefaultNavigationPreferences();
initBackupButtons();
initDbLocationPreference();
+ initGeoDirPreferences();
initDebugPreference();
initBasicMemberPreferences();
initSend2CgeoPreferences();
@@ -373,12 +372,13 @@ public class SettingsActivity extends PreferenceActivity {
final Preference memoryDumpPref = getPreference(R.string.pref_memory_dump);
memoryDumpPref
.setOnPreferenceClickListener(new OnPreferenceClickListener() {
- @Override public boolean onPreferenceClick(
- final Preference preference) {
- DebugUtils.createMemoryDump(SettingsActivity.this);
- return true;
- }
- });
+ @Override
+ public boolean onPreferenceClick(
+ final Preference preference) {
+ DebugUtils.createMemoryDump(SettingsActivity.this);
+ return true;
+ }
+ });
}
public static void initHardwareAccelerationPreferences() {
@@ -413,6 +413,28 @@ public class SettingsActivity extends PreferenceActivity {
});
}
+ private void initGeoDirPreferences() {
+ final Preference playServices = getPreference(R.string.pref_googleplayservices);
+ playServices.setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+ CgeoApplication.getInstance().setupGeoDataObservables((Boolean) newValue, Settings.useLowPowerMode());
+ return true;
+ }
+ });
+ playServices.setEnabled(CgeoApplication.getInstance().isGooglePlayServicesAvailable());
+ getPreference(R.string.pref_lowpowermode).setOnPreferenceChangeListener(new OnPreferenceChangeListener() {
+ @Override
+ public boolean onPreferenceChange(final Preference preference, final Object newValue) {
+ final CgeoApplication app = CgeoApplication.getInstance();
+ final Boolean useLowPower = (Boolean) newValue;
+ app.setupGeoDataObservables(Settings.useGooglePlayServices(), useLowPower);
+ app.setupDirectionObservable(useLowPower);
+ return true;
+ }
+ });
+ }
+
void initBasicMemberPreferences() {
getPreference(R.string.preference_screen_basicmembers)
.setEnabled(!Settings.isGCPremiumMember());
diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
index b3b751b..3da5736 100644
--- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java
+++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java
@@ -28,7 +28,8 @@ public class DistanceComparator extends AbstractCacheComparator {
public DistanceComparator(final Geopoint coords, final List<Geocache> list) {
this.coords = coords;
- this.list = list;
+ // create new list so we can iterate over the list in parallel with the cache list adapter
+ this.list = new ArrayList<>(list);
}
/**
diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
index 57a69ee..a2da6ee 100644
--- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
+++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java
@@ -26,7 +26,8 @@ public class PopularityRatioComparator extends AbstractCacheComparator {
if ((ratio2 - ratio1) > 0.0f) {
return 1;
- } else if ((ratio2 - ratio1) < 0.0f) {
+ }
+ if ((ratio2 - ratio1) < 0.0f) {
return -1;
}
diff --git a/main/src/cgeo/geocaching/sorting/SortActionProvider.java b/main/src/cgeo/geocaching/sorting/SortActionProvider.java
index 6251984..e6db330 100644
--- a/main/src/cgeo/geocaching/sorting/SortActionProvider.java
+++ b/main/src/cgeo/geocaching/sorting/SortActionProvider.java
@@ -136,9 +136,7 @@ public class SortActionProvider extends ActionProvider implements OnMenuItemClic
final CacheComparator comparator = cacheComparator.newInstance();
onClickListener.call(comparator);
}
- } catch (final InstantiationException e) {
- Log.e("selectComparator", e);
- } catch (final IllegalAccessException e) {
+ } catch (final InstantiationException | IllegalAccessException e) {
Log.e("selectComparator", e);
}
}
diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java
index fbd2d7e..11e10c1 100644
--- a/main/src/cgeo/geocaching/speech/SpeechService.java
+++ b/main/src/cgeo/geocaching/speech/SpeechService.java
@@ -1,5 +1,6 @@
package cgeo.geocaching.speech;
+import cgeo.geocaching.Intents;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.ActivityMixin;
import cgeo.geocaching.geopoint.Geopoint;
@@ -31,7 +32,6 @@ public class SpeechService extends Service implements OnInitListener {
private static final int SPEECH_MINPAUSE_SECONDS = 5;
private static final int SPEECH_MAXPAUSE_SECONDS = 30;
- private static final String EXTRA_TARGET_COORDS = "target";
private static Activity startingActivity;
private static boolean isRunning = false;
/**
@@ -152,7 +152,7 @@ public class SpeechService extends Service implements OnInitListener {
@Override
public int onStartCommand(final Intent intent, final int flags, final int startId) {
if (intent != null) {
- target = intent.getParcelableExtra(EXTRA_TARGET_COORDS);
+ target = intent.getParcelableExtra(Intents.EXTRA_COORDS);
}
return START_NOT_STICKY; // service can be stopped by system, if under memory pressure
}
@@ -168,7 +168,7 @@ public class SpeechService extends Service implements OnInitListener {
isRunning = true;
startingActivity = activity;
final Intent talkingService = new Intent(activity, SpeechService.class);
- talkingService.putExtra(EXTRA_TARGET_COORDS, dstCoords);
+ talkingService.putExtra(Intents.EXTRA_COORDS, dstCoords);
activity.startService(talkingService);
}
diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java
index c89c0b6..253d91f 100644
--- a/main/src/cgeo/geocaching/twitter/Twitter.java
+++ b/main/src/cgeo/geocaching/twitter/Twitter.java
@@ -10,6 +10,7 @@ import cgeo.geocaching.geopoint.Geopoint;
import cgeo.geocaching.geopoint.GeopointFormatter.Format;
import cgeo.geocaching.network.Network;
import cgeo.geocaching.network.OAuth;
+import cgeo.geocaching.network.OAuthTokens;
import cgeo.geocaching.network.Parameters;
import cgeo.geocaching.settings.Settings;
import cgeo.geocaching.utils.Log;
@@ -17,6 +18,7 @@ import cgeo.geocaching.utils.LogTemplateProvider;
import cgeo.geocaching.utils.LogTemplateProvider.LogContext;
import ch.boye.httpclientandroidlib.HttpResponse;
+
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
import org.eclipse.jdt.annotation.Nullable;
@@ -25,7 +27,7 @@ public final class Twitter {
private static final String HASH_PREFIX_WITH_BLANK = " #";
private static final int MAX_TWEET_SIZE = 140;
- public static void postTweetCache(String geocode, final @Nullable LogEntry logEntry) {
+ public static void postTweetCache(final String geocode, final @Nullable LogEntry logEntry) {
final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB);
if (cache == null) {
return;
@@ -33,7 +35,7 @@ public final class Twitter {
postTweet(CgeoApplication.getInstance(), getStatusMessage(cache, logEntry), null);
}
- public static void postTweetTrackable(String geocode, final @Nullable LogEntry logEntry) {
+ public static void postTweetTrackable(final String geocode, final @Nullable LogEntry logEntry) {
final Trackable trackable = DataStore.loadTrackable(geocode);
if (trackable == null) {
return;
@@ -48,7 +50,7 @@ public final class Twitter {
try {
final String status = shortenToMaxSize(statusIn);
- Parameters parameters = new Parameters("status", status);
+ final Parameters parameters = new Parameters("status", status);
if (coords != null) {
parameters.put(
"lat", coords.format(Format.LAT_DECDEGREE_RAW),
@@ -56,7 +58,7 @@ public final class Twitter {
"display_coordinates", "true");
}
- OAuth.signOAuth("api.twitter.com", "/1.1/statuses/update.json", "POST", true, parameters, Settings.getTokenPublic(), Settings.getTokenSecret(), Settings.getKeyConsumerPublic(), Settings.getKeyConsumerSecret());
+ OAuth.signOAuth("api.twitter.com", "/1.1/statuses/update.json", "POST", true, parameters, new OAuthTokens(Settings.getTokenPublic(), Settings.getTokenSecret()), Settings.getKeyConsumerPublic(), Settings.getKeyConsumerSecret());
final HttpResponse httpResponse = Network.postRequest("https://api.twitter.com/1.1/statuses/update.json", parameters);
if (httpResponse != null) {
if (httpResponse.getStatusLine().getStatusCode() == 200) {
@@ -67,13 +69,13 @@ public final class Twitter {
} else {
Log.e("Tweet could not be posted. Reason: httpResponse Object is null");
}
- } catch (Exception e) {
+ } catch (final Exception e) {
Log.e("Twitter.postTweet", e);
}
}
private static String shortenToMaxSize(final String status) {
- String result = StringUtils.trim(status);
+ final String result = StringUtils.trim(status);
if (StringUtils.length(result) > MAX_TWEET_SIZE) {
return StringUtils.substring(result, 0, MAX_TWEET_SIZE - 1) + '…';
}
@@ -98,7 +100,7 @@ public final class Twitter {
}
private static String appendHashTags(final String status) {
- StringBuilder builder = new StringBuilder(status);
+ final StringBuilder builder = new StringBuilder(status);
appendHashTag(builder, "cgeo");
appendHashTag(builder, "geocaching");
return builder.toString();
diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
index 40cd726..d55d9c9 100644
--- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
+++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java
@@ -102,11 +102,14 @@ public final class CacheDetailsCreator {
public void addCacheState(final Geocache cache) {
if (cache.isLogOffline() || cache.isArchived() || cache.isDisabled() || cache.isPremiumMembersOnly() || cache.isFound()) {
final List<String> states = new ArrayList<>(5);
+ String date = getVisitedDate(cache);
if (cache.isLogOffline()) {
- states.add(res.getString(R.string.cache_status_offline_log));
+ states.add(res.getString(R.string.cache_status_offline_log) + date);
+ // reset the found date, to avoid showing it twice
+ date = "";
}
if (cache.isFound()) {
- states.add(res.getString(R.string.cache_status_found));
+ states.add(res.getString(R.string.cache_status_found) + date);
}
if (cache.isArchived()) {
states.add(res.getString(R.string.cache_status_archived));
@@ -121,12 +124,17 @@ public final class CacheDetailsCreator {
}
}
+ private static String getVisitedDate(final Geocache cache) {
+ final long visited = cache.getVisitedDate();
+ return visited != 0 ? " (" + Formatter.formatShortDate(visited) + ")" : "";
+ }
+
public void addRating(final Geocache cache) {
if (cache.getRating() > 0) {
final RelativeLayout itemLayout = addStars(R.string.cache_rating, cache.getRating());
if (cache.getVotes() > 0) {
final TextView itemAddition = ButterKnife.findById(itemLayout, R.id.addition);
- itemAddition.setText("(" + cache.getVotes() + ")");
+ itemAddition.setText(" (" + cache.getVotes() + ')');
itemAddition.setVisibility(View.VISIBLE);
}
}
@@ -170,7 +178,7 @@ public final class CacheDetailsCreator {
}
public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) {
- Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt);
+ final Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt);
String text = "--";
if (distance != null) {
text = Units.getDistanceFromKilometers(distance);
diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java
index eaede2a..8cb2177 100644
--- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java
+++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java
@@ -120,9 +120,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> {
public CacheListAdapter(final Activity activity, final List<Geocache> list, final CacheListType cacheListType) {
super(activity, 0, list);
final IGeoData currentGeo = CgeoApplication.getInstance().currentGeo();
- if (currentGeo != null) {
- coords = currentGeo.getCoords();
- }
+ coords = currentGeo.getCoords();
this.res = activity.getResources();
this.list = list;
this.cacheListType = cacheListType;
diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java
index 240afcf..a227770 100644
--- a/main/src/cgeo/geocaching/ui/CompassView.java
+++ b/main/src/cgeo/geocaching/ui/CompassView.java
@@ -81,8 +81,9 @@ public class CompassView extends View {
}
public void updateGraphics() {
- final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown);
- final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown);
+ final float newAzimuthShown = initialDisplay ? northMeasured : smoothUpdate(northMeasured, azimuthShown);
+ final float newCacheHeadingShown = initialDisplay ? cacheHeadingMeasured : smoothUpdate(cacheHeadingMeasured, cacheHeadingShown);
+ initialDisplay = false;
if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 ||
Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) {
azimuthShown = newAzimuthShown;
@@ -151,17 +152,6 @@ public class CompassView extends View {
* @param cacheHeading the cache direction (extra rotation of the needle)
*/
public void updateNorth(final float northHeading, final float cacheHeading) {
- if (initialDisplay) {
- // We will force the compass to move brutally if this is the first
- // update since it is visible.
- azimuthShown = northHeading;
- cacheHeadingShown = cacheHeading;
-
- // it may take some time to get an initial direction measurement for the device
- if (northHeading != 0.0) {
- initialDisplay = false;
- }
- }
northMeasured = northHeading;
cacheHeadingMeasured = cacheHeading;
}
diff --git a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
index e2e587e..3af950f 100644
--- a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
+++ b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java
@@ -1,40 +1,37 @@
-package cgeo.geocaching.ui;
-
-import cgeo.geocaching.utils.CryptUtils;
-
-import org.eclipse.jdt.annotation.NonNull;
-
-import android.text.Spannable;
-import android.view.View;
-import android.widget.TextView;
-
-public class DecryptTextClickListener implements View.OnClickListener {
-
- @NonNull private final TextView targetView;
-
- public DecryptTextClickListener(@NonNull final TextView targetView) {
- this.targetView = targetView;
- }
-
- @Override
- public final void onClick(final View view) {
- try {
- // do not run the click listener if a link was clicked
- if (targetView.getSelectionStart() != -1 || targetView.getSelectionEnd() != -1) {
- return;
- }
-
- CharSequence text = targetView.getText();
- if (text instanceof Spannable) {
- Spannable span = (Spannable) text;
- targetView.setText(CryptUtils.rot13(span));
- }
- else {
- String string = (String) text;
- targetView.setText(CryptUtils.rot13(string));
- }
- } catch (RuntimeException e) {
- // nothing
- }
- }
-}
+package cgeo.geocaching.ui;
+
+import cgeo.geocaching.utils.CryptUtils;
+
+import org.eclipse.jdt.annotation.NonNull;
+
+import android.text.Spannable;
+import android.view.View;
+import android.widget.TextView;
+
+public class DecryptTextClickListener implements View.OnClickListener {
+
+ @NonNull private final TextView targetView;
+
+ public DecryptTextClickListener(@NonNull final TextView targetView) {
+ this.targetView = targetView;
+ }
+
+ @Override
+ public final void onClick(final View view) {
+ try {
+ // do not run the click listener if a link was clicked
+ if (targetView.getSelectionStart() != -1 || targetView.getSelectionEnd() != -1) {
+ return;
+ }
+
+ CharSequence text = targetView.getText();
+ if (text instanceof Spannable) {
+ targetView.setText(CryptUtils.rot13((Spannable) text));
+ } else {
+ targetView.setText(CryptUtils.rot13((String) text));
+ }
+ } catch (final RuntimeException ignore) {
+ // nothing
+ }
+ }
+}
diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java
index 8bd4ac2..458f8db 100644
--- a/main/src/cgeo/geocaching/ui/ImagesList.java
+++ b/main/src/cgeo/geocaching/ui/ImagesList.java
@@ -102,7 +102,7 @@ public class ImagesList {
imagesView = ButterKnife.findById(parentView, R.id.spoiler_list);
- final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false);
+ final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST.id, false);
for (final Image img : images) {
final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, imagesView, false);
diff --git a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java
index 1046f81..15c9556 100644
--- a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java
+++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java
@@ -22,11 +22,11 @@ public class DateDialog extends DialogFragment {
private Calendar date;
public static DateDialog getInstance(final Calendar date) {
- final DateDialog dd = new DateDialog();
+ final DateDialog dateDialog = new DateDialog();
final Bundle args = new Bundle();
args.putSerializable("date", date);
- dd.setArguments(args);
- return dd;
+ dateDialog.setArguments(args);
+ return dateDialog;
}
@Override
diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
index 21e1a82..47ce6e1 100644
--- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
+++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java
@@ -25,8 +25,15 @@ import android.text.Editable;
import android.text.InputType;
import android.text.TextWatcher;
import android.view.ContextThemeWrapper;
+import android.view.View;
+import android.view.ViewGroup;
import android.view.WindowManager;
+import android.widget.ArrayAdapter;
import android.widget.EditText;
+import android.widget.ListAdapter;
+import android.widget.TextView;
+
+import java.util.List;
/**
* Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the
@@ -410,4 +417,44 @@ public final class Dialogs {
private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) {
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(StringUtils.isNotBlank(input));
}
+
+ public static interface ItemWithIcon {
+ /**
+ * @return the drawable
+ */
+ int getIcon();
+ }
+
+ public static <T extends ItemWithIcon> void select(final Activity activity, final String title, final List<T> items, final Action1<T> listener) {
+ final ListAdapter adapter = new ArrayAdapter<T>(
+ activity,
+ android.R.layout.select_dialog_item,
+ android.R.id.text1,
+ items) {
+ @Override
+ public View getView(final int position, final View convertView, final ViewGroup parent) {
+ // standard list entry
+ final View v = super.getView(position, convertView, parent);
+
+ // add image
+ final TextView tv = (TextView) v.findViewById(android.R.id.text1);
+ tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).getIcon(), 0, 0, 0);
+
+ // Add margin between image and text
+ final int dp5 = (int) (5 * activity.getResources().getDisplayMetrics().density + 0.5f);
+ tv.setCompoundDrawablePadding(dp5);
+
+ return v;
+ }
+ };
+
+ new AlertDialog.Builder(activity)
+ .setTitle(title)
+ .setAdapter(adapter, new DialogInterface.OnClickListener() {
+ @Override
+ public void onClick(final DialogInterface dialog, final int item) {
+ listener.call(items.get(item));
+ }
+ }).show();
+ }
}
diff --git a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java
index 23caf79..3aaeec1 100644
--- a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java
+++ b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java
@@ -4,8 +4,7 @@ import cgeo.geocaching.ImagesActivity;
import cgeo.geocaching.LogEntry;
import cgeo.geocaching.R;
import cgeo.geocaching.activity.AbstractActionBarActivity;
-import cgeo.geocaching.list.StoredList;
-import cgeo.geocaching.network.HtmlImage;
+import cgeo.geocaching.network.SmileyImage;
import cgeo.geocaching.ui.AbstractCachingListViewPageViewCreator;
import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod;
import cgeo.geocaching.ui.DecryptTextClickListener;
@@ -87,7 +86,7 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre
if (TextUtils.containsHtml(logText)) {
logText = log.getDisplayText();
final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler();
- holder.text.setText(Html.fromHtml(logText, new HtmlImage(getGeocode(), false, StoredList.STANDARD_LIST_ID, false, holder.text),
+ holder.text.setText(Html.fromHtml(logText, new SmileyImage(getGeocode(), holder.text),
unknownTagsHandler), TextView.BufferType.SPANNABLE);
} else {
holder.text.setText(logText, TextView.BufferType.SPANNABLE);
diff --git a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
index 300f510..24c8871 100644
--- a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
+++ b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java
@@ -15,14 +15,16 @@ import java.util.List;
public class TrackableLogsViewCreator extends LogsViewCreator {
- private final Trackable trackable;
+ private Trackable trackable;
+ private final TrackableActivity trackableActivity;
/**
* @param trackableActivity
*/
- public TrackableLogsViewCreator(TrackableActivity trackableActivity, final Trackable trackable) {
+ public TrackableLogsViewCreator(final TrackableActivity trackableActivity) {
super(trackableActivity);
- this.trackable = trackable;
+ this.trackableActivity = trackableActivity;
+ trackable = trackableActivity.getTrackable();
}
@Override
@@ -32,6 +34,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator {
@Override
protected List<LogEntry> getLogs() {
+ trackable = trackableActivity.getTrackable();
return trackable.getLogs();
}
@@ -41,7 +44,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator {
}
@Override
- protected void fillCountOrLocation(LogViewHolder holder, final LogEntry log) {
+ protected void fillCountOrLocation(final LogViewHolder holder, final LogEntry log) {
if (StringUtils.isBlank(log.cacheName)) {
holder.countOrLocation.setVisibility(View.GONE);
} else {
@@ -50,7 +53,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator {
final String cacheName = log.cacheName;
holder.countOrLocation.setOnClickListener(new View.OnClickListener() {
@Override
- public void onClick(View arg0) {
+ public void onClick(final View arg0) {
CacheDetailActivity.startActivityGuid(activity, cacheGuid, Html.fromHtml(cacheName).toString());
}
});
diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java
index fdd9a9d..5ab2c75 100644
--- a/main/src/cgeo/geocaching/utils/AngleUtils.java
+++ b/main/src/cgeo/geocaching/utils/AngleUtils.java
@@ -1,7 +1,17 @@
package cgeo.geocaching.utils;
+import cgeo.geocaching.CgeoApplication;
+
+import android.content.Context;
+import android.view.Surface;
+import android.view.WindowManager;
+
public final class AngleUtils {
+ private static class WindowManagerHolder {
+ public static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE);
+ }
+
private AngleUtils() {
// Do not instantiate
}
@@ -27,4 +37,37 @@ public final class AngleUtils {
public static float normalize(final float angle) {
return (angle >= 0 ? angle : (360 - ((-angle) % 360))) % 360;
}
+
+ public static int getRotationOffset() {
+ switch (WindowManagerHolder.WINDOW_MANAGER.getDefaultDisplay().getRotation()) {
+ case Surface.ROTATION_90:
+ return 90;
+ case Surface.ROTATION_180:
+ return 180;
+ case Surface.ROTATION_270:
+ return 270;
+ default:
+ return 0;
+ }
+ }
+
+ /**
+ * Take the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @param direction the unadjusted direction in degrees, in the [0, 360[ range
+ * @return the adjusted direction in degrees, in the [0, 360[ range
+ */
+ public static float getDirectionNow(final float direction) {
+ return normalize(direction + getRotationOffset());
+ }
+
+ /**
+ * Reverse the phone rotation (through a given activity) in account and adjust the direction.
+ *
+ * @param direction the unadjusted direction in degrees, in the [0, 360[ range
+ * @return the adjusted direction in degrees, in the [0, 360[ range
+ */
+ public static float reverseDirectionNow(final float direction) {
+ return normalize(direction - getRotationOffset());
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/ClipboardUtils.java b/main/src/cgeo/geocaching/utils/ClipboardUtils.java
index 77250f3..d91c644 100644
--- a/main/src/cgeo/geocaching/utils/ClipboardUtils.java
+++ b/main/src/cgeo/geocaching/utils/ClipboardUtils.java
@@ -9,7 +9,6 @@ import android.content.Context;
* This class uses the deprecated function ClipboardManager.setText(CharSequence).
* API 11 introduced setPrimaryClip(ClipData)
*/
-@SuppressWarnings("deprecation")
public final class ClipboardUtils {
private ClipboardUtils() {
@@ -22,6 +21,7 @@ public final class ClipboardUtils {
* @param text
* The text to place in the clipboard.
*/
+ @SuppressWarnings("deprecation")
public static void copyToClipboard(final CharSequence text) {
// fully qualified name used here to avoid buggy deprecation warning (of javac) on the import statement
final android.text.ClipboardManager clipboard = (android.text.ClipboardManager) CgeoApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE);
diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java
index 815c2f4..f2ff0c2 100644
--- a/main/src/cgeo/geocaching/utils/CryptUtils.java
+++ b/main/src/cgeo/geocaching/utils/CryptUtils.java
@@ -1,6 +1,5 @@
package cgeo.geocaching.utils;
-
import org.apache.commons.lang3.CharEncoding;
import org.apache.commons.lang3.StringUtils;
import org.eclipse.jdt.annotation.NonNull;
@@ -23,28 +22,29 @@ public final class CryptUtils {
// utility class
}
- private static char[] base64map1 = new char[64];
- private static byte[] base64map2 = new byte[128];
+ private static final byte[] EMPTY = {};
+ private static char[] BASE64MAP1 = new char[64];
+ private static byte[] BASE64MAP2 = new byte[128];
static {
int i = 0;
for (char c = 'A'; c <= 'Z'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
for (char c = 'a'; c <= 'z'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
for (char c = '0'; c <= '9'; c++) {
- base64map1[i++] = c;
+ BASE64MAP1[i++] = c;
}
- base64map1[i++] = '+';
- base64map1[i++] = '/';
+ BASE64MAP1[i++] = '+';
+ BASE64MAP1[i++] = '/';
- for (i = 0; i < base64map2.length; i++) {
- base64map2[i] = -1;
+ for (i = 0; i < BASE64MAP2.length; i++) {
+ BASE64MAP2[i] = -1;
}
for (i = 0; i < 64; i++) {
- base64map2[base64map1[i]] = (byte) i;
+ BASE64MAP2[BASE64MAP1[i]] = (byte) i;
}
}
@@ -88,9 +88,7 @@ public final class CryptUtils {
final MessageDigest digest = MessageDigest.getInstance("MD5");
digest.update(text.getBytes(CharEncoding.UTF_8), 0, text.length());
return new BigInteger(1, digest.digest()).toString(16);
- } catch (NoSuchAlgorithmException e) {
- Log.e("CryptUtils.md5", e);
- } catch (UnsupportedEncodingException e) {
+ } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) {
Log.e("CryptUtils.md5", e);
}
@@ -98,20 +96,16 @@ public final class CryptUtils {
}
public static byte[] hashHmac(String text, String salt) {
- byte[] macBytes = {};
try {
final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(CharEncoding.UTF_8), "HmacSHA1");
final Mac mac = Mac.getInstance("HmacSHA1");
mac.init(secretKeySpec);
- macBytes = mac.doFinal(text.getBytes(CharEncoding.UTF_8));
- } catch (GeneralSecurityException e) {
- Log.e("CryptUtils.hashHmac", e);
- } catch (UnsupportedEncodingException e) {
+ return mac.doFinal(text.getBytes(CharEncoding.UTF_8));
+ } catch (GeneralSecurityException | UnsupportedEncodingException e) {
Log.e("CryptUtils.hashHmac", e);
+ return EMPTY;
}
-
- return macBytes;
}
public static CharSequence rot13(final Spannable span) {
@@ -145,11 +139,11 @@ public final class CryptUtils {
int o1 = ((i0 & 3) << 4) | (i1 >>> 4);
int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6);
int o3 = i2 & 0x3F;
- out[op++] = base64map1[o0];
- out[op++] = base64map1[o1];
- out[op] = op < oDataLen ? base64map1[o2] : '=';
+ out[op++] = BASE64MAP1[o0];
+ out[op++] = BASE64MAP1[o1];
+ out[op] = op < oDataLen ? BASE64MAP1[o2] : '=';
op++;
- out[op] = op < oDataLen ? base64map1[o3] : '=';
+ out[op] = op < oDataLen ? BASE64MAP1[o3] : '=';
op++;
}
diff --git a/main/src/cgeo/geocaching/utils/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java
index 3068cd4..1b774f8 100644
--- a/main/src/cgeo/geocaching/utils/Formatter.java
+++ b/main/src/cgeo/geocaching/utils/Formatter.java
@@ -33,7 +33,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatTime(long date) {
+ public static String formatTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME);
}
@@ -45,7 +45,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDate(long date) {
+ public static String formatDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE);
}
@@ -58,7 +58,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatFullDate(long date) {
+ public static String formatFullDate(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE
| DateUtils.FORMAT_SHOW_YEAR);
}
@@ -71,8 +71,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDate(long date) {
- DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
+ public static String formatShortDate(final long date) {
+ final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context);
return dateFormat.format(date);
}
@@ -84,8 +84,8 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateVerbally(long date) {
- int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ public static String formatShortDateVerbally(final long date) {
+ final int diff = cgeo.geocaching.utils.DateUtils.daysSince(date);
switch (diff) {
case 0:
return CgeoApplication.getInstance().getString(R.string.log_today);
@@ -104,7 +104,7 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatShortDateTime(long date) {
+ public static String formatShortDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL);
}
@@ -116,11 +116,11 @@ public abstract class Formatter {
* milliseconds since the epoch
* @return the formatted string
*/
- public static String formatDateTime(long date) {
+ public static String formatDateTime(final long date) {
return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME);
}
- public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) {
+ public static String formatCacheInfoLong(final Geocache cache, final CacheListType cacheListType) {
final ArrayList<String> infos = new ArrayList<>();
if (StringUtils.isNotBlank(cache.getGeocode())) {
infos.add(cache.getGeocode());
@@ -137,13 +137,13 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatCacheInfoShort(Geocache cache) {
+ public static String formatCacheInfoShort(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>();
addShortInfos(cache, infos);
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- private static void addShortInfos(Geocache cache, final ArrayList<String> infos) {
+ private static void addShortInfos(final Geocache cache, final ArrayList<String> infos) {
if (cache.hasDifficulty()) {
infos.add("D " + String.format("%.1f", cache.getDifficulty()));
}
@@ -162,7 +162,7 @@ public abstract class Formatter {
}
}
- public static String formatCacheInfoHistory(Geocache cache) {
+ public static String formatCacheInfoHistory(final Geocache cache) {
final ArrayList<String> infos = new ArrayList<>(3);
infos.add(StringUtils.upperCase(cache.getGeocode()));
infos.add(Formatter.formatDate(cache.getVisitedDate()));
@@ -170,9 +170,9 @@ public abstract class Formatter {
return StringUtils.join(infos, Formatter.SEPARATOR);
}
- public static String formatWaypointInfo(Waypoint waypoint) {
+ public static String formatWaypointInfo(final Waypoint waypoint) {
final List<String> infos = new ArrayList<>(3);
- WaypointType waypointType = waypoint.getWaypointType();
+ final WaypointType waypointType = waypoint.getWaypointType();
if (waypointType != WaypointType.OWN && waypointType != null) {
infos.add(waypointType.getL10n());
}
@@ -188,4 +188,16 @@ public abstract class Formatter {
}
return StringUtils.join(infos, Formatter.SEPARATOR);
}
+
+ public static String formatDaysAgo(final long date) {
+ final int days = cgeo.geocaching.utils.DateUtils.daysSince(date);
+ switch (days) {
+ case 0:
+ return CgeoApplication.getInstance().getString(R.string.log_today);
+ case 1:
+ return CgeoApplication.getInstance().getString(R.string.log_yesterday);
+ default:
+ return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days);
+ }
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java
index 51c4d6e..e90b70d 100644
--- a/main/src/cgeo/geocaching/utils/HtmlUtils.java
+++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java
@@ -24,7 +24,7 @@ public final class HtmlUtils {
* @param html
* @return
*/
- public static String extractText(CharSequence html) {
+ public static String extractText(final CharSequence html) {
if (StringUtils.isBlank(html)) {
return StringUtils.EMPTY;
}
@@ -32,13 +32,13 @@ public final class HtmlUtils {
// recognize images in textview HTML contents
if (html instanceof Spanned) {
- Spanned text = (Spanned) html;
- Object[] styles = text.getSpans(0, text.length(), Object.class);
- ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
- for (Object style : styles) {
+ final Spanned text = (Spanned) html;
+ final Object[] styles = text.getSpans(0, text.length(), Object.class);
+ final ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>();
+ for (final Object style : styles) {
if (style instanceof ImageSpan) {
- int start = text.getSpanStart(style);
- int end = text.getSpanEnd(style);
+ final int start = text.getSpanStart(style);
+ final int end = text.getSpanEnd(style);
removals.add(Pair.of(start, end));
}
}
@@ -47,12 +47,12 @@ public final class HtmlUtils {
Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() {
@Override
- public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) {
+ public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) {
return rhs.getRight().compareTo(lhs.getRight());
}
});
result = text.toString();
- for (Pair<Integer, Integer> removal : removals) {
+ for (final Pair<Integer, Integer> removal : removals) {
result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight());
}
}
@@ -60,4 +60,14 @@ public final class HtmlUtils {
// now that images are gone, do a normal html to text conversion
return Html.fromHtml(result).toString().trim();
}
+
+ public static String removeExtraParagraph(final String html) {
+ if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) {
+ final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim();
+ if (extractText(paragraph).equals(paragraph)) {
+ return paragraph;
+ }
+ }
+ return html;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java
index 739ecc4..c2b7327 100644
--- a/main/src/cgeo/geocaching/utils/ImageUtils.java
+++ b/main/src/cgeo/geocaching/utils/ImageUtils.java
@@ -1,6 +1,7 @@
package cgeo.geocaching.utils;
import cgeo.geocaching.CgeoApplication;
+import cgeo.geocaching.Image;
import cgeo.geocaching.R;
import cgeo.geocaching.compatibility.Compatibility;
@@ -25,6 +26,8 @@ import android.graphics.drawable.Drawable;
import android.media.ExifInterface;
import android.net.Uri;
import android.os.Environment;
+import android.text.Html;
+import android.text.Html.ImageGetter;
import android.util.Base64;
import android.util.Base64InputStream;
import android.widget.TextView;
@@ -36,8 +39,11 @@ import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
+import java.util.Collection;
import java.util.Date;
+import java.util.LinkedHashSet;
import java.util.Locale;
+import java.util.Set;
public final class ImageUtils {
private static final int[] ORIENTATIONS = new int[] {
@@ -49,6 +55,10 @@ public final class ImageUtils {
private static final int[] ROTATION = new int[] { 90, 180, 270 };
private static final int MAX_DISPLAY_IMAGE_XY = 800;
+ // Images whose URL contains one of those patterns will not be available on the Images tab
+ // for opening into an external application.
+ private final static String[] NO_EXTERNAL = new String[] { "geocheck.org" };
+
private ImageUtils() {
// Do not let this class be instantiated, this is a utility class.
}
@@ -61,7 +71,7 @@ public final class ImageUtils {
* @return BitmapDrawable The scaled image
*/
public static BitmapDrawable scaleBitmapToFitDisplay(@NonNull final Bitmap image) {
- Point displaySize = Compatibility.getDisplaySize();
+ final Point displaySize = Compatibility.getDisplaySize();
final int maxWidth = displaySize.x - 25;
final int maxHeight = displaySize.y - 25;
return scaleBitmapTo(image, maxWidth, maxHeight);
@@ -76,7 +86,7 @@ public final class ImageUtils {
*/
@Nullable
public static Bitmap readAndScaleImageToFitDisplay(@NonNull final String filename) {
- Point displaySize = Compatibility.getDisplaySize();
+ final Point displaySize = Compatibility.getDisplaySize();
// Restrict image size to 800 x 800 to prevent OOM on tablets
final int maxWidth = Math.min(displaySize.x - 25, MAX_DISPLAY_IMAGE_XY);
final int maxHeight = Math.min(displaySize.y - 25, MAX_DISPLAY_IMAGE_XY);
@@ -128,12 +138,12 @@ public final class ImageUtils {
*/
public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) {
try {
- FileOutputStream out = new FileOutputStream(pathOfOutputImage);
- BufferedOutputStream bos = new BufferedOutputStream(out);
+ final FileOutputStream out = new FileOutputStream(pathOfOutputImage);
+ final BufferedOutputStream bos = new BufferedOutputStream(out);
bitmap.compress(format, quality, bos);
bos.flush();
bos.close();
- } catch (IOException e) {
+ } catch (final IOException e) {
Log.e("ImageHelper.storeBitmap", e);
}
}
@@ -152,7 +162,7 @@ public final class ImageUtils {
if (maxXY <= 0) {
return filePath;
}
- Bitmap image = readDownsampledImage(filePath, maxXY, maxXY);
+ final Bitmap image = readDownsampledImage(filePath, maxXY, maxXY);
if (image == null) {
return null;
}
@@ -184,7 +194,7 @@ public final class ImageUtils {
try {
final ExifInterface exif = new ExifInterface(filePath);
orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL);
- } catch (IOException e) {
+ } catch (final IOException e) {
Log.e("ImageUtils.readDownsampledImage", e);
}
final BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options();
@@ -233,7 +243,7 @@ public final class ImageUtils {
}
// Create a media file name
- String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
+ final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date());
return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg");
}
@@ -254,7 +264,7 @@ public final class ImageUtils {
* @return <tt>true</tt> if the URL contains at least one of the patterns, <tt>false</tt> otherwise
*/
public static boolean containsPattern(final String url, final String[] patterns) {
- for (String entry : patterns) {
+ for (final String entry : patterns) {
if (StringUtils.containsIgnoreCase(url, entry)) {
return true;
}
@@ -282,7 +292,7 @@ public final class ImageUtils {
/**
* Decode a base64-encoded string and save the result into a stream.
- *
+ *
* @param inString
* the encoded string
* @param out
@@ -303,15 +313,39 @@ public final class ImageUtils {
}
/**
+ * Add images present in the HTML description to the existing collection.
+ *
+ * @param images a collection of images
+ * @param htmlText the HTML description to be parsed
+ * @param geocode the common title for images in the description
+ */
+ public static void addImagesFromHtml(final Collection<Image> images, final String htmlText, final String geocode) {
+ final Set<String> urls = new LinkedHashSet<>();
+ for (final Image image : images) {
+ urls.add(image.getUrl());
+ }
+ Html.fromHtml(StringUtils.defaultString(htmlText), new ImageGetter() {
+ @Override
+ public Drawable getDrawable(final String source) {
+ if (!urls.contains(source) && canBeOpenedExternally(source)) {
+ images.add(new Image(source, StringUtils.defaultString(geocode)));
+ urls.add(source);
+ }
+ return null;
+ }
+ }, null);
+ }
+
+ /**
* Container which can hold a drawable (initially an empty one) and get a newer version when it
* becomes available. It also invalidates the view the container belongs to, so that it is
* redrawn properly.
*/
- @SuppressWarnings("deprecation")
- public final static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
+ public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> {
private Drawable drawable;
final private TextView view;
+ @SuppressWarnings("deprecation")
public ContainerDrawable(@NonNull final TextView view) {
this.view = view;
drawable = null;
@@ -324,7 +358,7 @@ public final class ImageUtils {
}
@Override
- public void draw(final Canvas canvas) {
+ public final void draw(final Canvas canvas) {
if (drawable != null) {
drawable.draw(canvas);
}
@@ -337,8 +371,55 @@ public final class ImageUtils {
view.setText(view.getText());
}
- public void updateFrom(final Observable<? extends Drawable> drawableObservable) {
+ public final void updateFrom(final Observable<? extends Drawable> drawableObservable) {
drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this);
}
}
+
+ /**
+ * Image that automatically scales to fit a line of text in the containing {@link TextView}.
+ */
+ public final static class LineHeightContainerDrawable extends ContainerDrawable {
+ private final TextView view;
+
+ public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) {
+ super(view, drawableObservable);
+ this.view = view;
+ }
+
+ @Override
+ public void call(final Drawable newDrawable) {
+ super.call(newDrawable);
+ setBounds(ImageUtils.scaleImageToLineHeight(newDrawable, view));
+ }
+ }
+
+ public static boolean canBeOpenedExternally(final String source) {
+ return !containsPattern(source, NO_EXTERNAL);
+ }
+
+ public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) {
+ final int lineHeight = (int) (view.getLineHeight() * 0.8);
+ final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight();
+ return new Rect(0, 0, width, lineHeight);
+ }
+
+ public static Bitmap convertToBitmap(final Drawable drawable) {
+ if (drawable instanceof BitmapDrawable) {
+ return ((BitmapDrawable) drawable).getBitmap();
+ }
+
+ // handle solid colors, which have no width
+ int width = drawable.getIntrinsicWidth();
+ width = width > 0 ? width : 1;
+ int height = drawable.getIntrinsicHeight();
+ height = height > 0 ? height : 1;
+
+ final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
+ final Canvas canvas = new Canvas(bitmap);
+ drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight());
+ drawable.draw(canvas);
+
+ return bitmap;
+ }
}
diff --git a/main/src/cgeo/geocaching/utils/JsonUtils.java b/main/src/cgeo/geocaching/utils/JsonUtils.java
new file mode 100644
index 0000000..492e137
--- /dev/null
+++ b/main/src/cgeo/geocaching/utils/JsonUtils.java
@@ -0,0 +1,20 @@
+package cgeo.geocaching.utils;
+
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.ObjectReader;
+import com.fasterxml.jackson.databind.ObjectWriter;
+import com.fasterxml.jackson.databind.node.JsonNodeFactory;
+
+public class JsonUtils {
+
+ private static final ObjectMapper mapper = new ObjectMapper();
+ public static final ObjectReader reader = mapper.reader();
+ public static final ObjectWriter writer = mapper.writer();
+
+ public static final JsonNodeFactory factory = new JsonNodeFactory(true);
+
+ private JsonUtils() {
+ // Do not instantiate
+ }
+
+}
diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
index a69f427..d4cf16e 100644
--- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
+++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java
@@ -20,12 +20,9 @@ import java.util.List;
* access has to be guarded externally or the synchronized getAsList method can be used
* to get a clone for iteration.
*/
-public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
- implements Cloneable, java.io.Serializable {
+public class LeastRecentlyUsedSet<E> extends AbstractSet<E> {
- private static final long serialVersionUID = -1942301031191419547L;
-
- private transient LeastRecentlyUsedMap<E, Object> map;
+ private final LeastRecentlyUsedMap<E, Object> map;
private static final Object PRESENT = new Object();
public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) {
@@ -132,26 +129,6 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
}
/**
- * (synchronized) Clone of the set
- * Copy of the HashSet code if clone()
- *
- * @see HashSet
- */
- @Override
- @SuppressWarnings("unchecked")
- public Object clone() throws CloneNotSupportedException {
- try {
- synchronized (this) {
- final LeastRecentlyUsedSet<E> newSet = (LeastRecentlyUsedSet<E>) super.clone();
- newSet.map = (LeastRecentlyUsedMap<E, Object>) map.clone();
- return newSet;
- }
- } catch (CloneNotSupportedException e) {
- throw new InternalError();
- }
- }
-
- /**
* Creates a clone as a list in a synchronized fashion.
*
* @return List based clone of the set
@@ -160,56 +137,4 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E>
return new ArrayList<>(this);
}
- /**
- * Serialization version of HashSet with the additional parameters for the custom Map
- *
- * @see HashSet
- */
- private void writeObject(java.io.ObjectOutputStream s)
- throws java.io.IOException {
- // Write out any hidden serialization magic
- s.defaultWriteObject();
-
- // Write out HashMap capacity and load factor
- s.writeInt(map.initialCapacity);
- s.writeFloat(map.loadFactor);
- s.writeInt(map.getMaxEntries());
-
- // Write out size
- s.writeInt(map.size());
-
- // Write out all elements in the proper order.
- for (final E e : map.keySet()) {
- s.writeObject(e);
- }
- }
-
- /**
- * Serialization version of HashSet with the additional parameters for the custom Map
- *
- * @see HashSet
- */
- @SuppressWarnings("unchecked")
- private void readObject(java.io.ObjectInputStream s)
- throws java.io.IOException, ClassNotFoundException {
- // Read in any hidden serialization magic
- s.defaultReadObject();
-
- // Read in HashMap capacity and load factor and create backing HashMap
- final int capacity = s.readInt();
- final float loadFactor = s.readFloat();
- final int maxEntries = s.readInt();
-
- map = new LeastRecentlyUsedMap.LruCache<>(maxEntries, capacity, loadFactor);
-
- // Read in size
- final int size = s.readInt();
-
- // Read in all elements in the proper order.
- for (int i = 0; i < size; i++) {
- E e = (E) s.readObject();
- map.put(e, PRESENT);
- }
- }
-
}
diff --git a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
index 1401542..0c6365c 100644
--- a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
+++ b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java
@@ -11,14 +11,12 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
private boolean defaultReplaced = false;
public static boolean activateHandler() {
-
final OOMDumpingUncaughtExceptionHandler handler = new OOMDumpingUncaughtExceptionHandler();
return handler.activate();
}
private boolean activate() {
-
defaultHandler = Thread.getDefaultUncaughtExceptionHandler();
// replace default handler if that has not been done already
@@ -34,10 +32,8 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
}
public static boolean resetToDefault() {
-
- boolean defaultResetted = false;
-
final UncaughtExceptionHandler unspecificHandler = Thread.getDefaultUncaughtExceptionHandler();
+ boolean defaultResetted = unspecificHandler != null;
if (unspecificHandler instanceof OOMDumpingUncaughtExceptionHandler) {
final OOMDumpingUncaughtExceptionHandler handler = (OOMDumpingUncaughtExceptionHandler) unspecificHandler;
@@ -48,7 +44,6 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand
}
private boolean reset() {
-
final boolean resetted = defaultReplaced;
if (defaultReplaced) {
diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java
index 241ba78..ef79f93 100644
--- a/main/src/cgeo/geocaching/utils/RxUtils.java
+++ b/main/src/cgeo/geocaching/utils/RxUtils.java
@@ -1,23 +1,49 @@
package cgeo.geocaching.utils;
import rx.Observable;
+import rx.Observable.OnSubscribe;
+import rx.Observable.Operator;
import rx.Scheduler;
+import rx.Scheduler.Worker;
+import rx.Subscriber;
+import rx.android.schedulers.AndroidSchedulers;
+import rx.functions.Action0;
+import rx.functions.Func1;
+import rx.internal.operators.OperatorTakeWhile;
import rx.observables.BlockingObservable;
import rx.schedulers.Schedulers;
+import rx.subscriptions.Subscriptions;
+
+import android.os.Handler;
+import android.os.HandlerThread;
+import android.os.Looper;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
public class RxUtils {
- // Utility class, not to be instanciated
- private RxUtils() {}
+ private RxUtils() {
+ // Utility class, not to be instantiated
+ }
public final static Scheduler computationScheduler = Schedulers.computation();
public static final Scheduler networkScheduler = Schedulers.from(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()));
+ private static final HandlerThread looperCallbacksThread =
+ new HandlerThread("Looper callbacks thread", android.os.Process.THREAD_PRIORITY_BACKGROUND);
+
+ static {
+ looperCallbacksThread.start();
+ }
+
+ public static final Looper looperCallbacksLooper = looperCallbacksThread.getLooper();
+ public static final Scheduler looperCallbacksScheduler = AndroidSchedulers.handlerThread(new Handler(looperCallbacksLooper));
+ public static final Worker looperCallbacksWorker = looperCallbacksScheduler.createWorker();
+
public static <T> void waitForCompletion(final BlockingObservable<T> observable) {
observable.lastOrDefault(null);
}
@@ -25,4 +51,73 @@ public class RxUtils {
public static void waitForCompletion(final Observable<?>... observables) {
waitForCompletion(Observable.merge(observables).toBlocking());
}
+
+ /**
+ * Subscribe function whose subscription and unsubscription take place on a looper thread.
+ *
+ * @param <T>
+ * the type of the observable
+ */
+ public static abstract class LooperCallbacks<T> implements OnSubscribe<T> {
+
+ final AtomicInteger counter = new AtomicInteger(0);
+ final long stopDelay;
+ final TimeUnit stopDelayUnit;
+ protected Subscriber<? super T> subscriber;
+
+ public LooperCallbacks(final long stopDelay, final TimeUnit stopDelayUnit) {
+ this.stopDelay = stopDelay;
+ this.stopDelayUnit = stopDelayUnit;
+ }
+
+ public LooperCallbacks() {
+ this(0, TimeUnit.SECONDS);
+ }
+
+ @Override
+ final public void call(final Subscriber<? super T> subscriber) {
+ this.subscriber = subscriber;
+ looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (counter.getAndIncrement() == 0) {
+ onStart();
+ }
+ subscriber.add(Subscriptions.create(new Action0() {
+ @Override
+ public void call() {
+ looperCallbacksWorker.schedule(new Action0() {
+ @Override
+ public void call() {
+ if (counter.decrementAndGet() == 0) {
+ onStop();
+ }
+ }
+ }, stopDelay, stopDelayUnit);
+ }
+ }));
+ }
+ });
+ }
+
+ abstract protected void onStart();
+
+ abstract protected void onStop();
+ }
+
+ public static <T> Operator<T, T> operatorTakeUntil(final Func1<? super T, Boolean> predicate) {
+ return new OperatorTakeWhile<>(new Func1<T, Boolean>() {
+ private boolean quitting = false;
+
+ @Override
+ public Boolean call(final T item) {
+ if (quitting) {
+ return false;
+ }
+ quitting |= predicate.call(item);
+ return true;
+ }
+ });
+ }
+
}
diff --git a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java b/main/src/cgeo/geocaching/utils/StartableHandlerThread.java
deleted file mode 100644
index 91ab1d0..0000000
--- a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java
+++ /dev/null
@@ -1,80 +0,0 @@
-package cgeo.geocaching.utils;
-
-import org.eclipse.jdt.annotation.NonNull;
-import rx.Subscriber;
-import rx.functions.Action0;
-import rx.subscriptions.Subscriptions;
-
-import android.annotation.SuppressLint;
-import android.content.Context;
-import android.os.Handler;
-import android.os.HandlerThread;
-import android.os.Message;
-
-/**
- * Derivated class of {@link android.os.HandlerThread} with an exposed handler and a start/stop mechanism
- * based on subscriptions.
- */
-
-public class StartableHandlerThread extends HandlerThread {
-
- private final static int START = 1;
- private final static int STOP = 2;
-
- static public interface Callback {
- public void start(final Context context, final Handler handler);
- public void stop();
- }
-
- // The handler and the thread are intimely linked, there will be no leak.
- @SuppressLint("HandlerLeak")
- private class StartableHandler extends Handler {
- public StartableHandler() {
- super(StartableHandlerThread.this.getLooper());
- }
-
- @Override
- public void handleMessage(final Message message) {
- if (callback != null) {
- switch (message.what) {
- case START:
- callback.start((Context) message.obj, this);
- break;
- case STOP:
- callback.stop();
- break;
- }
- }
- }
- }
-
- private Handler handler;
- private Callback callback;
-
- public StartableHandlerThread(@NonNull final String name, final int priority, final Callback callback) {
- super(name, priority);
- this.callback = callback;
- }
-
- public StartableHandlerThread(@NonNull final String name, final int priority) {
- this(name, priority, null);
- }
-
- public synchronized Handler getHandler() {
- if (handler == null) {
- handler = new StartableHandler();
- }
- return handler;
- }
-
- public void start(final Subscriber<?> subscriber, final Context context) {
- getHandler().obtainMessage(START, context).sendToTarget();
- subscriber.add(Subscriptions.create(new Action0() {
- @Override
- public void call() {
- getHandler().sendEmptyMessage(STOP);
- }
- }));
- }
-
-}
diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java
index 77aa167..04a9007 100644
--- a/main/src/cgeo/geocaching/utils/TextUtils.java
+++ b/main/src/cgeo/geocaching/utils/TextUtils.java
@@ -27,11 +27,11 @@ public final class TextUtils {
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param trim
* Set to true if the group found should be trim'ed
@@ -44,37 +44,38 @@ public final class TextUtils {
* @return defaultValue or the n-th group if the pattern matches (trimmed if wanted)
*/
@SuppressFBWarnings("DM_STRING_CTOR")
- public static String getMatch(@Nullable final String data, final Pattern p, final boolean trim, final int group, final String defaultValue, final boolean last) {
+ public static String getMatch(@Nullable final String data, final Pattern pattern, final boolean trim, final int group, final String defaultValue, final boolean last) {
if (data != null) {
-
- String result = null;
- final Matcher matcher = p.matcher(data);
-
+ final Matcher matcher = pattern.matcher(data);
if (matcher.find()) {
- result = matcher.group(group);
- }
- if (null != result) {
- final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result);
- result = remover.replaceAll(" ");
+ String result = matcher.group(group);
+ while (last && matcher.find()) {
+ result = matcher.group(group);
+ }
- return trim ? new String(result).trim() : new String(result);
- // Java copies the whole page String, when matching with regular expressions
- // later this would block the garbage collector, as we only need tiny parts of the page
- // see http://developer.android.com/reference/java/lang/String.html#backing_array
- // Thus the creating of a new String via String constructor is necessary here!!
+ if (result != null) {
+ final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result);
+ result = remover.replaceAll(" ");
- // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler!
+ // Some versions of Java copy the whole page String, when matching with regular expressions
+ // later this would block the garbage collector, as we only need tiny parts of the page
+ // see http://developer.android.com/reference/java/lang/String.html#backing_array
+ // Thus the creating of a new String via String constructor is voluntary here!!
+ // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler!
+ return trim ? new String(result).trim() : new String(result);
+ }
}
}
+
return defaultValue;
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param trim
* Set to true if the group found should be trim'ed
@@ -82,38 +83,35 @@ public final class TextUtils {
* Value to return if the pattern is not found
* @return defaultValue or the first group if the pattern matches (trimmed if wanted)
*/
- public static String getMatch(final String data, final Pattern p, final boolean trim, final String defaultValue) {
- return TextUtils.getMatch(data, p, trim, 1, defaultValue, false);
+ public static String getMatch(final String data, final Pattern pattern, final boolean trim, final String defaultValue) {
+ return TextUtils.getMatch(data, pattern, trim, 1, defaultValue, false);
}
/**
- * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned
+ * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned
*
* @param data
* Data to search in
- * @param p
+ * @param pattern
* Pattern to search for
* @param defaultValue
* Value to return if the pattern is not found
* @return defaultValue or the first group if the pattern matches (trimmed)
*/
- public static String getMatch(@Nullable final String data, final Pattern p, final String defaultValue) {
- return TextUtils.getMatch(data, p, true, 1, defaultValue, false);
+ public static String getMatch(@Nullable final String data, final Pattern pattern, final String defaultValue) {
+ return TextUtils.getMatch(data, pattern, true, 1, defaultValue, false);
}
/**
- * Searches for the pattern p in the data.
+ * Searches for the pattern pattern in the data.
*
* @param data
- * @param p
- * @return true if data contains the pattern p
+ * @param pattern
+ * @return true if data contains the pattern pattern
*/
- public static boolean matches(final String data, final Pattern p) {
- if (data == null) {
- return false;
- }
+ public static boolean matches(final String data, final Pattern pattern) {
// matcher is faster than String.contains() and more flexible - it takes patterns instead of fixed texts
- return p.matcher(data).find();
+ return data != null && pattern.matcher(data).find();
}
@@ -182,7 +180,7 @@ public final class TextUtils {
*/
public static long checksum(final String input) {
final CRC32 checksum = new CRC32();
- checksum.update(input.getBytes());
+ checksum.update(input.getBytes(CHARSET_UTF8));
return checksum.getValue();
}
}