diff options
Diffstat (limited to 'main/src/cgeo/geocaching')
118 files changed, 3183 insertions, 2694 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..8375734 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -18,7 +18,6 @@ import cgeo.geocaching.connector.IConnector; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCConstants; import cgeo.geocaching.enumerations.CacheAttribute; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.WaypointType; @@ -61,15 +60,16 @@ import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; -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 +171,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 +238,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 { @@ -263,7 +266,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } // If we open this cache from a search, let's properly initialize the title bar, even if we don't have cache details - updateTitleBar(geocode, name, null); + setCacheTitleBar(geocode, name, null); final LoadCacheHandler loadCacheHandler = new LoadCacheHandler(this, progress); @@ -293,8 +296,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 +329,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 +348,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override + public void onPause() { + geoDataSubscription.unsubscribe(); + super.onPause(); + } + + @Override public void onDestroy() { createSubscriptions.unsubscribe(); super.onDestroy(); @@ -591,7 +611,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // allow cache to notify CacheDetailActivity when it changes so it can be reloaded cache.setChangeNotificationHandler(new ChangeNotificationHandler(this, progress)); - updateTitleBar(cache.getGeocode(), cache.getName(), cache.getType()); + setCacheTitleBar(cache); // reset imagesList so Images view page will be redrawn imagesList = null; @@ -604,19 +624,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc Settings.addCacheToHistory(cache.getGeocode()); } - private void updateTitleBar(@Nullable final String geocode, @Nullable final String name, @Nullable final CacheType type) { - if (StringUtils.isNotBlank(name)) { - setTitle(StringUtils.isNotBlank(geocode) ? name + " (" + geocode + ")" : name); - } else { - setTitle(StringUtils.isNotBlank(geocode) ? geocode : res.getString(R.string.cache)); - } - if (type != null) { - getSupportActionBar().setIcon(getResources().getDrawable(type.markerId)); - } else { - getSupportActionBar().setIcon(android.R.color.transparent); - } - } - /** * Tries to navigate to the {@link Geocache} of this activity using the default navigation tool. */ @@ -910,9 +917,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 +2230,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 +2246,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 +2259,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void handleMessage(final Message msg) { - notifyDatasetChanged(activityRef); + notifyDataSetChanged(activityRef); } } @@ -2267,12 +2274,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 a817aa5..5eaf17e 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, adapter.getCheckedOrAllCaches()).start(); + } + }); } /** @@ -975,8 +983,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private void updateAdapter() { adapter.notifyDataSetChanged(); - adapter.forceSort(); adapter.reFilter(); + adapter.checkEvents(); + adapter.forceSort(); } private void showFooterLoadingCaches() { @@ -1078,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); } } @@ -1125,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++) { @@ -1136,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() { @@ -1158,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()); @@ -1306,14 +1315,14 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } - private class ClearOfflineLogsThread extends Thread { + private static class ClearOfflineLogsThread extends Thread { final private Handler handler; final private List<Geocache> selected; - public ClearOfflineLogsThread(final Handler handlerIn) { + public ClearOfflineLogsThread(final Handler handlerIn, final List<Geocache> selectedCaches) { handler = handlerIn; - selected = adapter.getCheckedOrAllCaches(); + selected = selectedCaches; } @Override @@ -1354,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; } @@ -1451,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); } @@ -1460,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); } @@ -1478,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); } @@ -1499,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); @@ -1528,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); } @@ -1547,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); } @@ -1566,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); @@ -1591,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/CacheMenuHandler.java b/main/src/cgeo/geocaching/CacheMenuHandler.java index 0dc6444..fa58179 100644 --- a/main/src/cgeo/geocaching/CacheMenuHandler.java +++ b/main/src/cgeo/geocaching/CacheMenuHandler.java @@ -18,7 +18,11 @@ import android.view.MenuItem; * TODO: replace by a fragment * */ -public class CacheMenuHandler extends AbstractUIFactory { +public final class CacheMenuHandler extends AbstractUIFactory { + + private CacheMenuHandler() { + // utility class + } /** * Methods to be implemented by the activity to react to the cache menu selections. 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..dd43bea 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -8,19 +8,24 @@ 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.NonNull; 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 +39,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; @@ -52,25 +53,19 @@ public class CompassActivity extends AbstractActionBarActivity { @InjectView(R.id.destination) protected TextView destinationTextView; @InjectView(R.id.cacheinfo) protected TextView cacheInfoView; - private static final String EXTRAS_COORDS = "coords"; - 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). */ - private @Nullable Geocache cache = null; + private Geocache cache = null; private Geopoint dstCoords = null; private float cacheHeading = 0; - private String title = null; - private String info = null; private boolean hasMagneticFieldSensor; + private String description; @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.compass_activity); + ButterKnife.inject(this); final SensorManager sensorManager = (SensorManager) getSystemService(SENSOR_SERVICE); hasMagneticFieldSensor = sensorManager.getDefaultSensor(Sensor.TYPE_MAGNETIC_FIELD) != null; @@ -80,37 +75,38 @@ public class CompassActivity extends AbstractActionBarActivity { // get parameters final Bundle extras = getIntent().getExtras(); - if (extras != null) { - final String geocode = extras.getString(EXTRAS_GEOCODE); - if (StringUtils.isNotEmpty(geocode)) { - cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); - } - title = geocode; - final String name = extras.getString(EXTRAS_NAME); - dstCoords = extras.getParcelable(EXTRAS_COORDS); - info = extras.getString(EXTRAS_CACHE_INFO); - - if (StringUtils.isNotBlank(name)) { - if (StringUtils.isNotBlank(title)) { - title += ": " + name; - } else { - title = name; - } - } - } else { - final Intent pointIntent = new Intent(this, NavigateAnyPointActivity.class); - startActivity(pointIntent); - + if (extras == null) { finish(); return; } - // set header - setTitle(); - setDestCoords(); - setCacheInfo(); + // cache must exist, except for "any point navigation" + final String geocode = extras.getString(Intents.EXTRA_GEOCODE); + if (geocode != null) { + cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + } - ButterKnife.inject(this); + // find the wanted navigation target + if (extras.containsKey(Intents.EXTRA_WAYPOINT_ID)) { + final int waypointId = extras.getInt(Intents.EXTRA_WAYPOINT_ID); + setTarget(DataStore.loadWaypoint(waypointId)); + } + else if (extras.containsKey(Intents.EXTRA_COORDS)) { + final Geopoint coords = extras.getParcelable(Intents.EXTRA_COORDS); + final String description = extras.getString(Intents.EXTRA_DESCRIPTION); + setTarget(coords, description); + } + else { + setTarget(cache); + } + + // set activity title just once, independent of what target is switched to + if (cache != null) { + setCacheTitleBar(cache); + } + else { + setTitle(StringUtils.defaultIfBlank(extras.getString(Intents.EXTRA_NAME), res.getString(R.string.navigation))); + } // make sure we can control the TTS volume setVolumeControlStream(AudioManager.STREAM_MUSIC); @@ -118,7 +114,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 @@ -134,38 +132,44 @@ public class CompassActivity extends AbstractActionBarActivity { setContentView(R.layout.compass_activity); ButterKnife.inject(this); + setTarget(dstCoords, description); - 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); @@ -177,6 +181,7 @@ public class CompassActivity extends AbstractActionBarActivity { } menu.findItem(R.id.menu_tts_start).setVisible(!SpeechService.isRunning()); menu.findItem(R.id.menu_tts_stop).setVisible(SpeechService.isRunning()); + menu.findItem(R.id.menu_compass_cache).setVisible(cache != null); return true; } @@ -203,36 +208,42 @@ public class CompassActivity extends AbstractActionBarActivity { SpeechService.stopService(this); invalidateOptionsMenuCompatible(); return true; + case R.id.menu_compass_cache: + setTarget(cache); + return true; default: 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) { + setTarget(waypoint); + return true; + } } } return super.onOptionsItemSelected(item); } - private void setTitle() { - if (StringUtils.isNotBlank(title)) { - setTitle(title); - } else { - setTitle(res.getString(R.string.navigation)); - } + private void setTarget(final Geopoint coords, final String description) { + setDestCoords(coords); + setTargetDescription(description); + updateDistanceInfo(app.currentGeo()); + + Log.d("destination set: " + description + " (" + dstCoords + ")"); } - private void setDestCoords() { + private void setTarget(final @NonNull Waypoint waypoint) { + setTarget(waypoint.getCoords(), waypoint.getName()); + } + + private void setTarget(final Geocache cache) { + setTarget(cache.getCoords(), Formatter.formatCacheInfoShort(cache)); + } + + private void setDestCoords(final Geopoint coords) { + dstCoords = coords; if (dstCoords == null) { return; } @@ -240,13 +251,14 @@ public class CompassActivity extends AbstractActionBarActivity { destinationTextView.setText(dstCoords.toString()); } - private void setCacheInfo() { - if (info == null) { + private void setTargetDescription(final @Nullable String newDescription) { + description = newDescription; + if (description == null) { cacheInfoView.setVisibility(View.GONE); return; } cacheInfoView.setVisibility(View.VISIBLE); - cacheInfoView.setText(info); + cacheInfoView.setText(description); } private void updateDistanceInfo(final IGeoData geo) { @@ -259,16 +271,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 +304,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,34 +317,24 @@ 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, - final String info) { - coordinates.clear(); - if (coordinatesWithType != null) { - for (final IWaypoint coordinate : coordinatesWithType) { - if (coordinate != null) { - coordinates.add(coordinate); - } - } - } - + public static void startActivityWaypoint(final Context context, final Waypoint waypoint) { final Intent navigateIntent = new Intent(context, CompassActivity.class); - navigateIntent.putExtra(EXTRAS_COORDS, coords); - navigateIntent.putExtra(EXTRAS_GEOCODE, geocode); - if (null != displayedName) { - navigateIntent.putExtra(EXTRAS_NAME, displayedName); - } - navigateIntent.putExtra(EXTRAS_CACHE_INFO, info); + navigateIntent.putExtra(Intents.EXTRA_GEOCODE, waypoint.getGeocode()); + navigateIntent.putExtra(Intents.EXTRA_WAYPOINT_ID, waypoint.getId()); 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 startActivityPoint(final Context context, final Geopoint coords, final String displayedName) { + final Intent navigateIntent = new Intent(context, CompassActivity.class); + navigateIntent.putExtra(Intents.EXTRA_COORDS, coords); + navigateIntent.putExtra(Intents.EXTRA_NAME, displayedName); + context.startActivity(navigateIntent); } - public static void startActivity(final Context context, final Geocache cache) { - startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(), null, - Formatter.formatCacheInfoShort(cache)); + public static void startActivityCache(final Context context, final Geocache cache) { + final Intent navigateIntent = new Intent(context, CompassActivity.class); + navigateIntent.putExtra(Intents.EXTRA_GEOCODE, cache.getGeocode()); + context.startActivity(navigateIntent); } } 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..71db685 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 * @@ -1070,7 +1041,7 @@ public class DataStore { * @param saveFlags * */ - public static void saveCache(final Geocache cache, final EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCache(final Geocache cache, final Set<LoadFlags.SaveFlag> saveFlags) { saveCaches(Collections.singletonList(cache), saveFlags); } @@ -1082,7 +1053,7 @@ public class DataStore { * @param saveFlags * */ - public static void saveCaches(final Collection<Geocache> caches, final EnumSet<LoadFlags.SaveFlag> saveFlags) { + public static void saveCaches(final Collection<Geocache> caches, final Set<LoadFlags.SaveFlag> saveFlags) { if (CollectionUtils.isEmpty(caches)) { return; } @@ -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 34caabb..1ba9c5a 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -66,11 +66,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C @ViewById(R.id.modify_cache_coordinates_local) protected RadioButton modifyLocal; @Extra(Intents.EXTRA_GEOCODE) protected String geocode = null; - @Extra(Intents.EXTRA_WAYPOINT_ID) protected int id = -1; - /** - * number of waypoints that the corresponding cache has until now - */ - @Extra(Intents.EXTRA_COUNT) protected int wpCount = 0; + @Extra(Intents.EXTRA_WAYPOINT_ID) protected int waypointId = -1; @InstanceState protected int waypointTypeSelectorPosition = -1; @@ -89,14 +85,14 @@ 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); - id = -1; + Log.d("No waypoint loaded to edit. id= " + waypointId); + waypointId = -1; } else { geocode = waypoint.getGeocode(); prefix = waypoint.getPrefix(); @@ -126,7 +122,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,17 +134,17 @@ 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) { + if (StringUtils.isBlank(geocode) && waypointId <= 0) { showToast(res.getString(R.string.err_waypoint_cache_unknown)); finish(); return; } - if (id <= 0) { + if (waypointId <= 0) { setTitle(res.getString(R.string.waypoint_add_title)); } else { setTitle(res.getString(R.string.waypoint_edit_title)); @@ -159,11 +155,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) { @@ -174,7 +170,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); setCoordsModificationVisibility(ConnectorFactory.getConnector(geocode), cache); } - if (id > 0) { // existing waypoint + if (waypointId > 0) { // existing waypoint waitDialog = ProgressDialog.show(this, null, res.getString(R.string.waypoint_loading), true); waitDialog.setCancelable(true); @@ -189,7 +185,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 +201,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) { } }); @@ -274,10 +275,10 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C @Override public void run() { try { - waypoint = DataStore.loadWaypoint(id); + waypoint = DataStore.loadWaypoint(waypointId); loadWaypointHandler.sendMessage(Message.obtain()); - } catch (Exception e) { + } catch (final Exception e) { Log.e("EditWaypointActivity.loadWaypoint.run", e); } } @@ -286,15 +287,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 +304,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 +351,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 +369,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 +387,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 +396,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 +404,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.waypoint_uploading), 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 +453,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); @@ -430,14 +461,14 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C waypoint.setCoords(coordsToSave); waypoint.setNote(noteText); waypoint.setVisited(visited); - waypoint.setId(id); + waypoint.setId(waypointId); - 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(waypointId); if (cache.addOrChangeWaypoint(waypoint, true)) { DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); if (!StaticMapsProvider.hasAllStaticMapsForWaypoint(geocode, waypoint)) { @@ -460,7 +491,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)); @@ -485,10 +516,10 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C } public static void startActivityEditWaypoint(final Context context, final Geocache cache, final int waypointId) { - EditWaypointActivity_.intent(context).geocode(cache.getGeocode()).id(waypointId).start(); + EditWaypointActivity_.intent(context).geocode(cache.getGeocode()).waypointId(waypointId).start(); } public static void startActivityAddWaypoint(final Context context, final Geocache cache) { - EditWaypointActivity_.intent(context).geocode(cache.getGeocode()).wpCount(cache.getWaypoints().size()).start(); + EditWaypointActivity_.intent(context).geocode(cache.getGeocode()).start(); } } 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..5ed2719 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,24 @@ 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"; + + public static final String EXTRA_DOWNLOAD = PREFIX + "download"; + + /** + * 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 +74,25 @@ 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; + } + try { + return CacheListType.valueOf(typeName); + } catch (final IllegalArgumentException e) { + return 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/StaticMapsActivity.java b/main/src/cgeo/geocaching/StaticMapsActivity.java index ceceab9..7d822ec 100644 --- a/main/src/cgeo/geocaching/StaticMapsActivity.java +++ b/main/src/cgeo/geocaching/StaticMapsActivity.java @@ -27,14 +27,11 @@ import java.util.List; @OptionsMenu(R.menu.static_maps_activity_options) public class StaticMapsActivity extends AbstractActionBarActivity { - private static final String EXTRAS_WAYPOINT = "waypoint"; - private static final String EXTRAS_DOWNLOAD = "download"; - private static final String EXTRAS_GEOCODE = "geocode"; - - @Extra(EXTRAS_DOWNLOAD) boolean download = false; - @Extra(EXTRAS_WAYPOINT) Integer waypointId = null; - @Extra(EXTRAS_GEOCODE) String geocode = null; + @Extra(Intents.EXTRA_DOWNLOAD) boolean download = false; + @Extra(Intents.EXTRA_WAYPOINT_ID) Integer waypointId = null; + @Extra(Intents.EXTRA_GEOCODE) String geocode = null; + private Geocache cache; private final List<Bitmap> maps = new ArrayList<>(); private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; @@ -99,6 +96,8 @@ public class StaticMapsActivity extends AbstractActionBarActivity { finish(); return; } + cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); + setCacheTitleBar(cache); waitDialog = ProgressDialog.show(this, null, res.getString(R.string.map_static_loading), true); waitDialog.setCancelable(true); @@ -116,7 +115,6 @@ public class StaticMapsActivity extends AbstractActionBarActivity { for (int level = 1; level <= StaticMapsProvider.MAPS_LEVEL_MAX; level++) { try { if (waypointId != null) { - final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); final Bitmap image = StaticMapsProvider.getWaypointMap(geocode, cache.getWaypointById(waypointId), level); if (image != null) { maps.add(image); @@ -150,7 +148,6 @@ public class StaticMapsActivity extends AbstractActionBarActivity { } private boolean downloadStaticMaps() { - final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (waypointId == null) { showToast(res.getString(R.string.info_storing_static_maps)); RxUtils.waitForCompletion(StaticMapsProvider.storeCacheStaticMap(cache)); 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..1540dbd 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -3,7 +3,9 @@ package cgeo.geocaching.activity; import butterknife.ButterKnife; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.Geocache; import cgeo.geocaching.R; +import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.ClipboardUtils; @@ -13,6 +15,7 @@ import cgeo.geocaching.utils.TranslationUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import rx.Subscription; import rx.subscriptions.Subscriptions; @@ -96,9 +99,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 +219,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); } } @@ -236,4 +239,22 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs }, this); } + + protected void setCacheTitleBar(@Nullable final String geocode, @Nullable final String name, @Nullable final CacheType type) { + if (StringUtils.isNotBlank(name)) { + setTitle(StringUtils.isNotBlank(geocode) ? name + " (" + geocode + ")" : name); + } else { + setTitle(StringUtils.isNotBlank(geocode) ? geocode : res.getString(R.string.cache)); + } + if (type != null) { + getSupportActionBar().setIcon(getResources().getDrawable(type.markerId)); + } else { + getSupportActionBar().setIcon(android.R.color.transparent); + } + } + + protected void setCacheTitleBar(final @NonNull Geocache cache) { + setCacheTitleBar(cache.getGeocode(), cache.getName(), cache.getType()); + } + } 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..c79dfad 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java @@ -20,19 +20,18 @@ 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.startActivityPoint(activity, coords, getString(R.string.navigation_direct_navigation)); } @Override - public void navigate(Activity activity, Waypoint waypoint) { - CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null, - waypoint.getWaypointType().getL10n()); + public void navigate(final Activity activity, final Waypoint waypoint) { + CompassActivity.startActivityWaypoint(activity, waypoint); } @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/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index c00723d..4d98e42 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -27,6 +27,10 @@ import java.util.List; public final class NavigationAppFactory extends AbstractAppFactory { + private NavigationAppFactory() { + // utility class + } + public enum NavigationAppsEnum { /** The internal compass activity */ COMPASS(new CompassApp(), 0, R.string.pref_navigation_menu_compass), 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/apps/cachelist/CacheListAppFactory.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java index 5886168..b6706a0 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java @@ -17,6 +17,11 @@ import java.util.ArrayList; import java.util.List; public final class CacheListAppFactory extends AbstractAppFactory { + + private CacheListAppFactory() { + // utility class + } + private static class LazyHolder { public static final CacheListApp[] apps = { new InternalCacheListMap(), @@ -71,10 +76,10 @@ public final class CacheListAppFactory extends AbstractAppFactory { } if (app != null) { try { - boolean result = app.invoke(caches, activity, search); + final boolean result = app.invoke(caches, activity, search); ActivityMixin.invalidateOptionsMenu(activity); return result; - } catch (Exception e) { + } catch (final Exception e) { Log.e("CacheListAppFactory.onMenuItemSelected", e); } } diff --git a/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java index ba177f0..c6c7709 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java +++ b/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java @@ -26,10 +26,10 @@ public class MapsWithMeCacheListApp extends AbstractApp implements CacheListApp } @Override - public boolean invoke(List<Geocache> caches, Activity activity, SearchResult search) { + public boolean invoke(final List<Geocache> caches, final Activity activity, final SearchResult search) { final MWMPoint[] points = new MWMPoint[caches.size()]; for (int i = 0; i < points.length; i++) { - Geocache geocache = caches.get(i); + final Geocache geocache = caches.get(i); points[i] = new MWMPoint(geocache.getCoords().getLatitude(), geocache.getCoords().getLongitude(), geocache.getName(), geocache.getGeocode()); } MapsWithMeApi.showPointsOnMap(activity, null, getPendingIntent(activity), points); @@ -44,22 +44,20 @@ public class MapsWithMeCacheListApp extends AbstractApp implements CacheListApp /** * get cache code from a PendingIntent after an invocation of MapsWithMe - * + * * @return */ @Nullable public static String getCacheFromMapsWithMe(final Context context, final Intent intent) { final MWMResponse mwmResponse = MWMResponse.extractFromIntent(context, intent); - if (mwmResponse != null) { - final MWMPoint point = mwmResponse.getPoint(); - if (point != null) { - return point.getId(); - } + final MWMPoint point = mwmResponse.getPoint(); + if (point != null) { + return point.getId(); } return null; } - private static PendingIntent getPendingIntent(Context context) { + private static PendingIntent getPendingIntent(final Context context) { final Intent intent = new Intent(context, CacheDetailActivity.class); return PendingIntent.getActivity(context, 0, intent, 0); } 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/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index 1d190e4..93d7098 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -48,7 +48,7 @@ public enum CacheType { private final int stringId; public final int markerId; - CacheType(String id, String pattern, String guid, int stringId, int markerId) { + CacheType(final String id, final String pattern, final String guid, final int stringId, final int markerId) { this.id = id; this.pattern = pattern; this.guid = guid; @@ -63,7 +63,7 @@ public enum CacheType { final HashMap<String, CacheType> mappingId = new HashMap<>(); final HashMap<String, CacheType> mappingPattern = new HashMap<>(); final HashMap<String, CacheType> mappingGuid = new HashMap<>(); - for (CacheType ct : values()) { + for (final CacheType ct : values()) { mappingId.put(ct.id, ct); mappingPattern.put(ct.pattern.toLowerCase(Locale.US), ct); mappingGuid.put(ct.guid, ct); @@ -72,6 +72,8 @@ public enum CacheType { mappingPattern.put("Mystery Cache".toLowerCase(Locale.US), MYSTERY); // This pattern briefly appeared on gc.com in 2014-08. mappingPattern.put("Traditional Geocache".toLowerCase(Locale.US), TRADITIONAL); + // map lab caches to the virtual type for the time being + mappingPattern.put("Lab Cache".toLowerCase(Locale.US), VIRTUAL); FIND_BY_ID = Collections.unmodifiableMap(mappingId); FIND_BY_PATTERN = Collections.unmodifiableMap(mappingPattern); @@ -121,7 +123,7 @@ public enum CacheType { * @param cache * @return true if this is the ALL type or if this type equals the type of the cache. */ - public boolean contains(ICache cache) { + public boolean contains(final ICache cache) { if (cache == null) { return false; } 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..aa1a539 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,8 +570,13 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { - cache.setType(CacheType.getByPattern(validate(body))); + public void end(final String bodyIn) { + String body = validate(bodyIn); + // lab caches wrongly contain a prefix in the type + if (body.startsWith("Geocache|")) { + body = StringUtils.substringAfter(body, "Geocache|").trim(); + } + cache.setType(CacheType.getByPattern(body)); } }); @@ -577,7 +584,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 +604,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 +624,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 +637,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 +650,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 +663,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 +679,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 +687,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 +695,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 +710,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 +740,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 +754,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 +772,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 +788,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 +801,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 +811,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 +820,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 +828,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 +847,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 +861,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 +869,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 +908,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 +918,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 +931,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 +944,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..b27693b 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; @@ -484,7 +459,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { mapView.repaintRequired(null); - setZoom(Settings.getMapZoom()); + setZoom(Settings.getMapZoom(mapMode)); mapView.getMapController().setCenter(Settings.getMapCenter()); if (null == mapStateIntent) { @@ -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 @@ -911,13 +886,13 @@ public class CGeoMap extends AbstractMap implements ViewFactory { } private void savePrefs() { - Settings.setMapZoom(mapView.getMapZoomLevel()); + Settings.setMapZoom(mapMode, mapView.getMapZoomLevel()); Settings.setMapCenter(mapView.getMapViewCenter()); } // 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 aaaf64a..48de880 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -12,6 +12,7 @@ import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.maps.CGeoMap.MapMode; import cgeo.geocaching.maps.MapProviderFactory; import cgeo.geocaching.maps.google.v1.GoogleMapProvider; import cgeo.geocaching.maps.interfaces.GeoPointImpl; @@ -109,75 +110,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()); @@ -409,6 +424,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 @@ -599,14 +622,51 @@ public class Settings { putBoolean(R.string.pref_maptrail, showTrail); } - public static int getMapZoom() { + /** + * Get last used zoom of the internal map. Differentiate between two use cases for a map of multiple caches (e.g. + * live map) and the map of a single cache (which is often zoomed in more deep). + * + * @param mapMode + * @return + */ + public static int getMapZoom(final MapMode mapMode) { + if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) { + return getCacheZoom(); + } + return getMapZoom(); + } + + public static void setMapZoom(final MapMode mapMode, final int zoomLevel) { + if (mapMode == MapMode.SINGLE || mapMode == MapMode.COORDS) { + setCacheZoom(zoomLevel); + } + else { + setMapZoom(zoomLevel); + } + } + + /** + * @return zoom used for the (live) map + */ + private static int getMapZoom() { return getInt(R.string.pref_lastmapzoom, 14); } - public static void setMapZoom(final int mapZoomLevel) { + private static void setMapZoom(final int mapZoomLevel) { putInt(R.string.pref_lastmapzoom, mapZoomLevel); } + /** + * @return zoom used for the map of a single cache + */ + private static int getCacheZoom() { + return getInt(R.string.pref_cache_zoom, 14); + } + + private static void setCacheZoom(final int zoomLevel) { + putInt(R.string.pref_cache_zoom, zoomLevel); + } + public static GeoPointImpl getMapCenter() { return getMapProvider().getMapItemFactory() .getGeoPointBase(new Geopoint(getInt(R.string.pref_lastmaplat, 0) / 1e6, @@ -627,7 +687,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; } } @@ -850,14 +910,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 541ce48..3da5736 100644 --- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java +++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java @@ -3,6 +3,7 @@ package cgeo.geocaching.sorting; import cgeo.geocaching.Geocache; import cgeo.geocaching.geopoint.Geopoint; +import java.util.ArrayList; import java.util.List; /** @@ -15,9 +16,20 @@ public class DistanceComparator extends AbstractCacheComparator { final private List<Geocache> list; private boolean cachedDistances; - public DistanceComparator(final Geopoint coords, List<Geocache> list) { + final static public DistanceComparator singleton = new DistanceComparator(); + + public DistanceComparator() { + // This constructor should not be used as a comparator as distances will not be updated. + // It is needed in order to really know we are sorting by Distances in the sort menu. + // If you need it for sorting, please use the second constructor. + coords = null; + list = new ArrayList<>(); + } + + 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 e9e65a0..e6db330 100644 --- a/main/src/cgeo/geocaching/sorting/SortActionProvider.java +++ b/main/src/cgeo/geocaching/sorting/SortActionProvider.java @@ -62,7 +62,7 @@ public class SortActionProvider extends ActionProvider implements OnMenuItemClic } private void registerComparators() { - register(R.string.caches_sort_distance, null); + register(R.string.caches_sort_distance, DistanceComparator.class); register(R.string.caches_sort_date_hidden, DateComparator.class); register(R.string.caches_sort_difficulty, DifficultyComparator.class); register(R.string.caches_sort_finds, FindsComparator.class); @@ -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 b879e54..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; @@ -179,6 +177,9 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { if (cacheComparator == null && eventsOnly) { return EventDateComparator.singleton; } + if (cacheComparator == null) { + return DistanceComparator.singleton; + } return cacheComparator; } 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/LoggingUI.java b/main/src/cgeo/geocaching/ui/LoggingUI.java index 8454474..e9656e8 100644 --- a/main/src/cgeo/geocaching/ui/LoggingUI.java +++ b/main/src/cgeo/geocaching/ui/LoggingUI.java @@ -17,7 +17,12 @@ import android.widget.ArrayAdapter; import java.util.ArrayList; import java.util.List; -public class LoggingUI extends AbstractUIFactory { +public final class LoggingUI extends AbstractUIFactory { + + private LoggingUI() { + // utility class + } + public static class LogTypeEntry { private final LogType logType; private final SpecialLogType specialLogType; @@ -79,7 +84,7 @@ public class LoggingUI extends AbstractUIFactory { final List<LogType> logTypes = cache.getPossibleLogTypes(); final ArrayList<LogTypeEntry> list = new ArrayList<>(); - for (LogType logType : logTypes) { + for (final LogType logType : logTypes) { list.add(new LogTypeEntry(logType, null, logType == currentLogType)); } if (cache.isLogOffline()) { @@ -94,7 +99,7 @@ public class LoggingUI extends AbstractUIFactory { builder.setAdapter(adapter, new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialog, int item) { + public void onClick(final DialogInterface dialog, final int item) { final LogTypeEntry logTypeEntry = adapter.getItem(item); if (logTypeEntry.logType == null) { switch (logTypeEntry.specialLogType) { @@ -116,7 +121,7 @@ public class LoggingUI extends AbstractUIFactory { } - public static void onPrepareOptionsMenu(Menu menu, Geocache cache) { + public static void onPrepareOptionsMenu(final Menu menu, final Geocache cache) { if (cache == null) { return; } @@ -127,7 +132,7 @@ public class LoggingUI extends AbstractUIFactory { itemOffline.setVisible(cache.supportsLogging() && Settings.getLogOffline()); } - public static void addMenuItems(Activity activity, Menu menu, Geocache cache) { + public static void addMenuItems(final Activity activity, final Menu menu, final Geocache cache) { activity.getMenuInflater().inflate(R.menu.logging_ui, menu); onPrepareOptionsMenu(menu, cache); } 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(); } } |
