diff options
Diffstat (limited to 'main/src')
33 files changed, 438 insertions, 295 deletions
diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index a66d181..11fe672 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -56,13 +56,12 @@ 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 rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; -import rx.Scheduler.Inner; import rx.Subscriber; import rx.android.observables.AndroidObservable; +import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; @@ -300,9 +299,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final String realGeocode = geocode; final String realGuid = guid; - Schedulers.io().schedule(new Action1<Inner>() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { search = Geocache.searchByGeocode(realGeocode, StringUtils.isBlank(realGeocode) ? realGuid : null, 0, false, loadCacheHandler); loadCacheHandler.sendMessage(Message.obtain()); } @@ -681,7 +680,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc DETAILS(R.string.detail), DESCRIPTION(R.string.cache_description), LOGS(R.string.cache_logs), - LOGSFRIENDS(R.string.cache_logsfriends), + LOGSFRIENDS(R.string.cache_logs_friends_and_own), WAYPOINTS(R.string.cache_waypoints), INVENTORY(R.string.cache_inventory), IMAGES(R.string.cache_images); @@ -2227,9 +2226,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc protected void storeCache(final int listId, final StoreCacheHandler storeCacheHandler) { progress.show(this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); - Schedulers.io().schedule(new Action1<Inner>() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { cache.store(listId, storeCacheHandler); } }); diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index 0aec119..5a385a2 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -574,7 +574,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA setMenuItemLabel(menu, R.id.menu_remove_from_history, R.string.cache_remove_from_history, R.string.cache_clear_history); setMenuItemLabel(menu, R.id.menu_export, R.string.export, R.string.export); - menu.findItem(R.id.menu_import_android).setVisible(Compatibility.isStorageAccessFrameworkAvailable()); + menu.findItem(R.id.menu_import_android).setVisible(Compatibility.isStorageAccessFrameworkAvailable() && isOffline); } catch (final RuntimeException e) { Log.e("CacheListActivity.onPrepareOptionsMenu", e); } @@ -1323,6 +1323,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA listId = list.id; title = list.title; + type = CacheListType.OFFLINE; Settings.saveLastList(listId); diff --git a/main/src/cgeo/geocaching/CachePopup.java b/main/src/cgeo/geocaching/CachePopup.java index 543be22..f91d275 100644 --- a/main/src/cgeo/geocaching/CachePopup.java +++ b/main/src/cgeo/geocaching/CachePopup.java @@ -11,7 +11,7 @@ import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; -import rx.Scheduler.Inner; +import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; @@ -126,9 +126,9 @@ public class CachePopup extends AbstractPopupActivity { protected void storeCache(final int listId) { final StoreCacheHandler storeCacheHandler = new StoreCacheHandler(R.string.cache_dialog_offline_save_message); progress.show(CachePopup.this, res.getString(R.string.cache_dialog_offline_save_title), res.getString(R.string.cache_dialog_offline_save_message), true, storeCacheHandler.cancelMessage()); - Schedulers.io().schedule(new Action1<Inner>() { + Schedulers.io().createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { cache.store(listId, storeCacheHandler); invalidateOptionsMenuCompatible(); } diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index 09aee93..d503969 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -11,6 +11,9 @@ import rx.observables.ConnectableObservable; import android.app.Application; +import java.io.IOException; +import java.lang.Thread.UncaughtExceptionHandler; + public class CgeoApplication extends Application { private boolean forceRelog = false; // c:geo needs to log into cache providers @@ -22,6 +25,34 @@ public class CgeoApplication extends Application { private volatile IGeoData currentGeo = null; private volatile float currentDirection = 0.0f; + private static final UncaughtExceptionHandler defaultHandler; + + static { + defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); + + Thread.setDefaultUncaughtExceptionHandler(new UncaughtExceptionHandler() { + + @Override + public void uncaughtException(Thread thread, Throwable ex) { + Log.e("UncaughtException", ex); + Throwable exx = ex; + while (exx.getCause() != null) { + exx = exx.getCause(); + } + if (exx.getClass().equals(OutOfMemoryError.class)) + { + try { + Log.e("OutOfMemory"); + android.os.Debug.dumpHprofData("/sdcard/dump.hprof"); + } catch (IOException e) { + Log.e("Error writing dump", e); + } + } + defaultHandler.uncaughtException(thread, ex); + } + }); + } + public CgeoApplication() { setInstance(this); } diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java index b6ea4f6..c18b6f4 100644 --- a/main/src/cgeo/geocaching/CreateShortcutActivity.java +++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java @@ -27,7 +27,7 @@ public class CreateShortcutActivity extends AbstractActivity { @Override public void call(final Integer listId) { - final Intent shortcut = createShortcut(listId.intValue()); + final Intent shortcut = createShortcut(listId); setResult(RESULT_OK, shortcut); // finish activity to return the shortcut diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index 32a4b64..350bc6b 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -1429,17 +1429,13 @@ public class DataStore { } } - public static void saveLogsWithoutTransaction(final String geocode, final List<LogEntry> logs) { + public static void saveLogsWithoutTransaction(final String geocode, final Iterable<LogEntry> logs) { // TODO delete logimages referring these logs database.delete(dbTableLogs, "geocode = ?", new String[]{geocode}); - if (logs.isEmpty()) { - return; - } - - SQLiteStatement insertLog = PreparedStatements.getInsertLog(); + final SQLiteStatement insertLog = PreparedStatements.getInsertLog(); final long timestamp = System.currentTimeMillis(); - for (LogEntry log : logs) { + for (final LogEntry log : logs) { insertLog.bindString(1, geocode); insertLog.bindLong(2, timestamp); insertLog.bindLong(3, log.type.id); @@ -1448,10 +1444,10 @@ public class DataStore { insertLog.bindLong(6, log.date); insertLog.bindLong(7, log.found); insertLog.bindLong(8, log.friend ? 1 : 0); - long logId = insertLog.executeInsert(); + final long logId = insertLog.executeInsert(); if (log.hasLogImages()) { - SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); - for (Image img : log.getLogImages()) { + final SQLiteStatement insertImage = PreparedStatements.getInsertLogImage(); + for (final Image img : log.getLogImages()) { insertImage.bindLong(1, logId); insertImage.bindString(2, img.getTitle()); insertImage.bindString(3, img.getUrl()); @@ -2895,6 +2891,7 @@ public class DataStore { } if (excludeDisabled) { where.append(" and ").append(dbTableCaches).append(".disabled == 0"); + where.append(" and ").append(dbTableCaches).append(".archived == 0"); } if (type != CacheType.ALL) { where.append(" and ").append(dbTableCaches).append(".type == '").append(type.id).append('\''); @@ -3164,7 +3161,7 @@ public class DataStore { } public static String[] getSuggestionsOwnerName(String input) { - return getSuggestions(dbTableCaches, "owner", input); + return getSuggestions(dbTableCaches, "owner_real", input); } public static String[] getSuggestionsTrackableCode(String input) { diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index 19c15fd..8836115 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -35,7 +35,6 @@ import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.UncertainProperty; import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; - import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.ListUtils; import org.apache.commons.collections4.Predicate; @@ -44,11 +43,9 @@ import org.apache.commons.lang3.ObjectUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; - import rx.Scheduler; -import rx.Scheduler.Inner; import rx.Subscription; -import rx.functions.Action1; +import rx.functions.Action0; import android.app.Activity; import android.content.Intent; @@ -1438,9 +1435,9 @@ public class Geocache implements ICache, IWaypoint { } public Subscription drop(final Handler handler, final Scheduler scheduler) { - return scheduler.schedule(new Action1<Inner>() { + return scheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { try { dropSynchronous(); handler.sendMessage(Message.obtain()); @@ -1499,9 +1496,9 @@ public class Geocache implements ICache, IWaypoint { } public Subscription refresh(final int newListId, final CancellableHandler handler, final Scheduler scheduler) { - return scheduler.schedule(new Action1<Inner>() { + return scheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { refreshSynchronous(newListId, handler); handler.sendEmptyMessage(CancellableHandler.DONE); } diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index 2b05263..cccd154 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -516,7 +516,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) { + if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED || typeSelected == LogType.WEBCAM_PHOTO_TAKEN) { cache.setFound(true); cache.setVisitedDate(new Date().getTime()); } diff --git a/main/src/cgeo/geocaching/SearchActivity.java b/main/src/cgeo/geocaching/SearchActivity.java index 2a37e27..d3bb2d8 100644 --- a/main/src/cgeo/geocaching/SearchActivity.java +++ b/main/src/cgeo/geocaching/SearchActivity.java @@ -174,8 +174,20 @@ public class SearchActivity extends AbstractActivity { } private void init() { - buttonLatitude.setOnClickListener(new FindByCoordsAction()); - buttonLongitude.setOnClickListener(new FindByCoordsAction()); + buttonLatitude.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + updateCoordinates(); + } + }); + buttonLongitude.setOnClickListener(new OnClickListener() { + + @Override + public void onClick(View v) { + updateCoordinates(); + } + }); buttonSearchCoords.setOnClickListener(new View.OnClickListener() { @@ -277,21 +289,17 @@ public class SearchActivity extends AbstractActivity { } } - private class FindByCoordsAction implements OnClickListener { - - @Override - public void onClick(final View arg0) { - final CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); - coordsDialog.setCancelable(true); - coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { - @Override - public void update(final Geopoint gp) { - buttonLatitude.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); - buttonLongitude.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); - } - }); - coordsDialog.show(); - } + private void updateCoordinates() { + final CoordinatesInputDialog coordsDialog = new CoordinatesInputDialog(SearchActivity.this, null, null, app.currentGeo()); + coordsDialog.setCancelable(true); + coordsDialog.setOnCoordinateUpdate(new CoordinatesInputDialog.CoordinateUpdate() { + @Override + public void update(final Geopoint gp) { + buttonLatitude.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); + buttonLongitude.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); + } + }); + coordsDialog.show(); } private void findByCoordsFn() { diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 12a2522..1916afd 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -11,7 +11,9 @@ import cgeo.geocaching.gcvote.GCVote; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; + import rx.Observable; import rx.functions.Func1; import rx.functions.Func2; @@ -60,6 +62,14 @@ public class SearchResult implements Parcelable { } /** + * Build a new empty search result with an error status. + */ + public SearchResult(final StatusCode statusCode) { + this(); + error = statusCode; + } + + /** * Copy a search result, for example to apply different filters on it. * * @param searchResult the original search result, which cannot be null @@ -155,6 +165,7 @@ public class SearchResult implements Parcelable { return 0; } + @NonNull public Set<String> getGeocodes() { return Collections.unmodifiableSet(geocodes); } @@ -218,7 +229,7 @@ public class SearchResult implements Parcelable { int excluded = 0; for (Geocache cache : caches) { // Is there any reason to exclude the cache from the list? - final boolean excludeCache = (excludeDisabled && cache.isDisabled()) || + final boolean excludeCache = (excludeDisabled && (cache.isDisabled() || cache.isArchived())) || (excludeMine && (cache.isOwner() || cache.isFound())) || (!cacheType.contains(cache)); if (excludeCache) { diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index c617012..b2cdf17 100644 --- a/main/src/cgeo/geocaching/SelectMapfileActivity.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -115,8 +115,7 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio } if (requestCode == REQUEST_DIRECTORY) { - final String directory = new File(data.getData().getPath()).getAbsolutePath(); - mapFile = directory; + mapFile = new File(data.getData().getPath()).getAbsolutePath(); close(); } } diff --git a/main/src/cgeo/geocaching/StaticMapsProvider.java b/main/src/cgeo/geocaching/StaticMapsProvider.java index eaab159..ae61112 100644 --- a/main/src/cgeo/geocaching/StaticMapsProvider.java +++ b/main/src/cgeo/geocaching/StaticMapsProvider.java @@ -11,6 +11,7 @@ import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; @@ -213,7 +214,7 @@ public final class StaticMapsProvider { url.append("marker_cache_").append(cache.getType().id); if (cache.isFound()) { url.append("_found"); - } else if (cache.isDisabled()) { + } else if (cache.isDisabled() || cache.isArchived()) { url.append("_disabled"); } url.append(".png"); diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 5422c11..4d3580c 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -47,6 +47,14 @@ import org.json.JSONArray; import org.json.JSONException; import org.json.JSONObject; +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.functions.Action1; +import rx.functions.Func0; +import rx.functions.Func2; +import rx.schedulers.Schedulers; + import android.net.Uri; import android.text.Html; @@ -335,32 +343,50 @@ public abstract class GCParser { } static SearchResult parseCache(final String page, final CancellableHandler handler) { - final SearchResult searchResult = parseCacheFromText(page, handler); + final ImmutablePair<StatusCode, Geocache> parsed = parseCacheFromText(page, handler); // attention: parseCacheFromText already stores implicitly through searchResult.addCache - if (searchResult != null && !searchResult.getGeocodes().isEmpty()) { - final Geocache cache = searchResult.getFirstCacheFromResult(LoadFlags.LOAD_CACHE_OR_DB); - if (cache == null) { - return null; - } - getExtraOnlineInfo(cache, page, handler); - // too late: it is already stored through parseCacheFromText - cache.setDetailedUpdatedNow(); - if (CancellableHandler.isCancelled(handler)) { - return null; - } + if (parsed.left != StatusCode.NO_ERROR) { + return new SearchResult(parsed.left); + } + + final Geocache cache = parsed.right; + getExtraOnlineInfo(cache, page, handler); + // too late: it is already stored through parseCacheFromText + cache.setDetailedUpdatedNow(); + if (CancellableHandler.isCancelled(handler)) { + return null; + } - // save full detailed caches - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + // save full detailed caches + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_cache); + DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); - // update progress message so user knows we're still working. This is more of a place holder than - // actual indication of what the program is doing - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_render); + // update progress message so user knows we're still working. This is more of a place holder than + // actual indication of what the program is doing + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_render); + return new SearchResult(cache); + } + + static SearchResult parseAndSaveCacheFromText(final String page, @Nullable final CancellableHandler handler) { + final ImmutablePair<StatusCode, Geocache> parsed = parseCacheFromText(page, handler); + final SearchResult result = new SearchResult(parsed.left); + if (parsed.left == StatusCode.NO_ERROR) { + result.addAndPutInCache(Collections.singletonList(parsed.right)); + DataStore.saveLogsWithoutTransaction(parsed.right.getGeocode(), getLogsFromDetails(page).toBlockingObservable().toIterable()); } - return searchResult; + return result; } - static SearchResult parseCacheFromText(final String pageIn, final CancellableHandler handler) { + /** + * 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}. + */ + 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)) { @@ -368,22 +394,17 @@ public abstract class GCParser { return null; } - final SearchResult searchResult = new SearchResult(); - if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { - searchResult.setError(StatusCode.UNPUBLISHED_CACHE); - return searchResult; + return ImmutablePair.of(StatusCode.UNPUBLISHED_CACHE, null); } if (pageIn.contains(GCConstants.STRING_PREMIUMONLY_1) || pageIn.contains(GCConstants.STRING_PREMIUMONLY_2)) { - searchResult.setError(StatusCode.PREMIUM_ONLY); - return searchResult; + return ImmutablePair.of(StatusCode.PREMIUM_ONLY, null); } final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString(); if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) { - searchResult.setError(StatusCode.UNKNOWN_ERROR); - return searchResult; + return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); } // first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields @@ -726,14 +747,11 @@ public abstract class GCParser { // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { - searchResult.setError(StatusCode.UNKNOWN_ERROR); - return searchResult; + return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); } cache.setDetailedUpdatedNow(); - searchResult.addAndPutInCache(Collections.singletonList(cache)); - DataStore.saveLogsWithoutTransaction(cache.getGeocode(), getLogsFromDetails(page, false)); - return searchResult; + return ImmutablePair.of(StatusCode.NO_ERROR, cache); } private static String getNumberString(final String numberWithPunctuation) { @@ -1622,116 +1640,142 @@ public abstract class GCParser { * * @param page * the text of the details page - * @param friends - * return friends logs only (will require a network request) - * @return a list of log entries or <code>null</code> if the logs could not be retrieved + * @return a list of log entries which will be empty if the logs could not be retrieved * */ - @Nullable - private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) { - String rawResponse; + @NonNull + private static Observable<LogEntry> getLogsFromDetails(final String page) { + // extract embedded JSON data from page + return parseLogs(false, TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, "")); + } - if (friends) { - final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); - if (!userTokenMatcher.find()) { - Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); - return null; - } + private enum SpecialLogs { + FRIENDS("sf"), + OWN("sp"); - final String userToken = userTokenMatcher.group(1); - final Parameters params = new Parameters( - "tkn", userToken, - "idx", "1", - "num", String.valueOf(GCConstants.NUMBER_OF_LOGS), - "decrypt", "true", - // "sp", Boolean.toString(personal), // personal logs - "sf", Boolean.toString(friends)); + final String paramName; - final HttpResponse response = Network.getRequest("http://www.geocaching.com/seek/geocache.logbook", params); - if (response == null) { - Log.e("GCParser.loadLogsFromDetails: cannot log logs, response is null"); - return null; - } - final int statusCode = response.getStatusLine().getStatusCode(); - if (statusCode != 200) { - Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information"); - return null; - } - rawResponse = Network.getResponseData(response); - if (rawResponse == null) { - Log.e("GCParser.loadLogsFromDetails: unable to read whole response"); - return null; - } - } else { - // extract embedded JSON data from page - rawResponse = TextUtils.getMatch(page, GCConstants.PATTERN_LOGBOOK, ""); + SpecialLogs(String paramName) { + this.paramName = paramName; } - return parseLogs(friends, rawResponse); + private String getParamName() { + return this.paramName; + } } - private static List<LogEntry> parseLogs(final boolean friends, String rawResponse) { - final List<LogEntry> logs = new ArrayList<LogEntry>(); - - // for non logged in users the log book is not shown - if (StringUtils.isBlank(rawResponse)) { - return logs; - } + /** + * Extract special logs (friends, own) through seperate request. + * + * @param page + * The page to extrat userToken from + * @param logType + * The logType to request + * @return Observable<LogEntry> The logs + */ + private static Observable<LogEntry> getSpecialLogs(final String page, final SpecialLogs logType) { + return Observable.defer(new Func0<Observable<? extends LogEntry>>() { + @Override + public Observable<? extends LogEntry> call() { + final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); + if (!userTokenMatcher.find()) { + Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); + return Observable.empty(); + } - try { - final JSONObject resp = new JSONObject(rawResponse); - if (!resp.getString("status").equals("success")) { - Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status")); - return null; + final String userToken = userTokenMatcher.group(1); + final Parameters params = new Parameters( + "tkn", userToken, + "idx", "1", + "num", String.valueOf(GCConstants.NUMBER_OF_LOGS), + logType.getParamName(), Boolean.toString(Boolean.TRUE), + "decrypt", "true"); + final HttpResponse response = Network.getRequest("http://www.geocaching.com/seek/geocache.logbook", params); + if (response == null) { + Log.e("GCParser.loadLogsFromDetails: cannot log logs, response is null"); + return Observable.empty(); + } + final int statusCode = response.getStatusLine().getStatusCode(); + if (statusCode != 200) { + Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information"); + return Observable.empty(); + } + String rawResponse = Network.getResponseData(response); + if (rawResponse == null) { + Log.e("GCParser.loadLogsFromDetails: unable to read whole response"); + return Observable.empty(); + } + return parseLogs(true, rawResponse); } + }).subscribeOn(Schedulers.io()); + } - final JSONArray data = resp.getJSONArray("data"); + private static Observable<LogEntry> parseLogs(final boolean markAsFriendsLog, final String rawResponse) { + return Observable.create(new OnSubscribe<LogEntry>() { + @Override + public void call(final Subscriber<? super LogEntry> subscriber) { + // for non logged in users the log book is not shown + if (StringUtils.isBlank(rawResponse)) { + subscriber.onCompleted(); + return; + } - for (int index = 0; index < data.length(); index++) { - final JSONObject entry = data.getJSONObject(index); + try { + final JSONObject resp = new JSONObject(rawResponse); + if (!resp.getString("status").equals("success")) { + Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status")); + subscriber.onCompleted(); + return; + } - // FIXME: use the "LogType" field instead of the "LogTypeImage" one. - final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); - final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); + final JSONArray data = resp.getJSONArray("data"); - long date = 0; - try { - date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (final ParseException e) { - Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); - } + for (int index = 0; index < data.length(); index++) { + final JSONObject entry = data.getJSONObject(index); - // 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 LogEntry logDone = new LogEntry( - TextUtils.removeControlCharacters(entry.getString("UserName")), - date, - LogType.getByIconName(logIconName), - logText); - logDone.found = entry.getInt("GeocacheFindCount"); - logDone.friend = friends; - - 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 Image logImage = new Image(url, title); - logDone.addLogImage(logImage); - } + // FIXME: use the "LogType" field instead of the "LogTypeImage" one. + final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); + final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); - logs.add(logDone); - } - } catch (final JSONException e) { - // failed to parse logs - Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); - } + long date = 0; + try { + date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); + } catch (final ParseException e) { + Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); + } - return logs; + // 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 LogEntry logDone = new LogEntry( + TextUtils.removeControlCharacters(entry.getString("UserName")), + date, + LogType.getByIconName(logIconName), + logText); + logDone.found = entry.getInt("GeocacheFindCount"); + 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 Image logImage = new Image(url, title); + logDone.addLogImage(logImage); + } + + subscriber.onNext(logDone); + } + } catch (final JSONException e) { + // failed to parse logs + Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); + } + subscriber.onCompleted(); + } + }); } @NonNull @@ -1823,32 +1867,39 @@ public abstract class GCParser { } private static void getExtraOnlineInfo(final Geocache cache, final String page, final CancellableHandler handler) { + // This method starts the page parsing for logs in the background, as well as retrieve the friends and own logs + // if requested. It merges them and stores them in the background, while the rating is retrieved if needed and + // stored. Then we wait for the log merging and saving to be completed before returning. if (CancellableHandler.isCancelled(handler)) { return; } - //cache.setLogs(loadLogsFromDetails(page, cache, false)); + final Observable<LogEntry> logs = getLogsFromDetails(page).subscribeOn(Schedulers.computation()); + Observable<LogEntry> specialLogs; if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - final List<LogEntry> friendLogs = getLogsFromDetails(page, true); - if (friendLogs != null && !friendLogs.isEmpty()) { - // create new list, as the existing log list is immutable - ArrayList<LogEntry> mergedLogs = new ArrayList<LogEntry>(cache.getLogs()); - for (final LogEntry log : friendLogs) { - if (mergedLogs.contains(log)) { - mergedLogs.get(mergedLogs.indexOf(log)).friend = true; - } else { - mergedLogs.add(log); + specialLogs = Observable.merge(getSpecialLogs(page, SpecialLogs.FRIENDS), + getSpecialLogs(page, SpecialLogs.OWN)); + } else { + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); + specialLogs = Observable.empty(); + } + final Observable<List<LogEntry>> mergedLogs = Observable.zip(logs.toList(), specialLogs.toList(), + new Func2<List<LogEntry>, List<LogEntry>, List<LogEntry>>() { + @Override + public List<LogEntry> call(final List<LogEntry> logEntries, final List<LogEntry> specialLogEntries) { + mergeFriendsLogs(logEntries, specialLogEntries); + return logEntries; } - } - DataStore.saveLogsWithoutTransaction(cache.getGeocode(), mergedLogs); - } - } - - if (Settings.isRatingWanted()) { - if (CancellableHandler.isCancelled(handler)) { - return; - } + }).cache(); + mergedLogs.subscribe(new Action1<List<LogEntry>>() { + @Override + public void call(final List<LogEntry> logEntries) { + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logEntries); + } + }); + + if (Settings.isRatingWanted() && !CancellableHandler.isCancelled(handler)) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_gcvote); final GCVoteRating rating = GCVote.getRating(cache.getGuid(), cache.getGeocode()); if (rating != null) { @@ -1857,6 +1908,28 @@ public abstract class GCParser { cache.setMyVote(rating.getMyVote()); } } + + // Wait for completion of logs parsing, retrieving and merging + mergedLogs.toBlockingObservable().last(); + } + + /** + * Merge log entries and mark them as friends logs (personal and friends) to identify + * them on friends/personal logs tab. + * + * @param mergedLogs + * the list to merge logs with + * @param logsToMerge + * the list of logs to merge + */ + private static void mergeFriendsLogs(final List<LogEntry> mergedLogs, final Iterable<LogEntry> logsToMerge) { + for (final LogEntry log : logsToMerge) { + if (mergedLogs.contains(log)) { + mergedLogs.get(mergedLogs.indexOf(log)).friend = true; + } else { + mergedLogs.add(log); + } + } } public static boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java index 049c633..bdcd78e 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiLiveConnector.java @@ -3,6 +3,7 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; +import cgeo.geocaching.ICache; import cgeo.geocaching.LogCacheActivity; import cgeo.geocaching.SearchResult; import cgeo.geocaching.connector.ILoggingManager; @@ -149,6 +150,11 @@ public class OCApiLiveConnector extends OCApiConnector implements ISearchByCente } @Override + public boolean isOwner(ICache cache) { + return StringUtils.equals(cache.getOwnerDisplayName(), getUserName()); + } + + @Override public String getUserName() { return userInfo.getName(); } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 3c93488..1294353 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -328,8 +328,11 @@ final class OkapiClient { parseCoreCache(response, cache); // not used: url - final JSONObject owner = response.getJSONObject(CACHE_OWNER); - cache.setOwnerDisplayName(parseUser(owner)); + final JSONObject ownerObject = response.getJSONObject(CACHE_OWNER); + final String owner = parseUser(ownerObject); + 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)); diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java index 2d7207a..5bfab28 100644 --- a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java +++ b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java @@ -24,22 +24,15 @@ class PopularityRatioFilter extends AbstractFilter { @Override public boolean accepts(final Geocache cache) { + final int finds = getFindsCount(cache); - int finds; - int favorites; - float ratio; - - finds = getFindsCount(cache); - - // prevent division by zero - if (finds == 0) { + if (finds == 0) { // Prevent division by zero return false; } - favorites = cache.getFavoritePoints(); - ratio = ((float) favorites / (float) finds) * 100.0f; - - return (ratio > minRatio) && (ratio <= maxRatio); + final int favorites = cache.getFavoritePoints(); + final float ratio = 100.0f * favorites / finds; + return ratio > minRatio && ratio <= maxRatio; } private static int getFindsCount(Geocache cache) { diff --git a/main/src/cgeo/geocaching/filter/StateFilter.java b/main/src/cgeo/geocaching/filter/StateFilter.java index f452259..fd14b69 100644 --- a/main/src/cgeo/geocaching/filter/StateFilter.java +++ b/main/src/cgeo/geocaching/filter/StateFilter.java @@ -32,6 +32,19 @@ abstract class StateFilter extends AbstractFilter { } + static class StateNotFoundFilter extends StateFilter { + + public StateNotFoundFilter() { + super(res.getString(R.string.cache_not_status_found)); + } + + @Override + public boolean accepts(final Geocache cache) { + return !cache.isFound(); + } + + } + static class StateArchivedFilter extends StateFilter { public StateArchivedFilter() { super(res.getString(R.string.cache_status_archived)); @@ -115,6 +128,7 @@ abstract class StateFilter extends AbstractFilter { public List<StateFilter> getFilters() { final List<StateFilter> filters = new ArrayList<StateFilter>(6); filters.add(new StateFoundFilter()); + filters.add(new StateNotFoundFilter()); filters.add(new StateArchivedFilter()); filters.add(new StateDisabledFilter()); filters.add(new StatePremiumFilter()); diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java index e557fc8..84e1163 100644 --- a/main/src/cgeo/geocaching/list/StoredList.java +++ b/main/src/cgeo/geocaching/list/StoredList.java @@ -16,6 +16,7 @@ import android.app.AlertDialog; import android.content.DialogInterface; import android.content.res.Resources; +import java.lang.ref.WeakReference; import java.text.Collator; import java.util.ArrayList; import java.util.Collections; @@ -58,12 +59,12 @@ public final class StoredList extends AbstractList { } public static class UserInterface { - private final Activity activity; + private final WeakReference<Activity> activityRef; private final CgeoApplication app; private final Resources res; - public UserInterface(final Activity activity) { - this.activity = activity; + public UserInterface(final @NonNull Activity activity) { + this.activityRef = new WeakReference<Activity>(activity); app = CgeoApplication.getInstance(); res = app.getResources(); } @@ -104,6 +105,7 @@ public final class StoredList extends AbstractList { final CharSequence[] items = new CharSequence[listsTitle.size()]; + final Activity activity = activityRef.get(); AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(res.getString(titleId)); builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { @@ -151,6 +153,10 @@ public final class StoredList extends AbstractList { @SuppressWarnings("unused") @Override public void call(final String listName) { + final Activity activity = activityRef.get(); + if (activity == null) { + return; + } final int newId = DataStore.createList(listName); new StoredList(newId, listName, 0); @@ -165,6 +171,10 @@ public final class StoredList extends AbstractList { } private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) { + final Activity activity = activityRef.get(); + if (activity == null) { + return; + } Dialogs.input(activity, dialogTitle, defaultValue, buttonTitle, new Action1<String>() { @Override diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 00aee36..b5d0b0e 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -43,9 +43,8 @@ import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.builder.HashCodeBuilder; import org.eclipse.jdt.annotation.NonNull; - -import rx.Scheduler; import rx.Subscription; +import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; import rx.subscriptions.CompositeSubscription; @@ -1007,9 +1006,9 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. */ private Subscription startLoadTimer() { - return Schedulers.newThread().schedulePeriodically(new Action1<Scheduler.Inner>() { + return Schedulers.newThread().createWorker().schedulePeriodically(new Action0() { @Override - public void call(Scheduler.Inner inner) { + public void call() { try { if (mapView != null) { // get current viewport @@ -1373,7 +1372,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto List<Geocache> removeList = new ArrayList<Geocache>(); for (Geocache cache : caches) { - if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled())) { + if ((excludeMine && cache.isFound()) || (excludeMine && cache.isOwner()) || (excludeDisabled && cache.isDisabled()) || (excludeDisabled && cache.isArchived())) { removeList.add(cache); } } diff --git a/main/src/cgeo/geocaching/maps/CachesOverlay.java b/main/src/cgeo/geocaching/maps/CachesOverlay.java index 0c7c296..8607c88 100644 --- a/main/src/cgeo/geocaching/maps/CachesOverlay.java +++ b/main/src/cgeo/geocaching/maps/CachesOverlay.java @@ -211,9 +211,9 @@ public class CachesOverlay extends AbstractItemizedOverlay { progress.show(context, context.getResources().getString(R.string.map_live), context.getResources().getString(R.string.cache_dialog_loading_details), true, null); - CachesOverlayItemImpl item = null; // prevent concurrent changes getOverlayImpl().lock(); + CachesOverlayItemImpl item = null; try { if (index < items.size()) { item = items.get(index); diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 9c55fe9..167559d 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -13,21 +13,20 @@ import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.io.IOUtils; 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.NonNull; import org.eclipse.jdt.annotation.Nullable; + import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Scheduler; -import rx.Scheduler.Inner; import rx.Subscriber; -import rx.functions.Action1; +import rx.functions.Action0; import rx.functions.Func0; import rx.functions.Func1; -import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; import rx.subscriptions.CompositeSubscription; @@ -44,6 +43,7 @@ import java.io.File; import java.io.FileInputStream; import java.io.FileNotFoundException; import java.util.Date; +import java.util.concurrent.Executor; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; @@ -94,8 +94,7 @@ public class HtmlImage implements Html.ImageGetter { final private PublishSubject<Observable<String>> loading = PublishSubject.create(); final Observable<String> waitForEnd = Observable.merge(loading).publish().refCount(); final CompositeSubscription subscription = new CompositeSubscription(waitForEnd.subscribe()); - final private Scheduler downloadScheduler = Schedulers.executor(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, - new LinkedBlockingQueue<Runnable>())); + final private Executor downloadExecutor = new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>()); public HtmlImage(final String geocode, final boolean returnErrorImage, final int listId, final boolean onlySave) { this.geocode = geocode; @@ -152,9 +151,9 @@ public class HtmlImage implements Html.ImageGetter { @Override public void call(final Subscriber<? super BitmapDrawable> subscriber) { subscription.add(subscriber); - subscriber.add(RxUtils.computationScheduler.schedule(new Action1<Inner>() { + subscriber.add(RxUtils.computationScheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk(); final BitmapDrawable bitmap = loaded.getLeft(); if (loaded.getRight()) { @@ -165,12 +164,11 @@ public class HtmlImage implements Html.ImageGetter { if (bitmap != null && !onlySave) { subscriber.onNext(bitmap); } - subscriber.add(downloadScheduler.schedule(new Action1<Inner>() { - @Override - public void call(final Inner inner) { + downloadExecutor.execute(new Runnable() { + @Override public void run() { downloadAndSave(subscriber); } - })); + }); } })); } @@ -205,9 +203,9 @@ public class HtmlImage implements Html.ImageGetter { if (onlySave) { subscriber.onCompleted(); } else { - RxUtils.computationScheduler.schedule(new Action1<Inner>() { + RxUtils.computationScheduler.createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { final Pair<BitmapDrawable, Boolean> loaded = loadFromDisk(); final BitmapDrawable image = loaded.getLeft(); if (image != null) { diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index 4055f01..bf9ebdf 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -5,10 +5,9 @@ import cgeo.geocaching.utils.Version; import org.json.JSONException; import org.json.JSONObject; -import rx.Scheduler; +import rx.functions.Action0; import rx.schedulers.Schedulers; import rx.subjects.BehaviorSubject; -import rx.functions.Action1; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; @@ -52,9 +51,9 @@ public class StatusUpdater { final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null)); static { - Schedulers.io().schedulePeriodically(new Action1<Scheduler.Inner>() { + Schedulers.io().createWorker().schedulePeriodically(new Action0() { @Override - public void call(final Scheduler.Inner inner) { + public void call() { final JSONObject response = Network.requestJSON("http://status.cgeo.org/api/status.json", new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java index ff4a439..13fcc82 100644 --- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java +++ b/main/src/cgeo/geocaching/sensors/DirectionProvider.java @@ -83,7 +83,7 @@ public class DirectionProvider { private boolean hasSensorChecked = false; public boolean hasSensor(Context context) { - if (hasSensorChecked == false) { + if (!hasSensorChecked) { hasSensor = getOrientationSensor(context) != null; hasSensorChecked = true; } diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java index a77b477..a68f2a6 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java @@ -1,18 +1,15 @@ package cgeo.geocaching.sensors; -import android.os.*; import cgeo.geocaching.utils.Log; - import cgeo.geocaching.utils.StartableHandlerThread; + import org.apache.commons.lang3.StringUtils; import rx.Observable; import rx.Observable.OnSubscribe; -import rx.Scheduler.Inner; 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; @@ -24,6 +21,7 @@ import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; +import android.os.Bundle; import java.util.concurrent.TimeUnit; @@ -98,9 +96,9 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { @Override public Subscription connect() { final CompositeSubscription subscription = new CompositeSubscription(); - AndroidSchedulers.handlerThread(handlerThread.getHandler()).schedule(new Action1<Inner>() { + AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { synchronized(lock) { if (count++ == 0) { Log.d("GeoDataProvider: starting the GPS and network listeners" + " (" + ++debugSessionCounter + ")"); @@ -118,9 +116,9 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { subscription.add(Subscriptions.create(new Action0() { @Override public void call() { - AndroidSchedulers.handlerThread(handlerThread.getHandler()).schedule(new Action1<Inner>() { + AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { @Override - public void call(final Inner inner) { + public void call() { synchronized (lock) { if (--count == 0) { Log.d("GeoDataProvider: stopping the GPS and network listeners" + " (" + debugSessionCounter + ")"); diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index dc1a39d..74a9b32 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -62,7 +62,7 @@ public class SettingsActivity extends PreferenceActivity { * directory and preference key in onActivityResult() easily just by knowing * the result code. */ - private enum DirChooserType { + private static enum DirChooserType { GPX_IMPORT_DIR(1, R.string.pref_gpxImportDir, Environment.getExternalStorageDirectory().getPath() + "/gpx", false), GPX_EXPORT_DIR(2, R.string.pref_gpxExportDir, @@ -189,7 +189,7 @@ public class SettingsActivity extends PreferenceActivity { private static void setServiceScreenSummary(PreferenceManager preferenceManager, final int preferenceKey) { - String summary = StringUtils.EMPTY; + String summary; switch (preferenceKey) { case R.string.pref_connectorGCActive: @@ -582,7 +582,7 @@ public class SettingsActivity extends PreferenceActivity { || isPreference(preference, R.string.pref_connectorOXActive) || isPreference(preference, R.string.pref_connectorECActive)) { // update summary - boolean boolVal = ((Boolean) value).booleanValue(); + final boolean boolVal = (Boolean) value; String summary = getServiceSummary(boolVal); if (OCPreferenceKeys.isOCPreference(preference.getKey())) { OCPreferenceKeys prefKey = OCPreferenceKeys.getByKey(preference.getKey()); diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java index 1ed8e68..57a69ee 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java @@ -12,16 +12,14 @@ public class PopularityRatioComparator extends AbstractCacheComparator { @Override protected int compareCaches(final Geocache cache1, final Geocache cache2) { - - float ratio1 = 0.0f; - float ratio2 = 0.0f; - int finds1 = cache1.getFindsCount(); int finds2 = cache2.getFindsCount(); + float ratio1 = 0.0f; if (finds1 != 0) { ratio1 = (((float) cache1.getFavoritePoints()) / ((float) finds1)); } + float ratio2 = 0.0f; if (finds2 != 0) { ratio2 = (((float) cache2.getFavoritePoints()) / ((float) finds2)); } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java index 799b695..06fa1fa 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingListViewPageViewCreator.java @@ -51,7 +51,6 @@ public abstract class AbstractCachingListViewPageViewCreator extends AbstractCac int logViewPosition = state.getInt(STATE_POSITION); int logViewPositionFromTop = state.getInt(STATE_POSITION_FROM_TOP); view.setSelectionFromTop(logViewPosition, logViewPositionFromTop); - return; } } diff --git a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java index 0c67384..71cd3b4 100644 --- a/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java +++ b/main/src/cgeo/geocaching/ui/AbstractCachingPageViewCreator.java @@ -52,6 +52,5 @@ public abstract class AbstractCachingPageViewCreator<ViewClass extends View> imp */ @Override public void setViewState(@NonNull Bundle state) { - return; } } diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index 0d90d9f..3a451a5 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -45,6 +45,7 @@ import android.widget.CheckBox; import android.widget.ImageView; import android.widget.TextView; +import java.lang.ref.WeakReference; import java.util.ArrayList; import java.util.Collections; import java.util.Comparator; @@ -377,7 +378,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { final boolean lightSkin = Settings.isLightSkin(); - final TouchListener touchListener = new TouchListener(cache); + final TouchListener touchListener = new TouchListener(cache, this); v.setOnClickListener(touchListener); v.setOnLongClickListener(touchListener); v.setOnTouchListener(touchListener); @@ -536,24 +537,30 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } } - private class TouchListener implements View.OnClickListener, View.OnLongClickListener, View.OnTouchListener { + private static class TouchListener implements View.OnClickListener, View.OnLongClickListener, View.OnTouchListener { private final Geocache cache; private final GestureDetector gestureDetector; + private final @NonNull WeakReference<CacheListAdapter> adapterRef; - public TouchListener(final Geocache cache) { + public TouchListener(final Geocache cache, final @NonNull CacheListAdapter adapter) { this.cache = cache; - gestureDetector = new GestureDetector(getContext(), new FlingGesture(cache)); + gestureDetector = new GestureDetector(adapter.getContext(), new FlingGesture(cache, adapter)); + adapterRef = new WeakReference<CacheListAdapter>(adapter); } // Tap on item @Override public void onClick(final View view) { - if (isSelectMode()) { + final CacheListAdapter adapter = adapterRef.get(); + if (adapter == null) { + return; + } + if (adapter.isSelectMode()) { cache.setStatusChecked(!cache.isStatusChecked()); - notifyDataSetChanged(); + adapter.notifyDataSetChanged(); } else { - CacheDetailActivity.startActivity(getContext(), cache.getGeocode(), cache.getName()); + CacheDetailActivity.startActivity(adapter.getContext(), cache.getGeocode(), cache.getName()); } } @@ -572,12 +579,14 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { } } - private class FlingGesture extends GestureDetector.SimpleOnGestureListener { + private static class FlingGesture extends GestureDetector.SimpleOnGestureListener { private final Geocache cache; + private final @NonNull WeakReference<CacheListAdapter> adapterRef; - public FlingGesture(final Geocache cache) { + public FlingGesture(final Geocache cache, final @NonNull CacheListAdapter adapter) { this.cache = cache; + adapterRef = new WeakReference<CacheListAdapter>(adapter); } @Override @@ -586,11 +595,15 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { if (Math.abs(e1.getY() - e2.getY()) > SWIPE_MAX_OFF_PATH) { return false; } + final CacheListAdapter adapter = adapterRef.get(); + if (adapter == null) { + return false; + } // left to right swipe if ((e2.getX() - e1.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { - if (!selectMode) { - switchSelectMode(); + if (!adapter.selectMode) { + adapter.switchSelectMode(); cache.setStatusChecked(true); } return true; @@ -598,8 +611,8 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { // right to left swipe if ((e1.getX() - e2.getX()) > SWIPE_MIN_DISTANCE && Math.abs(velocityX) > Math.abs(velocityY)) { - if (selectMode) { - switchSelectMode(); + if (adapter.selectMode) { + adapter.switchSelectMode(); } return true; } diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index f7111f7..915303b 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -3,10 +3,9 @@ package cgeo.geocaching.ui; import cgeo.geocaching.R; import cgeo.geocaching.utils.AngleUtils; -import rx.Scheduler; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; +import rx.functions.Action0; import android.content.Context; import android.content.res.Resources; @@ -88,9 +87,9 @@ public class CompassView extends View { initialDisplay = true; - periodicUpdate = AndroidSchedulers.mainThread().schedulePeriodically(new Action1<Scheduler.Inner>() { + periodicUpdate = AndroidSchedulers.mainThread().createWorker().schedulePeriodically(new Action0() { @Override - public void call(final Scheduler.Inner inner) { + public void call() { final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || diff --git a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java index b2ce11a..67b8923 100644 --- a/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/CoordinatesInputDialog.java @@ -1,17 +1,18 @@ package cgeo.geocaching.ui.dialog; import cgeo.geocaching.Geocache; -import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.R; import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.Settings.CoordInputFormatEnum; import cgeo.geocaching.utils.EditUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import android.os.Bundle; import android.text.Editable; @@ -25,9 +26,11 @@ import android.widget.EditText; import android.widget.Spinner; import android.widget.TextView; +import java.lang.ref.WeakReference; + public class CoordinatesInputDialog extends NoTitleDialog { - final private AbstractActivity context; + final private WeakReference<AbstractActivity> context; final private IGeoData geo; final private Geocache cache; private Geopoint gp; @@ -43,9 +46,9 @@ public class CoordinatesInputDialog extends NoTitleDialog { private CoordInputFormatEnum currentFormat = null; - public CoordinatesInputDialog(final AbstractActivity context, final Geocache cache, final Geopoint gp, final IGeoData geo) { + public CoordinatesInputDialog(final @NonNull AbstractActivity context, final Geocache cache, final Geopoint gp, final IGeoData geo) { super(context, ActivityMixin.getDialogTheme()); - this.context = context; + this.context = new WeakReference<AbstractActivity>(context); this.geo = geo; this.cache = cache; @@ -66,7 +69,7 @@ public class CoordinatesInputDialog extends NoTitleDialog { final Spinner spinner = (Spinner) findViewById(R.id.spinnerCoordinateFormats); final ArrayAdapter<CharSequence> adapter = - ArrayAdapter.createFromResource(context, + ArrayAdapter.createFromResource(context.get(), R.array.waypoint_coordinate_formats, android.R.layout.simple_spinner_item); adapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -371,7 +374,8 @@ public class CoordinatesInputDialog extends NoTitleDialog { // Signaled and returned below } if (signalError) { - context.showToast(context.getResources().getString(R.string.err_parse_lat_lon)); + final AbstractActivity activity = context.get(); + activity.showToast(activity.getResources().getString(R.string.err_parse_lat_lon)); } return false; } @@ -423,7 +427,8 @@ public class CoordinatesInputDialog extends NoTitleDialog { @Override public void onClick(View v) { if (geo == null || geo.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_point_unknown_position)); + final AbstractActivity activity = context.get(); + activity.showToast(activity.getResources().getString(R.string.err_point_unknown_position)); return; } @@ -437,7 +442,8 @@ public class CoordinatesInputDialog extends NoTitleDialog { @Override public void onClick(View v) { if (cache == null || cache.getCoords() == null) { - context.showToast(context.getResources().getString(R.string.err_location_unknown)); + final AbstractActivity activity = context.get(); + activity.showToast(activity.getResources().getString(R.string.err_location_unknown)); return; } diff --git a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java index 4ce2e0c..14840b9 100644 --- a/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java +++ b/main/src/cgeo/geocaching/utils/DatabaseBackupUtils.java @@ -10,7 +10,6 @@ import org.apache.commons.lang3.StringUtils; import android.app.Activity; import android.app.ProgressDialog; -import android.content.Context; import android.content.res.Resources; import java.io.File; @@ -53,7 +52,6 @@ public class DatabaseBackupUtils { } public static boolean createBackup(final Activity activity, final Runnable runAfterwards) { - final Context context = activity; // avoid overwriting an existing backup with an empty database // (can happen directly after reinstalling the app) if (DataStore.getAllCachesCount() == 0) { @@ -61,9 +59,9 @@ public class DatabaseBackupUtils { return false; } - final ProgressDialog dialog = ProgressDialog.show(context, - context.getString(R.string.init_backup), - context.getString(R.string.init_backup_running), true, false); + final ProgressDialog dialog = ProgressDialog.show(activity, + activity.getString(R.string.init_backup), + activity.getString(R.string.init_backup_running), true, false); new Thread() { @Override public void run() { @@ -75,9 +73,9 @@ public class DatabaseBackupUtils { Dialogs.message(activity, R.string.init_backup_backup, backupFileName != null - ? context.getString(R.string.init_backup_success) + ? activity.getString(R.string.init_backup_success) + "\n" + backupFileName - : context.getString(R.string.init_backup_failed)); + : activity.getString(R.string.init_backup_failed)); if (runAfterwards != null) { runAfterwards.run(); } diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index 8e7864c..deba573 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -3,16 +3,10 @@ package cgeo.geocaching.utils; import rx.Scheduler; import rx.schedulers.Schedulers; -import java.util.concurrent.LinkedBlockingQueue; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - public class RxUtils { // Utility class, not to be instanciated private RxUtils() {} - final static private int cores = Runtime.getRuntime().availableProcessors(); - public final static Scheduler computationScheduler = Schedulers.executor(new ThreadPoolExecutor(1, cores, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); - + public final static Scheduler computationScheduler = Schedulers.computation(); } |
