diff options
Diffstat (limited to 'main/src')
42 files changed, 565 insertions, 366 deletions
diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index 683579f..88cad01 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -14,12 +14,14 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.CacheDetailsCreator; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.lang3.StringUtils; + import rx.Observable; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; +import rx.schedulers.Schedulers; import android.graphics.Rect; import android.os.Bundle; @@ -79,13 +81,13 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements if (!cache.supportsGCVote()) { return; } - RxUtils.subscribeOnIOThenUI(Observable.defer(new Func0<Observable<GCVoteRating>>() { + AndroidObservable.bindActivity(this, Observable.defer(new Func0<Observable<GCVoteRating>>() { @Override public Observable<GCVoteRating> call() { final GCVoteRating rating = GCVote.getRating(cache.getGuid(), geocode); return rating != null ? Observable.just(rating) : Observable.<GCVoteRating>empty(); } - }), new Action1<GCVoteRating>() { + })).subscribe(new Action1<GCVoteRating>() { @Override public void call(final GCVoteRating rating) { cache.setRating(rating.getRating()); @@ -93,7 +95,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements DataStore.saveChangedCache(cache); details.addRating(cache); } - }); + }, Schedulers.io()); } protected void init() { @@ -160,11 +162,6 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements } @Override - public void onPause() { - super.onPause(); - } - - @Override public boolean onPrepareOptionsMenu(Menu menu) { super.onPrepareOptionsMenu(menu); @@ -255,7 +252,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements * @param view * unused here but needed since this method is referenced from XML layout */ - public final void goDefaultNavigation(@SuppressWarnings("unused") View view) { + public final void goDefaultNavigation(View view) { navigateTo(); finish(); } diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index a8fa8ee..da8cc7d 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -8,6 +8,7 @@ import cgeo.geocaching.activity.AbstractActivity; import cgeo.geocaching.activity.AbstractViewPagerActivity; import cgeo.geocaching.activity.Progress; import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; +import cgeo.geocaching.apps.cachelist.MapsWithMeCacheListApp; import cgeo.geocaching.compatibility.Compatibility; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.IConnector; @@ -45,7 +46,6 @@ import cgeo.geocaching.utils.CryptUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; -import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.SimpleCancellableHandler; import cgeo.geocaching.utils.SimpleHandler; import cgeo.geocaching.utils.TextUtils; @@ -56,14 +56,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 rx.Observable; import rx.Observable.OnSubscribe; import rx.Observer; import rx.Scheduler.Inner; import rx.Subscriber; -import rx.Subscription; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.schedulers.Schedulers; +import rx.subscriptions.CompositeSubscription; import android.R.color; import android.app.AlertDialog; @@ -168,7 +170,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc private TextView cacheDistanceView; protected ImagesList imagesList; - private Subscription imagesSubscription; + private CompositeSubscription createSubscriptions; /** * waypoint selected in context menu. This variable will be gone when the waypoint context menu is a fragment. */ @@ -178,6 +180,8 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.cachedetail_activity); + createSubscriptions = new CompositeSubscription(); + // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.cache)); @@ -195,6 +199,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc guid = extras.getString(Intents.EXTRA_GUID); } + // When clicking a cache in MapsWithMe, we get back a PendingIntent + if (StringUtils.isEmpty(geocode)) { + geocode = MapsWithMeCacheListApp.getCacheFromMapsWithMe(this, getIntent()); + } + // try to get data from URI if (geocode == null && guid == null && uri != null) { final String uriHost = uri.getHost().toLowerCase(Locale.US); @@ -330,9 +339,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void onDestroy() { - if (imagesList != null) { - imagesSubscription.unsubscribe(); - } + createSubscriptions.unsubscribe(); super.onDestroy(); } @@ -345,11 +352,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override - public void onPause() { - super.onPause(); - } - - @Override public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo info) { super.onCreateContextMenu(menu, view, info); final int viewId = view.getId(); @@ -649,7 +651,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return; } imagesList = new ImagesList(this, cache.getGeocode()); - imagesSubscription = imagesList.loadImages(imageView, cache.getImages(), false); + createSubscriptions.add(imagesList.loadImages(imageView, cache.getImages(), false)); } public static void startActivity(final Context context, final String geocode) { @@ -878,7 +880,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view = (ScrollView) getLayoutInflater().inflate(R.layout.cachedetail_details_page, null); // Start loading preview map - RxUtils.subscribeOnIOThenUI(previewMap, new Action1<BitmapDrawable>() { + AndroidObservable.bindActivity(CacheDetailActivity.this, previewMap).subscribe(new Action1<BitmapDrawable>() { @Override public void call(final BitmapDrawable image) { final Bitmap bitmap = image.getBitmap(); @@ -888,7 +890,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc view.findViewById(R.id.map_preview_box).setVisibility(View.VISIBLE); } } - }); + }, Schedulers.io()); detailsList = (LinearLayout) view.findViewById(R.id.details_list); final CacheDetailsCreator details = new CacheDetailsCreator(CacheDetailActivity.this, detailsList); @@ -1605,7 +1607,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } }); - RxUtils.subscribeOnIOThenUI(producer, new Observer<Spanned>() { + AndroidObservable.bindActivity(this, producer).subscribe(new Observer<Spanned>() { @Override public void onCompleted() { if (null != loadingIndicatorView) { @@ -1668,7 +1670,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } descriptionView.setBackgroundResource(backcolor); } - }); + }, Schedulers.io()); } private class WaypointsViewCreator extends AbstractCachingPageViewCreator<ListView> { diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index b2404d9..0aec119 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -1384,7 +1384,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA * @param view * unused here but needed since this method is referenced from XML layout */ - public void goMap(@SuppressWarnings("unused") View view) { + public void goMap(View view) { if (!cacheToShow()) { return; } diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index ff7a025..36dcf27 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -122,11 +122,6 @@ public class CompassActivity extends AbstractActivity { } @Override - public void onPause() { - super.onPause(); - } - - @Override public void onDestroy() { compassView.destroyDrawingCache(); SpeechService.stopService(this); diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index 7e676b0..0adefb8 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -20,16 +20,17 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.FileUtils; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.MiscUtils; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; + +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; import rx.functions.Func1; +import rx.schedulers.Schedulers; import rx.util.async.Async; import android.app.Activity; @@ -393,7 +394,7 @@ public class DataStore { */ public static void moveDatabase(final Activity fromActivity) { final ProgressDialog dialog = ProgressDialog.show(fromActivity, fromActivity.getString(R.string.init_dbmove_dbmove), fromActivity.getString(R.string.init_dbmove_running), true, false); - RxUtils.subscribeOnIOThenUI(Async.fromCallable(new Func0<Boolean>() { + AndroidObservable.bindActivity(fromActivity, Async.fromCallable(new Func0<Boolean>() { @Override public Boolean call() { if (!LocalStorage.isExternalStorageAvailable()) { @@ -418,14 +419,14 @@ public class DataStore { init(); return true; } - }), new Action1<Boolean>() { + })).subscribe(new Action1<Boolean>() { @Override public void call(final Boolean success) { dialog.dismiss(); final String message = success ? fromActivity.getString(R.string.init_dbmove_success) : fromActivity.getString(R.string.init_dbmove_failed); Dialogs.message(fromActivity, R.string.init_dbmove_dbmove, message); } - }); + }, Schedulers.io()); } private static File databasePath(final boolean internal) { @@ -1188,7 +1189,6 @@ public class DataStore { saveAttributesWithoutTransaction(cache); saveWaypointsWithoutTransaction(cache); saveSpoilersWithoutTransaction(cache); - saveLogsWithoutTransaction(cache.getGeocode(), cache.getLogs()); saveLogCountsWithoutTransaction(cache); saveInventoryWithoutTransaction(cache.getGeocode(), cache.getInventory()); @@ -1313,7 +1313,7 @@ public class DataStore { */ private static void removeOutdatedWaypointsOfCache(final @NonNull Geocache cache, final @NonNull Collection<String> remainingWaypointIds) { final String idList = StringUtils.join(remainingWaypointIds, ','); - database.delete(dbTableWaypoints, "geocode = ? AND _id NOT in (" + idList + ")", new String[]{cache.getGeocode()}); + database.delete(dbTableWaypoints, "geocode = ? AND _id NOT in (" + idList + ")", new String[] { cache.getGeocode() }); } /** @@ -1421,7 +1421,7 @@ public class DataStore { } } - private static void saveLogsWithoutTransaction(final String geocode, final List<LogEntry> logs) { + public static void saveLogsWithoutTransaction(final String geocode, final List<LogEntry> logs) { // TODO delete logimages referring these logs database.delete(dbTableLogs, "geocode = ?", new String[]{geocode}); @@ -1558,8 +1558,8 @@ public class DataStore { return new HashSet<Geocache>(); } - final Set<Geocache> result = new HashSet<Geocache>(); - final List<String> remaining = new LinkedList<String>(geocodes); + Set<Geocache> result = new HashSet<Geocache>(); + Set<String> remaining = new HashSet<String>(geocodes); if (loadFlags.contains(LoadFlag.LOAD_CACHE_BEFORE)) { for (String geocode : new HashSet<String>(remaining)) { @@ -1579,7 +1579,7 @@ public class DataStore { loadFlags.contains(LoadFlag.LOAD_INVENTORY) || loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { - final Collection<Geocache> cachesFromDB = loadCachesFromGeocodes(remaining, loadFlags); + final Set<Geocache> cachesFromDB = loadCachesFromGeocodes(remaining, loadFlags); result.addAll(cachesFromDB); for (final Geocache cache : cachesFromDB) { remaining.remove(cache.getGeocode()); @@ -1605,11 +1605,15 @@ public class DataStore { /** * Load caches. * - * @param allGeocodes + * @param geocodes * @param loadFlags * @return Set of loaded caches. Never null. */ - private static Collection<Geocache> loadCachesFromGeocodes(final List<String> allGeocodes, final EnumSet<LoadFlag> loadFlags) { + private static Set<Geocache> loadCachesFromGeocodes(final Set<String> geocodes, final EnumSet<LoadFlag> loadFlags) { + if (CollectionUtils.isEmpty(geocodes)) { + return Collections.emptySet(); + } + // do not log the entire collection of geo codes to the debug log. This can be more than 100 KB of text for large lists! init(); @@ -1624,73 +1628,67 @@ public class DataStore { } query.append(" WHERE ").append(dbTableCaches).append('.'); + query.append(DataStore.whereGeocodeIn(geocodes)); - final String queryBegin = query.toString(); - final List<Geocache> result = new ArrayList<Geocache>(allGeocodes.size()); - - for (List<String> geocodes: MiscUtils.buffer(allGeocodes, 50)) { - final Cursor cursor = database.rawQuery(queryBegin + DataStore.whereGeocodeIn(geocodes), null); - try { - final Set<Geocache> caches = new HashSet<Geocache>(); - int logIndex = -1; + Cursor cursor = database.rawQuery(query.toString(), null); + try { + final Set<Geocache> caches = new HashSet<Geocache>(); + int logIndex = -1; - while (cursor.moveToNext()) { - final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); + while (cursor.moveToNext()) { + Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); - if (loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES)) { - cache.setAttributes(loadAttributes(cache.getGeocode())); - } + if (loadFlags.contains(LoadFlag.LOAD_ATTRIBUTES)) { + cache.setAttributes(loadAttributes(cache.getGeocode())); + } - if (loadFlags.contains(LoadFlag.LOAD_WAYPOINTS)) { - final List<Waypoint> waypoints = loadWaypoints(cache.getGeocode()); - if (CollectionUtils.isNotEmpty(waypoints)) { - cache.setWaypoints(waypoints, false); - } + if (loadFlags.contains(LoadFlag.LOAD_WAYPOINTS)) { + final List<Waypoint> waypoints = loadWaypoints(cache.getGeocode()); + if (CollectionUtils.isNotEmpty(waypoints)) { + cache.setWaypoints(waypoints, false); } + } - if (loadFlags.contains(LoadFlag.LOAD_SPOILERS)) { - final List<Image> spoilers = loadSpoilers(cache.getGeocode()); - cache.setSpoilers(spoilers); - } + if (loadFlags.contains(LoadFlag.LOAD_SPOILERS)) { + final List<Image> spoilers = loadSpoilers(cache.getGeocode()); + cache.setSpoilers(spoilers); + } - if (loadFlags.contains(LoadFlag.LOAD_LOGS)) { - cache.setLogs(loadLogs(cache.getGeocode())); - final Map<LogType, Integer> logCounts = loadLogCounts(cache.getGeocode()); - if (MapUtils.isNotEmpty(logCounts)) { - cache.getLogCounts().clear(); - cache.getLogCounts().putAll(logCounts); - } + if (loadFlags.contains(LoadFlag.LOAD_LOGS)) { + final Map<LogType, Integer> logCounts = loadLogCounts(cache.getGeocode()); + if (MapUtils.isNotEmpty(logCounts)) { + cache.getLogCounts().clear(); + cache.getLogCounts().putAll(logCounts); } + } - if (loadFlags.contains(LoadFlag.LOAD_INVENTORY)) { - final List<Trackable> inventory = loadInventory(cache.getGeocode()); - if (CollectionUtils.isNotEmpty(inventory)) { - if (cache.getInventory() == null) { - cache.setInventory(new ArrayList<Trackable>()); - } else { - cache.getInventory().clear(); - } - cache.getInventory().addAll(inventory); + if (loadFlags.contains(LoadFlag.LOAD_INVENTORY)) { + final List<Trackable> inventory = loadInventory(cache.getGeocode()); + if (CollectionUtils.isNotEmpty(inventory)) { + if (cache.getInventory() == null) { + cache.setInventory(new ArrayList<Trackable>()); + } else { + cache.getInventory().clear(); } + cache.getInventory().addAll(inventory); } + } - if (loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { - if (logIndex < 0) { - logIndex = cursor.getColumnIndex("log"); - } - cache.setLogOffline(!cursor.isNull(logIndex)); + if (loadFlags.contains(LoadFlag.LOAD_OFFLINE_LOG)) { + if (logIndex < 0) { + logIndex = cursor.getColumnIndex("log"); } - cache.addStorageLocation(StorageLocation.DATABASE); - cacheCache.putCacheInCache(cache); - - caches.add(cache); + cache.setLogOffline(!cursor.isNull(logIndex)); } - result.addAll(caches); - } finally { - cursor.close(); + cache.addStorageLocation(StorageLocation.DATABASE); + cacheCache.putCacheInCache(cache); + + caches.add(cache); } + return caches; + } finally { + cursor.close(); } - return result; } @@ -1918,6 +1916,11 @@ public class DataStore { return false; } + /** + * @param geocode + * @return an immutable, non null list of logs + */ + @NonNull public static List<LogEntry> loadLogs(String geocode) { List<LogEntry> logs = new ArrayList<LogEntry>(); @@ -1953,7 +1956,7 @@ public class DataStore { cursor.close(); - return logs; + return Collections.unmodifiableList(logs); } public static Map<LogType, Integer> loadLogCounts(String geocode) { diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index 45775a7..55de0a6 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -10,11 +10,11 @@ import cgeo.geocaching.enumerations.WaypointType; import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.TextUtils; @@ -22,7 +22,6 @@ import org.androidannotations.annotations.EActivity; import org.androidannotations.annotations.Extra; import org.androidannotations.annotations.InstanceState; import org.androidannotations.annotations.ViewById; - import org.apache.commons.lang3.StringUtils; import android.app.ProgressDialog; @@ -205,11 +204,6 @@ public class EditWaypointActivity extends AbstractActivity { super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA)); } - @Override - public void onPause() { - super.onPause(); - } - private void initializeWaypointTypeSelector() { ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<WaypointType>(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); diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index a224e4e..bd1d8bf 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -25,6 +25,7 @@ import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.DateUtils; import cgeo.geocaching.utils.ImageUtils; import cgeo.geocaching.utils.LazyInitializedList; import cgeo.geocaching.utils.Log; @@ -34,12 +35,16 @@ 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; import org.apache.commons.lang3.BooleanUtils; 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; @@ -50,11 +55,13 @@ 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.text.Html; import android.text.Html.ImageGetter; +import java.io.File; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -73,6 +80,10 @@ import java.util.regex.Pattern; /** * Internal c:geo representation of a "cache" */ +/** + * @author kep9fe + * + */ public class Geocache implements ICache, IWaypoint { private static final int OWN_WP_PREFIX_OFFSET = 17; @@ -138,12 +149,7 @@ public class Geocache implements ICache, IWaypoint { } }; private List<Image> spoilers = null; - private final LazyInitializedList<LogEntry> logs = new LazyInitializedList<LogEntry>() { - @Override - public List<LogEntry> call() { - return DataStore.loadLogs(geocode); - } - }; + private List<Trackable> inventory = null; private Map<LogType, Integer> logCounts = new HashMap<LogType, Integer>(); private boolean userModifiedCoords = false; @@ -177,11 +183,10 @@ public class Geocache implements ICache, IWaypoint { * * @param gpxParser */ - public Geocache(@SuppressWarnings("unused") GPXParser gpxParser) { + public Geocache(GPXParser gpxParser) { setReliableLatLon(true); setAttributes(Collections.<String> emptyList()); setWaypoints(Collections.<Waypoint> emptyList(), false); - setLogs(Collections.<LogEntry> emptyList()); } public void setChangeNotificationHandler(Handler newNotificationHandler) { @@ -349,12 +354,6 @@ public class Geocache implements ICache, IWaypoint { inventory = other.inventory; inventoryItems = other.inventoryItems; } - if (logs.isEmpty()) { // keep last known logs if none - logs.clear(); - if (other.logs != null) { - logs.addAll(other.logs); - } - } if (logCounts.isEmpty()) { logCounts = other.logCounts; } @@ -421,7 +420,6 @@ public class Geocache implements ICache, IWaypoint { attributes == other.attributes && waypoints == other.waypoints && spoilers == other.spoilers && - logs == other.logs && inventory == other.inventory && logCounts == other.logCounts && ObjectUtils.equals(logOffline, other.logOffline) && @@ -776,10 +774,7 @@ public class Geocache implements ICache, IWaypoint { @Override public List<Image> getSpoilers() { - if (spoilers == null) { - return Collections.emptyList(); - } - return Collections.unmodifiableList(spoilers); + return ListUtils.unmodifiableList(ListUtils.emptyIfNull(spoilers)); } @Override @@ -1005,21 +1000,24 @@ public class Geocache implements ICache, IWaypoint { } /** - * @return never <code>null</code> + * The list of logs is immutable, because it is directly fetched from the database on demand, and not stored at this + * object. If you want to modify logs, you have to load all logs of the cache, create a new list from the existing + * list and store that new list in the database. + * + * @return immutable list of logs */ + @NonNull public List<LogEntry> getLogs() { - // It is important to return the underlying list here and not the lazily initialized one, - // because database manipulation may erase the existing logs before methods are called - // on the previous logs, when updating the saved logs for example. - return logs.getUnderlyingList(); + return DataStore.loadLogs(geocode); } /** - * @return only the logs of friends, never <code>null</code> + * @return only the logs of friends */ + @NonNull public List<LogEntry> getFriendsLogs() { final ArrayList<LogEntry> friendLogs = new ArrayList<LogEntry>(); - for (final LogEntry log : logs) { + for (final LogEntry log : getLogs()) { if (log.friend) { friendLogs.add(log); } @@ -1027,17 +1025,6 @@ public class Geocache implements ICache, IWaypoint { return Collections.unmodifiableList(friendLogs); } - /** - * @param logs - * the log entries - */ - public void setLogs(List<LogEntry> logs) { - this.logs.clear(); - if (logs != null) { - this.logs.addAll(logs); - } - } - public boolean isLogOffline() { return BooleanUtils.isTrue(logOffline); } @@ -1735,6 +1722,7 @@ public class Geocache implements ICache, IWaypoint { public Collection<Image> getImages() { final LinkedList<Image> result = new LinkedList<Image>(); result.addAll(getSpoilers()); + addLocalSpoilersTo(result); for (final LogEntry log : getLogs()) { result.addAll(log.getLogImages()); } @@ -1746,6 +1734,23 @@ public class Geocache implements ICache, IWaypoint { return result; } + // add spoilers stored locally in /sdcard/GeocacheSpoilers + private void addLocalSpoilersTo(final List<Image> spoilers) { + if (StringUtils.length(geocode) >= 2) { + final String suffix = StringUtils.right(geocode, 2); + final File baseDir = new File(Environment.getExternalStorageDirectory().toString(), "GeocacheSpoilers"); + final File lastCharDir = new File(baseDir, suffix.substring(1)); + final File secondToLastCharDir = new File(lastCharDir, suffix.substring(0, 1)); + final File finalDir = new File(secondToLastCharDir, getGeocode()); + final File[] files = finalDir.listFiles(); + if (files != null) { + for (final File image : files) { + spoilers.add(new Image("file://" + image.getAbsolutePath(), image.getName())); + } + } + } + } + public void setDetailedUpdatedNow() { final long now = System.currentTimeMillis(); setUpdated(now); @@ -1806,4 +1811,23 @@ public class Geocache implements ICache, IWaypoint { return (getType().applyDistanceRule() || hasUserModifiedCoords()) && getConnector() == GCConnector.getInstance(); } + public LogType getDefaultLogType() { + if (isEventCache()) { + final Date eventDate = getHiddenDate(); + boolean expired = DateUtils.isPastEvent(this); + + if (hasOwnLog(LogType.WILL_ATTEND) || expired || (eventDate != null && DateUtils.daysSince(eventDate.getTime()) == 0)) { + return hasOwnLog(LogType.ATTENDED) ? LogType.NOTE : LogType.ATTENDED; + } + return LogType.WILL_ATTEND; + } + if (isFound()) { + return LogType.NOTE; + } + if (getType() == CacheType.WEBCAM) { + return LogType.WEBCAM_PHOTO_TAKEN; + } + return LogType.FOUND_IT; + } + } diff --git a/main/src/cgeo/geocaching/Image.java b/main/src/cgeo/geocaching/Image.java index 22c76aa..50ea80e 100644 --- a/main/src/cgeo/geocaching/Image.java +++ b/main/src/cgeo/geocaching/Image.java @@ -1,5 +1,7 @@ package cgeo.geocaching; +import cgeo.geocaching.utils.FileUtils; + import org.apache.commons.lang3.StringUtils; import android.content.Context; @@ -8,6 +10,8 @@ import android.net.Uri; import android.os.Parcel; import android.os.Parcelable; +import java.io.File; + public class Image implements Parcelable { private final String url; private final String title; @@ -23,6 +27,10 @@ public class Image implements Parcelable { this(url, title, null); } + public Image(final File file) { + this("file://" + file.getAbsolutePath(), file.getName(), null); + } + public Image(final Parcel in) { url = in.readString(); title = in.readString(); @@ -85,4 +93,22 @@ public class Image implements Parcelable { return "???"; } + + /** + * Check if the URL represents a file on the local file system. + * + * @return <tt>true</tt> if the URL scheme is <tt>file</tt>, <tt>false</tt> otherwise + */ + public boolean isLocalFile() { + return FileUtils.isFileUrl(url); + } + + /** + * Local file name when {@link #isLocalFile()} is <tt>true</tt>. + * + * @return the local file + */ + public File localFile() { + return FileUtils.urlToFile(url); + } } diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index 0f65a1d..2b05263 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -3,7 +3,6 @@ package cgeo.geocaching; import cgeo.geocaching.connector.ILoggingManager; import cgeo.geocaching.connector.ImageResult; import cgeo.geocaching.connector.LogResult; -import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.enumerations.LogType; import cgeo.geocaching.enumerations.LogTypeTrackable; @@ -341,25 +340,10 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private void setDefaultValues() { date = Calendar.getInstance(); rating = GCVote.NO_RATING; - if (cache.isEventCache()) { - final Date eventDate = cache.getHiddenDate(); - boolean expired = DateUtils.isPastEvent(cache); - - if (cache.hasOwnLog(LogType.WILL_ATTEND) || expired) { - typeSelected = cache.hasOwnLog(LogType.ATTENDED) ? LogType.NOTE : LogType.ATTENDED; - } else { - typeSelected = LogType.WILL_ATTEND; - } - // it this is an attended event log, use the event date by default instead of the current date - if (expired && typeSelected == LogType.ATTENDED) { - date.setTime(eventDate); - } - } else { - if (cache.isFound()) { - typeSelected = LogType.NOTE; - } else { - typeSelected = cache.getType() == CacheType.WEBCAM ? LogType.WEBCAM_PHOTO_TAKEN : LogType.FOUND_IT; - } + typeSelected = cache.getDefaultLogType(); + // it this is an attended event log, use the event date by default instead of the current date + if (cache.isEventCache() && DateUtils.isPastEvent(cache) && typeSelected == LogType.ATTENDED) { + date.setTime(cache.getHiddenDate()); } text = null; imageCaption = StringUtils.EMPTY; @@ -531,16 +515,20 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia final LogResult logResult = loggingManager.postLog(cache, typeSelected, date, log, logPwd, trackables); if (logResult.getPostLogResult() == StatusCode.NO_ERROR) { - final LogEntry logNow = new LogEntry(date.getTimeInMillis(), typeSelected, log); - - cache.getLogs().add(0, logNow); - + // update geocache in DB if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED) { cache.setFound(true); cache.setVisitedDate(new Date().getTime()); } - DataStore.saveChangedCache(cache); + + // update logs in DB + ArrayList<LogEntry> newLogs = new ArrayList<LogEntry>(cache.getLogs()); + final LogEntry logNow = new LogEntry(date.getTimeInMillis(), typeSelected, log); + newLogs.add(0, logNow); + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), newLogs); + + // update offline log in DB cache.clearOfflineLog(); if (typeSelected == LogType.FOUND_IT) { @@ -557,7 +545,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia final String uploadedImageUrl = imageResult.getImageUri(); if (StringUtils.isNotEmpty(uploadedImageUrl)) { logNow.addLogImage(new Image(uploadedImageUrl, imageCaption, imageDescription)); - DataStore.saveChangedCache(cache); + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), newLogs); } return imageResult.getPostResult(); } diff --git a/main/src/cgeo/geocaching/LogEntry.java b/main/src/cgeo/geocaching/LogEntry.java index fde0564..ca4a3d1 100644 --- a/main/src/cgeo/geocaching/LogEntry.java +++ b/main/src/cgeo/geocaching/LogEntry.java @@ -14,6 +14,11 @@ import java.util.Collections; import java.util.List; import java.util.regex.Pattern; +/** + * Entry in a log book. This object should not be referenced directly from a Geocache object to reduce the memory usage + * of the Geocache objects. + * + */ public final class LogEntry { private static final Pattern PATTERN_REMOVE_COLORS = Pattern.compile("</?font.*?>", Pattern.CASE_INSENSITIVE); diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index b82614b..42dd58d 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -21,7 +21,6 @@ import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.DatabaseBackupUtils; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.Version; import com.google.zxing.integration.android.IntentIntegrator; @@ -30,7 +29,9 @@ import org.apache.commons.lang3.StringUtils; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; +import rx.schedulers.Schedulers; import rx.subscriptions.Subscriptions; import android.app.AlertDialog; @@ -551,13 +552,13 @@ public class MainActivity extends AbstractActivity { } } }); - RxUtils.subscribeOnIOThenUI(address.onErrorResumeNext(Observable.from(geo.getCoords().toString())), - new Action1<String>() { + AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.from(geo.getCoords().toString()))) + .subscribe(new Action1<String>() { @Override public void call(final String address) { navLocation.setText(address); } - }); + }, Schedulers.io()); } } else { navLocation.setText(geo.getCoords().toString()); @@ -569,7 +570,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoFindOnMap(@SuppressWarnings("unused") final View v) { + public void cgeoFindOnMap(final View v) { findOnMap.setPressed(true); CGeoMap.startActivityLiveMap(this); } @@ -578,7 +579,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoFindNearest(@SuppressWarnings("unused") final View v) { + public void cgeoFindNearest(final View v) { if (app.currentGeo().getCoords() == null) { return; } @@ -591,7 +592,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoFindByOffline(@SuppressWarnings("unused") final View v) { + public void cgeoFindByOffline(final View v) { findByOffline.setPressed(true); CacheListActivity.startActivityOffline(this); } @@ -600,7 +601,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoSearch(@SuppressWarnings("unused") final View v) { + public void cgeoSearch(final View v) { advanced.setPressed(true); startActivity(new Intent(this, SearchActivity.class)); } @@ -609,7 +610,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoPoint(@SuppressWarnings("unused") final View v) { + public void cgeoPoint(final View v) { any.setPressed(true); startActivity(new Intent(this, NavigateAnyPointActivity.class)); } @@ -618,7 +619,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoFilter(@SuppressWarnings("unused") final View v) { + public void cgeoFilter(final View v) { filter.setPressed(true); filter.performClick(); } @@ -627,7 +628,7 @@ public class MainActivity extends AbstractActivity { * @param v * unused here but needed since this method is referenced from XML layout */ - public void cgeoNavSettings(@SuppressWarnings("unused") final View v) { + public void cgeoNavSettings(final View v) { startActivity(new Intent(android.provider.Settings.ACTION_LOCATION_SOURCE_SETTINGS)); } @@ -708,7 +709,7 @@ public class MainActivity extends AbstractActivity { * @param view * unused here but needed since this method is referenced from XML layout */ - public void showAbout(@SuppressWarnings("unused") final View view) { + public void showAbout(final View view) { startActivity(new Intent(this, AboutActivity.class)); } @@ -716,7 +717,7 @@ public class MainActivity extends AbstractActivity { * @param view * unused here but needed since this method is referenced from XML layout */ - public void goSearch(@SuppressWarnings("unused") final View view) { + public void goSearch(final View view) { onSearchRequested(); } diff --git a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java index d577eb5..0a750e0 100644 --- a/main/src/cgeo/geocaching/NavigateAnyPointActivity.java +++ b/main/src/cgeo/geocaching/NavigateAnyPointActivity.java @@ -9,13 +9,13 @@ import cgeo.geocaching.apps.cache.navi.NavigationAppFactory; import cgeo.geocaching.geopoint.DistanceParser; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter; +import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.AbstractViewHolder; import cgeo.geocaching.ui.Formatter; import cgeo.geocaching.ui.dialog.CoordinatesInputDialog; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; @@ -238,11 +238,6 @@ public class NavigateAnyPointActivity extends AbstractActivity { init(); } - @Override - public void onPause() { - super.onPause(); - } - private void init() { latButton.setOnClickListener(new CoordDialogListener()); lonButton.setOnClickListener(new CoordDialogListener()); diff --git a/main/src/cgeo/geocaching/PocketQueryList.java b/main/src/cgeo/geocaching/PocketQueryList.java index e748b29..2ac137f 100644 --- a/main/src/cgeo/geocaching/PocketQueryList.java +++ b/main/src/cgeo/geocaching/PocketQueryList.java @@ -2,13 +2,14 @@ package cgeo.geocaching; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.connector.gc.GCParser; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.collections4.CollectionUtils; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; +import rx.schedulers.Schedulers; import android.app.Activity; import android.app.AlertDialog; @@ -45,19 +46,19 @@ public final class PocketQueryList { public static void promptForListSelection(final Activity activity, final Action1<PocketQueryList> runAfterwards) { final Dialog waitDialog = ProgressDialog.show(activity, activity.getString(R.string.search_pocket_title), activity.getString(R.string.search_pocket_loading), true, true); - RxUtils.subscribeOnIOThenUI(Observable.create(new OnSubscribe<List<PocketQueryList>>() { + AndroidObservable.bindActivity(activity, Observable.create(new OnSubscribe<List<PocketQueryList>>() { @Override public void call(final Subscriber<? super List<PocketQueryList>> subscriber) { subscriber.onNext(GCParser.searchPocketQueryList()); subscriber.onCompleted(); } - }), new Action1<List<PocketQueryList>>() { + })).subscribe(new Action1<List<PocketQueryList>>() { @Override public void call(final List<PocketQueryList> pocketQueryLists) { waitDialog.dismiss(); selectFromPocketQueries(activity, pocketQueryLists, runAfterwards); } - }); + }, Schedulers.io()); } private static void selectFromPocketQueries(final Activity activity, final List<PocketQueryList> pocketQueryList, final Action1<PocketQueryList> runAfterwards) { if (CollectionUtils.isEmpty(pocketQueryList)) { diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java index a59316f..f8552d7 100644 --- a/main/src/cgeo/geocaching/StatusFragment.java +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -3,10 +3,11 @@ package cgeo.geocaching; import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.network.StatusUpdater.Status; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import rx.Subscription; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; +import rx.schedulers.Schedulers; import android.content.Intent; import android.content.res.Resources; @@ -30,53 +31,53 @@ public class StatusFragment extends Fragment { final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false); final ImageView statusIcon = (ImageView) statusGroup.findViewById(R.id.status_icon); final TextView statusMessage = (TextView) statusGroup.findViewById(R.id.status_message); - statusSubscription = RxUtils.subscribeOnIOThenUI(StatusUpdater.latestStatus, new Action1<Status>() { - @Override - public void call(final Status status) { - if (status == null) { - statusGroup.setVisibility(View.INVISIBLE); - return; - } + statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribe(new Action1<Status>() { + @Override + public void call(final Status status) { + if (status == null) { + statusGroup.setVisibility(View.INVISIBLE); + return; + } - final Resources res = getResources(); - final String packageName = getActivity().getPackageName(); + final Resources res = getResources(); + final String packageName = getActivity().getPackageName(); - if (status.icon != null) { - final int iconId = res.getIdentifier(status.icon, "drawable", packageName); - if (iconId != 0) { - statusIcon.setImageResource(iconId); - statusIcon.setVisibility(View.VISIBLE); - } else { - Log.w("StatusHandler: could not find icon corresponding to @drawable/" + status.icon); - statusIcon.setVisibility(View.GONE); - } - } else { - statusIcon.setVisibility(View.GONE); - } + if (status.icon != null) { + final int iconId = res.getIdentifier(status.icon, "drawable", packageName); + if (iconId != 0) { + statusIcon.setImageResource(iconId); + statusIcon.setVisibility(View.VISIBLE); + } else { + Log.w("StatusHandler: could not find icon corresponding to @drawable/" + status.icon); + statusIcon.setVisibility(View.GONE); + } + } else { + statusIcon.setVisibility(View.GONE); + } - String message = status.message; - if (status.messageId != null) { - final int messageId = res.getIdentifier(status.messageId, "string", packageName); - if (messageId != 0) { - message = res.getString(messageId); - } - } + String message = status.message; + if (status.messageId != null) { + final int messageId = res.getIdentifier(status.messageId, "string", packageName); + if (messageId != 0) { + message = res.getString(messageId); + } + } - statusMessage.setText(message); - statusGroup.setVisibility(View.VISIBLE); + statusMessage.setText(message); + statusGroup.setVisibility(View.VISIBLE); - if (status.url != null) { - statusGroup.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(status.url))); + if (status.url != null) { + statusGroup.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(status.url))); + } + }); + } else { + statusGroup.setClickable(false); } - }); - } else { - statusGroup.setClickable(false); - } - } - }); + } + }, Schedulers.io()); return statusGroup; } diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index a14a397..81d23c9 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -20,12 +20,12 @@ import cgeo.geocaching.ui.UserNameClickListener; import cgeo.geocaching.ui.logs.TrackableLogsViewCreator; import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.UnknownTagsHandler; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import rx.android.observables.AndroidObservable; import rx.android.observables.ViewObservable; import rx.functions.Action1; @@ -524,7 +524,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } }); - RxUtils.subscribeThenUI(new HtmlImage(geocode, true, 0, false).fetchDrawable(trackable.getImage()), new Action1<BitmapDrawable>() { + AndroidObservable.bindActivity(TrackableActivity.this, new HtmlImage(geocode, true, 0, false).fetchDrawable(trackable.getImage())).subscribe(new Action1<BitmapDrawable>() { @Override public void call(final BitmapDrawable bitmapDrawable) { trackableImage.setImageDrawable(bitmapDrawable); diff --git a/main/src/cgeo/geocaching/apps/cache/navi/MapsWithMeApp.java b/main/src/cgeo/geocaching/apps/cache/navi/MapsWithMeApp.java new file mode 100644 index 0000000..ea5aebb --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cache/navi/MapsWithMeApp.java @@ -0,0 +1,43 @@ +package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.Waypoint; +import cgeo.geocaching.geopoint.Geopoint; + +import com.mapswithme.maps.api.MapsWithMeApi; + +import android.app.Activity; + +public class MapsWithMeApp extends AbstractPointNavigationApp { + + protected MapsWithMeApp() { + super(getString(R.string.cache_menu_mapswithme), R.id.cache_app_mapswithme, null); + } + + @Override + public void navigate(Activity activity, Geopoint coords) { + navigate(activity, coords, getString(R.string.unknown)); + } + + @Override + public void navigate(Activity activity, Geocache cache) { + navigate(activity, cache.getCoords(), cache.getName()); + } + + private static void navigate(Activity activity, Geopoint coords, String label) { + MapsWithMeApi.showPointOnMap(activity, coords.getLatitude(), coords.getLongitude(), label); + } + + @Override + public void navigate(Activity activity, Waypoint waypoint) { + navigate(activity, waypoint.getCoords(), waypoint.getName()); + } + + @Override + public boolean isInstalled() { + // the library can handle the app not being installed + return true; + } + +} diff --git a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java index bf0e776..3177a29 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/NavigationAppFactory.java @@ -72,7 +72,8 @@ public final class NavigationAppFactory extends AbstractAppFactory { CACHE_BEACON(new CacheBeaconApp(), 14, R.string.pref_navigation_menu_cache_beacon), GCC(new GccApp(), 15, R.string.pref_navigation_menu_gcc), WHERE_YOU_GO(new WhereYouGoApp(), 16, R.string.pref_navigation_menu_where_you_go), - PEBBLE(new PebbleApp(), 17, R.string.pref_navigation_menu_pebble); + PEBBLE(new PebbleApp(), 17, R.string.pref_navigation_menu_pebble), + MAPSWITHME(new MapsWithMeApp(), 22, R.string.pref_navigation_menu_mapswithme); NavigationAppsEnum(final App app, final int id, final int preferenceKey) { this.app = app; diff --git a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java index 4df9d26..8212111 100644 --- a/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java +++ b/main/src/cgeo/geocaching/apps/cachelist/CacheListAppFactory.java @@ -21,7 +21,8 @@ public final class CacheListAppFactory extends AbstractAppFactory { public static final CacheListApp[] apps = { new InternalCacheListMap(), new LocusShowCacheListApp(), - new LocusExportCacheListApp() + new LocusExportCacheListApp(), + new MapsWithMeCacheListApp() }; } diff --git a/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java b/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java new file mode 100644 index 0000000..ba177f0 --- /dev/null +++ b/main/src/cgeo/geocaching/apps/cachelist/MapsWithMeCacheListApp.java @@ -0,0 +1,67 @@ +package cgeo.geocaching.apps.cachelist; + +import cgeo.geocaching.CacheDetailActivity; +import cgeo.geocaching.Geocache; +import cgeo.geocaching.R; +import cgeo.geocaching.SearchResult; +import cgeo.geocaching.apps.AbstractApp; + +import com.mapswithme.maps.api.MWMPoint; +import com.mapswithme.maps.api.MWMResponse; +import com.mapswithme.maps.api.MapsWithMeApi; + +import org.eclipse.jdt.annotation.Nullable; + +import android.app.Activity; +import android.app.PendingIntent; +import android.content.Context; +import android.content.Intent; + +import java.util.List; + +public class MapsWithMeCacheListApp extends AbstractApp implements CacheListApp { + + protected MapsWithMeCacheListApp() { + super(getString(R.string.caches_map_mapswithme), R.id.cache_app_mapswithme, Intent.ACTION_VIEW); + } + + @Override + public boolean invoke(List<Geocache> caches, Activity activity, SearchResult search) { + final MWMPoint[] points = new MWMPoint[caches.size()]; + for (int i = 0; i < points.length; i++) { + 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); + return true; + } + + @Override + public boolean isInstalled() { + // API can handle installing on the fly + return true; + } + + /** + * 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(); + } + } + return null; + } + + private static PendingIntent getPendingIntent(Context context) { + final Intent intent = new Intent(context, CacheDetailActivity.class); + return PendingIntent.getActivity(context, 0, intent, 0); + } + +} diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 56d434d..0549b3f 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -36,6 +36,7 @@ import cgeo.geocaching.utils.SynchronizedDateFormat; import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; @@ -723,9 +724,6 @@ public abstract class GCParser { cache.parseWaypointsFromNote(); - // logs - cache.setLogs(getLogsFromDetails(page, false)); - // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { searchResult.setError(StatusCode.UNKNOWN_ERROR); @@ -734,6 +732,7 @@ public abstract class GCParser { cache.setDetailedUpdatedNow(); searchResult.addAndPutInCache(Collections.singletonList(cache)); + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), getLogsFromDetails(page, false)); return searchResult; } @@ -1831,16 +1830,18 @@ public abstract class GCParser { //cache.setLogs(loadLogsFromDetails(page, cache, false)); if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - final List<LogEntry> allLogs = cache.getLogs(); final List<LogEntry> friendLogs = getLogsFromDetails(page, true); - if (friendLogs != null) { + 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 (allLogs.contains(log)) { - allLogs.get(allLogs.indexOf(log)).friend = true; + if (mergedLogs.contains(log)) { + mergedLogs.get(mergedLogs.indexOf(log)).friend = true; } else { - cache.getLogs().add(log); + mergedLogs.add(log); } } + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), mergedLogs); } } diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java index 280069f..7cced74 100644 --- a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java @@ -4,12 +4,13 @@ import cgeo.geocaching.R; import cgeo.geocaching.loaders.RecaptchaReceiver; import cgeo.geocaching.network.Network; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.io.IOUtils; import rx.Observable; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; +import rx.schedulers.Schedulers; import android.app.Activity; import android.app.AlertDialog; @@ -56,7 +57,7 @@ public class RecaptchaHandler extends Handler { return Observable.empty(); } }); - RxUtils.subscribeOnIOThenUI(captcha, new Action1<Bitmap>() { + AndroidObservable.bindActivity(activity, captcha).subscribe(new Action1<Bitmap>() { @Override public void call(final Bitmap bitmap) { imageView.setImageBitmap(bitmap); @@ -66,7 +67,7 @@ public class RecaptchaHandler extends Handler { public void call(final Throwable throwable) { // Do nothing } - }); + }, Schedulers.io()); reloadButton.setEnabled(true); } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index 712bb26..3c93488 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -372,7 +372,6 @@ final class OkapiClient { } cache.setAttributes(parseAttributes(response.getJSONArray(CACHE_ATTRNAMES), response.optJSONArray(CACHE_ATTR_ACODES))); - cache.setLogs(parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); //TODO: Store license per cache //cache.setLicense(response.getString("attribution_note")); cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false); @@ -388,6 +387,7 @@ final class OkapiClient { cache.setDetailedUpdatedNow(); // save full detailed caches DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); } catch (final JSONException e) { Log.e("OkapiClient.parseCache", e); } diff --git a/main/src/cgeo/geocaching/enumerations/CacheType.java b/main/src/cgeo/geocaching/enumerations/CacheType.java index 35fe7a1..506c791 100644 --- a/main/src/cgeo/geocaching/enumerations/CacheType.java +++ b/main/src/cgeo/geocaching/enumerations/CacheType.java @@ -19,7 +19,8 @@ public enum CacheType { MYSTERY("mystery", "Unknown Cache", "40861821-1835-4e11-b666-8d41064d03fe", R.string.mystery, R.drawable.type_mystery), LETTERBOX("letterbox", "Letterbox hybrid", "4bdd8fb2-d7bc-453f-a9c5-968563b15d24", R.string.letterbox, R.drawable.type_letterbox), EVENT("event", "Event Cache", "69eb8534-b718-4b35-ae3c-a856a55b0874", R.string.event, R.drawable.type_event), - MEGA_EVENT("mega", "Mega-event Cache", "69eb8535-b718-4b35-ae3c-a856a55b0874", R.string.mega, R.drawable.type_mega), + MEGA_EVENT("mega", "Mega-Event Cache", "69eb8535-b718-4b35-ae3c-a856a55b0874", R.string.mega, R.drawable.type_mega), + GIGA_EVENT("giga", "Giga-Event Cache", "51420629-5739-4945-8bdd-ccfd434c0ead", R.string.giga, R.drawable.type_giga), EARTH("earth", "Earthcache", "c66f5cf3-9523-4549-b8dd-759cd2f18db8", R.string.earth, R.drawable.type_earth), CITO("cito", "Cache in Trash out Event", "57150806-bc1a-42d6-9cf0-538d171a2d22", R.string.cito, R.drawable.type_cito), WEBCAM("webcam", "Webcam Cache", "31d2ae3c-c358-4b5f-8dcd-2185bf472d3d", R.string.webcam, R.drawable.type_webcam), @@ -89,7 +90,7 @@ public enum CacheType { } public boolean isEvent() { - return EVENT == this || MEGA_EVENT == this || CITO == this || LOSTANDFOUND == this || BLOCK_PARTY == this || GPS_EXHIBIT == this; + return EVENT == this || MEGA_EVENT == this || CITO == this || GIGA_EVENT == this || LOSTANDFOUND == this || BLOCK_PARTY == this || GPS_EXHIBIT == this; } @Override diff --git a/main/src/cgeo/geocaching/export/GpxSerializer.java b/main/src/cgeo/geocaching/export/GpxSerializer.java index fef91cf..b2587aa 100644 --- a/main/src/cgeo/geocaching/export/GpxSerializer.java +++ b/main/src/cgeo/geocaching/export/GpxSerializer.java @@ -34,7 +34,7 @@ public final class GpxSerializer { public static final String PREFIX_XSI = "http://www.w3.org/2001/XMLSchema-instance"; public static final String PREFIX_GPX = "http://www.topografix.com/GPX/1/0"; public static final String PREFIX_GROUNDSPEAK = "http://www.groundspeak.com/cache/1/0"; - public static final String PREFIX_GSAK = "http://www.gsak.net/xmlv1/4"; + public static final String PREFIX_GSAK = "http://www.gsak.net/xmlv1/6"; public static final String PREFIX_CGEO = "http://www.cgeo.org/wptext/1/0"; /** @@ -74,7 +74,7 @@ public final class GpxSerializer { gpx.attribute(PREFIX_XSI, "schemaLocation", PREFIX_GPX + " http://www.topografix.com/GPX/1/0/gpx.xsd " + PREFIX_GROUNDSPEAK + " http://www.groundspeak.com/cache/1/0/1/cache.xsd " + - PREFIX_GSAK + " http://www.gsak.net/xmlv1/4/gsak.xsd"); + PREFIX_GSAK + " http://www.gsak.net/xmlv1/6/gsak.xsd"); // Split the overall set of geocodes into small chunks. That is a compromise between memory efficiency (because // we don't load all caches fully into memory) and speed (because we don't query each cache separately). @@ -129,8 +129,8 @@ public final class GpxSerializer { "container", cache.getSize().id, "difficulty", Float.toString(cache.getDifficulty()), "terrain", Float.toString(cache.getTerrain()), - "country", cache.getLocation(), - "state", "", + "country", getCountry(cache), + "state", getState(cache), "encoded_hints", cache.getHint()); writeAttributes(cache); @@ -149,6 +149,9 @@ public final class GpxSerializer { writeTravelBugs(cache); gpx.endTag(PREFIX_GROUNDSPEAK, "cache"); + + writeGSAK(cache); + gpx.endTag(PREFIX_GPX, "wpt"); writeWaypoints(cache); @@ -160,6 +163,25 @@ public final class GpxSerializer { } } + private void writeGSAK(final Geocache cache) throws IOException { + gpx.startTag(PREFIX_GSAK, "wptExtension"); + XmlUtils.multipleTexts(gpx, PREFIX_GSAK, + "IsPremium", gpxBoolean(cache.isPremiumMembersOnly()), + "FavPoints", Integer.toString(cache.getFavoritePoints()), + "Watch", gpxBoolean(cache.isOnWatchlist()), + "GcNote", StringUtils.trimToEmpty(cache.getPersonalNote())); + gpx.endTag(PREFIX_GSAK, "wptExtension"); + } + + /** + * @param boolFlag + * @return XML schema compliant boolean representation of the boolean flag. This must be either true, false, 0 or 1, + * but no other value (also not upper case True/False). + */ + private static String gpxBoolean(boolean boolFlag) { + return boolFlag ? "true" : "false"; + } + private void writeWaypoints(final Geocache cache) throws IOException { final List<Waypoint> waypoints = cache.getWaypoints(); final List<Waypoint> ownWaypoints = new ArrayList<Waypoint>(waypoints.size()); @@ -239,12 +261,13 @@ public final class GpxSerializer { } private void writeLogs(final Geocache cache) throws IOException { - if (cache.getLogs().isEmpty()) { + List<LogEntry> logs = cache.getLogs(); + if (logs.isEmpty()) { return; } gpx.startTag(PREFIX_GROUNDSPEAK, "logs"); - for (final LogEntry log : cache.getLogs()) { + for (final LogEntry log : logs) { gpx.startTag(PREFIX_GROUNDSPEAK, "log"); gpx.attribute("", "id", Integer.toString(log.id)); @@ -317,4 +340,27 @@ public final class GpxSerializer { gpx.endTag(PREFIX_GROUNDSPEAK, "attributes"); } + public static String getState(final Geocache cache) { + return getLocationPart(cache, 0); + } + + private static String getLocationPart(final Geocache cache, int partIndex) { + final String location = cache.getLocation(); + if (StringUtils.contains(location, ", ")) { + final String[] parts = StringUtils.split(location, ','); + if (parts.length == 2) { + return StringUtils.trim(parts[partIndex]); + } + } + return StringUtils.EMPTY; + } + + public static String getCountry(final Geocache cache) { + String country = getLocationPart(cache, 1); + if (StringUtils.isNotEmpty(country)) { + return country; + } + // fall back to returning everything, but only for the country + return cache.getLocation(); + } } diff --git a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java index 35e6265..07b4fb4 100644 --- a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java +++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java @@ -121,6 +121,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext } + @SuppressWarnings("static-method") protected boolean requireFiles() { return true; } diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index f5380be..8d328e4 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -42,6 +42,7 @@ import java.util.Collection; import java.util.Date; import java.util.EnumSet; import java.util.HashSet; +import java.util.List; import java.util.Locale; import java.util.Set; import java.util.regex.Pattern; @@ -99,6 +100,7 @@ public abstract class GPXParser extends FileParser { private String parentCacheCode = null; private boolean wptVisited = false; private boolean wptUserDefined = false; + private List<LogEntry> logs = new ArrayList<LogEntry>(); /** * Parser result. Maps geocode to cache. @@ -334,6 +336,7 @@ public abstract class GPXParser extends FileParser { // finally store the cache in the database result.add(geocode); DataStore.saveCache(cache, EnumSet.of(SaveFlag.SAVE_DB)); + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logs); // avoid the cachecache using lots of memory for caches which the user did not actually look at DataStore.removeCache(geocode, EnumSet.of(RemoveFlag.REMOVE_CACHE)); @@ -757,7 +760,7 @@ public abstract class GPXParser extends FileParser { @Override public void end() { if (log.type != LogType.UNKNOWN) { - cache.getLogs().add(log); + logs.add(log); } } }); @@ -876,7 +879,7 @@ public abstract class GPXParser extends FileParser { /** * Add listeners for c:geo extensions - * + * * @param cacheParent */ private void registerCgeoExtensions(final Element cacheParent) { @@ -985,6 +988,7 @@ public abstract class GPXParser extends FileParser { parentCacheCode = null; wptVisited = false; wptUserDefined = false; + logs = new ArrayList<LogEntry>(); cache = new Geocache(this); diff --git a/main/src/cgeo/geocaching/filter/AttributeFilter.java b/main/src/cgeo/geocaching/filter/AttributeFilter.java index 6f0d5da..552a48c 100644 --- a/main/src/cgeo/geocaching/filter/AttributeFilter.java +++ b/main/src/cgeo/geocaching/filter/AttributeFilter.java @@ -1,14 +1,11 @@ package cgeo.geocaching.filter; import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; import cgeo.geocaching.R; -import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; import android.content.res.Resources; -import java.util.EnumSet; import java.util.LinkedList; import java.util.List; @@ -29,11 +26,7 @@ class AttributeFilter extends AbstractFilter { @Override public boolean accepts(final Geocache cache) { - Geocache fullCache = DataStore.loadCache(cache.getGeocode(), EnumSet.of(LoadFlag.LOAD_ATTRIBUTES)); - if (fullCache == null) { - fullCache = cache; - } - return fullCache.getAttributes().contains(attribute); + return cache.getAttributes().contains(attribute); } public static class Factory implements IFilterFactory { diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java index 7e2a83c..e557fc8 100644 --- a/main/src/cgeo/geocaching/list/StoredList.java +++ b/main/src/cgeo/geocaching/list/StoredList.java @@ -147,6 +147,8 @@ public final class StoredList extends AbstractList { public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, 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. + @SuppressWarnings("unused") @Override public void call(final String listName) { final int newId = DataStore.createList(listName); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java index e993548..7a5aab2 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/MapsforgeMapView.java @@ -15,6 +15,7 @@ import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; import org.mapsforge.android.maps.MapView; import org.mapsforge.android.maps.Projection; import org.mapsforge.android.maps.mapgenerator.MapGenerator; @@ -49,7 +50,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } @Override - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { try { // Google Maps and OSM Maps use different zoom levels for the same view. // Here we don't want the Google Maps compatible zoom level, but the actual one. @@ -74,6 +75,7 @@ public class MapsforgeMapView extends MapView implements MapViewImpl { } @Override + @NonNull public GeoPointImpl getMapViewCenter() { GeoPoint point = getMapPosition().getMapCenter(); return new MapsforgeGeoPoint(point.latitudeE6, point.longitudeE6); diff --git a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java index 30caed5..4fa4e02 100644 --- a/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java +++ b/main/src/cgeo/geocaching/maps/mapsforge/v024/MapsforgeMapView024.java @@ -41,7 +41,7 @@ public class MapsforgeMapView024 extends MapView implements MapViewImpl { } @Override - public void draw(Canvas canvas) { + public void draw(@NonNull Canvas canvas) { try { // Google Maps and OSM Maps use different zoom levels for the same view. // Here we don't want the Google Maps compatible zoom level, but the actual one. diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 8ee2ba7..9c55fe9 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -25,6 +25,7 @@ import rx.Scheduler; import rx.Scheduler.Inner; import rx.Subscriber; import rx.functions.Action1; +import rx.functions.Func0; import rx.functions.Func1; import rx.schedulers.Schedulers; import rx.subjects.PublishSubject; @@ -132,6 +133,18 @@ public class HtmlImage implements Html.ImageGetter { return Observable.from(getTransparent1x1Image(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>>() { + @Override + public Observable<? extends BitmapDrawable> call() { + final Bitmap bitmap = loadCachedImage(FileUtils.urlToFile(url), true).getLeft(); + return bitmap != null ? Observable.from(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty(); + } + }).subscribeOn(RxUtils.computationScheduler); + } + final boolean shared = url.contains("/images/icons/icon_"); final String pseudoGeocode = shared ? SHARED : geocode; diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java index 788d5bd..ff4a439 100644 --- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java +++ b/main/src/cgeo/geocaching/sensors/DirectionProvider.java @@ -30,6 +30,7 @@ public class DirectionProvider { private int count = 0; private SensorManager sensorManager; + @Override public void onSensorChanged(final SensorEvent event) { subject.onNext(event.values[0]); @@ -50,23 +51,52 @@ public class DirectionProvider { //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); } - // This will be removed when using a new location service. Until then, it is okay to be used. - @SuppressWarnings("deprecation") @Override public void start(final Context context, final Handler handler) { + if (!hasSensor(context)) { + return; + } if (++count == 1) { - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - sensorManager.registerListener(this, sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION), SensorManager.SENSOR_DELAY_NORMAL, handler); + 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 == false) { + 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 handlerThread = diff --git a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java index c10cb48..0f30142 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java @@ -4,6 +4,7 @@ import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.settings.Settings; import org.apache.commons.lang3.tuple.ImmutablePair; + import rx.Observable; import rx.Subscription; import rx.android.schedulers.AndroidSchedulers; @@ -37,7 +38,7 @@ public abstract class GeoDirHandler { * * @param geoData the new geographical data */ - public void updateGeoData(@SuppressWarnings("unused") final IGeoData geoData) { + public void updateGeoData(final IGeoData geoData) { } /** @@ -46,7 +47,7 @@ public abstract class GeoDirHandler { * * @param direction the new direction */ - public void updateDirection(@SuppressWarnings("unused") final float direction) { + public void updateDirection(final float direction) { } /** @@ -59,7 +60,7 @@ public abstract class GeoDirHandler { * If the device goes fast enough, or if the compass use is not enabled in the settings, * the GPS direction information will be used instead of the compass one. */ - public void updateGeoDir(@SuppressWarnings("unused") final IGeoData geoData, @SuppressWarnings("unused") final float direction) { + public void updateGeoDir(final IGeoData geoData, final float direction) { } private static Observable<Float> fixedDirection() { diff --git a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java index 298270b..917c9c4 100644 --- a/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java +++ b/main/src/cgeo/geocaching/settings/AbstractCheckCredentialsPreference.java @@ -5,12 +5,13 @@ import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; +import rx.schedulers.Schedulers; import rx.util.async.Async; import android.app.ProgressDialog; @@ -64,12 +65,12 @@ public abstract class AbstractCheckCredentialsPreference extends AbstractClickab loginDialog.setCancelable(false); Cookies.clearCookies(); - RxUtils.subscribeOnIOThenUI(Async.start(new Func0<ImmutablePair<StatusCode, ? extends Drawable>>() { + AndroidObservable.bindActivity(activity, Async.start(new Func0<ImmutablePair<StatusCode, ? extends Drawable>>() { @Override public ImmutablePair<StatusCode, ? extends Drawable> call() { return login(); } - }), new Action1<ImmutablePair<StatusCode, ? extends Drawable>>() { + })).subscribe(new Action1<ImmutablePair<StatusCode, ? extends Drawable>>() { @Override public void call(final ImmutablePair<StatusCode, ? extends Drawable> loginInfo) { loginDialog.dismiss(); @@ -85,7 +86,7 @@ public abstract class AbstractCheckCredentialsPreference extends AbstractClickab } activity.initBasicMemberPreferences(); } - }); + }, Schedulers.io()); return false; // no shared preference has to be changed } diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java index 1b18438..cc2de9f 100644 --- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java +++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java @@ -6,13 +6,14 @@ import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.ui.dialog.Dialogs; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import ch.boye.httpclientandroidlib.HttpResponse; import org.apache.commons.lang3.StringUtils; import rx.Observable; +import rx.android.observables.AndroidObservable; import rx.functions.Action1; import rx.functions.Func0; +import rx.schedulers.Schedulers; import android.app.ProgressDialog; import android.content.Context; @@ -52,7 +53,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { activity.getString(R.string.init_sendToCgeo_registering), true); progressDialog.setCancelable(false); - RxUtils.subscribeOnIOThenUI(Observable.defer(new Func0<Observable<Integer>>() { + AndroidObservable.bindActivity(activity, Observable.defer(new Func0<Observable<Integer>>() { @Override public Observable<Integer> call() { final String nam = StringUtils.defaultString(deviceName); @@ -74,7 +75,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { return Observable.empty(); } - }).firstOrDefault(0), new Action1<Integer>() { + }).firstOrDefault(0)).subscribe(new Action1<Integer>() { @Override public void call(final Integer pin) { progressDialog.dismiss(); @@ -86,7 +87,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { Dialogs.message(activity, R.string.init_sendToCgeo, R.string.init_sendToCgeo_register_fail); } } - }); + }, Schedulers.io()); return true; } diff --git a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java index 2b171b4..968dce5 100644 --- a/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java +++ b/main/src/cgeo/geocaching/sorting/AbstractCacheComparator.java @@ -34,12 +34,13 @@ public abstract class AbstractCacheComparator implements CacheComparator { /** * Check necessary preconditions (like missing fields) before running the comparison itself. * Caches not filling the conditions will be placed last, sorted by Geocode. - * + * * The default returns <code>true</code> and can be overridden if needed in child classes. - * + * * @param cache * @return <code>true</code> if the cache holds the necessary data to be compared meaningfully */ + @SuppressWarnings("static-method") protected boolean canCompare(final Geocache cache) { return true; } diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index d827e3e..0d90d9f 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -377,7 +377,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { final boolean lightSkin = Settings.isLightSkin(); - final TouchListener touchListener = new TouchListener(cache, v); + final TouchListener touchListener = new TouchListener(cache); v.setOnClickListener(touchListener); v.setOnLongClickListener(touchListener); v.setOnTouchListener(touchListener); @@ -541,7 +541,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { private final Geocache cache; private final GestureDetector gestureDetector; - public TouchListener(final Geocache cache, final View view) { + public TouchListener(final Geocache cache) { this.cache = cache; gestureDetector = new GestureDetector(getContext(), new FlingGesture(cache)); } diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index d1b2a64..5727971 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -6,11 +6,11 @@ import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.RxUtils; import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import rx.Subscription; +import rx.android.observables.AndroidObservable; import rx.functions.Action0; import rx.functions.Action1; import rx.subscriptions.CompositeSubscription; @@ -118,7 +118,7 @@ public class ImagesList { final ImageView imageView = (ImageView) inflater.inflate(R.layout.image_item, null); assert(imageView != null); - subscriptions.add(RxUtils.subscribeThenUI(imgGetter.fetchDrawable(img.getUrl()), new Action1<BitmapDrawable>() { + subscriptions.add(AndroidObservable.bindActivity(activity, imgGetter.fetchDrawable(img.getUrl())).subscribe(new Action1<BitmapDrawable>() { @Override public void call(final BitmapDrawable image) { display(imageView, image, img, rowView); @@ -214,7 +214,7 @@ public class ImagesList { private void viewImageInStandardApp(final Image img, final BitmapDrawable image) { try { final Intent intent = new Intent().setAction(android.content.Intent.ACTION_VIEW); - final File file = LocalStorage.getStorageFile(geocode, img.getUrl(), true, true); + final File file = img.isLocalFile() ? img.localFile() : LocalStorage.getStorageFile(geocode, img.getUrl(), true, true); if (file.exists()) { intent.setDataAndType(Uri.fromFile(file), mimeTypeForUrl(img.getUrl())); } else { diff --git a/main/src/cgeo/geocaching/utils/FileUtils.java b/main/src/cgeo/geocaching/utils/FileUtils.java index 4a349a8..de068fd 100644 --- a/main/src/cgeo/geocaching/utils/FileUtils.java +++ b/main/src/cgeo/geocaching/utils/FileUtils.java @@ -153,4 +153,32 @@ public final class FileUtils { } return true; } + + /** + * Check if the URL represents a file on the local file system. + * + * @return <tt>true</tt> if the URL scheme is <tt>file</tt>, <tt>false</tt> otherwise + */ + public static boolean isFileUrl(final String url) { + return StringUtils.startsWith(url, "file://"); + } + + /** + * Build an URL from a file name. + * + * @param file a local file name + * @return an URL with the <tt>file</tt> scheme + */ + public static String fileToUrl(final File file) { + return "file://" + file.getAbsolutePath(); + } + + /** + * Local file name when {@link #isLocalFile()} is <tt>true</tt>. + * + * @return the local file + */ + public static File urlToFile(final String url) { + return new File(StringUtils.substring(url, 7)); + } } diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index e226703..ffb7bf3 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -273,9 +273,11 @@ public final class ImageUtils { /** * Decode a base64-encoded string and save the result into a stream. - * - * @param inString the encoded string - * @param outFile the file to save the decoded result into + * + * @param inString + * the encoded string + * @param out + * the stream to save the decoded result into */ public static void decodeBase64ToStream(final String inString, final OutputStream out) throws IOException { Base64InputStream in = null; diff --git a/main/src/cgeo/geocaching/utils/MiscUtils.java b/main/src/cgeo/geocaching/utils/MiscUtils.java deleted file mode 100644 index 122c4eb..0000000 --- a/main/src/cgeo/geocaching/utils/MiscUtils.java +++ /dev/null @@ -1,40 +0,0 @@ -package cgeo.geocaching.utils; - -import org.apache.commons.collections4.iterators.IteratorIterable; -import org.apache.commons.lang3.NotImplementedException; - -import java.util.Iterator; -import java.util.List; - -final public class MiscUtils { - - private MiscUtils() {} // Do not instantiate - - public static <T> Iterable<List<T>> buffer(final List<T> original, final int n) { - if (n <= 0) { - throw new IllegalArgumentException("buffer size must be positive"); - } - return new IteratorIterable<List<T>>(new Iterator<List<T>>() { - final int size = original.size(); - int next = 0; - - @Override - public boolean hasNext() { - return next < size; - } - - @Override - public List<T> next() { - final List<T> result = original.subList(next, Math.min(next + n, size)); - next += n; - return result; - } - - @Override - public void remove() { - throw new NotImplementedException("remove"); - } - }); - } - -} diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index 9926bab..8e7864c 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -1,11 +1,6 @@ package cgeo.geocaching.utils; -import rx.Observable; -import rx.Observer; import rx.Scheduler; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action1; import rx.schedulers.Schedulers; import java.util.concurrent.LinkedBlockingQueue; @@ -20,32 +15,4 @@ public class 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 static <T> Subscription subscribeThenUI(final Observable<T> observable, final Observer<T> observer) { - return observable.observeOn(AndroidSchedulers.mainThread()).subscribe(observer); - } - - public static <T> Subscription subscribeThenUI(final Observable<T> observable, final Action1<T> action) { - return observable.observeOn(AndroidSchedulers.mainThread()).subscribe(action); - } - - public static <T> Subscription subscribeThenUI(final Observable<T> observable, final Action1<T> action, final Action1<Throwable> onError) { - return observable.observeOn(AndroidSchedulers.mainThread()).subscribe(action, onError); - } - - public static <T> Observable<T> onIO(final Observable<T> observable) { - return observable.subscribeOn(Schedulers.io()); - } - - public static <T> Subscription subscribeOnIOThenUI(final Observable<T> observable, final Observer<T> observer) { - return subscribeThenUI(onIO(observable), observer); - } - - public static <T> Subscription subscribeOnIOThenUI(final Observable<T> observable, final Action1<T> action) { - return subscribeThenUI(onIO(observable), action); - } - - public static <T> Subscription subscribeOnIOThenUI(final Observable<T> observable, final Action1<T> action, final Action1<Throwable> onError) { - return subscribeThenUI(onIO(observable), action, onError); - } - } |
