diff options
Diffstat (limited to 'main')
44 files changed, 659 insertions, 806 deletions
diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml index 1884a9b..6e10c96 100644 --- a/main/AndroidManifest.xml +++ b/main/AndroidManifest.xml @@ -308,6 +308,7 @@ android:name="cgeo.geocaching.TrackableActivity" android:configChanges="keyboardHidden|orientation" android:label="@string/app_name" > + <!-- TravelBug URL via coord.info redirection --> <intent-filter> <action android:name="android.intent.action.VIEW" /> @@ -326,6 +327,19 @@ <category android:name="android.intent.category.BROWSABLE" /> <data + android:host="www.coord.info" + android:pathPrefix="/TB" + android:scheme="http" /> + </intent-filter> + + <!-- TravelBug URL tracking page --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data android:host="geocaching.com" android:pathPrefix="/track/details.aspx" android:scheme="http" /> @@ -341,6 +355,55 @@ android:pathPrefix="/track/details.aspx" android:scheme="http" /> </intent-filter> + + <!-- GeoKrety URLs --> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="geokrety.org" + android:pathPrefix="/konkret.php" + android:scheme="http" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="www.geokrety.org" + android:pathPrefix="/konkret.php" + android:scheme="http" /> + </intent-filter> + + <!-- Geokrety QR code URLs, not yet implemented + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="geokrety.org" + android:pathPrefix="/m/qr.php" + android:scheme="http" /> + </intent-filter> + <intent-filter> + <action android:name="android.intent.action.VIEW" /> + + <category android:name="android.intent.category.DEFAULT" /> + <category android:name="android.intent.category.BROWSABLE" /> + + <data + android:host="www.geokrety.org" + android:pathPrefix="/m/qr.php" + android:scheme="http" /> + </intent-filter> + --> </activity> <activity android:name=".CompassActivity" diff --git a/main/libs/android-support-v4.jar b/main/libs/android-support-v4.jar Binary files differindex 9056828..96644ed 100644 --- a/main/libs/android-support-v4.jar +++ b/main/libs/android-support-v4.jar diff --git a/main/libs/rxjava-android-0.15.1.jar b/main/libs/rxjava-android-0.15.1.jar Binary files differnew file mode 100644 index 0000000..4e50a44 --- /dev/null +++ b/main/libs/rxjava-android-0.15.1.jar diff --git a/main/libs/rxjava-core-0.15.1.jar b/main/libs/rxjava-core-0.15.1.jar Binary files differnew file mode 100644 index 0000000..5759337 --- /dev/null +++ b/main/libs/rxjava-core-0.15.1.jar diff --git a/main/proguard-project.txt b/main/proguard-project.txt index 03ef6b3..eae0a42 100644 --- a/main/proguard-project.txt +++ b/main/proguard-project.txt @@ -13,6 +13,11 @@ # apache.commons.collections has some bean related collections, which are undefined in Android -dontwarn java.beans.* +# rxjava includes references to the test frameworks within their class files +-dontwarn org.mockito.** +-dontwarn org.junit.** +-dontwarn org.robolectric.** + #-dontnote org.apache.commons.logging.** -keep public class cgeo.geocaching.* diff --git a/main/res/layout/main_activity.xml b/main/res/layout/main_activity.xml index ef86954..9e124a1 100644 --- a/main/res/layout/main_activity.xml +++ b/main/res/layout/main_activity.xml @@ -35,8 +35,7 @@ android:gravity="center" android:orientation="vertical" > - <LinearLayout - style="@style/icon_mainscreen_row" > + <LinearLayout style="@style/icon_mainscreen_row" > <LinearLayout style="@style/icon_mainscreen_cell" @@ -52,8 +51,7 @@ android:text="@string/live_map_button" /> </LinearLayout> - <LinearLayout - style="@style/icon_mainscreen_cell" > + <LinearLayout style="@style/icon_mainscreen_cell" > <ImageView android:id="@+id/nearest" @@ -76,8 +74,7 @@ style="@style/icon_mainscreen_count" android:textIsSelectable="false" /> - <LinearLayout - style="@style/icon_mainscreen_cell_counter" > + <LinearLayout style="@style/icon_mainscreen_cell_counter" > <ImageView android:id="@+id/search_offline" @@ -91,8 +88,7 @@ </RelativeLayout> </LinearLayout> - <LinearLayout - style="@style/icon_mainscreen_row" > + <LinearLayout style="@style/icon_mainscreen_row" > <LinearLayout style="@style/icon_mainscreen_cell" @@ -146,7 +142,6 @@ android:layout_alignParentBottom="true" android:layout_marginLeft="6dip" android:layout_marginRight="6dip" - android:onClick="cgeoNavSettings" android:orientation="vertical" > <LinearLayout @@ -158,11 +153,13 @@ <TextView android:id="@+id/nav_location" style="@style/location_current" + android:onClick="cgeoNavSettings" android:text="@string/loc_trying" /> <RelativeLayout android:layout_width="fill_parent" - android:layout_height="wrap_content" > + android:layout_height="wrap_content" + android:onClick="cgeoNavSettings" > <TextView android:id="@+id/nav_type" diff --git a/main/res/values-de/strings.xml b/main/res/values-de/strings.xml index 927cce0..e36a62e 100644 --- a/main/res/values-de/strings.xml +++ b/main/res/values-de/strings.xml @@ -806,7 +806,7 @@ <string name="user_menu_view_found">Gefundene Caches</string> <string name="user_menu_open_browser">Profil im Browser öffnen</string> <string name="user_menu_send_message">Nachricht senden</string> - <string name="user_menu_open_contact">Öffne Adressbuch</string> + <string name="user_menu_open_contact">Öffne Kontakt</string> <string name="navigation">Navigation</string> <string name="compass_title">Kompass</string> <string name="use_gps">Nur GPS nutzen</string> diff --git a/main/res/values/changelog_master.xml b/main/res/values/changelog_master.xml index 1e3c3d4..8ebc0f7 100644 --- a/main/res/values/changelog_master.xml +++ b/main/res/values/changelog_master.xml @@ -2,5 +2,12 @@ <resources> <!-- changelog for the master branch --> <string name="changelog_master" translatable="false"> + <b>Next feature release</b>\n + <b>New Features:</b>\n + · Parallel loading from different platforms to speed up live map\n + <b>Bugfixing:</b>\n + · \n + \n + \n </string> </resources> diff --git a/main/res/values/preference_keys.xml b/main/res/values/preference_keys.xml index 0e4675d..ad08ec9 100644 --- a/main/res/values/preference_keys.xml +++ b/main/res/values/preference_keys.xml @@ -60,6 +60,7 @@ <string name="pref_fakekey_preference_backup_info">fakekey_preference_backup_info</string> <string name="pref_fakekey_preference_backup">fakekey_preference_backup</string> <string name="pref_fakekey_preference_restore">fakekey_preference_restore</string> + <string name="pref_fakekey_preference_maintenance_directories">pref_fakekey_preference_maintenance_directories</string> <string name="pref_dbonsdcard">dbonsdcard</string> <string name="pref_debug">debug</string> <!-- preferences used internally --> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index 7b40811..75b0ae6 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -525,6 +525,9 @@ <string name="init_use_native_ua">Android browser</string> <string name="init_summary_use_native_ua">Identify as Android browser. Solves login problems when using certain network providers.</string> <string name="init_rendertheme_folder">Map Themes Directory</string> + <string name="init_maintenance">Maintenance</string> + <string name="init_maintenance_directories_note">c:geo stores images, log images and other files related to a cache in a separate directory. In some cases (like importing/exporting the database) this directory may contain outdated files, which can be deleted here.</string> + <string name="init_maintenance_directories">Delete orphaned files</string> <string name="settings_open_website">Open website</string> <string name="settings_settings">Settings</string> <string name="settings_information">Information</string> diff --git a/main/res/xml/preferences.xml b/main/res/xml/preferences.xml index de0914e..2cef219 100644 --- a/main/res/xml/preferences.xml +++ b/main/res/xml/preferences.xml @@ -609,6 +609,15 @@ android:key="@string/pref_dbonsdcard" android:title="@string/init_dbonsdcard" /> </PreferenceCategory> + <PreferenceCategory android:title="@string/init_maintenance" > + <cgeo.geocaching.settings.TextPreference + android:layout="@layout/text_preference" + android:text="@string/init_maintenance_directories_note" /> + + <Preference + android:key="@string/pref_fakekey_preference_maintenance_directories" + android:title="@string/init_maintenance_directories" /> + </PreferenceCategory> <PreferenceCategory android:title="@string/init_debug_title" > <cgeo.geocaching.settings.TextPreference android:layout="@layout/text_preference" diff --git a/main/src/cgeo/geocaching/AbstractPopupActivity.java b/main/src/cgeo/geocaching/AbstractPopupActivity.java index 5f24030..38e37da 100644 --- a/main/src/cgeo/geocaching/AbstractPopupActivity.java +++ b/main/src/cgeo/geocaching/AbstractPopupActivity.java @@ -40,7 +40,7 @@ public abstract class AbstractPopupActivity extends AbstractActivity implements private final GeoDirHandler geoUpdate = new GeoDirHandler() { @Override - protected void updateGeoData(final IGeoData geo) { + public void updateGeoData(final IGeoData geo) { try { if (geo.getCoords() != null && cache != null && cache.getCoords() != null) { cacheDistance.setText(Units.getDistanceFromKilometers(geo.getCoords().distanceTo(cache.getCoords()))); diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index 4068e38..dce4a13 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -176,24 +176,15 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc // set title in code, as the activity needs a hard coded title due to the intent filters setTitle(res.getString(R.string.cache)); - String geocode = null; - - // TODO Why can it happen that search is not null? onCreate should be called only once and it is not set before. - if (search != null) { - cache = search.getFirstCacheFromResult(LoadFlags.LOAD_ALL_DB_ONLY); - if (cache != null && cache.getGeocode() != null) { - geocode = cache.getGeocode(); - } - } - // get parameters final Bundle extras = getIntent().getExtras(); final Uri uri = getIntent().getData(); // try to get data from extras String name = null; + String geocode = null; String guid = null; - if (geocode == null && extras != null) { + if (extras != null) { geocode = extras.getString(Intents.EXTRA_GEOCODE); name = extras.getString(Intents.EXTRA_NAME); guid = extras.getString(Intents.EXTRA_GUID); @@ -542,20 +533,19 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg((String) msg.obj); } else { - CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); + final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); if (activity == null) { return; } - SearchResult search = activity.getSearch(); - if (search == null) { + if (activity.search == null) { showToast(R.string.err_dwld_details_failed); dismissProgress(); finishActivity(); return; } - if (search.getError() != null) { - activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + search.getError().getErrorString(activity.getResources()) + "."); + if (activity.search.getError() != null) { + activity.showToast(activity.getResources().getString(R.string.err_dwld_details_failed) + " " + activity.search.getError().getErrorString(activity.getResources()) + "."); dismissProgress(); finishActivity(); return; @@ -1368,7 +1358,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc final LinearLayout layout = (LinearLayout) view.findViewById(R.id.favpoint_box); final boolean supportsFavoritePoints = cache.supportsFavoritePoints(); layout.setVisibility(supportsFavoritePoints ? View.VISIBLE : View.GONE); - if (!supportsFavoritePoints || cache.isOwner() || !Settings.isPremiumMember()) { + if (!supportsFavoritePoints || cache.isOwner() || !Settings.isGCPremiumMember()) { return; } final Button buttonAdd = (Button) view.findViewById(R.id.add_to_favpoint); @@ -2255,10 +2245,6 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc return cache; } - public SearchResult getSearch() { - return search; - } - private static class StoreCacheHandler extends SimpleCancellableHandler { public StoreCacheHandler(CacheDetailActivity activity, Progress progress) { diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index cc8b178..5e189b3 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -304,13 +304,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA showProgress(false); progress.dismiss(); - - if (!isPaused()) { - // If the current activity has been paused, then we do not want to fiddle with the - // GPS and direction states. If the activity later gets resumed, its onResume() - // function will take care of turning the GPS back on. - startGeoAndDir(); - } } } }; @@ -472,12 +465,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public void onResume() { super.onResume(); - startGeoAndDir(); + geoDirHandler.startGeo(); + if (Settings.isLiveMap()) { + geoDirHandler.startDir(); + } adapter.setSelectMode(false); setAdapterCurrentCoordinates(true); - if (loadCachesHandler != null && search != null) { + if (search != null) { replaceCacheListFromSearch(); loadCachesHandler.sendEmptyMessage(0); } @@ -503,8 +499,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override public void onPause() { - removeGeoAndDir(); - + geoDirHandler.stopGeoAndDir(); super.onPause(); } @@ -742,8 +737,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public void run(IFilter selectedFilter) { if (selectedFilter != null) { setFilter(selectedFilter); - } - else { + } else { // clear filter setFilter(null); } @@ -960,7 +954,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA boolean enableMore = (type != CacheListType.OFFLINE && cacheList.size() < MAX_LIST_ITEMS); if (enableMore && search != null) { final int count = search.getTotalCountGC(); - enableMore = enableMore && count > 0 && cacheList.size() < count; + enableMore = count > 0 && cacheList.size() < count; } if (enableMore) { @@ -973,17 +967,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA listFooter.setClickable(enableMore); } - private void startGeoAndDir() { - geoDirHandler.startGeo(); - if (Settings.isLiveMap()) { - geoDirHandler.startDir(); - } - } - - private void removeGeoAndDir() { - geoDirHandler.stopGeoAndDir(); - } - private void importGpx() { GpxFileListActivity.startSubActivity(this, listId); } @@ -1002,7 +985,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // provided to this method as a parameter. Pull that uri using "resultData.getData()" if (data != null) { final Uri uri = data.getData(); - new GPXImporter(CacheListActivity.this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri)); + new GPXImporter(this, listId, importGpxAttachementFinishedHandler).importGPX(uri, null, getDisplayName(uri)); } } @@ -1146,7 +1129,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override public void run() { - removeGeoAndDir(); // First refresh caches that do not yet have static maps to get them a chance to get a copy // before the limit expires, unless we do not want to store offline maps. final List<Geocache> allCaches = Settings.isStoreOfflineMaps() ? @@ -1190,7 +1172,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - private class LoadFromWebThread extends Thread { + private static class LoadFromWebThread extends Thread { final private Handler handler; final private int listIdLFW; @@ -1207,9 +1189,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override public void run() { - - removeGeoAndDir(); - int delay = -1; int times = 0; @@ -1267,8 +1246,6 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } handler.sendEmptyMessage(ret); - - startGeoAndDir(); } } @@ -1283,9 +1260,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA @Override protected Void doInBackgroundInternal(Geocache[] caches) { - removeGeoAndDir(); DataStore.markDropped(Arrays.asList(caches)); - startGeoAndDir(); return null; } @@ -1670,8 +1645,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA break; case POCKET: final String guid = extras.getString(Intents.EXTRA_POCKET_GUID); - final String pocket_name = extras.getString(Intents.EXTRA_NAME); - title = pocket_name; + title = extras.getString(Intents.EXTRA_NAME); loader = new PocketGeocacheListLoader(app, guid); break; } diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index 2500d10..1cea5f1 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -1,10 +1,10 @@ package cgeo.geocaching; -import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.ui.dialog.Dialogs; -import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; +import rx.Observable; + import android.app.Activity; import android.app.Application; import android.app.ProgressDialog; @@ -14,12 +14,11 @@ import java.util.concurrent.atomic.AtomicBoolean; public class CgeoApplication extends Application { - private volatile GeoDataProvider geo; - private volatile DirectionProvider dir; + private volatile Observable<IGeoData> geo; + private volatile Observable<Float> dir; private boolean forceRelog = false; // c:geo needs to log into cache providers public boolean showLoginToast = true; //login toast shown just once. private boolean liveMapHintShown = false; // livemap hint has been shown - final private StatusUpdater statusUpdater = new StatusUpdater(); private static CgeoApplication instance; public CgeoApplication() { @@ -35,26 +34,11 @@ public class CgeoApplication extends Application { } @Override - public void onCreate() { - new Thread(statusUpdater).start(); - } - - @Override public void onLowMemory() { Log.i("Cleaning applications cache."); DataStore.removeAllFromCache(); } - @Override - public void onTerminate() { - Log.d("Terminating c:geo…"); - - DataStore.clean(); - DataStore.closeDb(); - - super.onTerminate(); - } - /** * Move the database to/from external cgdata in a new thread, * showing a progress window @@ -82,29 +66,11 @@ public class CgeoApplication extends Application { }.start(); } - /** - * Register an observer to receive GeoData information. - * <br/> - * If there is a chance that no observers are registered before this - * method is called, it is necessary to call it from a task implementing - * a looper interface as the data provider will use listeners that - * require a looper thread to run. - * - * @param observer a geodata observer - */ - public void addGeoObserver(final IObserver<? super IGeoData> observer) { - currentGeoObject().addObserver(observer); - } - - public void deleteGeoObserver(final IObserver<? super IGeoData> observer) { - currentGeoObject().deleteObserver(observer); - } - - private GeoDataProvider currentGeoObject() { + public Observable<IGeoData> currentGeoObject() { if (geo == null) { synchronized(this) { if (geo == null) { - geo = new GeoDataProvider(this); + geo = GeoDataProvider.create(this); } } } @@ -112,22 +78,14 @@ public class CgeoApplication extends Application { } public IGeoData currentGeo() { - return currentGeoObject().getMemory(); + return currentGeoObject().first().toBlockingObservable().single(); } - public void addDirectionObserver(final IObserver<? super Float> observer) { - currentDirObject().addObserver(observer); - } - - public void deleteDirectionObserver(final IObserver<? super Float> observer) { - currentDirObject().deleteObserver(observer); - } - - private DirectionProvider currentDirObject() { + public Observable<Float> currentDirObject() { if (dir == null) { synchronized(this) { if (dir == null) { - dir = new DirectionProvider(this); + dir = DirectionProvider.create(this); } } } @@ -135,11 +93,7 @@ public class CgeoApplication extends Application { } public Float currentDirection() { - return currentDirObject().getMemory(); - } - - public StatusUpdater getStatusUpdater() { - return statusUpdater; + return currentDirObject().first().toBlockingObservable().single(); } public boolean isLiveMapHintShown() { diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index 8955afd..6fc2de1 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -150,12 +150,13 @@ public class CompassActivity extends AbstractActivity { final CgeoApplication app = CgeoApplication.getInstance(); final IGeoData geo = app.currentGeo(); if (geo != null) { - geoDirHandler.update(geo); + geoDirHandler.updateGeoData(geo); } final Float dir = app.currentDirection(); if (dir != null) { - geoDirHandler.update(dir); + geoDirHandler.updateDirection(dir); } + } @Override diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index 6da1af8..9d8a640 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -811,12 +811,19 @@ public class DataStore { /** * Remove obsolete cache directories in c:geo private storage. - * + */ + public static void removeObsoleteCacheDirectories() { + removeObsoleteCacheDirectories(database); + } + + /** + * Remove obsolete cache directories in c:geo private storage. + * * @param db * the read-write database to use */ private static void removeObsoleteCacheDirectories(final SQLiteDatabase db) { - final Pattern oldFilePattern = Pattern.compile("^[GC|TB|O][A-Z0-9]{4,7}$"); + final Pattern oldFilePattern = Pattern.compile("^[GC|TB|EC|GK|O][A-Z0-9]{4,7}$"); final SQLiteStatement select = db.compileStatement("select count(*) from " + dbTableCaches + " where geocode = ?"); final File[] files = LocalStorage.getStorage().listFiles(); final ArrayList<File> toRemove = new ArrayList<File>(files.length); @@ -2302,11 +2309,6 @@ public class DataStore { return new SearchResult(geocodes); } - /** delete caches from the DB store 3 days or more before */ - public static void clean() { - clean(false); - } - /** * Remove caches with listId = 0 * @@ -2355,6 +2357,8 @@ public class DataStore { cursor.close(); + geocodes = exceptCachesWithOfflineLog(geocodes); + if (!geocodes.isEmpty()) { Log.d("Database clean: removing " + geocodes.size() + " geocaches from listId=0"); removeCaches(geocodes, LoadFlags.REMOVE_ALL); @@ -2367,6 +2371,33 @@ public class DataStore { databaseCleaned = true; } + /** + * remove all geocodes from the given list of geocodes where an offline log exists + * + * @param geocodes + * @return + */ + private static Set<String> exceptCachesWithOfflineLog(Set<String> geocodes) { + if (geocodes.isEmpty()) { + return geocodes; + } + + init(); + final Cursor cursor = database.query( + dbTableLogsOffline, + new String[] { "geocode" }, + null, + null, + null, + null, + null); + + final List<String> geocodesWithOfflineLog = Arrays.asList(getFirstColumn(cursor)); + + geocodes.removeAll(geocodesWithOfflineLog); + return geocodes; + } + public static void removeAllFromCache() { // clean up CacheCache cacheCache.removeAllFromCache(); diff --git a/main/src/cgeo/geocaching/DirectionProvider.java b/main/src/cgeo/geocaching/DirectionProvider.java index ae58fed..cc44155 100644 --- a/main/src/cgeo/geocaching/DirectionProvider.java +++ b/main/src/cgeo/geocaching/DirectionProvider.java @@ -1,9 +1,13 @@ package cgeo.geocaching; import cgeo.geocaching.compatibility.Compatibility; -import cgeo.geocaching.utils.MemorySubject; -import edu.umd.cs.findbugs.annotations.SuppressFBWarnings; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subjects.BehaviorSubject; import android.app.Activity; import android.content.Context; @@ -12,58 +16,61 @@ import android.hardware.SensorEvent; import android.hardware.SensorEventListener; import android.hardware.SensorManager; -public class DirectionProvider extends MemorySubject<Float> implements SensorEventListener { +public class DirectionProvider implements OnSubscribeFunc<Float> { private final SensorManager sensorManager; + private final BehaviorSubject<Float> subject = BehaviorSubject.create(0.0f); - // Previous values signaled to observers to avoid re-sending the same value when the - // device doesn't change orientation. The orientation is usually given with a 1 degree - // precision by Android, so it is not uncommon to obtain exactly the same value several - // times. - private float previous = -1; - - public DirectionProvider(final Context context) { - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - + static public Observable<Float> create(final Context context) { + return new DirectionProvider((SensorManager) context.getSystemService(Context.SENSOR_SERVICE)).worker.refCount(); } - @Override - protected void onFirstObserver() { - @SuppressWarnings("deprecation") - // This will be removed when using a new location service. Until then, it is okay to be used. - final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); - sensorManager.registerListener(this, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); + private DirectionProvider(final SensorManager sensorManager) { + this.sensorManager = sensorManager; } @Override - protected void onLastObserver() { - sensorManager.unregisterListener(this); + public Subscription onSubscribe(final Observer<? super Float> observer) { + return subject.distinctUntilChanged().subscribe(observer); } - @Override - public void onAccuracyChanged(final Sensor sensor, int accuracy) { - /* - * There is a bug in Android, which apparently causes this method to be called every - * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging - * this event leads to the log being flooded with multiple entries _per second_, - * which I experienced when running cgeo in a building (with GPS and network being - * unreliable). - * - * See for example https://code.google.com/p/android/issues/detail?id=14792 - */ + private final ConnectableObservable<Float> worker = new ConnectableObservable<Float>(this) { + @Override + public Subscription connect() { + @SuppressWarnings("deprecation") + // This will be removed when using a new location service. Until then, it is okay to be used. + final Sensor defaultSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + final SensorEventListener listener = new SensorEventListener() { + @Override + public void onSensorChanged(final SensorEvent event) { + subject.onNext(event.values[0]); + } - //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); - } + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + /* + * There is a bug in Android, which apparently causes this method to be called every + * time the sensor _value_ changed, even if the _accuracy_ did not change. So logging + * this event leads to the log being flooded with multiple entries _per second_, + * which I experienced when running cgeo in a building (with GPS and network being + * unreliable). + * + * See for example https://code.google.com/p/android/issues/detail?id=14792 + */ - @Override - @SuppressFBWarnings("FE_FLOATING_POINT_EQUALITY") - public void onSensorChanged(final SensorEvent event) { - final float direction = event.values[0]; - if (direction != previous) { - notifyObservers(direction); - previous = direction; + //Log.i(Settings.tag, "Compass' accuracy is low (" + accuracy + ")"); + } + }; + + sensorManager.registerListener(listener, defaultSensor, SensorManager.SENSOR_DELAY_NORMAL); + return new Subscription() { + @Override + public void unsubscribe() { + sensorManager.unregisterListener(listener); + } + }; } - } + }; /** * Take the phone rotation (through a given activity) in account and adjust the direction. @@ -72,6 +79,7 @@ public class DirectionProvider extends MemorySubject<Float> implements SensorEve * @param direction the unadjusted direction in degrees, in the [0, 360[ range * @return the adjusted direction in degrees, in the [0, 360[ range */ + public static float getDirectionNow(final Activity activity, final float direction) { return Compatibility.getDirectionNow(direction, activity); } diff --git a/main/src/cgeo/geocaching/GeoDataProvider.java b/main/src/cgeo/geocaching/GeoDataProvider.java index 73aefce..c61b7e7 100644 --- a/main/src/cgeo/geocaching/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/GeoDataProvider.java @@ -3,9 +3,14 @@ package cgeo.geocaching; import cgeo.geocaching.enumerations.LocationProviderType; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.MemorySubject; import org.apache.commons.lang3.StringUtils; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.observables.ConnectableObservable; +import rx.subjects.BehaviorSubject; import android.content.Context; import android.location.GpsSatellite; @@ -15,22 +20,14 @@ import android.location.LocationListener; import android.location.LocationManager; import android.os.Bundle; -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.TimeUnit; - -/** - * Provide information about the user location. This class should be instantiated only once per application. - */ -class GeoDataProvider extends MemorySubject<IGeoData> { +class GeoDataProvider implements OnSubscribeFunc<IGeoData> { private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last"; private final LocationManager geoManager; - private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); private final LocationData gpsLocation = new LocationData(); private final LocationData netLocation = new LocationData(); - private final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); - private final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); - private final Unregisterer unregisterer = new Unregisterer(); + private final BehaviorSubject<IGeoData> subject; + public boolean gpsEnabled = false; public int satellitesVisible = 0; public int satellitesFixed = 0; @@ -111,51 +108,6 @@ class GeoDataProvider extends MemorySubject<IGeoData> { } } - private class Unregisterer extends Thread { - - private boolean unregisterRequested = false; - private final ArrayBlockingQueue<Boolean> queue = new ArrayBlockingQueue<Boolean>(1); - - public void cancelUnregister() { - try { - queue.put(false); - } catch (final InterruptedException e) { - // Do nothing - } - } - - public void lateUnregister() { - try { - queue.put(true); - } catch (final InterruptedException e) { - // Do nothing - } - } - - @Override - public void run() { - try { - while (true) { - if (unregisterRequested) { - final Boolean element = queue.poll(2500, TimeUnit.MILLISECONDS); - if (element == null) { - // Timeout - unregisterListeners(); - unregisterRequested = false; - } else { - unregisterRequested = element; - } - } else { - unregisterRequested = queue.take(); - } - } - } catch (final InterruptedException e) { - // Do nothing - } - } - - } - /** * Build a new geo data provider object. * <p/> @@ -164,10 +116,50 @@ class GeoDataProvider extends MemorySubject<IGeoData> { * * @param context the context used to retrieve the system services */ - GeoDataProvider(final Context context) { + protected GeoDataProvider(final Context context) { geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - unregisterer.start(); + subject = BehaviorSubject.create(findInitialLocation()); + } + + public static Observable<IGeoData> create(final Context context) { + final GeoDataProvider provider = new GeoDataProvider(context); + return provider.worker.refCount(); + } + @Override + public Subscription onSubscribe(final Observer<? super IGeoData> observer) { + return subject.subscribe(observer); + } + + final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) { + @Override + public Subscription connect() { + final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); + geoManager.addGpsStatusListener(gpsStatusListener); + + final Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); + final Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); + + for (final Listener listener : new Listener[] { networkListener, gpsListener }) { + try { + geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); + } catch (final Exception e) { + Log.w("There is no location provider " + listener.locationProvider); + } + } + + return new Subscription() { + @Override + public void unsubscribe() { + geoManager.removeUpdates(networkListener); + geoManager.removeUpdates(gpsListener); + geoManager.removeGpsStatusListener(gpsStatusListener); + } + }; + } + }; + + private IGeoData findInitialLocation() { final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER); try { // Try to find a sensible initial location from the last locations known to Android. @@ -195,45 +187,13 @@ class GeoDataProvider extends MemorySubject<IGeoData> { } // Start with an historical GeoData just in case someone queries it before we get // a chance to get any information. - notifyObservers(new GeoData(initialLocation, false, 0, 0)); + return new GeoData(initialLocation, false, 0, 0); } private static void copyCoords(final Location target, final Location source) { target.setLatitude(source.getLatitude()); target.setLongitude(source.getLongitude()); } - private void registerListeners() { - geoManager.addGpsStatusListener(gpsStatusListener); - - for (final Listener listener : new Listener[] { networkListener, gpsListener }) { - try { - geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); - } catch (final Exception e) { - Log.w("There is no location provider " + listener.locationProvider); - } - } - } - - private synchronized void unregisterListeners() { - // This method must be synchronized because it will be called asynchronously from the Unregisterer thread. - // We check that no observers have been re-added to prevent a race condition. - if (sizeObservers() == 0) { - geoManager.removeUpdates(networkListener); - geoManager.removeUpdates(gpsListener); - geoManager.removeGpsStatusListener(gpsStatusListener); - } - } - - @Override - protected void onFirstObserver() { - unregisterer.cancelUnregister(); - registerListeners(); - } - - @Override - protected void onLastObserver() { - unregisterer.lateUnregister(); - } private class Listener implements LocationListener { private final String locationProvider; @@ -336,7 +296,7 @@ class GeoDataProvider extends MemorySubject<IGeoData> { // We do not necessarily get signalled when satellites go to 0/0. final int visible = gpsLocation.isRecent() ? satellitesVisible : 0; final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed); - notifyObservers(current); + subject.onNext(current); } } diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index 7905fcf..4927b10 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -24,9 +24,15 @@ import cgeo.geocaching.utils.Version; import com.google.zxing.integration.android.IntentIntegrator; import com.google.zxing.integration.android.IntentResult; - -import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import rx.Observable; +import rx.Observable.OnSubscribeFunc; +import rx.Observer; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.concurrency.Schedulers; +import rx.subscriptions.Subscriptions; +import rx.util.functions.Action1; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -77,8 +83,6 @@ public class MainActivity extends AbstractActivity { private boolean cleanupRunning = false; private int countBubbleCnt = 0; private Geopoint addCoords = null; - private List<Address> addresses = null; - private boolean addressObtaining = false; private boolean initialized = false; private final UpdateLocation locationUpdater = new UpdateLocation(); @@ -115,40 +119,24 @@ public class MainActivity extends AbstractActivity { } }; - private Handler obtainAddressHandler = new Handler() { - - @Override - public void handleMessage(final Message msg) { - try { - if (CollectionUtils.isNotEmpty(addresses)) { - final Address address = addresses.get(0); - final ArrayList<String> addressParts = new ArrayList<String>(); - - final String countryName = address.getCountryName(); - if (countryName != null) { - addressParts.add(countryName); - } - final String locality = address.getLocality(); - if (locality != null) { - addressParts.add(locality); - } else { - final String adminArea = address.getAdminArea(); - if (adminArea != null) { - addressParts.add(adminArea); - } - } - - addCoords = app.currentGeo().getCoords(); + private static String formatAddress(final Address address) { + final ArrayList<String> addressParts = new ArrayList<String>(); - navLocation.setText(StringUtils.join(addressParts, ", ")); - } - } catch (RuntimeException e) { - // nothing + final String countryName = address.getCountryName(); + if (countryName != null) { + addressParts.add(countryName); + } + final String locality = address.getLocality(); + if (locality != null) { + addressParts.add(locality); + } else { + final String adminArea = address.getAdminArea(); + if (adminArea != null) { + addressParts.add(adminArea); } - - addresses = null; } - }; + return StringUtils.join(addressParts, ", "); + } private class SatellitesHandler extends GeoDirHandler { @@ -285,7 +273,7 @@ public class MainActivity extends AbstractActivity { @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); - menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isPremiumMember()); + menu.findItem(R.id.menu_pocket_queries).setVisible(Settings.isGCPremiumMember()); return true; } @@ -309,7 +297,7 @@ public class MainActivity extends AbstractActivity { startScannerApplication(); return true; case R.id.menu_pocket_queries: - if (!Settings.isPremiumMember()) { + if (!Settings.isGCPremiumMember()) { return true; } new PocketQueryList.UserInterface(MainActivity.this).promptForListSelection(new RunnableWithArgument<PocketQueryList>() { @@ -525,52 +513,61 @@ public class MainActivity extends AbstractActivity { @Override public void updateGeoData(final IGeoData geo) { - try { - if (geo.getCoords() != null) { - if (!nearestView.isClickable()) { - nearestView.setFocusable(true); - nearestView.setClickable(true); - nearestView.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - cgeoFindNearest(v); - } - }); - nearestView.setBackgroundResource(R.drawable.main_nearby); + if (!nearestView.isClickable()) { + nearestView.setFocusable(true); + nearestView.setClickable(true); + nearestView.setOnClickListener(new OnClickListener() { + @Override + public void onClick(final View v) { + cgeoFindNearest(v); } + }); + nearestView.setBackgroundResource(R.drawable.main_nearby); + } - navType.setText(res.getString(geo.getLocationProvider().resourceId)); + navType.setText(res.getString(geo.getLocationProvider().resourceId)); - if (geo.getAccuracy() >= 0) { - int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; - navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed)); - } else { - navAccuracy.setText(null); - } + if (geo.getAccuracy() >= 0) { + int speed = Math.round(geo.getSpeed()) * 60 * 60 / 1000; + navAccuracy.setText("±" + Units.getDistanceFromMeters(geo.getAccuracy()) + Formatter.SEPARATOR + Units.getSpeed(speed)); + } else { + navAccuracy.setText(null); + } - if (Settings.isShowAddress()) { - if (addCoords == null) { - navLocation.setText(res.getString(R.string.loc_no_addr)); - } - if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5 && !addressObtaining)) { - (new ObtainAddressThread()).start(); + if (Settings.isShowAddress()) { + if (addCoords == null) { + navLocation.setText(R.string.loc_no_addr); + } + if (addCoords == null || (geo.getCoords().distanceTo(addCoords) > 0.5)) { + final Observable<String> address = Observable.create(new OnSubscribeFunc<String>() { + @Override + public Subscription onSubscribe(final Observer<? super String> observer) { + try { + addCoords = geo.getCoords(); + final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault()); + final Geopoint coords = app.currentGeo().getCoords(); + final List<Address> addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1); + if (!addresses.isEmpty()) { + observer.onNext(formatAddress(addresses.get(0))); + } + observer.onCompleted(); + } catch (final IOException e) { + observer.onError(e); + } + return Subscriptions.empty(); } - } else { - navLocation.setText(geo.getCoords().toString()); - } - } else { - if (nearestView.isClickable()) { - nearestView.setFocusable(false); - nearestView.setClickable(false); - nearestView.setOnClickListener(null); - nearestView.setBackgroundResource(R.drawable.main_nearby_disabled); - } - navType.setText(null); - navAccuracy.setText(null); - navLocation.setText(res.getString(R.string.loc_trying)); + }).subscribeOn(Schedulers.threadPoolForIO()); + AndroidObservable.fromActivity(MainActivity.this, address) + .onErrorResumeNext(Observable.just(geo.getCoords().toString())) + .subscribe(new Action1<String>() { + @Override + public void call(final String address) { + navLocation.setText(address); + } + }); } - } catch (RuntimeException e) { - Log.w("Failed to update location."); + } else { + navLocation.setText(geo.getCoords().toString()); } } } @@ -714,33 +711,6 @@ public class MainActivity extends AbstractActivity { } } - private class ObtainAddressThread extends Thread { - - public ObtainAddressThread() { - setPriority(Thread.MIN_PRIORITY); - } - - @Override - public void run() { - if (addressObtaining) { - return; - } - addressObtaining = true; - - try { - final Geocoder geocoder = new Geocoder(MainActivity.this, Locale.getDefault()); - final Geopoint coords = app.currentGeo().getCoords(); - addresses = geocoder.getFromLocation(coords.getLatitude(), coords.getLongitude(), 1); - } catch (IOException e) { - Log.i("Failed to obtain address"); - } - - obtainAddressHandler.sendEmptyMessage(0); - - addressObtaining = false; - } - } - /** * @param view * unused here but needed since this method is referenced from XML layout diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 131a01c..46ac38e 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -10,6 +10,7 @@ import cgeo.geocaching.gcvote.GCVote; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.Nullable; import android.os.Parcel; import android.os.Parcelable; @@ -228,6 +229,7 @@ public class SearchResult implements Parcelable { return result; } + @Nullable public Geocache getFirstCacheFromResult(final EnumSet<LoadFlag> loadFlags) { return CollectionUtils.isNotEmpty(geocodes) ? DataStore.loadCache(geocodes.iterator().next(), loadFlags) : null; } diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java index 4f70f0e..a4b5973 100644 --- a/main/src/cgeo/geocaching/StatusFragment.java +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -1,15 +1,17 @@ package cgeo.geocaching; +import cgeo.geocaching.network.StatusUpdater; import cgeo.geocaching.network.StatusUpdater.Status; -import cgeo.geocaching.utils.IObserver; import cgeo.geocaching.utils.Log; +import rx.Subscription; +import rx.android.observables.AndroidObservable; +import rx.util.functions.Action1; + import android.content.Intent; import android.content.res.Resources; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.support.v4.app.Fragment; import android.view.LayoutInflater; import android.view.View; @@ -20,91 +22,68 @@ import android.widget.TextView; public class StatusFragment extends Fragment { - private ViewGroup status; - private ImageView statusIcon; - private TextView statusMessage; - - final private StatusHandler statusHandler = new StatusHandler(); + private Subscription statusSubscription; @Override public View onCreateView(final LayoutInflater inflater, final ViewGroup container, final Bundle savedInstanceState) { super.onCreateView(inflater, container, savedInstanceState); - status = (ViewGroup) inflater.inflate(R.layout.status, container, false); - statusIcon = (ImageView) status.findViewById(R.id.status_icon); - statusMessage = (TextView) status.findViewById(R.id.status_message); - return status; - } - - @Override - public void onResume() { - super.onResume(); - CgeoApplication.getInstance().getStatusUpdater().addObserver(statusHandler); - } - - @Override - public void onPause() { - CgeoApplication.getInstance().getStatusUpdater().deleteObserver(statusHandler); - super.onPause(); - } - - private class StatusHandler extends Handler implements IObserver<Status> { - - @Override - public void update(final Status data) { - obtainMessage(0, data).sendToTarget(); - } - - @Override - public void handleMessage(final Message msg) { - final Status data = (Status) msg.obj; - updateDisplay(data != null && data.message != null ? data : Status.defaultStatus()); - } - - private void updateDisplay(final Status data) { - - if (data == null) { - status.setVisibility(View.INVISIBLE); - return; - } - - final Resources res = getResources(); - final String packageName = getActivity().getPackageName(); + 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 = AndroidObservable.fromFragment(this, StatusUpdater.latestStatus).subscribe(new Action1<Status>() { + @Override + public void call(final Status status) { + if (status == null) { + statusGroup.setVisibility(View.INVISIBLE); + return; + } - if (data.icon != null) { - final int iconId = res.getIdentifier(data.icon, "drawable", packageName); - if (iconId != 0) { - statusIcon.setImageResource(iconId); - statusIcon.setVisibility(View.VISIBLE); + 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 { - Log.w("StatusHandler: could not find icon corresponding to @drawable/" + data.icon); statusIcon.setVisibility(View.GONE); } - } else { - statusIcon.setVisibility(View.GONE); - } - String message = data.message; - if (data.messageId != null) { - final int messageId = res.getIdentifier(data.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); - status.setVisibility(View.VISIBLE); + statusMessage.setText(message); + statusGroup.setVisibility(View.VISIBLE); - if (data.url != null) { - status.setOnClickListener(new OnClickListener() { - @Override - public void onClick(final View v) { - startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(data.url))); - } - }); - } else { - status.setClickable(false); + 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); + } } - } + }); + return statusGroup; + } + @Override + public void onDestroy() { + statusSubscription.unsubscribe(); + super.onDestroy(); } + } diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index dcfd80a..34f546a 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -130,6 +130,8 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // try to get data from URI if (geocode == null && guid == null && id == null && uri != null) { + geocode = ConnectorFactory.getTrackableFromURL(uri.toString()); + final String uriHost = uri.getHost().toLowerCase(Locale.US); if (uriHost.contains("geocaching.com")) { geocode = uri.getQueryParameter("tracker"); diff --git a/main/src/cgeo/geocaching/activity/AbstractListActivity.java b/main/src/cgeo/geocaching/activity/AbstractListActivity.java index 2adae7a..a5d5c14 100644 --- a/main/src/cgeo/geocaching/activity/AbstractListActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractListActivity.java @@ -11,7 +11,6 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen IAbstractActivity { private boolean keepScreenOn = false; - private boolean paused = true; protected CgeoApplication app = null; protected Resources res = null; @@ -85,26 +84,4 @@ public abstract class AbstractListActivity extends FragmentListActivity implemen // initialize action bar title with activity title ActivityMixin.setTitle(this, getTitle()); } - - @Override - public void onResume() { - paused = false; - super.onResume(); - } - - @Override - public void onPause() { - super.onPause(); - paused = true; - } - - /** - * Check if the current activity is paused. This must be called and acted - * upon only from the UI thread. - * - * @return <code>true</code> if the current activity is paused - */ - protected boolean isPaused() { - return paused; - } } diff --git a/main/src/cgeo/geocaching/connector/ConnectorFactory.java b/main/src/cgeo/geocaching/connector/ConnectorFactory.java index 0081951..68e3142 100644 --- a/main/src/cgeo/geocaching/connector/ConnectorFactory.java +++ b/main/src/cgeo/geocaching/connector/ConnectorFactory.java @@ -27,6 +27,10 @@ import cgeo.geocaching.geopoint.Viewport; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; +import rx.Observable; +import rx.concurrency.Schedulers; +import rx.util.functions.Func1; +import rx.util.functions.Func2; import java.util.ArrayList; import java.util.Arrays; @@ -176,14 +180,30 @@ public final class ConnectorFactory { } /** @see ISearchByViewPort#searchByViewport */ - public static SearchResult searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { - final SearchResult result = new SearchResult(); - for (final ISearchByViewPort connector : searchByViewPortConns) { - if (connector.isActive()) { - result.addSearchResult(connector.searchByViewport(viewport, tokens)); + public static Observable<SearchResult> searchByViewport(final @NonNull Viewport viewport, final MapTokens tokens) { + return Observable.from(searchByViewPortConns).filter(new Func1<ISearchByViewPort, Boolean>() { + @Override + public Boolean call(final ISearchByViewPort connector) { + return connector.isActive(); } - } - return result; + }).parallel(new Func1<Observable<ISearchByViewPort>, Observable<SearchResult>>() { + @Override + public Observable<SearchResult> call(final Observable<ISearchByViewPort> connector) { + return connector.map(new Func1<ISearchByViewPort, SearchResult>() { + @Override + public SearchResult call(final ISearchByViewPort connector) { + return connector.searchByViewport(viewport, tokens); + } + }); + } + }, Schedulers.threadPoolForIO()).reduce(new SearchResult(), new Func2<SearchResult, SearchResult, SearchResult>() { + + @Override + public SearchResult call(final SearchResult result, final SearchResult searchResult) { + result.addSearchResult(searchResult); + return result; + } + }); } public static String getGeocodeFromURL(final String url) { @@ -200,6 +220,12 @@ public final class ConnectorFactory { return TRACKABLE_CONNECTORS; } + /** + * Get the geocode of a trackable from a URL. + * + * @param url + * @return {@code null} if the URL cannot be decoded + */ public static String getTrackableFromURL(final String url) { if (url == null) { return null; diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index e946748..9c6b831 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -91,7 +91,7 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override public boolean supportsPersonalNote() { - return Settings.isPremiumMember(); + return Settings.isGCPremiumMember(); } @Override @@ -285,7 +285,22 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, @Override protected String getCacheUrlPrefix() { - return CACHE_URL_SHORT; + return null; // UNUSED + } + + @Override + public String getGeocodeFromUrl(String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandle(code)) { + return code; + } + // expanded geocaching.com URLs + code = StringUtils.substringBetween(url, "/geocache/", "_"); + if (code != null && canHandle(code)) { + return code; + } + return null; } @Override diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index a7cf6cf..f747911 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -97,7 +97,7 @@ public class GCLogin extends AbstractLogin { } if (getLoginStatus(loginData)) { - Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Already logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); } @@ -132,7 +132,7 @@ public class GCLogin extends AbstractLogin { assert loginData != null; // Caught above if (getLoginStatus(loginData)) { - Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getMemberStatus() + ')'); + Log.i("Successfully logged in Geocaching.com as " + username + " (" + Settings.getGCMemberStatus() + ')'); if (switchToEnglish(loginData) && retry) { return login(false); @@ -204,9 +204,9 @@ public class GCLogin extends AbstractLogin { Log.e("getLoginStatus: bad cache count", e); } setActualCachesFound(cachesCount); - Settings.setMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(page, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if ( page.contains(GCConstants.MEMBER_STATUS_RENEW) ) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } return true; } @@ -259,9 +259,9 @@ public class GCLogin extends AbstractLogin { final String responseData = StringUtils.defaultString(Network.getResponseData(Network.getRequest("http://www.geocaching.com/my/"))); final String profile = TextUtils.replaceWhitespace(responseData); - Settings.setMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); + Settings.setGCMemberStatus(TextUtils.getMatch(profile, GCConstants.PATTERN_MEMBER_STATUS, true, null)); if (profile.contains(GCConstants.MEMBER_STATUS_RENEW)) { - Settings.setMemberStatus(GCConstants.MEMBER_STATUS_PM); + Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 6c94150..2782b64 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -368,7 +368,7 @@ public class GCMap { } } - if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isPremiumMember()) { + if (strategy.flags.contains(StrategyFlag.SEARCH_NEARBY) && Settings.isGCPremiumMember()) { final Geopoint center = viewport.getCenter(); if ((lastSearchViewport == null) || !lastSearchViewport.contains(center)) { //FIXME We don't have a RecaptchaReceiver!? diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index 62ccb14..fe962e6 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -282,7 +282,7 @@ public abstract class GCParser { recaptchaText = thread.getText(); } - if (!cids.isEmpty() && (Settings.isPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { + if (!cids.isEmpty() && (Settings.isGCPremiumMember() || showCaptcha) && ((thread == null || StringUtils.isBlank(thread.getChallenge())) || StringUtils.isNotBlank(recaptchaText))) { Log.i("Trying to get .loc for " + cids.size() + " caches"); try { @@ -347,6 +347,9 @@ public abstract class GCParser { // 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(); @@ -741,7 +744,7 @@ public abstract class GCParser { cache.parseWaypointsFromNote(); // logs - cache.setLogs(loadLogsFromDetails(page, cache, false, true)); + cache.setLogs(getLogsFromDetails(page, false)); // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { @@ -1380,8 +1383,7 @@ public abstract class GCParser { } private static boolean changeFavorite(final Geocache cache, final boolean add) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1400,6 +1402,11 @@ public abstract class GCParser { return false; } + private static String getUserToken(final Geocache cache) { + final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); + return TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + } + /** * Removes the cache from the favorites. * @@ -1613,19 +1620,20 @@ public abstract class GCParser { } /** - * Load logs from a cache details page. + * Extract logs from a cache details page. * * @param page * the text of the details page - * @param cache - * the cache object to put the logs in * @param friends - * retrieve friend logs + * 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 + * */ - private static List<LogEntry> loadLogsFromDetails(final String page, final Geocache cache, final boolean friends, final boolean getDataFromPage) { + @Nullable + private static List<LogEntry> getLogsFromDetails(final String page, final boolean friends) { String rawResponse; - if (!getDataFromPage) { + if (friends) { final MatcherWrapper userTokenMatcher = new MatcherWrapper(GCConstants.PATTERN_USERTOKEN, page); if (!userTokenMatcher.find()) { Log.e("GCParser.loadLogsFromDetails: unable to extract userToken"); @@ -1825,7 +1833,7 @@ public abstract class GCParser { if (Settings.isFriendLogsWanted()) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); final List<LogEntry> allLogs = cache.getLogs(); - final List<LogEntry> friendLogs = loadLogsFromDetails(page, cache, true, false); + final List<LogEntry> friendLogs = getLogsFromDetails(page, true); if (friendLogs != null) { for (final LogEntry log : friendLogs) { if (allLogs.contains(log)) { @@ -1860,8 +1868,7 @@ public abstract class GCParser { } public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } @@ -1896,8 +1903,7 @@ public abstract class GCParser { } public static boolean uploadPersonalNote(Geocache cache) { - final String page = requestHtmlPage(cache.getGeocode(), null, "n", "0"); - final String userToken = TextUtils.getMatch(page, GCConstants.PATTERN_USERTOKEN, ""); + final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } diff --git a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java index 69efddc..fb554b9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/AbstractTrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.connector.AbstractConnector; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -15,7 +16,8 @@ public abstract class AbstractTrackableConnector implements TrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { return null; } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java index 8387076..03052f9 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyConnector.java @@ -4,6 +4,10 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.network.Network; import cgeo.geocaching.utils.Log; +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; + import java.util.regex.Pattern; public class GeokretyConnector extends AbstractTrackableConnector { @@ -29,7 +33,7 @@ public class GeokretyConnector extends AbstractTrackableConnector { return GeokretyParser.parse(page); } - private static int getId(String geocode) { + protected static int getId(String geocode) { try { final String hex = geocode.substring(2); return Integer.parseInt(hex, 16); @@ -39,4 +43,25 @@ public class GeokretyConnector extends AbstractTrackableConnector { return -1; } + @Override + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // http://geokrety.org/konkret.php?id=38545 + String id = StringUtils.substringAfterLast(url, "konkret.php?id="); + if (StringUtils.isNumeric(id)) { + return geocode(Integer.parseInt(id)); + } + return null; + } + + /** + * Get geocode from geokrety id + * + * @param id + * @return + */ + public static String geocode(final int id) { + return String.format("GK%04X", id); + } + } diff --git a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java index ee8c8c0..0e64abd 100644 --- a/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java +++ b/main/src/cgeo/geocaching/connector/trackable/GeokretyParser.java @@ -37,8 +37,8 @@ public class GeokretyParser { public void start(Attributes attributes) { try { final String kretyId = attributes.getValue("id"); - if (StringUtils.isNotBlank(kretyId)) { - trackable.setGeocode(geocode(Integer.parseInt(kretyId))); + if (StringUtils.isNumeric(kretyId)) { + trackable.setGeocode(GeokretyConnector.geocode(Integer.parseInt(kretyId))); } final String distance = attributes.getValue("dist"); if (StringUtils.isNotBlank(distance)) { @@ -88,8 +88,4 @@ public class GeokretyParser { } return null; } - - protected static String geocode(final int id) { - return String.format("GK%04X", id); - } } diff --git a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java index 0990d96..6071b5f 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TrackableConnector.java @@ -4,6 +4,7 @@ import cgeo.geocaching.Trackable; import cgeo.geocaching.connector.UserAction; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; @@ -21,7 +22,8 @@ public interface TrackableConnector { public Trackable searchTrackable(String geocode, String guid, String id); - public String getTrackableCodeFromUrl(final @NonNull String url); + public @Nullable + String getTrackableCodeFromUrl(final @NonNull String url); public @NonNull List<UserAction> getUserActions(); diff --git a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java index dad285c..77848d7 100644 --- a/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java +++ b/main/src/cgeo/geocaching/connector/trackable/TravelBugConnector.java @@ -7,6 +7,7 @@ import cgeo.geocaching.connector.gc.GCParser; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; +import org.eclipse.jdt.annotation.Nullable; import java.util.List; import java.util.regex.Pattern; @@ -20,7 +21,7 @@ public class TravelBugConnector extends AbstractTrackableConnector { @Override public boolean canHandleTrackable(String geocode) { - return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches(); + return TravelBugConnector.PATTERN_TB_CODE.matcher(geocode).matches() && !StringUtils.startsWithIgnoreCase(geocode, "GC"); } @Override @@ -54,8 +55,18 @@ public class TravelBugConnector extends AbstractTrackableConnector { } @Override - public String getTrackableCodeFromUrl(@NonNull String url) { - return StringUtils.substringAfterLast(url, "?tracker="); + public @Nullable + String getTrackableCodeFromUrl(@NonNull String url) { + // coord.info URLs + String code = StringUtils.substringAfterLast(url, "coord.info/"); + if (code != null && canHandleTrackable(code)) { + return code; + } + code = StringUtils.substringAfterLast(url, "?tracker="); + if (code != null && canHandleTrackable(code)) { + return code; + } + return null; } @Override diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index c98ba72..65d8861 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -898,7 +898,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto private long timeLastPositionOverlayCalculation = 0; @Override - protected void updateGeoData(final IGeoData geo) { + public void updateGeoData(final IGeoData geo) { if (geo.isPseudoLocation()) { locationValid = false; } else { @@ -1182,7 +1182,7 @@ public class CGeoMap extends AbstractMap implements OnMapDragListener, ViewFacto } } } - final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens); + final SearchResult searchResult = ConnectorFactory.searchByViewport(viewport.resize(0.8), tokens).toBlockingObservable().single(); downloaded = true; Set<Geocache> result = searchResult.getCachesFromSearchResult(LoadFlags.LOAD_CACHE_OR_DB); diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index cb4c7f4..40281ad 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -1,21 +1,22 @@ package cgeo.geocaching.network; import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.utils.MemorySubject; -import cgeo.geocaching.utils.PeriodicHandler; -import cgeo.geocaching.utils.PeriodicHandler.PeriodicHandlerListener; import cgeo.geocaching.utils.Version; import org.json.JSONException; import org.json.JSONObject; +import rx.Observable; +import rx.concurrency.Schedulers; +import rx.subjects.BehaviorSubject; +import rx.util.functions.Func1; import android.os.Build.VERSION; import android.os.Build.VERSION_CODES; -import android.os.Looper; import java.util.Locale; +import java.util.concurrent.TimeUnit; -public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implements Runnable, PeriodicHandlerListener { +public class StatusUpdater { static public class Status { final public String message; @@ -30,24 +31,41 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement this.url = url; } + Status(final JSONObject response) { + message = get(response, "message"); + messageId = get(response, "message_id"); + icon = get(response, "icon"); + url = get(response, "url"); + } + final static public Status closeoutStatus = new Status("", "status_closeout_warning", "attribute_abandonedbuilding", "http://faq.cgeo.org/#7_69"); - final static public Status defaultStatus() { + final static public Status defaultStatus(final Status upToDate) { + if (upToDate != null && upToDate.message != null) { + return upToDate; + } return VERSION.SDK_INT < VERSION_CODES.ECLAIR_MR1 ? closeoutStatus : null; } } - @Override - public void onPeriodic() { - final JSONObject response = - Network.requestJSON("http://status.cgeo.org/api/status.json", - new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), - "version_name", Version.getVersionName(CgeoApplication.getInstance()), - "locale", Locale.getDefault().toString())); - if (response != null) { - notifyObservers(new Status(get(response, "message"), get(response, "message_id"), get(response, "icon"), get(response, "url"))); - } + final static private Observable<Status> statusObservable = + Observable.interval(30, TimeUnit.MINUTES).startWith(-1L).flatMap(new Func1<Long, Observable<Status>>() { + @Override + public Observable<Status> call(Long id) { + final JSONObject response = + Network.requestJSON("http://status.cgeo.org/api/status.json", + new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), + "version_name", Version.getVersionName(CgeoApplication.getInstance()), + "locale", Locale.getDefault().toString())); + return response != null ? Observable.just(Status.defaultStatus((new Status(response)))) : Observable.<Status>empty(); + } + }).subscribeOn(Schedulers.threadPoolForIO()); + + final static public BehaviorSubject<Status> latestStatus = BehaviorSubject.create(Status.defaultStatus(null)); + + static { + statusObservable.subscribe(latestStatus); } private static String get(final JSONObject json, final String key) { @@ -58,11 +76,4 @@ public class StatusUpdater extends MemorySubject<StatusUpdater.Status> implement } } - @Override - public void run() { - Looper.prepare(); - new PeriodicHandler(1800000L, this).start(); - Looper.loop(); - } - } diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index 0732866..ee97c13 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -313,16 +313,16 @@ public class Settings { return getBoolean(R.string.pref_connectorOXActive, false); } - public static boolean isPremiumMember() { + public static boolean isGCPremiumMember() { // Basic Member, Premium Member, ??? - return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getMemberStatus()); + return GCConstants.MEMBER_STATUS_PM.equalsIgnoreCase(Settings.getGCMemberStatus()); } - public static String getMemberStatus() { + public static String getGCMemberStatus() { return getString(R.string.pref_memberstatus, ""); } - public static boolean setMemberStatus(final String memberStatus) { + public static boolean setGCMemberStatus(final String memberStatus) { if (StringUtils.isBlank(memberStatus)) { return remove(R.string.pref_memberstatus); } @@ -478,7 +478,7 @@ public class Settings { } public static boolean getLoadDirImg() { - return !isPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true); + return !isGCPremiumMember() && getBoolean(R.string.pref_loaddirectionimg, true); } public static void setGcCustomDate(final String format) { @@ -506,7 +506,7 @@ public class Settings { } public static boolean isShowCaptcha() { - return !isPremiumMember() && getBoolean(R.string.pref_showcaptcha, false); + return !isGCPremiumMember() && getBoolean(R.string.pref_showcaptcha, false); } public static boolean isExcludeDisabledCaches() { diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index 58acfc1..04880c4 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -1,6 +1,7 @@ package cgeo.geocaching.settings; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.DataStore; import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.SelectMapfileActivity; @@ -19,8 +20,10 @@ import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import org.openintents.intents.FileManagerIntents; +import android.app.ProgressDialog; import android.content.Context; import android.content.Intent; +import android.content.res.Resources; import android.net.Uri; import android.os.Build; import android.os.Bundle; @@ -121,6 +124,7 @@ public class SettingsActivity extends PreferenceActivity { initSend2CgeoPreferences(); initServicePreferences(); initNavigationMenuPreferences(); + initMaintenanceButtons(); for (int k : new int[] { R.string.pref_username, R.string.pref_password, R.string.pref_pass_vote, R.string.pref_signature, @@ -142,7 +146,7 @@ public class SettingsActivity extends PreferenceActivity { } } getPreference(R.string.pref_fakekey_basicmembers_screen) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); redrawScreen(R.string.pref_fakekey_navigation_menu_screen); } @@ -310,6 +314,35 @@ public class SettingsActivity extends PreferenceActivity { }); } + public void initMaintenanceButtons() { + Preference dirMaintenance = getPreference(R.string.pref_fakekey_preference_maintenance_directories); + dirMaintenance.setOnPreferenceClickListener(new OnPreferenceClickListener() { + @Override + public boolean onPreferenceClick(final Preference preference) { + // disable the button, as the cleanup runs in background and should not be invoked a second time + preference.setEnabled(false); + + Resources res = getResources(); + final SettingsActivity activity = SettingsActivity.this; + final ProgressDialog dialog = ProgressDialog.show(activity, res.getString(R.string.init_maintenance), res.getString(R.string.init_maintenance_directories), true, false); + new Thread() { + @Override + public void run() { + DataStore.removeObsoleteCacheDirectories(); + activity.runOnUiThread(new Runnable() { + @Override + public void run() { + dialog.dismiss(); + } + }); + } + }.start(); + + return true; + } + }); + } + private void initDbLocationPreference() { Preference p = getPreference(R.string.pref_dbonsdcard); p.setPersistent(false); @@ -337,11 +370,11 @@ public class SettingsActivity extends PreferenceActivity { void initBasicMemberPreferences() { getPreference(R.string.pref_fakekey_basicmembers_screen) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); getPreference(R.string.pref_loaddirectionimg) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); getPreference(R.string.pref_showcaptcha) - .setEnabled(!Settings.isPremiumMember()); + .setEnabled(!Settings.isGCPremiumMember()); redrawScreen(R.string.pref_fakekey_services_screen); } diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java index 2a72bbf..8c650c3 100644 --- a/main/src/cgeo/geocaching/speech/SpeechService.java +++ b/main/src/cgeo/geocaching/speech/SpeechService.java @@ -47,7 +47,7 @@ public class SpeechService extends Service implements OnInitListener { GeoDirHandler geoHandler = new GeoDirHandler() { @Override - protected void updateDirection(float newDirection) { + public void updateDirection(float newDirection) { if (CgeoApplication.getInstance().currentGeo().getSpeed() <= 5) { direction = DirectionProvider.getDirectionNow(startingActivity, newDirection); directionInitialized = true; @@ -56,7 +56,7 @@ public class SpeechService extends Service implements OnInitListener { } @Override - protected void updateGeoData(cgeo.geocaching.IGeoData newGeo) { + public void updateGeoData(cgeo.geocaching.IGeoData newGeo) { position = newGeo.getCoords(); positionInitialized = true; if (!Settings.isUseCompass() || newGeo.getSpeed() > 5) { diff --git a/main/src/cgeo/geocaching/utils/GeoDirHandler.java b/main/src/cgeo/geocaching/utils/GeoDirHandler.java index c85648b..b9c605b 100644 --- a/main/src/cgeo/geocaching/utils/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/utils/GeoDirHandler.java @@ -4,12 +4,15 @@ import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.IGeoData; import cgeo.geocaching.settings.Settings; -import android.os.Handler; -import android.os.Message; +import rx.Observable; +import rx.Subscription; +import rx.android.concurrency.AndroidSchedulers; +import rx.util.functions.Action1; + +import java.util.concurrent.TimeUnit; /** - * GeoData and Direction handler. Manipulating geodata and direction information - * through a GeoDirHandler ensures that all listeners are registered from a {@link android.os.Looper} thread. + * GeoData and Direction handler. * <p> * To use this class, override at least one of {@link #updateDirection(float)} or {@link #updateGeoData(IGeoData)}. You * need to start the handler using one of @@ -21,47 +24,11 @@ import android.os.Message; * A good place might be the {@code onResume} method of the Activity. Stop the Handler accordingly in {@code onPause}. * </p> */ -public abstract class GeoDirHandler extends Handler implements IObserver<Object> { - - private static final int OBSERVABLE = 1 << 1; - private static final int START_GEO = 1 << 2; - private static final int START_DIR = 1 << 3; - private static final int STOP_GEO = 1 << 4; - private static final int STOP_DIR = 1 << 5; - +public abstract class GeoDirHandler { private static final CgeoApplication app = CgeoApplication.getInstance(); - @Override - final public void handleMessage(final Message message) { - if ((message.what & START_GEO) != 0) { - app.addGeoObserver(this); - } - - if ((message.what & START_DIR) != 0) { - app.addDirectionObserver(this); - } - - if ((message.what & STOP_GEO) != 0) { - app.deleteGeoObserver(this); - } - - if ((message.what & STOP_DIR) != 0) { - app.deleteDirectionObserver(this); - } - - if ((message.what & OBSERVABLE) != 0) { - if (message.obj instanceof IGeoData) { - updateGeoData((IGeoData) message.obj); - } else { - updateDirection((Float) message.obj); - } - } - } - - @Override - final public void update(final Object o) { - obtainMessage(OBSERVABLE, o).sendToTarget(); - } + private Subscription dirSubscription = null; + private Subscription geoSubscription = null; /** * Update method called when new IGeoData is available. @@ -69,7 +36,7 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * @param data * the new data */ - protected void updateGeoData(final IGeoData data) { + public void updateGeoData(final IGeoData data) { // Override this in children } @@ -79,24 +46,38 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * @param direction * the new direction */ - protected void updateDirection(final float direction) { + public void updateDirection(final float direction) { // Override this in children } /** * Register the current GeoDirHandler for GeoData information. */ - public void startGeo() { - sendEmptyMessage(START_GEO); + public synchronized void startGeo() { + geoSubscription = app.currentGeoObject() + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1<IGeoData>() { + @Override + public void call(final IGeoData geoData) { + updateGeoData(geoData); + } + }); } /** * Register the current GeoDirHandler for direction information if the preferences * allow it. */ - public void startDir() { + public synchronized void startDir() { if (Settings.isUseCompass()) { - sendEmptyMessage(START_DIR); + dirSubscription = app.currentDirObject() + .subscribeOn(AndroidSchedulers.mainThread()) + .subscribe(new Action1<Float>() { + @Override + public void call(final Float direction) { + updateDirection(direction); + } + }); } } @@ -105,27 +86,43 @@ public abstract class GeoDirHandler extends Handler implements IObserver<Object> * preferences allow it). */ public void startGeoAndDir() { - sendEmptyMessage(START_GEO | (Settings.isUseCompass() ? START_DIR : 0)); + startGeo(); + startDir(); } /** * Unregister the current GeoDirHandler for GeoData information. */ - public void stopGeo() { - sendEmptyMessage(STOP_GEO); + public synchronized void stopGeo() { + // Delay the unsubscription by 2.5 seconds, so that another activity has + // the time to subscribe and the GPS receiver will not be turned down. + if (geoSubscription != null) { + final Subscription subscription = geoSubscription; + geoSubscription = null; + Observable.interval(2500, TimeUnit.MILLISECONDS).take(1).subscribe(new Action1<Long>() { + @Override + public void call(final Long aLong) { + subscription.unsubscribe(); + } + }); + } } /** * Unregister the current GeoDirHandler for direction information. */ - public void stopDir() { - sendEmptyMessage(STOP_DIR); + public synchronized void stopDir() { + if (dirSubscription != null) { + dirSubscription.unsubscribe(); + dirSubscription = null; + } } /** * Unregister the current GeoDirHandler for GeoData and direction information. */ public void stopGeoAndDir() { - sendEmptyMessage(STOP_GEO | STOP_DIR); + stopGeo(); + stopDir(); } } diff --git a/main/src/cgeo/geocaching/utils/IObserver.java b/main/src/cgeo/geocaching/utils/IObserver.java deleted file mode 100644 index bfcc798..0000000 --- a/main/src/cgeo/geocaching/utils/IObserver.java +++ /dev/null @@ -1,22 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Observer interface. - * <p/> - * An observer will receive updates about the observed object (implementing the {@link ISubject} interface) through its - * {@link #update(Object)} method. - * - * @param <T> - * the kind of data to observe - */ -public interface IObserver<T> { - - /** - * Called when an observed object has updated its data. - * - * @param data - * the updated data - */ - void update(final T data); - -} diff --git a/main/src/cgeo/geocaching/utils/ISubject.java b/main/src/cgeo/geocaching/utils/ISubject.java deleted file mode 100644 index c325db0..0000000 --- a/main/src/cgeo/geocaching/utils/ISubject.java +++ /dev/null @@ -1,57 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Interface for subjects objects. Those can be observed by objects implementing the {@link IObserver} interface. - * - * @param <T> - * the kind of data to observe - */ - -public interface ISubject<T> { - - /** - * Add an observer to the observers list. - * <p/> - * Observers will be notified with no particular order. - * - * @param observer - * the observer to add - * @return true if the observer has been added, false if it was present already - */ - public boolean addObserver(final IObserver<? super T> observer); - - /** - * Delete an observer from the observers list. - * - * @param observer - * the observer to remove - * @return true if the observer has been removed, false if it was not in the list of observers - */ - public boolean deleteObserver(final IObserver<? super T> observer); - - /** - * Number of observers currently observing the object. - * - * @return the number of observers - */ - public int sizeObservers(); - - /** - * Notify all the observers that new data is available. - * <p/> - * The {@link IObserver#update(Object)} method of each observer will be called with no particular order. - * - * @param data - * the updated data - * @return true if at least one observer was notified, false if there were no observers - */ - public boolean notifyObservers(final T data); - - /** - * Clear the observers list. - * - * @return true if there were observers before calling this method, false if the observers list was empty - */ - public boolean clearObservers(); - -} diff --git a/main/src/cgeo/geocaching/utils/MemorySubject.java b/main/src/cgeo/geocaching/utils/MemorySubject.java deleted file mode 100644 index c424528..0000000 --- a/main/src/cgeo/geocaching/utils/MemorySubject.java +++ /dev/null @@ -1,45 +0,0 @@ -package cgeo.geocaching.utils; - -/** - * Synchronized implementation of the {@link ISubject} interface with an added pull interface. - * - * @param <T> - * the kind of data to observe - */ -public class MemorySubject<T> extends Subject<T> { - - /** - * The latest version of the observed data. - * <p/> - * A child class implementation may want to set this field from its constructors, in case early observers request - * the data before it got a chance to get updated. Otherwise, <code>null</code> will be returned until updated - * data is available. - */ - private T memory; - - @Override - public synchronized boolean addObserver(final IObserver<? super T> observer) { - final boolean added = super.addObserver(observer); - if (added && memory != null) { - observer.update(memory); - } - return added; - } - - @Override - public synchronized boolean notifyObservers(final T data) { - memory = data; - return super.notifyObservers(data); - } - - /** - * Get the memorized version of the data. - * - * @return the initial data set by the subject (which may be <code>null</code>), - * or the updated data if it is available - */ - public synchronized T getMemory() { - return memory; - } - -} diff --git a/main/src/cgeo/geocaching/utils/Subject.java b/main/src/cgeo/geocaching/utils/Subject.java deleted file mode 100644 index b1754cc..0000000 --- a/main/src/cgeo/geocaching/utils/Subject.java +++ /dev/null @@ -1,76 +0,0 @@ -package cgeo.geocaching.utils; - -import java.util.LinkedHashSet; -import java.util.Set; - -/** - * Synchronized implementation of the {@link ISubject} interface. - * - * @param <T> - * the kind of data to observe - */ -public class Subject<T> implements ISubject<T> { - - /** - * Collection of observers. - */ - protected final Set<IObserver<? super T>> observers = new LinkedHashSet<IObserver<? super T>>(); - - @Override - public synchronized boolean addObserver(final IObserver<? super T> observer) { - final boolean added = observers.add(observer); - if (added && observers.size() == 1) { - onFirstObserver(); - } - return added; - } - - @Override - public synchronized boolean deleteObserver(final IObserver<? super T> observer) { - final boolean removed = observers.remove(observer); - if (removed && observers.isEmpty()) { - onLastObserver(); - } - return removed; - } - - @Override - public synchronized boolean notifyObservers(final T arg) { - final boolean nonEmpty = !observers.isEmpty(); - for (final IObserver<? super T> observer : observers) { - observer.update(arg); - } - return nonEmpty; - } - - @Override - public synchronized int sizeObservers() { - return observers.size(); - } - - @Override - public synchronized boolean clearObservers() { - final boolean nonEmpty = !observers.isEmpty(); - for (final IObserver<? super T> observer : observers) { - deleteObserver(observer); - } - return nonEmpty; - } - - /** - * Method called when the collection of observers goes from empty to non-empty. - * <p/> - * The default implementation does nothing and may be overwritten by child classes. - */ - protected void onFirstObserver() { - } - - /** - * Method called when the collection of observers goes from non-empty to empty. - * <p/> - * The default implementation does nothing and may be overwritten by child classes. - */ - protected void onLastObserver() { - } - -} |
