diff options
Diffstat (limited to 'main')
250 files changed, 3578 insertions, 2845 deletions
diff --git a/main/.factorypath b/main/.factorypath index 1440abd..3f23356 100644 --- a/main/.factorypath +++ b/main/.factorypath @@ -1,4 +1,4 @@ <factorypath> - <factorypathentry kind="WKSPJAR" id="/cgeo/libs/butterknife-5.1.1.jar" enabled="true" runInBatchMode="false"/> + <factorypathentry kind="WKSPJAR" id="/cgeo/libs/butterknife-5.1.2.jar" enabled="true" runInBatchMode="false"/> <factorypathentry kind="WKSPJAR" id="/cgeo/compile-libs/androidannotations-3.0.1.jar" enabled="true" runInBatchMode="false"/> </factorypath> diff --git a/main/.settings/org.moreunit.prefs b/main/.settings/org.moreunit.prefs new file mode 100644 index 0000000..17ff166 --- /dev/null +++ b/main/.settings/org.moreunit.prefs @@ -0,0 +1,4 @@ +eclipse.preferences.version=1 +org.moreunit.preferences.version=2 +org.moreunit.unitsourcefolder=cgeo\:src\:cgeo-test\:src\#cgeo\:src\:cgeo-test\:gen +org.moreunit.useprojectsettings=true diff --git a/main/AndroidManifest.xml b/main/AndroidManifest.xml index 94c23c9..20db392 100644 --- a/main/AndroidManifest.xml +++ b/main/AndroidManifest.xml @@ -51,10 +51,14 @@ <meta-data android:name="com.google.android.backup.api_key" android:value="AEdPqrEAAAAIsvD_aUSDMwWOf9NkwwxZ4kJJI_AG2EaxjSu2jw" /> + <meta-data + android:name="com.google.android.gms.version" + android:value="@integer/google_play_services_version" /> <activity android:name=".MainActivity" android:configChanges="keyboardHidden|orientation" + android:exported="true" android:label="@string/app_name" android:theme="@style/cgeo_main" android:windowSoftInputMode="stateHidden" > @@ -68,6 +72,7 @@ <activity android:name=".SearchActivity" android:configChanges="keyboardHidden|orientation" + android:exported="true" android:label="@string/app_name" android:launchMode="singleTop" android:parentActivityName="cgeo.geocaching.MainActivity" @@ -136,6 +141,7 @@ <activity android:name=".NavigateAnyPointActivity" android:configChanges="keyboardHidden|orientation" + android:exported="true" android:label="@string/search_destination" android:parentActivityName="cgeo.geocaching.MainActivity" android:windowSoftInputMode="stateHidden" > @@ -166,6 +172,7 @@ <activity android:name=".CacheListActivity" android:configChanges="keyboardHidden|orientation|screenSize" + android:exported="true" android:label="@string/app_name" android:parentActivityName="cgeo.geocaching.MainActivity" > <meta-data @@ -207,6 +214,11 @@ </intent-filter> </activity> <activity + android:exported="true" + android:name=".maps.MapActivity" + android:label="@string/map_map" > + </activity> + <activity android:name=".maps.google.v1.GoogleMapActivity" android:label="@string/map_map" > </activity> @@ -322,6 +334,39 @@ android:pathPrefix="/viewcache.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="opencaching.fr" + android:pathPrefix="/OC" + 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.opencaching.fr" + android:pathPrefix="/OC" + 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.opencaching.fr" + android:pathPrefix="/viewcache.php" + android:scheme="http" /> + </intent-filter> </activity> <activity android:name="cgeo.geocaching.TrackableActivity" @@ -512,4 +557,4 @@ android:authorities="cgeo.geocaching.search.SuggestionProvider" /> </application> -</manifest>
\ No newline at end of file +</manifest> diff --git a/main/build.gradle b/main/build.gradle index 7ad61d4..a1a397d 100644 --- a/main/build.gradle +++ b/main/build.gradle @@ -27,14 +27,17 @@ gradle connectedCheck //https://github.com/stephanenicolas/Quality-Tools-for-Android def AAVersion = '3.0.1' -def RXVersion = '0.19.6' +def RXVersion = '0.20.2' +def JacksonCoreVersion = '2.4.1.1' +def JacksonDatabindVersion = '2.4.1.3' +def JacksonAnnotationsVersion = '2.4.1' group = 'cgeo.geocaching' version = '0.0.1' android { compileSdkVersion "Google Inc.:Google APIs:19" //compileSdkVersion 19 - buildToolsVersion "19.1.0" + buildToolsVersion "20" compileOptions { sourceCompatibility JavaVersion.VERSION_1_7 @@ -81,8 +84,8 @@ android { //packageNameSuffix ".debug" //zipAlign = true debuggable true - runProguard false - proguardFiles getDefaultProguardFile('proguard-android.txt'), '../tests/proguard.cfg' + runProguard true + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-project.txt' versionNameSuffix " Debug " + versionProps['betaNumber'] } release { @@ -152,8 +155,9 @@ dependencies { compile files('libs/mapsforge-map-0.3.0-jar-with-dependencies.jar') compile 'com.android.support:appcompat-v7:19.1.0' + compile 'com.google.android.gms:play-services:5.0.89' - compile 'com.jakewharton:butterknife:5.1.1' + compile 'com.jakewharton:butterknife:5.1.2' compile 'org.apache.commons:commons-collections4:4.0' compile 'org.apache.commons:commons-lang3:3.3.2' compile 'commons-io:commons-io:2.4' @@ -163,6 +167,10 @@ dependencies { compile "com.netflix.rxjava:rxjava-android:$RXVersion" compile "com.netflix.rxjava:rxjava-async-util:$RXVersion" + compile "com.fasterxml.jackson.core:jackson-core:$JacksonCoreVersion" + compile "com.fasterxml.jackson.core:jackson-databind:$JacksonDatabindVersion" + compile "com.fasterxml.jackson.core:jackson-annotations:$JacksonAnnotationsVersion" + //TEST //compile files('compile-libs/androidannotations-3.0.1.jar') //compile files('compile-libs/findbugs-ant.jar') @@ -249,7 +257,7 @@ dependencies { unitTestCompile 'com.google.android:android-test:4.1.1.4' unitTestCompile 'com.googlecode.androidannotations:androidannotations-api:$AAVersion' - unitTestCompile 'com.jakewharton:butterknife:5.1.1' + unitTestCompile 'com.jakewharton:butterknife:5.1.2' unitTestCompile 'org.apache.commons:commons-collections4:4.0' unitTestCompile 'commons-io:commons-io:2.4' unitTestCompile 'org.apache.commons:commons-lang3:3.3.2' @@ -358,4 +366,4 @@ task addTest { // always do the addtest on prebuild gradle.projectsEvaluated { preBuild.dependsOn(addTest) -}*/
\ No newline at end of file +}*/ diff --git a/main/build.xml b/main/build.xml index 43c37b2..070526b 100644 --- a/main/build.xml +++ b/main/build.xml @@ -1,12 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <project name="cgeo"> - <!-- The local.properties file is created and updated by the 'android' tool. + <!-- The local.properties file is created and updated by the 'android' tool. It contains the path to the SDK. It should *NOT* be checked into Version Control Systems. --> - <loadproperties srcFile="local.properties" /> + <loadproperties srcFile="local.properties" /> - <!-- The ant.properties file can be created by you. It is only edited by the + <!-- The ant.properties file can be created by you. It is only edited by the 'android' tool to add properties to it. This is the place to change some Ant specific build properties. Here are some properties you may want to change/update: @@ -26,9 +26,9 @@ application and should be checked into Version Control Systems. --> - <property file="ant.properties" /> + <property file="ant.properties" /> - <!-- The project.properties file is created and updated by the 'android' + <!-- The project.properties file is created and updated by the 'android' tool, as well as ADT. This contains project specific properties such as project target, and library @@ -37,36 +37,48 @@ This file is an integral part of the build system for your application and should be checked into Version Control Systems. --> - <loadproperties srcFile="project.properties" /> - - <!-- The private.properties file sets api-keys as well as keystore, + <loadproperties srcFile="project.properties" /> + + <!-- The private.properties file sets api-keys as well as keystore, certificate and passwords (if you want). See /templates/private.properties for more information. --> - <property file="private.properties" /> + <property file="private.properties" /> - <!-- quick check on sdk.dir --> - <fail + <!-- quick check on sdk.dir --> + <fail message="sdk.dir is missing. Make sure to generate local.properties using 'android update project'" unless="sdk.dir" /> - - <!-- Custom targets --> - <target name="install_release" description="Install signed release application" depends="release"> - <exec executable="${sdk.dir}/platform-tools/adb"> - <arg line="-d install -r ./bin/${ant.project.name}-release.apk" /> - </exec> - </target> - - <target name="debugAPI" depends="-pre-build" + + <!-- Custom targets --> + <target name="install_release" description="Install signed release application" depends="release"> + <exec executable="${sdk.dir}/platform-tools/adb"> + <arg line="-d install -r ./bin/${ant.project.name}-release.apk" /> + </exec> + </target> + + <target name="run_release" description="Run signed release application" depends="install_release"> + <exec executable="${sdk.dir}/platform-tools/adb"> + <arg line="-d shell am start -n cgeo.geocaching/.MainActivity" /> + </exec> + </target> + + <target name="debugAPI" depends="-pre-build" description="Changes API key to debug"> - </target> - + </target> + <taskdef name="findbugs" classname="edu.umd.cs.findbugs.anttask.FindBugsTask"> <classpath> - <pathelement location="./compile-libs/findbugs-ant.jar"/> + <pathelement location="./compile-libs/findbugs-ant.jar"/> </classpath> </taskdef> + <!-- We need to enable ProGuard in debug mode as we otherwise run into the 64 K methods dex limit. + As long as we don't obfuscate, we can even use the same ProGuard config in both builds. --> + <target name="-debug-obfuscation-check"> + <property name="proguard.enabled" value="true"/> + </target> + <target name="findbugs"> <mkdir dir="reports" /> <gettarget @@ -86,62 +98,77 @@ <class location="${out.dir}" /> </findbugs> </target> - + <!-- extension targets. Uncomment the ones where you want to do custom work in between standard targets --> - <target name="-pre-build"> - <condition property="build.mode.release" else="false"> - <equals arg1="${build.target}" arg2="release" /> - </condition> - - <if condition="${build.mode.release}"> - <then> - <filterset id="maps-key"> - <filter token="maps.api.key" value="${maps.api.key.market}"/> - </filterset> - </then> - <else> - <filterset id="maps-key"> - <filter token="maps.api.key" value="${maps.api.key}"/> - </filterset> - </else> - </if> - <copy file="./templates/keys.xml" todir="./res/values/" overwrite="true"> - <filterset refid="maps-key" /> - <filterset> - <filter token="ocde.okapi.consumer.key" value="${ocde.okapi.consumer.key}"/> - <filter token="ocde.okapi.consumer.secret" value="${ocde.okapi.consumer.secret}"/> - <filter token="ocpl.okapi.consumer.key" value="${ocpl.okapi.consumer.key}"/> - <filter token="ocpl.okapi.consumer.secret" value="${ocpl.okapi.consumer.secret}"/> - <filter token="ocus.okapi.consumer.key" value="${ocus.okapi.consumer.key}"/> - <filter token="ocus.okapi.consumer.secret" value="${ocus.okapi.consumer.secret}"/> - <filter token="ocnl.okapi.consumer.key" value="${ocnl.okapi.consumer.key}"/> - <filter token="ocnl.okapi.consumer.secret" value="${ocnl.okapi.consumer.secret}"/> - <filter token="ocro.okapi.consumer.key" value="${ocro.okapi.consumer.key}"/> - <filter token="ocro.okapi.consumer.secret" value="${ocro.okapi.consumer.secret}"/> - <filter token="ocuk.okapi.consumer.key" value="${ocuk.okapi.consumer.key}"/> - <filter token="ocuk.okapi.consumer.secret" value="${ocuk.okapi.consumer.secret}"/> - </filterset> - </copy> - </target> + <target name="-pre-build"> + <!-- Enable ProGuard in debug builds --> + <condition property="proguard.enabled" value="true" else="false"> + <isset property="proguard.config" /> + </condition> + <if condition="${proguard.enabled}"> + <then> + <echo level="info">Proguard.config is enabled</echo> + <!-- Secondary dx input (jar files) is empty since all the jar files will be in the obfuscated jar --> + <path id="out.dex.jar.input.ref" /> + </then> + <else> + <echo level="info">Proguard.config is disabled</echo> + </else> + </if> + + <!-- Add all the keys for Google Maps, Twitter and OKAPI in the build --> + <condition property="build.mode.release" else="false"> + <equals arg1="${build.target}" arg2="release" /> + </condition> + <if condition="${build.mode.release}"> + <then> + <filterset id="maps-key"> + <filter token="maps.api.key" value="${maps.api.key.market}" /> + </filterset> + </then> + <else> + <filterset id="maps-key"> + <filter token="maps.api.key" value="${maps.api.key}"/> + </filterset> + </else> + </if> + <copy file="./templates/keys.xml" todir="./res/values/" overwrite="true"> + <filterset refid="maps-key" /> + <filterset> + <filter token="ocde.okapi.consumer.key" value="${ocde.okapi.consumer.key}"/> + <filter token="ocde.okapi.consumer.secret" value="${ocde.okapi.consumer.secret}"/> + <filter token="ocpl.okapi.consumer.key" value="${ocpl.okapi.consumer.key}"/> + <filter token="ocpl.okapi.consumer.secret" value="${ocpl.okapi.consumer.secret}"/> + <filter token="ocus.okapi.consumer.key" value="${ocus.okapi.consumer.key}"/> + <filter token="ocus.okapi.consumer.secret" value="${ocus.okapi.consumer.secret}"/> + <filter token="ocnl.okapi.consumer.key" value="${ocnl.okapi.consumer.key}"/> + <filter token="ocnl.okapi.consumer.secret" value="${ocnl.okapi.consumer.secret}"/> + <filter token="ocro.okapi.consumer.key" value="${ocro.okapi.consumer.key}"/> + <filter token="ocro.okapi.consumer.secret" value="${ocro.okapi.consumer.secret}"/> + <filter token="ocuk.okapi.consumer.key" value="${ocuk.okapi.consumer.key}"/> + <filter token="ocuk.okapi.consumer.secret" value="${ocuk.okapi.consumer.secret}"/> + </filterset> + </copy> + </target> -<!-- start of modifications for android annotations, see https://github.com/excilys/androidannotations/wiki/Building-Project-Ant --> + <!-- start of modifications for android annotations, see https://github.com/excilys/androidannotations/wiki/Building-Project-Ant --> <property name="generated.dir" value="annotation_gen" /> <property name="generated.absolute.dir" location="${generated.dir}" /> - <property name="java.compilerargs" value="-s '${generated.absolute.dir}'" /> + <property name="java.compilerargs" value="-s '${generated.absolute.dir}'" /> <target name="-pre-compile"> <mkdir dir="${generated.absolute.dir}" /> </target> - <target name="-compile" depends="-pre-build, -build-setup, -code-gen, -pre-compile"> - <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping..."> - <!-- merge the project's own classpath and the tested project's classpath --> - <path id="project.javac.classpath"> - <path refid="project.all.jars.path" /> - <path refid="tested.project.classpath" /> - <path path="${java.compiler.classpath}" /> - <fileset dir="compile-libs" includes="*.jar"/> - </path> - <javac encoding="${java.encoding}" + <target name="-compile" depends="-pre-build, -build-setup, -code-gen, -pre-compile"> + <do-only-if-manifest-hasCode elseText="hasCode = false. Skipping..."> + <!-- merge the project's own classpath and the tested project's classpath --> + <path id="project.javac.classpath"> + <path refid="project.all.jars.path" /> + <path refid="tested.project.classpath" /> + <path path="${java.compiler.classpath}" /> + <fileset dir="compile-libs" includes="*.jar"/> + </path> + <javac encoding="${java.encoding}" source="${java.source}" target="${java.target}" debug="true" extdirs="" includeantruntime="false" destdir="${out.classes.absolute.dir}" @@ -149,77 +176,77 @@ verbose="${verbose}" classpathref="project.javac.classpath" fork="${need.javac.fork}"> - <src path="${source.absolute.dir}" /> - <src path="${gen.absolute.dir}" /> - <compilerarg line="${java.compilerargs}" /> - </javac> - - <!-- if the project is instrumented, intrument the classes --> - <if condition="${build.is.instrumented}"> - <then> - <echo level="info">Instrumenting classes from ${out.absolute.dir}/classes...</echo> - - <!-- build the filter to remove R, Manifest, BuildConfig --> - <getemmafilter + <src path="${source.absolute.dir}" /> + <src path="${gen.absolute.dir}" /> + <compilerarg line="${java.compilerargs}" /> + </javac> + + <!-- if the project is instrumented, intrument the classes --> + <if condition="${build.is.instrumented}"> + <then> + <echo level="info">Instrumenting classes from ${out.absolute.dir}/classes...</echo> + + <!-- build the filter to remove R, Manifest, BuildConfig --> + <getemmafilter appPackage="${project.app.package}" libraryPackagesRefId="project.library.packages" filterOut="emma.default.filter"/> - <!-- define where the .em file is going. This may have been + <!-- define where the .em file is going. This may have been setup already if this is a library --> - <property name="emma.coverage.absolute.file" location="${out.absolute.dir}/coverage.em" /> + <property name="emma.coverage.absolute.file" location="${out.absolute.dir}/coverage.em" /> - <!-- It only instruments class files, not any external libs --> - <emma enabled="true"> - <instr verbosity="${verbosity}" + <!-- It only instruments class files, not any external libs --> + <emma enabled="true"> + <instr verbosity="${verbosity}" mode="overwrite" instrpath="${out.absolute.dir}/classes" outdir="${out.absolute.dir}/classes" metadatafile="${emma.coverage.absolute.file}"> - <filter excludes="${emma.default.filter}" /> - <filter excludes="android.*, com.*, org.*" /> - <filter value="${emma.filter}" /> - </instr> - </emma> - </then> - </if> - - <!-- if the project is a library then we generate a jar file --> - <if condition="${project.is.library}"> - <then> - <echo level="info">Creating library output jar file...</echo> - <property name="out.library.jar.file" location="${out.absolute.dir}/classes.jar" /> - <if> - <condition> - <length string="${android.package.excludes}" trim="true" when="greater" length="0" /> - </condition> - <then> - <echo level="info">Custom jar packaging exclusion: ${android.package.excludes}</echo> - </then> - </if> - - <propertybyreplace name="project.app.package.path" input="${project.app.package}" replace="." with="/" /> - - <jar destfile="${out.library.jar.file}"> - <fileset dir="${out.classes.absolute.dir}" + <filter excludes="${emma.default.filter}" /> + <filter excludes="android.*, com.*, org.*" /> + <filter value="${emma.filter}" /> + </instr> + </emma> + </then> + </if> + + <!-- if the project is a library then we generate a jar file --> + <if condition="${project.is.library}"> + <then> + <echo level="info">Creating library output jar file...</echo> + <property name="out.library.jar.file" location="${out.absolute.dir}/classes.jar" /> + <if> + <condition> + <length string="${android.package.excludes}" trim="true" when="greater" length="0" /> + </condition> + <then> + <echo level="info">Custom jar packaging exclusion: ${android.package.excludes}</echo> + </then> + </if> + + <propertybyreplace name="project.app.package.path" input="${project.app.package}" replace="." with="/" /> + + <jar destfile="${out.library.jar.file}"> + <fileset dir="${out.classes.absolute.dir}" includes="**/*.class" excludes="${project.app.package.path}/R.class ${project.app.package.path}/R$*.class ${project.app.package.path}/BuildConfig.class"/> - <fileset dir="${source.absolute.dir}" excludes="**/*.java ${android.package.excludes}" /> - </jar> - </then> - </if> + <fileset dir="${source.absolute.dir}" excludes="**/*.java ${android.package.excludes}" /> + </jar> + </then> + </if> - </do-only-if-manifest-hasCode> - </target> -<!-- end of modifications for android-annotations --> - - -<!-- + </do-only-if-manifest-hasCode> + </target> + <!-- end of modifications for android-annotations --> + + + <!-- <target name="-post-compile"> </target> --> - <!-- Import the actual build file. + <!-- Import the actual build file. To customize existing targets, there are two options: - Customize only one target: @@ -237,7 +264,7 @@ In all cases you must update the value of version-tag below to read 'custom' instead of an integer, in order to avoid having your file be overridden by tools such as "android update project" --> - <!-- version-tag: custom --> - <import file="${sdk.dir}/tools/ant/build.xml" /> + <!-- version-tag: custom --> + <import file="${sdk.dir}/tools/ant/build.xml" /> </project> diff --git a/main/cgeo.iml b/main/cgeo.iml index 7ba7ab9..10cf664 100644 --- a/main/cgeo.iml +++ b/main/cgeo.iml @@ -6,6 +6,7 @@ <proGuardCfgFiles> <file>file://$MODULE_DIR$/proguard-project.txt</file> </proGuardCfgFiles> + <option name="UPDATE_PROPERTY_FILES" value="false" /> <includeAssetsFromLibraries>true</includeAssetsFromLibraries> </configuration> </facet> @@ -46,7 +47,7 @@ <jarDirectory url="file://$MODULE_DIR$/compile-libs" recursive="false" type="SOURCES" /> </library> </orderEntry> - <orderEntry type="module-library" exported="" scope="PROVIDED"> + <orderEntry type="module-library" exported=""> <library> <CLASSES> <root url="file://$MODULE_DIR$/libs" /> @@ -59,6 +60,7 @@ <orderEntry type="module" module-name="mapswithme-api" exported="" /> <orderEntry type="module" module-name="showcaseview" exported="" /> <orderEntry type="module" module-name="android-support-v7-appcompat" exported="" /> + <orderEntry type="module" module-name="google-play-services_lib" exported="" /> </component> </module> diff --git a/main/libs/butterknife-5.1.1.jar b/main/libs/butterknife-5.1.1.jar Binary files differdeleted file mode 100644 index 45ab11f..0000000 --- a/main/libs/butterknife-5.1.1.jar +++ /dev/null diff --git a/main/libs/butterknife-5.1.2.jar b/main/libs/butterknife-5.1.2.jar Binary files differnew file mode 100644 index 0000000..b1fbf55 --- /dev/null +++ b/main/libs/butterknife-5.1.2.jar diff --git a/main/libs/jackson-annotations-2.4.1.jar b/main/libs/jackson-annotations-2.4.1.jar Binary files differnew file mode 100644 index 0000000..decd2d1 --- /dev/null +++ b/main/libs/jackson-annotations-2.4.1.jar diff --git a/main/libs/jackson-annotations-2.4.1.jar.properties b/main/libs/jackson-annotations-2.4.1.jar.properties new file mode 100644 index 0000000..710f903 --- /dev/null +++ b/main/libs/jackson-annotations-2.4.1.jar.properties @@ -0,0 +1,2 @@ +src=src/jackson-annotations-2.4.1-sources.jar +doc=src/jackson-annotations-2.4.1-javadoc.jar diff --git a/main/libs/jackson-core-2.4.1.1.jar b/main/libs/jackson-core-2.4.1.1.jar Binary files differnew file mode 100644 index 0000000..5fe10ae --- /dev/null +++ b/main/libs/jackson-core-2.4.1.1.jar diff --git a/main/libs/jackson-core-2.4.1.1.jar.properties b/main/libs/jackson-core-2.4.1.1.jar.properties new file mode 100644 index 0000000..0a3222e --- /dev/null +++ b/main/libs/jackson-core-2.4.1.1.jar.properties @@ -0,0 +1,2 @@ +src=src/jackson-core-2.4.1.1-sources.jar +doc=src/jackson-core-2.4.1.1-javadoc.jar diff --git a/main/libs/jackson-databind-2.4.1.3.jar b/main/libs/jackson-databind-2.4.1.3.jar Binary files differnew file mode 100644 index 0000000..231bd1f --- /dev/null +++ b/main/libs/jackson-databind-2.4.1.3.jar diff --git a/main/libs/jackson-databind-2.4.1.3.jar.properties b/main/libs/jackson-databind-2.4.1.3.jar.properties new file mode 100644 index 0000000..f4cc029 --- /dev/null +++ b/main/libs/jackson-databind-2.4.1.3.jar.properties @@ -0,0 +1,2 @@ +src=src/jackson-databind-2.4.1.3-sources.jar +doc=src/jackson-databind-2.4.1.3-javadoc.jar diff --git a/main/libs/rxjava-android-0.19.6.jar.properties b/main/libs/rxjava-android-0.19.6.jar.properties deleted file mode 100644 index 7414a89..0000000 --- a/main/libs/rxjava-android-0.19.6.jar.properties +++ /dev/null @@ -1,2 +0,0 @@ -src=src/rxjava-android-0.19.6-sources.jar -doc=src/rxjava-android-0.19.6-javadoc.jar diff --git a/main/libs/rxjava-android-0.19.6.jar b/main/libs/rxjava-android-0.20.2.jar Binary files differindex ccc8b6a..d38a36f 100644 --- a/main/libs/rxjava-android-0.19.6.jar +++ b/main/libs/rxjava-android-0.20.2.jar diff --git a/main/libs/rxjava-android-0.20.2.jar.properties b/main/libs/rxjava-android-0.20.2.jar.properties new file mode 100644 index 0000000..8b4e999 --- /dev/null +++ b/main/libs/rxjava-android-0.20.2.jar.properties @@ -0,0 +1,2 @@ +src=src/rxjava-android-0.20.2-sources.jar +doc=src/rxjava-android-0.20.2-javadoc.jar diff --git a/main/libs/rxjava-async-util-0.19.6.jar b/main/libs/rxjava-async-util-0.19.6.jar Binary files differdeleted file mode 100644 index 93c1a9b..0000000 --- a/main/libs/rxjava-async-util-0.19.6.jar +++ /dev/null diff --git a/main/libs/rxjava-async-util-0.19.6.jar.properties b/main/libs/rxjava-async-util-0.19.6.jar.properties deleted file mode 100644 index 0396d9a..0000000 --- a/main/libs/rxjava-async-util-0.19.6.jar.properties +++ /dev/null @@ -1,2 +0,0 @@ -src=src/rxjava-async-util-0.19.6-sources.jar -doc=src/rxjava-async-util-0.19.6-javadoc.jar diff --git a/main/libs/rxjava-async-util-0.20.2.jar b/main/libs/rxjava-async-util-0.20.2.jar Binary files differnew file mode 100644 index 0000000..8dc4982 --- /dev/null +++ b/main/libs/rxjava-async-util-0.20.2.jar diff --git a/main/libs/rxjava-async-util-0.20.2.jar.properties b/main/libs/rxjava-async-util-0.20.2.jar.properties new file mode 100644 index 0000000..f9b0524 --- /dev/null +++ b/main/libs/rxjava-async-util-0.20.2.jar.properties @@ -0,0 +1,2 @@ +src=src/rxjava-async-util-0.20.2-sources.jar +doc=src/rxjava-async-util-0.20.2-javadoc.jar diff --git a/main/libs/rxjava-core-0.19.6.jar b/main/libs/rxjava-core-0.19.6.jar Binary files differdeleted file mode 100644 index 9b6cbfa..0000000 --- a/main/libs/rxjava-core-0.19.6.jar +++ /dev/null diff --git a/main/libs/rxjava-core-0.19.6.jar.properties b/main/libs/rxjava-core-0.19.6.jar.properties deleted file mode 100644 index 41b8394..0000000 --- a/main/libs/rxjava-core-0.19.6.jar.properties +++ /dev/null @@ -1,2 +0,0 @@ -src=src/rxjava-core-0.19.6-sources.jar -doc=src/rxjava-core-0.19.6-javadoc.jar diff --git a/main/libs/rxjava-core-0.20.2.jar b/main/libs/rxjava-core-0.20.2.jar Binary files differnew file mode 100644 index 0000000..fda98cc --- /dev/null +++ b/main/libs/rxjava-core-0.20.2.jar diff --git a/main/libs/rxjava-core-0.20.2.jar.properties b/main/libs/rxjava-core-0.20.2.jar.properties new file mode 100644 index 0000000..7da0271 --- /dev/null +++ b/main/libs/rxjava-core-0.20.2.jar.properties @@ -0,0 +1,2 @@ +src=src/rxjava-core-0.20.2-sources.jar +doc=src/rxjava-core-0.20.2-javadoc.jar diff --git a/main/libs/src/jackson-annotations-2.4.1-javadoc.jar b/main/libs/src/jackson-annotations-2.4.1-javadoc.jar Binary files differnew file mode 100644 index 0000000..40bb9d2 --- /dev/null +++ b/main/libs/src/jackson-annotations-2.4.1-javadoc.jar diff --git a/main/libs/src/jackson-annotations-2.4.1-sources.jar b/main/libs/src/jackson-annotations-2.4.1-sources.jar Binary files differnew file mode 100644 index 0000000..f9e50a4 --- /dev/null +++ b/main/libs/src/jackson-annotations-2.4.1-sources.jar diff --git a/main/libs/src/jackson-core-2.4.1.1-javadoc.jar b/main/libs/src/jackson-core-2.4.1.1-javadoc.jar Binary files differnew file mode 100644 index 0000000..cfae85e --- /dev/null +++ b/main/libs/src/jackson-core-2.4.1.1-javadoc.jar diff --git a/main/libs/src/jackson-core-2.4.1.1-sources.jar b/main/libs/src/jackson-core-2.4.1.1-sources.jar Binary files differnew file mode 100644 index 0000000..0647e10 --- /dev/null +++ b/main/libs/src/jackson-core-2.4.1.1-sources.jar diff --git a/main/libs/src/jackson-databind-2.4.1.3-javadoc.jar b/main/libs/src/jackson-databind-2.4.1.3-javadoc.jar Binary files differnew file mode 100644 index 0000000..5bab5af --- /dev/null +++ b/main/libs/src/jackson-databind-2.4.1.3-javadoc.jar diff --git a/main/libs/src/jackson-databind-2.4.1.3-sources.jar b/main/libs/src/jackson-databind-2.4.1.3-sources.jar Binary files differnew file mode 100644 index 0000000..1bab3a5 --- /dev/null +++ b/main/libs/src/jackson-databind-2.4.1.3-sources.jar diff --git a/main/libs/src/rxjava-android-0.19.6-javadoc b/main/libs/src/rxjava-android-0.19.6-javadoc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/main/libs/src/rxjava-android-0.19.6-javadoc diff --git a/main/libs/src/rxjava-android-0.19.6-javadoc.jar b/main/libs/src/rxjava-android-0.19.6-javadoc.jar Binary files differdeleted file mode 100644 index a92ab4f..0000000 --- a/main/libs/src/rxjava-android-0.19.6-javadoc.jar +++ /dev/null diff --git a/main/libs/src/rxjava-android-0.19.6-sources.jar b/main/libs/src/rxjava-android-0.19.6-sources.jar Binary files differdeleted file mode 100644 index ea4eba0..0000000 --- a/main/libs/src/rxjava-android-0.19.6-sources.jar +++ /dev/null diff --git a/main/libs/src/rxjava-android-0.20.2-javadoc.jar b/main/libs/src/rxjava-android-0.20.2-javadoc.jar Binary files differnew file mode 100644 index 0000000..fd093ca --- /dev/null +++ b/main/libs/src/rxjava-android-0.20.2-javadoc.jar diff --git a/main/libs/src/rxjava-android-0.20.2-sources.jar b/main/libs/src/rxjava-android-0.20.2-sources.jar Binary files differnew file mode 100644 index 0000000..4f836c7 --- /dev/null +++ b/main/libs/src/rxjava-android-0.20.2-sources.jar diff --git a/main/libs/src/rxjava-async-util-0.19.6-javadoc b/main/libs/src/rxjava-async-util-0.19.6-javadoc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/main/libs/src/rxjava-async-util-0.19.6-javadoc diff --git a/main/libs/src/rxjava-async-util-0.19.6-javadoc.jar b/main/libs/src/rxjava-async-util-0.19.6-javadoc.jar Binary files differdeleted file mode 100644 index f943e5f..0000000 --- a/main/libs/src/rxjava-async-util-0.19.6-javadoc.jar +++ /dev/null diff --git a/main/libs/src/rxjava-async-util-0.20.2-javadoc.jar b/main/libs/src/rxjava-async-util-0.20.2-javadoc.jar Binary files differnew file mode 100644 index 0000000..89d3096 --- /dev/null +++ b/main/libs/src/rxjava-async-util-0.20.2-javadoc.jar diff --git a/main/libs/src/rxjava-async-util-0.19.6-sources.jar b/main/libs/src/rxjava-async-util-0.20.2-sources.jar Binary files differindex 82ca499..21a32c4 100644 --- a/main/libs/src/rxjava-async-util-0.19.6-sources.jar +++ b/main/libs/src/rxjava-async-util-0.20.2-sources.jar diff --git a/main/libs/src/rxjava-core-0.19.6-javadoc b/main/libs/src/rxjava-core-0.19.6-javadoc new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/main/libs/src/rxjava-core-0.19.6-javadoc diff --git a/main/libs/src/rxjava-core-0.19.6-javadoc.jar b/main/libs/src/rxjava-core-0.19.6-javadoc.jar Binary files differdeleted file mode 100644 index 5523ee0..0000000 --- a/main/libs/src/rxjava-core-0.19.6-javadoc.jar +++ /dev/null diff --git a/main/libs/src/rxjava-core-0.19.6-sources.jar b/main/libs/src/rxjava-core-0.19.6-sources.jar Binary files differdeleted file mode 100644 index 0c2aa68..0000000 --- a/main/libs/src/rxjava-core-0.19.6-sources.jar +++ /dev/null diff --git a/main/libs/src/rxjava-core-0.20.2-javadoc.jar b/main/libs/src/rxjava-core-0.20.2-javadoc.jar Binary files differnew file mode 100644 index 0000000..26d69fe --- /dev/null +++ b/main/libs/src/rxjava-core-0.20.2-javadoc.jar diff --git a/main/libs/src/rxjava-core-0.20.2-sources.jar b/main/libs/src/rxjava-core-0.20.2-sources.jar Binary files differnew file mode 100644 index 0000000..1d63878 --- /dev/null +++ b/main/libs/src/rxjava-core-0.20.2-sources.jar diff --git a/main/lint.xml b/main/lint.xml index 905b9a4..60a2d28 100644 --- a/main/lint.xml +++ b/main/lint.xml @@ -12,6 +12,8 @@ </issue> <issue id="MissingTranslation" severity="ignore" /> <issue id="Registered" severity="ignore" /> + <issue id="RtlHardcoded" severity="ignore" /> + <issue id="RtlSymmetry" severity="ignore" /> <issue id="UnusedAttribute" severity="ignore" /> <issue id="UnusedResources"> <ignore path="res/drawable-mdpi/attribute_maintenance.png" /> diff --git a/main/proguard-project.txt b/main/proguard-project.txt index 2a27d99..5cc8783 100644 --- a/main/proguard-project.txt +++ b/main/proguard-project.txt @@ -21,9 +21,12 @@ # rxjava internal references sun.misc.Unsafe -dontwarn sun.misc.Unsafe +# jackson internal references +-dontwarn org.w3c.dom.bootstrap.DOMImplementationRegistry + #-dontnote org.apache.commons.logging.** --keep public class cgeo.geocaching.* +-keep class cgeo.geocaching.** { *; } -keep class android.support.v4.os.** { *; } -keep class ch.boye.httpclientandroidlib.conn.scheme.Scheme { *; } @@ -59,7 +62,16 @@ # Null analysis annotations of Eclipse JDT are just used by the Eclipse compiler, so ignore them here -dontwarn org.eclipse.jdt.annotation.** +# keep Emma code coverage during debug builds, and ignore related warnings +-keep class com.vladium.** { *; } +-dontwarn com.vladium.** + +# keep some test only utils classes +-keep class org.apache.commons.lang3.StringUtils { *; } +-keep class org.apache.commons.io.IOUtils { *; } +-keep class org.apache.commons.io.FileUtils { *; } + # action providers are only referenced from XML -keep public class cgeo.geocaching.sorting.SortActionProvider { *; } -keep public class cgeo.geocaching.ui.NavigationActionProvider { *; } --keep public class cgeo.geocaching.apps.cache.navi.NavigationSelectionActionProvider { *; }
\ No newline at end of file +-keep public class cgeo.geocaching.apps.cache.navi.NavigationSelectionActionProvider { *; } diff --git a/main/project.properties b/main/project.properties index a228075..4a461c9 100644 --- a/main/project.properties +++ b/main/project.properties @@ -8,12 +8,13 @@ # project structure. # # To enable ProGuard to shrink and obfuscate your code, uncomment this (available properties: sdk.dir, user.home): -proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt +proguard.config=${sdk.dir}/tools/proguard/proguard-android.txt:proguard-project.txt:../google-play-services_lib/proguard.txt # Project target. target=Google Inc.:Google APIs:19 android.library.reference.1=../mapswithme-api android.library.reference.2=../android-support-v7-appcompat +android.library.reference.3=../google-play-services_lib +android.library.reference.4=../showcaseview java.source=1.7 java.target=1.7 -android.library.reference.3=../showcaseview diff --git a/main/project/eclipse installation/cgeo eclipse components.p2f b/main/project/eclipse installation/cgeo eclipse components.p2f index f758e68..3afac28 100644 --- a/main/project/eclipse installation/cgeo eclipse components.p2f +++ b/main/project/eclipse installation/cgeo eclipse components.p2f @@ -1,40 +1,108 @@ <?xml version='1.0' encoding='UTF-8'?> <?p2f version='1.0.0'?> <p2f version='1.0.0'> - <ius size='7'> - <iu id='com.android.ide.eclipse.ddms.feature.group' name='Android DDMS' version='22.2.1.v201309180102-833290'> + <ius size='20'> + <iu id='com.android.ide.eclipse.ddms.feature.feature.group' name='Android DDMS' version='23.0.2.1259578'> <repositories size='1'> <repository location='https://dl-ssl.google.com/android/eclipse/'/> </repositories> </iu> - <iu id='com.android.ide.eclipse.adt.feature.group' name='Android Development Tools' version='22.2.1.v201309180102-833290'> + <iu id='com.android.ide.eclipse.adt.feature.feature.group' name='Android Development Tools' version='23.0.2.1259578'> <repositories size='1'> <repository location='https://dl-ssl.google.com/android/eclipse/'/> </repositories> </iu> - <iu id='com.android.ide.eclipse.hierarchyviewer.feature.group' name='Android Hierarchy Viewer' version='22.2.1.v201309180102-833290'> + <iu id='com.android.ide.eclipse.hierarchyviewer.feature.feature.group' name='Android Hierarchy Viewer' version='23.0.2.1259578'> <repositories size='1'> <repository location='https://dl-ssl.google.com/android/eclipse/'/> </repositories> </iu> - <iu id='com.android.ide.eclipse.traceview.feature.group' name='Android Traceview' version='22.2.1.v201309180102-833290'> + <iu id='com.android.ide.eclipse.traceview.feature.feature.group' name='Android Traceview' version='23.0.2.1259578'> <repositories size='1'> <repository location='https://dl-ssl.google.com/android/eclipse/'/> </repositories> </iu> - <iu id='org.eclipse.mylyn.github.feature.feature.group' name='Eclipse GitHub integration with task focused interface' version='3.0.0.201306101825-r'> + <iu id='org.eclipse.recommenders.mylyn.rcp.feature.feature.group' name='Code Recommenders Mylyn Integration' version='2.1.0.v20140608-1213'> <repositories size='1'> - <repository location='http://download.eclipse.org/releases/kepler'/> + <repository location='http://download.eclipse.org/releases/luna'/> </repositories> </iu> - <iu id='epp.package.java' name='Eclipse IDE for Java Developers' version='2.0.1.20130919-0803'> + <iu id='org.eclipse.recommenders.snipmatch.rcp.feature.feature.group' name='Code Recommenders Snipmatch' version='2.1.0.v20140606-1807'> <repositories size='1'> - <repository location='http://download.eclipse.org/releases/kepler'/> + <repository location='http://download.eclipse.org/releases/luna'/> </repositories> </iu> - <iu id='edu.umd.cs.findbugs.plugin.eclipse.feature.group' name='FindBugs Feature' version='2.0.2.20121210'> + <iu id='org.eclipse.mylyn.github.feature.feature.group' name='Eclipse GitHub integration with task focused interface' version='3.4.0.201406110918-r'> + <repositories size='2'> + <repository location='http://download.eclipse.org/releases/luna'/> + <repository location='http://download.eclipse.org/egit/github/updates'/> + </repositories> + </iu> + <iu id='epp.package.java' name='Eclipse IDE for Java Developers' version='4.4.0.20140612-0500'> + <repositories size='1'> + <repository location='http://download.eclipse.org/releases/luna'/> + </repositories> + </iu> + <iu id='edu.umd.cs.findbugs.plugin.eclipse.feature.group' name='FindBugs Feature' version='3.0.0.20140625-7bb789f'> + <repositories size='1'> + <repository location='http://findbugs.cs.umd.edu/eclipse/'/> + </repositories> + </iu> + <iu id='com.genymobile.genymotion.ide.eclipse.feature.group' name='Genymotion Eclipse Tools' version='1.0.3.201403261147'> + <repositories size='1'> + <repository location='http://plugins.genymotion.com/eclipse'/> + </repositories> + </iu> + <iu id='org.springsource.ide.eclipse.gradle.feature.feature.group' name='Gradle IDE' version='3.6.0.201407080553-RELEASE'> + <repositories size='1'> + <repository location='http://dist.springsource.com/release/TOOLS/gradle'/> + </repositories> + </iu> + <iu id='org.eclipse.jgit.http.apache.feature.group' name='Java implementation of Git - optional Http support using Apache httpclient' version='3.4.1.201406201815-r'> + <repositories size='1'> + <repository location='http://download.eclipse.org/egit/updates'/> + </repositories> + </iu> + <iu id='org.eclipse.jgit.java7.feature.group' name='Java implementation of Git - optional Java 7 libraries' version='3.4.1.201406201815-r'> + <repositories size='1'> + <repository location='http://download.eclipse.org/egit/updates'/> + </repositories> + </iu> + <iu id='org.eclipse.wst.jsdt.feature.feature.group' name='JavaScript Development Tools' version='1.6.0.v201405071612-7G7FFyXFBBoPc8Pvc8c0wn'> + <repositories size='1'> + <repository location='http://download.eclipse.org/releases/luna'/> + </repositories> + </iu> + <iu id='markdown.editor.feature.feature.group' name='Markdown Editor' version='0.2.3'> + <repositories size='1'> + <repository location='http://www.winterwell.com/software/updatesite/'/> + </repositories> + </iu> + <iu id='org.moreunit.feature.group' name='MoreUnit For Java' version='3.0.4'> <repositories size='1'> - <repository location='http://findbugs.cs.umd.edu/eclipse'/> + <repository location='http://moreunit.sourceforge.net/update-site/'/> + </repositories> + </iu> + <iu id='org.eclipse.mylyn.gerrit.feature.feature.group' name='Mylyn Reviews Connector: Gerrit' version='2.3.0.v20140605-1717'> + <repositories size='3'> + <repository location='http://download.eclipse.org/releases/luna'/> + <repository location='http://download.eclipse.org/mylyn/releases/luna'/> + <repository location='http://download.eclipse.org/mylyn/releases/latest'/> + </repositories> + </iu> + <iu id='org.eclipse.oomph.winexplorer.feature.group' name='Oomph Windows Explorer' version='1.0.0.v20140803-0627'> + <repositories size='1'> + <repository location='http://download.eclipse.org/oomph/updates/'/> + </repositories> + </iu> + <iu id='org.projectusus.feature.group' name='Project Usus' version='0.7.4.201401122006'> + <repositories size='1'> + <repository location='http://projectusus.googlecode.com/svn/updates/'/> + </repositories> + </iu> + <iu id='com.android.ide.eclipse.gldebugger.feature.feature.group' name='Tracer for OpenGL ES' version='23.0.2.1259578'> + <repositories size='1'> + <repository location='https://dl-ssl.google.com/android/eclipse/'/> </repositories> </iu> </ius> diff --git a/main/project/libraries/update-libs.sh b/main/project/libraries/update-libs.sh index f2e9ced..1a80f70 100755 --- a/main/project/libraries/update-libs.sh +++ b/main/project/libraries/update-libs.sh @@ -1,7 +1,10 @@ #! /bin/sh # -RXJAVA=0.19.6 +RXJAVA=0.20.2 +JACKSONCORE=2.4.1.1 +JACKSONDATABIND=2.4.1.3 +JACKSONANNOTATIONS=2.4.1 cd $(git rev-parse --show-toplevel)/main/libs @@ -31,3 +34,10 @@ updatelib com/netflix/rxjava rxjava-core $RXJAVA updatelib com/netflix/rxjava rxjava-android $RXJAVA updatelib com/netflix/rxjava rxjava-async-util $RXJAVA fixgradle RXVersion $RXJAVA + +updatelib com/fasterxml/jackson/core jackson-core $JACKSONCORE +fixgradle JacksonCoreVersion $JACKSONCORE +updatelib com/fasterxml/jackson/core jackson-databind $JACKSONDATABIND +fixgradle JacksonDatabindVersion $JACKSONDATABIND +updatelib com/fasterxml/jackson/core jackson-annotations $JACKSONANNOTATIONS +fixgradle JacksonAnnotationsVersion $JACKSONANNOTATIONS diff --git a/main/res/layout/about_changes_page.xml b/main/res/layout/about_changes_page.xml index 544d338..355ce90 100644 --- a/main/res/layout/about_changes_page.xml +++ b/main/res/layout/about_changes_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".AboutActivity$ChangeLogViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/about_contributors_page.xml b/main/res/layout/about_contributors_page.xml index 9a0955e..844cb34 100644 --- a/main/res/layout/about_contributors_page.xml +++ b/main/res/layout/about_contributors_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".AboutActivity$ContributorsViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/about_help_page.xml b/main/res/layout/about_help_page.xml index 0439e22..8985ffa 100644 --- a/main/res/layout/about_help_page.xml +++ b/main/res/layout/about_help_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".AboutActivity$HelpViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/about_license_page.xml b/main/res/layout/about_license_page.xml index b33f0f9..e91adf1 100644 --- a/main/res/layout/about_license_page.xml +++ b/main/res/layout/about_license_page.xml @@ -4,7 +4,8 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".AboutActivity$LicenseViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/about_version_page.xml b/main/res/layout/about_version_page.xml index 80630e4..2eeadcc 100644 --- a/main/res/layout/about_version_page.xml +++ b/main/res/layout/about_version_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".AboutActivity$VersionViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/addresslist_activity.xml b/main/res/layout/addresslist_activity.xml index 4fbe53c..57d13f2 100644 --- a/main/res/layout/addresslist_activity.xml +++ b/main/res/layout/addresslist_activity.xml @@ -3,7 +3,8 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".AddressListActivity" > <ListView android:id="@android:id/list" diff --git a/main/res/layout/addresslist_item.xml b/main/res/layout/addresslist_item.xml index dac1768..aa04728 100644 --- a/main/res/layout/addresslist_item.xml +++ b/main/res/layout/addresslist_item.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/button_map" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -9,7 +10,8 @@ android:paddingBottom="7dip" android:paddingLeft="5dip" android:paddingRight="5dip" - android:paddingTop="7dip" > + android:paddingTop="7dip" + tools:context=".ui.AddressListAdapter" > <TextView android:id="@+id/label" diff --git a/main/res/layout/attribute_image.xml b/main/res/layout/attribute_image.xml index f68aef4..75eff6a 100644 --- a/main/res/layout/attribute_image.xml +++ b/main/res/layout/attribute_image.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/attribute_images" android:layout_width="wrap_content" - android:layout_height="wrap_content" > + android:layout_height="wrap_content" + tools:context=".CacheDetailActivity$AttributeViewBuilder" > <ImageView android:id="@+id/attribute_image" diff --git a/main/res/layout/authorization_activity.xml b/main/res/layout/authorization_activity.xml index 2907286..6364ee2 100644 --- a/main/res/layout/authorization_activity.xml +++ b/main/res/layout/authorization_activity.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".network.OAuthAuthorizationActivity" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/cachedetail_activity.xml b/main/res/layout/cachedetail_activity.xml index d197bcd..b2e16a0 100644 --- a/main/res/layout/cachedetail_activity.xml +++ b/main/res/layout/cachedetail_activity.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:background="?background_color"
- android:orientation="vertical" >
+ android:orientation="vertical"
+ tools:context=".CacheDetailActivity" >
<android.support.v4.view.ViewPager
android:id="@+id/viewpager"
diff --git a/main/res/layout/cachedetail_description_page.xml b/main/res/layout/cachedetail_description_page.xml index dba6789..4fa342d 100644 --- a/main/res/layout/cachedetail_description_page.xml +++ b/main/res/layout/cachedetail_description_page.xml @@ -1,7 +1,9 @@ <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
- android:layout_height="fill_parent" >
+ android:layout_height="fill_parent"
+ tools:context=".CacheDetailActivity$DescriptionViewCreator" >
<LinearLayout
android:layout_width="fill_parent"
@@ -134,6 +136,7 @@ android:id="@+id/edit_personalnote"
style="@style/button_small"
android:text="@string/cache_personal_note_edit" />
+
<Button
android:id="@+id/upload_personalnote"
style="@style/button_small"
diff --git a/main/res/layout/cachedetail_details_page.xml b/main/res/layout/cachedetail_details_page.xml index 7b67846..e8902fd 100644 --- a/main/res/layout/cachedetail_details_page.xml +++ b/main/res/layout/cachedetail_details_page.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
- android:scrollbars="none" >
+ android:scrollbars="none"
+ tools:context=".CacheDetailActivity$DetailsViewCreator" >
<LinearLayout
android:layout_width="fill_parent"
diff --git a/main/res/layout/cachedetail_images_page.xml b/main/res/layout/cachedetail_images_page.xml index 2360dd0..cf26fe5 100644 --- a/main/res/layout/cachedetail_images_page.xml +++ b/main/res/layout/cachedetail_images_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?>
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:orientation="vertical"
- android:padding="4dip" >
+ android:padding="4dip"
+ tools:context=".CacheDetailActivity$ImagesViewCreator" >
<LinearLayout
android:id="@+id/spoiler_list"
diff --git a/main/res/layout/cachedetail_inventory_page.xml b/main/res/layout/cachedetail_inventory_page.xml index a15cedd..c134a79 100644 --- a/main/res/layout/cachedetail_inventory_page.xml +++ b/main/res/layout/cachedetail_inventory_page.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="?background_color"
android:clipToPadding="false"
- android:scrollbarStyle="outsideOverlay" >
+ android:scrollbarStyle="outsideOverlay"
+ tools:context=".CacheDetailActivity$InventoryViewCreator" >
</ListView>
\ No newline at end of file diff --git a/main/res/layout/cachedetail_waypoints_footer.xml b/main/res/layout/cachedetail_waypoints_footer.xml index 58187c1..440363b 100644 --- a/main/res/layout/cachedetail_waypoints_footer.xml +++ b/main/res/layout/cachedetail_waypoints_footer.xml @@ -1,9 +1,10 @@ <?xml version="1.0" encoding="utf-8"?>
<Button xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/add_waypoint"
style="@style/button_full"
android:layout_margin="10dip"
- android:padding="10dip"
android:clickable="true"
- android:text="@string/cache_waypoints_add" />
-
+ android:padding="10dip"
+ android:text="@string/cache_waypoints_add"
+ tools:context=".CacheDetailActivity$WaypointsViewCreator" />
diff --git a/main/res/layout/cachedetail_waypoints_page.xml b/main/res/layout/cachedetail_waypoints_page.xml index 8111210..dbf896a 100644 --- a/main/res/layout/cachedetail_waypoints_page.xml +++ b/main/res/layout/cachedetail_waypoints_page.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?>
<ListView xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="fill_parent"
android:layout_height="fill_parent"
android:cacheColorHint="?background_color"
@@ -11,6 +12,7 @@ android:footerDividersEnabled="false"
android:headerDividersEnabled="false"
android:listSelector="?background_color"
- android:scrollbarStyle="outsideOverlay" >
+ android:scrollbarStyle="outsideOverlay"
+ tools:context=".CacheDetailActivity$WaypointsViewCreator" >
</ListView>
\ No newline at end of file diff --git a/main/res/layout/cachelist_spinneritem.xml b/main/res/layout/cachelist_spinneritem.xml index 58e070e..fce9b88 100644 --- a/main/res/layout/cachelist_spinneritem.xml +++ b/main/res/layout/cachelist_spinneritem.xml @@ -1,14 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" - android:orientation="vertical" android:layout_width="match_parent" + style="?attr/spinnerDropDownItemStyle" + android:layout_width="match_parent" android:layout_height="?attr/dropdownListPreferredItemHeight" - android:minHeight="?attr/dropdownListPreferredItemHeight" android:layout_gravity="left|center_vertical" - style="?attr/spinnerDropDownItemStyle" - > - + android:minHeight="?attr/dropdownListPreferredItemHeight" + android:orientation="vertical" + tools:context=".CacheListSpinnerAdapter" > <TextView android:id="@android:id/text1" diff --git a/main/res/layout/cacheslist_activity.xml b/main/res/layout/cacheslist_activity.xml index 561baa2..62d9861 100644 --- a/main/res/layout/cacheslist_activity.xml +++ b/main/res/layout/cacheslist_activity.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".CacheListActivity" > <include layout="@layout/filter_bar" /> @@ -33,6 +35,7 @@ android:fastScrollEnabled="true" android:padding="0dip" android:scrollbarStyle="outsideOverlay" - android:visibility="gone" /> + android:visibility="gone" + tools:listitem="@layout/cacheslist_item" /> </LinearLayout>
\ No newline at end of file diff --git a/main/res/layout/cacheslist_footer.xml b/main/res/layout/cacheslist_footer.xml index ed2de18..ff323f4 100644 --- a/main/res/layout/cacheslist_footer.xml +++ b/main/res/layout/cacheslist_footer.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/more_caches" android:layout_width="fill_parent" android:layout_height="fill_parent" @@ -13,4 +14,5 @@ android:singleLine="true" android:text="@string/caches_no_cache" android:textColor="@color/just_white" - android:textSize="16sp" /> + android:textSize="16sp" + tools:context=".CacheListActivity" /> diff --git a/main/res/layout/cacheslist_item.xml b/main/res/layout/cacheslist_item.xml index 260b0d0..efb622e 100644 --- a/main/res/layout/cacheslist_item.xml +++ b/main/res/layout/cacheslist_item.xml @@ -5,7 +5,8 @@ android:id="@+id/one_cache" android:layout_width="fill_parent" android:layout_height="wrap_content" - android:background="?background_color" > + android:background="?background_color" + tools:context=".ui.CacheListAdapter" > <!-- selection mode checkbox --> diff --git a/main/res/layout/compass_activity.xml b/main/res/layout/compass_activity.xml index ca61cc3..914a17a 100644 --- a/main/res/layout/compass_activity.xml +++ b/main/res/layout/compass_activity.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".CompassActivity" > <LinearLayout android:id="@+id/info1" @@ -16,6 +18,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:text="@null" android:textColor="?text_color" android:textSize="14sp" /> @@ -24,6 +27,7 @@ android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" + android:text="@null" android:textColor="?text_color" android:textSize="14sp" /> diff --git a/main/res/layout/coordinatesinput_dialog.xml b/main/res/layout/coordinatesinput_dialog.xml index 9b5cb8d..a12bedf 100644 --- a/main/res/layout/coordinatesinput_dialog.xml +++ b/main/res/layout/coordinatesinput_dialog.xml @@ -3,7 +3,8 @@ android:id="@+id/scroller" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:fillViewport="true" > + android:fillViewport="true" + tools:context=".ui.dialog.CoordinatesInputDialog" > <LinearLayout android:id="@+id/scroller_child" diff --git a/main/res/layout/editwaypoint_activity.xml b/main/res/layout/editwaypoint_activity.xml index c21e25d..785ee91 100644 --- a/main/res/layout/editwaypoint_activity.xml +++ b/main/res/layout/editwaypoint_activity.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".EditWaypointActivity" > <LinearLayout android:layout_width="fill_parent" @@ -49,17 +51,17 @@ android:entries="@array/distance_units" /> </LinearLayout> - <AutoCompleteTextView - android:id="@+id/name" - style="@style/edittext_full" - android:hint="@string/waypoint_name" /> - <Spinner android:id="@+id/type" android:layout_width="fill_parent" android:layout_height="wrap_content" android:visibility="gone" /> + <AutoCompleteTextView + android:id="@+id/name" + style="@style/edittext_full" + android:hint="@string/waypoint_name" /> + <EditText android:id="@+id/note" style="@style/edittext_full" diff --git a/main/res/layout/fieldnote_export_dialog.xml b/main/res/layout/fieldnote_export_dialog.xml index 9859b0f..c76225c 100644 --- a/main/res/layout/fieldnote_export_dialog.xml +++ b/main/res/layout/fieldnote_export_dialog.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:padding="3dip" >
+ android:padding="3dip"
+ tools:context=".export.FieldnoteExport" >
<TextView
android:layout_width="wrap_content"
diff --git a/main/res/layout/fragment_edit_note.xml b/main/res/layout/fragment_edit_note.xml index 1e9fbf9..893dc31 100644 --- a/main/res/layout/fragment_edit_note.xml +++ b/main/res/layout/fragment_edit_note.xml @@ -1,9 +1,11 @@ <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/edit_note" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".ui.EditNoteDialog" > <EditText android:id="@+id/note" diff --git a/main/res/layout/gpx.xml b/main/res/layout/gpx.xml index 4367f82..e1d696d 100644 --- a/main/res/layout/gpx.xml +++ b/main/res/layout/gpx.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".files.AbstractFileListActivity" > <Button android:id="@+id/select_dir" diff --git a/main/res/layout/gpx_export_dialog.xml b/main/res/layout/gpx_export_dialog.xml index 9c97848..02e9134 100644 --- a/main/res/layout/gpx_export_dialog.xml +++ b/main/res/layout/gpx_export_dialog.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
+ xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
- android:padding="3dip" >
+ android:padding="3dip"
+ tools:context=".export.GpxExport" >
<TextView
android:id="@id/info"
diff --git a/main/res/layout/gpx_item.xml b/main/res/layout/gpx_item.xml index f0a0647..372bb9a 100644 --- a/main/res/layout/gpx_item.xml +++ b/main/res/layout/gpx_item.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/button_map" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -9,7 +10,8 @@ android:paddingBottom="7dip" android:paddingLeft="5dip" android:paddingRight="5dip" - android:paddingTop="7dip" > + android:paddingTop="7dip" + tools:context=".ui.GPXListAdapter" > <TextView android:id="@+id/filepath" diff --git a/main/res/layout/image_item.xml b/main/res/layout/image_item.xml index 3478444..5eae256 100644 --- a/main/res/layout/image_item.xml +++ b/main/res/layout/image_item.xml @@ -1,7 +1,9 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/map_image" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_gravity="center" - android:gravity="center" /> + android:gravity="center" + tools:context=".ui.ImagesList" /> diff --git a/main/res/layout/images_activity.xml b/main/res/layout/images_activity.xml index 861fa7e..d32e9e5 100644 --- a/main/res/layout/images_activity.xml +++ b/main/res/layout/images_activity.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".ImagesActivity" > <include layout="@layout/cachedetail_images_page" /> diff --git a/main/res/layout/imageselect_activity.xml b/main/res/layout/imageselect_activity.xml index 690baa2..0c74a18 100644 --- a/main/res/layout/imageselect_activity.xml +++ b/main/res/layout/imageselect_activity.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".ImageSelectActivity" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/internal_browser.xml b/main/res/layout/internal_browser.xml index 1e09ee0..cf38076 100644 --- a/main/res/layout/internal_browser.xml +++ b/main/res/layout/internal_browser.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="utf-8"?> - <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" - android:orientation="vertical" - android:layout_width="match_parent" - android:layout_height="match_parent"> + xmlns:tools="http://schemas.android.com/tools" + android:layout_width="match_parent" + android:layout_height="match_parent" + android:orientation="vertical" + tools:context=".activity.SimpleWebviewActivity" > <WebView - android:id="@+id/webview" - android:layout_width="match_parent" - android:layout_height="match_parent" /> + android:id="@+id/webview" + android:layout_width="match_parent" + android:layout_height="match_parent" /> + </LinearLayout>
\ No newline at end of file diff --git a/main/res/layout/livemapinfo.xml b/main/res/layout/livemapinfo.xml index 889febc..564ed00 100644 --- a/main/res/layout/livemapinfo.xml +++ b/main/res/layout/livemapinfo.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/live_map_scroll" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".ui.dialog.LiveMapInfoDialogBuilder" > <LinearLayout android:layout_width="fill_parent" @@ -18,7 +20,6 @@ android:text="@string/live_map_notification" android:textColor="?text_color" android:textSize="14sp" /> - </LinearLayout> </ScrollView>
\ No newline at end of file diff --git a/main/res/layout/logcache_activity.xml b/main/res/layout/logcache_activity.xml index b01ea74..2294fc7 100644 --- a/main/res/layout/logcache_activity.xml +++ b/main/res/layout/logcache_activity.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" android:orientation="vertical" - android:padding="4dip" xmlns:tools="http://schemas.android.com/tools"> + android:padding="4dip" + tools:context=".LogCacheActivity" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/logcache_trackable_item.xml b/main/res/layout/logcache_trackable_item.xml index 1eab116..72082ac 100644 --- a/main/res/layout/logcache_trackable_item.xml +++ b/main/res/layout/logcache_trackable_item.xml @@ -1,12 +1,14 @@ <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_marginBottom="5dip" android:orientation="horizontal" android:paddingBottom="10dip" android:paddingLeft="10dip" - android:paddingRight="10dip" > + android:paddingRight="10dip" + tools:context=".LogCacheActivity" > <LinearLayout android:id="@+id/info" diff --git a/main/res/layout/logs_item.xml b/main/res/layout/logs_item.xml index 6ce20bb..b362d9e 100644 --- a/main/res/layout/logs_item.xml +++ b/main/res/layout/logs_item.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="wrap_content" android:paddingBottom="3dip" - android:paddingTop="3dip" > + android:paddingTop="3dip" + tools:context=".ui.logs.LogsViewCreator$1" > <TextView android:id="@+id/author" diff --git a/main/res/layout/logs_page.xml b/main/res/layout/logs_page.xml index 11f7855..e168be5 100644 --- a/main/res/layout/logs_page.xml +++ b/main/res/layout/logs_page.xml @@ -12,6 +12,7 @@ android:headerDividersEnabled="false"
android:listSelector="?background_color"
android:scrollbarStyle="outsideOverlay"
+ tools:context=".ui.logs.LogsViewCreator"
tools:listitem="@layout/logs_item" >
</ListView>
\ No newline at end of file diff --git a/main/res/layout/logtrackable_activity.xml b/main/res/layout/logtrackable_activity.xml index 2a901ea..dcd526b 100644 --- a/main/res/layout/logtrackable_activity.xml +++ b/main/res/layout/logtrackable_activity.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".LogTrackableActivity" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/main_activity.xml b/main/res/layout/main_activity.xml index d2bd025..474565f 100644 --- a/main/res/layout/main_activity.xml +++ b/main/res/layout/main_activity.xml @@ -4,7 +4,8 @@ android:id="@+id/mainscreen" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:layout_gravity="center" > + android:layout_gravity="center" + tools:context=".MainActivity" > <fragment android:id="@+id/status" diff --git a/main/res/layout/main_activity_connectorstatus.xml b/main/res/layout/main_activity_connectorstatus.xml index 33c4d79..6c334aa 100644 --- a/main/res/layout/main_activity_connectorstatus.xml +++ b/main/res/layout/main_activity_connectorstatus.xml @@ -1,4 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <TextView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" style="@style/location_current" - android:text="@string/init_login_popup_working" /> + android:text="@string/init_login_popup_working" + tools:context=".MainActivity$1" /> diff --git a/main/res/layout/map_google.xml b/main/res/layout/map_google.xml index 85f550a..85fee7a 100644 --- a/main/res/layout/map_google.xml +++ b/main/res/layout/map_google.xml @@ -1,10 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > - - + android:orientation="vertical" + tools:context=".maps.google.v1.GoogleMapProvider" > <include layout="@layout/actionbar_maps" /> diff --git a/main/res/layout/map_mapsforge.xml b/main/res/layout/map_mapsforge.xml index c44a3ee..a559545 100644 --- a/main/res/layout/map_mapsforge.xml +++ b/main/res/layout/map_mapsforge.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".maps.mapsforge.MapsforgeMapProvider" > <include layout="@layout/actionbar_maps" /> diff --git a/main/res/layout/map_mapsforge_old.xml b/main/res/layout/map_mapsforge_old.xml index daa5f74..9ed6596 100644 --- a/main/res/layout/map_mapsforge_old.xml +++ b/main/res/layout/map_mapsforge_old.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".maps.mapsforge.MapsforgeMapProvider" > <include layout="@layout/actionbar_maps" /> diff --git a/main/res/layout/mapfile_item.xml b/main/res/layout/mapfile_item.xml index c665894..dd17f69 100644 --- a/main/res/layout/mapfile_item.xml +++ b/main/res/layout/mapfile_item.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/mapfile_item" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -9,7 +10,8 @@ android:paddingBottom="7dip" android:paddingLeft="5dip" android:paddingRight="5dip" - android:paddingTop="7dip" > + android:paddingTop="7dip" + tools:context=".ui.FileSelectionListAdapter" > <TextView android:id="@+id/mapfilepath" diff --git a/main/res/layout/navigateanypoint_activity.xml b/main/res/layout/navigateanypoint_activity.xml index 3305d56..caec2f4 100644 --- a/main/res/layout/navigateanypoint_activity.xml +++ b/main/res/layout/navigateanypoint_activity.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".NavigateAnyPointActivity" > <ListView android:id="@+id/historyList" diff --git a/main/res/layout/navigateanypoint_header.xml b/main/res/layout/navigateanypoint_header.xml index caf2e48..05d3ed0 100644 --- a/main/res/layout/navigateanypoint_header.xml +++ b/main/res/layout/navigateanypoint_header.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:weightSum="1" > + android:weightSum="1" + tools:context=".NavigateAnyPointActivity" > <RelativeLayout style="@style/separator_horizontal_layout" > diff --git a/main/res/layout/popup.xml b/main/res/layout/popup.xml index de94d18..436c7d8 100644 --- a/main/res/layout/popup.xml +++ b/main/res/layout/popup.xml @@ -5,7 +5,7 @@ android:layout_height="fill_parent" android:background="?background_color_transparent" android:orientation="vertical" - tools:context=".CachePopup"> + tools:context=".CachePopupFragment" > <include layout="@layout/actionbar_popup" /> <ScrollView diff --git a/main/res/layout/preference_info_icon.xml b/main/res/layout/preference_info_icon.xml index 4de0a15..597e400 100644 --- a/main/res/layout/preference_info_icon.xml +++ b/main/res/layout/preference_info_icon.xml @@ -1,5 +1,7 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:layout_height="match_parent" - android:src="?attr/settings_info_icon" /> + android:layout_height="match_parent" + android:src="?attr/settings_info_icon" + tools:context=".settings.InfoPreference" /> diff --git a/main/res/layout/recaptcha_dialog.xml b/main/res/layout/recaptcha_dialog.xml index 66ad4ef..9c95813 100644 --- a/main/res/layout/recaptcha_dialog.xml +++ b/main/res/layout/recaptcha_dialog.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".connector.gc.RecaptchaHandler" > <LinearLayout android:layout_width="match_parent" @@ -27,7 +29,6 @@ android:layout_gravity="center_vertical" android:scaleType="centerInside" android:src="@drawable/ic_menu_refresh" /> - </LinearLayout> <EditText diff --git a/main/res/layout/search_activity.xml b/main/res/layout/search_activity.xml index 894c461..902c707 100644 --- a/main/res/layout/search_activity.xml +++ b/main/res/layout/search_activity.xml @@ -5,7 +5,8 @@ android:layout_height="fill_parent" android:background="?background_color" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".SearchActivity" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/simple_dir_chooser.xml b/main/res/layout/simple_dir_chooser.xml index 167b2ec..300579d 100644 --- a/main/res/layout/simple_dir_chooser.xml +++ b/main/res/layout/simple_dir_chooser.xml @@ -4,7 +4,8 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".files.SimpleDirChooser" > <LinearLayout android:id="@+id/headerLayout" diff --git a/main/res/layout/simple_dir_item.xml b/main/res/layout/simple_dir_item.xml index e268943..2abce4c 100644 --- a/main/res/layout/simple_dir_item.xml +++ b/main/res/layout/simple_dir_item.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="horizontal" > + android:orientation="horizontal" + tools:context=".files.SimpleDirChooser" > <CheckBox android:id="@+id/CheckBox" diff --git a/main/res/layout/simple_way_point.xml b/main/res/layout/simple_way_point.xml index 88f76e7..333bb9d 100644 --- a/main/res/layout/simple_way_point.xml +++ b/main/res/layout/simple_way_point.xml @@ -1,8 +1,10 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/linearLayout1" android:layout_width="wrap_content" - android:layout_height="wrap_content" > + android:layout_height="wrap_content" + tools:context=".NavigateAnyPointActivity$DestinationHistoryAdapter" > <ImageView android:id="@+id/imageView1" diff --git a/main/res/layout/star_image.xml b/main/res/layout/star_image.xml index 919fedb..809a17d 100644 --- a/main/res/layout/star_image.xml +++ b/main/res/layout/star_image.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="12dip" android:layout_height="12dip" android:layout_gravity="center" android:layout_margin="1dip" android:gravity="center" android:scaleType="fitXY" - android:src="@drawable/star_off" /> + android:src="@drawable/star_off" + tools:context=".ui.CacheDetailsCreator" /> diff --git a/main/res/layout/staticmaps_activity.xml b/main/res/layout/staticmaps_activity.xml index c8806b4..3ca336e 100644 --- a/main/res/layout/staticmaps_activity.xml +++ b/main/res/layout/staticmaps_activity.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="UTF-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".StaticMapsActivity" > <LinearLayout android:id="@+id/maps_list" diff --git a/main/res/layout/staticmaps_activity_item.xml b/main/res/layout/staticmaps_activity_item.xml index 6b76192..8600246 100644 --- a/main/res/layout/staticmaps_activity_item.xml +++ b/main/res/layout/staticmaps_activity_item.xml @@ -1,5 +1,6 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/map_image" android:layout_width="fill_parent" android:layout_height="wrap_content" @@ -7,4 +8,5 @@ android:layout_marginBottom="10dip" android:gravity="center" android:padding="3dip" - android:scaleType="fitCenter" /> + android:scaleType="fitCenter" + tools:context=".StaticMapsActivity" /> diff --git a/main/res/layout/status.xml b/main/res/layout/status.xml index ef2a57a..76f9a8d 100644 --- a/main/res/layout/status.xml +++ b/main/res/layout/status.xml @@ -1,10 +1,12 @@ <?xml version="1.0" encoding="utf-8"?> <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/status" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="@drawable/helper_bcg" - android:visibility="invisible" > + android:visibility="invisible" + tools:context=".StatusFragment" > <ImageView android:id="@+id/status_icon" diff --git a/main/res/layout/template_preference_dialog.xml b/main/res/layout/template_preference_dialog.xml index c5fc548..343cc5f 100644 --- a/main/res/layout/template_preference_dialog.xml +++ b/main/res/layout/template_preference_dialog.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" android:orientation="vertical" - android:padding="8dp" > + android:padding="8dp" + tools:context=".settings.TemplateTextPreference" > <EditText android:id="@+id/signature_dialog_text" diff --git a/main/res/layout/trackable_details_view.xml b/main/res/layout/trackable_details_view.xml index 07f4e4e..e2c162e 100644 --- a/main/res/layout/trackable_details_view.xml +++ b/main/res/layout/trackable_details_view.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <ScrollView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:orientation="vertical" - android:padding="4dip" > + android:padding="4dip" + tools:context=".TrackableActivity$DetailsViewCreator" > <LinearLayout android:layout_width="fill_parent" diff --git a/main/res/layout/trackable_image.xml b/main/res/layout/trackable_image.xml index 89955bc..8a55cb7 100644 --- a/main/res/layout/trackable_image.xml +++ b/main/res/layout/trackable_image.xml @@ -1,9 +1,11 @@ <?xml version="1.0" encoding="utf-8"?> <ImageView xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/trackable_image" android:layout_width="fill_parent" android:layout_height="wrap_content" android:layout_gravity="center_vertical" android:layout_marginBottom="3dip" android:gravity="center" - android:scaleType="center" /> + android:scaleType="center" + tools:context=".TrackableActivity$DetailsViewCreator" /> diff --git a/main/res/layout/usefulapps_activity.xml b/main/res/layout/usefulapps_activity.xml index 7abb212..8ecea23 100644 --- a/main/res/layout/usefulapps_activity.xml +++ b/main/res/layout/usefulapps_activity.xml @@ -4,7 +4,8 @@ android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".UsefulAppsActivity" > <ListView android:id="@+id/apps_list" diff --git a/main/res/layout/usefulapps_item.xml b/main/res/layout/usefulapps_item.xml index 70e7baf..8cb2511 100644 --- a/main/res/layout/usefulapps_item.xml +++ b/main/res/layout/usefulapps_item.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:id="@+id/app_layout" android:layout_width="fill_parent" android:layout_height="fill_parent" android:layout_marginBottom="10dip" android:descendantFocusability="blocksDescendants" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".UsefulAppsActivity" > <RelativeLayout style="@style/separator_horizontal_layout" > diff --git a/main/res/layout/waypoint_item.xml b/main/res/layout/waypoint_item.xml index 4148dab..7b3c33a 100644 --- a/main/res/layout/waypoint_item.xml +++ b/main/res/layout/waypoint_item.xml @@ -8,7 +8,8 @@ android:descendantFocusability="blocksDescendants" android:longClickable="true" android:orientation="vertical" - android:paddingTop="9dp" > + android:paddingTop="9dp" + tools:context=".CacheDetailActivity$WaypointsViewCreator$1" > <RelativeLayout android:layout_width="fill_parent" @@ -97,4 +98,4 @@ style="@style/separator_horizontal" android:layout_marginTop="9dp" /> -</LinearLayout>
\ No newline at end of file +</LinearLayout> diff --git a/main/res/layout/waypoint_popup.xml b/main/res/layout/waypoint_popup.xml index 287fc3a..9a566ee 100644 --- a/main/res/layout/waypoint_popup.xml +++ b/main/res/layout/waypoint_popup.xml @@ -1,11 +1,13 @@ <?xml version="1.0" encoding="UTF-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" + xmlns:tools="http://schemas.android.com/tools" android:layout_width="fill_parent" android:layout_height="fill_parent" android:background="?background_color_transparent" - android:orientation="vertical" > + android:orientation="vertical" + tools:context=".WaypointPopupFragment" > - <include layout="@layout/actionbar_popup" /> + <include layout="@layout/actionbar_popup" /> <ScrollView android:id="@+id/details_list_box" diff --git a/main/res/menu/compass_activity_options.xml b/main/res/menu/compass_activity_options.xml index 30861dd..59c0b36 100644 --- a/main/res/menu/compass_activity_options.xml +++ b/main/res/menu/compass_activity_options.xml @@ -25,6 +25,7 @@ android:id="@+id/menu_select_destination" android:icon="@drawable/ic_menu_myplaces" android:title="@string/destination_select" + android:visible="false" app:showAsAction="ifRoom|withText"> <menu /> <!-- filled dynamically --> </item> @@ -43,7 +44,7 @@ android:id="@+id/menu_compass_sensor_magnetic" android:title="@string/use_compass"> </item> - </group> + </group> </menu> </item> diff --git a/main/res/values-ca/strings.xml b/main/res/values-ca/strings.xml index e2d2c8f..5ba01e1 100644 --- a/main/res/values-ca/strings.xml +++ b/main/res/values-ca/strings.xml @@ -1171,7 +1171,6 @@ <item quantity="one">%s preferit</item> <item quantity="other">%s preferits</item> </plurals> - <string name="percent_favorite_points">% \ preferits</string> <string name="cgeo_shortcut">Drecera del c:geo</string> <string name="create_shortcut">Crea una drecera</string> <string name="send">Envia</string> diff --git a/main/res/values-cs/strings.xml b/main/res/values-cs/strings.xml index 9ab2d11..d9848b9 100644 --- a/main/res/values-cs/strings.xml +++ b/main/res/values-cs/strings.xml @@ -1146,7 +1146,6 @@ <string name="tts_one_oclock">jedna hodina</string> <string name="tts_oclock">%s hodin</string> <string name="clipboard_copy_ok">ZkopÃrováno do schránky</string> - <string name="percent_favorite_points">%\ oblÃbené</string> <string name="cgeo_shortcut">c:geo zástupce</string> <string name="create_shortcut">VytvoÅ™it zástupce</string> <string name="send">Odeslat</string> diff --git a/main/res/values-da/strings.xml b/main/res/values-da/strings.xml index e86ebce..e1c9737 100644 --- a/main/res/values-da/strings.xml +++ b/main/res/values-da/strings.xml @@ -1150,7 +1150,6 @@ <item quantity="one">%s favorit</item> <item quantity="other">%s favoritter</item> </plurals> - <string name="percent_favorite_points">% favoritter</string> <string name="cgeo_shortcut">c:geo genvej</string> <string name="create_shortcut">Opret genvej</string> <string name="send">Send</string> diff --git a/main/res/values-de/strings.xml b/main/res/values-de/strings.xml index 671a098..2eba660 100644 --- a/main/res/values-de/strings.xml +++ b/main/res/values-de/strings.xml @@ -1168,7 +1168,6 @@ <item quantity="one">%s Favorit</item> <item quantity="other">%s Favoriten</item> </plurals> - <string name="percent_favorite_points">% Favoriten</string> <string name="cgeo_shortcut">c:geo Shortcut</string> <string name="create_shortcut">Shortcut erstellen</string> <string name="send">Senden</string> diff --git a/main/res/values-es/strings.xml b/main/res/values-es/strings.xml index 17e318d..1c247f7 100644 --- a/main/res/values-es/strings.xml +++ b/main/res/values-es/strings.xml @@ -835,5 +835,4 @@ <string name="tts_one_oclock">La una en punto</string> <string name="tts_oclock">%s en punto</string> <string name="clipboard_copy_ok">Copiado al portapapeles</string> - <string name="percent_favorite_points">% \ favoritos</string> -</resources> + </resources> diff --git a/main/res/values-fr/strings.xml b/main/res/values-fr/strings.xml index 0287b48..8bbb50e 100644 --- a/main/res/values-fr/strings.xml +++ b/main/res/values-fr/strings.xml @@ -1169,7 +1169,6 @@ <item quantity="one">%s favori</item> <item quantity="other">%s favoris</item> </plurals> - <string name="percent_favorite_points">%\ favoris</string> <string name="cgeo_shortcut">c:geo raccourci</string> <string name="create_shortcut">Créer un raccourci</string> <string name="send">Envoyer</string> diff --git a/main/res/values-hu/strings.xml b/main/res/values-hu/strings.xml index 8352fb8..dbe5127 100644 --- a/main/res/values-hu/strings.xml +++ b/main/res/values-hu/strings.xml @@ -1165,7 +1165,6 @@ <item quantity="one">%s kedvenc pont</item> <item quantity="other">%s kedvenc pont</item> </plurals> - <string name="percent_favorite_points">%\ kedvenc pont</string> <string name="cgeo_shortcut">c:geo parancsikon</string> <string name="create_shortcut">Parancsikon létrehozása</string> <string name="send">Küldés</string> diff --git a/main/res/values-it/strings.xml b/main/res/values-it/strings.xml index e079b1d..a4e164b 100644 --- a/main/res/values-it/strings.xml +++ b/main/res/values-it/strings.xml @@ -1165,7 +1165,6 @@ <item quantity="one">%s preferito</item> <item quantity="other">%s preferiti</item> </plurals> - <string name="percent_favorite_points">%\ preferiti</string> <string name="cgeo_shortcut">collegamento a c:geo</string> <string name="create_shortcut">Crea collegamento</string> <string name="send">Invia</string> diff --git a/main/res/values-lt/strings.xml b/main/res/values-lt/strings.xml index d0e6f9d..1c3cd6a 100644 --- a/main/res/values-lt/strings.xml +++ b/main/res/values-lt/strings.xml @@ -1163,7 +1163,6 @@ <item quantity="few">%s mÄ—giamos</item> <item quantity="other">%s mÄ—giamų</item> </plurals> - <string name="percent_favorite_points">%\ mÄ—giamos</string> <string name="cgeo_shortcut">c:geo nuoroda</string> <string name="create_shortcut">Sukurti nuorodÄ…</string> <string name="send">Siųsti</string> diff --git a/main/res/values-nb/strings.xml b/main/res/values-nb/strings.xml index b007671..20141d1 100644 --- a/main/res/values-nb/strings.xml +++ b/main/res/values-nb/strings.xml @@ -1143,7 +1143,6 @@ <item quantity="one">%s favoritt</item> <item quantity="other">%s favoritt</item> </plurals> - <string name="percent_favorite_points">%\ favoritter</string> <string name="cgeo_shortcut">c:geo-snartvei</string> <string name="create_shortcut">Opprett snarvei</string> <string name="send">Send</string> diff --git a/main/res/values-nl/strings.xml b/main/res/values-nl/strings.xml index e949042..4e5a865 100644 --- a/main/res/values-nl/strings.xml +++ b/main/res/values-nl/strings.xml @@ -1170,7 +1170,6 @@ <item quantity="one">%s favoriet</item> <item quantity="other">%s favorieten</item> </plurals> - <string name="percent_favorite_points">%\ favorieten</string> <string name="cgeo_shortcut">c:geo snelkoppeling</string> <string name="create_shortcut">Maak snelkoppeling</string> <string name="send">Verzenden</string> diff --git a/main/res/values-pl/strings.xml b/main/res/values-pl/strings.xml index 13d1930..bedd758 100644 --- a/main/res/values-pl/strings.xml +++ b/main/res/values-pl/strings.xml @@ -1171,7 +1171,6 @@ <item quantity="few">%s ulubione</item> <item quantity="other">%s ulubionych</item> </plurals> - <string name="percent_favorite_points">%\ ulubionych</string> <string name="cgeo_shortcut">skrót do c:geo</string> <string name="create_shortcut">Utwórz skrót</string> <string name="send">WyÅ›lij</string> diff --git a/main/res/values-pt/strings.xml b/main/res/values-pt/strings.xml index 7702571..76d0e79 100644 --- a/main/res/values-pt/strings.xml +++ b/main/res/values-pt/strings.xml @@ -1155,7 +1155,6 @@ <item quantity="one">%s favorito</item> <item quantity="other">%s favoritos</item> </plurals> - <string name="percent_favorite_points">%\ favoritos</string> <string name="cgeo_shortcut">Atalho c:geo</string> <string name="create_shortcut">Criar atalho</string> <string name="send">Enviar</string> diff --git a/main/res/values-ro/strings.xml b/main/res/values-ro/strings.xml index f875043..5323b12 100644 --- a/main/res/values-ro/strings.xml +++ b/main/res/values-ro/strings.xml @@ -1132,7 +1132,6 @@ <string name="tts_one_oclock">ora unu</string> <string name="tts_oclock">ora %s</string> <string name="clipboard_copy_ok">Copiat</string> - <string name="percent_favorite_points">%\ favorite</string> <string name="cgeo_shortcut">legătură la c:geo</string> <string name="create_shortcut">Crează scurtătură</string> <string name="send">Trimite</string> diff --git a/main/res/values-sk/strings.xml b/main/res/values-sk/strings.xml index 230dcd3..a711073 100644 --- a/main/res/values-sk/strings.xml +++ b/main/res/values-sk/strings.xml @@ -1172,7 +1172,6 @@ <item quantity="few">%s obľúbené</item> <item quantity="other">%s obľúbených</item> </plurals> - <string name="percent_favorite_points">% \ obľúbených</string> <string name="cgeo_shortcut">c:Geo odkaz</string> <string name="create_shortcut">VytvoriÅ¥ odkaz</string> <string name="send">OdoslaÅ¥</string> diff --git a/main/res/values-sl/strings.xml b/main/res/values-sl/strings.xml index b6cd540..e09d6a1 100644 --- a/main/res/values-sl/strings.xml +++ b/main/res/values-sl/strings.xml @@ -1183,7 +1183,6 @@ <item quantity="few">%s favoritov</item> <item quantity="other">%s favoritov</item> </plurals> - <string name="percent_favorite_points">%\ favoritov</string> <string name="cgeo_shortcut">Bližnjica c:geo</string> <string name="create_shortcut">Ustvari bližnjico</string> <string name="send">PoÅ¡lji</string> diff --git a/main/res/values-sv/strings.xml b/main/res/values-sv/strings.xml index cebf782..58b2169 100644 --- a/main/res/values-sv/strings.xml +++ b/main/res/values-sv/strings.xml @@ -1166,7 +1166,6 @@ <item quantity="one">igÃ¥r</item> <item quantity="other">%d dagar sedan</item> </plurals> - <string name="percent_favorite_points">%\ favoriter</string> <string name="cgeo_shortcut">c:geo genväg</string> <string name="create_shortcut">Skapa genväg</string> <string name="send">Skicka</string> diff --git a/main/res/values/changelog_master.xml b/main/res/values/changelog_master.xml index 1e3c3d4..b11f2b3 100644 --- a/main/res/values/changelog_master.xml +++ b/main/res/values/changelog_master.xml @@ -2,5 +2,13 @@ <resources> <!-- changelog for the master branch --> <string name="changelog_master" translatable="false"> + <b>Next feature release:</b>\n + · New: Google Play Services used for location (less battery usage)\n + · New: Optional low power GPS mode for parts of the app which don\'t require high precision\n + · New: Confirmation dialog for dangerous log types\n + · New: Store found date of found caches when importing via GPX\n + · Fix: Improved compass rotation algorithm\n + · Fix: Smilies fit line height in logbook\n + \n </string> </resources> diff --git a/main/res/values/preference_keys.xml b/main/res/values/preference_keys.xml index e6ef67a..04375cd 100644 --- a/main/res/values/preference_keys.xml +++ b/main/res/values/preference_keys.xml @@ -106,9 +106,10 @@ <string name="pref_coordinputformat">coordinputformat</string> <string name="pref_gccustomdate">gccustomdate</string> <string name="pref_cookiestore">cookiestore</string> + <string name="pref_googleplayservices">googleplayservices</string> + <string name="pref_lowpowermode">lowpowerlocation</string> <string name="pref_lastdetailspage">lastdetailspage</string> <string name="pref_livemapstrategy">livemapstrategy</string> - <string name="pref_hidelivemaphint">hidelivemaphint</string> <string name="pref_livemaphintshowcount">livemaphintshowcount</string> <string name="pref_settingsversion">settingsversion</string> <string name="pref_trackableaction">trackableaction</string> diff --git a/main/res/values/strings.xml b/main/res/values/strings.xml index b8c77a2..f411cc4 100644 --- a/main/res/values/strings.xml +++ b/main/res/values/strings.xml @@ -228,6 +228,8 @@ <!-- location service --> <string name="loc_last">Last known</string> <string name="loc_net">Network</string> + <string name="loc_fused">Fused</string> + <string name="loc_low_power">Low-power</string> <string name="loc_gps">GPS</string> <string name="loc_sat">Sat</string> <string name="loc_trying">Trying to Locate</string> @@ -324,6 +326,7 @@ <string name="caches_filter_popularity_ratio">Favorites [%]</string> <string name="caches_removing_from_history">Removing from History…</string> <string name="caches_clear_offlinelogs">Clear offline logs</string> + <string name="caches_clear_offlinelogs_message">Do you want to clear the offline logs?</string> <string name="caches_clear_offlinelogs_progress">Clearing offline logs</string> <!-- caches lists --> @@ -527,6 +530,12 @@ <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="init_location">Geolocation</string> + <string name="init_location_note">On devices equipped with Google Play Services, c:geo can automatically use a better geolocation provider. However, this prevents the use of an external BlueTooth GPS receiver.</string> + <string name="init_location_googleplayservices">Use Google Play Services</string> + <string name="init_low_power">Low-power mode</string> + <string name="init_low_power_note">The low-power mode avoids using the GPS and the gyroscope when a highly accurate location is not strictly necessary, at the cost of longer fixing times.</string> + <string name="init_low_power_mode">Activate low-power mode</string> <string name="init_create_memory_dump">Create memory dump</string> <string name="init_memory_dump">Memory dump</string> <string name="init_memory_dumped">Memory dumped to %s</string> @@ -938,11 +947,11 @@ <string name="license_dismiss">Dismiss</string> <!-- helpers --> - <string name="helper_calendar_title">c:geo calendar add-on</string> - <string name="helper_calendar_missing">c:geo calendar add-on not installed.</string> + <string name="helper_calendar_title">c:geo calendar plugin</string> + <string name="helper_calendar_missing">c:geo calendar plugin not installed.</string> <string name="helper_calendar_description">Enables you to export event caches to the calendar on your device.</string> <string name="helper_sendtocgeo_title">Send to c:geo</string> - <string name="helper_contacts_title">c:geo contacts add-on</string> + <string name="helper_contacts_title">c:geo contacts plugin</string> <string name="helper_contacts_description">Enables you to open a contact card (of your address book) directly from a log entry, so you can more easily ask friends for help.</string> <string name="helper_sendtocgeo_description">Send to c:geo is a browser extension <strong>for your PC</strong>. When browsing geocaching.com, you can send caches to your smartphone with the click of a button directly inside the browser.</string> <string name="helper_locus_title">Locus</string> @@ -959,7 +968,7 @@ <string name="helper_google_translate_description">If you download translation packages in the Google Translate app, then you can easily translate cache descriptions in c:geo by a long tap on the cache description text (without an Internet connection).</string> <!-- add-ons --> - <string name="addon_missing_title">Missing Add-On</string> + <string name="addon_missing_title">Missing plugin</string> <string name="addon_download_prompt">Get it now from Google Play.</string> <!-- export --> @@ -1266,7 +1275,7 @@ <item quantity="other">%s favorites</item> </plurals> - <string name="percent_favorite_points">%\ favorites</string> + <string name="more_than_percent_favorite_points">> %d%% favorites</string> <!-- shortcuts --> <string name="cgeo_shortcut">c:geo shortcut</string> @@ -1279,4 +1288,6 @@ <string name="showcase_main_text">c:geo now places menu items in the title bar like other modern apps. Some items are hidden behind the dotted symbol. Long press a button to see its description.</string> <string name="showcase_cachelist_title">Switching lists</string> <string name="showcase_cachelist_text">You can switch between your geocache lists by clicking the title of the list.</string> + <string name="confirm_log_title">Unusual log type</string> + <string name="confirm_log_message">You want to log \'%s\'. Are you sure?</string> </resources> diff --git a/main/res/xml/preferences.xml b/main/res/xml/preferences.xml index 1ed739d..78d79bd 100644 --- a/main/res/xml/preferences.xml +++ b/main/res/xml/preferences.xml @@ -745,6 +745,25 @@ android:key="@string/pref_fakekey_preference_maintenance_directories" android:title="@string/init_maintenance_directories" /> </PreferenceCategory> + <PreferenceCategory android:title="@string/init_location"> + <cgeo.geocaching.settings.TextPreference + android:layout="@layout/text_preference" + android:text="@string/init_location_note" /> + + <CheckBoxPreference + android:defaultValue="true" + android:key="@string/pref_googleplayservices" + android:title="@string/init_location_googleplayservices" /> + </PreferenceCategory> + <PreferenceCategory android:title="@string/init_low_power"> + <cgeo.geocaching.settings.TextPreference + android:layout="@layout/text_preference" + android:text="@string/init_low_power_note" /> + <CheckBoxPreference + android:defaultValue="false" + android:key="@string/pref_lowpowermode" + android:title="@string/init_low_power_mode" /> + </PreferenceCategory> <PreferenceCategory android:title="@string/init_hardware_acceleration_title"> <cgeo.geocaching.settings.TextPreference android:layout="@layout/text_preference" diff --git a/main/src/cgeo/geocaching/AbstractDialogFragment.java b/main/src/cgeo/geocaching/AbstractDialogFragment.java index 4025347..6f64146 100644 --- a/main/src/cgeo/geocaching/AbstractDialogFragment.java +++ b/main/src/cgeo/geocaching/AbstractDialogFragment.java @@ -42,7 +42,6 @@ import android.widget.ImageView; import android.widget.TextView; public abstract class AbstractDialogFragment extends DialogFragment implements CacheMenuHandler.ActivityInterface, PopupMenu.OnMenuItemClickListener, MenuItem.OnMenuItemClickListener { - protected CgeoApplication app = null; protected Resources res = null; protected String geocode; protected CacheDetailsCreator details; @@ -61,7 +60,6 @@ public abstract class AbstractDialogFragment extends DialogFragment implements C public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); res = getResources(); - app = (CgeoApplication) getActivity().getApplication(); setHasOptionsMenu(true); } diff --git a/main/src/cgeo/geocaching/AbstractLoggingActivity.java b/main/src/cgeo/geocaching/AbstractLoggingActivity.java index 8448e45..15e8848 100644 --- a/main/src/cgeo/geocaching/AbstractLoggingActivity.java +++ b/main/src/cgeo/geocaching/AbstractLoggingActivity.java @@ -2,6 +2,7 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.activity.ActivityMixin; +import cgeo.geocaching.activity.Keyboard; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCSmiliesProvider; @@ -77,4 +78,9 @@ public abstract class AbstractLoggingActivity extends AbstractActionBarActivity final EditText log = (EditText) findViewById(R.id.log); ActivityMixin.insertAtPosition(log, newText, moveCursor); } + + protected void requestKeyboardForLogging() { + new Keyboard(this).show(findViewById(R.id.log)); + } + } diff --git a/main/src/cgeo/geocaching/CacheDetailActivity.java b/main/src/cgeo/geocaching/CacheDetailActivity.java index d3ab633..a59d9b9 100644 --- a/main/src/cgeo/geocaching/CacheDetailActivity.java +++ b/main/src/cgeo/geocaching/CacheDetailActivity.java @@ -66,10 +66,12 @@ import org.eclipse.jdt.annotation.Nullable; import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; +import rx.Subscription; import rx.android.observables.AndroidObservable; import rx.functions.Action0; import rx.functions.Action1; import rx.subscriptions.CompositeSubscription; +import rx.subscriptions.Subscriptions; import android.R.color; import android.app.AlertDialog; @@ -171,6 +173,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc */ private Waypoint selectedWaypoint; + private boolean requireGeodata; + private Subscription geoDataSubscription = Subscriptions.empty(); + @Override public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.cachedetail_activity); @@ -235,7 +240,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc finish(); return; } - } else if (uriHost.contains("opencaching.de")) { + } else if (uriHost.contains("opencaching.de") || uriHost.contains("opencaching.fr")) { if (StringUtils.startsWith(uriPath, "/oc")) { geocode = uriPath.substring(1).toUpperCase(Locale.US); } else { @@ -293,8 +298,11 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (getPage(position) == Page.IMAGES) { loadCacheImages(); } + requireGeodata = getPage(position) == Page.DETAILS; + startOrStopGeoDataListener(); } }); + requireGeodata = pageToOpen == 1; final String realGeocode = geocode; final String realGuid = guid; @@ -323,9 +331,17 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc outState.putInt(STATE_PAGE_INDEX, getCurrentItem()); } + private void startOrStopGeoDataListener() { + geoDataSubscription.unsubscribe(); + if (requireGeodata) { + geoDataSubscription = locationUpdater.start(GeoDirHandler.UPDATE_GEODATA); + } + } + @Override public void onResume() { - super.onResume(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA)); + super.onResume(); + startOrStopGeoDataListener(); if (refreshOnResume) { notifyDataSetChanged(); @@ -334,6 +350,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc } @Override + public void onPause() { + geoDataSubscription.unsubscribe(); + super.onPause(); + } + + @Override public void onDestroy() { createSubscriptions.unsubscribe(); super.onDestroy(); @@ -910,9 +932,9 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc public void call(final Integer selectedListId) { storeCache(selectedListId, new StoreCacheHandler(CacheDetailActivity.this, progress)); } - }, true, StoredList.TEMPORARY_LIST_ID); + }, true, StoredList.TEMPORARY_LIST.id); } else { - storeCache(StoredList.TEMPORARY_LIST_ID, new StoreCacheHandler(this, progress)); + storeCache(StoredList.TEMPORARY_LIST.id, new StoreCacheHandler(this, progress)); } } @@ -2223,7 +2245,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg(R.string.cache_dialog_offline_save_message, (String) msg.obj); } else { - notifyDatasetChanged(activityRef); + notifyDataSetChanged(activityRef); } } } @@ -2239,7 +2261,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (UPDATE_LOAD_PROGRESS_DETAIL == msg.what && msg.obj instanceof String) { updateStatusMsg(R.string.cache_dialog_refresh_message, (String) msg.obj); } else { - notifyDatasetChanged(activityRef); + notifyDataSetChanged(activityRef); } } } @@ -2252,7 +2274,7 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc @Override public void handleMessage(final Message msg) { - notifyDatasetChanged(activityRef); + notifyDataSetChanged(activityRef); } } @@ -2267,12 +2289,12 @@ public class CacheDetailActivity extends AbstractViewPagerActivity<CacheDetailAc if (msg.what == MESSAGE_FAILED) { super.handleMessage(msg); } else { - notifyDatasetChanged(activityRef); + notifyDataSetChanged(activityRef); } } } - private static void notifyDatasetChanged(final WeakReference<AbstractActivity> activityRef) { + private static void notifyDataSetChanged(final WeakReference<AbstractActivity> activityRef) { final CacheDetailActivity activity = ((CacheDetailActivity) activityRef.get()); if (activity != null) { activity.notifyDataSetChanged(); diff --git a/main/src/cgeo/geocaching/CacheListActivity.java b/main/src/cgeo/geocaching/CacheListActivity.java index 522004e..7b887aa 100644 --- a/main/src/cgeo/geocaching/CacheListActivity.java +++ b/main/src/cgeo/geocaching/CacheListActivity.java @@ -41,7 +41,6 @@ import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.network.Cookies; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; -import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; @@ -52,6 +51,7 @@ import cgeo.geocaching.ui.CacheListAdapter; import cgeo.geocaching.ui.LoggingUI; import cgeo.geocaching.ui.WeakReferenceHandler; import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.AsyncTaskWithProgress; import cgeo.geocaching.utils.CancellableHandler; import cgeo.geocaching.utils.DateUtils; @@ -134,13 +134,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA private int detailTotal = 0; private int detailProgress = 0; private long detailProgressTime = 0L; - private int listId = StoredList.TEMPORARY_LIST_ID; // Only meaningful for the OFFLINE type + private int listId = StoredList.TEMPORARY_LIST.id; // Only meaningful for the OFFLINE type private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateDirection(final float direction) { if (Settings.isLiveList()) { - adapter.setActualHeading(DirectionProvider.getDirectionNow(direction)); + adapter.setActualHeading(AngleUtils.getDirectionNow(direction)); } } @@ -392,8 +392,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA // get parameters Bundle extras = getIntent().getExtras(); if (extras != null) { - final Object typeObject = extras.get(Intents.EXTRA_LIST_TYPE); - type = (typeObject instanceof CacheListType) ? (CacheListType) typeObject : CacheListType.OFFLINE; + type = Intents.getListType(getIntent()); coords = extras.getParcelable(Intents.EXTRA_COORDS); } else { @@ -405,6 +404,9 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA coords = Geopoint.ZERO; } } + if (type == CacheListType.NEAREST) { + coords = CgeoApplication.getInstance().currentGeo().getCoords(); + } setTitle(title); @@ -513,7 +515,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public void onResume() { super.onResume(); - resumeSubscription = geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.UPDATE_DIRECTION); + resumeSubscription = geoDirHandler.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.UPDATE_DIRECTION | GeoDirHandler.LOW_POWER); adapter.setSelectMode(false); setAdapterCurrentCoordinates(true); @@ -769,7 +771,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return new SearchResult(geocodes); } - public void deletePastEvents() { + private void deletePastEvents() { final List<Geocache> deletion = new ArrayList<>(); for (final Geocache cache : adapter.getCheckedOrAllCaches()) { if (DateUtils.isPastEvent(cache)) { @@ -779,9 +781,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA new DropDetailsTask().execute(deletion.toArray(new Geocache[deletion.size()])); } - public void clearOfflineLogs() { - progress.show(this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage()); - new ClearOfflineLogsThread(clearOfflineLogsHandler).start(); + private void clearOfflineLogs() { + Dialogs.confirmYesNo(this, R.string.caches_clear_offlinelogs, R.string.caches_clear_offlinelogs_message, new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + progress.show(CacheListActivity.this, null, res.getString(R.string.caches_clear_offlinelogs_progress), true, clearOfflineLogsHandler.cancelMessage()); + new ClearOfflineLogsThread(clearOfflineLogsHandler).start(); + } + }); } /** @@ -1079,11 +1087,11 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } refreshStoredInternal(caches); } - }, true, StoredList.TEMPORARY_LIST_ID, newListName); + }, true, StoredList.TEMPORARY_LIST.id, newListName); } else { if (type != CacheListType.OFFLINE) { for (final Geocache geocache : caches) { - if (geocache.getListId() == StoredList.TEMPORARY_LIST_ID) { + if (geocache.getListId() == StoredList.TEMPORARY_LIST.id) { geocache.setListId(StoredList.STANDARD_LIST_ID); } } @@ -1126,7 +1134,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA }); } - public void removeFromHistory() { + private void removeFromHistory() { final List<Geocache> caches = adapter.getCheckedOrAllCaches(); final String[] geocodes = new String[caches.size()]; for (int i = 0; i < geocodes.length; i++) { @@ -1137,7 +1145,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA getSupportLoaderManager().initLoader(CacheListLoaderType.REMOVE_FROM_HISTORY.getLoaderId(), b, this); } - public void importWeb() { + private void importWeb() { // menu is also shown with no device connected if (!Settings.isRegisteredForSend2cgeo()) { Dialogs.confirm(this, R.string.web_import_title, R.string.init_sendToCgeo_description, new OnClickListener() { @@ -1159,7 +1167,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA threadWeb.start(); } - public void dropStored() { + private void dropStored() { final int titleId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected : R.string.caches_remove_all; final int messageId = (adapter.getCheckedCount() > 0) ? R.string.caches_remove_selected_confirm : R.string.caches_remove_all_confirm; final String message = getString(messageId, adapter.getCheckedOrAllCount()); @@ -1355,13 +1363,13 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA }; } - public void switchListById(final int id) { + private void switchListById(final int id) { if (id < 0) { return; } if (id == PseudoList.HISTORY_LIST.id) { - CacheListActivity.startActivityHistory(this); + startActivity(CacheListActivity.getHistoryIntent(this)); finish(); return; } @@ -1452,7 +1460,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA public static void startActivityOffline(final Context context) { final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OFFLINE); + Intents.putListType(cachesIntent, CacheListType.OFFLINE); context.startActivity(cachesIntent); } @@ -1461,7 +1469,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.OWNER); + Intents.putListType(cachesIntent, CacheListType.OWNER); cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName); context.startActivity(cachesIntent); } @@ -1479,7 +1487,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.FINDER); + Intents.putListType(cachesIntent, CacheListType.FINDER); cachesIntent.putExtra(Intents.EXTRA_USERNAME, userName); context.startActivity(cachesIntent); } @@ -1500,25 +1508,17 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } } - public static void startActivityNearest(final AbstractActivity context, final Geopoint coordsNow) { - if (!isValidCoords(context, coordsNow)) { - return; - } - final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.NEAREST); - cachesIntent.putExtra(Intents.EXTRA_COORDS, coordsNow); - context.startActivity(cachesIntent); + public static Intent getNearestIntent(final Activity context) { + return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.NEAREST); } - public static void startActivityHistory(final Context context) { - final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.HISTORY); - context.startActivity(cachesIntent); + public static Intent getHistoryIntent(final Context context) { + return Intents.putListType(new Intent(context, CacheListActivity.class), CacheListType.HISTORY); } public static void startActivityAddress(final Context context, final Geopoint coords, final String address) { final Intent addressIntent = new Intent(context, CacheListActivity.class); - addressIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.ADDRESS); + Intents.putListType(addressIntent, CacheListType.ADDRESS); addressIntent.putExtra(Intents.EXTRA_COORDS, coords); addressIntent.putExtra(Intents.EXTRA_ADDRESS, address); context.startActivity(addressIntent); @@ -1529,7 +1529,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.COORDINATE); + Intents.putListType(cachesIntent, CacheListType.COORDINATE); cachesIntent.putExtra(Intents.EXTRA_COORDS, coords); context.startActivity(cachesIntent); } @@ -1548,15 +1548,15 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.KEYWORD); + Intents.putListType(cachesIntent, CacheListType.KEYWORD); cachesIntent.putExtra(Intents.EXTRA_KEYWORD, keyword); context.startActivity(cachesIntent); } public static void startActivityMap(final Context context, final SearchResult search) { final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.MAP); cachesIntent.putExtra(Intents.EXTRA_SEARCH, search); + Intents.putListType(cachesIntent, CacheListType.MAP); context.startActivity(cachesIntent); } @@ -1567,7 +1567,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA return; } final Intent cachesIntent = new Intent(context, CacheListActivity.class); - cachesIntent.putExtra(Intents.EXTRA_LIST_TYPE, CacheListType.POCKET); + Intents.putListType(cachesIntent, CacheListType.POCKET); cachesIntent.putExtra(Intents.EXTRA_NAME, pq.getName()); cachesIntent.putExtra(Intents.EXTRA_POCKET_GUID, guid); context.startActivity(cachesIntent); @@ -1592,7 +1592,7 @@ public class CacheListActivity extends AbstractListActivity implements FilteredA } if (listId == PseudoList.ALL_LIST.id) { title = res.getString(R.string.list_all_lists); - } else if (listId <= StoredList.TEMPORARY_LIST_ID) { + } else if (listId <= StoredList.TEMPORARY_LIST.id) { listId = StoredList.STANDARD_LIST_ID; title = res.getString(R.string.stored_caches_button); } else { diff --git a/main/src/cgeo/geocaching/CachePopupFragment.java b/main/src/cgeo/geocaching/CachePopupFragment.java index b2af12c..995fea2 100644 --- a/main/src/cgeo/geocaching/CachePopupFragment.java +++ b/main/src/cgeo/geocaching/CachePopupFragment.java @@ -17,8 +17,6 @@ import rx.functions.Action0; import rx.functions.Action1; import rx.schedulers.Schedulers; -import android.content.Context; -import android.content.Intent; import android.content.res.Configuration; import android.os.Bundle; import android.os.Handler; @@ -135,9 +133,9 @@ public class CachePopupFragment extends AbstractDialogFragment { public void call(final Integer selectedListId) { storeCache(selectedListId); } - }, true, StoredList.TEMPORARY_LIST_ID); + }, true, StoredList.TEMPORARY_LIST.id); } else { - storeCache(StoredList.TEMPORARY_LIST_ID); + storeCache(StoredList.TEMPORARY_LIST.id); } } @@ -212,12 +210,6 @@ public class CachePopupFragment extends AbstractDialogFragment { getActivity().finish(); } - public static void startActivity(final Context context, final String geocode) { - final Intent popupIntent = new Intent(context, CachePopup.class); - popupIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); - context.startActivity(popupIntent); - } - @Override protected Geopoint getCoordinates() { if (cache == null) { diff --git a/main/src/cgeo/geocaching/CgeoApplication.java b/main/src/cgeo/geocaching/CgeoApplication.java index a81319d..34dab09 100644 --- a/main/src/cgeo/geocaching/CgeoApplication.java +++ b/main/src/cgeo/geocaching/CgeoApplication.java @@ -1,15 +1,26 @@ package cgeo.geocaching; -import cgeo.geocaching.sensors.DirectionProvider; +import cgeo.geocaching.playservices.LocationProvider; +import cgeo.geocaching.sensors.GeoData; import cgeo.geocaching.sensors.GeoDataProvider; +import cgeo.geocaching.sensors.GpsStatusProvider; +import cgeo.geocaching.sensors.GpsStatusProvider.Status; import cgeo.geocaching.sensors.IGeoData; +import cgeo.geocaching.sensors.OrientationProvider; +import cgeo.geocaching.sensors.RotationProvider; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.OOMDumpingUncaughtExceptionHandler; +import cgeo.geocaching.utils.RxUtils; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesUtil; + +import org.eclipse.jdt.annotation.NonNull; import rx.Observable; import rx.functions.Action1; -import rx.observables.ConnectableObservable; +import rx.functions.Func1; import android.app.Application; import android.view.ViewConfiguration; @@ -23,9 +34,20 @@ public class CgeoApplication extends Application { private boolean liveMapHintShownInThisSession = false; // livemap hint has been shown private static CgeoApplication instance; private Observable<IGeoData> geoDataObservable; + private Observable<IGeoData> geoDataObservableLowPower; private Observable<Float> directionObservable; - private volatile IGeoData currentGeo = null; + private Observable<Status> gpsStatusObservable; + @NonNull private volatile IGeoData currentGeo = GeoData.DUMMY_LOCATION; + private volatile boolean hasValidLocation = false; private volatile float currentDirection = 0.0f; + private boolean isGooglePlayServicesAvailable = false; + private final Action1<IGeoData> rememberGeodataAction = new Action1<IGeoData>() { + @Override + public void call(final IGeoData geoData) { + currentGeo = geoData; + hasValidLocation = true; + } + }; public static void dumpOnOutOfMemory(final boolean enable) { @@ -60,12 +82,7 @@ public class CgeoApplication extends Application { final Field menuKeyField = ViewConfiguration.class.getDeclaredField("sHasPermanentMenuKey"); menuKeyField.setAccessible(true); menuKeyField.setBoolean(config, false); - } catch (final IllegalArgumentException e) { - // ignore - } catch (final NoSuchFieldException e) { - // ignore - } catch (final IllegalAccessException e) { - // ignore + } catch (IllegalArgumentException | IllegalAccessException | NoSuchFieldException ignore) { } // Set language to English if the user decided so. @@ -73,6 +90,52 @@ public class CgeoApplication extends Application { // ensure initialization of lists DataStore.getLists(); + + // Check if Google Play services is available + if (GooglePlayServicesUtil.isGooglePlayServicesAvailable(this) == ConnectionResult.SUCCESS) { + isGooglePlayServicesAvailable = true; + } + Log.i("Google Play services are " + (isGooglePlayServicesAvailable ? "" : "not ") + "available"); + setupGeoDataObservables(Settings.useGooglePlayServices(), Settings.useLowPowerMode()); + setupDirectionObservable(Settings.useLowPowerMode()); + gpsStatusObservable = GpsStatusProvider.create(this).replay(1).refCount(); + + // Attempt to acquire an initial location before any real activity happens. + geoDataObservableLowPower.subscribeOn(RxUtils.looperCallbacksScheduler).first().subscribe(rememberGeodataAction); + } + + public void setupGeoDataObservables(final boolean useGooglePlayServices, final boolean useLowPowerLocation) { + if (useGooglePlayServices) { + geoDataObservable = LocationProvider.getMostPrecise(this).doOnNext(rememberGeodataAction); + if (useLowPowerLocation) { + geoDataObservableLowPower = LocationProvider.getLowPower(this, true).doOnNext(rememberGeodataAction); + } else { + geoDataObservableLowPower = geoDataObservable; + } + } else { + geoDataObservable = GeoDataProvider.create(this).replay(1).refCount().doOnNext(rememberGeodataAction); + geoDataObservableLowPower = geoDataObservable; + } + } + + public void setupDirectionObservable(final boolean useLowPower) { + directionObservable = RotationProvider.create(this, useLowPower).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() { + @Override + public Observable<? extends Float> call(final Throwable throwable) { + return OrientationProvider.create(CgeoApplication.this); + } + }).onErrorResumeNext(new Func1<Throwable, Observable<? extends Float>>() { + @Override + public Observable<? extends Float> call(final Throwable throwable) { + Log.e("Device orientation will not be available as no suitable sensors were found"); + return Observable.<Float>never().startWith(0.0f); + } + }).replay(1).refCount().doOnNext(new Action1<Float>() { + @Override + public void call(final Float direction) { + currentDirection = direction; + } + }); } @Override @@ -81,40 +144,32 @@ public class CgeoApplication extends Application { DataStore.removeAllFromCache(); } - public synchronized Observable<IGeoData> geoDataObservable() { - if (geoDataObservable == null) { - final ConnectableObservable<IGeoData> onDemand = GeoDataProvider.create(this).replay(1); - onDemand.subscribe(new Action1<IGeoData>() { - @Override - public void call(final IGeoData geoData) { - currentGeo = geoData; - } - }); - geoDataObservable = onDemand.refCount(); - } - return geoDataObservable; - } - - public synchronized Observable<Float> directionObservable() { - if (directionObservable == null) { - final ConnectableObservable<Float> onDemand = DirectionProvider.create(this).replay(1); - onDemand.subscribe(new Action1<Float>() { - @Override - public void call(final Float direction) { - currentDirection = direction; - } - }); - directionObservable = onDemand.refCount(); - } + public Observable<IGeoData> geoDataObservable(final boolean lowPower) { + return lowPower ? geoDataObservableLowPower : geoDataObservable; + } + + public Observable<Float> directionObservable() { return directionObservable; } + public Observable<Status> gpsStatusObservable() { + if (gpsStatusObservable == null) { + gpsStatusObservable = GpsStatusProvider.create(this).share(); + } + return gpsStatusObservable; + } + + @NonNull public IGeoData currentGeo() { - return currentGeo != null ? currentGeo : geoDataObservable().toBlocking().first(); + return currentGeo; + } + + public boolean hasValidLocation() { + return hasValidLocation; } public Float distanceNonBlocking(final ICoordinates target) { - if (currentGeo == null || target.getCoords() == null) { + if (target.getCoords() == null) { return null; } return currentGeo.getCoords().distanceTo(target); @@ -150,4 +205,8 @@ public class CgeoApplication extends Application { forceRelog = true; } + public boolean isGooglePlayServicesAvailable() { + return isGooglePlayServicesAvailable; + } + } diff --git a/main/src/cgeo/geocaching/CompassActivity.java b/main/src/cgeo/geocaching/CompassActivity.java index 025d938..00fa790 100644 --- a/main/src/cgeo/geocaching/CompassActivity.java +++ b/main/src/cgeo/geocaching/CompassActivity.java @@ -8,19 +8,23 @@ import cgeo.geocaching.enumerations.LoadFlags; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.Units; import cgeo.geocaching.maps.CGeoMap; -import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDirHandler; +import cgeo.geocaching.sensors.GpsStatusProvider.Status; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.speech.SpeechService; import cgeo.geocaching.ui.CompassView; import cgeo.geocaching.ui.LoggingUI; +import cgeo.geocaching.utils.AngleUtils; import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.Log; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action1; + import android.content.Context; import android.content.Intent; import android.content.res.Configuration; @@ -34,14 +38,10 @@ import android.view.SubMenu; import android.view.View; import android.widget.TextView; -import java.util.ArrayList; -import java.util.Collection; import java.util.List; public class CompassActivity extends AbstractActionBarActivity { - private static final int COORDINATES_OFFSET = 10; - @InjectView(R.id.nav_type) protected TextView navType; @InjectView(R.id.nav_accuracy) protected TextView navAccuracy; @InjectView(R.id.nav_satellites) protected TextView navSatellites; @@ -56,7 +56,6 @@ public class CompassActivity extends AbstractActionBarActivity { private static final String EXTRAS_NAME = "name"; private static final String EXTRAS_GEOCODE = "geocode"; private static final String EXTRAS_CACHE_INFO = "cacheinfo"; - private static final List<IWaypoint> coordinates = new ArrayList<>(); /** * Destination of the compass, or null (if the compass is used for a waypoint only). @@ -118,7 +117,9 @@ public class CompassActivity extends AbstractActionBarActivity { @Override public void onResume() { - super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR)); + super.onResume(geoDirHandler.start(GeoDirHandler.UPDATE_GEODIR), + app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(gpsStatusHandler)); + forceRefresh(); } @Override @@ -138,34 +139,42 @@ public class CompassActivity extends AbstractActionBarActivity { setTitle(); setDestCoords(); setCacheInfo(); + forceRefresh(); + } + private void forceRefresh() { // Force a refresh of location and direction when data is available. final CgeoApplication app = CgeoApplication.getInstance(); final IGeoData geo = app.currentGeo(); - if (geo != null) { - geoDirHandler.updateGeoDir(geo, app.currentDirection()); - } + geoDirHandler.updateGeoDir(geo, app.currentDirection()); } @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.compass_activity_options, menu); menu.findItem(R.id.menu_compass_sensor).setVisible(hasMagneticFieldSensor); - final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu(); - if (coordinates.size() > 1) { - for (int i = 0; i < coordinates.size(); i++) { - final IWaypoint coordinate = coordinates.get(i); - subMenu.add(0, COORDINATES_OFFSET + i, 0, coordinate.getName() + " (" + coordinate.getCoordType() + ")"); - } - } else { - menu.findItem(R.id.menu_select_destination).setVisible(false); - } if (cache != null) { LoggingUI.addMenuItems(this, menu, cache); } + addWaypointItems(menu); return true; } + private void addWaypointItems(final Menu menu) { + if (cache != null) { + final List<Waypoint> waypoints = cache.getWaypoints(); + boolean visible = false; + final SubMenu subMenu = menu.findItem(R.id.menu_select_destination).getSubMenu(); + for (final Waypoint waypoint : waypoints) { + if (waypoint.getCoords() != null) { + subMenu.add(0, waypoint.getId(), 0, waypoint.getName()); + visible = true; + } + } + menu.findItem(R.id.menu_select_destination).setVisible(visible); + } + } + @Override public boolean onPrepareOptionsMenu(final Menu menu) { super.onPrepareOptionsMenu(menu); @@ -207,18 +216,19 @@ public class CompassActivity extends AbstractActionBarActivity { if (LoggingUI.onMenuItemSelected(item, this, cache)) { return true; } - final int coordinatesIndex = id - COORDINATES_OFFSET; - if (coordinatesIndex >= 0 && coordinatesIndex < coordinates.size()) { - final IWaypoint coordinate = coordinates.get(coordinatesIndex); - title = coordinate.getName(); - dstCoords = coordinate.getCoords(); - setTitle(); - setDestCoords(); - setCacheInfo(); - updateDistanceInfo(app.currentGeo()); - - Log.d("destination set: " + title + " (" + dstCoords + ")"); - return true; + if (cache != null) { + final Waypoint waypoint = cache.getWaypointById(id); + if (waypoint != null) { + title = waypoint.getName(); + dstCoords = waypoint.getCoords(); + setTitle(); + setDestCoords(); + setCacheInfo(); + updateDistanceInfo(app.currentGeo()); + + Log.d("destination set: " + title + " (" + dstCoords + ")"); + return true; + } } } return super.onOptionsItemSelected(item); @@ -259,16 +269,22 @@ public class CompassActivity extends AbstractActionBarActivity { headingView.setText(Math.round(cacheHeading) + "°"); } + private final Action1<Status> gpsStatusHandler = new Action1<Status>() { + @Override + public void call(final Status gpsStatus) { + if (gpsStatus.satellitesVisible >= 0) { + navSatellites.setText(res.getString(R.string.loc_sat) + ": " + gpsStatus.satellitesFixed + "/" + gpsStatus.satellitesVisible); + } else { + navSatellites.setText(""); + } + } + }; + private final GeoDirHandler geoDirHandler = new GeoDirHandler() { @Override public void updateGeoDir(final IGeoData geo, final float dir) { try { if (geo.getCoords() != null) { - if (geo.getSatellitesVisible() >= 0) { - navSatellites.setText(res.getString(R.string.loc_sat) + ": " + geo.getSatellitesFixed() + "/" + geo.getSatellitesVisible()); - } else { - navSatellites.setText(""); - } navType.setText(res.getString(geo.getLocationProvider().resourceId)); if (geo.getAccuracy() >= 0) { @@ -286,7 +302,7 @@ public class CompassActivity extends AbstractActionBarActivity { navLocation.setText(res.getString(R.string.loc_trying)); } - updateNorthHeading(DirectionProvider.getDirectionNow(dir)); + updateNorthHeading(AngleUtils.getDirectionNow(dir)); } catch (final RuntimeException e) { Log.w("Failed to LocationUpdater location."); } @@ -299,17 +315,8 @@ public class CompassActivity extends AbstractActionBarActivity { } } - public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType, + public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final String info) { - coordinates.clear(); - if (coordinatesWithType != null) { - for (final IWaypoint coordinate : coordinatesWithType) { - if (coordinate != null) { - coordinates.add(coordinate); - } - } - } - final Intent navigateIntent = new Intent(context, CompassActivity.class); navigateIntent.putExtra(EXTRAS_COORDS, coords); navigateIntent.putExtra(EXTRAS_GEOCODE, geocode); @@ -320,12 +327,12 @@ public class CompassActivity extends AbstractActionBarActivity { context.startActivity(navigateIntent); } - public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords, final Collection<IWaypoint> coordinatesWithType) { - CompassActivity.startActivity(context, geocode, displayedName, coords, coordinatesWithType, null); + public static void startActivity(final Context context, final String geocode, final String displayedName, final Geopoint coords) { + startActivity(context, geocode, displayedName, coords, null); } - public static void startActivity(final Context context, final Geocache cache) { - startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(), null, + public static void startActivityCache(final Context context, final Geocache cache) { + startActivity(context, cache.getGeocode(), cache.getName(), cache.getCoords(), Formatter.formatCacheInfoShort(cache)); } diff --git a/main/src/cgeo/geocaching/CreateShortcutActivity.java b/main/src/cgeo/geocaching/CreateShortcutActivity.java index ffcf81b..cf6d486 100644 --- a/main/src/cgeo/geocaching/CreateShortcutActivity.java +++ b/main/src/cgeo/geocaching/CreateShortcutActivity.java @@ -1,19 +1,54 @@ package cgeo.geocaching; import cgeo.geocaching.activity.AbstractActionBarActivity; -import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.maps.MapActivity; +import cgeo.geocaching.ui.dialog.Dialogs; +import cgeo.geocaching.ui.dialog.Dialogs.ItemWithIcon; +import cgeo.geocaching.utils.ImageUtils; import rx.functions.Action1; import android.content.Intent; import android.content.Intent.ShortcutIconResource; +import android.graphics.Bitmap; +import android.graphics.drawable.Drawable; +import android.graphics.drawable.LayerDrawable; import android.os.Bundle; +import java.util.ArrayList; +import java.util.List; + public class CreateShortcutActivity extends AbstractActionBarActivity { + private static class Shortcut implements ItemWithIcon { + + private final int titleResourceId; + private final int drawableResourceId; + private final Intent intent; + + /** + * shortcut with a separate icon + */ + public Shortcut(final int titleResourceId, final int drawableResourceId, final Intent intent) { + this.titleResourceId = titleResourceId; + this.drawableResourceId = drawableResourceId; + this.intent = intent; + } + + @Override + public int getIcon() { + return drawableResourceId; + } + + @Override + public String toString() { + return CgeoApplication.getInstance().getString(titleResourceId); + } + } + @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); // init @@ -23,35 +58,82 @@ public class CreateShortcutActivity extends AbstractActionBarActivity { } private void promptForShortcut() { + final List<Shortcut> shortcuts = new ArrayList<>(); + + shortcuts.add(new Shortcut(R.string.live_map_button, R.drawable.main_live, new Intent(this, MapActivity.class))); + shortcuts.add(new Shortcut(R.string.caches_nearby_button, R.drawable.main_nearby, CacheListActivity.getNearestIntent(this))); + + // TODO: make logging activities ask for cache/trackable when being invoked externally + // shortcuts.add(new Shortcut(R.string.cache_menu_visit, new Intent(this, LogCacheActivity.class))); + // shortcuts.add(new Shortcut(R.string.trackable_log_touch, new Intent(this, LogTrackableActivity.class))); + + final Shortcut offlineShortcut = new Shortcut(R.string.stored_caches_button, R.drawable.main_stored, null); + shortcuts.add(offlineShortcut); + shortcuts.add(new Shortcut(R.string.advanced_search_button, R.drawable.main_search, new Intent(this, SearchActivity.class))); + shortcuts.add(new Shortcut(R.string.any_button, R.drawable.main_any, new Intent(this, NavigateAnyPointActivity.class))); + shortcuts.add(new Shortcut(R.string.menu_history, R.drawable.main_stored, CacheListActivity.getHistoryIntent(this))); + + Dialogs.select(this, getString(R.string.create_shortcut), shortcuts, new Action1<Shortcut>() { + + @Override + public void call(final Shortcut shortcut) { + if (shortcut == offlineShortcut) { + promptForListShortcut(); + } + else { + createShortcutAndFinish(shortcut.toString(), shortcut.intent, shortcut.drawableResourceId); + } + } + }); + } + + protected void promptForListShortcut() { new StoredList.UserInterface(this).promptForListSelection(R.string.create_shortcut, new Action1<Integer>() { @Override public void call(final Integer listId) { - final Intent shortcut = createShortcut(listId); - setResult(RESULT_OK, shortcut); - - // finish activity to return the shortcut - finish(); + createOfflineListShortcut(listId.intValue()); } - }, false, PseudoList.HISTORY_LIST.id); + }, true, -1); } - protected Intent createShortcut(int listId) { + protected void createOfflineListShortcut(final int listId) { final StoredList list = DataStore.getList(listId); if (list == null) { - return null; + return; } // target to be executed by the shortcut final Intent targetIntent = new Intent(this, CacheListActivity.class); targetIntent.putExtra(Intents.EXTRA_LIST_ID, list.id); - final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, R.drawable.cgeo); // shortcut to be returned + createShortcutAndFinish(list.title, targetIntent, R.drawable.main_stored); + } + + private void createShortcutAndFinish(final String title, final Intent targetIntent, final int iconResourceId) { final Intent intent = new Intent(); intent.putExtra(Intent.EXTRA_SHORTCUT_INTENT, targetIntent); - intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, list.title); - intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); - return intent; + intent.putExtra(Intent.EXTRA_SHORTCUT_NAME, title); + if (iconResourceId == R.drawable.cgeo) { + final ShortcutIconResource iconResource = Intent.ShortcutIconResource.fromContext(this, iconResourceId); + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON_RESOURCE, iconResource); + } + else { + intent.putExtra(Intent.EXTRA_SHORTCUT_ICON, createOverlay(iconResourceId)); + } + + setResult(RESULT_OK, intent); + + // finish activity to return the shortcut + finish(); + } + + private Bitmap createOverlay(final int drawableResourceId) { + final LayerDrawable layerDrawable = new LayerDrawable(new Drawable[] { + res.getDrawable(drawableResourceId), res.getDrawable(R.drawable.cgeo) }); + layerDrawable.setLayerInset(0, 0, 0, 10, 10); + layerDrawable.setLayerInset(1, 50, 50, 0, 0); + return ImageUtils.convertToBitmap(layerDrawable); } } diff --git a/main/src/cgeo/geocaching/DataStore.java b/main/src/cgeo/geocaching/DataStore.java index e404b22..9ee588b 100644 --- a/main/src/cgeo/geocaching/DataStore.java +++ b/main/src/cgeo/geocaching/DataStore.java @@ -58,6 +58,7 @@ import java.util.Collection; import java.util.Collections; import java.util.Comparator; import java.util.Date; +import java.util.EnumMap; import java.util.EnumSet; import java.util.HashMap; import java.util.HashSet; @@ -111,35 +112,32 @@ public class DataStore { "cg_caches.direction," + // 16 "cg_caches.distance," + // 17 "cg_caches.terrain," + // 18 - "cg_caches.latlon," + // 19 - "cg_caches.location," + // 20 - "cg_caches.personal_note," + // 21 - "cg_caches.shortdesc," + // 22 - "cg_caches.favourite_cnt," + // 23 - "cg_caches.rating," + // 24 - "cg_caches.votes," + // 25 - "cg_caches.myvote," + // 26 - "cg_caches.disabled," + // 27 - "cg_caches.archived," + // 28 - "cg_caches.members," + // 29 - "cg_caches.found," + // 30 - "cg_caches.favourite," + // 31 - "cg_caches.inventoryunknown," + // 32 - "cg_caches.onWatchlist," + // 33 - "cg_caches.reliable_latlon," + // 34 - "cg_caches.coordsChanged," + // 35 - "cg_caches.latitude," + // 36 - "cg_caches.longitude," + // 37 - "cg_caches.finalDefined," + // 38 - "cg_caches._id," + // 39 - "cg_caches.inventorycoins," + // 40 - "cg_caches.inventorytags," + // 41 - "cg_caches.logPasswordRequired"; // 42 - - //TODO: remove "latlon" field from cache table + "cg_caches.location," + // 19 + "cg_caches.personal_note," + // 20 + "cg_caches.shortdesc," + // 21 + "cg_caches.favourite_cnt," + // 22 + "cg_caches.rating," + // 23 + "cg_caches.votes," + // 24 + "cg_caches.myvote," + // 25 + "cg_caches.disabled," + // 26 + "cg_caches.archived," + // 27 + "cg_caches.members," + // 28 + "cg_caches.found," + // 29 + "cg_caches.favourite," + // 30 + "cg_caches.inventoryunknown," + // 31 + "cg_caches.onWatchlist," + // 32 + "cg_caches.reliable_latlon," + // 33 + "cg_caches.coordsChanged," + // 34 + "cg_caches.latitude," + // 35 + "cg_caches.longitude," + // 36 + "cg_caches.finalDefined," + // 37 + "cg_caches._id," + // 38 + "cg_caches.inventorycoins," + // 39 + "cg_caches.inventorytags," + // 40 + "cg_caches.logPasswordRequired"; // 41 /** The list of fields needed for mapping. */ - private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latlon", "latitude", "longitude", "note", "own", "visited" }; + private static final String[] WAYPOINT_COLUMNS = new String[] { "_id", "geocode", "updated", "type", "prefix", "lookup", "name", "latitude", "longitude", "note", "own", "visited" }; /** Number of days (as ms) after temporarily saved caches are deleted */ private final static long DAYS_AFTER_CACHE_IS_DELETED = 3 * 24 * 60 * 60 * 1000; @@ -183,7 +181,6 @@ public class DataStore { + "size text, " + "difficulty float, " + "terrain float, " - + "latlon text, " + "location text, " + "direction double, " + "distance double, " @@ -214,9 +211,7 @@ public class DataStore { + "create table " + dbTableLists + " (" + "_id integer primary key autoincrement, " + "title text not null, " - + "updated long not null, " - + "latitude double, " - + "longitude double " + + "updated long not null" + "); "; private static final String dbCreateAttributes = "" + "create table " + dbTableAttributes + " (" @@ -235,7 +230,6 @@ public class DataStore { + "prefix text, " + "lookup text, " + "name text, " - + "latlon text, " + "latitude double, " + "longitude double, " + "note text, " @@ -638,7 +632,6 @@ public class DataStore { + "size text, " + "difficulty float, " + "terrain float, " - + "latlon text, " + "location text, " + "direction double, " + "distance double, " @@ -665,7 +658,7 @@ public class DataStore { db.execSQL(dbCreateCachesTemp); db.execSQL("insert into " + dbTableCachesTemp + " select _id,updated,detailed,detailedupdate,visiteddate,geocode,reason,cacheid,guid,type,name,own,owner,owner_real," + - "hidden,hint,size,difficulty,terrain,latlon,location,direction,distance,latitude,longitude, 0," + + "hidden,hint,size,difficulty,terrain,location,direction,distance,latitude,longitude, 0," + "personal_note,shortdesc,description,favourite_cnt,rating,votes,myvote,disabled,archived,members,found,favourite,inventorycoins," + "inventorytags,inventoryunknown,onWatchlist from " + dbTableCaches); db.execSQL("drop table " + dbTableCaches); @@ -681,13 +674,12 @@ public class DataStore { + "prefix text, " + "lookup text, " + "name text, " - + "latlon text, " + "latitude double, " + "longitude double, " + "note text " + "); "; db.execSQL(dbCreateWaypointsTemp); - db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latlon, latitude, longitude, note from " + dbTableWaypoints); + db.execSQL("insert into " + dbTableWaypointsTemp + " select _id, geocode, updated, type, prefix, lookup, name, latitude, longitude, note from " + dbTableWaypoints); db.execSQL("drop table " + dbTableWaypoints); db.execSQL("alter table " + dbTableWaypointsTemp + " rename to " + dbTableWaypoints); @@ -1009,7 +1001,7 @@ public class DataStore { } synchronized (listId) { listId.bindString(1, value); - return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST_ID; + return listId.simpleQueryForLong() != StoredList.TEMPORARY_LIST.id; } } catch (final SQLiteDoneException e) { // Do nothing, it only means we have no information on the cache @@ -1041,27 +1033,6 @@ public class DataStore { return null; } - public static String getCacheidForGeocode(final String geocode) { - if (StringUtils.isBlank(geocode)) { - return null; - } - init(); - - try { - final SQLiteStatement description = PreparedStatements.getCacheIdOfGeocode(); - synchronized (description) { - description.bindString(1, geocode); - return description.simpleQueryForString(); - } - } catch (final SQLiteDoneException e) { - // Do nothing, it only means we have no information on the cache - } catch (final Exception e) { - Log.e("DataStore.getCacheidForGeocode", e); - } - - return null; - } - /** * Save/store a cache to the CacheCache * @@ -1294,7 +1265,6 @@ public class DataStore { values.put("prefix", oneWaypoint.getPrefix()); values.put("lookup", oneWaypoint.getLookup()); values.put("name", oneWaypoint.getName()); - values.put("latlon", oneWaypoint.getLatlon()); putCoords(values, oneWaypoint.getCoords()); values.put("note", oneWaypoint.getNote()); values.put("own", oneWaypoint.isUserDefined() ? 1 : 0); @@ -1373,7 +1343,6 @@ public class DataStore { values.put("prefix", waypoint.getPrefix()); values.put("lookup", waypoint.getLookup()); values.put("name", waypoint.getName()); - values.put("latlon", waypoint.getLatlon()); putCoords(values, waypoint.getCoords()); values.put("note", waypoint.getNote()); values.put("own", waypoint.isUserDefined() ? 1 : 0); @@ -1562,7 +1531,7 @@ public class DataStore { return new HashSet<>(); } - final Set<Geocache> result = new HashSet<>(); + final Set<Geocache> result = new HashSet<>(geocodes.size()); final Set<String> remaining = new HashSet<>(geocodes); if (loadFlags.contains(LoadFlag.CACHE_BEFORE)) { @@ -1640,7 +1609,7 @@ public class DataStore { int logIndex = -1; while (cursor.moveToNext()) { - final Geocache cache = DataStore.createCacheFromDatabaseContent(cursor); + final Geocache cache = createCacheFromDatabaseContent(cursor); if (loadFlags.contains(LoadFlag.ATTRIBUTES)) { cache.setAttributes(loadAttributes(cache.getGeocode())); @@ -1750,25 +1719,25 @@ public class DataStore { } cache.setTerrain(cursor.getFloat(18)); // do not set cache.location - cache.setCoords(getCoords(cursor, 36, 37)); - cache.setPersonalNote(cursor.getString(21)); + cache.setPersonalNote(cursor.getString(20)); // do not set cache.shortdesc // do not set cache.description - cache.setFavoritePoints(cursor.getInt(23)); - cache.setRating(cursor.getFloat(24)); - cache.setVotes(cursor.getInt(25)); - cache.setMyVote(cursor.getFloat(26)); - cache.setDisabled(cursor.getInt(27) == 1); - cache.setArchived(cursor.getInt(28) == 1); - cache.setPremiumMembersOnly(cursor.getInt(29) == 1); - cache.setFound(cursor.getInt(30) == 1); - cache.setFavorite(cursor.getInt(31) == 1); - cache.setInventoryItems(cursor.getInt(32)); - cache.setOnWatchlist(cursor.getInt(33) == 1); - cache.setReliableLatLon(cursor.getInt(34) > 0); - cache.setUserModifiedCoords(cursor.getInt(35) > 0); - cache.setFinalDefined(cursor.getInt(38) > 0); - cache.setLogPasswordRequired(cursor.getInt(42) > 0); + cache.setFavoritePoints(cursor.getInt(22)); + cache.setRating(cursor.getFloat(23)); + cache.setVotes(cursor.getInt(24)); + cache.setMyVote(cursor.getFloat(25)); + cache.setDisabled(cursor.getInt(26) == 1); + cache.setArchived(cursor.getInt(27) == 1); + cache.setPremiumMembersOnly(cursor.getInt(28) == 1); + cache.setFound(cursor.getInt(29) == 1); + cache.setFavorite(cursor.getInt(30) == 1); + cache.setInventoryItems(cursor.getInt(31)); + cache.setOnWatchlist(cursor.getInt(32) == 1); + cache.setReliableLatLon(cursor.getInt(33) > 0); + cache.setUserModifiedCoords(cursor.getInt(34) > 0); + cache.setCoords(getCoords(cursor, 35, 36)); + cache.setFinalDefined(cursor.getInt(37) > 0); + cache.setLogPasswordRequired(cursor.getInt(41) > 0); Log.d("Loading " + cache.toString() + " (" + cache.getListId() + ") from DB"); @@ -1850,7 +1819,6 @@ public class DataStore { waypoint.setGeocode(cursor.getString(cursor.getColumnIndex("geocode"))); waypoint.setPrefix(cursor.getString(cursor.getColumnIndex("prefix"))); waypoint.setLookup(cursor.getString(cursor.getColumnIndex("lookup"))); - waypoint.setLatlon(cursor.getString(cursor.getColumnIndex("latlon"))); waypoint.setCoords(getCoords(cursor, cursor.getColumnIndex("latitude"), cursor.getColumnIndex("longitude"))); waypoint.setNote(cursor.getString(cursor.getColumnIndex("note"))); @@ -1970,7 +1938,7 @@ public class DataStore { init(); - final Map<LogType, Integer> logCounts = new HashMap<>(); + final Map<LogType, Integer> logCounts = new EnumMap<>(LogType.class); final Cursor cursor = database.query( dbTableLogCount, @@ -2866,7 +2834,7 @@ public class DataStore { final StringBuilder whereExpr = new StringBuilder("geocode in ("); final Iterator<String> iterator = geocodes.iterator(); while (true) { - whereExpr.append(DatabaseUtils.sqlEscapeString(StringUtils.upperCase(iterator.next()))); + DatabaseUtils.appendEscapedSQLString(whereExpr, StringUtils.upperCase(iterator.next())); if (!iterator.hasNext()) { break; } @@ -3003,10 +2971,6 @@ public class DataStore { return getStatement("listFromGeocode", "SELECT reason FROM " + dbTableCaches + " WHERE guid = ?"); } - private static SQLiteStatement getCacheIdOfGeocode() { - return getStatement("cacheIdFromGeocode", "SELECT cacheid FROM " + dbTableCaches + " WHERE geocode = ?"); - } - private static SQLiteStatement getGeocodeOfGuid() { return getStatement("geocodeFromGuid", "SELECT geocode FROM " + dbTableCaches + " WHERE guid = ?"); } @@ -3018,7 +2982,7 @@ public class DataStore { } public static void markDropped(final List<Geocache> caches) { - moveToList(caches, StoredList.TEMPORARY_LIST_ID); + moveToList(caches, StoredList.TEMPORARY_LIST.id); } public static Viewport getBounds(final String geocode) { diff --git a/main/src/cgeo/geocaching/EditWaypointActivity.java b/main/src/cgeo/geocaching/EditWaypointActivity.java index 73cb477..5dccad8 100644 --- a/main/src/cgeo/geocaching/EditWaypointActivity.java +++ b/main/src/cgeo/geocaching/EditWaypointActivity.java @@ -89,10 +89,10 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C */ private Geocache cache; - private Handler loadWaypointHandler = new Handler() { + private final Handler loadWaypointHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { try { if (waypoint == null) { Log.d("No waypoint loaded to edit. id= " + id); @@ -126,7 +126,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C if (own) { initializeWaypointTypeSelector(); } - } catch (RuntimeException e) { + } catch (final RuntimeException e) { Log.e("EditWaypointActivity.loadWaypointHandler", e); } finally { if (waitDialog != null) { @@ -138,7 +138,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C }; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.editwaypoint_activity); if (StringUtils.isBlank(geocode) && id <= 0) { @@ -159,11 +159,11 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C addWaypoint.setOnClickListener(new SaveWaypointListener()); - List<String> wayPointNames = new ArrayList<>(); - for (WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { + final List<String> wayPointNames = new ArrayList<>(); + for (final WaypointType wpt : WaypointType.ALL_TYPES_EXCEPT_OWN_AND_ORIGINAL) { wayPointNames.add(wpt.getL10n()); } - ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); + final ArrayAdapter<String> adapter = new ArrayAdapter<>(this, android.R.layout.simple_dropdown_item_1line, wayPointNames); waypointName.setAdapter(adapter); if (savedInstanceState != null) { @@ -189,7 +189,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C disableSuggestions(distanceView); } - private void setCoordsModificationVisibility(IConnector con, Geocache cache) { + private void setCoordsModificationVisibility(final IConnector con, final Geocache cache) { if (cache != null && (cache.getType() == CacheType.MYSTERY || cache.getType() == CacheType.MULTI)) { coordinatesGroup.setVisibility(View.VISIBLE); modifyBoth.setVisibility(con.supportsOwnCoordinates() ? View.VISIBLE : View.GONE); @@ -205,18 +205,23 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C } private void initializeWaypointTypeSelector() { - ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()])); + final ArrayAdapter<WaypointType> wpAdapter = new ArrayAdapter<>(this, android.R.layout.simple_spinner_item, POSSIBLE_WAYPOINT_TYPES.toArray(new WaypointType[POSSIBLE_WAYPOINT_TYPES.size()])); wpAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); waypointTypeSelector.setAdapter(wpAdapter); waypointTypeSelector.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView<?> parent, View v, int pos, long id) { + public void onItemSelected(final AdapterView<?> parent, final View v, final int pos, final long id) { + final String oldDefaultName = waypointTypeSelectorPosition >= 0 ? getDefaultWaypointName(POSSIBLE_WAYPOINT_TYPES.get(waypointTypeSelectorPosition)) : StringUtils.EMPTY; waypointTypeSelectorPosition = pos; + final String currentName = waypointName.getText().toString().trim(); + if (StringUtils.isBlank(currentName) || oldDefaultName.equals(currentName)) { + waypointName.setText(getDefaultWaypointName(getSelectedWaypointType())); + } } @Override - public void onNothingSelected(AdapterView<?> parent) { + public void onNothingSelected(final AdapterView<?> parent) { } }); @@ -277,7 +282,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C waypoint = DataStore.loadWaypoint(id); loadWaypointHandler.sendMessage(Message.obtain()); - } catch (Exception e) { + } catch (final Exception e) { Log.e("EditWaypointActivity.loadWaypoint.run", e); } } @@ -286,15 +291,15 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C private class CoordDialogListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { Geopoint gp = null; try { gp = new Geopoint(buttonLat.getText().toString(), buttonLon.getText().toString()); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { // button text is blank when creating new waypoint } - Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); - CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo()); + final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + final CoordinatesInputDialog coordsDialog = CoordinatesInputDialog.getInstance(cache, gp, app.currentGeo()); coordsDialog.setCancelable(true); coordsDialog.show(getSupportFragmentManager(),"wpeditdialog"); } @@ -303,11 +308,43 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C } @Override - public void updateCoordinates(Geopoint gp) { + public void updateCoordinates(final Geopoint gp) { buttonLat.setText(gp.format(GeopointFormatter.Format.LAT_DECMINUTE)); buttonLon.setText(gp.format(GeopointFormatter.Format.LON_DECMINUTE)); } + /** + * Suffix the waypoint type with a running number to get a default name. + * + * @param type + * type to create a new default name for + * + * @return + */ + private String getDefaultWaypointName(final WaypointType type) { + final ArrayList<String> wpNames = new ArrayList<>(); + for (final Waypoint waypoint : cache.getWaypoints()) { + wpNames.add(waypoint.getName()); + } + // try final and trailhead without index + if (type == WaypointType.FINAL || type == WaypointType.TRAILHEAD) { + if (!wpNames.contains(type.getL10n())) { + return type.getL10n(); + } + } + // for other types add an index by default + int index = 1; + while (wpNames.contains(type.getL10n() + " " + index)) { + index++; + } + return type.getL10n() + " " + index; + } + + private WaypointType getSelectedWaypointType() { + final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition(); + return selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType(); + } + public static final int SUCCESS = 0; public static final int UPLOAD_START = 1; public static final int UPLOAD_ERROR = 2; @@ -318,7 +355,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C private class SaveWaypointListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { final String bearingText = bearing.getText().toString(); // combine distance from EditText and distanceUnit saved from Spinner final String distanceText = distanceView.getText().toString() + distanceUnits.get(distanceUnitSelector.getSelectedItemPosition()); @@ -336,7 +373,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C if (StringUtils.isNotBlank(latText) && StringUtils.isNotBlank(lonText)) { try { coords = new Geopoint(latText, lonText); - } catch (Geopoint.ParseException e) { + } catch (final Geopoint.ParseException e) { showToast(res.getString(e.resource)); return; } @@ -354,7 +391,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C double bearing; try { bearing = Double.parseDouble(bearingText); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { Dialogs.message(EditWaypointActivity.this, R.string.err_point_bear_and_dist_title, R.string.err_point_bear_and_dist); return; } @@ -363,7 +400,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C try { distance = DistanceParser.parseDistance(distanceText, !Settings.isUseImperialUnits()); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { showToast(res.getString(R.string.err_parse_dist)); return; } @@ -371,19 +408,17 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C coords = coords.project(bearing, distance); } - // if no name is given, just give the waypoint its number as name final String givenName = waypointName.getText().toString().trim(); - final String name = StringUtils.isNotEmpty(givenName) ? givenName : res.getString(R.string.waypoint) + " " + (wpCount + 1); + final String name = StringUtils.defaultIfBlank(givenName, getDefaultWaypointName(getSelectedWaypointType())); final String noteText = note.getText().toString().trim(); final Geopoint coordsToSave = coords; - final int selectedTypeIndex = waypointTypeSelector.getSelectedItemPosition(); - final WaypointType type = selectedTypeIndex >= 0 ? POSSIBLE_WAYPOINT_TYPES.get(selectedTypeIndex) : waypoint.getWaypointType(); + final WaypointType type = getSelectedWaypointType(); final boolean visited = visitedCheckBox.isChecked(); final ProgressDialog progress = ProgressDialog.show(EditWaypointActivity.this, getString(R.string.cache), getString(R.string.waypoint_being_saved), true); final Handler finishHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { // TODO: The order of showToast, progress.dismiss and finish is different in these cases. Why? switch (msg.what) { case UPLOAD_SUCCESS: @@ -422,7 +457,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C class SaveWptTask extends AsyncTask<Void, Void, Void> { @Override - protected Void doInBackground(Void... params) { + protected Void doInBackground(final Void... params) { final Waypoint waypoint = new Waypoint(name, type, own); waypoint.setGeocode(geocode); waypoint.setPrefix(prefix); @@ -432,12 +467,12 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C waypoint.setVisited(visited); waypoint.setId(id); - Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); + final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_WAYPOINTS); if (cache == null) { finishHandler.sendEmptyMessage(SAVE_ERROR); return null; } - Waypoint oldWaypoint = cache.getWaypointById(id); + final Waypoint oldWaypoint = cache.getWaypointById(id); if (cache.addOrChangeWaypoint(waypoint, true)) { DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); if (!StaticMapsProvider.hasAllStaticMapsForWaypoint(geocode, waypoint)) { @@ -460,7 +495,7 @@ public class EditWaypointActivity extends AbstractActionBarActivity implements C finishHandler.sendEmptyMessage(UPLOAD_START); if (cache.supportsOwnCoordinates()) { - boolean result = uploadModifiedCoords(cache, waypoint.getCoords()); + final boolean result = uploadModifiedCoords(cache, waypoint.getCoords()); finishHandler.sendEmptyMessage(result ? SUCCESS : UPLOAD_ERROR); } else { showToast(getString(R.string.waypoint_coordinates_couldnt_be_modified_on_website)); diff --git a/main/src/cgeo/geocaching/Geocache.java b/main/src/cgeo/geocaching/Geocache.java index d9b2856..8bf64dc 100644 --- a/main/src/cgeo/geocaching/Geocache.java +++ b/main/src/cgeo/geocaching/Geocache.java @@ -12,11 +12,9 @@ import cgeo.geocaching.connector.gc.GCConnector; import cgeo.geocaching.connector.gc.GCConstants; import cgeo.geocaching.connector.gc.Tile; import cgeo.geocaching.connector.gc.UncertainProperty; -import cgeo.geocaching.enumerations.CacheAttribute; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; -import cgeo.geocaching.enumerations.LoadFlags.LoadFlag; import cgeo.geocaching.enumerations.LoadFlags.RemoveFlag; import cgeo.geocaching.enumerations.LoadFlags.SaveFlag; import cgeo.geocaching.enumerations.LogType; @@ -54,14 +52,12 @@ import rx.functions.Action0; import android.app.Activity; import android.content.Intent; import android.content.res.Resources; -import android.graphics.drawable.Drawable; import android.net.Uri; import android.os.Environment; import android.os.Handler; import android.os.Message; import android.os.Parcelable; import android.text.Html; -import android.text.Html.ImageGetter; import java.io.File; import java.util.ArrayList; @@ -69,10 +65,9 @@ import java.util.Calendar; import java.util.Collection; import java.util.Collections; import java.util.Date; +import java.util.EnumMap; import java.util.EnumSet; -import java.util.HashMap; import java.util.HashSet; -import java.util.LinkedHashSet; import java.util.LinkedList; import java.util.List; import java.util.Map; @@ -88,7 +83,7 @@ public class Geocache implements ICache, IWaypoint { private long updated = 0; private long detailedUpdate = 0; private long visitedDate = 0; - private int listId = StoredList.TEMPORARY_LIST_ID; + private int listId = StoredList.TEMPORARY_LIST.id; private boolean detailed = false; private String geocode = ""; private String cacheId = ""; @@ -149,7 +144,7 @@ public class Geocache implements ICache, IWaypoint { private List<Image> spoilers = null; private List<Trackable> inventory = null; - private Map<LogType, Integer> logCounts = new HashMap<>(); + private Map<LogType, Integer> logCounts = new EnumMap<>(LogType.class); private boolean userModifiedCoords = false; // temporary values private boolean statusChecked = false; @@ -163,10 +158,6 @@ public class Geocache implements ICache, IWaypoint { private Handler changeNotificationHandler = null; - // Images whose URL contains one of those patterns will not be available on the Images tab - // for opening into an external application. - private final String[] NO_EXTERNAL = new String[]{"geocheck.org"}; - /** * Create a new cache. To be used everywhere except for the GPX parser */ @@ -251,7 +242,7 @@ public class Geocache implements ICache, IWaypoint { if (visitedDate == 0) { visitedDate = other.visitedDate; } - if (listId == StoredList.TEMPORARY_LIST_ID) { + if (listId == StoredList.TEMPORARY_LIST.id) { listId = other.listId; } if (StringUtils.isBlank(geocode)) { @@ -455,11 +446,7 @@ public class Geocache implements ICache, IWaypoint { ActivityMixin.showToast(fromActivity, fromActivity.getResources().getString(R.string.err_cannot_log_visit)); return; } - final Intent logVisitIntent = new Intent(fromActivity, LogCacheActivity.class); - logVisitIntent.putExtra(LogCacheActivity.EXTRAS_ID, cacheId); - logVisitIntent.putExtra(LogCacheActivity.EXTRAS_GEOCODE, geocode); - - fromActivity.startActivity(logVisitIntent); + fromActivity.startActivity(LogCacheActivity.getLogCacheIntent(fromActivity, cacheId, geocode)); } public void logOffline(final Activity fromActivity, final LogType logType) { @@ -1432,7 +1419,7 @@ public class Geocache implements ICache, IWaypoint { } public void store(final CancellableHandler handler) { - store(StoredList.TEMPORARY_LIST_ID, handler); + store(StoredList.TEMPORARY_LIST.id, handler); } public void store(final int listId, final CancellableHandler handler) { @@ -1523,7 +1510,6 @@ public class Geocache implements ICache, IWaypoint { @Override public void call() { refreshSynchronous(handler); - handler.sendEmptyMessage(CancellableHandler.DONE); } }); } @@ -1614,7 +1600,7 @@ public class Geocache implements ICache, IWaypoint { RxUtils.waitForCompletion(StaticMapsProvider.downloadMaps(cache), imgGetter.waitForEndObservable(handler)); if (handler != null) { - handler.sendMessage(Message.obtain()); + handler.sendEmptyMessage(CancellableHandler.DONE); } } catch (final Exception e) { Log.e("Geocache.storeCache", e); @@ -1627,7 +1613,7 @@ public class Geocache implements ICache, IWaypoint { return null; } - if (!forceReload && listId == StoredList.TEMPORARY_LIST_ID && (DataStore.isOffline(geocode, guid) || DataStore.isThere(geocode, guid, true, true))) { + if (!forceReload && listId == StoredList.TEMPORARY_LIST.id && (DataStore.isOffline(geocode, guid) || DataStore.isThere(geocode, guid, true, true))) { final SearchResult search = new SearchResult(); final String realGeocode = StringUtils.isNotBlank(geocode) ? geocode : DataStore.getGeocodeForGuid(guid); search.addGeocode(realGeocode); @@ -1693,22 +1679,6 @@ public class Geocache implements ICache, IWaypoint { return null; } - /** - * check whether the cache has a given attribute - * - * @param attribute - * @param yes - * true if we are looking for the attribute_yes version, false for the attribute_no version - * @return - */ - public boolean hasAttribute(final CacheAttribute attribute, final boolean yes) { - Geocache fullCache = DataStore.loadCache(getGeocode(), EnumSet.of(LoadFlag.ATTRIBUTES)); - if (fullCache == null) { - fullCache = this; - } - return fullCache.getAttributes().contains(attribute.getAttributeName(yes)); - } - public boolean hasStaticMap() { return StaticMapsProvider.hasStaticMap(this); } @@ -1720,23 +1690,6 @@ public class Geocache implements ICache, IWaypoint { } }; - private void addDescriptionImagesUrls(final Collection<Image> images) { - final Set<String> urls = new LinkedHashSet<>(); - for (final Image image : images) { - urls.add(image.getUrl()); - } - Html.fromHtml(getDescription(), new ImageGetter() { - @Override - public Drawable getDrawable(final String source) { - if (!urls.contains(source) && !ImageUtils.containsPattern(source, NO_EXTERNAL)) { - images.add(new Image(source, geocode)); - urls.add(source); - } - return null; - } - }, null); - } - public Collection<Image> getImages() { final LinkedList<Image> result = new LinkedList<>(); result.addAll(getSpoilers()); @@ -1744,11 +1697,7 @@ public class Geocache implements ICache, IWaypoint { for (final LogEntry log : getLogs()) { result.addAll(log.getLogImages()); } - final Set<String> urls = new HashSet<>(result.size()); - for (final Image image : result) { - urls.add(image.getUrl()); - } - addDescriptionImagesUrls(result); + ImageUtils.addImagesFromHtml(result, getDescription(), geocode); return result; } diff --git a/main/src/cgeo/geocaching/GpxFileListActivity.java b/main/src/cgeo/geocaching/GpxFileListActivity.java index dae52c4..3da4927 100644 --- a/main/src/cgeo/geocaching/GpxFileListActivity.java +++ b/main/src/cgeo/geocaching/GpxFileListActivity.java @@ -1,63 +1,63 @@ -package cgeo.geocaching;
-
-import cgeo.geocaching.connector.ConnectorFactory;
-import cgeo.geocaching.connector.IConnector;
-import cgeo.geocaching.files.AbstractFileListActivity;
-import cgeo.geocaching.files.GPXImporter;
-import cgeo.geocaching.list.StoredList;
-import cgeo.geocaching.settings.Settings;
-import cgeo.geocaching.ui.GPXListAdapter;
-
-import org.apache.commons.lang3.StringUtils;
-
-import android.app.Activity;
-import android.content.Intent;
-
-import java.io.File;
-import java.util.Collections;
-import java.util.List;
-
-public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter> {
-
- public GpxFileListActivity() {
- super(new String[] { "gpx", "loc", "zip" });
- }
-
- @Override
- protected GPXListAdapter getAdapter(List<File> files) {
- return new GPXListAdapter(this, files);
- }
-
- @Override
- protected List<File> getBaseFolders() {
- return Collections.singletonList(new File(Settings.getGpxImportDir()));
- }
-
- public static void startSubActivity(Activity fromActivity, int listId) {
- final Intent intent = new Intent(fromActivity, GpxFileListActivity.class);
- intent.putExtra(Intents.EXTRA_LIST_ID, StoredList.getConcreteList(listId));
- fromActivity.startActivityForResult(intent, 0);
- }
-
- @Override
- protected boolean filenameBelongsToList(final String filename) {
- if (super.filenameBelongsToList(filename)) {
- if (StringUtils.endsWithIgnoreCase(filename, GPXImporter.ZIP_FILE_EXTENSION)) {
- for (IConnector connector : ConnectorFactory.getConnectors()) {
- if (connector.isZippedGPXFile(filename)) {
- return true;
- }
- }
- return false;
- }
- // filter out waypoint files
- return !StringUtils.containsIgnoreCase(filename, GPXImporter.WAYPOINTS_FILE_SUFFIX);
- }
- return false;
- }
-
- public int getListId() {
- return listId;
- }
-
-}
+package cgeo.geocaching; + +import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.files.AbstractFileListActivity; +import cgeo.geocaching.files.GPXImporter; +import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.ui.GPXListAdapter; + +import org.apache.commons.lang3.StringUtils; + +import android.app.Activity; +import android.content.Intent; + +import java.io.File; +import java.util.Collections; +import java.util.List; + +public class GpxFileListActivity extends AbstractFileListActivity<GPXListAdapter> { + + public GpxFileListActivity() { + super(new String[] { "gpx", "loc", "zip" }); + } + + @Override + protected GPXListAdapter getAdapter(List<File> files) { + return new GPXListAdapter(this, files); + } + + @Override + protected List<File> getBaseFolders() { + return Collections.singletonList(new File(Settings.getGpxImportDir())); + } + + public static void startSubActivity(Activity fromActivity, int listId) { + final Intent intent = new Intent(fromActivity, GpxFileListActivity.class); + intent.putExtra(Intents.EXTRA_LIST_ID, StoredList.getConcreteList(listId)); + fromActivity.startActivityForResult(intent, 0); + } + + @Override + protected boolean filenameBelongsToList(final String filename) { + if (super.filenameBelongsToList(filename)) { + if (StringUtils.endsWithIgnoreCase(filename, GPXImporter.ZIP_FILE_EXTENSION)) { + for (IConnector connector : ConnectorFactory.getConnectors()) { + if (connector.isZippedGPXFile(filename)) { + return true; + } + } + return false; + } + // filter out waypoint files + return !StringUtils.containsIgnoreCase(filename, GPXImporter.WAYPOINTS_FILE_SUFFIX); + } + return false; + } + + public int getListId() { + return listId; + } + +} diff --git a/main/src/cgeo/geocaching/ImageSelectActivity.java b/main/src/cgeo/geocaching/ImageSelectActivity.java index a64b4cf..3b4039e 100644 --- a/main/src/cgeo/geocaching/ImageSelectActivity.java +++ b/main/src/cgeo/geocaching/ImageSelectActivity.java @@ -48,11 +48,6 @@ public class ImageSelectActivity extends AbstractActionBarActivity { @InjectView(R.id.cancel) protected Button clearButton; @InjectView(R.id.image_preview) protected ImageView imagePreview; - static final String EXTRAS_CAPTION = "caption"; - static final String EXTRAS_DESCRIPTION = "description"; - static final String EXTRAS_URI_AS_STRING = "uri"; - static final String EXTRAS_SCALE = "scale"; - private static final String SAVED_STATE_IMAGE_CAPTION = "cgeo.geocaching.saved_state_image_caption"; private static final String SAVED_STATE_IMAGE_DESCRIPTION = "cgeo.geocaching.saved_state_image_description"; private static final String SAVED_STATE_IMAGE_URI = "cgeo.geocaching.saved_state_image_uri"; @@ -80,10 +75,10 @@ public class ImageSelectActivity extends AbstractActionBarActivity { // Get parameters from intent and basic cache information from database final Bundle extras = getIntent().getExtras(); if (extras != null) { - imageCaption = extras.getString(EXTRAS_CAPTION); - imageDescription = extras.getString(EXTRAS_DESCRIPTION); - imageUri = Uri.parse(extras.getString(EXTRAS_URI_AS_STRING)); - scaleChoiceIndex = extras.getInt(EXTRAS_SCALE, scaleChoiceIndex); + imageCaption = extras.getString(Intents.EXTRA_CAPTION); + imageDescription = extras.getString(Intents.EXTRA_DESCRIPTION); + imageUri = Uri.parse(extras.getString(Intents.EXTRA_URI_AS_STRING)); + scaleChoiceIndex = extras.getInt(Intents.EXTRA_SCALE, scaleChoiceIndex); } // Restore previous state @@ -97,7 +92,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { cameraButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectImageFromCamera(); } }); @@ -105,7 +100,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { storedButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View view) { + public void onClick(final View view) { selectImageFromStorage(); } }); @@ -123,20 +118,20 @@ public class ImageSelectActivity extends AbstractActionBarActivity { scaleView.setSelection(scaleChoiceIndex); scaleView.setOnItemSelectedListener(new OnItemSelectedListener() { @Override - public void onItemSelected(AdapterView<?> arg0, View arg1, int arg2, long arg3) { + public void onItemSelected(final AdapterView<?> arg0, final View arg1, final int arg2, final long arg3) { scaleChoiceIndex = scaleView.getSelectedItemPosition(); Settings.setLogImageScale(scaleChoiceIndex); } @Override - public void onNothingSelected(AdapterView<?> arg0) { + public void onNothingSelected(final AdapterView<?> arg0) { } }); saveButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { saveImageInfo(true); } }); @@ -144,7 +139,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { clearButton.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View v) { + public void onClick(final View v) { saveImageInfo(false); } }); @@ -162,17 +157,17 @@ public class ImageSelectActivity extends AbstractActionBarActivity { outState.putInt(SAVED_STATE_IMAGE_SCALE, scaleChoiceIndex); } - public void saveImageInfo(boolean saveInfo) { + public void saveImageInfo(final boolean saveInfo) { if (saveInfo) { final String filename = writeScaledImage(imageUri.getPath()); if (filename != null) { imageUri = Uri.parse(filename); final Intent intent = new Intent(); syncEditTexts(); - intent.putExtra(EXTRAS_CAPTION, imageCaption); - intent.putExtra(EXTRAS_DESCRIPTION, imageDescription); - intent.putExtra(EXTRAS_URI_AS_STRING, imageUri.toString()); - intent.putExtra(EXTRAS_SCALE, scaleChoiceIndex); + intent.putExtra(Intents.EXTRA_CAPTION, imageCaption); + intent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription); + intent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString()); + intent.putExtra(Intents.EXTRA_SCALE, scaleChoiceIndex); setResult(RESULT_OK, intent); } else { showToast(res.getString(R.string.err_select_logimage_failed)); @@ -193,7 +188,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { private void selectImageFromCamera() { // create Intent to take a picture and return control to the calling application - Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); + final Intent intent = new Intent(MediaStore.ACTION_IMAGE_CAPTURE); imageUri = ImageUtils.getOutputImageFileUri(); // create a file to save the image if (imageUri == null) { @@ -207,14 +202,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity { } private void selectImageFromStorage() { - Intent intent = new Intent(Intent.ACTION_GET_CONTENT); + final Intent intent = new Intent(Intent.ACTION_GET_CONTENT); intent.setType("image/jpeg"); startActivityForResult(Intent.createChooser(intent, "Select Image"), SELECT_STORED_IMAGE); } @Override - protected void onActivityResult(int requestCode, int resultCode, Intent data) { + protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (resultCode == RESULT_CANCELED) { // User cancelled the image capture showToast(getResources().getString(R.string.info_select_logimage_cancelled)); @@ -232,7 +227,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { if (data != null) { final Uri selectedImage = data.getData(); if (Build.VERSION.SDK_INT < VERSION_CODES.KITKAT) { - String[] filePathColumn = { MediaColumns.DATA }; + final String[] filePathColumn = { MediaColumns.DATA }; Cursor cursor = null; try { @@ -243,14 +238,14 @@ public class ImageSelectActivity extends AbstractActionBarActivity { } cursor.moveToFirst(); - int columnIndex = cursor.getColumnIndex(filePathColumn[0]); - String filePath = cursor.getString(columnIndex); + final int columnIndex = cursor.getColumnIndex(filePathColumn[0]); + final String filePath = cursor.getString(columnIndex); if (StringUtils.isBlank(filePath)) { showFailure(); return; } imageUri = Uri.parse(filePath); - } catch (Exception e) { + } catch (final Exception e) { Log.e("ImageSelectActivity.onActivityResult", e); showFailure(); } finally { @@ -269,7 +264,7 @@ public class ImageSelectActivity extends AbstractActionBarActivity { output = new FileOutputStream(outputFile); LocalStorage.copy(input, output); imageUri = Uri.fromFile(outputFile); - } catch (FileNotFoundException e) { + } catch (final FileNotFoundException e) { Log.e("ImageSelectActivity.onStartResult", e); } finally { IOUtils.closeQuietly(input); diff --git a/main/src/cgeo/geocaching/Intents.java b/main/src/cgeo/geocaching/Intents.java index a55c22a..e2b204e 100644 --- a/main/src/cgeo/geocaching/Intents.java +++ b/main/src/cgeo/geocaching/Intents.java @@ -1,5 +1,13 @@ package cgeo.geocaching; +import cgeo.geocaching.enumerations.CacheListType; + +import org.apache.commons.lang3.StringUtils; +import org.eclipse.jdt.annotation.NonNull; + +import android.content.Intent; +import android.os.Bundle; + public class Intents { private Intents() { @@ -18,7 +26,22 @@ public class Intents { public static final String EXTRA_KEYWORD = PREFIX + "keyword"; public static final String EXTRA_KEYWORD_SEARCH = PREFIX + "keyword_search"; public static final String EXTRA_LIST_ID = PREFIX + "list_id"; - public static final String EXTRA_LIST_TYPE = PREFIX + "list_type"; + public static final String EXTRA_CAPTION = PREFIX + "caption"; + public static final String EXTRA_DESCRIPTION = PREFIX + "description"; + public static final String EXTRA_URI_AS_STRING = PREFIX + "uri"; + public static final String EXTRA_SCALE = PREFIX + "scale"; + + public static final String EXTRA_WPTTYPE = PREFIX + "wpttype"; + public static final String EXTRA_MAPSTATE = PREFIX + "mapstate"; + public static final String EXTRA_MAP_TITLE = PREFIX + "mapTitle"; + public static final String EXTRA_MAP_MODE = PREFIX + "mapMode"; + public static final String EXTRA_LIVE_ENABLED = PREFIX + "liveEnabled"; + + /** + * list type to be used with the cache list activity. Be aware to use the String representation of the corresponding + * enum. + */ + private static final String EXTRA_LIST_TYPE = PREFIX + "list_type"; public static final String EXTRA_MAP_FILE = PREFIX + "map_file"; public static final String EXTRA_NAME = PREFIX + "name"; public static final String EXTRA_SEARCH = PREFIX + "search"; @@ -49,4 +72,27 @@ public class Intents { public static final String EXTRA_OAUTH_TEMP_TOKEN_SECRET_PREF = PREFIX_OAUTH + "tempSecretPref"; public static final String EXTRA_OAUTH_TOKEN_PUBLIC_KEY = PREFIX_OAUTH + "publicTokenPref"; public static final String EXTRA_OAUTH_TOKEN_SECRET_KEY = PREFIX_OAUTH + "secretTokenPref"; + + public static Intent putListType(final Intent intent, final @NonNull CacheListType listType) { + intent.putExtra(Intents.EXTRA_LIST_TYPE, listType.name()); + return intent; + } + + public static @NonNull CacheListType getListType(final Intent intent) { + final Bundle extras = intent.getExtras(); + if (extras == null) { + return CacheListType.OFFLINE; + } + final String typeName = extras.getString(Intents.EXTRA_LIST_TYPE); + if (StringUtils.isBlank(typeName)) { + return CacheListType.OFFLINE; + } + CacheListType listType; + try { + listType = CacheListType.valueOf(typeName); + } catch (final IllegalArgumentException e) { + return CacheListType.OFFLINE; + } + return (listType != null) ? listType : CacheListType.OFFLINE; + } } diff --git a/main/src/cgeo/geocaching/LogCacheActivity.java b/main/src/cgeo/geocaching/LogCacheActivity.java index bc87525..3558b3c 100644 --- a/main/src/cgeo/geocaching/LogCacheActivity.java +++ b/main/src/cgeo/geocaching/LogCacheActivity.java @@ -54,8 +54,6 @@ import java.util.Date; import java.util.List; public class LogCacheActivity extends AbstractLoggingActivity implements DateDialog.DateDialogParent { - static final String EXTRAS_GEOCODE = "geocode"; - static final String EXTRAS_ID = "id"; private static final String SAVED_STATE_RATING = "cgeo.geocaching.saved_state_rating"; private static final String SAVED_STATE_TYPE = "cgeo.geocaching.saved_state_type"; @@ -208,9 +206,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia // Get parameters from intent and basic cache information from database final Bundle extras = getIntent().getExtras(); if (extras != null) { - geocode = extras.getString(EXTRAS_GEOCODE); + geocode = extras.getString(Intents.EXTRA_GEOCODE); if (StringUtils.isBlank(geocode)) { - final String cacheid = extras.getString(EXTRAS_ID); + final String cacheid = extras.getString(Intents.EXTRA_ID); if (StringUtils.isNotBlank(cacheid)) { geocode = DataStore.getGeocodeForGuid(cacheid); } @@ -288,6 +286,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia loggingManager = cache.getLoggingManager(this); loggingManager.init(); + requestKeyboardForLogging(); } private void initializeRatingBar(final RatingBar ratingBar) { @@ -423,7 +422,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia if (logResult.getPostLogResult() == StatusCode.NO_ERROR) { // update geocache in DB - if (typeSelected == LogType.FOUND_IT || typeSelected == LogType.ATTENDED || typeSelected == LogType.WEBCAM_PHOTO_TAKEN) { + if (typeSelected.isFoundLog()) { cache.setFound(true); cache.setVisitedDate(new Date().getTime()); } @@ -578,9 +577,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia private void selectImage() { final Intent selectImageIntent = new Intent(this, ImageSelectActivity.class); - selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_CAPTION, imageCaption); - selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_DESCRIPTION, imageDescription); - selectImageIntent.putExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING, imageUri.toString()); + selectImageIntent.putExtra(Intents.EXTRA_CAPTION, imageCaption); + selectImageIntent.putExtra(Intents.EXTRA_DESCRIPTION, imageDescription); + selectImageIntent.putExtra(Intents.EXTRA_URI_AS_STRING, imageUri.toString()); startActivityForResult(selectImageIntent, SELECT_IMAGE); } @@ -589,9 +588,9 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia protected void onActivityResult(final int requestCode, final int resultCode, final Intent data) { if (requestCode == SELECT_IMAGE) { if (resultCode == RESULT_OK) { - imageCaption = data.getStringExtra(ImageSelectActivity.EXTRAS_CAPTION); - imageDescription = data.getStringExtra(ImageSelectActivity.EXTRAS_DESCRIPTION); - imageUri = Uri.parse(data.getStringExtra(ImageSelectActivity.EXTRAS_URI_AS_STRING)); + imageCaption = data.getStringExtra(Intents.EXTRA_CAPTION); + imageDescription = data.getStringExtra(Intents.EXTRA_DESCRIPTION); + imageUri = Uri.parse(data.getStringExtra(Intents.EXTRA_URI_AS_STRING)); } else if (resultCode != RESULT_CANCELED) { // Image capture failed, advise user showToast(getResources().getString(R.string.err_select_logimage_failed)); @@ -603,7 +602,7 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia public boolean onOptionsItemSelected(final MenuItem item) { switch (item.getItemId()) { case R.id.menu_send: - sendLog(); + sendLogAndConfirm(); return true; case R.id.menu_image: selectImage(); @@ -622,18 +621,32 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia return super.onOptionsItemSelected(item); } - private void sendLog() { + private void sendLogAndConfirm() { if (!sendButtonEnabled) { Dialogs.message(this, R.string.log_post_not_possible); + return; + } + if (typeSelected.mustConfirmLog()) { + Dialogs.confirm(this, R.string.confirm_log_title, res.getString(R.string.confirm_log_message, typeSelected.getL10n()), new OnClickListener() { + + @Override + public void onClick(final DialogInterface dialog, final int which) { + sendLogInternal(); + } + }); } else { - final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? - R.string.log_saving : - R.string.log_saving_and_uploading); - new Poster(this, message).execute(currentLogText(), currentLogPassword()); + sendLogInternal(); } } + private void sendLogInternal() { + final String message = res.getString(StringUtils.isBlank(imageUri.getPath()) ? + R.string.log_saving : + R.string.log_saving_and_uploading); + new Poster(this, message).execute(currentLogText(), currentLogPassword()); + } + @Override public boolean onCreateOptionsMenu(final Menu menu) { super.onCreateOptionsMenu(menu); @@ -650,4 +663,11 @@ public class LogCacheActivity extends AbstractLoggingActivity implements DateDia .setTarget(new ActionItemTarget(this, R.id.menu_send)) .setContent(R.string.showcase_logcache_title, R.string.showcase_logcache_text); } + + public static Intent getLogCacheIntent(final Activity context, final String cacheId, final String geocode) { + final Intent logVisitIntent = new Intent(context, LogCacheActivity.class); + logVisitIntent.putExtra(Intents.EXTRA_ID, cacheId); + logVisitIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); + return logVisitIntent; + } } diff --git a/main/src/cgeo/geocaching/LogTrackableActivity.java b/main/src/cgeo/geocaching/LogTrackableActivity.java index 26f7c84..5c6d0f5 100644 --- a/main/src/cgeo/geocaching/LogTrackableActivity.java +++ b/main/src/cgeo/geocaching/LogTrackableActivity.java @@ -3,6 +3,7 @@ package cgeo.geocaching; import butterknife.ButterKnife; import butterknife.InjectView; +import cgeo.geocaching.activity.Keyboard; import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.connector.gc.GCParser; import cgeo.geocaching.enumerations.LogType; @@ -146,6 +147,17 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat } init(); + requestKeyboardForLogging(); + } + + @Override + protected void requestKeyboardForLogging() { + if (StringUtils.isBlank(trackingEditText.getText())) { + new Keyboard(this).show(trackingEditText); + } + else { + super.requestKeyboardForLogging(); + } } @Override @@ -309,6 +321,9 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat tweetCheck.isChecked() && tweetBox.getVisibility() == View.VISIBLE) { Twitter.postTweetTrackable(geocode, new LogEntry(0, typeSelected, log)); } + if (status == StatusCode.NO_ERROR) { + addLocalTrackableLog(log); + } return status; } catch (final Exception e) { @@ -318,6 +333,20 @@ public class LogTrackableActivity extends AbstractLoggingActivity implements Dat return StatusCode.LOG_POST_ERROR; } + /** + * Adds the new log to the list of log entries for this trackable to be able to show it in the trackable activity. + * + * + * @param logText + */ + private void addLocalTrackableLog(final String logText) { + final LogEntry logEntry = new LogEntry(Calendar.getInstance().getTimeInMillis(), typeSelected, logText); + final ArrayList<LogEntry> modifiedLogs = new ArrayList<>(trackable.getLogs()); + modifiedLogs.add(0, logEntry); + trackable.setLogs(modifiedLogs); + DataStore.saveTrackable(trackable); + } + public static void startActivity(final Context context, final Trackable trackable) { final Intent logTouchIntent = new Intent(context, LogTrackableActivity.class); logTouchIntent.putExtra(Intents.EXTRA_GEOCODE, trackable.getGeocode()); diff --git a/main/src/cgeo/geocaching/MainActivity.java b/main/src/cgeo/geocaching/MainActivity.java index c0c6712..40504fb 100644 --- a/main/src/cgeo/geocaching/MainActivity.java +++ b/main/src/cgeo/geocaching/MainActivity.java @@ -7,8 +7,6 @@ import cgeo.geocaching.activity.AbstractActionBarActivity; import cgeo.geocaching.activity.ShowcaseViewBuilder; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.capability.ILogin; -import cgeo.geocaching.connector.gc.GCConnector; -import cgeo.geocaching.connector.gc.GCLogin; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.geopoint.Geopoint; @@ -17,6 +15,8 @@ import cgeo.geocaching.list.PseudoList; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.maps.CGeoMap; import cgeo.geocaching.sensors.GeoDirHandler; +import cgeo.geocaching.sensors.GpsStatusProvider; +import cgeo.geocaching.sensors.GpsStatusProvider.Status; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.settings.SettingsActivity; @@ -38,8 +38,8 @@ import rx.Observable; import rx.Observable.OnSubscribe; import rx.Subscriber; import rx.android.observables.AndroidObservable; +import rx.android.schedulers.AndroidSchedulers; import rx.functions.Action1; -import rx.subscriptions.Subscriptions; import android.app.AlertDialog; import android.app.AlertDialog.Builder; @@ -147,37 +147,16 @@ public class MainActivity extends AbstractActionBarActivity { return StringUtils.join(addressParts, ", "); } - private class SatellitesHandler extends GeoDirHandler { - - private boolean gpsEnabled = false; - private int satellitesFixed = 0; - private int satellitesVisible = 0; - + private final Action1<GpsStatusProvider.Status> satellitesHandler = new Action1<Status>() { @Override - public void updateGeoData(final IGeoData data) { - if (data.getGpsEnabled() == gpsEnabled && - data.getSatellitesFixed() == satellitesFixed && - data.getSatellitesVisible() == satellitesVisible) { - return; - } - gpsEnabled = data.getGpsEnabled(); - satellitesFixed = data.getSatellitesFixed(); - satellitesVisible = data.getSatellitesVisible(); - - if (gpsEnabled) { - if (satellitesFixed > 0) { - navSatellites.setText(res.getString(R.string.loc_sat) + ": " + satellitesFixed + '/' + satellitesVisible); - } else if (satellitesVisible >= 0) { - navSatellites.setText(res.getString(R.string.loc_sat) + ": 0/" + satellitesVisible); - } + public void call(final Status gpsStatus) { + if (gpsStatus.gpsEnabled) { + navSatellites.setText(res.getString(R.string.loc_sat) + ": " + gpsStatus.satellitesFixed + '/' + gpsStatus.satellitesVisible); } else { navSatellites.setText(res.getString(R.string.loc_gps_disabled)); } } - - } - - private final SatellitesHandler satellitesHandler = new SatellitesHandler(); + }; private final Handler firstLoginHandler = new Handler() { @@ -231,8 +210,12 @@ public class MainActivity extends AbstractActionBarActivity { @Override public void onResume() { - super.onResume(Subscriptions.from(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA), satellitesHandler.start(GeoDirHandler.UPDATE_GEODATA))); + super.onResume(locationUpdater.start(GeoDirHandler.UPDATE_GEODATA | GeoDirHandler.LOW_POWER), + app.gpsStatusObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(satellitesHandler)); updateUserInfoHandler.sendEmptyMessage(-1); + if (app.hasValidLocation()) { + locationUpdater.updateGeoData(app.currentGeo()); + } startBackgroundLogin(); init(); } @@ -247,9 +230,9 @@ public class MainActivity extends AbstractActionBarActivity { new Thread() { @Override public void run() { - if (mustLogin && conn == GCConnector.getInstance()) { + if (mustLogin) { // Properly log out from geocaching.com - GCLogin.getInstance().logout(); + conn.logout(); } conn.login(firstLoginHandler, MainActivity.this); updateUserInfoHandler.sendEmptyMessage(-1); @@ -315,7 +298,7 @@ public class MainActivity extends AbstractActionBarActivity { startActivity(new Intent(this, SettingsActivity.class)); return true; case R.id.menu_history: - CacheListActivity.startActivityHistory(this); + startActivity(CacheListActivity.getHistoryIntent(this)); return true; case R.id.menu_scan: startScannerApplication(); @@ -578,7 +561,7 @@ public class MainActivity extends AbstractActionBarActivity { } } }); - AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.from(geo.getCoords().toString()))) + AndroidObservable.bindActivity(MainActivity.this, address.onErrorResumeNext(Observable.just(geo.getCoords().toString()))) .subscribeOn(RxUtils.networkScheduler) .subscribe(new Action1<String>() { @Override @@ -599,7 +582,7 @@ public class MainActivity extends AbstractActionBarActivity { */ public void cgeoFindOnMap(final View v) { findOnMap.setPressed(true); - CGeoMap.startActivityLiveMap(this); + startActivity(CGeoMap.getLiveMapIntent(this)); } /** @@ -612,7 +595,7 @@ public class MainActivity extends AbstractActionBarActivity { } nearestView.setPressed(true); - CacheListActivity.startActivityNearest(this, app.currentGeo().getCoords()); + startActivity(CacheListActivity.getNearestIntent(this)); } /** @@ -733,12 +716,18 @@ public class MainActivity extends AbstractActionBarActivity { } private void checkShowChangelog() { - final long lastChecksum = Settings.getLastChangelogChecksum(); - final long checksum = TextUtils.checksum(getString(R.string.changelog_master) + getString(R.string.changelog_release)); - Settings.setLastChangelogChecksum(checksum); - // don't show change log after new install... - if (lastChecksum > 0 && lastChecksum != checksum) { - AboutActivity.showChangeLog(this); + // temporary workaround for #4143 + //TODO: understand and avoid if possible + try { + final long lastChecksum = Settings.getLastChangelogChecksum(); + final long checksum = TextUtils.checksum(getString(R.string.changelog_master) + getString(R.string.changelog_release)); + Settings.setLastChangelogChecksum(checksum); + // don't show change log after new install... + if (lastChecksum > 0 && lastChecksum != checksum) { + AboutActivity.showChangeLog(this); + } + } catch (final Exception ex) { + Log.e("Error checking/showing changelog!", ex); } } diff --git a/main/src/cgeo/geocaching/SearchResult.java b/main/src/cgeo/geocaching/SearchResult.java index 74cc59d..6015872 100644 --- a/main/src/cgeo/geocaching/SearchResult.java +++ b/main/src/cgeo/geocaching/SearchResult.java @@ -320,7 +320,7 @@ public class SearchResult implements Parcelable { return cObservable.flatMap(new Func1<C, Observable<? extends SearchResult>>() { @Override public Observable<? extends SearchResult> call(final C c) { - return c.isActive() ? Observable.from(func.call(c)) : Observable.<SearchResult>empty(); + return c.isActive() ? Observable.just(func.call(c)) : Observable.<SearchResult>empty(); } }); } diff --git a/main/src/cgeo/geocaching/SelectMapfileActivity.java b/main/src/cgeo/geocaching/SelectMapfileActivity.java index dc898d7..da41250 100644 --- a/main/src/cgeo/geocaching/SelectMapfileActivity.java +++ b/main/src/cgeo/geocaching/SelectMapfileActivity.java @@ -33,7 +33,7 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio private String mapFile; - private static int REQUEST_DIRECTORY = 1; + private final static int REQUEST_DIRECTORY = 1; @Override public void onCreate(Bundle savedInstanceState) { @@ -98,8 +98,8 @@ public class SelectMapfileActivity extends AbstractFileListActivity<FileSelectio } @Override - public void setCurrentFile(String newFile) { - mapFile = newFile; + public void setCurrentFile(String name) { + mapFile = name; } @Override diff --git a/main/src/cgeo/geocaching/StatusFragment.java b/main/src/cgeo/geocaching/StatusFragment.java index a228363..3fd4434 100644 --- a/main/src/cgeo/geocaching/StatusFragment.java +++ b/main/src/cgeo/geocaching/StatusFragment.java @@ -9,7 +9,6 @@ import cgeo.geocaching.utils.Log; import rx.Subscription; import rx.android.observables.AndroidObservable; import rx.functions.Action1; -import rx.schedulers.Schedulers; import rx.subscriptions.Subscriptions; import android.content.Intent; @@ -34,7 +33,7 @@ public class StatusFragment extends Fragment { final ViewGroup statusGroup = (ViewGroup) inflater.inflate(R.layout.status, container, false); final ImageView statusIcon = ButterKnife.findById(statusGroup, R.id.status_icon); final TextView statusMessage = ButterKnife.findById(statusGroup, R.id.status_message); - statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus).subscribeOn(Schedulers.io()) + statusSubscription = AndroidObservable.bindFragment(this, StatusUpdater.latestStatus) .subscribe(new Action1<Status>() { @Override public void call(final Status status) { diff --git a/main/src/cgeo/geocaching/Trackable.java b/main/src/cgeo/geocaching/Trackable.java index 9c2b044..fe53109 100644 --- a/main/src/cgeo/geocaching/Trackable.java +++ b/main/src/cgeo/geocaching/Trackable.java @@ -3,13 +3,16 @@ package cgeo.geocaching; import cgeo.geocaching.connector.ConnectorFactory; import cgeo.geocaching.connector.trackable.TrackableConnector; import cgeo.geocaching.enumerations.LogType; +import cgeo.geocaching.utils.ImageUtils; import org.apache.commons.lang3.StringUtils; import android.text.Html; import java.util.ArrayList; +import java.util.Collection; import java.util.Date; +import java.util.LinkedList; import java.util.List; public class Trackable implements ILogable { @@ -214,6 +217,18 @@ public class Trackable implements ILogable { this.trackingcode = trackingcode; } + public Collection<Image> getImages() { + final List<Image> images = new LinkedList<>(); + if (StringUtils.isNotBlank(image)) { + images.add(new Image(image, StringUtils.defaultIfBlank(name, geocode))); + } + ImageUtils.addImagesFromHtml(images, getDetails(), geocode); + for (final LogEntry log : getLogs()) { + images.addAll(log.getLogImages()); + } + return images; + } + static public List<LogType> getPossibleLogTypes() { final List<LogType> logTypes = new ArrayList<>(); logTypes.add(LogType.RETRIEVED_IT); diff --git a/main/src/cgeo/geocaching/TrackableActivity.java b/main/src/cgeo/geocaching/TrackableActivity.java index 41b2d92..81516c3 100644 --- a/main/src/cgeo/geocaching/TrackableActivity.java +++ b/main/src/cgeo/geocaching/TrackableActivity.java @@ -15,29 +15,33 @@ import cgeo.geocaching.network.HtmlImage; import cgeo.geocaching.ui.AbstractCachingPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.CacheDetailsCreator; +import cgeo.geocaching.ui.ImagesList; import cgeo.geocaching.ui.UserActionsClickListener; import cgeo.geocaching.ui.UserNameClickListener; import cgeo.geocaching.ui.logs.TrackableLogsViewCreator; import cgeo.geocaching.utils.Formatter; import cgeo.geocaching.utils.HtmlUtils; import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.UnknownTagsHandler; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.apache.commons.lang3.tuple.Pair; +import rx.Observable; import rx.android.observables.AndroidObservable; import rx.android.observables.ViewObservable; import rx.functions.Action1; +import rx.functions.Func0; +import rx.subscriptions.CompositeSubscription; import android.app.ProgressDialog; import android.content.Intent; import android.graphics.drawable.BitmapDrawable; import android.net.Uri; import android.os.Bundle; -import android.os.Handler; -import android.os.Message; import android.support.v7.app.ActionBar; import android.support.v7.view.ActionMode; import android.text.Html; @@ -59,9 +63,12 @@ import java.util.Locale; public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivity.Page> implements ActivitySharingInterface { + private CompositeSubscription createSubscriptions; + public enum Page { DETAILS(R.string.detail), - LOGS(R.string.cache_logs); + LOGS(R.string.cache_logs), + IMAGES(R.string.cache_images); private final int resId; @@ -77,50 +84,9 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi private String id = null; private LayoutInflater inflater = null; private ProgressDialog waitDialog = null; - private final Handler loadTrackableHandler = new Handler() { - - @Override - public void handleMessage(final Message msg) { - if (trackable == null) { - if (waitDialog != null) { - waitDialog.dismiss(); - } - - if (StringUtils.isNotBlank(geocode)) { - showToast(res.getString(R.string.err_tb_find) + " " + geocode + "."); - } else { - showToast(res.getString(R.string.err_tb_find_that)); - } - - finish(); - return; - } - - try { - inflater = getLayoutInflater(); - geocode = trackable.getGeocode(); - - if (StringUtils.isNotBlank(trackable.getName())) { - setTitle(Html.fromHtml(trackable.getName()).toString()); - } else { - setTitle(trackable.getName()); - } - - invalidateOptionsMenuCompatible(); - reinitializeViewPager(); - - } catch (final Exception e) { - Log.e("TrackableActivity.loadTrackableHandler: ", e); - } - - if (waitDialog != null) { - waitDialog.dismiss(); - } - - } - }; - private CharSequence clickedItemText = null; + private ImagesList imagesList = null; + /** * Action mode of the current contextual action bar (e.g. for copy and share actions). */ @@ -201,15 +167,28 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi } else { message = res.getString(R.string.trackable); } - waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true); - // If we have a newer Android device setup Android Beam for easy cache sharing initializeAndroidBeam(this); - createViewPager(0, null); - final LoadTrackableThread thread = new LoadTrackableThread(loadTrackableHandler, geocode, guid, id); - thread.start(); + createViewPager(0, new OnPageSelectedListener() { + @Override + public void onPageSelected(final int position) { + // Lazy loading of trackable images + if (getPage(position) == Page.IMAGES) { + loadTrackableImages(); + } + } + }); + waitDialog = ProgressDialog.show(this, message, res.getString(R.string.trackable_details_loading), true, true); + createSubscriptions = new CompositeSubscription(); + createSubscriptions.add(AndroidObservable.bindActivity(this, loadTrackable(geocode, guid, id)).singleOrDefault(null).subscribe(new Action1<Trackable>() { + @Override + public void call(final Trackable trackable) { + TrackableActivity.this.trackable = trackable; + displayTrackable(); + } + })); } @Override @@ -245,87 +224,85 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi return super.onPrepareOptionsMenu(menu); } - private class LoadTrackableThread extends Thread { - final private Handler handler; - final private String geocode; - final private String guid; - final private String id; - - public LoadTrackableThread(final Handler handlerIn, final String geocodeIn, final String guidIn, final String idIn) { - handler = handlerIn; - geocode = geocodeIn; - guid = guidIn; - id = idIn; - } - - @Override - public void run() { - if (StringUtils.isNotEmpty(geocode)) { - - // iterate over the connectors as some codes may be handled by multiple connectors - for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) { - if (trackableConnector.canHandleTrackable(geocode)) { - trackable = trackableConnector.searchTrackable(geocode, guid, id); - if (trackable != null) { - break; + private static Observable<Trackable> loadTrackable(final String geocode, final String guid, final String id) { + return Observable.defer(new Func0<Observable<Trackable>>() { + @Override + public Observable<Trackable> call() { + if (StringUtils.isNotEmpty(geocode)) { + // iterate over the connectors as some codes may be handled by multiple connectors + for (final TrackableConnector trackableConnector : ConnectorFactory.getTrackableConnectors()) { + if (trackableConnector.canHandleTrackable(geocode)) { + final Trackable trackable = trackableConnector.searchTrackable(geocode, guid, id); + if (trackable != null) { + return Observable.just(trackable); + } } } + // Check local storage (offline case) + final Trackable trackable = DataStore.loadTrackable(geocode); + if (trackable != null) { + return Observable.just(trackable); + } } - // Check local storage (offline case) - if (trackable == null) { - trackable = DataStore.loadTrackable(geocode); - } - } - // fall back to GC search by GUID - if (trackable == null) { - trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id); + + // Fall back to GC search by GUID + final Trackable trackable = TravelBugConnector.getInstance().searchTrackable(geocode, guid, id); + return trackable != null ? Observable.just(trackable) : Observable.<Trackable>empty(); } - handler.sendMessage(Message.obtain()); - } + }).subscribeOn(RxUtils.networkScheduler); } - private class TrackableIconThread extends Thread { - final private String url; - final private Handler handler; + public void displayTrackable() { + if (trackable == null) { + if (waitDialog != null) { + waitDialog.dismiss(); + } + + if (StringUtils.isNotBlank(geocode)) { + showToast(res.getString(R.string.err_tb_find) + " " + geocode + "."); + } else { + showToast(res.getString(R.string.err_tb_find_that)); + } - public TrackableIconThread(final String urlIn, final Handler handlerIn) { - url = urlIn; - handler = handlerIn; + finish(); + return; } - @Override - public void run() { - if (url == null || handler == null) { - return; + try { + inflater = getLayoutInflater(); + geocode = trackable.getGeocode(); + + if (StringUtils.isNotBlank(trackable.getName())) { + setTitle(Html.fromHtml(trackable.getName()).toString()); + } else { + setTitle(trackable.getName()); } - try { - final HtmlImage imgGetter = new HtmlImage(trackable.getGeocode(), false, 0, false); + invalidateOptionsMenuCompatible(); + reinitializeViewPager(); - final BitmapDrawable image = imgGetter.getDrawable(url); - final Message message = handler.obtainMessage(0, image); - handler.sendMessage(message); - } catch (final Exception e) { - Log.e("TrackableActivity.TrackableIconThread.run: ", e); - } + } catch (final Exception e) { + Log.e("TrackableActivity.loadTrackableHandler: ", e); } - } - - private static class TrackableIconHandler extends Handler { - final private ActionBar view; - public TrackableIconHandler(final ActionBar viewIn) { - view = viewIn; + if (waitDialog != null) { + waitDialog.dismiss(); } - @Override - public void handleMessage(final Message message) { - final BitmapDrawable image = (BitmapDrawable) message.obj; - if (image != null && view != null) { - image.setBounds(0, 0, view.getHeight(), view.getHeight()); - view.setIcon(image); + } + + private void setupIcon(final ActionBar actionBar, final String url) { + final HtmlImage imgGetter = new HtmlImage(HtmlImage.SHARED, false, 0, false); + AndroidObservable.bindActivity(this, imgGetter.fetchDrawable(url)).subscribe(new Action1<BitmapDrawable>() { + @Override + public void call(final BitmapDrawable image) { + if (actionBar != null) { + final int height = actionBar.getHeight(); + image.setBounds(0, 0, height, height); + actionBar.setIcon(image); + } } - } + }); } public static void startActivity(final AbstractActivity fromContext, @@ -343,11 +320,38 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi case DETAILS: return new DetailsViewCreator(); case LOGS: - return new TrackableLogsViewCreator(this, trackable); + return new TrackableLogsViewCreator(this); + case IMAGES: + return new ImagesViewCreator(); } throw new IllegalStateException(); // cannot happen as long as switch case is enum complete } + private class ImagesViewCreator extends AbstractCachingPageViewCreator<View> { + + @Override + public View getDispatchedView(final ViewGroup parentView) { + view = getLayoutInflater().inflate(R.layout.cachedetail_images_page, parentView, false); + return view; + } + } + + private void loadTrackableImages() { + if (imagesList != null) { + return; + } + final PageViewCreator creator = getViewCreator(Page.IMAGES); + if (creator == null) { + return; + } + final View imageView = creator.getView(null); + if (imageView == null) { + return; + } + imagesList = new ImagesList(this, trackable.getGeocode()); + createSubscriptions.add(imagesList.loadImages(imageView, trackable.getImages(), false)); + } + @Override protected String getTitle(final Page page) { return res.getString(page.resId); @@ -357,9 +361,12 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi protected Pair<List<? extends Page>, Integer> getOrderedPages() { final List<Page> pages = new ArrayList<>(); pages.add(Page.DETAILS); - if (!trackable.getLogs().isEmpty()) { + if (CollectionUtils.isNotEmpty(trackable.getLogs())) { pages.add(Page.LOGS); } + if (CollectionUtils.isNotEmpty(trackable.getImages())) { + pages.add(Page.IMAGES); + } return new ImmutablePair<List<? extends Page>, Integer>(pages, 0); } @@ -382,9 +389,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi // action bar icon if (StringUtils.isNotBlank(trackable.getIconUrl())) { - final TrackableIconHandler iconHandler = new TrackableIconHandler(getSupportActionBar()); - final TrackableIconThread iconThread = new TrackableIconThread(trackable.getIconUrl(), iconHandler); - iconThread.start(); + setupIcon(getSupportActionBar(), trackable.getIconUrl()); } // trackable name @@ -433,8 +438,7 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi if (showTimeSpan && trackable.getLogs() != null) { for (final LogEntry log : trackable.getLogs()) { if (log.type == LogType.RETRIEVED_IT || log.type == LogType.GRABBED_IT || log.type == LogType.DISCOVERED_IT || log.type == LogType.PLACED_IT) { - final int days = log.daysSinceLog(); - text.append(" (").append(res.getQuantityString(R.plurals.days_ago, days, days)).append(')'); + text.append(" (").append(Formatter.formatDaysAgo(log.date)).append(')'); break; } } @@ -595,4 +599,24 @@ public class TrackableActivity extends AbstractViewPagerActivity<TrackableActivi return false; } + @Override + protected void onResume() { + super.onResume(); + // refresh the logs view after coming back from logging a trackable + if (trackable != null) { + final Trackable updatedTrackable = DataStore.loadTrackable(trackable.getGeocode()); + trackable.setLogs(updatedTrackable.getLogs()); + reinitializeViewPager(); + } + } + + @Override + protected void onDestroy() { + createSubscriptions.unsubscribe(); + super.onDestroy(); + } + + public Trackable getTrackable() { + return trackable; + } } diff --git a/main/src/cgeo/geocaching/Waypoint.java b/main/src/cgeo/geocaching/Waypoint.java index 7381aab..b2c5305 100644 --- a/main/src/cgeo/geocaching/Waypoint.java +++ b/main/src/cgeo/geocaching/Waypoint.java @@ -21,13 +21,13 @@ public class Waypoint implements IWaypoint { public static final String PREFIX_OWN = "OWN"; private static final int ORDER_UNDEFINED = -2; + private static final Pattern PATTERN_COORDS = Pattern.compile("\\b[nNsS]\\s*\\d"); private int id = -1; private String geocode = "geocode"; private WaypointType waypointType = WaypointType.WAYPOINT; private String prefix = ""; private String lookup = ""; private String name = ""; - private String latlon = ""; private Geopoint coords = null; private String note = ""; private int cachedOrder = ORDER_UNDEFINED; @@ -67,9 +67,6 @@ public class Waypoint implements IWaypoint { if (StringUtils.isBlank(name)) { setName(old.name); } - if (StringUtils.isBlank(latlon) || latlon.startsWith("?")) { // there are waypoints containing "???" - latlon = old.latlon; - } if (coords == null) { coords = old.coords; } @@ -204,14 +201,6 @@ public class Waypoint implements IWaypoint { this.name = name; } - public String getLatlon() { - return latlon; - } - - public void setLatlon(final String latlon) { - this.latlon = latlon; - } - @Override public Geopoint getCoords() { return coords; @@ -303,10 +292,9 @@ public class Waypoint implements IWaypoint { */ public static Collection<Waypoint> parseWaypointsFromNote(@NonNull final String initialNote) { final List<Waypoint> waypoints = new LinkedList<>(); - final Pattern COORDPATTERN = Pattern.compile("\\b[nNsS]{1}\\s*\\d"); // begin of coordinates String note = initialNote; - MatcherWrapper matcher = new MatcherWrapper(COORDPATTERN, note); + MatcherWrapper matcher = new MatcherWrapper(PATTERN_COORDS, note); int count = 1; while (matcher.find()) { try { @@ -321,12 +309,11 @@ public class Waypoint implements IWaypoint { waypoints.add(waypoint); count++; } - } catch (final Geopoint.ParseException e) { - // ignore + } catch (final Geopoint.ParseException ignore) { } note = note.substring(matcher.start() + 1); - matcher = new MatcherWrapper(COORDPATTERN, note); + matcher = new MatcherWrapper(PATTERN_COORDS, note); } return waypoints; } diff --git a/main/src/cgeo/geocaching/activity/AbstractActivity.java b/main/src/cgeo/geocaching/activity/AbstractActivity.java index 4fe750a..603211e 100644 --- a/main/src/cgeo/geocaching/activity/AbstractActivity.java +++ b/main/src/cgeo/geocaching/activity/AbstractActivity.java @@ -96,9 +96,9 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs return super.onOptionsItemSelected(item); } - public void onResume(final Subscription resumeSubscription) { + public void onResume(final Subscription... resumeSubscriptions) { super.onResume(); - this.resumeSubscription = resumeSubscription; + this.resumeSubscription = Subscriptions.from(resumeSubscriptions); } @Override @@ -216,7 +216,7 @@ public abstract class AbstractActivity extends ActionBarActivity implements IAbs } protected void initializeAndroidBeam(final ActivitySharingInterface sharingInterface) { - if (Build.VERSION.SDK_INT > Build.VERSION_CODES.ICE_CREAM_SANDWICH) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH) { initializeICSAndroidBeam(sharingInterface); } } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java index 6c6ffda..f31d175 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/AbstractRadarApp.java @@ -9,6 +9,9 @@ import android.content.Intent; public abstract class AbstractRadarApp extends AbstractPointNavigationApp { + protected static final String RADAR_EXTRA_LONGITUDE = "longitude"; + protected static final String RADAR_EXTRA_LATITUDE = "latitude"; + private final String intentAction; protected AbstractRadarApp(final String name, final int id, final String intent, final String packageName) { diff --git a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java index 03d2220..743ce1f 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/CompassApp.java @@ -20,19 +20,19 @@ class CompassApp extends AbstractPointNavigationApp { } @Override - public void navigate(Activity activity, Geopoint coords) { - CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords, null); + public void navigate(final Activity activity, final Geopoint coords) { + CompassActivity.startActivity(activity, getString(R.string.navigation_direct_navigation), getString(R.string.navigation_target), coords); } @Override - public void navigate(Activity activity, Waypoint waypoint) { - CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), null, + public void navigate(final Activity activity, final Waypoint waypoint) { + CompassActivity.startActivity(activity, waypoint.getPrefix() + "/" + waypoint.getLookup(), waypoint.getName(), waypoint.getCoords(), waypoint.getWaypointType().getL10n()); } @Override - public void navigate(Activity activity, Geocache cache) { - CompassActivity.startActivity(activity, cache); + public void navigate(final Activity activity, final Geocache cache) { + CompassActivity.startActivityCache(activity, cache); } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java index 4924786..f5ccef4 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/GoogleMapsDirectionApp.java @@ -1,10 +1,10 @@ package cgeo.geocaching.apps.cache.navi; import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.R; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.maps.MapProviderFactory; +import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.utils.Log; import android.app.Activity; @@ -23,15 +23,13 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp { } @Override - public void navigate(Activity activity, Geopoint coords) { + public void navigate(final Activity activity, final Geopoint coords) { try { - IGeoData geo = CgeoApplication.getInstance().currentGeo(); - final Geopoint coordsNow = geo == null ? null : geo.getCoords(); - - if (coordsNow != null) { + final IGeoData geo = CgeoApplication.getInstance().currentGeo(); + if (geo.getCoords() != null) { activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri .parse("http://maps.google.com/maps?f=d&saddr=" - + coordsNow.getLatitude() + "," + coordsNow.getLongitude() + "&daddr=" + + geo.getCoords().getLatitude() + "," + geo.getCoords().getLongitude() + "&daddr=" + coords.getLatitude() + "," + coords.getLongitude()))); } else { activity.startActivity(new Intent(Intent.ACTION_VIEW, Uri @@ -39,7 +37,7 @@ public class GoogleMapsDirectionApp extends AbstractPointNavigationApp { + coords.getLatitude() + "," + coords.getLongitude()))); } - } catch (Exception e) { + } catch (final Exception e) { Log.i("GoogleMapsDirectionApp: application not available.", e); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java index 5d645f7..4dbfadd 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/OruxMapsApp.java @@ -8,6 +8,8 @@ import android.content.Intent; class OruxMapsApp extends AbstractPointNavigationApp { + private static final String ORUXMAPS_EXTRA_LONGITUDE = "longitude"; + private static final String ORUXMAPS_EXTRA_LATITUDE = "latitude"; private static final String INTENT = "com.oruxmaps.VIEW_MAP_ONLINE"; OruxMapsApp() { @@ -17,8 +19,8 @@ class OruxMapsApp extends AbstractPointNavigationApp { @Override public void navigate(Activity activity, Geopoint point) { final Intent intent = new Intent(INTENT); - intent.putExtra("latitude", point.getLatitude());//latitude, wgs84 datum - intent.putExtra("longitude", point.getLongitude());//longitude, wgs84 datum + intent.putExtra(ORUXMAPS_EXTRA_LATITUDE, point.getLatitude());//latitude, wgs84 datum + intent.putExtra(ORUXMAPS_EXTRA_LONGITUDE, point.getLongitude());//longitude, wgs84 datum activity.startActivity(intent); } diff --git a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java index ac83085..a12a38e 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/PebbleApp.java @@ -1,26 +1,26 @@ -package cgeo.geocaching.apps.cache.navi;
-
-import cgeo.geocaching.R;
-import cgeo.geocaching.geopoint.Geopoint;
-
-import android.content.Intent;
-
-/**
- * Application for communication with the Pebble watch.
- *
- */
-class PebbleApp extends AbstractRadarApp {
-
- private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO";
- private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc";
-
- PebbleApp() {
- super(getString(R.string.cache_menu_pebble), R.id.cache_app_pebble, INTENT, PACKAGE_NAME);
- }
-
- @Override
- protected void addCoordinates(final Intent intent, final Geopoint coords) {
- intent.putExtra("latitude", coords.getLatitude());
- intent.putExtra("longitude", coords.getLongitude());
- }
+package cgeo.geocaching.apps.cache.navi; + +import cgeo.geocaching.R; +import cgeo.geocaching.geopoint.Geopoint; + +import android.content.Intent; + +/** + * Application for communication with the Pebble watch. + * + */ +class PebbleApp extends AbstractRadarApp { + + private static final String INTENT = "com.webmajstr.pebble_gc.NAVIGATE_TO"; + private static final String PACKAGE_NAME = "com.webmajstr.pebble_gc"; + + PebbleApp() { + super(getString(R.string.cache_menu_pebble), R.id.cache_app_pebble, INTENT, PACKAGE_NAME); + } + + @Override + protected void addCoordinates(final Intent intent, final Geopoint coords) { + intent.putExtra(RADAR_EXTRA_LATITUDE, coords.getLatitude()); + intent.putExtra(RADAR_EXTRA_LONGITUDE, coords.getLongitude()); + } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java index 41cf2d8..0ee512b 100644 --- a/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java +++ b/main/src/cgeo/geocaching/apps/cache/navi/RadarApp.java @@ -16,8 +16,8 @@ class RadarApp extends AbstractRadarApp { @Override protected void addCoordinates(final Intent intent, final Geopoint coords) { - intent.putExtra("latitude", (float) coords.getLatitude()); - intent.putExtra("longitude", (float) coords.getLongitude()); + intent.putExtra(RADAR_EXTRA_LATITUDE, (float) coords.getLatitude()); + intent.putExtra(RADAR_EXTRA_LONGITUDE, (float) coords.getLongitude()); } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java b/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java deleted file mode 100644 index a6d7e9b..0000000 --- a/main/src/cgeo/geocaching/concurrent/BlockingThreadPool.java +++ /dev/null @@ -1,57 +0,0 @@ -package cgeo.geocaching.concurrent; - - -import java.util.concurrent.ArrayBlockingQueue; -import java.util.concurrent.BlockingQueue; -import java.util.concurrent.ThreadFactory; -import java.util.concurrent.ThreadPoolExecutor; -import java.util.concurrent.TimeUnit; - -/** - * BlockingThreadPool restricts the amount of parallel threads executing Runnables. - */ -public class BlockingThreadPool { - /** The queue holding the Runnable. **/ - private BlockingQueue<Runnable> queue = null; - /** The Executor. **/ - private ThreadPoolExecutor executor; - - /** - * Creates a ThreadPool with a given maximum of parallel threads running. - * Idle threads will be stopped until new threads are added. - * - * @param poolSize - * Maximum amout of parallel Threads - * @param priority - * The Thread priority e.g. Thread.MIN_PRIORITY - */ - public BlockingThreadPool(int poolSize, int priority) { - ThreadFactory threadFactory = new PriorityThreadFactory(priority); - this.queue = new ArrayBlockingQueue<>(poolSize, true); - this.executor = new ThreadPoolExecutor(0, poolSize, 5, TimeUnit.SECONDS, this.queue); - this.executor.setThreadFactory(threadFactory); - } - - /** - * Add a runnable to the pool. This will start the core threads in the underlying - * executor and try to add the Runnable to the pool. This method waits until timeout - * if no free thread is available. - * - * @param task - * The Runnable to add to the pool - * @param timeout - * The timeout to wait for a free thread - * @param unit - * The timeout unit - * @return true/false successful added - * @throws InterruptedException - * Operation was interrupted - */ - public boolean add(Runnable task, int timeout, TimeUnit unit) throws InterruptedException { - this.executor.setCorePoolSize(this.executor.getMaximumPoolSize()); - this.executor.prestartAllCoreThreads(); - boolean successfull = this.queue.offer(task, timeout, unit); - this.executor.setCorePoolSize(0); - return successfull; - } -} diff --git a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java b/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java deleted file mode 100644 index 0da198b..0000000 --- a/main/src/cgeo/geocaching/concurrent/PriorityThreadFactory.java +++ /dev/null @@ -1,25 +0,0 @@ -package cgeo.geocaching.concurrent; - -import org.eclipse.jdt.annotation.NonNull; - -import java.util.concurrent.ThreadFactory; - -/** - * Helper class for setting Thread priority in ThreadPool. - */ -public class PriorityThreadFactory implements ThreadFactory { - private int priority; - - public PriorityThreadFactory(int priority) { - this.priority = priority; - } - - @NonNull - @Override - public Thread newThread(Runnable r) { - Thread result = new Thread(r); - result.setPriority(this.priority); - return result; - } - -} diff --git a/main/src/cgeo/geocaching/connector/AbstractConnector.java b/main/src/cgeo/geocaching/connector/AbstractConnector.java index 9729e06..a929e2b 100644 --- a/main/src/cgeo/geocaching/connector/AbstractConnector.java +++ b/main/src/cgeo/geocaching/connector/AbstractConnector.java @@ -105,9 +105,9 @@ public abstract class AbstractConnector implements IConnector { return null; } - protected static boolean isNumericId(final String string) { + protected static boolean isNumericId(final String str) { try { - return Integer.parseInt(string) > 0; + return Integer.parseInt(str) > 0; } catch (NumberFormatException e) { } return false; @@ -295,4 +295,6 @@ public abstract class AbstractConnector implements IConnector { return actions; } + public void logout() { + } } diff --git a/main/src/cgeo/geocaching/connector/capability/ILogin.java b/main/src/cgeo/geocaching/connector/capability/ILogin.java index 4a839c8..b8b4975 100644 --- a/main/src/cgeo/geocaching/connector/capability/ILogin.java +++ b/main/src/cgeo/geocaching/connector/capability/ILogin.java @@ -23,6 +23,11 @@ public interface ILogin extends IConnector { boolean login(Handler handler, Context fromActivity); /** + * Log out of the connector if possible. + */ + void logout(); + + /** * Returns the status of the last {@link}login() request * * @return diff --git a/main/src/cgeo/geocaching/connector/ec/ECApi.java b/main/src/cgeo/geocaching/connector/ec/ECApi.java index 421d112..3e7472e 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECApi.java +++ b/main/src/cgeo/geocaching/connector/ec/ECApi.java @@ -14,17 +14,18 @@ import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.list.StoredList; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.SynchronizedDateFormat; import ch.boye.httpclientandroidlib.HttpResponse; +import com.fasterxml.jackson.databind.JsonNode; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; +import java.io.IOException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collection; @@ -161,7 +162,7 @@ public class ECApi { } try { - return new GPX10Parser(StoredList.TEMPORARY_LIST_ID).parse(response.getEntity().getContent(), null); + return new GPX10Parser(StoredList.TEMPORARY_LIST.id).parse(response.getEntity().getContent(), null); } catch (Exception e) { Log.e("Error importing gpx from extremcaching.com", e); return Collections.emptyList(); @@ -171,46 +172,44 @@ public class ECApi { private static List<Geocache> importCachesFromJSON(final HttpResponse response) { if (response != null) { try { - final String data = Network.getResponseDataAlways(response); - if (StringUtils.isBlank(data) || StringUtils.equals(data, "[]")) { + final JsonNode json = JsonUtils.reader.readTree(Network.getResponseDataAlways(response)); + if (!json.isArray()) { return Collections.emptyList(); } - final JSONArray json = new JSONArray(data); - final int len = json.length(); - final List<Geocache> caches = new ArrayList<>(len); - for (int i = 0; i < len; i++) { - final Geocache cache = parseCache(json.getJSONObject(i)); + final List<Geocache> caches = new ArrayList<>(json.size()); + for (final JsonNode node: json) { + final Geocache cache = parseCache(node); if (cache != null) { caches.add(cache); } } return caches; - } catch (final JSONException e) { - Log.w("JSONResult", e); + } catch (IOException | ClassCastException e) { + Log.w("importCachesFromJSON", e); } } return Collections.emptyList(); } - private static Geocache parseCache(final JSONObject response) { - final Geocache cache = new Geocache(); - cache.setReliableLatLon(true); + private static Geocache parseCache(final JsonNode response) { try { - cache.setGeocode("EC" + response.getString("cache_id")); - cache.setName(response.getString("title")); - cache.setCoords(new Geopoint(response.getString("lat"), response.getString("lon"))); - cache.setType(getCacheType(response.getString("type"))); - cache.setDifficulty((float) response.getDouble("difficulty")); - cache.setTerrain((float) response.getDouble("terrain")); - cache.setSize(CacheSize.getById(response.getString("size"))); - cache.setFound(response.getInt("found") == 1); + final Geocache cache = new Geocache(); + cache.setReliableLatLon(true); + cache.setGeocode("EC" + response.get("cache_id").asText()); + cache.setName(response.get("title").asText()); + cache.setCoords(new Geopoint(response.get("lat").asText(), response.get("lon").asText())); + cache.setType(getCacheType(response.get("type").asText())); + cache.setDifficulty((float) response.get("difficulty").asDouble()); + cache.setTerrain((float) response.get("terrain").asDouble()); + cache.setSize(CacheSize.getById(response.get("size").asText())); + cache.setFound(response.get("found").asInt() == 1); DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE)); - } catch (final JSONException e) { + return cache; + } catch (final NullPointerException e) { Log.e("ECApi.parseCache", e); return null; } - return cache; } private static CacheType getCacheType(final String cacheType) { diff --git a/main/src/cgeo/geocaching/connector/ec/ECLogin.java b/main/src/cgeo/geocaching/connector/ec/ECLogin.java index 012bdc9..35c2db4 100644 --- a/main/src/cgeo/geocaching/connector/ec/ECLogin.java +++ b/main/src/cgeo/geocaching/connector/ec/ECLogin.java @@ -7,14 +7,18 @@ import cgeo.geocaching.enumerations.StatusCode; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.Log; import ch.boye.httpclientandroidlib.HttpResponse; + +import com.fasterxml.jackson.databind.JsonNode; + import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.Nullable; -import org.json.JSONException; -import org.json.JSONObject; + +import java.io.IOException; public class ECLogin extends AbstractLogin { @@ -26,7 +30,7 @@ public class ECLogin extends AbstractLogin { } private static class SingletonHolder { - private static ECLogin INSTANCE = new ECLogin(); + private final static ECLogin INSTANCE = new ECLogin(); } public static ECLogin getInstance() { @@ -93,18 +97,18 @@ public class ECLogin extends AbstractLogin { setActualStatus(app.getString(R.string.init_login_popup_ok)); try { - final JSONObject json = new JSONObject(data); + final JsonNode json = JsonUtils.reader.readTree(data); - final String sid = json.getString("sid"); + final String sid = json.get("sid").asText(); if (!StringUtils.isBlank(sid)) { sessionId = sid; setActualLoginStatus(true); - setActualUserName(json.getString("username")); - setActualCachesFound(json.getInt("found")); + setActualUserName(json.get("username").asText()); + setActualCachesFound(json.get("found").asInt()); return true; } resetLoginStatus(); - } catch (final JSONException e) { + } catch (IOException | NullPointerException e) { Log.e("ECLogin.getLoginStatus", e); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConnector.java b/main/src/cgeo/geocaching/connector/gc/GCConnector.java index 3b7c31e..4512979 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConnector.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConnector.java @@ -344,6 +344,11 @@ public class GCConnector extends AbstractConnector implements ISearchByGeocode, } @Override + public void logout() { + GCLogin.getInstance().logout(); + } + + @Override public String getUserName() { return GCLogin.getInstance().getActualUserName(); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCConstants.java b/main/src/cgeo/geocaching/connector/gc/GCConstants.java index 3f16156..c2021bb 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCConstants.java +++ b/main/src/cgeo/geocaching/connector/gc/GCConstants.java @@ -45,7 +45,6 @@ public final class GCConstants { public final static Pattern PATTERN_OWNER_USERID = Pattern.compile("other caches <a href=\"/seek/nearest\\.aspx\\?u=(.*?)\">hidden</a> or"); public final static Pattern PATTERN_FOUND = Pattern.compile("ctl00_ContentBody_GeoNav_logText\">(Found It|Attended)"); public final static Pattern PATTERN_FOUND_ALTERNATIVE = Pattern.compile("<div class=\"StatusInformationWidget FavoriteWidget\""); - public final static Pattern PATTERN_FOUND_DATE = Pattern.compile(">Logged on: ([^<]+?)<"); public final static Pattern PATTERN_OWNER_DISPLAYNAME = Pattern.compile("<div id=\"ctl00_ContentBody_mcd1\">[^<]+<a href=\"[^\"]+\">([^<]+)</a>"); public final static Pattern PATTERN_TYPE = Pattern.compile("<a href=\"/seek/nearest.aspx\\?tx=([0-9a-f-]+)"); public final static Pattern PATTERN_HIDDEN = Pattern.compile("<div id=\"ctl00_ContentBody_mcd2\">\\W*Hidden[\\s:]*([^<]+?)</div>"); diff --git a/main/src/cgeo/geocaching/connector/gc/GCLogin.java b/main/src/cgeo/geocaching/connector/gc/GCLogin.java index 9f430c0..16f20b8 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCLogin.java +++ b/main/src/cgeo/geocaching/connector/gc/GCLogin.java @@ -175,6 +175,9 @@ public class GCLogin extends AbstractLogin { return StatusCode.NO_ERROR; } + private static String removeDotAndComma(final String str) { + return StringUtils.replaceChars(str, ".,", null); + } /** * Check if the user has been logged in when he retrieved the data. @@ -201,7 +204,7 @@ public class GCLogin extends AbstractLogin { setActualUserName(TextUtils.getMatch(page, GCConstants.PATTERN_LOGIN_NAME, true, "???")); int cachesCount = 0; try { - cachesCount = Integer.parseInt(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0").replaceAll("[,.]", "")); + cachesCount = Integer.parseInt(removeDotAndComma(TextUtils.getMatch(page, GCConstants.PATTERN_CACHES_FOUND, true, "0"))); } catch (final NumberFormatException e) { Log.e("getLoginStatus: bad cache count", e); } @@ -266,7 +269,7 @@ public class GCLogin extends AbstractLogin { Settings.setGCMemberStatus(GCConstants.MEMBER_STATUS_PM); } - setActualCachesFound(Integer.parseInt(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1").replaceAll("[,.]", ""))); + setActualCachesFound(Integer.parseInt(removeDotAndComma(TextUtils.getMatch(profile, GCConstants.PATTERN_CACHES_FOUND, true, "-1")))); final String avatarURL = TextUtils.getMatch(profile, GCConstants.PATTERN_AVATAR_IMAGE_PROFILE_PAGE, false, null); if (avatarURL != null) { diff --git a/main/src/cgeo/geocaching/connector/gc/GCMap.java b/main/src/cgeo/geocaching/connector/gc/GCMap.java index 27ce06e..bacaddb 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCMap.java +++ b/main/src/cgeo/geocaching/connector/gc/GCMap.java @@ -9,6 +9,7 @@ import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LiveMapStrategy.Strategy; import cgeo.geocaching.enumerations.LiveMapStrategy.StrategyFlag; import cgeo.geocaching.enumerations.StatusCode; +import cgeo.geocaching.files.ParserException; import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter.Format; import cgeo.geocaching.geopoint.Units; @@ -16,20 +17,23 @@ import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Formatter; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.LeastRecentlyUsedMap; import cgeo.geocaching.utils.Log; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringUtils; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import rx.Observable; import rx.functions.Func2; import android.graphics.Bitmap; +import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Arrays; @@ -60,53 +64,41 @@ public class GCMap { // {"name":"HP: Hannover - Sahlkamp","gc":"GC2Q97X","g":"a09149ca-00e0-4aa2-b332-db2b4dfb18d2","available":true,"archived":false,"subrOnly":false,"li":false,"fp":"0","difficulty":{"text":1.0,"value":"1"},"terrain":{"text":1.5,"value":"1_5"},"hidden":"5/29/2011","container":{"text":"Small","value":"small.gif"},"type":{"text":"Traditional Cache","value":2},"owner":{"text":"GeoM@n","value":"1deaa69e-6bcc-421d-95a1-7d32b468cb82"}}] // } - final JSONObject json = new JSONObject(data); - final String status = json.getString("status"); + final ObjectNode json = (ObjectNode) JsonUtils.reader.readTree(data); + final String status = json.path("status").asText(); if (StringUtils.isBlank(status)) { - - throw new JSONException("No status inside JSON"); + throw new ParserException("No status inside JSON"); } if ("success".compareTo(status) != 0) { - throw new JSONException("Wrong status inside JSON"); + throw new ParserException("Wrong status inside JSON"); } - final JSONArray dataArray = json.getJSONArray("data"); + final ArrayNode dataArray = (ArrayNode) json.get("data"); if (dataArray == null) { - throw new JSONException("No data inside JSON"); + throw new ParserException("No data inside JSON"); } final ArrayList<Geocache> caches = new ArrayList<>(); - for (int j = 0; j < dataArray.length(); j++) { + for (final JsonNode dataObject: dataArray) { final Geocache cache = new Geocache(); - - JSONObject dataObject = dataArray.getJSONObject(j); - cache.setName(dataObject.getString("name")); - cache.setGeocode(dataObject.getString("gc")); - cache.setGuid(dataObject.getString("g")); // 34c2e609-5246-4f91-9029-d6c02b0f2a82" - cache.setDisabled(!dataObject.getBoolean("available")); - cache.setArchived(dataObject.getBoolean("archived")); - cache.setPremiumMembersOnly(dataObject.getBoolean("subrOnly")); + cache.setName(dataObject.path("name").asText()); + cache.setGeocode(dataObject.path("gc").asText()); + cache.setGuid(dataObject.path("g").asText()); // 34c2e609-5246-4f91-9029-d6c02b0f2a82" + cache.setDisabled(!dataObject.path("available").asBoolean()); + cache.setArchived(dataObject.path("archived").asBoolean()); + cache.setPremiumMembersOnly(dataObject.path("subrOnly").asBoolean()); // "li" seems to be "false" always - cache.setFavoritePoints(Integer.parseInt(dataObject.getString("fp"))); - JSONObject difficultyObj = dataObject.getJSONObject("difficulty"); - cache.setDifficulty(Float.parseFloat(difficultyObj.getString("text"))); // 3.5 - JSONObject terrainObj = dataObject.getJSONObject("terrain"); - cache.setTerrain(Float.parseFloat(terrainObj.getString("text"))); // 1.5 - cache.setHidden(GCLogin.parseGcCustomDate(dataObject.getString("hidden"), "MM/dd/yyyy")); // 7/23/2001 - JSONObject containerObj = dataObject.getJSONObject("container"); - cache.setSize(CacheSize.getById(containerObj.getString("text"))); // Regular - JSONObject typeObj = dataObject.getJSONObject("type"); - cache.setType(CacheType.getByPattern(typeObj.getString("text"))); // Traditional Cache - JSONObject ownerObj = dataObject.getJSONObject("owner"); - cache.setOwnerDisplayName(ownerObj.getString("text")); + cache.setFavoritePoints(Integer.parseInt(dataObject.path("fp").asText())); + cache.setDifficulty(Float.parseFloat(dataObject.path("difficulty").path("text").asText())); // 3.5 + cache.setTerrain(Float.parseFloat(dataObject.path("terrain").path("text").asText())); // 1.5 + cache.setHidden(GCLogin.parseGcCustomDate(dataObject.path("hidden").asText(), "MM/dd/yyyy")); // 7/23/2001 + cache.setSize(CacheSize.getById(dataObject.path("container").path("text").asText())); // Regular + cache.setType(CacheType.getByPattern(dataObject.path("type").path("text").asText())); // Traditional Cache + cache.setOwnerDisplayName(dataObject.path("owner").path("text").asText()); caches.add(cache); } result.addAndPutInCache(caches); - } catch (JSONException e) { - result.setError(StatusCode.UNKNOWN_ERROR); - } catch (ParseException e) { - result.setError(StatusCode.UNKNOWN_ERROR); - } catch (NumberFormatException e) { + } catch (ParserException | ParseException | IOException | NumberFormatException ignore) { result.setError(StatusCode.UNKNOWN_ERROR); } return result; @@ -125,7 +117,7 @@ public class GCMap { final LeastRecentlyUsedMap<String, String> nameCache = new LeastRecentlyUsedMap.LruCache<>(2000); // JSON id, cache name if (StringUtils.isEmpty(data)) { - throw new JSONException("No page given"); + throw new ParserException("No page given"); } // Example JSON information @@ -134,34 +126,33 @@ public class GCMap { // "data":{"55_55":[{"i":"gEaR","n":"Spiel & Sport"}],"55_54":[{"i":"gEaR","n":"Spiel & Sport"}],"17_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"55_53":[{"i":"gEaR","n":"Spiel & Sport"}],"17_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"17_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"57_53":[{"i":"gEaR","n":"Spiel & Sport"}],"57_55":[{"i":"gEaR","n":"Spiel & Sport"}],"3_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"3_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"57_54":[{"i":"gEaR","n":"Spiel & Sport"}],"3_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"15_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"15_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"15_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"4_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"4_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"4_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"16_25":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"16_26":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"16_27":[{"i":"Rkzt","n":"EDSSW: Rathaus "}],"2_62":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"2_60":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"2_61":[{"i":"gOWz","n":"Baumarktserie - Wer Wo Was -"}],"56_53":[{"i":"gEaR","n":"Spiel & Sport"}],"56_54":[{"i":"gEaR","n":"Spiel & Sport"}],"56_55":[{"i":"gEaR","n":"Spiel & Sport"}]} // } - final JSONObject json = new JSONObject(data); + final ObjectNode json = (ObjectNode) JsonUtils.reader.readTree(data); - final JSONArray grid = json.getJSONArray("grid"); - if (grid == null || grid.length() != (UTFGrid.GRID_MAXY + 1)) { - throw new JSONException("No grid inside JSON"); + final ArrayNode grid = (ArrayNode) json.get("grid"); + if (grid == null || grid.size() != (UTFGrid.GRID_MAXY + 1)) { + throw new ParserException("No grid inside JSON"); } - final JSONArray keys = json.getJSONArray("keys"); + final ArrayNode keys = (ArrayNode) json.get("keys"); if (keys == null) { - throw new JSONException("No keys inside JSON"); + throw new ParserException("No keys inside JSON"); } - final JSONObject dataObject = json.getJSONObject("data"); + final ObjectNode dataObject = (ObjectNode) json.get("data"); if (dataObject == null) { - throw new JSONException("No data inside JSON"); + throw new ParserException("No data inside JSON"); } // iterate over the data and construct all caches in this tile Map<String, List<UTFGridPosition>> positions = new HashMap<>(); // JSON id as key Map<String, List<UTFGridPosition>> singlePositions = new HashMap<>(); // JSON id as key - for (int i = 1; i < keys.length(); i++) { // index 0 is empty - String key = keys.getString(i); - if (StringUtils.isNotBlank(key)) { + for (final JsonNode rawKey: keys) { + final String key = rawKey.asText(); + if (StringUtils.isNotBlank(key)) { // index 0 is empty UTFGridPosition pos = UTFGridPosition.fromString(key); - JSONArray dataForKey = dataObject.getJSONArray(key); - for (int j = 0; j < dataForKey.length(); j++) { - JSONObject cacheInfo = dataForKey.getJSONObject(j); - String id = cacheInfo.getString("i"); - nameCache.put(id, cacheInfo.getString("n")); + final ArrayNode dataForKey = (ArrayNode) dataObject.get(key); + for (final JsonNode cacheInfo: dataForKey) { + final String id = cacheInfo.get("i").asText(); + nameCache.put(id, cacheInfo.get("n").asText()); List<UTFGridPosition> listOfPositions = positions.get(id); List<UTFGridPosition> singleListOfPositions = singlePositions.get(id); @@ -174,7 +165,7 @@ public class GCMap { } listOfPositions.add(pos); - if (dataForKey.length() == 1) { + if (dataForKey.size() == 1) { singleListOfPositions.add(pos); } @@ -220,9 +211,7 @@ public class GCMap { searchResult.addAndPutInCache(caches); Log.d("Retrieved " + searchResult.getCount() + " caches for tile " + tile.toString()); - } catch (RuntimeException e) { - Log.e("GCMap.parseMapJSON", e); - } catch (JSONException e) { + } catch (RuntimeException | ParserException | IOException e) { Log.e("GCMap.parseMapJSON", e); } diff --git a/main/src/cgeo/geocaching/connector/gc/GCParser.java b/main/src/cgeo/geocaching/connector/gc/GCParser.java index c771049..6919173 100644 --- a/main/src/cgeo/geocaching/connector/gc/GCParser.java +++ b/main/src/cgeo/geocaching/connector/gc/GCParser.java @@ -30,6 +30,8 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.ui.DirectionImage; import cgeo.geocaching.utils.CancellableHandler; +import cgeo.geocaching.utils.HtmlUtils; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.MatcherWrapper; import cgeo.geocaching.utils.RxUtils; @@ -38,15 +40,16 @@ import cgeo.geocaching.utils.TextUtils; import ch.boye.httpclientandroidlib.HttpResponse; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.lang3.StringEscapeUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import rx.Observable; import rx.Observable.OnSubscribe; @@ -59,6 +62,7 @@ import android.net.Uri; import android.text.Html; import java.io.File; +import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; @@ -75,6 +79,7 @@ import java.util.regex.Pattern; public abstract class GCParser { private final static SynchronizedDateFormat dateTbIn1 = new SynchronizedDateFormat("EEEEE, dd MMMMM yyyy", Locale.ENGLISH); // Saturday, 28 March 2009 private final static SynchronizedDateFormat dateTbIn2 = new SynchronizedDateFormat("EEEEE, MMMMM dd, yyyy", Locale.ENGLISH); // Saturday, March 28, 2009 + private final static ImmutablePair<StatusCode, Geocache> UNKNOWN_PARSE_ERROR = ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); private static SearchResult parseSearch(final String url, final String pageContent, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(pageContent)) { @@ -125,12 +130,12 @@ public abstract class GCParser { page = page.substring(startPos + 1, endPos - startPos + 1); // cut between <table> and </table> - final String[] rows = page.split("<tr class="); - final int rows_count = rows.length; + final String[] rows = StringUtils.splitByWholeSeparator(page, "<tr class="); + final int rowsCount = rows.length; int excludedCaches = 0; final ArrayList<Geocache> caches = new ArrayList<>(); - for (int z = 1; z < rows_count; z++) { + for (int z = 1; z < rowsCount; z++) { final Geocache cache = new Geocache(); final String row = rows[z]; @@ -161,7 +166,7 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse GUID and/or Disabled - Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data"); + Log.w("GCParser.parseSearch: Failed to parse GUID and/or Disabled data", e); } if (Settings.isExcludeDisabledCaches() && (cache.isDisabled() || cache.isArchived())) { @@ -173,11 +178,11 @@ public abstract class GCParser { cache.setGeocode(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_GEOCODE, true, 1, cache.getGeocode(), true)); // cache type - cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, true, 1, null, true))); + cache.setType(CacheType.getByPattern(TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_TYPE, null))); // cache direction - image if (Settings.getLoadDirImg()) { - final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, 1, null, false); + final String direction = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_DIRECTION_DISTANCE, false, null); if (direction != null) { cache.setDirectionImg(direction); } @@ -204,19 +209,19 @@ public abstract class GCParser { } // size - final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, 1, null, false); + final String container = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_CONTAINER, false, null); cache.setSize(CacheSize.getById(container)); // date hidden, makes sorting event caches easier - final String dateHidden = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_HIDDEN_DATE, false, 1, null, false); + final String dateHidden = TextUtils.getMatch(row, GCConstants.PATTERN_SEARCH_HIDDEN_DATE, false, null); if (StringUtils.isNotBlank(dateHidden)) { try { - Date date = GCLogin.parseGcCustomDate(dateHidden); + final Date date = GCLogin.parseGcCustomDate(dateHidden); if (date != null) { cache.setHidden(date); } - } catch (ParseException e) { - Log.e("Error parsing event date from search"); + } catch (final ParseException e) { + Log.e("Error parsing event date from search", e); } } @@ -266,7 +271,7 @@ public abstract class GCParser { cache.setFavoritePoints(Integer.parseInt(result)); } } catch (final NumberFormatException e) { - Log.w("GCParser.parseSearch: Failed to parse favorite count"); + Log.w("GCParser.parseSearch: Failed to parse favorite count", e); } caches.add(cache); @@ -280,7 +285,7 @@ public abstract class GCParser { searchResult.setTotalCountGC(Integer.parseInt(result) - excludedCaches); } } catch (final NumberFormatException e) { - Log.w("GCParser.parseSearch: Failed to parse cache count"); + Log.w("GCParser.parseSearch: Failed to parse cache count", e); } String recaptchaText = null; @@ -380,17 +385,20 @@ public abstract class GCParser { * Parse cache from text and return either an error code or a cache object in a pair. Note that inline logs are * not parsed nor saved, while the cache itself is. * - * @param pageIn the page text to parse - * @param handler the handler to send the progress notifications to - * @return a pair, with a {@link StatusCode} on the left, and a non-nulll cache objet on the right - * iff the status code is {@link StatusCode.NO_ERROR}. + * @param pageIn + * the page text to parse + * @param handler + * the handler to send the progress notifications to + * @return a pair, with a {@link StatusCode} on the left, and a non-null cache object on the right + * iff the status code is {@link StatusCode.NO_ERROR}. */ + @NonNull static private ImmutablePair<StatusCode, Geocache> parseCacheFromText(final String pageIn, @Nullable final CancellableHandler handler) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_details); if (StringUtils.isBlank(pageIn)) { Log.e("GCParser.parseCache: No page given"); - return null; + return UNKNOWN_PARSE_ERROR; } if (pageIn.contains(GCConstants.STRING_UNPUBLISHED_OTHER) || pageIn.contains(GCConstants.STRING_UNPUBLISHED_FROM_SEARCH)) { @@ -403,12 +411,12 @@ public abstract class GCParser { final String cacheName = Html.fromHtml(TextUtils.getMatch(pageIn, GCConstants.PATTERN_NAME, true, "")).toString(); if (GCConstants.STRING_UNKNOWN_ERROR.equalsIgnoreCase(cacheName)) { - return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); + return UNKNOWN_PARSE_ERROR; } // first handle the content with line breaks, then trim everything for easier matching and reduced memory consumption in parsed fields String personalNoteWithLineBreaks = ""; - MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_PERSONALNOTE, pageIn); + final MatcherWrapper matcher = new MatcherWrapper(GCConstants.PATTERN_PERSONALNOTE, pageIn); if (matcher.find()) { personalNoteWithLineBreaks = matcher.group(1).trim(); } @@ -446,7 +454,7 @@ public abstract class GCParser { final int pos = tableInside.indexOf(GCConstants.STRING_CACHEDETAILS); if (pos == -1) { Log.e("GCParser.parseCache: ID \"cacheDetails\" not found on page"); - return null; + return UNKNOWN_PARSE_ERROR; } tableInside = tableInside.substring(pos); @@ -490,7 +498,7 @@ public abstract class GCParser { } } catch (final ParseException e) { // failed to parse cache hidden date - Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date"); + Log.w("GCParser.parseCache: Failed to parse cache hidden (event) date", e); } // favorite @@ -579,13 +587,13 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse cache attributes - Log.w("GCParser.parseCache: Failed to parse cache attributes"); + Log.w("GCParser.parseCache: Failed to parse cache attributes", e); } // cache spoilers try { if (CancellableHandler.isCancelled(handler)) { - return null; + return UNKNOWN_PARSE_ERROR; } CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_spoilers); @@ -608,7 +616,7 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse cache spoilers - Log.w("GCParser.parseCache: Failed to parse cache spoilers"); + Log.w("GCParser.parseCache: Failed to parse cache spoilers", e); } // cache inventory @@ -642,7 +650,7 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse cache inventory - Log.w("GCParser.parseCache: Failed to parse cache inventory (2)"); + Log.w("GCParser.parseCache: Failed to parse cache inventory (2)", e); } // cache logs counts @@ -664,7 +672,7 @@ public abstract class GCParser { } } catch (final NumberFormatException e) { // failed to parse logs - Log.w("GCParser.parseCache: Failed to parse cache log count"); + Log.w("GCParser.parseCache: Failed to parse cache log count", e); } // waypoints - reset collection @@ -680,13 +688,13 @@ public abstract class GCParser { cache.addOrChangeWaypoint(waypoint, false); cache.setUserModifiedCoords(true); } - } catch (final Geopoint.GeopointException e) { + } catch (final Geopoint.GeopointException ignored) { } int wpBegin = page.indexOf("<table class=\"Table\" id=\"ctl00_ContentBody_Waypoints\">"); if (wpBegin != -1) { // parse waypoints if (CancellableHandler.isCancelled(handler)) { - return null; + return UNKNOWN_PARSE_ERROR; } CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_waypoints); @@ -707,10 +715,10 @@ public abstract class GCParser { wpList = wpList.substring(wpBegin + 7, wpEnd); } - final String[] wpItems = wpList.split("<tr"); + final String[] wpItems = StringUtils.splitByWholeSeparator(wpList, "<tr"); for (int j = 1; j < wpItems.length; j++) { - String[] wp = wpItems[j].split("<td"); + String[] wp = StringUtils.splitByWholeSeparator(wpItems[j], "<td"); // waypoint name // res is null during the unit tests @@ -730,13 +738,12 @@ public abstract class GCParser { // waypoint latitude and longitude latlon = Html.fromHtml(TextUtils.getMatch(wp[7], GCConstants.PATTERN_WPPREFIXORLOOKUPORLATLON, false, 2, "", false)).toString().trim(); if (!StringUtils.startsWith(latlon, "???")) { - waypoint.setLatlon(latlon); waypoint.setCoords(new Geopoint(latlon)); } j++; if (wpItems.length > j) { - wp = wpItems[j].split("<td"); + wp = StringUtils.splitByWholeSeparator(wpItems[j], "<td"); } // waypoint note @@ -751,7 +758,7 @@ public abstract class GCParser { // last check for necessary cache conditions if (StringUtils.isBlank(cache.getGeocode())) { - return ImmutablePair.of(StatusCode.UNKNOWN_ERROR, null); + return UNKNOWN_PARSE_ERROR; } cache.setDetailedUpdatedNow(); @@ -762,7 +769,7 @@ public abstract class GCParser { return StringUtils.replaceChars(numberWithPunctuation, ".,", ""); } - public static SearchResult searchByNextPage(final SearchResult search, boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByNextPage(final SearchResult search, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (search == null) { return null; } @@ -845,7 +852,7 @@ public abstract class GCParser { * @return */ @Nullable - private static SearchResult searchByAny(final CacheType cacheType, final boolean my, final boolean showCaptcha, final Parameters params, RecaptchaReceiver recaptchaReceiver) { + private static SearchResult searchByAny(final CacheType cacheType, final boolean my, final boolean showCaptcha, final Parameters params, final RecaptchaReceiver recaptchaReceiver) { insertCacheType(params, cacheType); final String uri = "http://www.geocaching.com/seek/nearest.aspx"; @@ -871,12 +878,12 @@ public abstract class GCParser { return search; } - public static SearchResult searchByCoords(final @NonNull Geopoint coords, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByCoords(final @NonNull Geopoint coords, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { final Parameters params = new Parameters("lat", Double.toString(coords.getLatitude()), "lng", Double.toString(coords.getLongitude())); return searchByAny(cacheType, false, showCaptcha, params, recaptchaReceiver); } - public static SearchResult searchByKeyword(final @NonNull String keyword, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByKeyword(final @NonNull String keyword, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(keyword)) { Log.e("GCParser.searchByKeyword: No keyword given"); return null; @@ -894,7 +901,7 @@ public abstract class GCParser { return false; } - public static SearchResult searchByUsername(final String userName, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByUsername(final String userName, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(userName)) { Log.e("GCParser.searchByUsername: No user name given"); return null; @@ -905,7 +912,7 @@ public abstract class GCParser { return searchByAny(cacheType, isSearchForMyCaches(userName), showCaptcha, params, recaptchaReceiver); } - public static SearchResult searchByPocketQuery(final String pocketGuid, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByPocketQuery(final String pocketGuid, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(pocketGuid)) { Log.e("GCParser.searchByPocket: No guid name given"); return null; @@ -916,7 +923,7 @@ public abstract class GCParser { return searchByAny(cacheType, false, showCaptcha, params, recaptchaReceiver); } - public static SearchResult searchByOwner(final String userName, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByOwner(final String userName, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(userName)) { Log.e("GCParser.searchByOwner: No user name given"); return null; @@ -926,32 +933,28 @@ public abstract class GCParser { return searchByAny(cacheType, isSearchForMyCaches(userName), showCaptcha, params, recaptchaReceiver); } - public static SearchResult searchByAddress(final String address, final CacheType cacheType, final boolean showCaptcha, RecaptchaReceiver recaptchaReceiver) { + public static SearchResult searchByAddress(final String address, final CacheType cacheType, final boolean showCaptcha, final RecaptchaReceiver recaptchaReceiver) { if (StringUtils.isBlank(address)) { Log.e("GCParser.searchByAddress: No address given"); return null; } - try { - final JSONObject response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); - if (response == null) { - return null; - } - if (!StringUtils.equalsIgnoreCase(response.getString("status"), "success")) { - return null; - } - if (!response.has("data")) { - return null; - } - final JSONObject data = response.getJSONObject("data"); - if (data == null) { - return null; - } - return searchByCoords(new Geopoint(data.getDouble("lat"), data.getDouble("lng")), cacheType, showCaptcha, recaptchaReceiver); - } catch (final JSONException e) { - Log.w("GCParser.searchByAddress", e); + + final ObjectNode response = Network.requestJSON("http://www.geocaching.com/api/geocode", new Parameters("q", address)); + if (response == null) { + return null; } - return null; + if (!StringUtils.equalsIgnoreCase(response.path("status").asText(), "success")) { + return null; + } + + final JsonNode data = response.path("data"); + final JsonNode latNode = data.get("lat"); + final JsonNode lngNode = data.get("lng"); + if (latNode == null || lngNode == null) { + return null; + } + return searchByCoords(new Geopoint(latNode.asDouble(), lngNode.asDouble()), cacheType, showCaptcha, recaptchaReceiver); } @Nullable @@ -1001,13 +1004,13 @@ public abstract class GCParser { return null; } - String subPage = StringUtils.substringAfter(page, "class=\"PocketQueryListTable"); + final String subPage = StringUtils.substringAfter(page, "class=\"PocketQueryListTable"); if (StringUtils.isEmpty(subPage)) { Log.e("GCParser.searchPocketQueryList: class \"PocketQueryListTable\" not found on page"); return Collections.emptyList(); } - List<PocketQueryList> list = new ArrayList<>(); + final List<PocketQueryList> list = new ArrayList<>(); final MatcherWrapper matcherPocket = new MatcherWrapper(GCConstants.PATTERN_LIST_PQ, subPage); @@ -1015,7 +1018,7 @@ public abstract class GCParser { int maxCaches; try { maxCaches = Integer.parseInt(matcherPocket.group(1)); - } catch (NumberFormatException e) { + } catch (final NumberFormatException e) { maxCaches = 0; Log.e("GCParser.searchPocketQueryList: Unable to parse max caches", e); } @@ -1029,7 +1032,7 @@ public abstract class GCParser { Collections.sort(list, new Comparator<PocketQueryList>() { @Override - public int compare(PocketQueryList left, PocketQueryList right) { + public int compare(final PocketQueryList left, final PocketQueryList right) { return String.CASE_INSENSITIVE_ORDER.compare(left.getName(), right.getName()); } }); @@ -1483,7 +1486,7 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse trackable owner name - Log.w("GCParser.parseTrackable: Failed to parse trackable owner name"); + Log.w("GCParser.parseTrackable: Failed to parse trackable owner name", e); } // trackable origin @@ -1514,7 +1517,7 @@ public abstract class GCParser { } } catch (final RuntimeException e) { // failed to parse trackable last known place - Log.w("GCParser.parseTrackable: Failed to parse trackable last known place"); + Log.w("GCParser.parseTrackable: Failed to parse trackable last known place", e); } // released date - can be missing on the page @@ -1522,12 +1525,12 @@ public abstract class GCParser { if (releaseString != null) { try { trackable.setReleased(dateTbIn1.parse(releaseString)); - } catch (ParseException e) { + } catch (final ParseException ignore) { if (trackable.getReleased() == null) { try { trackable.setReleased(dateTbIn2.parse(releaseString)); - } catch (ParseException e1) { - Log.e("Could not parse trackable release " + releaseString); + } catch (final ParseException e) { + Log.e("Could not parse trackable release " + releaseString, e); } } } @@ -1545,7 +1548,7 @@ public abstract class GCParser { } // trackable goal - trackable.setGoal(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal()))); + trackable.setGoal(HtmlUtils.removeExtraParagraph(convertLinks(TextUtils.getMatch(page, GCConstants.PATTERN_TRACKABLE_GOAL, true, trackable.getGoal())))); // trackable details & image try { @@ -1558,12 +1561,12 @@ public abstract class GCParser { trackable.setImage(StringUtils.replace(image, "/display/", "/large/")); } if (StringUtils.isNotEmpty(details) && !StringUtils.equals(details, "No additional details available.")) { - trackable.setDetails(convertLinks(details)); + trackable.setDetails(HtmlUtils.removeExtraParagraph(convertLinks(details))); } } } catch (final RuntimeException e) { // failed to parse trackable details & image - Log.w("GCParser.parseTrackable: Failed to parse trackable details & image"); + Log.w("GCParser.parseTrackable: Failed to parse trackable details & image", e); } if (StringUtils.isEmpty(trackable.getDetails()) && page.contains(GCConstants.ERROR_TB_NOT_ACTIVATED)) { trackable.setDetails(CgeoApplication.getInstance().getString(R.string.trackable_not_activated)); @@ -1585,7 +1588,7 @@ public abstract class GCParser { long date = 0; try { date = GCLogin.parseGcCustomDate(matcherLogs.group(2)).getTime(); - } catch (final ParseException e) { + } catch (final ParseException ignore) { } final LogEntry logDone = new LogEntry( @@ -1630,7 +1633,7 @@ public abstract class GCParser { return trackable; } - private static String convertLinks(String input) { + private static String convertLinks(final String input) { if (input == null) { return null; } @@ -1691,7 +1694,7 @@ public abstract class GCParser { Log.e("GCParser.loadLogsFromDetails: error " + statusCode + " when requesting log information"); return Observable.empty(); } - String rawResponse = Network.getResponseData(response); + final String rawResponse = Network.getResponseData(response); if (rawResponse == null) { Log.e("GCParser.loadLogsFromDetails: unable to read whole response"); return Observable.empty(); @@ -1712,56 +1715,51 @@ public abstract class GCParser { } try { - final JSONObject resp = new JSONObject(rawResponse); - if (!resp.getString("status").equals("success")) { - Log.e("GCParser.loadLogsFromDetails: status is " + resp.getString("status")); + final ObjectNode resp = (ObjectNode) JsonUtils.reader.readTree(rawResponse); + if (!resp.path("status").asText().equals("success")) { + Log.e("GCParser.loadLogsFromDetails: status is " + resp.path("status").asText("[absent]")); subscriber.onCompleted(); return; } - final JSONArray data = resp.getJSONArray("data"); - - for (int index = 0; index < data.length(); index++) { - final JSONObject entry = data.getJSONObject(index); - + final ArrayNode data = (ArrayNode) resp.get("data"); + for (final JsonNode entry: data) { // FIXME: use the "LogType" field instead of the "LogTypeImage" one. - final String logIconNameExt = entry.optString("LogTypeImage", ".gif"); + final String logIconNameExt = entry.path("LogTypeImage").asText(".gif"); final String logIconName = logIconNameExt.substring(0, logIconNameExt.length() - 4); long date = 0; try { - date = GCLogin.parseGcCustomDate(entry.getString("Visited")).getTime(); - } catch (final ParseException e) { - Log.e("GCParser.loadLogsFromDetails: failed to parse log date."); + date = GCLogin.parseGcCustomDate(entry.get("Visited").asText()).getTime(); + } catch (ParseException | NullPointerException e) { + Log.e("GCParser.loadLogsFromDetails: failed to parse log date", e); } // TODO: we should update our log data structure to be able to record // proper coordinates, and make them clickable. In the meantime, it is // better to integrate those coordinates into the text rather than not // display them at all. - final String latLon = entry.getString("LatLonString"); - final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.getString("LogText")); + final String latLon = entry.path("LatLonString").asText(); + final String logText = (StringUtils.isEmpty(latLon) ? "" : (latLon + "<br/><br/>")) + TextUtils.removeControlCharacters(entry.path("LogText").asText()); final LogEntry logDone = new LogEntry( - TextUtils.removeControlCharacters(entry.getString("UserName")), + TextUtils.removeControlCharacters(entry.path("UserName").asText()), date, LogType.getByIconName(logIconName), logText); - logDone.found = entry.getInt("GeocacheFindCount"); + logDone.found = entry.path("GeocacheFindCount").asInt(); logDone.friend = markAsFriendsLog; - final JSONArray images = entry.getJSONArray("Images"); - for (int i = 0; i < images.length(); i++) { - final JSONObject image = images.getJSONObject(i); - final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.getString("FileName"); - final String title = TextUtils.removeControlCharacters(image.getString("Name")); + final ArrayNode images = (ArrayNode) entry.get("Images"); + for (final JsonNode image: images) { + final String url = "http://imgcdn.geocaching.com/cache/log/large/" + image.path("FileName").asText(); + final String title = TextUtils.removeControlCharacters(image.path("Name").asText()); final Image logImage = new Image(url, title); logDone.addLogImage(logImage); } subscriber.onNext(logDone); } - } catch (final JSONException e) { - // failed to parse logs + } catch (final IOException e) { Log.w("GCParser.loadLogsFromDetails: Failed to parse cache logs", e); } subscriber.onCompleted(); @@ -1770,7 +1768,7 @@ public abstract class GCParser { } @NonNull - public static List<LogType> parseTypes(String page) { + public static List<LogType> parseTypes(final String page) { if (StringUtils.isEmpty(page)) { return Collections.emptyList(); } @@ -1865,16 +1863,11 @@ public abstract class GCParser { return; } + CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); final Observable<LogEntry> logs = getLogs(page, Logs.ALL); - Observable<LogEntry> specialLogs; - if (Settings.isFriendLogsWanted()) { - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - specialLogs = Observable.merge(getLogs(page, Logs.FRIENDS), - getLogs(page, Logs.OWN)); - } else { - CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_logs); - specialLogs = Observable.empty(); - } + final Observable<LogEntry> ownLogs = getLogs(page, Logs.OWN).cache(); + final Observable<LogEntry> specialLogs = Settings.isFriendLogsWanted() ? + Observable.merge(getLogs(page, Logs.FRIENDS), ownLogs) : Observable.<LogEntry>empty(); final Observable<List<LogEntry>> mergedLogs = Observable.zip(logs.toList(), specialLogs.toList(), new Func2<List<LogEntry>, List<LogEntry>, List<LogEntry>>() { @Override @@ -1889,6 +1882,16 @@ public abstract class GCParser { DataStore.saveLogsWithoutTransaction(cache.getGeocode(), logEntries); } }); + if (cache.isFound() && cache.getVisitedDate() == 0) { + ownLogs.subscribe(new Action1<LogEntry>() { + @Override + public void call(final LogEntry logEntry) { + if (logEntry.type == LogType.FOUND_IT) { + cache.setVisitedDate(logEntry.date); + } + } + }); + } if (Settings.isRatingWanted() && !CancellableHandler.isCancelled(handler)) { CancellableHandler.sendLoadProgressDetail(handler, R.string.cache_dialog_loading_details_status_gcvote); @@ -1923,75 +1926,61 @@ public abstract class GCParser { } } - public static boolean uploadModifiedCoordinates(Geocache cache, Geopoint wpt) { + public static boolean uploadModifiedCoordinates(final Geocache cache, final Geopoint wpt) { return editModifiedCoordinates(cache, wpt); } - public static boolean deleteModifiedCoordinates(Geocache cache) { + public static boolean deleteModifiedCoordinates(final Geocache cache) { return editModifiedCoordinates(cache, null); } - public static boolean editModifiedCoordinates(Geocache cache, Geopoint wpt) { + public static boolean editModifiedCoordinates(final Geocache cache, final Geopoint wpt) { final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } - try { - JSONObject jo; - if (wpt != null) { - jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken) - .put("data", new JSONObject() - .put("lat", wpt.getLatitudeE6() / 1E6) - .put("lng", wpt.getLongitudeE6() / 1E6)))); - } else { - jo = new JSONObject().put("dto", (new JSONObject().put("ut", userToken))); - } - - final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate"; + final ObjectNode jo = new ObjectNode(JsonUtils.factory); + final ObjectNode dto = jo.putObject("dto").put("ut", userToken); + if (wpt != null) { + dto.putObject("data").put("lat", wpt.getLatitudeE6() / 1E6).put("lng", wpt.getLongitudeE6() / 1E6); + } - final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); - Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + final String uriSuffix = wpt != null ? "SetUserCoordinate" : "ResetUserCoordinate"; - if (response != null && response.getStatusLine().getStatusCode() == 200) { - Log.i("GCParser.editModifiedCoordinates - edited on GC.com"); - return true; - } + final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); - } catch (final JSONException e) { - Log.e("Unknown exception with json wrap code", e); + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.editModifiedCoordinates - edited on GC.com"); + return true; } + Log.e("GCParser.deleteModifiedCoordinates - cannot delete modified coords"); return false; } - public static boolean uploadPersonalNote(Geocache cache) { + public static boolean uploadPersonalNote(final Geocache cache) { final String userToken = getUserToken(cache); if (StringUtils.isEmpty(userToken)) { return false; } - try { - final JSONObject jo = new JSONObject() - .put("dto", (new JSONObject() - .put("et", StringUtils.defaultString(cache.getPersonalNote())) - .put("ut", userToken))); - - final String uriSuffix = "SetUserCacheNote"; + final ObjectNode jo = new ObjectNode(JsonUtils.factory); + jo.putObject("dto").put("et", StringUtils.defaultString(cache.getPersonalNote())).put("ut", userToken); - final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; - final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); - Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); + final String uriSuffix = "SetUserCacheNote"; - if (response != null && response.getStatusLine().getStatusCode() == 200) { - Log.i("GCParser.uploadPersonalNote - uploaded to GC.com"); - return true; - } + final String uriPrefix = "http://www.geocaching.com/seek/cache_details.aspx/"; + final HttpResponse response = Network.postJsonRequest(uriPrefix + uriSuffix, jo); + Log.i("Sending to " + uriPrefix + uriSuffix + " :" + jo.toString()); - } catch (final JSONException e) { - Log.e("Unknown exception with json wrap code", e); + if (response != null && response.getStatusLine().getStatusCode() == 200) { + Log.i("GCParser.uploadPersonalNote - uploaded to GC.com"); + return true; } + Log.e("GCParser.uploadPersonalNote - cannot upload personal note"); return false; } diff --git a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java index 6095514..affeb7d 100644 --- a/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java +++ b/main/src/cgeo/geocaching/connector/gc/RecaptchaHandler.java @@ -42,15 +42,15 @@ public class RecaptchaHandler extends Handler { } private void loadChallenge(final ImageView imageView, final View reloadButton) { - final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<? extends Bitmap>>() { + final Observable<Bitmap> captcha = Observable.defer(new Func0<Observable<Bitmap>>() { @Override - public Observable<? extends Bitmap> call() { + public Observable<Bitmap> call() { final String url = "http://www.google.com/recaptcha/api/image?c=" + recaptchaReceiver.getChallenge(); final InputStream is = Network.getResponseStream(Network.getRequest(url)); if (is != null) { try { final Bitmap img = BitmapFactory.decodeStream(is); - return Observable.from(img); + return Observable.just(img); } catch (final Exception e) { Log.e("RecaptchaHandler.getCaptcha", e); return Observable.error(e); diff --git a/main/src/cgeo/geocaching/connector/gc/Tile.java b/main/src/cgeo/geocaching/connector/gc/Tile.java index 18fe65c..ff13fe3 100644 --- a/main/src/cgeo/geocaching/connector/gc/Tile.java +++ b/main/src/cgeo/geocaching/connector/gc/Tile.java @@ -56,11 +56,11 @@ public class Tile { private final int zoomLevel; private final Viewport viewPort; - public Tile(Geopoint origin, int zoomlevel) { + public Tile(final Geopoint origin, final int zoomlevel) { this(calcX(origin, clippedZoomlevel(zoomlevel)), calcY(origin, clippedZoomlevel(zoomlevel)), clippedZoomlevel(zoomlevel)); } - private Tile(int tileX, int tileY, int zoomlevel) { + private Tile(final int tileX, final int tileY, final int zoomlevel) { this.zoomLevel = clippedZoomlevel(zoomlevel); @@ -74,7 +74,7 @@ public class Tile { return zoomLevel; } - private static int clippedZoomlevel(int zoomlevel) { + private static int clippedZoomlevel(final int zoomlevel) { return Math.max(Math.min(zoomlevel, ZOOMLEVEL_MAX), ZOOMLEVEL_MIN); } @@ -95,7 +95,7 @@ public class Tile { */ private static int calcY(final Geopoint origin, final int zoomlevel) { // Optimization from Bing - double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude())); + final double sinLatRad = Math.sin(Math.toRadians(origin.getLatitude())); // The cut of the fractional part instead of rounding to the nearest integer is intentional and part of the algorithm return (int) ((0.5 - Math.log((1 + sinLatRad) / (1 - sinLatRad)) / (4 * Math.PI)) * NUMBER_OF_TILES[zoomlevel]); } @@ -115,13 +115,13 @@ public class Tile { * href="http://developers.cloudmade.com/projects/tiles/examples/convert-coordinates-to-tile-numbers">Cloudmade</a> */ @NonNull - public Geopoint getCoord(UTFGridPosition pos) { + public Geopoint getCoord(final UTFGridPosition pos) { - double pixX = tileX * TILE_SIZE + pos.x * 4; - double pixY = tileY * TILE_SIZE + pos.y * 4; + final double pixX = tileX * TILE_SIZE + pos.x * 4; + final double pixY = tileY * TILE_SIZE + pos.y * 4; - double lonDeg = ((360.0 * pixX) / NUMBER_OF_PIXELS[this.zoomLevel]) - 180.0; - double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * pixY / NUMBER_OF_PIXELS[this.zoomLevel]))); + final double lonDeg = ((360.0 * pixX) / NUMBER_OF_PIXELS[this.zoomLevel]) - 180.0; + final double latRad = Math.atan(Math.sinh(Math.PI * (1 - 2 * pixY / NUMBER_OF_PIXELS[this.zoomLevel]))); return new Geopoint(Math.toDegrees(latRad), lonDeg); } @@ -153,8 +153,8 @@ public class Tile { / Math.log(2) ); - Tile tileLeft = new Tile(left, zoom); - Tile tileRight = new Tile(right, zoom); + final Tile tileLeft = new Tile(left, zoom); + final Tile tileRight = new Tile(right, zoom); if (Math.abs(tileLeft.tileX - tileRight.tileX) < (numberOfTiles - 1)) { zoom += 1; @@ -190,8 +190,8 @@ public class Tile { ) / Math.log(2) ); - Tile tileBottom = new Tile(bottom, zoom); - Tile tileTop = new Tile(top, zoom); + final Tile tileBottom = new Tile(bottom, zoom); + final Tile tileTop = new Tile(top, zoom); if (Math.abs(tileBottom.tileY - tileTop.tileY) > (numberOfTiles - 1)) { zoom -= 1; @@ -200,7 +200,7 @@ public class Tile { return Math.min(zoom, ZOOMLEVEL_MAX); } - private static double tanGrad(double angleGrad) { + private static double tanGrad(final double angleGrad) { return Math.tan(angleGrad / 180.0 * Math.PI); } @@ -211,12 +211,12 @@ public class Tile { * @param x * @return */ - private static double asinh(double x) { + private static double asinh(final double x) { return Math.log(x + Math.sqrt(x * x + 1.0)); } @Override - public boolean equals(Object o) { + public boolean equals(final Object o) { if (this == o) { return true; } @@ -260,7 +260,7 @@ public class Tile { public Bitmap call() { try { return response != null ? BitmapFactory.decodeStream(response.getEntity().getContent()) : null; - } catch (IOException e) { + } catch (final IOException e) { Log.e("Tile.requestMapTile() ", e); return null; } @@ -298,20 +298,20 @@ public class Tile { * @return */ protected static Set<Tile> getTilesForViewport(final Viewport viewport, final int tilesOnAxis, final int minZoom) { - Set<Tile> tiles = new HashSet<>(); - int zoom = Math.max( + final Set<Tile> tiles = new HashSet<>(); + final int zoom = Math.max( Math.min(Tile.calcZoomLon(viewport.bottomLeft, viewport.topRight, tilesOnAxis), Tile.calcZoomLat(viewport.bottomLeft, viewport.topRight, tilesOnAxis)), minZoom); - Tile tileBottomLeft = new Tile(viewport.bottomLeft, zoom); - Tile tileTopRight = new Tile(viewport.topRight, zoom); + final Tile tileBottomLeft = new Tile(viewport.bottomLeft, zoom); + final Tile tileTopRight = new Tile(viewport.topRight, zoom); - int xLow = Math.min(tileBottomLeft.getX(), tileTopRight.getX()); - int xHigh = Math.max(tileBottomLeft.getX(), tileTopRight.getX()); + final int xLow = Math.min(tileBottomLeft.getX(), tileTopRight.getX()); + final int xHigh = Math.max(tileBottomLeft.getX(), tileTopRight.getX()); - int yLow = Math.min(tileBottomLeft.getY(), tileTopRight.getY()); - int yHigh = Math.max(tileBottomLeft.getY(), tileTopRight.getY()); + final int yLow = Math.min(tileBottomLeft.getY(), tileTopRight.getY()); + final int yHigh = Math.max(tileBottomLeft.getY(), tileTopRight.getY()); for (int xNum = xLow; xNum <= xHigh; xNum++) { for (int yNum = yLow; yNum <= yHigh; yNum++) { @@ -324,8 +324,6 @@ public class Tile { public static class TileCache extends LeastRecentlyUsedSet<Tile> { - private static final long serialVersionUID = -1942301031192719547L; - public TileCache() { super(64); } diff --git a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java index 284234e..9e9ec7f 100644 --- a/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java +++ b/main/src/cgeo/geocaching/connector/oc/OCApiConnector.java @@ -31,7 +31,7 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { private final ApiSupport apiSupport; private final String licenseString; - public OCApiConnector(String name, String host, String prefix, String cK, String licenseString, ApiSupport apiSupport) { + public OCApiConnector(final String name, final String host, final String prefix, final String cK, final String licenseString, final ApiSupport apiSupport) { super(name, host, prefix); this.cK = cK; this.apiSupport = apiSupport; @@ -39,7 +39,12 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { } public void addAuthentication(final Parameters params) { - params.put(CryptUtils.rot13("pbafhzre_xrl"), CryptUtils.rot13(cK)); + final String rotCK = CryptUtils.rot13(cK); + // check that developers are not using the Ant defined properties without any values + if (StringUtils.startsWith(rotCK, "${")) { + throw new IllegalStateException("invalid OKAPI OAuth token " + rotCK); + } + params.put(CryptUtils.rot13("pbafhzre_xrl"), rotCK); } @Override @@ -93,13 +98,13 @@ public class OCApiConnector extends OCConnector implements ISearchByGeocode { /** * Checks if a search based on a user name targets the current user - * + * * @param username * Name of the user the query is searching after * @return True - search target and current is same, False - current user not known or not the same as username */ @SuppressWarnings("static-method") - public boolean isSearchForMyCaches(String username) { + public boolean isSearchForMyCaches(final String username) { return false; } } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java index bd87042..f665a1a 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiClient.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiClient.java @@ -27,36 +27,40 @@ import cgeo.geocaching.geopoint.GeopointFormatter; import cgeo.geocaching.geopoint.Viewport; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.OAuth; +import cgeo.geocaching.network.OAuthTokens; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.SynchronizedDateFormat; import ch.boye.httpclientandroidlib.HttpResponse; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ArrayNode; +import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.lang3.StringUtils; -import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.json.JSONArray; -import org.json.JSONException; -import org.json.JSONObject; import android.net.Uri; +import java.io.IOException; import java.text.ParseException; import java.util.ArrayList; import java.util.Calendar; import java.util.Collections; import java.util.Date; import java.util.EnumSet; -import java.util.Iterator; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.TimeZone; +import java.util.regex.Pattern; final class OkapiClient { @@ -132,6 +136,8 @@ final class OkapiClient { private static final String METHOD_SEARCH_NEAREST = "services/caches/search/nearest"; private static final String METHOD_RETRIEVE_CACHES = "services/caches/geocaches"; + private static final Pattern PATTERN_TIMEZONE = Pattern.compile("([+-][01][0-9]):([03])0"); + public static Geocache getCache(final String geoCode) { final Parameters params = new Parameters("cache_code", geoCode); final IConnector connector = ConnectorFactory.getConnector(geoCode); @@ -209,10 +215,15 @@ final class OkapiClient { } addFilterParams(valueMap, connector, my); - params.add("search_params", new JSONObject(valueMap).toString()); + try { + params.add("search_params", JsonUtils.writer.writeValueAsString(valueMap)); + } catch (final JsonProcessingException e) { + Log.e("requestCaches", e); + return Collections.emptyList(); + } addRetrieveParams(params, connector); - final JSONObject data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params).data; + final ObjectNode data = request(connector, OkapiService.SERVICE_SEARCH_AND_RETRIEVE, params).data; if (data == null) { return Collections.emptyList(); @@ -245,7 +256,7 @@ final class OkapiClient { final Parameters params = new Parameters("cache_code", cache.getGeocode()); params.add("watched", watched ? "true" : "false"); - final JSONObject data = request(connector, OkapiService.SERVICE_MARK_CACHE, params).data; + final ObjectNode data = request(connector, OkapiService.SERVICE_MARK_CACHE, params).data; if (data == null) { return false; @@ -269,68 +280,58 @@ final class OkapiClient { params.add("password", logPassword); } - final JSONObject data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params).data; + final ObjectNode data = request(connector, OkapiService.SERVICE_SUBMIT_LOG, params).data; if (data == null) { return new LogResult(StatusCode.LOG_POST_ERROR, ""); } try { - if (data.getBoolean("success")) { - return new LogResult(StatusCode.NO_ERROR, data.getString("log_uuid")); + if (data.get("success").asBoolean()) { + return new LogResult(StatusCode.NO_ERROR, data.get("log_uuid").asText()); } return new LogResult(StatusCode.LOG_POST_ERROR, ""); - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.postLog", e); } return new LogResult(StatusCode.LOG_POST_ERROR, ""); } - private static List<Geocache> parseCaches(final JSONObject response) { + private static List<Geocache> parseCaches(final ObjectNode response) { try { // Check for empty result - final String result = response.getString("results"); - if (StringUtils.isBlank(result) || StringUtils.equals(result, "[]")) { + final JsonNode results = response.path("results"); + if (!results.isObject()) { return Collections.emptyList(); } // Get and iterate result list - final JSONObject cachesResponse = response.getJSONObject("results"); - if (cachesResponse != null) { - final List<Geocache> caches = new ArrayList<>(cachesResponse.length()); - final Iterator<?> keys = cachesResponse.keys(); - while (keys.hasNext()) { - final Object next = keys.next(); - if (next instanceof String) { - final String key = (String) next; - final Geocache cache = parseSmallCache(cachesResponse.getJSONObject(key)); - caches.add(cache); - } - } - return caches; + final List<Geocache> caches = new ArrayList<>(results.size()); + for (final JsonNode cache: results) { + caches.add(parseSmallCache((ObjectNode) cache)); } - } catch (final JSONException e) { + return caches; + } catch (ClassCastException | NullPointerException e) { Log.e("OkapiClient.parseCachesResult", e); } return Collections.emptyList(); } - private static Geocache parseSmallCache(final JSONObject response) { + private static Geocache parseSmallCache(final ObjectNode response) { final Geocache cache = new Geocache(); cache.setReliableLatLon(true); try { - parseCoreCache(response, cache); - DataStore.saveCache(cache, EnumSet.of(SaveFlag.CACHE)); - } catch (final JSONException e) { + } catch (final NullPointerException e) { + // FIXME: here we may return a partially filled cache Log.e("OkapiClient.parseSmallCache", e); } return cache; } - private static Geocache parseCache(final JSONObject response) { + private static Geocache parseCache(final ObjectNode response) { final Geocache cache = new Geocache(); cache.setReliableLatLon(true); try { @@ -338,28 +339,27 @@ final class OkapiClient { parseCoreCache(response, cache); // not used: url - final JSONObject ownerObject = response.getJSONObject(CACHE_OWNER); - final String owner = parseUser(ownerObject); + final String owner = parseUser(response.get(CACHE_OWNER)); cache.setOwnerDisplayName(owner); // OpenCaching has no distinction between user id and user display name. Set the ID anyway to simplify c:geo workflows. cache.setOwnerUserId(owner); - cache.getLogCounts().put(LogType.FOUND_IT, response.getInt(CACHE_FOUNDS)); - cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.getInt(CACHE_NOTFOUNDS)); + cache.getLogCounts().put(LogType.FOUND_IT, response.get(CACHE_FOUNDS).asInt()); + cache.getLogCounts().put(LogType.DIDNT_FIND_IT, response.get(CACHE_NOTFOUNDS).asInt()); // only current Api - cache.getLogCounts().put(LogType.WILL_ATTEND, response.optInt(CACHE_WILLATTENDS)); + cache.getLogCounts().put(LogType.WILL_ATTEND, response.path(CACHE_WILLATTENDS).asInt()); - if (!response.isNull(CACHE_RATING)) { - cache.setRating((float) response.getDouble(CACHE_RATING)); + if (response.has(CACHE_RATING)) { + cache.setRating((float) response.get(CACHE_RATING).asDouble()); } - cache.setVotes(response.getInt(CACHE_VOTES)); + cache.setVotes(response.get(CACHE_VOTES).asInt()); - cache.setFavoritePoints(response.getInt(CACHE_RECOMMENDATIONS)); + cache.setFavoritePoints(response.get(CACHE_RECOMMENDATIONS).asInt()); // not used: req_password // Prepend gc-link to description if available final StringBuilder description = new StringBuilder(500); - if (!response.isNull("gc_code")) { - final String gccode = response.getString("gc_code"); + if (response.hasNonNull("gc_code")) { + final String gccode = response.get("gc_code").asText(); description.append(CgeoApplication.getInstance().getResources() .getString(R.string.cache_listed_on, GCConnector.getInstance().getName())) .append(": <a href=\"http://coord.info/") @@ -368,71 +368,70 @@ final class OkapiClient { .append(gccode) .append("</a><br /><br />"); } - description.append(response.getString(CACHE_DESCRIPTION)); + description.append(response.get(CACHE_DESCRIPTION).asText()); cache.setDescription(description.toString()); // currently the hint is delivered as HTML (contrary to OKAPI documentation), so we can store it directly - cache.setHint(response.getString(CACHE_HINT)); + cache.setHint(response.get(CACHE_HINT).asText()); // not used: hints - final JSONArray images = response.getJSONArray(CACHE_IMAGES); + final ArrayNode images = (ArrayNode) response.get(CACHE_IMAGES); if (images != null) { - for (int i = 0; i < images.length(); i++) { - final JSONObject imageResponse = images.getJSONObject(i); - final String title = imageResponse.getString(CACHE_IMAGE_CAPTION); - final String url = absoluteUrl(imageResponse.getString(CACHE_IMAGE_URL), cache.getGeocode()); + for (final JsonNode imageResponse: images) { + final String title = imageResponse.get(CACHE_IMAGE_CAPTION).asText(); + final String url = absoluteUrl(imageResponse.get(CACHE_IMAGE_URL).asText(), cache.getGeocode()); // all images are added as spoiler images, although OKAPI has spoiler and non spoiler images cache.addSpoiler(new Image(url, title)); } } - cache.setAttributes(parseAttributes(response.getJSONArray(CACHE_ATTRNAMES), response.optJSONArray(CACHE_ATTR_ACODES))); + cache.setAttributes(parseAttributes((ArrayNode) response.path(CACHE_ATTRNAMES), (ArrayNode) response.get(CACHE_ATTR_ACODES))); //TODO: Store license per cache //cache.setLicense(response.getString("attribution_note")); - cache.setWaypoints(parseWaypoints(response.getJSONArray(CACHE_WPTS)), false); + cache.setWaypoints(parseWaypoints((ArrayNode) response.path(CACHE_WPTS)), false); - cache.setInventory(parseTrackables(response.getJSONArray(CACHE_TRACKABLES))); + cache.setInventory(parseTrackables((ArrayNode) response.path(CACHE_TRACKABLES))); - if (!response.isNull(CACHE_IS_WATCHED)) { - cache.setOnWatchlist(response.getBoolean(CACHE_IS_WATCHED)); + if (response.has(CACHE_IS_WATCHED)) { + cache.setOnWatchlist(response.get(CACHE_IS_WATCHED).asBoolean()); } - if (!response.isNull(CACHE_MY_NOTES)) { - cache.setPersonalNote(response.getString(CACHE_MY_NOTES)); + if (response.hasNonNull(CACHE_MY_NOTES)) { + cache.setPersonalNote(response.get(CACHE_MY_NOTES).asText()); cache.parseWaypointsFromNote(); } - cache.setLogPasswordRequired(response.getBoolean(CACHE_REQ_PASSWORD)); + cache.setLogPasswordRequired(response.get(CACHE_REQ_PASSWORD).asBoolean()); cache.setDetailedUpdatedNow(); // save full detailed caches DataStore.saveCache(cache, EnumSet.of(SaveFlag.DB)); - DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs(response.getJSONArray(CACHE_LATEST_LOGS))); - } catch (final JSONException e) { + DataStore.saveLogsWithoutTransaction(cache.getGeocode(), parseLogs((ArrayNode) response.path(CACHE_LATEST_LOGS))); + } catch (ClassCastException | NullPointerException e) { Log.e("OkapiClient.parseCache", e); } return cache; } - private static void parseCoreCache(final JSONObject response, final Geocache cache) throws JSONException { - cache.setGeocode(response.getString(CACHE_CODE)); - cache.setName(response.getString(CACHE_NAME)); + private static void parseCoreCache(final ObjectNode response, final Geocache cache) { + cache.setGeocode(response.get(CACHE_CODE).asText()); + cache.setName(response.get(CACHE_NAME).asText()); // not used: names - setLocation(cache, response.getString(CACHE_LOCATION)); - cache.setType(getCacheType(response.getString(CACHE_TYPE))); + setLocation(cache, response.get(CACHE_LOCATION).asText()); + cache.setType(getCacheType(response.get(CACHE_TYPE).asText())); - final String status = response.getString(CACHE_STATUS); + final String status = response.get(CACHE_STATUS).asText(); cache.setDisabled(status.equalsIgnoreCase(CACHE_STATUS_DISABLED)); cache.setArchived(status.equalsIgnoreCase(CACHE_STATUS_ARCHIVED)); cache.setSize(getCacheSize(response)); - cache.setDifficulty((float) response.getDouble(CACHE_DIFFICULTY)); - cache.setTerrain((float) response.getDouble(CACHE_TERRAIN)); + cache.setDifficulty((float) response.get(CACHE_DIFFICULTY).asDouble()); + cache.setTerrain((float) response.get(CACHE_TERRAIN).asDouble()); - cache.setInventoryItems(response.getInt(CACHE_TRACKABLES_COUNT)); + cache.setInventoryItems(response.get(CACHE_TRACKABLES_COUNT).asInt()); - if (!response.isNull(CACHE_IS_FOUND)) { - cache.setFound(response.getBoolean(CACHE_IS_FOUND)); + if (response.has(CACHE_IS_FOUND)) { + cache.setFound(response.get(CACHE_IS_FOUND).asBoolean()); } - cache.setHidden(parseDate(response.getString(CACHE_HIDDEN))); + cache.setHidden(parseDate(response.get(CACHE_HIDDEN).asText())); } private static String absoluteUrl(final String url, final String geocode) { @@ -448,38 +447,36 @@ final class OkapiClient { return url; } - private static String parseUser(final JSONObject user) throws JSONException { - return user.getString(USER_USERNAME); + private static String parseUser(final JsonNode user) { + return user.get(USER_USERNAME).asText(); } - private static List<LogEntry> parseLogs(final JSONArray logsJSON) { + private static List<LogEntry> parseLogs(final ArrayNode logsJSON) { final List<LogEntry> result = new LinkedList<>(); - for (int i = 0; i < logsJSON.length(); i++) { + for (final JsonNode logResponse: logsJSON) { try { - final JSONObject logResponse = logsJSON.getJSONObject(i); final LogEntry log = new LogEntry( - parseUser(logResponse.getJSONObject(LOG_USER)), - parseDate(logResponse.getString(LOG_DATE)).getTime(), - parseLogType(logResponse.getString(LOG_TYPE)), - logResponse.getString(LOG_COMMENT).trim()); + parseUser(logResponse.get(LOG_USER)), + parseDate(logResponse.get(LOG_DATE).asText()).getTime(), + parseLogType(logResponse.get(LOG_TYPE).asText()), + logResponse.get(LOG_COMMENT).asText().trim()); result.add(log); - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.parseLogs", e); } } return result; } - private static List<Waypoint> parseWaypoints(final JSONArray wptsJson) { + private static List<Waypoint> parseWaypoints(final ArrayNode wptsJson) { List<Waypoint> result = null; - for (int i = 0; i < wptsJson.length(); i++) { + for (final JsonNode wptResponse: wptsJson) { try { - final JSONObject wptResponse = wptsJson.getJSONObject(i); - final Waypoint wpt = new Waypoint(wptResponse.getString(WPT_NAME), - parseWptType(wptResponse.getString(WPT_TYPE)), + final Waypoint wpt = new Waypoint(wptResponse.get(WPT_NAME).asText(), + parseWptType(wptResponse.get(WPT_TYPE).asText()), false); - wpt.setNote(wptResponse.getString(WPT_DESCRIPTION)); - final Geopoint pt = parseCoords(wptResponse.getString(WPT_LOCATION)); + wpt.setNote(wptResponse.get(WPT_DESCRIPTION).asText()); + final Geopoint pt = parseCoords(wptResponse.get(WPT_LOCATION).asText()); if (pt != null) { wpt.setCoords(pt); } @@ -488,26 +485,25 @@ final class OkapiClient { } wpt.setPrefix(wpt.getName()); result.add(wpt); - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.parseWaypoints", e); } } return result; } - private static List<Trackable> parseTrackables(final JSONArray trackablesJson) { - if (trackablesJson.length() == 0) { + private static List<Trackable> parseTrackables(final ArrayNode trackablesJson) { + if (trackablesJson.size() == 0) { return Collections.emptyList(); } final List<Trackable> result = new ArrayList<>(); - for (int i = 0; i < trackablesJson.length(); i++) { + for (final JsonNode trackableResponse: trackablesJson) { try { - final JSONObject trackableResponse = trackablesJson.getJSONObject(i); final Trackable trk = new Trackable(); - trk.setGeocode(trackableResponse.getString(TRK_GEOCODE)); - trk.setName(trackableResponse.getString(TRK_NAME)); + trk.setGeocode(trackableResponse.get(TRK_GEOCODE).asText()); + trk.setName(trackableResponse.get(TRK_NAME).asText()); result.add(trk); - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.parseWaypoints", e); // Don't overwrite internal state with possibly partial result return null; @@ -576,7 +572,7 @@ final class OkapiClient { } private static Date parseDate(final String date) { - final String strippedDate = date.replaceAll("\\+0([0-9]){1}\\:00", "+0$100"); + final String strippedDate = PATTERN_TIMEZONE.matcher(date).replaceAll("$1$20"); try { return ISO8601DATEFORMAT.parse(strippedDate); } catch (final ParseException e) { @@ -595,14 +591,14 @@ final class OkapiClient { return null; } - private static List<String> parseAttributes(final JSONArray nameList, final JSONArray acodeList) { + private static List<String> parseAttributes(final ArrayNode nameList, final ArrayNode acodeList) { final List<String> result = new ArrayList<>(); - for (int i = 0; i < nameList.length(); i++) { + for (int i = 0; i < nameList.size(); i++) { try { - final String name = nameList.getString(i); - final int acode = acodeList != null ? Integer.parseInt(acodeList.getString(i).substring(1)) : CacheAttribute.NO_ID; + final String name = nameList.get(i).asText(); + final int acode = acodeList != null ? Integer.parseInt(acodeList.get(i).asText().substring(1)) : CacheAttribute.NO_ID; final CacheAttribute attr = CacheAttribute.getByOcACode(acode); if (attr != null) { @@ -610,7 +606,7 @@ final class OkapiClient { } else { result.add(name); } - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.parseAttributes", e); } } @@ -624,27 +620,27 @@ final class OkapiClient { cache.setCoords(new Geopoint(latitude, longitude)); } - private static CacheSize getCacheSize(final JSONObject response) { - if (response.isNull(CACHE_SIZE2)) { + private static CacheSize getCacheSize(final ObjectNode response) { + if (!response.has(CACHE_SIZE2)) { return getCacheSizeDeprecated(response); } try { - final String size = response.getString(CACHE_SIZE2); + final String size = response.get(CACHE_SIZE2).asText(); return CacheSize.getById(size); - } catch (final JSONException e) { + } catch (final NullPointerException e) { Log.e("OkapiClient.getCacheSize", e); return getCacheSizeDeprecated(response); } } - private static CacheSize getCacheSizeDeprecated(final JSONObject response) { - if (response.isNull(CACHE_SIZE_DEPRECATED)) { + private static CacheSize getCacheSizeDeprecated(final ObjectNode response) { + if (!response.has(CACHE_SIZE_DEPRECATED)) { return CacheSize.NOT_CHOSEN; } double size = 0; try { - size = response.getDouble(CACHE_SIZE_DEPRECATED); - } catch (final JSONException e) { + size = response.get(CACHE_SIZE_DEPRECATED).asDouble(); + } catch (final NullPointerException e) { Log.e("OkapiClient.getCacheSize", e); } switch ((int) Math.round(size)) { @@ -732,19 +728,22 @@ final class OkapiClient { @NonNull private static JSONResult request(final OCApiConnector connector, final OkapiService service, final Parameters params) { if (connector == null) { - return new JSONResult(null); + return new JSONResult("unknown OKAPI connector"); } final String host = connector.getHost(); if (StringUtils.isBlank(host)) { - return new JSONResult(null); + return new JSONResult("unknown OKAPI connector host"); } params.add("langpref", getPreferredLanguage()); if (connector.getSupportedAuthLevel() == OAuthLevel.Level3) { - final ImmutablePair<String, String> tokens = Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId()); - OAuth.signOAuth(host, service.methodName, "GET", false, params, tokens.left, tokens.right, connector.getCK(), connector.getCS()); + final OAuthTokens tokens = new OAuthTokens(connector); + if (!tokens.isValid()) { + return new JSONResult("invalid oauth tokens"); + } + OAuth.signOAuth(host, service.methodName, "GET", false, params, tokens, connector.getCK(), connector.getCS()); } else { connector.addAuthentication(params); } @@ -810,16 +809,7 @@ final class OkapiClient { return null; } - final JSONObject data = result.data; - if (!data.isNull(USER_UUID)) { - try { - return data.getString(USER_UUID); - } catch (final JSONException e) { - Log.e("OkapiClient.getUserUUID - uuid", e); - } - } - - return null; + return result.data.path(USER_UUID).asText(null); } public static UserInfo getUserInfo(final OCApiLiveConnector connector) { @@ -833,31 +823,11 @@ final class OkapiClient { return new UserInfo(StringUtils.EMPTY, 0, UserInfoStatus.getFromOkapiError(error.getResult())); } - final JSONObject data = result.data; - - String name = StringUtils.EMPTY; - boolean successUserName = false; - - if (!data.isNull(USER_USERNAME)) { - try { - name = data.getString(USER_USERNAME); - successUserName = true; - } catch (final JSONException e) { - Log.e("OkapiClient.getUserInfo - name", e); - } - } - - int finds = 0; - boolean successFinds = false; - - if (!data.isNull(USER_CACHES_FOUND)) { - try { - finds = data.getInt(USER_CACHES_FOUND); - successFinds = true; - } catch (final JSONException e) { - Log.e("OkapiClient.getUserInfo - finds", e); - } - } + final ObjectNode data = result.data; + final boolean successUserName = data.has(USER_USERNAME); + final String name = data.path(USER_USERNAME).asText(); + final boolean successFinds = data.has(USER_CACHES_FOUND); + final int finds = data.path(USER_CACHES_FOUND).asInt(); return new UserInfo(name, finds, successUserName && successFinds ? UserInfoStatus.SUCCESSFUL : UserInfoStatus.FAILED); } @@ -874,7 +844,7 @@ final class OkapiClient { if (!result.isSuccess) { return new OkapiError(result.data); } - return new OkapiError(new JSONObject()); + return new OkapiError(new ObjectNode(JsonUtils.factory)); } /** @@ -884,21 +854,27 @@ final class OkapiClient { private static class JSONResult { public final boolean isSuccess; - public final JSONObject data; + public final ObjectNode data; public JSONResult(final @Nullable HttpResponse response) { - final boolean isSuccess = Network.isSuccess(response); + final boolean isRequestSuccessful = Network.isSuccess(response); final String responseData = Network.getResponseDataAlways(response); - JSONObject data = null; + ObjectNode tempData = null; if (responseData != null) { try { - data = new JSONObject(responseData); - } catch (final JSONException e) { + tempData = (ObjectNode) JsonUtils.reader.readTree(responseData); + } catch (IOException | ClassCastException e) { Log.w("JSONResult", e); } } - this.data = data; - this.isSuccess = isSuccess && data != null; + data = tempData; + isSuccess = isRequestSuccessful && tempData != null; + } + + public JSONResult(final @NonNull String errorMessage) { + isSuccess = false; + data = new ObjectNode(JsonUtils.factory); + data.putObject("error").put("developer_message", errorMessage); } } } diff --git a/main/src/cgeo/geocaching/connector/oc/OkapiError.java b/main/src/cgeo/geocaching/connector/oc/OkapiError.java index 7faf2c7..b847207 100644 --- a/main/src/cgeo/geocaching/connector/oc/OkapiError.java +++ b/main/src/cgeo/geocaching/connector/oc/OkapiError.java @@ -2,11 +2,11 @@ package cgeo.geocaching.connector.oc; import cgeo.geocaching.utils.Log; +import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; -import org.json.JSONException; -import org.json.JSONObject; /** * Handles the JSON error response from OKAPI @@ -26,7 +26,7 @@ public class OkapiError { @NonNull private final OkapiErrors state; @NonNull private final String message; - public OkapiError(@Nullable JSONObject data) { + public OkapiError(@Nullable ObjectNode data) { // A null-response is by definition an error (some exception occurred somewhere in the flow) if (data == null) { @@ -39,10 +39,10 @@ public class OkapiError { String localmessage = null; OkapiErrors localstate = OkapiErrors.UNSPECIFIED; try { - JSONObject error = data.getJSONObject("error"); + final ObjectNode error = (ObjectNode) data.get("error"); // Check reason_stack element to look for the specific oauth problems we want to report back if (error.has("reason_stack")) { - String reason = error.getString("reason_stack"); + final String reason = error.get("reason_stack").asText(); if (StringUtils.contains(reason, "invalid_oauth_request")) { if (StringUtils.contains(reason, "invalid_timestamp")) { localstate = OkapiErrors.INVALID_TIMESTAMP; @@ -53,10 +53,10 @@ public class OkapiError { } // Check if we can extract a message as well if (error.has("developer_message")) { - localmessage = error.getString("developer_message"); + localmessage = error.get("developer_message").asText(); assert localmessage != null; // by virtue of defaultString } - } catch (JSONException ex) { + } catch (ClassCastException | NullPointerException ex) { Log.d("OkapiError: Failed to parse JSON", ex); localstate = OkapiErrors.UNSPECIFIED; } diff --git a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java index b2afff5..21207ec 100644 --- a/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java +++ b/main/src/cgeo/geocaching/connector/ox/OpenCachingApi.java @@ -39,9 +39,9 @@ public class OpenCachingApi { return null; } - private static HttpResponse getRequest(String string, Parameters parameters) { + private static HttpResponse getRequest(final String uri, final Parameters parameters) { parameters.add("Authorization", DEV_KEY); - return Network.getRequest(string, parameters); + return Network.getRequest(uri, parameters); } private static Collection<Geocache> importCachesFromResponse(final HttpResponse response, final boolean isDetailed) { @@ -50,7 +50,7 @@ public class OpenCachingApi { } Collection<Geocache> caches; try { - caches = new OXGPXParser(StoredList.TEMPORARY_LIST_ID, isDetailed).parse(response.getEntity().getContent(), null); + caches = new OXGPXParser(StoredList.TEMPORARY_LIST.id, isDetailed).parse(response.getEntity().getContent(), null); } catch (Exception e) { Log.e("Error importing from OpenCaching.com", e); return Collections.emptyList(); diff --git a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java index f2c79fe..a6f0114 100644 --- a/main/src/cgeo/geocaching/enumerations/LocationProviderType.java +++ b/main/src/cgeo/geocaching/enumerations/LocationProviderType.java @@ -5,6 +5,8 @@ import cgeo.geocaching.R; public enum LocationProviderType { GPS(R.string.loc_gps), NETWORK(R.string.loc_net), + FUSED(R.string.loc_fused), + LOW_POWER(R.string.loc_low_power), LAST(R.string.loc_last); public final int resourceId; diff --git a/main/src/cgeo/geocaching/enumerations/LogType.java b/main/src/cgeo/geocaching/enumerations/LogType.java index 84ab7b9..5345611 100644 --- a/main/src/cgeo/geocaching/enumerations/LogType.java +++ b/main/src/cgeo/geocaching/enumerations/LogType.java @@ -52,7 +52,7 @@ public enum LogType { private final int stringId; public final int markerId; - LogType(int id, String iconName, String type, String oc_type, int stringId, int markerId) { + LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId, final int markerId) { this.id = id; this.iconName = iconName; this.type = type; @@ -61,7 +61,7 @@ public enum LogType { this.markerId = markerId; } - LogType(int id, String iconName, String type, String oc_type, int stringId) { + LogType(final int id, final String iconName, final String type, final String oc_type, final int stringId) { this(id, iconName, type, oc_type, stringId, R.drawable.mark_gray); } @@ -70,7 +70,7 @@ public enum LogType { static { final HashMap<String, LogType> mappingPattern = new HashMap<>(); final HashMap<String, LogType> mappingType = new HashMap<>(); - for (LogType lt : values()) { + for (final LogType lt : values()) { if (lt.iconName != null) { mappingPattern.put(lt.iconName, lt); } @@ -81,7 +81,7 @@ public enum LogType { } public static LogType getById(final int id) { - for (LogType logType : values()) { + for (final LogType logType : values()) { if (logType.id == id) { return logType; } @@ -113,4 +113,12 @@ public enum LogType { public final String getL10n() { return CgeoApplication.getInstance().getBaseContext().getResources().getString(stringId); } + + public final boolean isFoundLog() { + return this == LogType.FOUND_IT || this == LogType.ATTENDED || this == LogType.WEBCAM_PHOTO_TAKEN; + } + + public boolean mustConfirmLog() { + return this == ARCHIVE || this == NEEDS_ARCHIVE; + } } diff --git a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java index 2a05cbc..fa84df9 100644 --- a/main/src/cgeo/geocaching/files/AbstractFileListActivity.java +++ b/main/src/cgeo/geocaching/files/AbstractFileListActivity.java @@ -38,7 +38,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext private String searchInfo; @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (msg.obj != null && waitDialog != null) { if (searchInfo == null) { searchInfo = res.getString(R.string.file_searching_in) + " "; @@ -52,7 +52,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext private String getDefaultFolders() { final ArrayList<String> names = new ArrayList<>(); - for (File dir : getExistingBaseFolders()) { + for (final File dir : getExistingBaseFolders()) { names.add(dir.getPath()); } return StringUtils.join(names, '\n'); @@ -62,7 +62,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext final private Handler loadFilesHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (waitDialog != null) { waitDialog.dismiss(); } @@ -76,17 +76,17 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext }; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); setTheme(); setContentView(R.layout.gpx); - Bundle extras = getIntent().getExtras(); + final Bundle extras = getIntent().getExtras(); if (extras != null) { listId = extras.getInt(Intents.EXTRA_LIST_ID); } - if (listId <= StoredList.TEMPORARY_LIST_ID) { + if (listId <= StoredList.TEMPORARY_LIST.id) { listId = StoredList.STANDARD_LIST_ID; } @@ -100,7 +100,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext true, new DialogInterface.OnCancelListener() { @Override - public void onCancel(DialogInterface arg0) { + public void onCancel(final DialogInterface arg0) { if (searchingThread != null && searchingThread.isAlive()) { searchingThread.notifyEnd(); } @@ -171,7 +171,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext } else { Log.w("No external media mounted."); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("AbstractFileListActivity.loadFiles.run", e); } @@ -181,7 +181,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext Collections.sort(files, new Comparator<File>() { @Override - public int compare(File lhs, File rhs) { + public int compare(final File lhs, final File rhs) { return lhs.getName().compareToIgnoreCase(rhs.getName()); } }); @@ -189,7 +189,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext loadFilesHandler.sendMessage(Message.obtain(loadFilesHandler)); } - private void listDirs(List<File> list, List<File> directories, FileListSelector selector, Handler feedbackHandler) { + private void listDirs(final List<File> list, final List<File> directories, final FileListSelector selector, final Handler feedbackHandler) { for (final File dir : directories) { FileUtils.listDir(list, dir, selector, feedbackHandler); } @@ -204,7 +204,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext * @return <code>true</code> if the filename belongs to the list */ protected boolean filenameBelongsToList(final String filename) { - for (String ext : extensions) { + for (final String ext : extensions) { if (StringUtils.endsWithIgnoreCase(filename, ext)) { return true; } @@ -213,7 +213,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext } protected List<File> getExistingBaseFolders() { - ArrayList<File> result = new ArrayList<>(); + final ArrayList<File> result = new ArrayList<>(); for (final File dir : getBaseFolders()) { if (dir.exists() && dir.isDirectory()) { result.add(dir); @@ -245,7 +245,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext boolean shouldEnd = false; @Override - public boolean isSelected(File file) { + public boolean isSelected(final File file) { return filenameBelongsToList(file.getName()); } @@ -254,7 +254,7 @@ public abstract class AbstractFileListActivity<T extends ArrayAdapter<File>> ext return shouldEnd; } - public synchronized void setShouldEnd(boolean shouldEnd) { + public synchronized void setShouldEnd(final boolean shouldEnd) { this.shouldEnd = shouldEnd; } } diff --git a/main/src/cgeo/geocaching/files/FileTypeDetector.java b/main/src/cgeo/geocaching/files/FileTypeDetector.java index 389b83a..ab0f032 100644 --- a/main/src/cgeo/geocaching/files/FileTypeDetector.java +++ b/main/src/cgeo/geocaching/files/FileTypeDetector.java @@ -10,7 +10,6 @@ import android.content.ContentResolver; import android.net.Uri; import java.io.BufferedReader; -import java.io.FileNotFoundException; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; @@ -37,8 +36,6 @@ public class FileTypeDetector { reader = new BufferedReader(new InputStreamReader(is)); type = detectHeader(reader); reader.close(); - } catch (FileNotFoundException e) { - Log.e("FileTypeDetector", e); } catch (IOException e) { Log.e("FileTypeDetector", e); } finally { diff --git a/main/src/cgeo/geocaching/files/GPXImporter.java b/main/src/cgeo/geocaching/files/GPXImporter.java index 52f68e1..4f1d391 100644 --- a/main/src/cgeo/geocaching/files/GPXImporter.java +++ b/main/src/cgeo/geocaching/files/GPXImporter.java @@ -113,7 +113,7 @@ public class GPXImporter { fileType = getFileTypeFromMimeType(mimeType); } - ImportThread importer = getImporterFromFileType(uri, contentResolver, + final ImportThread importer = getImporterFromFileType(uri, contentResolver, fileType); if (importer != null) { @@ -139,14 +139,15 @@ public class GPXImporter { final String mimeType) { if (GPX_MIME_TYPES.contains(mimeType)) { return FileType.GPX; - } else if (ZIP_MIME_TYPES.contains(mimeType)) { + } + if (ZIP_MIME_TYPES.contains(mimeType)) { return FileType.ZIP; } - return FileType.UNKNOWN; + return FileType.UNKNOWN; } - private ImportThread getImporterFromFileType(Uri uri, - ContentResolver contentResolver, FileType fileType) { + private ImportThread getImporterFromFileType(final Uri uri, + final ContentResolver contentResolver, final FileType fileType) { switch (fileType) { case ZIP: return new ImportGpxZipAttachmentThread(uri, contentResolver, @@ -178,7 +179,7 @@ public class GPXImporter { final Handler importStepHandler; final CancellableHandler progressHandler; - protected ImportThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + protected ImportThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { this.listId = listId; this.importStepHandler = importStepHandler; this.progressHandler = progressHandler; @@ -244,7 +245,7 @@ public class GPXImporter { static class ImportLocFileThread extends ImportThread { private final File file; - public ImportLocFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportLocFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.file = file; } @@ -262,7 +263,7 @@ public class GPXImporter { private final Uri uri; private final ContentResolver contentResolver; - public ImportLocAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportLocAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.uri = uri; this.contentResolver = contentResolver; @@ -284,7 +285,7 @@ public class GPXImporter { static abstract class ImportGpxThread extends ImportThread { - protected ImportGpxThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + protected ImportGpxThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); } @@ -305,13 +306,13 @@ public class GPXImporter { static class ImportGpxFileThread extends ImportGpxThread { private final File cacheFile; - public ImportGpxFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportGpxFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.cacheFile = file; } @Override - protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException { Log.i("Import GPX file: " + cacheFile.getAbsolutePath()); importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, (int) cacheFile.length())); Collection<Geocache> caches = parser.parse(cacheFile, progressHandler); @@ -333,17 +334,21 @@ public class GPXImporter { private final Uri uri; private final ContentResolver contentResolver; - public ImportGpxAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportGpxAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.uri = uri; this.contentResolver = contentResolver; } @Override - protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException { Log.i("Import GPX from uri: " + uri); - importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, -1)); final InputStream is = contentResolver.openInputStream(uri); + int streamSize = is.available(); + if (streamSize == 0) { + streamSize = -1; + } + importStepHandler.sendMessage(importStepHandler.obtainMessage(IMPORT_STEP_READ_FILE, R.string.gpx_import_loading_caches, streamSize)); try { return parser.parse(is, progressHandler); } finally { @@ -354,12 +359,12 @@ public class GPXImporter { static abstract class ImportGpxZipThread extends ImportGpxThread { - protected ImportGpxZipThread(int listId, Handler importStepHandler, CancellableHandler progressHandler) { + protected ImportGpxZipThread(final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); } @Override - protected Collection<Geocache> doImport(GPXParser parser) throws IOException, ParserException { + protected Collection<Geocache> doImport(final GPXParser parser) throws IOException, ParserException { Collection<Geocache> caches = Collections.emptySet(); // can't assume that GPX file comes before waypoint file in zip -> so we need two passes // 1. parse GPX files @@ -403,7 +408,7 @@ public class GPXImporter { static class ImportGpxZipFileThread extends ImportGpxZipThread { private final File cacheFile; - public ImportGpxZipFileThread(final File file, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportGpxZipFileThread(final File file, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.cacheFile = file; Log.i("Import zipped GPX: " + file); @@ -419,7 +424,7 @@ public class GPXImporter { private final Uri uri; private final ContentResolver contentResolver; - public ImportGpxZipAttachmentThread(Uri uri, ContentResolver contentResolver, int listId, Handler importStepHandler, CancellableHandler progressHandler) { + public ImportGpxZipAttachmentThread(final Uri uri, final ContentResolver contentResolver, final int listId, final Handler importStepHandler, final CancellableHandler progressHandler) { super(listId, importStepHandler, progressHandler); this.uri = uri; this.contentResolver = contentResolver; @@ -434,14 +439,14 @@ public class GPXImporter { final private CancellableHandler progressHandler = new CancellableHandler() { @Override - public void handleRegularMessage(Message msg) { + public void handleRegularMessage(final Message msg) { progress.setProgress(msg.arg1); } }; final private Handler importStepHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { switch (msg.what) { case IMPORT_STEP_START: final Message cancelMessage = importStepHandler.obtainMessage(IMPORT_STEP_CANCEL); diff --git a/main/src/cgeo/geocaching/files/GPXParser.java b/main/src/cgeo/geocaching/files/GPXParser.java index 89ee887..ccc265e 100644 --- a/main/src/cgeo/geocaching/files/GPXParser.java +++ b/main/src/cgeo/geocaching/files/GPXParser.java @@ -8,6 +8,8 @@ import cgeo.geocaching.R; import cgeo.geocaching.Trackable; import cgeo.geocaching.Waypoint; import cgeo.geocaching.connector.ConnectorFactory; +import cgeo.geocaching.connector.IConnector; +import cgeo.geocaching.connector.capability.ILogin; import cgeo.geocaching.enumerations.CacheSize; import cgeo.geocaching.enumerations.CacheType; import cgeo.geocaching.enumerations.LoadFlags; @@ -120,12 +122,12 @@ public abstract class GPXParser extends FileParser { private final class UserDataListener implements EndTextElementListener { private final int index; - public UserDataListener(int index) { + public UserDataListener(final int index) { this.index = index; } @Override - public void end(String user) { + public void end(final String user) { userData[index] = validate(user); } } @@ -250,13 +252,13 @@ public abstract class GPXParser extends FileParser { } } - protected GPXParser(int listIdIn, String namespaceIn, String versionIn) { + protected GPXParser(final int listIdIn, final String namespaceIn, final String versionIn) { listId = listIdIn; namespace = namespaceIn; version = versionIn; } - static Date parseDate(String inputUntrimmed) throws ParseException { + static Date parseDate(final String inputUntrimmed) throws ParseException { String input = inputUntrimmed.trim(); // remove milliseconds to reduce number of needed patterns final MatcherWrapper matcher = new MatcherWrapper(PATTERN_MILLISECONDS, input); @@ -280,7 +282,7 @@ public abstract class GPXParser extends FileParser { root.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { scriptUrl = body; } }); @@ -289,7 +291,7 @@ public abstract class GPXParser extends FileParser { waypoint.setStartElementListener(new StartElementListener() { @Override - public void start(Attributes attrs) { + public void start(final Attributes attrs) { try { if (attrs.getIndex("lat") > -1 && attrs.getIndex("lon") > -1) { final String latitude = attrs.getValue("lat"); @@ -399,7 +401,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "time").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { try { cache.setHidden(parseDate(body)); } catch (final Exception e) { @@ -412,7 +414,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "name").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { name = body; String content = body.trim(); @@ -431,7 +433,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "desc").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { desc = body; cache.setShortDescription(validate(body)); @@ -442,7 +444,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "cmt").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { cmt = body; cache.setDescription(validate(body)); @@ -453,8 +455,8 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "type").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { - final String[] content = body.split("\\|"); + public void end(final String body) { + final String[] content = StringUtils.split(body, '|'); if (content.length > 0) { type = content[0].toLowerCase(Locale.US).trim(); } @@ -477,7 +479,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "url").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String url) { + public void end(final String url) { final MatcherWrapper matcher = new MatcherWrapper(PATTERN_GUID, url); if (matcher.matches()) { final String guid = matcher.group(1); @@ -497,7 +499,7 @@ public abstract class GPXParser extends FileParser { waypoint.getChild(namespace, "urlname").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String urlName) { + public void end(final String urlName) { if (cache.getName().equals(cache.getGeocode()) && StringUtils.startsWith(cache.getGeocode(), "WM")) { cache.setName(StringUtils.trim(urlName)); } @@ -520,7 +522,7 @@ public abstract class GPXParser extends FileParser { gcCache.setStartElementListener(new StartElementListener() { @Override - public void start(Attributes attrs) { + public void start(final Attributes attrs) { try { if (attrs.getIndex("id") > -1) { cache.setCacheId(attrs.getValue("id")); @@ -541,7 +543,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String cacheName) { + public void end(final String cacheName) { cache.setName(validate(cacheName)); } }); @@ -550,7 +552,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "owner").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String ownerUserId) { + public void end(final String ownerUserId) { cache.setOwnerUserId(validate(ownerUserId)); } }); @@ -559,7 +561,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "placed_by").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String ownerDisplayName) { + public void end(final String ownerDisplayName) { cache.setOwnerDisplayName(validate(ownerDisplayName)); } }); @@ -568,7 +570,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { cache.setType(CacheType.getByPattern(validate(body))); } }); @@ -577,7 +579,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "container").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { cache.setSize(CacheSize.getById(validate(body))); } }); @@ -597,7 +599,7 @@ public abstract class GPXParser extends FileParser { gcAttribute.setStartElementListener(new StartElementListener() { @Override - public void start(Attributes attrs) { + public void start(final Attributes attrs) { try { if (attrs.getIndex("id") > -1 && attrs.getIndex("inc") > -1) { final int attributeId = Integer.parseInt(attrs.getValue("id")); @@ -617,7 +619,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "difficulty").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { try { cache.setDifficulty(Float.parseFloat(body)); } catch (final NumberFormatException e) { @@ -630,7 +632,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "terrain").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { try { cache.setTerrain(Float.parseFloat(body)); } catch (final NumberFormatException e) { @@ -643,7 +645,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "country").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String country) { + public void end(final String country) { if (StringUtils.isBlank(cache.getLocation())) { cache.setLocation(validate(country)); } else { @@ -656,7 +658,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "state").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String state) { + public void end(final String state) { final String trimmedState = state.trim(); if (StringUtils.isNotEmpty(trimmedState)) { // state can be completely empty if (StringUtils.isBlank(cache.getLocation())) { @@ -672,7 +674,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "encoded_hints").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String encoded) { + public void end(final String encoded) { cache.setHint(validate(encoded)); } }); @@ -680,7 +682,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "short_description").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String shortDesc) { + public void end(final String shortDesc) { cache.setShortDescription(validate(shortDesc)); } }); @@ -688,7 +690,7 @@ public abstract class GPXParser extends FileParser { gcCache.getChild(nsGC, "long_description").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String desc) { + public void end(final String desc) { cache.setDescription(validate(desc)); } }); @@ -703,7 +705,7 @@ public abstract class GPXParser extends FileParser { gcTB.setStartElementListener(new StartElementListener() { @Override - public void start(Attributes attrs) { + public void start(final Attributes attrs) { trackable = new Trackable(); try { @@ -733,7 +735,7 @@ public abstract class GPXParser extends FileParser { gcTB.getChild(nsGC, "name").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String tbName) { + public void end(final String tbName) { trackable.setName(validate(tbName)); } }); @@ -747,7 +749,7 @@ public abstract class GPXParser extends FileParser { gcLog.setStartElementListener(new StartElementListener() { @Override - public void start(Attributes attrs) { + public void start(final Attributes attrs) { log = new LogEntry("", 0, LogType.UNKNOWN, ""); try { @@ -765,6 +767,13 @@ public abstract class GPXParser extends FileParser { @Override public void end() { if (log.type != LogType.UNKNOWN) { + if (log.type.isFoundLog() && StringUtils.isNotBlank(log.author)) { + final IConnector connector = ConnectorFactory.getConnector(cache); + if (connector instanceof ILogin && StringUtils.equals(log.author, ((ILogin) connector).getUserName())) { + cache.setFound(true); + cache.setVisitedDate(log.date); + } + } logs.add(log); } } @@ -774,7 +783,7 @@ public abstract class GPXParser extends FileParser { gcLog.getChild(nsGC, "date").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { try { log.date = parseDate(body).getTime(); } catch (final Exception e) { @@ -787,7 +796,7 @@ public abstract class GPXParser extends FileParser { gcLog.getChild(nsGC, "type").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { final String logType = validate(body); log.type = LogType.getByType(logType); } @@ -797,7 +806,7 @@ public abstract class GPXParser extends FileParser { gcLog.getChild(nsGC, "finder").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String finderName) { + public void end(final String finderName) { log.author = validate(finderName); } }); @@ -806,7 +815,7 @@ public abstract class GPXParser extends FileParser { gcLog.getChild(nsGC, "text").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String logText) { + public void end(final String logText) { log.log = validate(logText); } }); @@ -814,7 +823,7 @@ public abstract class GPXParser extends FileParser { try { progressStream = new ProgressInputStream(stream); - BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8)); + final BufferedReader reader = new BufferedReader(new InputStreamReader(progressStream, CharEncoding.UTF_8)); Xml.parse(new InvalidXMLCharacterFilterReader(reader), root.getContentHandler()); return DataStore.loadCaches(result, EnumSet.of(LoadFlag.DB_MINIMAL)); } catch (final SAXException e) { @@ -833,7 +842,7 @@ public abstract class GPXParser extends FileParser { gsak.getChild(gsakNamespace, "Watch").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String watchList) { + public void end(final String watchList) { cache.setOnWatchlist(Boolean.valueOf(watchList.trim())); } }); @@ -847,7 +856,7 @@ public abstract class GPXParser extends FileParser { gsak.getChild(gsakNamespace, "Parent").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String body) { + public void end(final String body) { parentCacheCode = body; } }); @@ -855,7 +864,7 @@ public abstract class GPXParser extends FileParser { gsak.getChild(gsakNamespace, "FavPoints").setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String favoritePoints) { + public void end(final String favoritePoints) { try { cache.setFavoritePoints(Integer.parseInt(favoritePoints)); } @@ -894,7 +903,7 @@ public abstract class GPXParser extends FileParser { cgeoVisited.setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String visited) { + public void end(final String visited) { wptVisited = Boolean.valueOf(visited.trim()); } }); @@ -904,7 +913,7 @@ public abstract class GPXParser extends FileParser { cgeoUserDefined.setEndTextElementListener(new EndTextElementListener() { @Override - public void end(String userDefined) { + public void end(final String userDefined) { wptUserDefined = Boolean.valueOf(userDefined.trim()); } }); @@ -917,7 +926,7 @@ public abstract class GPXParser extends FileParser { * @param cache * currently imported cache */ - protected void afterParsing(Geocache cache) { + protected void afterParsing(final Geocache cache) { // can be overridden by sub classes } @@ -930,7 +939,7 @@ public abstract class GPXParser extends FileParser { */ protected abstract Element getCacheParent(Element waypoint); - protected static String validate(String input) { + protected static String validate(final String input) { if ("nil".equalsIgnoreCase(input)) { return ""; } diff --git a/main/src/cgeo/geocaching/files/IFileSelectionView.java b/main/src/cgeo/geocaching/files/IFileSelectionView.java index 5bbc1b2..0407ee4 100644 --- a/main/src/cgeo/geocaching/files/IFileSelectionView.java +++ b/main/src/cgeo/geocaching/files/IFileSelectionView.java @@ -8,7 +8,7 @@ public interface IFileSelectionView { String getCurrentFile(); - void setCurrentFile(String string); + void setCurrentFile(final String name); void close(); diff --git a/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java b/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java index a7a3e1b..8a089d2 100644 --- a/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java +++ b/main/src/cgeo/geocaching/files/InvalidXMLCharacterFilterReader.java @@ -1,98 +1,94 @@ -package cgeo.geocaching.files;
-
-import org.apache.commons.lang3.StringUtils;
-
-import java.io.FilterReader;
-import java.io.IOException;
-import java.io.Reader;
-
-/**
- * Filter reader which can filter out invalid XML characters and character references.
- *
- */
-public class InvalidXMLCharacterFilterReader extends FilterReader
-{
-
- public InvalidXMLCharacterFilterReader(Reader in) {
- super(in);
- }
-
- /**
- * Every overload of {@link Reader#read()} method delegates to this one so
- * it is enough to override only this one. <br />
- * To skip invalid characters this method shifts only valid chars to left
- * and returns decreased value of the original read method. So after last
- * valid character there will be some unused chars in the buffer.
- *
- * @return Number of read valid characters or <code>-1</code> if end of the
- * underling reader was reached.
- */
- @Override
- public int read(char[] cbuf, int off, int len) throws IOException {
- int read = super.read(cbuf, off, len);
- // check for end
- if (read == -1) {
- return -1;
- }
- // target position
- int pos = off - 1;
-
- int entityStart = -1;
- for (int readPos = off; readPos < off + read; readPos++) {
- boolean useChar = true;
- switch (cbuf[readPos]) {
- case '&':
- pos++;
- entityStart = readPos;
- break;
- case ';':
- pos++;
- if (entityStart >= 0) {
- int entityLength = readPos - entityStart + 1;
- if (entityLength <= 5) {
- String entity = new String(cbuf, entityStart, entityLength);
- if (StringUtils.startsWith(entity, "&#")) {
- String numberString = StringUtils.substringBetween(entity, "&#", ";");
- final int value;
- if (StringUtils.startsWith(numberString, "x")) {
- value = Integer.parseInt(numberString.substring(1), 16);
- }
- else {
- value = Integer.parseInt(numberString);
- }
- if (!isValidXMLChar((char) value)) {
- pos -= entityLength;
- useChar = false;
- }
- }
- }
- }
- break;
- default:
- if (isValidXMLChar(cbuf[readPos])) {
- pos++;
- } else {
- continue;
- }
- }
- // copy, and skip unwanted characters
- if (pos < readPos && useChar) {
- cbuf[pos] = cbuf[readPos];
- }
- }
- return pos - off + 1;
- }
-
- private static boolean isValidXMLChar(char c) {
- if ((c == 0x9) ||
- (c == 0xA) ||
- (c == 0xD) ||
- ((c >= 0x20) && (c <= 0xD7FF)) ||
- ((c >= 0xE000) && (c <= 0xFFFD)) ||
- ((c >= 0x10000) && (c <= 0x10FFFF)))
- {
- return true;
- }
- return false;
- }
+package cgeo.geocaching.files; + +import org.apache.commons.lang3.StringUtils; + +import java.io.FilterReader; +import java.io.IOException; +import java.io.Reader; + +/** + * Filter reader which can filter out invalid XML characters and character references. + * + */ +public class InvalidXMLCharacterFilterReader extends FilterReader +{ + + public InvalidXMLCharacterFilterReader(Reader in) { + super(in); + } + + /** + * Every overload of {@link Reader#read()} method delegates to this one so + * it is enough to override only this one. <br /> + * To skip invalid characters this method shifts only valid chars to left + * and returns decreased value of the original read method. So after last + * valid character there will be some unused chars in the buffer. + * + * @return Number of read valid characters or <code>-1</code> if end of the + * underling reader was reached. + */ + @Override + public int read(char[] cbuf, int off, int len) throws IOException { + int read = super.read(cbuf, off, len); + // check for end + if (read == -1) { + return -1; + } + // target position + int pos = off - 1; + + int entityStart = -1; + for (int readPos = off; readPos < off + read; readPos++) { + boolean useChar = true; + switch (cbuf[readPos]) { + case '&': + pos++; + entityStart = readPos; + break; + case ';': + pos++; + if (entityStart >= 0) { + int entityLength = readPos - entityStart + 1; + if (entityLength <= 5) { + String entity = new String(cbuf, entityStart, entityLength); + if (StringUtils.startsWith(entity, "&#")) { + String numberString = StringUtils.substringBetween(entity, "&#", ";"); + final int value; + if (StringUtils.startsWith(numberString, "x")) { + value = Integer.parseInt(numberString.substring(1), 16); + } + else { + value = Integer.parseInt(numberString); + } + if (!isValidXMLChar((char) value)) { + pos -= entityLength; + useChar = false; + } + } + } + } + break; + default: + if (isValidXMLChar(cbuf[readPos])) { + pos++; + } else { + continue; + } + } + // copy, and skip unwanted characters + if (pos < readPos && useChar) { + cbuf[pos] = cbuf[readPos]; + } + } + return pos - off + 1; + } + + private static boolean isValidXMLChar(char c) { + return (c == 0x9) || + (c == 0xA) || + (c == 0xD) || + ((c >= 0x20) && (c <= 0xD7FF)) || + ((c >= 0xE000) && (c <= 0xFFFD)) || + ((c >= 0x10000) && (c <= 0x10FFFF)); + } }
\ No newline at end of file diff --git a/main/src/cgeo/geocaching/files/LocParser.java b/main/src/cgeo/geocaching/files/LocParser.java index 2871d77..13f8cca 100644 --- a/main/src/cgeo/geocaching/files/LocParser.java +++ b/main/src/cgeo/geocaching/files/LocParser.java @@ -88,7 +88,7 @@ public final class LocParser extends FileParser { } // >> premium only - final String[] points = fileContent.split("<waypoint>"); + final String[] points = StringUtils.splitByWholeSeparator(fileContent, "<waypoint>"); // parse coordinates for (String pointString : points) { diff --git a/main/src/cgeo/geocaching/files/SimpleDirChooser.java b/main/src/cgeo/geocaching/files/SimpleDirChooser.java index 2aadf16..0139206 100644 --- a/main/src/cgeo/geocaching/files/SimpleDirChooser.java +++ b/main/src/cgeo/geocaching/files/SimpleDirChooser.java @@ -263,7 +263,7 @@ public class SimpleDirChooser extends AbstractListActivity { private boolean checked = false; private boolean writeable = false; - private static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() { + private final static Comparator<Option> NAME_COMPARATOR = new Comparator<SimpleDirChooser.Option>() { @Override public int compare(final Option lhs, final Option rhs) { diff --git a/main/src/cgeo/geocaching/filter/PopularityFilter.java b/main/src/cgeo/geocaching/filter/PopularityFilter.java index a0244b9..0fc807d 100644 --- a/main/src/cgeo/geocaching/filter/PopularityFilter.java +++ b/main/src/cgeo/geocaching/filter/PopularityFilter.java @@ -29,8 +29,7 @@ class PopularityFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { final List<IFilter> filters = new ArrayList<>(FAVORITES.length); - for (int i = 0; i < FAVORITES.length; i++) { - final int minRange = FAVORITES[i]; + for (final int minRange : FAVORITES) { final int maxRange = Integer.MAX_VALUE; final String range = "> " + minRange; final String name = CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.favorite_points, minRange, range); diff --git a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java index a04f219..f7ac4db 100644 --- a/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java +++ b/main/src/cgeo/geocaching/filter/PopularityRatioFilter.java @@ -16,7 +16,7 @@ class PopularityRatioFilter extends AbstractFilter { private final int minRatio; private final int maxRatio; - public PopularityRatioFilter(String name, final int minRatio, final int maxRatio) { + public PopularityRatioFilter(final String name, final int minRatio, final int maxRatio) { super(name); this.minRatio = minRatio; this.maxRatio = maxRatio; @@ -35,11 +35,11 @@ class PopularityRatioFilter extends AbstractFilter { return ratio > minRatio && ratio <= maxRatio; } - private static int getFindsCount(Geocache cache) { + private static int getFindsCount(final Geocache cache) { if (cache.getLogCounts().isEmpty()) { cache.setLogCounts(DataStore.loadLogCounts(cache.getGeocode())); } - Integer logged = cache.getLogCounts().get(LogType.FOUND_IT); + final Integer logged = cache.getLogCounts().get(LogType.FOUND_IT); if (logged != null) { return logged; } @@ -53,10 +53,9 @@ class PopularityRatioFilter extends AbstractFilter { @Override public List<IFilter> getFilters() { final List<IFilter> filters = new ArrayList<>(RATIOS.length); - for (int i = 0; i < RATIOS.length; i++) { - final int minRange = RATIOS[i]; + for (final int minRange : RATIOS) { final int maxRange = Integer.MAX_VALUE; - final String name = "> " + minRange + " " + CgeoApplication.getInstance().getResources().getString(R.string.percent_favorite_points); + final String name = CgeoApplication.getInstance().getResources().getString(R.string.more_than_percent_favorite_points, minRange); filters.add(new PopularityRatioFilter(name, minRange, maxRange)); } return filters; diff --git a/main/src/cgeo/geocaching/gcvote/GCVote.java b/main/src/cgeo/geocaching/gcvote/GCVote.java index 8de3edc..a5b31f0 100644 --- a/main/src/cgeo/geocaching/gcvote/GCVote.java +++ b/main/src/cgeo/geocaching/gcvote/GCVote.java @@ -8,31 +8,27 @@ import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.LeastRecentlyUsedMap; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.MatcherWrapper; +import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; +import org.apache.commons.io.Charsets; +import org.apache.commons.io.IOUtils; import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.tuple.ImmutablePair; import org.eclipse.jdt.annotation.NonNull; +import org.xmlpull.v1.XmlPullParser; +import org.xmlpull.v1.XmlPullParserFactory; +import java.io.InputStream; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; import java.util.HashMap; import java.util.List; -import java.util.Locale; import java.util.Map; -import java.util.regex.Pattern; public final class GCVote { public static final float NO_RATING = 0; - private static final Pattern PATTERN_LOG_IN = Pattern.compile("loggedIn='([^']+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_GUID = Pattern.compile("cacheId='([^']+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_WAYPOINT = Pattern.compile("waypoint='([^']+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_RATING = Pattern.compile("voteAvg='([0-9.]+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_VOTES = Pattern.compile("voteCnt='([0-9]+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_VOTE = Pattern.compile("voteUser='([0-9.]+)'", Pattern.CASE_INSENSITIVE); - private static final Pattern PATTERN_VOTE_ELEMENT = Pattern.compile("<vote ([^>]+)>", Pattern.CASE_INSENSITIVE); private static final int MAX_CACHED_RATINGS = 1000; private static final LeastRecentlyUsedMap<String, GCVoteRating> RATINGS_CACHE = new LeastRecentlyUsedMap.LruCache<>(MAX_CACHED_RATINGS); @@ -71,124 +67,68 @@ public final class GCVote { * @param geocodes * @return */ + @NonNull private static Map<String, GCVoteRating> getRating(final List<String> guids, final List<String> geocodes) { if (guids == null && geocodes == null) { - return null; + return Collections.emptyMap(); } - final Map<String, GCVoteRating> ratings = new HashMap<>(); + final Parameters params = new Parameters("version", "cgeo"); + final ImmutablePair<String, String> login = Settings.getGCvoteLogin(); + if (login != null) { + params.put("userName", login.left, "password", login.right); + } + // use guid or gccode for lookup + final boolean requestByGuids = CollectionUtils.isNotEmpty(guids); + if (requestByGuids) { + params.put("cacheIds", StringUtils.join(guids, ',')); + } else { + params.put("waypoints", StringUtils.join(geocodes, ',')); + } + final InputStream response = Network.getResponseStream(Network.getRequest("http://gcvote.com/getVotes.php", params)); + if (response == null) { + return Collections.emptyMap(); + } try { - final Parameters params = new Parameters(); - if (Settings.isLogin()) { - final ImmutablePair<String, String> login = Settings.getGCvoteLogin(); - if (login != null) { - params.put("userName", login.left, "password", login.right); - } - } - // use guid or gccode for lookup - boolean requestByGuids = true; - if (guids != null && !guids.isEmpty()) { - params.put("cacheIds", StringUtils.join(guids.toArray(), ',')); - } else { - params.put("waypoints", StringUtils.join(geocodes.toArray(), ',')); - requestByGuids = false; - } - params.put("version", "cgeo"); - final String page = Network.getResponseData(Network.getRequest("http://gcvote.com/getVotes.php", params)); - if (page == null) { - return null; - } - - final MatcherWrapper matcherVoteElement = new MatcherWrapper(PATTERN_VOTE_ELEMENT, page); - while (matcherVoteElement.find()) { - String voteData = matcherVoteElement.group(1); - if (voteData == null) { - continue; - } - - String id = null; - String guid = null; - final MatcherWrapper matcherGuid = new MatcherWrapper(PATTERN_GUID, voteData); - if (matcherGuid.find()) { - if (matcherGuid.groupCount() > 0) { - guid = matcherGuid.group(1); - if (requestByGuids) { - id = guid; - } - } - } - if (!requestByGuids) { - final MatcherWrapper matcherWp = new MatcherWrapper(PATTERN_WAYPOINT, voteData); - if (matcherWp.find()) { - if (matcherWp.groupCount() > 0) { - id = matcherWp.group(1); - } - } - } - if (id == null) { - continue; - } - - boolean loggedIn = false; - final MatcherWrapper matcherLoggedIn = new MatcherWrapper(PATTERN_LOG_IN, page); - if (matcherLoggedIn.find()) { - if (matcherLoggedIn.groupCount() > 0) { - if (matcherLoggedIn.group(1).equalsIgnoreCase("true")) { - loggedIn = true; - } - } - } - - float rating = NO_RATING; - try { - final MatcherWrapper matcherRating = new MatcherWrapper(PATTERN_RATING, voteData); - if (matcherRating.find()) { - rating = Float.parseFloat(matcherRating.group(1)); - } - } catch (NumberFormatException e) { - Log.w("GCVote.getRating: Failed to parse rating"); - } - if (!isValidRating(rating)) { - continue; - } - - int votes = -1; - try { - final MatcherWrapper matcherVotes = new MatcherWrapper(PATTERN_VOTES, voteData); - if (matcherVotes.find()) { - votes = Integer.parseInt(matcherVotes.group(1)); - } - } catch (NumberFormatException e) { - Log.w("GCVote.getRating: Failed to parse vote count"); - } - if (votes < 0) { - continue; - } + return getRatingsFromXMLResponse(response, requestByGuids); + } finally { + IOUtils.closeQuietly(response); + } + } - float myVote = NO_RATING; - if (loggedIn) { - try { - final MatcherWrapper matcherVote = new MatcherWrapper(PATTERN_VOTE, voteData); - if (matcherVote.find()) { - myVote = Float.parseFloat(matcherVote.group(1)); - } - } catch (NumberFormatException e) { - Log.w("GCVote.getRating: Failed to parse user's vote"); + static Map<String, GCVoteRating> getRatingsFromXMLResponse(@NonNull final InputStream response, final boolean requestByGuids) { + try { + final XmlPullParserFactory factory = XmlPullParserFactory.newInstance(); + final XmlPullParser xpp = factory.newPullParser(); + xpp.setInput(response, Charsets.UTF_8.name()); + boolean loggedIn = false; + final Map<String, GCVoteRating> ratings = new HashMap<>(); + int eventType = xpp.getEventType(); + while (eventType != XmlPullParser.END_DOCUMENT) { + if (eventType == XmlPullParser.START_TAG) { + final String tagName = xpp.getName(); + if (StringUtils.equals(tagName, "vote")) { + final String guid = xpp.getAttributeValue(null, "cacheId"); + final String id = requestByGuids ? guid : xpp.getAttributeValue(null, "waypoint"); + final float myVote = loggedIn ? Float.parseFloat(xpp.getAttributeValue(null, "voteUser")) : 0; + final GCVoteRating voteRating = new GCVoteRating(Float.parseFloat(xpp.getAttributeValue(null, "voteAvg")), + Integer.parseInt(xpp.getAttributeValue(null, "voteCnt")), + myVote); + ratings.put(id, voteRating); + } else if (StringUtils.equals(tagName, "votes")) { + loggedIn = StringUtils.equals(xpp.getAttributeValue(null, "loggedIn"), "true"); } } - - if (StringUtils.isNotBlank(id)) { - GCVoteRating gcvoteRating = new GCVoteRating(rating, votes, myVote); - ratings.put(id, gcvoteRating); - RATINGS_CACHE.put(guid, gcvoteRating); - } + eventType = xpp.next(); } - } catch (RuntimeException e) { - Log.e("GCVote.getRating", e); - } + RATINGS_CACHE.putAll(ratings); + return ratings; + } catch (final Exception e) { + Log.e("Cannot parse GC vote result", e); + return Collections.emptyMap(); - return ratings; + } } /** @@ -236,16 +176,14 @@ public final class GCVote { try { final Map<String, GCVoteRating> ratings = GCVote.getRating(null, geocodes); - if (MapUtils.isNotEmpty(ratings)) { - // save found cache coordinates - for (Geocache cache : caches) { - if (ratings.containsKey(cache.getGeocode())) { - GCVoteRating rating = ratings.get(cache.getGeocode()); + // save found cache coordinates + for (Geocache cache : caches) { + if (ratings.containsKey(cache.getGeocode())) { + GCVoteRating rating = ratings.get(cache.getGeocode()); - cache.setRating(rating.getRating()); - cache.setVotes(rating.getVotes()); - cache.setMyVote(rating.getMyVote()); - } + cache.setRating(rating.getRating()); + cache.setVotes(rating.getVotes()); + cache.setMyVote(rating.getMyVote()); } } } catch (Exception e) { @@ -275,10 +213,6 @@ public final class GCVote { return rating >= MIN_RATING && rating <= MAX_RATING; } - public static String getRatingText(final float rating) { - return String.format(Locale.getDefault(), "%.1f", rating); - } - public static boolean isVotingPossible(final Geocache cache) { return Settings.isGCvoteLogin() && StringUtils.isNotBlank(cache.getGuid()) && cache.supportsGCVote(); } diff --git a/main/src/cgeo/geocaching/geopoint/Viewport.java b/main/src/cgeo/geocaching/geopoint/Viewport.java index ba0e040..a48b0a1 100644 --- a/main/src/cgeo/geocaching/geopoint/Viewport.java +++ b/main/src/cgeo/geocaching/geopoint/Viewport.java @@ -79,6 +79,22 @@ public final class Viewport { && coords.getLatitudeE6() <= topRight.getLatitudeE6(); } + /** + * Count the number of points present in the viewport. + * + * @param points a collection of (possibly null) points + * @return the number of non-null points in the viewport + */ + public int count(final @NonNull Collection<? extends ICoordinates> points) { + int total = 0; + for (ICoordinates point: points) { + if (point != null && contains(point)) { + total += 1; + } + } + return total; + } + @Override public String toString() { return "(" + bottomLeft.toString() + "," + topRight.toString() + ")"; diff --git a/main/src/cgeo/geocaching/list/AbstractList.java b/main/src/cgeo/geocaching/list/AbstractList.java index 9b57b3a..5a20b9d 100644 --- a/main/src/cgeo/geocaching/list/AbstractList.java +++ b/main/src/cgeo/geocaching/list/AbstractList.java @@ -8,7 +8,7 @@ public abstract class AbstractList { public final int id; public final String title; - private static SparseArray<AbstractList> LISTS = new SparseArray<>(); + private final static SparseArray<AbstractList> LISTS = new SparseArray<>(); public AbstractList(final int id, final String title) { this.id = id; diff --git a/main/src/cgeo/geocaching/list/StoredList.java b/main/src/cgeo/geocaching/list/StoredList.java index 53632a0..abb6af1 100644 --- a/main/src/cgeo/geocaching/list/StoredList.java +++ b/main/src/cgeo/geocaching/list/StoredList.java @@ -24,12 +24,12 @@ import java.util.Comparator; import java.util.List; public final class StoredList extends AbstractList { - public static final int TEMPORARY_LIST_ID = 0; - public static final StoredList TEMPORARY_LIST = new StoredList(TEMPORARY_LIST_ID, "<temporary>", 0); // Never displayed + private static final int TEMPORARY_LIST_ID = 0; + public static final StoredList TEMPORARY_LIST = new StoredList(TEMPORARY_LIST_ID, "<temporary>", 0); // Never displayed public static final int STANDARD_LIST_ID = 1; private final int count; // this value is only valid as long as the list is not changed by other database operations - public StoredList(int id, String title, int count) { + public StoredList(final int id, final String title, final int count) { super(id, title); this.count = count; } @@ -48,7 +48,7 @@ public final class StoredList extends AbstractList { } @Override - public boolean equals(Object obj) { + public boolean equals(final Object obj) { if (this == obj) { return true; } @@ -69,10 +69,6 @@ public final class StoredList extends AbstractList { res = app.getResources(); } - public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards) { - promptForListSelection(titleId, runAfterwards, false, -1); - } - public void promptForListSelection(final int titleId, @NonNull final Action1<Integer> runAfterwards, final boolean onlyConcreteLists, final int exceptListId) { promptForListSelection(titleId, runAfterwards, onlyConcreteLists, exceptListId, StringUtils.EMPTY); } @@ -81,18 +77,18 @@ public final class StoredList extends AbstractList { final List<AbstractList> lists = getMenuLists(onlyConcreteLists, exceptListId); final List<CharSequence> listsTitle = new ArrayList<>(); - for (AbstractList list : lists) { + for (final AbstractList list : lists) { listsTitle.add(list.getTitleAndCount()); } final CharSequence[] items = new CharSequence[listsTitle.size()]; final Activity activity = activityRef.get(); - AlertDialog.Builder builder = new AlertDialog.Builder(activity); + final AlertDialog.Builder builder = new AlertDialog.Builder(activity); builder.setTitle(res.getString(titleId)); builder.setItems(listsTitle.toArray(items), new DialogInterface.OnClickListener() { @Override - public void onClick(DialogInterface dialogInterface, int itemId) { + public void onClick(final DialogInterface dialogInterface, final int itemId) { final AbstractList list = lists.get(itemId); if (list == PseudoList.NEW_LIST) { // create new list on the fly @@ -106,12 +102,12 @@ public final class StoredList extends AbstractList { builder.create().show(); } - public static List<AbstractList> getMenuLists(boolean onlyConcreteLists, int exceptListId) { + public static List<AbstractList> getMenuLists(final boolean onlyConcreteLists, final int exceptListId) { final List<AbstractList> lists = new ArrayList<>(); lists.addAll(getSortedLists()); - if (exceptListId > StoredList.TEMPORARY_LIST_ID) { - StoredList exceptList = DataStore.getList(exceptListId); + if (exceptListId > StoredList.TEMPORARY_LIST.id) { + final StoredList exceptList = DataStore.getList(exceptListId); if (exceptList != null) { lists.remove(exceptList); } @@ -138,7 +134,7 @@ public final class StoredList extends AbstractList { Collections.sort(lists, new Comparator<StoredList>() { @Override - public int compare(StoredList lhs, StoredList rhs) { + public int compare(final StoredList lhs, final StoredList rhs) { // have the standard list at the top if (lhs.id == STANDARD_LIST_ID) { return -1; @@ -153,7 +149,7 @@ public final class StoredList extends AbstractList { return lists; } - public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, String newListName) { + public void promptForListCreation(@NonNull final Action1<Integer> runAfterwards, final String newListName) { handleListNameInput(newListName, R.string.list_dialog_create_title, R.string.list_dialog_create, new Action1<String>() { // We need to update the list cache by creating a new StoredList object here. @@ -177,7 +173,7 @@ public final class StoredList extends AbstractList { }); } - private void handleListNameInput(final String defaultValue, int dialogTitle, int buttonTitle, final Action1<String> runnable) { + private void handleListNameInput(final String defaultValue, final int dialogTitle, final int buttonTitle, final Action1<String> runnable) { final Activity activity = activityRef.get(); if (activity == null) { return; @@ -187,7 +183,7 @@ public final class StoredList extends AbstractList { @Override public void call(final String input) { // remove whitespaces added by autocompletion of Android keyboard - String listName = StringUtils.trim(input); + final String listName = StringUtils.trim(input); if (StringUtils.isNotBlank(listName)) { runnable.call(listName); } @@ -225,8 +221,8 @@ public final class StoredList extends AbstractList { /** * Return the given list, if it is a concrete list. Return the default list otherwise. */ - public static int getConcreteList(int listId) { - if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST_ID || listId == PseudoList.HISTORY_LIST.id) { + public static int getConcreteList(final int listId) { + if (listId == PseudoList.ALL_LIST.id || listId == TEMPORARY_LIST.id || listId == PseudoList.HISTORY_LIST.id) { return STANDARD_LIST_ID; } return listId; diff --git a/main/src/cgeo/geocaching/maps/CGeoMap.java b/main/src/cgeo/geocaching/maps/CGeoMap.java index 8aae0cc..d257cc6 100644 --- a/main/src/cgeo/geocaching/maps/CGeoMap.java +++ b/main/src/cgeo/geocaching/maps/CGeoMap.java @@ -6,6 +6,7 @@ import cgeo.geocaching.CacheListActivity; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Geocache; +import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.SearchResult; import cgeo.geocaching.Waypoint; @@ -31,7 +32,6 @@ import cgeo.geocaching.maps.interfaces.MapProvider; import cgeo.geocaching.maps.interfaces.MapSource; import cgeo.geocaching.maps.interfaces.MapViewImpl; import cgeo.geocaching.maps.interfaces.OnMapDragListener; -import cgeo.geocaching.sensors.DirectionProvider; import cgeo.geocaching.sensors.GeoDirHandler; import cgeo.geocaching.sensors.IGeoData; import cgeo.geocaching.settings.Settings; @@ -125,16 +125,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory { private static final int UPDATE_PROGRESS = 0; private static final int FINISHED_LOADING_DETAILS = 1; - //Menu - private static final String EXTRAS_GEOCODE = "geocode"; - private static final String EXTRAS_COORDS = "coords"; - private static final String EXTRAS_WPTTYPE = "wpttype"; - private static final String EXTRAS_MAPSTATE = "mapstate"; - private static final String EXTRAS_SEARCH = "search"; - private static final String EXTRAS_MAP_TITLE = "mapTitle"; - private static final String EXTRAS_MAP_MODE = "mapMode"; - private static final String EXTRAS_LIVE_ENABLED = "liveEnabled"; - private static final String BUNDLE_MAP_SOURCE = "mapSource"; private static final String BUNDLE_MAP_STATE = "mapState"; private static final String BUNDLE_LIVE_ENABLED = "liveEnabled"; @@ -204,7 +194,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory { private static BlockingQueue<Runnable> downloadQueue = new ArrayBlockingQueue<>(1); private static ThreadPoolExecutor downloadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, downloadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); private static BlockingQueue<Runnable> loadQueue = new ArrayBlockingQueue<>(1); - private static ThreadPoolExecutor loadExecutor = new ThreadPoolExecutor(1, 1, 60, TimeUnit.SECONDS, loadQueue, new ThreadPoolExecutor.DiscardOldestPolicy()); // handlers /** Updates the titles */ @@ -373,21 +362,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { } protected void countVisibleCaches() { - final List<Geocache> protectedCaches = caches.getAsList(); - - int count = 0; - if (!protectedCaches.isEmpty()) { - final Viewport viewport = mapView.getViewport(); - - for (final Geocache cache : protectedCaches) { - if (cache != null && cache.getCoords() != null) { - if (viewport.contains(cache)) { - count++; - } - } - } - } - cachesCnt = count; + cachesCnt = mapView.getViewport().count(caches.getAsList()); } @Override @@ -417,14 +392,14 @@ public class CGeoMap extends AbstractMap implements ViewFactory { // Get parameters from the intent final Bundle extras = activity.getIntent().getExtras(); if (extras != null) { - mapMode = (MapMode) extras.get(EXTRAS_MAP_MODE); - isLiveEnabled = extras.getBoolean(EXTRAS_LIVE_ENABLED, false); - searchIntent = extras.getParcelable(EXTRAS_SEARCH); - geocodeIntent = extras.getString(EXTRAS_GEOCODE); - coordsIntent = extras.getParcelable(EXTRAS_COORDS); - waypointTypeIntent = WaypointType.findById(extras.getString(EXTRAS_WPTTYPE)); - mapStateIntent = extras.getIntArray(EXTRAS_MAPSTATE); - mapTitle = extras.getString(EXTRAS_MAP_TITLE); + mapMode = (MapMode) extras.get(Intents.EXTRA_MAP_MODE); + isLiveEnabled = extras.getBoolean(Intents.EXTRA_LIVE_ENABLED, false); + searchIntent = extras.getParcelable(Intents.EXTRA_SEARCH); + geocodeIntent = extras.getString(Intents.EXTRA_GEOCODE); + coordsIntent = extras.getParcelable(Intents.EXTRA_COORDS); + waypointTypeIntent = WaypointType.findById(extras.getString(Intents.EXTRA_WPTTYPE)); + mapStateIntent = extras.getIntArray(Intents.EXTRA_MAPSTATE); + mapTitle = extras.getString(Intents.EXTRA_MAP_TITLE); } else { mapMode = MapMode.LIVE; @@ -506,7 +481,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { } prepareFilterBar(); - if (!app.isLiveMapHintShownInThisSession() && !Settings.getHideLiveMapHint() && Settings.getLiveMapHintShowCount() <= 3) { + if (!app.isLiveMapHintShownInThisSession() && Settings.getLiveMapHintShowCount() <= 3) { LiveMapInfoDialogBuilder.create(activity).show(); } } @@ -716,7 +691,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { public void call(final Integer selectedListId) { storeCaches(geocodes, selectedListId); } - }, true, StoredList.TEMPORARY_LIST_ID); + }, true, StoredList.TEMPORARY_LIST.id); } else { storeCaches(geocodes, StoredList.STANDARD_LIST_ID); } @@ -872,19 +847,19 @@ public class CGeoMap extends AbstractMap implements ViewFactory { // prepare information to restart a similar view final Intent mapIntent = new Intent(activity, Settings.getMapProvider().getMapClass()); - mapIntent.putExtra(EXTRAS_SEARCH, searchIntent); - mapIntent.putExtra(EXTRAS_GEOCODE, geocodeIntent); + mapIntent.putExtra(Intents.EXTRA_SEARCH, searchIntent); + mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocodeIntent); if (coordsIntent != null) { - mapIntent.putExtra(EXTRAS_COORDS, coordsIntent); + mapIntent.putExtra(Intents.EXTRA_COORDS, coordsIntent); } - mapIntent.putExtra(EXTRAS_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null); - mapIntent.putExtra(EXTRAS_MAP_TITLE, mapTitle); - mapIntent.putExtra(EXTRAS_MAP_MODE, mapMode); - mapIntent.putExtra(EXTRAS_LIVE_ENABLED, isLiveEnabled); + mapIntent.putExtra(Intents.EXTRA_WPTTYPE, waypointTypeIntent != null ? waypointTypeIntent.id : null); + mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, mapTitle); + mapIntent.putExtra(Intents.EXTRA_MAP_MODE, mapMode); + mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, isLiveEnabled); final int[] mapState = currentMapState(); if (mapState != null) { - mapIntent.putExtra(EXTRAS_MAPSTATE, mapState); + mapIntent.putExtra(Intents.EXTRA_MAPSTATE, mapState); } // start the new map @@ -917,7 +892,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { // Set center of map to my location if appropriate. private void myLocationInMiddle(final IGeoData geo) { - if (followMyLocation && !geo.isPseudoLocation()) { + if (followMyLocation) { centerMap(geo.getCoords()); } } @@ -933,8 +908,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { // minimum change of location in fraction of map width/height (whatever is smaller) for position overlay update private static final float MIN_LOCATION_DELTA = 0.01f; - Location currentLocation = new Location(""); - boolean locationValid = false; + Location currentLocation = CgeoApplication.getInstance().currentGeo().getLocation(); float currentHeading; private long timeLastPositionOverlayCalculation = 0; @@ -949,15 +923,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory { @Override public void updateGeoDir(final IGeoData geo, final float dir) { - if (geo.isPseudoLocation()) { - locationValid = false; - } else { - locationValid = true; - - currentLocation = geo.getLocation(); - currentHeading = DirectionProvider.getDirectionNow(dir); - repaintPositionOverlay(); - } + currentLocation = geo.getLocation(); + currentHeading = AngleUtils.getDirectionNow(dir); + repaintPositionOverlay(); } /** @@ -971,16 +939,16 @@ public class CGeoMap extends AbstractMap implements ViewFactory { try { final CGeoMap map = mapRef.get(); if (map != null) { - final boolean needsRepaintForDistance = needsRepaintForDistance(); + final boolean needsRepaintForDistanceOrAccuracy = needsRepaintForDistanceOrAccuracy(); final boolean needsRepaintForHeading = needsRepaintForHeading(); - if (needsRepaintForDistance) { + if (needsRepaintForDistanceOrAccuracy) { if (map.followMyLocation) { map.centerMap(new Geopoint(currentLocation)); } } - if (needsRepaintForDistance || needsRepaintForHeading) { + if (needsRepaintForDistanceOrAccuracy || needsRepaintForHeading) { map.overlayPositionAndScale.setCoordinates(currentLocation); map.overlayPositionAndScale.setHeading(currentHeading); map.mapView.repaintRequired(map.overlayPositionAndScale); @@ -1000,11 +968,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { return Math.abs(AngleUtils.difference(currentHeading, map.overlayPositionAndScale.getHeading())) > MIN_HEADING_DELTA; } - boolean needsRepaintForDistance() { - if (!locationValid) { - return false; - } - + boolean needsRepaintForDistanceOrAccuracy() { final CGeoMap map = mapRef.get(); if (map == null) { return false; @@ -1013,6 +977,9 @@ public class CGeoMap extends AbstractMap implements ViewFactory { float dist = Float.MAX_VALUE; if (lastLocation != null) { + if (lastLocation.getAccuracy() != currentLocation.getAccuracy()) { + return true; + } dist = currentLocation.distanceTo(lastLocation); } @@ -1039,7 +1006,7 @@ public class CGeoMap extends AbstractMap implements ViewFactory { displayPoint(coordsIntent); loadTimer = Subscriptions.empty(); } else { - loadTimer = startLoadTimer(); + loadTimer = Schedulers.newThread().createWorker().schedulePeriodically(new LoadTimerAction(this), 0, 250, TimeUnit.MILLISECONDS); } return loadTimer; } @@ -1096,13 +1063,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory { } /** - * loading timer Triggers every 250ms and checks for viewport change and starts a {@link LoadRunnable}. - */ - private Subscription startLoadTimer() { - return Schedulers.newThread().createWorker().schedulePeriodically(new LoadTimerAction(this), 0, 250, TimeUnit.MILLISECONDS); - } - - /** * get if map is loading something * * @return @@ -1252,9 +1212,6 @@ public class CGeoMap extends AbstractMap implements ViewFactory { //render displayExecutor.execute(new DisplayRunnable(this)); - } catch (final ThreadDeath e) { - Log.d("DownloadThread stopped"); - displayHandler.sendEmptyMessage(UPDATE_TITLE); } finally { showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); // hide progress } @@ -1304,19 +1261,14 @@ public class CGeoMap extends AbstractMap implements ViewFactory { } itemsToDisplay.add(getCacheItem(cache)); } - - overlayCaches.updateItems(itemsToDisplay); - displayHandler.sendEmptyMessage(INVALIDATE_MAP); - - } else { + } + // don't add other waypoints to overlayCaches if just one point should be displayed + if (coordsIntent == null) { overlayCaches.updateItems(itemsToDisplay); - displayHandler.sendEmptyMessage(INVALIDATE_MAP); } + displayHandler.sendEmptyMessage(INVALIDATE_MAP); displayHandler.sendEmptyMessage(UPDATE_TITLE); - } catch (final ThreadDeath e) { - Log.d("DisplayThread stopped"); - displayHandler.sendEmptyMessage(UPDATE_TITLE); } finally { showProgressHandler.sendEmptyMessage(HIDE_PROGRESS); } @@ -1612,42 +1564,41 @@ public class CGeoMap extends AbstractMap implements ViewFactory { public static void startActivitySearch(final Activity fromActivity, final SearchResult search, final String title) { final Intent mapIntent = newIntent(fromActivity); - mapIntent.putExtra(EXTRAS_SEARCH, search); - mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIST); - mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false); + mapIntent.putExtra(Intents.EXTRA_SEARCH, search); + mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIST); + mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false); if (StringUtils.isNotBlank(title)) { - mapIntent.putExtra(CGeoMap.EXTRAS_MAP_TITLE, title); + mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title); } fromActivity.startActivity(mapIntent); } - public static void startActivityLiveMap(final Activity fromActivity) { - final Intent mapIntent = newIntent(fromActivity); - mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.LIVE); - mapIntent.putExtra(EXTRAS_LIVE_ENABLED, Settings.isLiveMap()); - fromActivity.startActivity(mapIntent); + public static Intent getLiveMapIntent(final Activity fromActivity) { + return newIntent(fromActivity) + .putExtra(Intents.EXTRA_MAP_MODE, MapMode.LIVE) + .putExtra(Intents.EXTRA_LIVE_ENABLED, Settings.isLiveMap()); } public static void startActivityCoords(final Activity fromActivity, final Geopoint coords, final WaypointType type, final String title) { final Intent mapIntent = newIntent(fromActivity); - mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.COORDS); - mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false); - mapIntent.putExtra(EXTRAS_COORDS, coords); + mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.COORDS); + mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false); + mapIntent.putExtra(Intents.EXTRA_COORDS, coords); if (type != null) { - mapIntent.putExtra(EXTRAS_WPTTYPE, type.id); + mapIntent.putExtra(Intents.EXTRA_WPTTYPE, type.id); } if (StringUtils.isNotBlank(title)) { - mapIntent.putExtra(EXTRAS_MAP_TITLE, title); + mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, title); } fromActivity.startActivity(mapIntent); } public static void startActivityGeoCode(final Activity fromActivity, final String geocode) { final Intent mapIntent = newIntent(fromActivity); - mapIntent.putExtra(EXTRAS_MAP_MODE, MapMode.SINGLE); - mapIntent.putExtra(EXTRAS_LIVE_ENABLED, false); - mapIntent.putExtra(EXTRAS_GEOCODE, geocode); - mapIntent.putExtra(EXTRAS_MAP_TITLE, geocode); + mapIntent.putExtra(Intents.EXTRA_MAP_MODE, MapMode.SINGLE); + mapIntent.putExtra(Intents.EXTRA_LIVE_ENABLED, false); + mapIntent.putExtra(Intents.EXTRA_GEOCODE, geocode); + mapIntent.putExtra(Intents.EXTRA_MAP_TITLE, geocode); fromActivity.startActivity(mapIntent); } diff --git a/main/src/cgeo/geocaching/maps/MapActivity.java b/main/src/cgeo/geocaching/maps/MapActivity.java new file mode 100644 index 0000000..28668ca --- /dev/null +++ b/main/src/cgeo/geocaching/maps/MapActivity.java @@ -0,0 +1,17 @@ +package cgeo.geocaching.maps; + +import android.app.Activity; +import android.os.Bundle; + +/** + * This activity provides an entry point for external intent calls, and then forwards to the currently used map activity + * implementation. + */ +public class MapActivity extends Activity { + @Override + protected void onCreate(final Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + startActivity(CGeoMap.getLiveMapIntent(this)); + finish(); + } +} diff --git a/main/src/cgeo/geocaching/maps/MapProviderFactory.java b/main/src/cgeo/geocaching/maps/MapProviderFactory.java index dd4ff0f..212188d 100644 --- a/main/src/cgeo/geocaching/maps/MapProviderFactory.java +++ b/main/src/cgeo/geocaching/maps/MapProviderFactory.java @@ -33,7 +33,8 @@ public class MapProviderFactory { public static boolean isGoogleMapsInstalled() { // Check if API key is available - if (StringUtils.isBlank(CgeoApplication.getInstance().getString(R.string.maps_api_key))) { + final String mapsKey = CgeoApplication.getInstance().getString(R.string.maps_api_key); + if (StringUtils.length(mapsKey) < 30 || StringUtils.contains(mapsKey, "key")) { Log.w("No Google API key available."); return false; } @@ -41,7 +42,7 @@ public class MapProviderFactory { // Check if API is available try { Class.forName("com.google.android.maps.MapActivity"); - } catch (ClassNotFoundException e) { + } catch (final ClassNotFoundException e) { return false; } @@ -59,7 +60,7 @@ public class MapProviderFactory { return provider1 == provider2 && provider1.isSameActivity(source1, source2); } - public static void addMapviewMenuItems(Menu menu) { + public static void addMapviewMenuItems(final Menu menu) { final SubMenu parentMenu = menu.findItem(R.id.menu_select_mapview).getSubMenu(); final int currentSource = Settings.getMapSource().getNumericalId(); @@ -78,8 +79,8 @@ public class MapProviderFactory { * @return the map source, or <tt>null</tt> if <tt>id</tt> does not correspond to a registered map source */ @Nullable - public static MapSource getMapSource(int id) { - for (MapSource mapSource : mapSources) { + public static MapSource getMapSource(final int id) { + for (final MapSource mapSource : mapSources) { if (mapSource.getNumericalId() == id) { return mapSource; } @@ -109,7 +110,7 @@ public class MapProviderFactory { */ public static void deleteOfflineMapSources() { final ArrayList<MapSource> deletion = new ArrayList<>(); - for (MapSource mapSource : mapSources) { + for (final MapSource mapSource : mapSources) { if (mapSource instanceof MapsforgeMapProvider.OfflineMapSource) { deletion.add(mapSource); } diff --git a/main/src/cgeo/geocaching/network/HtmlImage.java b/main/src/cgeo/geocaching/network/HtmlImage.java index 31edc9f..ab902d2 100644 --- a/main/src/cgeo/geocaching/network/HtmlImage.java +++ b/main/src/cgeo/geocaching/network/HtmlImage.java @@ -89,7 +89,7 @@ public class HtmlImage implements Html.ImageGetter { final private int maxWidth; final private int maxHeight; final private Resources resources; - final private TextView view; + protected final TextView view; // Background loading final private PublishSubject<Observable<String>> loading = PublishSubject.create(); @@ -154,6 +154,10 @@ public class HtmlImage implements Html.ImageGetter { if (view == null) { return drawable.toBlocking().lastOrDefault(null); } + return getContainerDrawable(drawable); + } + + protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) { return new ContainerDrawable(view, drawable); } @@ -162,17 +166,17 @@ public class HtmlImage implements Html.ImageGetter { public Observable<BitmapDrawable> fetchDrawable(final String url) { if (StringUtils.isBlank(url) || ImageUtils.containsPattern(url, BLOCKED)) { - return Observable.from(ImageUtils.getTransparent1x1Drawable(resources)); + return Observable.just(ImageUtils.getTransparent1x1Drawable(resources)); } // Explicit local file URLs are loaded from the filesystem regardless of their age. The IO part is short // enough to make the whole operation on the computation scheduler. if (FileUtils.isFileUrl(url)) { - return Observable.defer(new Func0<Observable<? extends BitmapDrawable>>() { + return Observable.defer(new Func0<Observable<BitmapDrawable>>() { @Override - public Observable<? extends BitmapDrawable> call() { + public Observable<BitmapDrawable> call() { final Bitmap bitmap = loadCachedImage(FileUtils.urlToFile(url), true).getLeft(); - return bitmap != null ? Observable.from(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty(); + return bitmap != null ? Observable.just(ImageUtils.scaleBitmapToFitDisplay(bitmap)) : Observable.<BitmapDrawable>empty(); } }).subscribeOn(RxUtils.computationScheduler); } @@ -208,12 +212,7 @@ public class HtmlImage implements Html.ImageGetter { private Pair<BitmapDrawable, Boolean> loadFromDisk() { final Pair<Bitmap, Boolean> loadResult = loadImageFromStorage(url, pseudoGeocode, shared); - final Bitmap bitmap = loadResult.getLeft(); - return new ImmutablePair<>(bitmap != null ? - ImageUtils.scaleBitmapToFitDisplay(bitmap) : - null, - loadResult.getRight() - ); + return scaleImage(loadResult); } private void downloadAndSave(final Subscriber<? super BitmapDrawable> subscriber) { @@ -254,6 +253,15 @@ public class HtmlImage implements Html.ImageGetter { }); } + @SuppressWarnings("static-method") + protected Pair<BitmapDrawable, Boolean> scaleImage(final Pair<Bitmap, Boolean> loadResult) { + final Bitmap bitmap = loadResult.getLeft(); + return new ImmutablePair<>(bitmap != null ? + ImageUtils.scaleBitmapToFitDisplay(bitmap) : + null, + loadResult.getRight()); + } + public Observable<String> waitForEndObservable(@Nullable final CancellableHandler handler) { if (handler != null) { handler.unsubscribeIfCancelled(subscription); diff --git a/main/src/cgeo/geocaching/network/Network.java b/main/src/cgeo/geocaching/network/Network.java index a49b302..e8c2b28 100644 --- a/main/src/cgeo/geocaching/network/Network.java +++ b/main/src/cgeo/geocaching/network/Network.java @@ -2,6 +2,7 @@ package cgeo.geocaching.network; import cgeo.geocaching.files.LocalStorage; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.JsonUtils; import cgeo.geocaching.utils.Log; import cgeo.geocaching.utils.TextUtils; @@ -26,11 +27,12 @@ import ch.boye.httpclientandroidlib.params.CoreConnectionPNames; import ch.boye.httpclientandroidlib.params.CoreProtocolPNames; import ch.boye.httpclientandroidlib.params.HttpParams; import ch.boye.httpclientandroidlib.util.EntityUtils; + +import com.fasterxml.jackson.databind.node.ObjectNode; + import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.Nullable; -import org.json.JSONException; -import org.json.JSONObject; import android.content.Context; import android.net.ConnectivityManager; @@ -43,6 +45,7 @@ import java.io.InputStream; import java.io.UnsupportedEncodingException; import java.net.URLDecoder; import java.net.URLEncoder; +import java.util.regex.Pattern; public abstract class Network { @@ -51,7 +54,7 @@ public abstract class Network { /** Native user agent, taken from a Android 2.2 Nexus **/ private final static String NATIVE_USER_AGENT = "Mozilla/5.0 (Linux; U; Android 2.2; en-us; Nexus One Build/FRF91) AppleWebKit/533.1 (KHTML, like Gecko) Version/4.0 Mobile Safari/533.1"; - private static final String PATTERN_PASSWORD = "(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+"; + private static final Pattern PATTERN_PASSWORD = Pattern.compile("(?<=[\\?&])[Pp]ass(w(or)?d)?=[^&#$]+"); private final static HttpParams clientParams = new BasicHttpParams(); @@ -63,7 +66,7 @@ public abstract class Network { } private static String hidePassword(final String message) { - return message.replaceAll(PATTERN_PASSWORD, "password=***"); + return PATTERN_PASSWORD.matcher(message).replaceAll("password=***"); } private static HttpClient getHttpClient() { @@ -107,14 +110,14 @@ public abstract class Network { * @return the HTTP response, or null in case of an encoding error params */ @Nullable - public static HttpResponse postJsonRequest(final String uri, final JSONObject json) { + public static HttpResponse postJsonRequest(final String uri, final ObjectNode json) { HttpPost request = new HttpPost(uri); request.addHeader("Content-Type", "application/json; charset=utf-8"); if (json != null) { try { request.setEntity(new StringEntity(json.toString(), CharEncoding.UTF_8)); } catch (UnsupportedEncodingException e) { - Log.e("postJsonRequest:JSON Entity: UnsupportedEncodingException"); + Log.e("postJsonRequest:JSON Entity: UnsupportedEncodingException", e); return null; } } @@ -344,14 +347,14 @@ public abstract class Network { * @return a JSON object if the request was successful and the body could be decoded, <code>null</code> otherwise */ @Nullable - public static JSONObject requestJSON(final String uri, @Nullable final Parameters params) { + public static ObjectNode requestJSON(final String uri, @Nullable final Parameters params) { final HttpResponse response = request("GET", uri, params, new Parameters("Accept", "application/json, text/javascript, */*; q=0.01"), null); final String responseData = getResponseData(response, false); if (responseData != null) { try { - return new JSONObject(responseData); - } catch (final JSONException e) { - Log.w("Network.requestJSON", e); + return (ObjectNode) JsonUtils.reader.readTree(responseData); + } catch (final IOException e) { + Log.w("requestJSON", e); } } diff --git a/main/src/cgeo/geocaching/network/OAuth.java b/main/src/cgeo/geocaching/network/OAuth.java index cfc62fc..c23ffbf 100644 --- a/main/src/cgeo/geocaching/network/OAuth.java +++ b/main/src/cgeo/geocaching/network/OAuth.java @@ -6,7 +6,6 @@ import ch.boye.httpclientandroidlib.NameValuePair; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; -import org.eclipse.jdt.annotation.Nullable; import java.util.ArrayList; import java.util.Date; @@ -18,8 +17,7 @@ public class OAuth { final String method, final boolean https, final Parameters params, - @Nullable final String token, - @Nullable final String tokenSecret, + final OAuthTokens tokens, final String consumerKey, final String consumerSecret) { params.put( @@ -27,7 +25,7 @@ public class OAuth { "oauth_nonce", CryptUtils.md5(Long.toString(System.currentTimeMillis())), "oauth_signature_method", "HMAC-SHA1", "oauth_timestamp", Long.toString(new Date().getTime() / 1000), - "oauth_token", StringUtils.defaultString(token), + "oauth_token", StringUtils.defaultString(tokens.getTokenPublic()), "oauth_version", "1.0"); params.sort(); @@ -36,7 +34,7 @@ public class OAuth { paramsEncoded.add(nameValue.getName() + "=" + OAuth.percentEncode(nameValue.getValue())); } - final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokenSecret); // both even if empty some of them! + final String keysPacked = consumerSecret + "&" + StringUtils.defaultString(tokens.getTokenSecret()); // both even if empty some of them! final @NonNull String joinedParams = StringUtils.join(paramsEncoded.toArray(), '&'); final String requestPacked = method + "&" + OAuth.percentEncode((https ? "https" : "http") + "://" + host + path) + "&" + OAuth.percentEncode(joinedParams); params.put("oauth_signature", CryptUtils.base64Encode(CryptUtils.hashHmac(requestPacked, keysPacked))); @@ -48,7 +46,7 @@ public class OAuth { * @param url * @return */ - static String percentEncode(@NonNull String url) { + static String percentEncode(@NonNull final String url) { return StringUtils.replace(Network.rfc3986URLEncode(url), "*", "%2A"); } } diff --git a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java index eb56f0b..5efea02 100644 --- a/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java +++ b/main/src/cgeo/geocaching/network/OAuthAuthorizationActivity.java @@ -40,6 +40,8 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { private static final int STATUS_ERROR = 0; private static final int STATUS_SUCCESS = 1; private static final int STATUS_ERROR_EXT_MSG = 2; + private static final Pattern PARAMS_PATTERN_1 = Pattern.compile("oauth_token=([\\w_.-]+)"); + private static final Pattern PARAMS_PATTERN_2 = Pattern.compile("oauth_token_secret=([\\w_.-]+)"); @NonNull private String host = StringUtils.EMPTY; @NonNull private String pathRequest = StringUtils.EMPTY; @@ -51,18 +53,16 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { @NonNull private String callback = StringUtils.EMPTY; private String OAtoken = null; private String OAtokenSecret = null; - private final Pattern paramsPattern1 = Pattern.compile("oauth_token=([a-zA-Z0-9\\-\\_.]+)"); - private final Pattern paramsPattern2 = Pattern.compile("oauth_token_secret=([a-zA-Z0-9\\-\\_.]+)"); @InjectView(R.id.start) protected Button startButton; @InjectView(R.id.auth_1) protected TextView auth_1; @InjectView(R.id.auth_2) protected TextView auth_2; private ProgressDialog requestTokenDialog = null; private ProgressDialog changeTokensDialog = null; - private Handler requestTokenHandler = new Handler() { + private final Handler requestTokenHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (requestTokenDialog != null && requestTokenDialog.isShowing()) { requestTokenDialog.dismiss(); } @@ -85,10 +85,10 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { }; - private Handler changeTokensHandler = new Handler() { + private final Handler changeTokensHandler = new Handler() { @Override - public void handleMessage(Message msg) { + public void handleMessage(final Message msg) { if (changeTokensDialog != null && changeTokensDialog.isShowing()) { changeTokensDialog.dismiss(); } @@ -105,10 +105,10 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { }; @Override - public void onCreate(Bundle savedInstanceState) { + public void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState, R.layout.authorization_activity); - Bundle extras = getIntent().getExtras(); + final Bundle extras = getIntent().getExtras(); if (extras != null) { host = BundleUtils.getString(extras, Intents.EXTRA_OAUTH_HOST, host); pathRequest = BundleUtils.getString(extras, Intents.EXTRA_OAUTH_PATH_REQUEST, pathRequest); @@ -125,7 +125,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { auth_1.setText(getAuthExplainShort()); auth_2.setText(getAuthExplainLong()); - ImmutablePair<String, String> tempToken = getTempTokens(); + final ImmutablePair<String, String> tempToken = getTempTokens(); OAtoken = tempToken.left; OAtokenSecret = tempToken.right; @@ -167,7 +167,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { final Parameters params = new Parameters(); params.put("oauth_callback", callback); final String method = "GET"; - OAuth.signOAuth(host, pathRequest, method, https, params, null, null, consumerKey, consumerSecret); + OAuth.signOAuth(host, pathRequest, method, https, params, new OAuthTokens(null, null), consumerKey, consumerSecret); final HttpResponse response = Network.getRequest(getUrlPrefix() + host + pathRequest, params); if (Network.isSuccess(response)) { @@ -176,11 +176,11 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { int status = STATUS_ERROR; if (StringUtils.isNotBlank(line)) { assert line != null; - final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(PARAMS_PATTERN_1, line); if (paramsMatcher1.find()) { OAtoken = paramsMatcher1.group(1); } - final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(PARAMS_PATTERN_2, line); if (paramsMatcher2.find()) { OAtokenSecret = paramsMatcher2.group(1); } @@ -193,9 +193,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { final String encodedParams = EntityUtils.toString(new UrlEncodedFormEntity(paramsBrowser)); startActivity(new Intent(Intent.ACTION_VIEW, Uri.parse(getUrlPrefix() + host + pathAuthorize + "?" + encodedParams))); status = STATUS_SUCCESS; - } catch (ParseException e) { - Log.e("OAuthAuthorizationActivity.requestToken", e); - } catch (IOException e) { + } catch (ParseException | IOException e) { Log.e("OAuthAuthorizationActivity.requestToken", e); } } @@ -221,17 +219,17 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { final Parameters params = new Parameters("oauth_verifier", verifier); final String method = "POST"; - OAuth.signOAuth(host, pathAccess, method, https, params, OAtoken, OAtokenSecret, consumerKey, consumerSecret); + OAuth.signOAuth(host, pathAccess, method, https, params, new OAuthTokens(OAtoken, OAtokenSecret), consumerKey, consumerSecret); final String line = StringUtils.defaultString(Network.getResponseData(Network.postRequest(getUrlPrefix() + host + pathAccess, params))); OAtoken = ""; OAtokenSecret = ""; - final MatcherWrapper paramsMatcher1 = new MatcherWrapper(paramsPattern1, line); + final MatcherWrapper paramsMatcher1 = new MatcherWrapper(PARAMS_PATTERN_1, line); if (paramsMatcher1.find()) { OAtoken = paramsMatcher1.group(1); } - final MatcherWrapper paramsMatcher2 = new MatcherWrapper(paramsPattern2, line); + final MatcherWrapper paramsMatcher2 = new MatcherWrapper(PARAMS_PATTERN_2, line); if (paramsMatcher2.find() && paramsMatcher2.groupCount() > 0) { OAtokenSecret = paramsMatcher2.group(1); } @@ -244,7 +242,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { setTokens(OAtoken, OAtokenSecret, true); status = AUTHENTICATED; } - } catch (Exception e) { + } catch (final Exception e) { Log.e("OAuthAuthorizationActivity.changeToken", e); } @@ -258,7 +256,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { private class StartListener implements View.OnClickListener { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { if (requestTokenDialog == null) { requestTokenDialog = new ProgressDialog(OAuthAuthorizationActivity.this); requestTokenDialog.setCancelable(false); @@ -333,7 +331,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { * @return String with a more detailed error message (user-facing, localized), can be empty */ @SuppressWarnings("static-method") - protected String getExtendedErrorMsg(HttpResponse response) { + protected String getExtendedErrorMsg(final HttpResponse response) { return StringUtils.EMPTY; } @@ -363,14 +361,14 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { @NonNull public final String consumerSecret; @NonNull public final String callback; - public OAuthParameters(@NonNull String host, - @NonNull String pathRequest, - @NonNull String pathAuthorize, - @NonNull String pathAccess, - boolean https, - @NonNull String consumerKey, - @NonNull String consumerSecret, - @NonNull String callback) { + public OAuthParameters(@NonNull final String host, + @NonNull final String pathRequest, + @NonNull final String pathAuthorize, + @NonNull final String pathAccess, + final boolean https, + @NonNull final String consumerKey, + @NonNull final String consumerSecret, + @NonNull final String callback) { this.host = host; this.pathRequest = pathRequest; this.pathAuthorize = pathAuthorize; @@ -381,7 +379,7 @@ public abstract class OAuthAuthorizationActivity extends AbstractActivity { this.callback = callback; } - public void setOAuthExtras(Intent intent) { + public void setOAuthExtras(final Intent intent) { if (intent != null) { intent.putExtra(Intents.EXTRA_OAUTH_HOST, host); intent.putExtra(Intents.EXTRA_OAUTH_PATH_REQUEST, pathRequest); diff --git a/main/src/cgeo/geocaching/network/OAuthTokens.java b/main/src/cgeo/geocaching/network/OAuthTokens.java new file mode 100644 index 0000000..9f45e7f --- /dev/null +++ b/main/src/cgeo/geocaching/network/OAuthTokens.java @@ -0,0 +1,38 @@ +package cgeo.geocaching.network; + +import cgeo.geocaching.connector.oc.OCApiConnector; +import cgeo.geocaching.settings.Settings; + +import org.apache.commons.lang3.StringUtils; +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.eclipse.jdt.annotation.NonNull; + +import android.util.Pair; + +public class OAuthTokens extends Pair<String, String> { + + public OAuthTokens(@NonNull final OCApiConnector connector) { + this(Settings.getTokenPair(connector.getTokenPublicPrefKeyId(), connector.getTokenSecretPrefKeyId())); + } + + public OAuthTokens(final ImmutablePair<String, String> tokenPair) { + this(tokenPair.left, tokenPair.right); + } + + public OAuthTokens(final String pub, final String secret) { + super(pub, secret); + } + + public boolean isValid() { + return StringUtils.isNotBlank(getTokenPublic()) && StringUtils.isNotBlank(getTokenSecret()); + } + + public String getTokenPublic() { + return first; + } + + public String getTokenSecret() { + return second; + } + +} diff --git a/main/src/cgeo/geocaching/network/SmileyImage.java b/main/src/cgeo/geocaching/network/SmileyImage.java new file mode 100644 index 0000000..86baeaa --- /dev/null +++ b/main/src/cgeo/geocaching/network/SmileyImage.java @@ -0,0 +1,44 @@ +package cgeo.geocaching.network; + +import cgeo.geocaching.list.StoredList; +import cgeo.geocaching.utils.ImageUtils; +import cgeo.geocaching.utils.ImageUtils.LineHeightContainerDrawable; + +import org.apache.commons.lang3.tuple.ImmutablePair; +import org.apache.commons.lang3.tuple.Pair; + +import rx.Observable; + +import android.graphics.Bitmap; +import android.graphics.drawable.BitmapDrawable; +import android.widget.TextView; + +/** + * Specialized image class for fetching and displaying smileys in the log book. + */ +public class SmileyImage extends HtmlImage { + + public SmileyImage(final String geocode, final TextView view) { + super(geocode, false, StoredList.STANDARD_LIST_ID, false, view); + } + + @Override + protected Pair<BitmapDrawable, Boolean> scaleImage(final Pair<Bitmap, Boolean> loadResult) { + final Bitmap bitmap = loadResult.getLeft(); + BitmapDrawable drawable; + if (bitmap != null) { + drawable = new BitmapDrawable(view.getResources(), bitmap); + drawable.setBounds(ImageUtils.scaleImageToLineHeight(drawable, view)); + } + else { + drawable = null; + } + return new ImmutablePair<>(drawable, loadResult.getRight()); + } + + @Override + protected BitmapDrawable getContainerDrawable(final Observable<BitmapDrawable> drawable) { + return new LineHeightContainerDrawable(view, drawable); + } + +} diff --git a/main/src/cgeo/geocaching/network/StatusUpdater.java b/main/src/cgeo/geocaching/network/StatusUpdater.java index 82650d1..bc4a5db 100644 --- a/main/src/cgeo/geocaching/network/StatusUpdater.java +++ b/main/src/cgeo/geocaching/network/StatusUpdater.java @@ -4,8 +4,7 @@ import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.utils.RxUtils; import cgeo.geocaching.utils.Version; -import org.json.JSONException; -import org.json.JSONObject; +import com.fasterxml.jackson.databind.node.ObjectNode; import rx.functions.Action0; import rx.subjects.BehaviorSubject; @@ -31,11 +30,11 @@ public class StatusUpdater { this.url = url; } - Status(final JSONObject response) { - message = get(response, "message"); - messageId = get(response, "message_id"); - icon = get(response, "icon"); - url = get(response, "url"); + Status(final ObjectNode response) { + message = response.path("message").asText(null); + messageId = response.path("message_id").asText(null); + icon = response.path("icon").asText(null); + url = response.path("url").asText(null); } final static public Status closeoutStatus = @@ -55,7 +54,7 @@ public class StatusUpdater { RxUtils.networkScheduler.createWorker().schedulePeriodically(new Action0() { @Override public void call() { - final JSONObject response = + final ObjectNode response = Network.requestJSON("http://status.cgeo.org/api/status.json", new Parameters("version_code", String.valueOf(Version.getVersionCode(CgeoApplication.getInstance())), "version_name", Version.getVersionName(CgeoApplication.getInstance()), @@ -67,12 +66,4 @@ public class StatusUpdater { }, 0, 1800, TimeUnit.SECONDS); } - private static String get(final JSONObject json, final String key) { - try { - return json.getString(key); - } catch (final JSONException e) { - return null; - } - } - } diff --git a/main/src/cgeo/geocaching/playservices/LocationProvider.java b/main/src/cgeo/geocaching/playservices/LocationProvider.java new file mode 100644 index 0000000..f235a3b --- /dev/null +++ b/main/src/cgeo/geocaching/playservices/LocationProvider.java @@ -0,0 +1,157 @@ +package cgeo.geocaching.playservices; + +import cgeo.geocaching.sensors.GeoData; +import cgeo.geocaching.sensors.IGeoData; +import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils; + +import com.google.android.gms.common.ConnectionResult; +import com.google.android.gms.common.GooglePlayServicesClient.ConnectionCallbacks; +import com.google.android.gms.common.GooglePlayServicesClient.OnConnectionFailedListener; +import com.google.android.gms.location.LocationClient; +import com.google.android.gms.location.LocationListener; +import com.google.android.gms.location.LocationRequest; + +import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Subscriber; +import rx.functions.Action0; +import rx.functions.Action1; +import rx.functions.Func1; +import rx.subjects.ReplaySubject; +import rx.subscriptions.Subscriptions; + +import android.content.Context; +import android.location.Location; +import android.os.Bundle; + +import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; + +public class LocationProvider implements ConnectionCallbacks, OnConnectionFailedListener, LocationListener { + + private static final LocationRequest LOCATION_REQUEST = + LocationRequest.create().setPriority(LocationRequest.PRIORITY_HIGH_ACCURACY).setInterval(2000).setFastestInterval(250); + private static final LocationRequest LOCATION_REQUEST_LOW_POWER = + LocationRequest.create().setPriority(LocationRequest.PRIORITY_BALANCED_POWER_ACCURACY).setInterval(10000).setFastestInterval(5000); + private static final AtomicInteger mostPreciseCount = new AtomicInteger(0); + private static final AtomicInteger lowPowerCount = new AtomicInteger(0); + private static LocationProvider instance = null; + private static ReplaySubject<IGeoData> subject = ReplaySubject.createWithSize(1); + private final LocationClient locationClient; + + private static synchronized LocationProvider getInstance(final Context context) { + if (instance == null) { + instance = new LocationProvider(context); + } + return instance; + } + + private synchronized void updateRequest() { + if (locationClient.isConnected()) { + if (mostPreciseCount.get() > 0) { + Log.d("LocationProvider: requesting most precise locations"); + locationClient.requestLocationUpdates(LOCATION_REQUEST, this, RxUtils.looperCallbacksLooper); + } else if (lowPowerCount.get() > 0) { + Log.d("LocationProvider: requesting low-power locations"); + locationClient.requestLocationUpdates(LOCATION_REQUEST_LOW_POWER, this, RxUtils.looperCallbacksLooper); + } else { + Log.d("LocationProvider: stopping location requests"); + locationClient.removeLocationUpdates(this); + } + } + } + + private static Observable<IGeoData> get(final Context context, final AtomicInteger reference) { + final LocationProvider instance = getInstance(context); + return Observable.create(new OnSubscribe<IGeoData>() { + @Override + public void call(final Subscriber<? super IGeoData> subscriber) { + if (reference.incrementAndGet() == 1) { + instance.updateRequest(); + } + subscriber.add(Subscriptions.create(new Action0() { + @Override + public void call() { + RxUtils.looperCallbacksWorker.schedule(new Action0() { + @Override + public void call() { + if (reference.decrementAndGet() == 0) { + instance.updateRequest(); + } + } + }, 2500, TimeUnit.MILLISECONDS); + } + })); + subscriber.add(subject.subscribe(new Action1<IGeoData>() { + @Override + public void call(final IGeoData geoData) { + subscriber.onNext(geoData); + } + })); + } + }); + } + + private static Observable<IGeoData> getInitialLocation(final Context context, final boolean lowPower) { + return get(context, lowPower ? lowPowerCount : mostPreciseCount).first(); + } + + public static Observable<IGeoData> getMostPrecise(final Context context) { + return get(context, mostPreciseCount); + } + + public static Observable<IGeoData> getLowPower(Context context, boolean withInitialLocation) { + final Observable<IGeoData> initialLocationObservable = withInitialLocation ? getInitialLocation(context, true) : Observable.<IGeoData>empty(); + final Observable<IGeoData> lowPowerObservable = get(context, lowPowerCount).skip(1); + final Observable<IGeoData> gpsFixObservable = get(context, mostPreciseCount).skip(1).lift(RxUtils.operatorTakeUntil(new Func1<IGeoData, Boolean>() { + @Override + public Boolean call(final IGeoData geoData) { + return geoData.getAccuracy() < 20; + } + })); + return initialLocationObservable.concatWith(lowPowerObservable.ambWith(gpsFixObservable.delaySubscription(6, TimeUnit.SECONDS)).first() + .concatWith(lowPowerObservable).timeout(25, TimeUnit.SECONDS).retry()); + } + + /** + * Build a new geo data provider object. + * <p/> + * There is no need to instantiate more than one such object in an application, as observers can be added + * at will. + * + * @param context the context used to retrieve the system services + */ + private LocationProvider(final Context context) { + final IGeoData initialLocation = GeoData.getInitialLocation(context); + if (initialLocation != null) { + subject.onNext(initialLocation); + } + locationClient = new LocationClient(context, this, this); + locationClient.connect(); + } + + @Override + public void onConnected(final Bundle bundle) { + updateRequest(); + } + + @Override + public void onDisconnected() { + } + + @Override + public void onConnectionFailed(final ConnectionResult connectionResult) { + Log.e("cannot connect to Google Play location service: " + connectionResult); + subject.onError(new RuntimeException("Connection failed: " + connectionResult)); + } + + @Override + public void onLocationChanged(final Location location) { + if (Settings.useLowPowerMode()) { + location.setProvider(GeoData.LOW_POWER_PROVIDER); + } + subject.onNext(new GeoData(location)); + } +} diff --git a/main/src/cgeo/geocaching/sensors/DirectionProvider.java b/main/src/cgeo/geocaching/sensors/DirectionProvider.java deleted file mode 100644 index ed5d76a..0000000 --- a/main/src/cgeo/geocaching/sensors/DirectionProvider.java +++ /dev/null @@ -1,146 +0,0 @@ -package cgeo.geocaching.sensors; - -import cgeo.geocaching.CgeoApplication; -import cgeo.geocaching.utils.AngleUtils; -import cgeo.geocaching.utils.StartableHandlerThread; - -import rx.Observable; -import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.subjects.BehaviorSubject; - -import android.content.Context; -import android.hardware.Sensor; -import android.hardware.SensorEvent; -import android.hardware.SensorEventListener; -import android.hardware.SensorManager; -import android.os.Handler; -import android.os.Process; -import android.view.Surface; -import android.view.WindowManager; - -public class DirectionProvider { - - private static final BehaviorSubject<Float> SUBJECT = BehaviorSubject.create(0.0f); - - private static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); - - private DirectionProvider() { - // utility class - } - - static class Listener implements SensorEventListener, StartableHandlerThread.Callback { - - private int count = 0; - - private SensorManager sensorManager; - - @Override - public void onSensorChanged(final SensorEvent event) { - SUBJECT.onNext(event.values[0]); - } - - @Override - public void onAccuracyChanged(final Sensor sensor, final int accuracy) { - /* - * There is a bug in Android, which apparently causes this method to be called every - * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here. - * - * See for example https://code.google.com/p/android/issues/detail?id=14792 - */ - } - - @Override - public void start(final Context context, final Handler handler) { - if (!hasSensor(context)) { - return; - } - if (++count == 1) { - Sensor orientationSensor = getOrientationSensor(context); - sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL, handler); - } - } - - @Override - public void stop() { - if (!hasSensor) { - return; - } - if (--count == 0) { - sensorManager.unregisterListener(this); - } - } - - /** - * Assume that there is an orientation sensor, unless we have really checked that - */ - private boolean hasSensor = true; - - /** - * Flag for one time check if there is a sensor. - */ - private boolean hasSensorChecked = false; - - public boolean hasSensor(Context context) { - if (!hasSensorChecked) { - hasSensor = getOrientationSensor(context) != null; - hasSensorChecked = true; - } - return hasSensor; - } - - // This will be removed when using a new location service. Until then, it is okay to be used. - @SuppressWarnings("deprecation") - private Sensor getOrientationSensor(final Context context) { - sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); - return sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); - } - - } - - private static final StartableHandlerThread HANDLER_THREAD = - new StartableHandlerThread("DirectionProvider thread", Process.THREAD_PRIORITY_BACKGROUND, new Listener()); - - static { - HANDLER_THREAD.start(); - } - - public static Observable<Float> create(final Context context) { - return Observable.create(new OnSubscribe<Float>() { - @Override - public void call(final Subscriber<? super Float> subscriber) { - HANDLER_THREAD.start(subscriber, context); - SUBJECT.subscribe(subscriber); - } - }); - } - - /** - * Take the phone rotation (through a given activity) in account and adjust the direction. - * - * @param direction the unadjusted direction in degrees, in the [0, 360[ range - * @return the adjusted direction in degrees, in the [0, 360[ range - */ - - public static float getDirectionNow(final float direction) { - return AngleUtils.normalize(direction + getRotationOffset()); - } - - static float reverseDirectionNow(final float direction) { - return AngleUtils.normalize(direction - getRotationOffset()); - } - - private static int getRotationOffset() { - switch (WINDOW_MANAGER.getDefaultDisplay().getRotation()) { - case Surface.ROTATION_90: - return 90; - case Surface.ROTATION_180: - return 180; - case Surface.ROTATION_270: - return 270; - default: - return 0; - } - } - -} diff --git a/main/src/cgeo/geocaching/sensors/GeoData.java b/main/src/cgeo/geocaching/sensors/GeoData.java index c0b3974..561c09f 100644 --- a/main/src/cgeo/geocaching/sensors/GeoData.java +++ b/main/src/cgeo/geocaching/sensors/GeoData.java @@ -2,22 +2,42 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.enumerations.LocationProviderType; import cgeo.geocaching.geopoint.Geopoint; +import cgeo.geocaching.utils.Log; +import android.content.Context; import android.location.Location; import android.location.LocationManager; -class GeoData extends Location implements IGeoData { - private final boolean gpsEnabled; - private final int satellitesVisible; - private final int satellitesFixed; - private final boolean pseudoLocation; +import javax.annotation.Nullable; - GeoData(final Location location, final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed, final boolean pseudoLocation) { +public class GeoData extends Location implements IGeoData { + + public static final String INITIAL_PROVIDER = "initial"; + public static final String FUSED_PROVIDER = "fused"; + public static final String LOW_POWER_PROVIDER = "low-power"; + + // Some devices will not have the last position available (for example the emulator). In this case, + // rather than waiting forever for a position update which might never come, we emulate it by placing + // the user arbitrarly at Paris Notre-Dame, one of the most visited free tourist attractions in the world. + final public static GeoData DUMMY_LOCATION = new GeoData(new Location(INITIAL_PROVIDER)); + static { + DUMMY_LOCATION.setLatitude(48.85308); + DUMMY_LOCATION.setLongitude(2.34962); + } + + public GeoData(final Location location) { super(location); - this.gpsEnabled = gpsEnabled; - this.satellitesVisible = satellitesVisible; - this.satellitesFixed = satellitesFixed; - this.pseudoLocation = pseudoLocation; + } + + @Nullable + static Location best(@Nullable final Location gpsLocation, @Nullable final Location netLocation) { + if (isRecent(gpsLocation) || !(netLocation != null)) { + return gpsLocation; + } + if (!(gpsLocation != null)) { + return netLocation; + } + return gpsLocation.getTime() >= netLocation.getTime() ? gpsLocation : netLocation; } @Override @@ -32,6 +52,13 @@ class GeoData extends Location implements IGeoData { if (provider.equals(LocationManager.NETWORK_PROVIDER)) { return LocationProviderType.NETWORK; } + // LocationManager.FUSED_PROVIDER constant is not available at API level 9 + if (provider.equals(FUSED_PROVIDER)) { + return LocationProviderType.FUSED; + } + if (provider.equals(LOW_POWER_PROVIDER)) { + return LocationProviderType.LOW_POWER; + } return LocationProviderType.LAST; } @@ -45,23 +72,35 @@ class GeoData extends Location implements IGeoData { return new Geopoint(this); } - @Override - public boolean getGpsEnabled() { - return gpsEnabled; + @Nullable public static GeoData getInitialLocation(final Context context) { + final LocationManager geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + if (geoManager == null) { + Log.w("No LocationManager available"); + return null; + } + try { + // Try to find a sensible initial location from the last locations known to Android. + final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); + final Location bestLocation = best(lastGpsLocation, lastNetworkLocation); + if (bestLocation != null) { + bestLocation.setProvider(INITIAL_PROVIDER); + return new GeoData(bestLocation); + } + Log.i("No last known location available"); + return null; + } catch (final Exception e) { + // This error is non-fatal as its only consequence is that we will start with a dummy location + // instead of a previously known one. + Log.e("Error when retrieving last known location", e); + return null; + } } - @Override - public int getSatellitesVisible() { - return satellitesVisible; - } - @Override - public int getSatellitesFixed() { - return satellitesFixed; - } - @Override - public boolean isPseudoLocation() { - return pseudoLocation; + public static boolean isRecent(@Nullable final Location location) { + return location != null && System.currentTimeMillis() <= location.getTime() + 30000; } + } diff --git a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java index a4799cb..faecbe3 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDataProvider.java +++ b/main/src/cgeo/geocaching/sensors/GeoDataProvider.java @@ -1,25 +1,13 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.utils.Log; -import cgeo.geocaching.utils.StartableHandlerThread; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; import org.apache.commons.lang3.StringUtils; import rx.Observable; -import rx.Observable.OnSubscribe; -import rx.Subscriber; -import rx.Subscription; -import rx.android.schedulers.AndroidSchedulers; -import rx.functions.Action0; -import rx.functions.Action1; -import rx.observables.ConnectableObservable; -import rx.subjects.BehaviorSubject; -import rx.subscriptions.CompositeSubscription; -import rx.subscriptions.Subscriptions; import android.content.Context; -import android.location.GpsSatellite; -import android.location.GpsStatus; import android.location.Location; import android.location.LocationListener; import android.location.LocationManager; @@ -27,40 +15,13 @@ import android.os.Bundle; import java.util.concurrent.TimeUnit; -public class GeoDataProvider implements OnSubscribe<IGeoData> { +public class GeoDataProvider extends LooperCallbacks<IGeoData> { - private static final String LAST_LOCATION_PSEUDO_PROVIDER = "last"; + private final Context context; private final LocationManager geoManager; - private final LocationData gpsLocation = new LocationData(); - private final LocationData netLocation = new LocationData(); - private final BehaviorSubject<IGeoData> subject; - private static final StartableHandlerThread handlerThread = - new StartableHandlerThread("GeoDataProvider thread", android.os.Process.THREAD_PRIORITY_BACKGROUND); - static { - handlerThread.start(); - } - - public boolean gpsEnabled = false; - public int satellitesVisible = 0; - public int satellitesFixed = 0; - - private static class LocationData { - public Location location; - public long timestamp = 0; - - public void update(final Location location) { - this.location = location; - timestamp = System.currentTimeMillis(); - } - - public boolean isRecent() { - return isValid() && System.currentTimeMillis() < timestamp + 30000; - } - - public boolean isValid() { - return location != null; - } - } + private Location latestGPSLocation = null; + private final Listener networkListener = new Listener(); + private final Listener gpsListener = new Listener(); /** * Build a new geo data provider object. @@ -71,117 +32,51 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { * @param context the context used to retrieve the system services */ protected GeoDataProvider(final Context context) { - geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); - subject = BehaviorSubject.create(findInitialLocation()); + super(2500, TimeUnit.MILLISECONDS); + this.context = context.getApplicationContext(); + geoManager = (LocationManager) this.context.getSystemService(Context.LOCATION_SERVICE); } public static Observable<IGeoData> create(final Context context) { - final GeoDataProvider provider = new GeoDataProvider(context); - return provider.worker.refCount(); + return Observable.create(new GeoDataProvider(context)); } @Override - public void call(final Subscriber<? super IGeoData> subscriber) { - subject.subscribe(subscriber); - } - - final ConnectableObservable<IGeoData> worker = new ConnectableObservable<IGeoData>(this) { - private int debugSessionCounter = 0; - - private final Object lock = new Object(); - private int count = 0; - - final private GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); - final private Listener networkListener = new Listener(LocationManager.NETWORK_PROVIDER, netLocation); - final private Listener gpsListener = new Listener(LocationManager.GPS_PROVIDER, gpsLocation); - - @Override - public void connect(Action1<? super Subscription> connection) { - final CompositeSubscription subscription = new CompositeSubscription(); - AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { - @Override - public void call() { - synchronized(lock) { - if (count++ == 0) { - Log.d("GeoDataProvider: starting the GPS and network listeners" + " (" + ++debugSessionCounter + ")"); - geoManager.addGpsStatusListener(gpsStatusListener); - for (final Listener listener : new Listener[] { networkListener, gpsListener }) { - try { - geoManager.requestLocationUpdates(listener.locationProvider, 0, 0, listener); - } catch (final Exception e) { - Log.w("There is no location provider " + listener.locationProvider); - } - } - } - } - - subscription.add(Subscriptions.create(new Action0() { - @Override - public void call() { - AndroidSchedulers.handlerThread(handlerThread.getHandler()).createWorker().schedule(new Action0() { - @Override - public void call() { - synchronized (lock) { - if (--count == 0) { - Log.d("GeoDataProvider: stopping the GPS and network listeners" + " (" + debugSessionCounter + ")"); - geoManager.removeUpdates(networkListener); - geoManager.removeUpdates(gpsListener); - geoManager.removeGpsStatusListener(gpsStatusListener); - } - } - } - }, 2500, TimeUnit.MILLISECONDS); - } - })); - } - }); - connection.call(subscription); + public void onStart() { + final IGeoData initialLocation = GeoData.getInitialLocation(context); + if (initialLocation != null) { + subscriber.onNext(initialLocation); } - }; - - private IGeoData findInitialLocation() { - final Location initialLocation = new Location(LAST_LOCATION_PSEUDO_PROVIDER); + Log.d("GeoDataProvider: starting the GPS and network listeners"); try { - // Try to find a sensible initial location from the last locations known to Android. - final Location lastGpsLocation = geoManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); - final Location lastNetworkLocation = geoManager.getLastKnownLocation(LocationManager.NETWORK_PROVIDER); - - // If both providers are non-null, take the most recent one - if (lastGpsLocation != null && lastNetworkLocation != null) { - if (lastGpsLocation.getTime() >= lastNetworkLocation.getTime()) { - copyCoords(initialLocation, lastGpsLocation); - } else { - copyCoords(initialLocation, lastNetworkLocation); - } - } else if (lastGpsLocation != null) { - copyCoords(initialLocation, lastGpsLocation); - } else if (lastNetworkLocation != null) { - copyCoords(initialLocation, lastNetworkLocation); - } else { - Log.i("GeoDataProvider: no last known location available"); - } + geoManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, gpsListener); + } catch (final Exception e) { + Log.w("Unable to create GPS location provider: " + e.getMessage()); + } + try { + geoManager.requestLocationUpdates(LocationManager.NETWORK_PROVIDER, 0, 0, networkListener); } catch (final Exception e) { - // This error is non-fatal as its only consequence is that we will start with a dummy location - // instead of a previously known one. - Log.e("GeoDataProvider: error when retrieving last known location", e); + Log.w("Unable to create network location provider: " + e.getMessage()); } - // Start with an historical GeoData just in case someone queries it before we get - // a chance to get any information. - return new GeoData(initialLocation, false, 0, 0, true); } - private static void copyCoords(final Location target, final Location source) { - target.setLatitude(source.getLatitude()); - target.setLongitude(source.getLongitude()); + @Override + protected void onStop() { + Log.d("GeoDataProvider: stopping the GPS and network listeners"); + geoManager.removeUpdates(networkListener); + geoManager.removeUpdates(gpsListener); } private class Listener implements LocationListener { - private final String locationProvider; - private final LocationData locationData; - Listener(final String locationProvider, final LocationData locationData) { - this.locationProvider = locationProvider; - this.locationData = locationData; + @Override + public void onLocationChanged(final Location location) { + if (StringUtils.equals(location.getProvider(), LocationManager.GPS_PROVIDER)) { + latestGPSLocation = location; + assign(latestGPSLocation); + } else { + assign(GeoData.best(latestGPSLocation, location)); + } } @Override @@ -198,86 +93,12 @@ public class GeoDataProvider implements OnSubscribe<IGeoData> { public void onProviderEnabled(final String provider) { // nothing } - - @Override - public void onLocationChanged(final Location location) { - locationData.update(location); - selectBest(); - } - } - - private final class GpsStatusListener implements GpsStatus.Listener { - - @Override - public void onGpsStatusChanged(final int event) { - boolean changed = false; - switch (event) { - case GpsStatus.GPS_EVENT_FIRST_FIX: - case GpsStatus.GPS_EVENT_SATELLITE_STATUS: { - final GpsStatus status = geoManager.getGpsStatus(null); - int visible = 0; - int fixed = 0; - for (final GpsSatellite satellite : status.getSatellites()) { - if (satellite.usedInFix()) { - fixed++; - } - visible++; - } - if (visible != satellitesVisible || fixed != satellitesFixed) { - satellitesVisible = visible; - satellitesFixed = fixed; - changed = true; - } - break; - } - case GpsStatus.GPS_EVENT_STARTED: - if (!gpsEnabled) { - gpsEnabled = true; - changed = true; - } - break; - case GpsStatus.GPS_EVENT_STOPPED: - if (gpsEnabled) { - gpsEnabled = false; - satellitesFixed = 0; - satellitesVisible = 0; - changed = true; - } - break; - default: - throw new IllegalStateException(); - } - - if (changed) { - selectBest(); - } - } - } - - private LocationData best() { - if (gpsLocation.isRecent() || !netLocation.isValid()) { - return gpsLocation.isValid() ? gpsLocation : null; - } - if (!gpsLocation.isValid()) { - return netLocation; - } - return gpsLocation.timestamp > netLocation.timestamp ? gpsLocation : netLocation; - } - - private void selectBest() { - assign(best()); } - private void assign(final LocationData locationData) { - if (locationData == null) { - return; - } - + private void assign(final Location location) { // We do not necessarily get signalled when satellites go to 0/0. - final int visible = gpsLocation.isRecent() ? satellitesVisible : 0; - final boolean pseudoLocation = StringUtils.equals(locationData.location.getProvider(), LAST_LOCATION_PSEUDO_PROVIDER); - final IGeoData current = new GeoData(locationData.location, gpsEnabled, visible, satellitesFixed, pseudoLocation); - subject.onNext(current); + final IGeoData current = new GeoData(location); + subscriber.onNext(current); } } diff --git a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java index 0f30142..d127784 100644 --- a/main/src/cgeo/geocaching/sensors/GeoDirHandler.java +++ b/main/src/cgeo/geocaching/sensors/GeoDirHandler.java @@ -2,6 +2,7 @@ package cgeo.geocaching.sensors; import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.settings.Settings; +import cgeo.geocaching.utils.AngleUtils; import org.apache.commons.lang3.tuple.ImmutablePair; @@ -21,7 +22,7 @@ import rx.subscriptions.CompositeSubscription; * accordingly in {@code onPause}. * * The direction is always relative to the top of the device (natural direction), and that it must - * be fixed using {@link DirectionProvider#getDirectionNow(float)}. When the direction is derived from the GPS, + * be fixed using {@link cgeo.geocaching.utils.AngleUtils#getDirectionNow(float)}. When the direction is derived from the GPS, * it is altered so that the fix can still be applied as if the information came from the compass. */ public abstract class GeoDirHandler { @@ -29,6 +30,7 @@ public abstract class GeoDirHandler { public static final int UPDATE_GEODATA = 1 << 1; public static final int UPDATE_DIRECTION = 1 << 2; public static final int UPDATE_GEODIR = 1 << 3; + public static final int LOW_POWER = 1 << 4; private static final CgeoApplication app = CgeoApplication.getInstance(); @@ -76,7 +78,7 @@ public abstract class GeoDirHandler { private static float fixDirection(final IGeoData geoData, final float direction) { final boolean useGPSBearing = !Settings.isUseCompass() || geoData.getSpeed() > 5; - return useGPSBearing ? DirectionProvider.reverseDirectionNow(geoData.getBearing()) : direction; + return useGPSBearing ? AngleUtils.reverseDirectionNow(geoData.getBearing()) : direction; } /** @@ -85,8 +87,9 @@ public abstract class GeoDirHandler { */ public Subscription start(final int flags) { final CompositeSubscription subscriptions = new CompositeSubscription(); + final boolean lowPower = (flags & LOW_POWER) != 0; if ((flags & UPDATE_GEODATA) != 0) { - subscriptions.add(app.geoDataObservable().observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() { + subscriptions.add(app.geoDataObservable(lowPower).observeOn(AndroidSchedulers.mainThread()).subscribe(new Action1<IGeoData>() { @Override public void call(final IGeoData geoData) { updateGeoData(geoData); @@ -102,7 +105,7 @@ public abstract class GeoDirHandler { })); } if ((flags & UPDATE_GEODIR) != 0) { - subscriptions.add(Observable.combineLatest(app.geoDataObservable(), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() { + subscriptions.add(Observable.combineLatest(app.geoDataObservable(lowPower), app.directionObservable(), new Func2<IGeoData, Float, ImmutablePair<IGeoData, Float>>() { @Override public ImmutablePair<IGeoData, Float> call(final IGeoData geoData, final Float direction) { return ImmutablePair.of(geoData, fixDirection(geoData, direction)); diff --git a/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java new file mode 100644 index 0000000..5f12e99 --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/GpsStatusProvider.java @@ -0,0 +1,99 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.sensors.GpsStatusProvider.Status; +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.content.Context; +import android.location.GpsSatellite; +import android.location.GpsStatus; +import android.location.LocationManager; + +public class GpsStatusProvider extends LooperCallbacks<Status> { + + public static class Status { + final public boolean gpsEnabled; + final public int satellitesVisible; + final public int satellitesFixed; + + public Status(final boolean gpsEnabled, final int satellitesVisible, final int satellitesFixed) { + this.gpsEnabled = gpsEnabled; + this.satellitesVisible = satellitesVisible; + this.satellitesFixed = satellitesFixed; + } + } + + private final LocationManager geoManager; + private final GpsStatus.Listener gpsStatusListener = new GpsStatusListener(); + private Status latest = new Status(false, 0, 0); + + private static final Status NO_GPS = new Status(false, 0, 0); + + /** + * Build a new gps status provider object. + * <p/> + * There is no need to instantiate more than one such object in an application, as observers can be added + * at will. + * + * @param context the context used to retrieve the system services + */ + protected GpsStatusProvider(final Context context) { + geoManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE); + } + + public static Observable<Status> create(final Context context) { + return Observable.create(new GpsStatusProvider(context)); + } + + @Override + protected void onStart() { + Log.d("GpsStatusProvider: starting the GPS status listener"); + subscriber.onNext(NO_GPS); + geoManager.addGpsStatusListener(gpsStatusListener); + } + + @Override + protected void onStop() { + Log.d("GpsStatusProvider: stopping the GPS status listener"); + geoManager.removeGpsStatusListener(gpsStatusListener); + } + + private final class GpsStatusListener implements GpsStatus.Listener { + + @Override + public void onGpsStatusChanged(final int event) { + switch (event) { + case GpsStatus.GPS_EVENT_FIRST_FIX: + case GpsStatus.GPS_EVENT_SATELLITE_STATUS: { + final GpsStatus status = geoManager.getGpsStatus(null); + int visible = 0; + int fixed = 0; + for (final GpsSatellite satellite : status.getSatellites()) { + if (satellite.usedInFix()) { + fixed++; + } + visible++; + } + if (visible == latest.satellitesVisible && fixed == latest.satellitesFixed) { + return; + } + latest = new Status(true, visible, fixed); + break; + } + case GpsStatus.GPS_EVENT_STARTED: + latest = new Status(true, 0, 0); + break; + case GpsStatus.GPS_EVENT_STOPPED: + latest = new Status(false, 0, 0); + break; + default: + throw new IllegalStateException(); + } + + subscriber.onNext(latest); + } + } + +} diff --git a/main/src/cgeo/geocaching/sensors/IGeoData.java b/main/src/cgeo/geocaching/sensors/IGeoData.java index 5b4f046..b78b805 100644 --- a/main/src/cgeo/geocaching/sensors/IGeoData.java +++ b/main/src/cgeo/geocaching/sensors/IGeoData.java @@ -10,13 +10,8 @@ public interface IGeoData { public Location getLocation(); public LocationProviderType getLocationProvider(); - public boolean isPseudoLocation(); - public Geopoint getCoords(); public float getBearing(); public float getSpeed(); public float getAccuracy(); - public boolean getGpsEnabled(); - public int getSatellitesVisible(); - public int getSatellitesFixed(); } diff --git a/main/src/cgeo/geocaching/sensors/OrientationProvider.java b/main/src/cgeo/geocaching/sensors/OrientationProvider.java new file mode 100644 index 0000000..83e0638 --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/OrientationProvider.java @@ -0,0 +1,67 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class OrientationProvider extends LooperCallbacks<Float> implements SensorEventListener { + + private final SensorManager sensorManager; + private final Sensor orientationSensor; + + @SuppressWarnings("deprecation") + protected OrientationProvider(final Context context) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + orientationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ORIENTATION); + if (orientationSensor != null) { + Log.d("OrientationProvider: sensor found"); + } else { + Log.w("OrientationProvider: no orientation sensor on this device"); + } + } + + @Override + public void onSensorChanged(final SensorEvent event) { + subscriber.onNext(event.values[0]); + } + + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + /* + * There is a bug in Android, which apparently causes this method to be called every + * time the sensor _value_ changed, even if the _accuracy_ did not change. Do not have any code in here. + * + * See for example https://code.google.com/p/android/issues/detail?id=14792 + */ + } + + @Override + public void onStart() { + if (orientationSensor != null) { + Log.d("OrientationProvider: starting the orientation provider"); + sensorManager.registerListener(this, orientationSensor, SensorManager.SENSOR_DELAY_NORMAL); + } else { + subscriber.onError(new RuntimeException("orientation sensor is absent on this device")); + } + } + + @Override + public void onStop() { + if (orientationSensor != null) { + Log.d("OrientationProvider: stopping the orientation provider"); + sensorManager.unregisterListener(this); + } + } + + public static Observable<Float> create(final Context context) { + return Observable.create(new OrientationProvider(context)); + } + +} diff --git a/main/src/cgeo/geocaching/sensors/RotationProvider.java b/main/src/cgeo/geocaching/sensors/RotationProvider.java new file mode 100644 index 0000000..40e2c3c --- /dev/null +++ b/main/src/cgeo/geocaching/sensors/RotationProvider.java @@ -0,0 +1,83 @@ +package cgeo.geocaching.sensors; + +import cgeo.geocaching.utils.Log; +import cgeo.geocaching.utils.RxUtils.LooperCallbacks; + +import rx.Observable; + +import android.annotation.TargetApi; +import android.content.Context; +import android.hardware.Sensor; +import android.hardware.SensorEvent; +import android.hardware.SensorEventListener; +import android.hardware.SensorManager; + +public class RotationProvider extends LooperCallbacks<Float> implements SensorEventListener { + + private final SensorManager sensorManager; + private final Sensor rotationSensor; + private final float[] rotationMatrix = new float[16]; + private final float[] orientation = new float[4]; + private final float[] values = new float[4]; + + @TargetApi(19) + protected RotationProvider(final Context context, final boolean lowPower) { + sensorManager = (SensorManager) context.getSystemService(Context.SENSOR_SERVICE); + // The geomagnetic rotation vector introduced in Android 4.4 (API 19) requires less power. Favour it + // even if it is more sensible to noise in low-power settings. + final Sensor sensor = lowPower ? sensorManager.getDefaultSensor(Sensor.TYPE_GEOMAGNETIC_ROTATION_VECTOR) : null; + if (sensor != null) { + rotationSensor = sensor; + Log.d("RotationProvider: geomagnetic (low-power) sensor found"); + } else { + rotationSensor = sensorManager.getDefaultSensor(Sensor.TYPE_ROTATION_VECTOR); + if (rotationSensor != null) { + Log.d("RotationProvider: sensor found"); + } else { + Log.w("RotationProvider: no rotation sensor on this device"); + } + } + } + + @Override + public void onSensorChanged(final SensorEvent event) { + // On some Samsung devices, SensorManager#getRotationMatrixFromVector throws an exception if the rotation + // vector has more than 4 elements. Since only the four first elements are used, we can truncate the vector + // without losing precision. + if (event.values.length > 4) { + System.arraycopy(event.values, 0, values, 0, 4); + SensorManager.getRotationMatrixFromVector(rotationMatrix, values); + } else { + SensorManager.getRotationMatrixFromVector(rotationMatrix, event.values); + } + SensorManager.getOrientation(rotationMatrix, orientation); + subscriber.onNext((float) (orientation[0] * 180 / Math.PI)); + } + + @Override + public void onAccuracyChanged(final Sensor sensor, final int accuracy) { + } + + @Override + public void onStart() { + if (rotationSensor != null) { + Log.d("RotationProvider: starting the rotation provider"); + sensorManager.registerListener(this, rotationSensor, SensorManager.SENSOR_DELAY_NORMAL); + } else { + subscriber.onError(new RuntimeException("rotation sensor is absent on this device")); + } + } + + @Override + public void onStop() { + if (rotationSensor != null) { + Log.d("RotationProvider: stopping the rotation provider"); + sensorManager.unregisterListener(this); + } + } + + public static Observable<Float> create(final Context context, final boolean lowPower) { + return Observable.create(new RotationProvider(context, lowPower)); + } + +} diff --git a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java index 84c343a..93480ee 100644 --- a/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java +++ b/main/src/cgeo/geocaching/settings/RegisterSend2CgeoPreference.java @@ -69,7 +69,7 @@ public class RegisterSend2CgeoPreference extends AbstractClickablePreference { final String[] strings = StringUtils.split(Network.getResponseData(response), ','); Settings.setWebNameCode(nam, strings[0]); try { - return Observable.from(Integer.parseInt(strings[1].trim())); + return Observable.just(Integer.parseInt(strings[1].trim())); } catch (final Exception e) { Log.e("RegisterSend2CgeoPreference", e); } diff --git a/main/src/cgeo/geocaching/settings/Settings.java b/main/src/cgeo/geocaching/settings/Settings.java index c4c1ae4..4dd959b 100644 --- a/main/src/cgeo/geocaching/settings/Settings.java +++ b/main/src/cgeo/geocaching/settings/Settings.java @@ -108,75 +108,89 @@ public class Settings { } private static void migrateSettings() { - // migrate from non standard file location and integer based boolean types - final int oldVersion = getInt(R.string.pref_settingsversion, 0); - if (oldVersion < 1) { - final String oldPreferencesName = "cgeo.pref"; - final SharedPreferences old = CgeoApplication.getInstance().getSharedPreferences(oldPreferencesName, Context.MODE_PRIVATE); + final int LATEST_PREFERENCES_VERSION = 2; + final int currentVersion = getInt(R.string.pref_settingsversion, 0); + + // No need to migrate if we are up to date. + if (currentVersion == LATEST_PREFERENCES_VERSION) { + return; + } + + // No need to migrate if we don't have older settings, defaults will be used instead. + final String preferencesNameV0 = "cgeo.pref"; + final SharedPreferences prefsV0 = CgeoApplication.getInstance().getSharedPreferences(preferencesNameV0, Context.MODE_PRIVATE); + if (currentVersion == 0 && prefsV0.getAll().isEmpty()) { + final Editor e = sharedPrefs.edit(); + e.putInt(getKey(R.string.pref_settingsversion), LATEST_PREFERENCES_VERSION); + e.commit(); + return; + } + + if (currentVersion < 1) { + // migrate from non standard file location and integer based boolean types final Editor e = sharedPrefs.edit(); - e.putString(getKey(R.string.pref_temp_twitter_token_secret), old.getString(getKey(R.string.pref_temp_twitter_token_secret), null)); - e.putString(getKey(R.string.pref_temp_twitter_token_public), old.getString(getKey(R.string.pref_temp_twitter_token_public), null)); - e.putBoolean(getKey(R.string.pref_help_shown), old.getInt(getKey(R.string.pref_help_shown), 0) != 0); - e.putFloat(getKey(R.string.pref_anylongitude), old.getFloat(getKey(R.string.pref_anylongitude), 0)); - e.putFloat(getKey(R.string.pref_anylatitude), old.getFloat(getKey(R.string.pref_anylatitude), 0)); - e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != old.getInt(getKey(R.string.pref_offlinemaps), 1)); - e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != old.getInt(getKey(R.string.pref_offlinewpmaps), 0)); - e.putString(getKey(R.string.pref_webDeviceCode), old.getString(getKey(R.string.pref_webDeviceCode), null)); - e.putString(getKey(R.string.pref_webDeviceName), old.getString(getKey(R.string.pref_webDeviceName), null)); - e.putBoolean(getKey(R.string.pref_maplive), old.getInt(getKey(R.string.pref_maplive), 1) != 0); - e.putInt(getKey(R.string.pref_mapsource), old.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT)); - e.putBoolean(getKey(R.string.pref_twitter), 0 != old.getInt(getKey(R.string.pref_twitter), 0)); - e.putBoolean(getKey(R.string.pref_showaddress), 0 != old.getInt(getKey(R.string.pref_showaddress), 1)); - e.putBoolean(getKey(R.string.pref_showcaptcha), old.getBoolean(getKey(R.string.pref_showcaptcha), false)); - e.putBoolean(getKey(R.string.pref_maptrail), old.getInt(getKey(R.string.pref_maptrail), 1) != 0); - e.putInt(getKey(R.string.pref_lastmapzoom), old.getInt(getKey(R.string.pref_lastmapzoom), 14)); - e.putBoolean(getKey(R.string.pref_livelist), 0 != old.getInt(getKey(R.string.pref_livelist), 1)); - e.putBoolean(getKey(R.string.pref_units), old.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric); - e.putBoolean(getKey(R.string.pref_skin), old.getInt(getKey(R.string.pref_skin), 0) != 0); - e.putInt(getKey(R.string.pref_lastusedlist), old.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID)); - e.putString(getKey(R.string.pref_cachetype), old.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id)); - e.putString(getKey(R.string.pref_twitter_token_secret), old.getString(getKey(R.string.pref_twitter_token_secret), null)); - e.putString(getKey(R.string.pref_twitter_token_public), old.getString(getKey(R.string.pref_twitter_token_public), null)); - e.putInt(getKey(R.string.pref_version), old.getInt(getKey(R.string.pref_version), 0)); - e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != old.getInt(getKey(R.string.pref_autoloaddesc), 1)); - e.putBoolean(getKey(R.string.pref_ratingwanted), old.getBoolean(getKey(R.string.pref_ratingwanted), true)); - e.putBoolean(getKey(R.string.pref_friendlogswanted), old.getBoolean(getKey(R.string.pref_friendlogswanted), true)); - e.putBoolean(getKey(R.string.pref_useenglish), old.getBoolean(getKey(R.string.pref_useenglish), false)); - e.putBoolean(getKey(R.string.pref_usecompass), 0 != old.getInt(getKey(R.string.pref_usecompass), 1)); - e.putBoolean(getKey(R.string.pref_trackautovisit), old.getBoolean(getKey(R.string.pref_trackautovisit), false)); - e.putBoolean(getKey(R.string.pref_sigautoinsert), old.getBoolean(getKey(R.string.pref_sigautoinsert), false)); - e.putBoolean(getKey(R.string.pref_logimages), old.getBoolean(getKey(R.string.pref_logimages), false)); - e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != old.getInt(getKey(R.string.pref_excludedisabled), 0)); - e.putBoolean(getKey(R.string.pref_excludemine), 0 != old.getInt(getKey(R.string.pref_excludemine), 0)); - e.putString(getKey(R.string.pref_mapfile), old.getString(getKey(R.string.pref_mapfile), null)); - e.putString(getKey(R.string.pref_signature), old.getString(getKey(R.string.pref_signature), null)); - e.putString(getKey(R.string.pref_pass_vote), old.getString(getKey(R.string.pref_pass_vote), null)); - e.putString(getKey(R.string.pref_password), old.getString(getKey(R.string.pref_password), null)); - e.putString(getKey(R.string.pref_username), old.getString(getKey(R.string.pref_username), null)); - e.putString(getKey(R.string.pref_memberstatus), old.getString(getKey(R.string.pref_memberstatus), "")); - e.putInt(getKey(R.string.pref_coordinputformat), old.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE)); - e.putBoolean(getKey(R.string.pref_log_offline), old.getBoolean(getKey(R.string.pref_log_offline), false)); - e.putBoolean(getKey(R.string.pref_choose_list), old.getBoolean(getKey(R.string.pref_choose_list), true)); - e.putBoolean(getKey(R.string.pref_loaddirectionimg), old.getBoolean(getKey(R.string.pref_loaddirectionimg), true)); - e.putString(getKey(R.string.pref_gccustomdate), old.getString(getKey(R.string.pref_gccustomdate), null)); - e.putInt(getKey(R.string.pref_showwaypointsthreshold), old.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT)); - e.putString(getKey(R.string.pref_cookiestore), old.getString(getKey(R.string.pref_cookiestore), null)); - e.putBoolean(getKey(R.string.pref_opendetailslastpage), old.getBoolean(getKey(R.string.pref_opendetailslastpage), false)); - e.putInt(getKey(R.string.pref_lastdetailspage), old.getInt(getKey(R.string.pref_lastdetailspage), 1)); - e.putInt(getKey(R.string.pref_defaultNavigationTool), old.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id)); - e.putInt(getKey(R.string.pref_defaultNavigationTool2), old.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id)); - e.putInt(getKey(R.string.pref_livemapstrategy), old.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id)); - e.putBoolean(getKey(R.string.pref_debug), old.getBoolean(getKey(R.string.pref_debug), false)); - e.putBoolean(getKey(R.string.pref_hidelivemaphint), old.getInt(getKey(R.string.pref_hidelivemaphint), 0) != 0); - e.putInt(getKey(R.string.pref_livemaphintshowcount), old.getInt(getKey(R.string.pref_livemaphintshowcount), 0)); + e.putString(getKey(R.string.pref_temp_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_secret), null)); + e.putString(getKey(R.string.pref_temp_twitter_token_public), prefsV0.getString(getKey(R.string.pref_temp_twitter_token_public), null)); + e.putBoolean(getKey(R.string.pref_help_shown), prefsV0.getInt(getKey(R.string.pref_help_shown), 0) != 0); + e.putFloat(getKey(R.string.pref_anylongitude), prefsV0.getFloat(getKey(R.string.pref_anylongitude), 0)); + e.putFloat(getKey(R.string.pref_anylatitude), prefsV0.getFloat(getKey(R.string.pref_anylatitude), 0)); + e.putBoolean(getKey(R.string.pref_offlinemaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinemaps), 1)); + e.putBoolean(getKey(R.string.pref_offlinewpmaps), 0 != prefsV0.getInt(getKey(R.string.pref_offlinewpmaps), 0)); + e.putString(getKey(R.string.pref_webDeviceCode), prefsV0.getString(getKey(R.string.pref_webDeviceCode), null)); + e.putString(getKey(R.string.pref_webDeviceName), prefsV0.getString(getKey(R.string.pref_webDeviceName), null)); + e.putBoolean(getKey(R.string.pref_maplive), prefsV0.getInt(getKey(R.string.pref_maplive), 1) != 0); + e.putInt(getKey(R.string.pref_mapsource), prefsV0.getInt(getKey(R.string.pref_mapsource), MAP_SOURCE_DEFAULT)); + e.putBoolean(getKey(R.string.pref_twitter), 0 != prefsV0.getInt(getKey(R.string.pref_twitter), 0)); + e.putBoolean(getKey(R.string.pref_showaddress), 0 != prefsV0.getInt(getKey(R.string.pref_showaddress), 1)); + e.putBoolean(getKey(R.string.pref_showcaptcha), prefsV0.getBoolean(getKey(R.string.pref_showcaptcha), false)); + e.putBoolean(getKey(R.string.pref_maptrail), prefsV0.getInt(getKey(R.string.pref_maptrail), 1) != 0); + e.putInt(getKey(R.string.pref_lastmapzoom), prefsV0.getInt(getKey(R.string.pref_lastmapzoom), 14)); + e.putBoolean(getKey(R.string.pref_livelist), 0 != prefsV0.getInt(getKey(R.string.pref_livelist), 1)); + e.putBoolean(getKey(R.string.pref_units), prefsV0.getInt(getKey(R.string.pref_units), unitsMetric) == unitsMetric); + e.putBoolean(getKey(R.string.pref_skin), prefsV0.getInt(getKey(R.string.pref_skin), 0) != 0); + e.putInt(getKey(R.string.pref_lastusedlist), prefsV0.getInt(getKey(R.string.pref_lastusedlist), StoredList.STANDARD_LIST_ID)); + e.putString(getKey(R.string.pref_cachetype), prefsV0.getString(getKey(R.string.pref_cachetype), CacheType.ALL.id)); + e.putString(getKey(R.string.pref_twitter_token_secret), prefsV0.getString(getKey(R.string.pref_twitter_token_secret), null)); + e.putString(getKey(R.string.pref_twitter_token_public), prefsV0.getString(getKey(R.string.pref_twitter_token_public), null)); + e.putInt(getKey(R.string.pref_version), prefsV0.getInt(getKey(R.string.pref_version), 0)); + e.putBoolean(getKey(R.string.pref_autoloaddesc), 0 != prefsV0.getInt(getKey(R.string.pref_autoloaddesc), 1)); + e.putBoolean(getKey(R.string.pref_ratingwanted), prefsV0.getBoolean(getKey(R.string.pref_ratingwanted), true)); + e.putBoolean(getKey(R.string.pref_friendlogswanted), prefsV0.getBoolean(getKey(R.string.pref_friendlogswanted), true)); + e.putBoolean(getKey(R.string.pref_useenglish), prefsV0.getBoolean(getKey(R.string.pref_useenglish), false)); + e.putBoolean(getKey(R.string.pref_usecompass), 0 != prefsV0.getInt(getKey(R.string.pref_usecompass), 1)); + e.putBoolean(getKey(R.string.pref_trackautovisit), prefsV0.getBoolean(getKey(R.string.pref_trackautovisit), false)); + e.putBoolean(getKey(R.string.pref_sigautoinsert), prefsV0.getBoolean(getKey(R.string.pref_sigautoinsert), false)); + e.putBoolean(getKey(R.string.pref_logimages), prefsV0.getBoolean(getKey(R.string.pref_logimages), false)); + e.putBoolean(getKey(R.string.pref_excludedisabled), 0 != prefsV0.getInt(getKey(R.string.pref_excludedisabled), 0)); + e.putBoolean(getKey(R.string.pref_excludemine), 0 != prefsV0.getInt(getKey(R.string.pref_excludemine), 0)); + e.putString(getKey(R.string.pref_mapfile), prefsV0.getString(getKey(R.string.pref_mapfile), null)); + e.putString(getKey(R.string.pref_signature), prefsV0.getString(getKey(R.string.pref_signature), null)); + e.putString(getKey(R.string.pref_pass_vote), prefsV0.getString(getKey(R.string.pref_pass_vote), null)); + e.putString(getKey(R.string.pref_password), prefsV0.getString(getKey(R.string.pref_password), null)); + e.putString(getKey(R.string.pref_username), prefsV0.getString(getKey(R.string.pref_username), null)); + e.putString(getKey(R.string.pref_memberstatus), prefsV0.getString(getKey(R.string.pref_memberstatus), "")); + e.putInt(getKey(R.string.pref_coordinputformat), prefsV0.getInt(getKey(R.string.pref_coordinputformat), CoordInputFormatEnum.DEFAULT_INT_VALUE)); + e.putBoolean(getKey(R.string.pref_log_offline), prefsV0.getBoolean(getKey(R.string.pref_log_offline), false)); + e.putBoolean(getKey(R.string.pref_choose_list), prefsV0.getBoolean(getKey(R.string.pref_choose_list), true)); + e.putBoolean(getKey(R.string.pref_loaddirectionimg), prefsV0.getBoolean(getKey(R.string.pref_loaddirectionimg), true)); + e.putString(getKey(R.string.pref_gccustomdate), prefsV0.getString(getKey(R.string.pref_gccustomdate), null)); + e.putInt(getKey(R.string.pref_showwaypointsthreshold), prefsV0.getInt(getKey(R.string.pref_showwaypointsthreshold), SHOW_WP_THRESHOLD_DEFAULT)); + e.putString(getKey(R.string.pref_cookiestore), prefsV0.getString(getKey(R.string.pref_cookiestore), null)); + e.putBoolean(getKey(R.string.pref_opendetailslastpage), prefsV0.getBoolean(getKey(R.string.pref_opendetailslastpage), false)); + e.putInt(getKey(R.string.pref_lastdetailspage), prefsV0.getInt(getKey(R.string.pref_lastdetailspage), 1)); + e.putInt(getKey(R.string.pref_defaultNavigationTool), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool), NavigationAppsEnum.COMPASS.id)); + e.putInt(getKey(R.string.pref_defaultNavigationTool2), prefsV0.getInt(getKey(R.string.pref_defaultNavigationTool2), NavigationAppsEnum.INTERNAL_MAP.id)); + e.putInt(getKey(R.string.pref_livemapstrategy), prefsV0.getInt(getKey(R.string.pref_livemapstrategy), Strategy.AUTO.id)); + e.putBoolean(getKey(R.string.pref_debug), prefsV0.getBoolean(getKey(R.string.pref_debug), false)); + e.putInt(getKey(R.string.pref_livemaphintshowcount), prefsV0.getInt(getKey(R.string.pref_livemaphintshowcount), 0)); e.putInt(getKey(R.string.pref_settingsversion), 1); // mark migrated e.commit(); } // changes for new settings dialog - if (oldVersion < 2) { + if (currentVersion < 2) { final Editor e = sharedPrefs.edit(); e.putBoolean(getKey(R.string.pref_units), !isUseImperialUnits()); @@ -408,6 +422,14 @@ public class Settings { return getString(R.string.pref_cookiestore, null); } + public static boolean useGooglePlayServices() { + return CgeoApplication.getInstance().isGooglePlayServicesAvailable() && getBoolean(R.string.pref_googleplayservices, true); + } + + public static boolean useLowPowerMode() { + return getBoolean(R.string.pref_lowpowermode, false); + } + /** * @param cacheType * The cache type used for future filtering @@ -626,7 +648,7 @@ public class Settings { mapSource = MapProviderFactory.getMapSource(id); if (mapSource != null) { // don't use offline maps if the map file is not valid - if ((!(mapSource instanceof OfflineMapSource)) || (isValidMapFile())) { + if (!(mapSource instanceof OfflineMapSource) || isValidMapFile()) { return mapSource; } } @@ -849,14 +871,6 @@ public class Settings { return Log.isDebug(); } - public static boolean getHideLiveMapHint() { - return getBoolean(R.string.pref_hidelivemaphint, false); - } - - public static void setHideLiveHint(final boolean hide) { - putBoolean(R.string.pref_hidelivemaphint, hide); - } - public static int getLiveMapHintShowCount() { return getInt(R.string.pref_livemaphintshowcount, 0); } diff --git a/main/src/cgeo/geocaching/settings/SettingsActivity.java b/main/src/cgeo/geocaching/settings/SettingsActivity.java index df6e680..b5d7b68 100644 --- a/main/src/cgeo/geocaching/settings/SettingsActivity.java +++ b/main/src/cgeo/geocaching/settings/SettingsActivity.java @@ -1,7 +1,5 @@ package cgeo.geocaching.settings; -import butterknife.ButterKnife; - import cgeo.geocaching.CgeoApplication; import cgeo.geocaching.DataStore; import cgeo.geocaching.Intents; @@ -126,6 +124,7 @@ public class SettingsActivity extends PreferenceActivity { initDefaultNavigationPreferences(); initBackupButtons(); initDbLocationPreference(); + initGeoDirPreferences(); initDebugPreference(); initBasicMemberPreferences(); initSend2CgeoPreferences(); @@ -373,12 +372,13 @@ public class SettingsActivity extends PreferenceActivity { final Preference memoryDumpPref = getPreference(R.string.pref_memory_dump); memoryDumpPref .setOnPreferenceClickListener(new OnPreferenceClickListener() { - @Override public boolean onPreferenceClick( - final Preference preference) { - DebugUtils.createMemoryDump(SettingsActivity.this); - return true; - } - }); + @Override + public boolean onPreferenceClick( + final Preference preference) { + DebugUtils.createMemoryDump(SettingsActivity.this); + return true; + } + }); } public static void initHardwareAccelerationPreferences() { @@ -413,6 +413,28 @@ public class SettingsActivity extends PreferenceActivity { }); } + private void initGeoDirPreferences() { + final Preference playServices = getPreference(R.string.pref_googleplayservices); + playServices.setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + CgeoApplication.getInstance().setupGeoDataObservables((Boolean) newValue, Settings.useLowPowerMode()); + return true; + } + }); + playServices.setEnabled(CgeoApplication.getInstance().isGooglePlayServicesAvailable()); + getPreference(R.string.pref_lowpowermode).setOnPreferenceChangeListener(new OnPreferenceChangeListener() { + @Override + public boolean onPreferenceChange(final Preference preference, final Object newValue) { + final CgeoApplication app = CgeoApplication.getInstance(); + final Boolean useLowPower = (Boolean) newValue; + app.setupGeoDataObservables(Settings.useGooglePlayServices(), useLowPower); + app.setupDirectionObservable(useLowPower); + return true; + } + }); + } + void initBasicMemberPreferences() { getPreference(R.string.preference_screen_basicmembers) .setEnabled(!Settings.isGCPremiumMember()); diff --git a/main/src/cgeo/geocaching/sorting/DistanceComparator.java b/main/src/cgeo/geocaching/sorting/DistanceComparator.java index b3b751b..3da5736 100644 --- a/main/src/cgeo/geocaching/sorting/DistanceComparator.java +++ b/main/src/cgeo/geocaching/sorting/DistanceComparator.java @@ -28,7 +28,8 @@ public class DistanceComparator extends AbstractCacheComparator { public DistanceComparator(final Geopoint coords, final List<Geocache> list) { this.coords = coords; - this.list = list; + // create new list so we can iterate over the list in parallel with the cache list adapter + this.list = new ArrayList<>(list); } /** diff --git a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java index 57a69ee..a2da6ee 100644 --- a/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java +++ b/main/src/cgeo/geocaching/sorting/PopularityRatioComparator.java @@ -26,7 +26,8 @@ public class PopularityRatioComparator extends AbstractCacheComparator { if ((ratio2 - ratio1) > 0.0f) { return 1; - } else if ((ratio2 - ratio1) < 0.0f) { + } + if ((ratio2 - ratio1) < 0.0f) { return -1; } diff --git a/main/src/cgeo/geocaching/sorting/SortActionProvider.java b/main/src/cgeo/geocaching/sorting/SortActionProvider.java index 6251984..e6db330 100644 --- a/main/src/cgeo/geocaching/sorting/SortActionProvider.java +++ b/main/src/cgeo/geocaching/sorting/SortActionProvider.java @@ -136,9 +136,7 @@ public class SortActionProvider extends ActionProvider implements OnMenuItemClic final CacheComparator comparator = cacheComparator.newInstance(); onClickListener.call(comparator); } - } catch (final InstantiationException e) { - Log.e("selectComparator", e); - } catch (final IllegalAccessException e) { + } catch (final InstantiationException | IllegalAccessException e) { Log.e("selectComparator", e); } } diff --git a/main/src/cgeo/geocaching/speech/SpeechService.java b/main/src/cgeo/geocaching/speech/SpeechService.java index fbd2d7e..11e10c1 100644 --- a/main/src/cgeo/geocaching/speech/SpeechService.java +++ b/main/src/cgeo/geocaching/speech/SpeechService.java @@ -1,5 +1,6 @@ package cgeo.geocaching.speech; +import cgeo.geocaching.Intents; import cgeo.geocaching.R; import cgeo.geocaching.activity.ActivityMixin; import cgeo.geocaching.geopoint.Geopoint; @@ -31,7 +32,6 @@ public class SpeechService extends Service implements OnInitListener { private static final int SPEECH_MINPAUSE_SECONDS = 5; private static final int SPEECH_MAXPAUSE_SECONDS = 30; - private static final String EXTRA_TARGET_COORDS = "target"; private static Activity startingActivity; private static boolean isRunning = false; /** @@ -152,7 +152,7 @@ public class SpeechService extends Service implements OnInitListener { @Override public int onStartCommand(final Intent intent, final int flags, final int startId) { if (intent != null) { - target = intent.getParcelableExtra(EXTRA_TARGET_COORDS); + target = intent.getParcelableExtra(Intents.EXTRA_COORDS); } return START_NOT_STICKY; // service can be stopped by system, if under memory pressure } @@ -168,7 +168,7 @@ public class SpeechService extends Service implements OnInitListener { isRunning = true; startingActivity = activity; final Intent talkingService = new Intent(activity, SpeechService.class); - talkingService.putExtra(EXTRA_TARGET_COORDS, dstCoords); + talkingService.putExtra(Intents.EXTRA_COORDS, dstCoords); activity.startService(talkingService); } diff --git a/main/src/cgeo/geocaching/twitter/Twitter.java b/main/src/cgeo/geocaching/twitter/Twitter.java index c89c0b6..253d91f 100644 --- a/main/src/cgeo/geocaching/twitter/Twitter.java +++ b/main/src/cgeo/geocaching/twitter/Twitter.java @@ -10,6 +10,7 @@ import cgeo.geocaching.geopoint.Geopoint; import cgeo.geocaching.geopoint.GeopointFormatter.Format; import cgeo.geocaching.network.Network; import cgeo.geocaching.network.OAuth; +import cgeo.geocaching.network.OAuthTokens; import cgeo.geocaching.network.Parameters; import cgeo.geocaching.settings.Settings; import cgeo.geocaching.utils.Log; @@ -17,6 +18,7 @@ import cgeo.geocaching.utils.LogTemplateProvider; import cgeo.geocaching.utils.LogTemplateProvider.LogContext; import ch.boye.httpclientandroidlib.HttpResponse; + import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; import org.eclipse.jdt.annotation.Nullable; @@ -25,7 +27,7 @@ public final class Twitter { private static final String HASH_PREFIX_WITH_BLANK = " #"; private static final int MAX_TWEET_SIZE = 140; - public static void postTweetCache(String geocode, final @Nullable LogEntry logEntry) { + public static void postTweetCache(final String geocode, final @Nullable LogEntry logEntry) { final Geocache cache = DataStore.loadCache(geocode, LoadFlags.LOAD_CACHE_OR_DB); if (cache == null) { return; @@ -33,7 +35,7 @@ public final class Twitter { postTweet(CgeoApplication.getInstance(), getStatusMessage(cache, logEntry), null); } - public static void postTweetTrackable(String geocode, final @Nullable LogEntry logEntry) { + public static void postTweetTrackable(final String geocode, final @Nullable LogEntry logEntry) { final Trackable trackable = DataStore.loadTrackable(geocode); if (trackable == null) { return; @@ -48,7 +50,7 @@ public final class Twitter { try { final String status = shortenToMaxSize(statusIn); - Parameters parameters = new Parameters("status", status); + final Parameters parameters = new Parameters("status", status); if (coords != null) { parameters.put( "lat", coords.format(Format.LAT_DECDEGREE_RAW), @@ -56,7 +58,7 @@ public final class Twitter { "display_coordinates", "true"); } - OAuth.signOAuth("api.twitter.com", "/1.1/statuses/update.json", "POST", true, parameters, Settings.getTokenPublic(), Settings.getTokenSecret(), Settings.getKeyConsumerPublic(), Settings.getKeyConsumerSecret()); + OAuth.signOAuth("api.twitter.com", "/1.1/statuses/update.json", "POST", true, parameters, new OAuthTokens(Settings.getTokenPublic(), Settings.getTokenSecret()), Settings.getKeyConsumerPublic(), Settings.getKeyConsumerSecret()); final HttpResponse httpResponse = Network.postRequest("https://api.twitter.com/1.1/statuses/update.json", parameters); if (httpResponse != null) { if (httpResponse.getStatusLine().getStatusCode() == 200) { @@ -67,13 +69,13 @@ public final class Twitter { } else { Log.e("Tweet could not be posted. Reason: httpResponse Object is null"); } - } catch (Exception e) { + } catch (final Exception e) { Log.e("Twitter.postTweet", e); } } private static String shortenToMaxSize(final String status) { - String result = StringUtils.trim(status); + final String result = StringUtils.trim(status); if (StringUtils.length(result) > MAX_TWEET_SIZE) { return StringUtils.substring(result, 0, MAX_TWEET_SIZE - 1) + '…'; } @@ -98,7 +100,7 @@ public final class Twitter { } private static String appendHashTags(final String status) { - StringBuilder builder = new StringBuilder(status); + final StringBuilder builder = new StringBuilder(status); appendHashTag(builder, "cgeo"); appendHashTag(builder, "geocaching"); return builder.toString(); diff --git a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java index 40cd726..d55d9c9 100644 --- a/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java +++ b/main/src/cgeo/geocaching/ui/CacheDetailsCreator.java @@ -102,11 +102,14 @@ public final class CacheDetailsCreator { public void addCacheState(final Geocache cache) { if (cache.isLogOffline() || cache.isArchived() || cache.isDisabled() || cache.isPremiumMembersOnly() || cache.isFound()) { final List<String> states = new ArrayList<>(5); + String date = getVisitedDate(cache); if (cache.isLogOffline()) { - states.add(res.getString(R.string.cache_status_offline_log)); + states.add(res.getString(R.string.cache_status_offline_log) + date); + // reset the found date, to avoid showing it twice + date = ""; } if (cache.isFound()) { - states.add(res.getString(R.string.cache_status_found)); + states.add(res.getString(R.string.cache_status_found) + date); } if (cache.isArchived()) { states.add(res.getString(R.string.cache_status_archived)); @@ -121,12 +124,17 @@ public final class CacheDetailsCreator { } } + private static String getVisitedDate(final Geocache cache) { + final long visited = cache.getVisitedDate(); + return visited != 0 ? " (" + Formatter.formatShortDate(visited) + ")" : ""; + } + public void addRating(final Geocache cache) { if (cache.getRating() > 0) { final RelativeLayout itemLayout = addStars(R.string.cache_rating, cache.getRating()); if (cache.getVotes() > 0) { final TextView itemAddition = ButterKnife.findById(itemLayout, R.id.addition); - itemAddition.setText("(" + cache.getVotes() + ")"); + itemAddition.setText(" (" + cache.getVotes() + ')'); itemAddition.setVisibility(View.VISIBLE); } } @@ -170,7 +178,7 @@ public final class CacheDetailsCreator { } public void addDistance(final Waypoint wpt, final TextView waypointDistanceView) { - Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt); + final Float distance = CgeoApplication.getInstance().distanceNonBlocking(wpt); String text = "--"; if (distance != null) { text = Units.getDistanceFromKilometers(distance); diff --git a/main/src/cgeo/geocaching/ui/CacheListAdapter.java b/main/src/cgeo/geocaching/ui/CacheListAdapter.java index eaede2a..8cb2177 100644 --- a/main/src/cgeo/geocaching/ui/CacheListAdapter.java +++ b/main/src/cgeo/geocaching/ui/CacheListAdapter.java @@ -120,9 +120,7 @@ public class CacheListAdapter extends ArrayAdapter<Geocache> { public CacheListAdapter(final Activity activity, final List<Geocache> list, final CacheListType cacheListType) { super(activity, 0, list); final IGeoData currentGeo = CgeoApplication.getInstance().currentGeo(); - if (currentGeo != null) { - coords = currentGeo.getCoords(); - } + coords = currentGeo.getCoords(); this.res = activity.getResources(); this.list = list; this.cacheListType = cacheListType; diff --git a/main/src/cgeo/geocaching/ui/CompassView.java b/main/src/cgeo/geocaching/ui/CompassView.java index 240afcf..a227770 100644 --- a/main/src/cgeo/geocaching/ui/CompassView.java +++ b/main/src/cgeo/geocaching/ui/CompassView.java @@ -81,8 +81,9 @@ public class CompassView extends View { } public void updateGraphics() { - final float newAzimuthShown = smoothUpdate(northMeasured, azimuthShown); - final float newCacheHeadingShown = smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); + final float newAzimuthShown = initialDisplay ? northMeasured : smoothUpdate(northMeasured, azimuthShown); + final float newCacheHeadingShown = initialDisplay ? cacheHeadingMeasured : smoothUpdate(cacheHeadingMeasured, cacheHeadingShown); + initialDisplay = false; if (Math.abs(AngleUtils.difference(azimuthShown, newAzimuthShown)) >= 2 || Math.abs(AngleUtils.difference(cacheHeadingShown, newCacheHeadingShown)) >= 2) { azimuthShown = newAzimuthShown; @@ -151,17 +152,6 @@ public class CompassView extends View { * @param cacheHeading the cache direction (extra rotation of the needle) */ public void updateNorth(final float northHeading, final float cacheHeading) { - if (initialDisplay) { - // We will force the compass to move brutally if this is the first - // update since it is visible. - azimuthShown = northHeading; - cacheHeadingShown = cacheHeading; - - // it may take some time to get an initial direction measurement for the device - if (northHeading != 0.0) { - initialDisplay = false; - } - } northMeasured = northHeading; cacheHeadingMeasured = cacheHeading; } diff --git a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java index e2e587e..3af950f 100644 --- a/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java +++ b/main/src/cgeo/geocaching/ui/DecryptTextClickListener.java @@ -1,40 +1,37 @@ -package cgeo.geocaching.ui;
-
-import cgeo.geocaching.utils.CryptUtils;
-
-import org.eclipse.jdt.annotation.NonNull;
-
-import android.text.Spannable;
-import android.view.View;
-import android.widget.TextView;
-
-public class DecryptTextClickListener implements View.OnClickListener {
-
- @NonNull private final TextView targetView;
-
- public DecryptTextClickListener(@NonNull final TextView targetView) {
- this.targetView = targetView;
- }
-
- @Override
- public final void onClick(final View view) {
- try {
- // do not run the click listener if a link was clicked
- if (targetView.getSelectionStart() != -1 || targetView.getSelectionEnd() != -1) {
- return;
- }
-
- CharSequence text = targetView.getText();
- if (text instanceof Spannable) {
- Spannable span = (Spannable) text;
- targetView.setText(CryptUtils.rot13(span));
- }
- else {
- String string = (String) text;
- targetView.setText(CryptUtils.rot13(string));
- }
- } catch (RuntimeException e) {
- // nothing
- }
- }
-}
+package cgeo.geocaching.ui; + +import cgeo.geocaching.utils.CryptUtils; + +import org.eclipse.jdt.annotation.NonNull; + +import android.text.Spannable; +import android.view.View; +import android.widget.TextView; + +public class DecryptTextClickListener implements View.OnClickListener { + + @NonNull private final TextView targetView; + + public DecryptTextClickListener(@NonNull final TextView targetView) { + this.targetView = targetView; + } + + @Override + public final void onClick(final View view) { + try { + // do not run the click listener if a link was clicked + if (targetView.getSelectionStart() != -1 || targetView.getSelectionEnd() != -1) { + return; + } + + CharSequence text = targetView.getText(); + if (text instanceof Spannable) { + targetView.setText(CryptUtils.rot13((Spannable) text)); + } else { + targetView.setText(CryptUtils.rot13((String) text)); + } + } catch (final RuntimeException ignore) { + // nothing + } + } +} diff --git a/main/src/cgeo/geocaching/ui/ImagesList.java b/main/src/cgeo/geocaching/ui/ImagesList.java index 8bd4ac2..458f8db 100644 --- a/main/src/cgeo/geocaching/ui/ImagesList.java +++ b/main/src/cgeo/geocaching/ui/ImagesList.java @@ -102,7 +102,7 @@ public class ImagesList { imagesView = ButterKnife.findById(parentView, R.id.spoiler_list); - final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST_ID, false); + final HtmlImage imgGetter = new HtmlImage(geocode, true, offline ? StoredList.STANDARD_LIST_ID : StoredList.TEMPORARY_LIST.id, false); for (final Image img : images) { final LinearLayout rowView = (LinearLayout) inflater.inflate(R.layout.cache_image_item, imagesView, false); diff --git a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java index 1046f81..15c9556 100644 --- a/main/src/cgeo/geocaching/ui/dialog/DateDialog.java +++ b/main/src/cgeo/geocaching/ui/dialog/DateDialog.java @@ -22,11 +22,11 @@ public class DateDialog extends DialogFragment { private Calendar date; public static DateDialog getInstance(final Calendar date) { - final DateDialog dd = new DateDialog(); + final DateDialog dateDialog = new DateDialog(); final Bundle args = new Bundle(); args.putSerializable("date", date); - dd.setArguments(args); - return dd; + dateDialog.setArguments(args); + return dateDialog; } @Override diff --git a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java index 21e1a82..47ce6e1 100644 --- a/main/src/cgeo/geocaching/ui/dialog/Dialogs.java +++ b/main/src/cgeo/geocaching/ui/dialog/Dialogs.java @@ -25,8 +25,15 @@ import android.text.Editable; import android.text.InputType; import android.text.TextWatcher; import android.view.ContextThemeWrapper; +import android.view.View; +import android.view.ViewGroup; import android.view.WindowManager; +import android.widget.ArrayAdapter; import android.widget.EditText; +import android.widget.ListAdapter; +import android.widget.TextView; + +import java.util.List; /** * Wrapper for {@link AlertDialog}. If you want to show a simple text, use one of the @@ -410,4 +417,44 @@ public final class Dialogs { private static void enableDialogButtonIfNotEmpty(final AlertDialog dialog, final String input) { dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(StringUtils.isNotBlank(input)); } + + public static interface ItemWithIcon { + /** + * @return the drawable + */ + int getIcon(); + } + + public static <T extends ItemWithIcon> void select(final Activity activity, final String title, final List<T> items, final Action1<T> listener) { + final ListAdapter adapter = new ArrayAdapter<T>( + activity, + android.R.layout.select_dialog_item, + android.R.id.text1, + items) { + @Override + public View getView(final int position, final View convertView, final ViewGroup parent) { + // standard list entry + final View v = super.getView(position, convertView, parent); + + // add image + final TextView tv = (TextView) v.findViewById(android.R.id.text1); + tv.setCompoundDrawablesWithIntrinsicBounds(items.get(position).getIcon(), 0, 0, 0); + + // Add margin between image and text + final int dp5 = (int) (5 * activity.getResources().getDisplayMetrics().density + 0.5f); + tv.setCompoundDrawablePadding(dp5); + + return v; + } + }; + + new AlertDialog.Builder(activity) + .setTitle(title) + .setAdapter(adapter, new DialogInterface.OnClickListener() { + @Override + public void onClick(final DialogInterface dialog, final int item) { + listener.call(items.get(item)); + } + }).show(); + } } diff --git a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java index 23caf79..3aaeec1 100644 --- a/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/LogsViewCreator.java @@ -4,8 +4,7 @@ import cgeo.geocaching.ImagesActivity; import cgeo.geocaching.LogEntry; import cgeo.geocaching.R; import cgeo.geocaching.activity.AbstractActionBarActivity; -import cgeo.geocaching.list.StoredList; -import cgeo.geocaching.network.HtmlImage; +import cgeo.geocaching.network.SmileyImage; import cgeo.geocaching.ui.AbstractCachingListViewPageViewCreator; import cgeo.geocaching.ui.AnchorAwareLinkMovementMethod; import cgeo.geocaching.ui.DecryptTextClickListener; @@ -87,7 +86,7 @@ public abstract class LogsViewCreator extends AbstractCachingListViewPageViewCre if (TextUtils.containsHtml(logText)) { logText = log.getDisplayText(); final UnknownTagsHandler unknownTagsHandler = new UnknownTagsHandler(); - holder.text.setText(Html.fromHtml(logText, new HtmlImage(getGeocode(), false, StoredList.STANDARD_LIST_ID, false, holder.text), + holder.text.setText(Html.fromHtml(logText, new SmileyImage(getGeocode(), holder.text), unknownTagsHandler), TextView.BufferType.SPANNABLE); } else { holder.text.setText(logText, TextView.BufferType.SPANNABLE); diff --git a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java index 300f510..24c8871 100644 --- a/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java +++ b/main/src/cgeo/geocaching/ui/logs/TrackableLogsViewCreator.java @@ -15,14 +15,16 @@ import java.util.List; public class TrackableLogsViewCreator extends LogsViewCreator { - private final Trackable trackable; + private Trackable trackable; + private final TrackableActivity trackableActivity; /** * @param trackableActivity */ - public TrackableLogsViewCreator(TrackableActivity trackableActivity, final Trackable trackable) { + public TrackableLogsViewCreator(final TrackableActivity trackableActivity) { super(trackableActivity); - this.trackable = trackable; + this.trackableActivity = trackableActivity; + trackable = trackableActivity.getTrackable(); } @Override @@ -32,6 +34,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator { @Override protected List<LogEntry> getLogs() { + trackable = trackableActivity.getTrackable(); return trackable.getLogs(); } @@ -41,7 +44,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator { } @Override - protected void fillCountOrLocation(LogViewHolder holder, final LogEntry log) { + protected void fillCountOrLocation(final LogViewHolder holder, final LogEntry log) { if (StringUtils.isBlank(log.cacheName)) { holder.countOrLocation.setVisibility(View.GONE); } else { @@ -50,7 +53,7 @@ public class TrackableLogsViewCreator extends LogsViewCreator { final String cacheName = log.cacheName; holder.countOrLocation.setOnClickListener(new View.OnClickListener() { @Override - public void onClick(View arg0) { + public void onClick(final View arg0) { CacheDetailActivity.startActivityGuid(activity, cacheGuid, Html.fromHtml(cacheName).toString()); } }); diff --git a/main/src/cgeo/geocaching/utils/AngleUtils.java b/main/src/cgeo/geocaching/utils/AngleUtils.java index fdd9a9d..5ab2c75 100644 --- a/main/src/cgeo/geocaching/utils/AngleUtils.java +++ b/main/src/cgeo/geocaching/utils/AngleUtils.java @@ -1,7 +1,17 @@ package cgeo.geocaching.utils; +import cgeo.geocaching.CgeoApplication; + +import android.content.Context; +import android.view.Surface; +import android.view.WindowManager; + public final class AngleUtils { + private static class WindowManagerHolder { + public static final WindowManager WINDOW_MANAGER = (WindowManager) CgeoApplication.getInstance().getSystemService(Context.WINDOW_SERVICE); + } + private AngleUtils() { // Do not instantiate } @@ -27,4 +37,37 @@ public final class AngleUtils { public static float normalize(final float angle) { return (angle >= 0 ? angle : (360 - ((-angle) % 360))) % 360; } + + public static int getRotationOffset() { + switch (WindowManagerHolder.WINDOW_MANAGER.getDefaultDisplay().getRotation()) { + case Surface.ROTATION_90: + return 90; + case Surface.ROTATION_180: + return 180; + case Surface.ROTATION_270: + return 270; + default: + return 0; + } + } + + /** + * Take the phone rotation (through a given activity) in account and adjust the direction. + * + * @param direction the unadjusted direction in degrees, in the [0, 360[ range + * @return the adjusted direction in degrees, in the [0, 360[ range + */ + public static float getDirectionNow(final float direction) { + return normalize(direction + getRotationOffset()); + } + + /** + * Reverse the phone rotation (through a given activity) in account and adjust the direction. + * + * @param direction the unadjusted direction in degrees, in the [0, 360[ range + * @return the adjusted direction in degrees, in the [0, 360[ range + */ + public static float reverseDirectionNow(final float direction) { + return normalize(direction - getRotationOffset()); + } } diff --git a/main/src/cgeo/geocaching/utils/ClipboardUtils.java b/main/src/cgeo/geocaching/utils/ClipboardUtils.java index 77250f3..d91c644 100644 --- a/main/src/cgeo/geocaching/utils/ClipboardUtils.java +++ b/main/src/cgeo/geocaching/utils/ClipboardUtils.java @@ -9,7 +9,6 @@ import android.content.Context; * This class uses the deprecated function ClipboardManager.setText(CharSequence). * API 11 introduced setPrimaryClip(ClipData) */ -@SuppressWarnings("deprecation") public final class ClipboardUtils { private ClipboardUtils() { @@ -22,6 +21,7 @@ public final class ClipboardUtils { * @param text * The text to place in the clipboard. */ + @SuppressWarnings("deprecation") public static void copyToClipboard(final CharSequence text) { // fully qualified name used here to avoid buggy deprecation warning (of javac) on the import statement final android.text.ClipboardManager clipboard = (android.text.ClipboardManager) CgeoApplication.getInstance().getSystemService(Context.CLIPBOARD_SERVICE); diff --git a/main/src/cgeo/geocaching/utils/CryptUtils.java b/main/src/cgeo/geocaching/utils/CryptUtils.java index 815c2f4..f2ff0c2 100644 --- a/main/src/cgeo/geocaching/utils/CryptUtils.java +++ b/main/src/cgeo/geocaching/utils/CryptUtils.java @@ -1,6 +1,5 @@ package cgeo.geocaching.utils; - import org.apache.commons.lang3.CharEncoding; import org.apache.commons.lang3.StringUtils; import org.eclipse.jdt.annotation.NonNull; @@ -23,28 +22,29 @@ public final class CryptUtils { // utility class } - private static char[] base64map1 = new char[64]; - private static byte[] base64map2 = new byte[128]; + private static final byte[] EMPTY = {}; + private static char[] BASE64MAP1 = new char[64]; + private static byte[] BASE64MAP2 = new byte[128]; static { int i = 0; for (char c = 'A'; c <= 'Z'; c++) { - base64map1[i++] = c; + BASE64MAP1[i++] = c; } for (char c = 'a'; c <= 'z'; c++) { - base64map1[i++] = c; + BASE64MAP1[i++] = c; } for (char c = '0'; c <= '9'; c++) { - base64map1[i++] = c; + BASE64MAP1[i++] = c; } - base64map1[i++] = '+'; - base64map1[i++] = '/'; + BASE64MAP1[i++] = '+'; + BASE64MAP1[i++] = '/'; - for (i = 0; i < base64map2.length; i++) { - base64map2[i] = -1; + for (i = 0; i < BASE64MAP2.length; i++) { + BASE64MAP2[i] = -1; } for (i = 0; i < 64; i++) { - base64map2[base64map1[i]] = (byte) i; + BASE64MAP2[BASE64MAP1[i]] = (byte) i; } } @@ -88,9 +88,7 @@ public final class CryptUtils { final MessageDigest digest = MessageDigest.getInstance("MD5"); digest.update(text.getBytes(CharEncoding.UTF_8), 0, text.length()); return new BigInteger(1, digest.digest()).toString(16); - } catch (NoSuchAlgorithmException e) { - Log.e("CryptUtils.md5", e); - } catch (UnsupportedEncodingException e) { + } catch (NoSuchAlgorithmException | UnsupportedEncodingException e) { Log.e("CryptUtils.md5", e); } @@ -98,20 +96,16 @@ public final class CryptUtils { } public static byte[] hashHmac(String text, String salt) { - byte[] macBytes = {}; try { final SecretKeySpec secretKeySpec = new SecretKeySpec(salt.getBytes(CharEncoding.UTF_8), "HmacSHA1"); final Mac mac = Mac.getInstance("HmacSHA1"); mac.init(secretKeySpec); - macBytes = mac.doFinal(text.getBytes(CharEncoding.UTF_8)); - } catch (GeneralSecurityException e) { - Log.e("CryptUtils.hashHmac", e); - } catch (UnsupportedEncodingException e) { + return mac.doFinal(text.getBytes(CharEncoding.UTF_8)); + } catch (GeneralSecurityException | UnsupportedEncodingException e) { Log.e("CryptUtils.hashHmac", e); + return EMPTY; } - - return macBytes; } public static CharSequence rot13(final Spannable span) { @@ -145,11 +139,11 @@ public final class CryptUtils { int o1 = ((i0 & 3) << 4) | (i1 >>> 4); int o2 = ((i1 & 0xf) << 2) | (i2 >>> 6); int o3 = i2 & 0x3F; - out[op++] = base64map1[o0]; - out[op++] = base64map1[o1]; - out[op] = op < oDataLen ? base64map1[o2] : '='; + out[op++] = BASE64MAP1[o0]; + out[op++] = BASE64MAP1[o1]; + out[op] = op < oDataLen ? BASE64MAP1[o2] : '='; op++; - out[op] = op < oDataLen ? base64map1[o3] : '='; + out[op] = op < oDataLen ? BASE64MAP1[o3] : '='; op++; } diff --git a/main/src/cgeo/geocaching/utils/Formatter.java b/main/src/cgeo/geocaching/utils/Formatter.java index 3068cd4..1b774f8 100644 --- a/main/src/cgeo/geocaching/utils/Formatter.java +++ b/main/src/cgeo/geocaching/utils/Formatter.java @@ -33,7 +33,7 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatTime(long date) { + public static String formatTime(final long date) { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_TIME); } @@ -45,7 +45,7 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatDate(long date) { + public static String formatDate(final long date) { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE); } @@ -58,7 +58,7 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatFullDate(long date) { + public static String formatFullDate(final long date) { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_YEAR); } @@ -71,8 +71,8 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatShortDate(long date) { - DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context); + public static String formatShortDate(final long date) { + final DateFormat dateFormat = android.text.format.DateFormat.getDateFormat(context); return dateFormat.format(date); } @@ -84,8 +84,8 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatShortDateVerbally(long date) { - int diff = cgeo.geocaching.utils.DateUtils.daysSince(date); + public static String formatShortDateVerbally(final long date) { + final int diff = cgeo.geocaching.utils.DateUtils.daysSince(date); switch (diff) { case 0: return CgeoApplication.getInstance().getString(R.string.log_today); @@ -104,7 +104,7 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatShortDateTime(long date) { + public static String formatShortDateTime(final long date) { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME | DateUtils.FORMAT_ABBREV_ALL); } @@ -116,11 +116,11 @@ public abstract class Formatter { * milliseconds since the epoch * @return the formatted string */ - public static String formatDateTime(long date) { + public static String formatDateTime(final long date) { return DateUtils.formatDateTime(context, date, DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_TIME); } - public static String formatCacheInfoLong(Geocache cache, CacheListType cacheListType) { + public static String formatCacheInfoLong(final Geocache cache, final CacheListType cacheListType) { final ArrayList<String> infos = new ArrayList<>(); if (StringUtils.isNotBlank(cache.getGeocode())) { infos.add(cache.getGeocode()); @@ -137,13 +137,13 @@ public abstract class Formatter { return StringUtils.join(infos, Formatter.SEPARATOR); } - public static String formatCacheInfoShort(Geocache cache) { + public static String formatCacheInfoShort(final Geocache cache) { final ArrayList<String> infos = new ArrayList<>(); addShortInfos(cache, infos); return StringUtils.join(infos, Formatter.SEPARATOR); } - private static void addShortInfos(Geocache cache, final ArrayList<String> infos) { + private static void addShortInfos(final Geocache cache, final ArrayList<String> infos) { if (cache.hasDifficulty()) { infos.add("D " + String.format("%.1f", cache.getDifficulty())); } @@ -162,7 +162,7 @@ public abstract class Formatter { } } - public static String formatCacheInfoHistory(Geocache cache) { + public static String formatCacheInfoHistory(final Geocache cache) { final ArrayList<String> infos = new ArrayList<>(3); infos.add(StringUtils.upperCase(cache.getGeocode())); infos.add(Formatter.formatDate(cache.getVisitedDate())); @@ -170,9 +170,9 @@ public abstract class Formatter { return StringUtils.join(infos, Formatter.SEPARATOR); } - public static String formatWaypointInfo(Waypoint waypoint) { + public static String formatWaypointInfo(final Waypoint waypoint) { final List<String> infos = new ArrayList<>(3); - WaypointType waypointType = waypoint.getWaypointType(); + final WaypointType waypointType = waypoint.getWaypointType(); if (waypointType != WaypointType.OWN && waypointType != null) { infos.add(waypointType.getL10n()); } @@ -188,4 +188,16 @@ public abstract class Formatter { } return StringUtils.join(infos, Formatter.SEPARATOR); } + + public static String formatDaysAgo(final long date) { + final int days = cgeo.geocaching.utils.DateUtils.daysSince(date); + switch (days) { + case 0: + return CgeoApplication.getInstance().getString(R.string.log_today); + case 1: + return CgeoApplication.getInstance().getString(R.string.log_yesterday); + default: + return CgeoApplication.getInstance().getResources().getQuantityString(R.plurals.days_ago, days, days); + } + } } diff --git a/main/src/cgeo/geocaching/utils/HtmlUtils.java b/main/src/cgeo/geocaching/utils/HtmlUtils.java index 51c4d6e..e90b70d 100644 --- a/main/src/cgeo/geocaching/utils/HtmlUtils.java +++ b/main/src/cgeo/geocaching/utils/HtmlUtils.java @@ -24,7 +24,7 @@ public final class HtmlUtils { * @param html * @return */ - public static String extractText(CharSequence html) { + public static String extractText(final CharSequence html) { if (StringUtils.isBlank(html)) { return StringUtils.EMPTY; } @@ -32,13 +32,13 @@ public final class HtmlUtils { // recognize images in textview HTML contents if (html instanceof Spanned) { - Spanned text = (Spanned) html; - Object[] styles = text.getSpans(0, text.length(), Object.class); - ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>(); - for (Object style : styles) { + final Spanned text = (Spanned) html; + final Object[] styles = text.getSpans(0, text.length(), Object.class); + final ArrayList<Pair<Integer, Integer>> removals = new ArrayList<>(); + for (final Object style : styles) { if (style instanceof ImageSpan) { - int start = text.getSpanStart(style); - int end = text.getSpanEnd(style); + final int start = text.getSpanStart(style); + final int end = text.getSpanEnd(style); removals.add(Pair.of(start, end)); } } @@ -47,12 +47,12 @@ public final class HtmlUtils { Collections.sort(removals, new Comparator<Pair<Integer, Integer>>() { @Override - public int compare(Pair<Integer, Integer> lhs, Pair<Integer, Integer> rhs) { + public int compare(final Pair<Integer, Integer> lhs, final Pair<Integer, Integer> rhs) { return rhs.getRight().compareTo(lhs.getRight()); } }); result = text.toString(); - for (Pair<Integer, Integer> removal : removals) { + for (final Pair<Integer, Integer> removal : removals) { result = result.substring(0, removal.getLeft()) + result.substring(removal.getRight()); } } @@ -60,4 +60,14 @@ public final class HtmlUtils { // now that images are gone, do a normal html to text conversion return Html.fromHtml(result).toString().trim(); } + + public static String removeExtraParagraph(final String html) { + if (StringUtils.startsWith(html, "<p>") && StringUtils.endsWith(html, "</p>")) { + final String paragraph = StringUtils.substring(html, "<p>".length(), html.length() - "</p>".length()).trim(); + if (extractText(paragraph).equals(paragraph)) { + return paragraph; + } + } + return html; + } } diff --git a/main/src/cgeo/geocaching/utils/ImageUtils.java b/main/src/cgeo/geocaching/utils/ImageUtils.java index 739ecc4..c2b7327 100644 --- a/main/src/cgeo/geocaching/utils/ImageUtils.java +++ b/main/src/cgeo/geocaching/utils/ImageUtils.java @@ -1,6 +1,7 @@ package cgeo.geocaching.utils; import cgeo.geocaching.CgeoApplication; +import cgeo.geocaching.Image; import cgeo.geocaching.R; import cgeo.geocaching.compatibility.Compatibility; @@ -25,6 +26,8 @@ import android.graphics.drawable.Drawable; import android.media.ExifInterface; import android.net.Uri; import android.os.Environment; +import android.text.Html; +import android.text.Html.ImageGetter; import android.util.Base64; import android.util.Base64InputStream; import android.widget.TextView; @@ -36,8 +39,11 @@ import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import java.text.SimpleDateFormat; +import java.util.Collection; import java.util.Date; +import java.util.LinkedHashSet; import java.util.Locale; +import java.util.Set; public final class ImageUtils { private static final int[] ORIENTATIONS = new int[] { @@ -49,6 +55,10 @@ public final class ImageUtils { private static final int[] ROTATION = new int[] { 90, 180, 270 }; private static final int MAX_DISPLAY_IMAGE_XY = 800; + // Images whose URL contains one of those patterns will not be available on the Images tab + // for opening into an external application. + private final static String[] NO_EXTERNAL = new String[] { "geocheck.org" }; + private ImageUtils() { // Do not let this class be instantiated, this is a utility class. } @@ -61,7 +71,7 @@ public final class ImageUtils { * @return BitmapDrawable The scaled image */ public static BitmapDrawable scaleBitmapToFitDisplay(@NonNull final Bitmap image) { - Point displaySize = Compatibility.getDisplaySize(); + final Point displaySize = Compatibility.getDisplaySize(); final int maxWidth = displaySize.x - 25; final int maxHeight = displaySize.y - 25; return scaleBitmapTo(image, maxWidth, maxHeight); @@ -76,7 +86,7 @@ public final class ImageUtils { */ @Nullable public static Bitmap readAndScaleImageToFitDisplay(@NonNull final String filename) { - Point displaySize = Compatibility.getDisplaySize(); + final Point displaySize = Compatibility.getDisplaySize(); // Restrict image size to 800 x 800 to prevent OOM on tablets final int maxWidth = Math.min(displaySize.x - 25, MAX_DISPLAY_IMAGE_XY); final int maxHeight = Math.min(displaySize.y - 25, MAX_DISPLAY_IMAGE_XY); @@ -128,12 +138,12 @@ public final class ImageUtils { */ public static void storeBitmap(final Bitmap bitmap, final Bitmap.CompressFormat format, final int quality, final String pathOfOutputImage) { try { - FileOutputStream out = new FileOutputStream(pathOfOutputImage); - BufferedOutputStream bos = new BufferedOutputStream(out); + final FileOutputStream out = new FileOutputStream(pathOfOutputImage); + final BufferedOutputStream bos = new BufferedOutputStream(out); bitmap.compress(format, quality, bos); bos.flush(); bos.close(); - } catch (IOException e) { + } catch (final IOException e) { Log.e("ImageHelper.storeBitmap", e); } } @@ -152,7 +162,7 @@ public final class ImageUtils { if (maxXY <= 0) { return filePath; } - Bitmap image = readDownsampledImage(filePath, maxXY, maxXY); + final Bitmap image = readDownsampledImage(filePath, maxXY, maxXY); if (image == null) { return null; } @@ -184,7 +194,7 @@ public final class ImageUtils { try { final ExifInterface exif = new ExifInterface(filePath); orientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_NORMAL); - } catch (IOException e) { + } catch (final IOException e) { Log.e("ImageUtils.readDownsampledImage", e); } final BitmapFactory.Options sizeOnlyOptions = new BitmapFactory.Options(); @@ -233,7 +243,7 @@ public final class ImageUtils { } // Create a media file name - String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); + final String timeStamp = new SimpleDateFormat("yyyyMMdd_HHmmss", Locale.US).format(new Date()); return new File(mediaStorageDir.getPath() + File.separator + "IMG_" + timeStamp + ".jpg"); } @@ -254,7 +264,7 @@ public final class ImageUtils { * @return <tt>true</tt> if the URL contains at least one of the patterns, <tt>false</tt> otherwise */ public static boolean containsPattern(final String url, final String[] patterns) { - for (String entry : patterns) { + for (final String entry : patterns) { if (StringUtils.containsIgnoreCase(url, entry)) { return true; } @@ -282,7 +292,7 @@ public final class ImageUtils { /** * Decode a base64-encoded string and save the result into a stream. - * + * * @param inString * the encoded string * @param out @@ -303,15 +313,39 @@ public final class ImageUtils { } /** + * Add images present in the HTML description to the existing collection. + * + * @param images a collection of images + * @param htmlText the HTML description to be parsed + * @param geocode the common title for images in the description + */ + public static void addImagesFromHtml(final Collection<Image> images, final String htmlText, final String geocode) { + final Set<String> urls = new LinkedHashSet<>(); + for (final Image image : images) { + urls.add(image.getUrl()); + } + Html.fromHtml(StringUtils.defaultString(htmlText), new ImageGetter() { + @Override + public Drawable getDrawable(final String source) { + if (!urls.contains(source) && canBeOpenedExternally(source)) { + images.add(new Image(source, StringUtils.defaultString(geocode))); + urls.add(source); + } + return null; + } + }, null); + } + + /** * Container which can hold a drawable (initially an empty one) and get a newer version when it * becomes available. It also invalidates the view the container belongs to, so that it is * redrawn properly. */ - @SuppressWarnings("deprecation") - public final static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> { + public static class ContainerDrawable extends BitmapDrawable implements Action1<Drawable> { private Drawable drawable; final private TextView view; + @SuppressWarnings("deprecation") public ContainerDrawable(@NonNull final TextView view) { this.view = view; drawable = null; @@ -324,7 +358,7 @@ public final class ImageUtils { } @Override - public void draw(final Canvas canvas) { + public final void draw(final Canvas canvas) { if (drawable != null) { drawable.draw(canvas); } @@ -337,8 +371,55 @@ public final class ImageUtils { view.setText(view.getText()); } - public void updateFrom(final Observable<? extends Drawable> drawableObservable) { + public final void updateFrom(final Observable<? extends Drawable> drawableObservable) { drawableObservable.observeOn(AndroidSchedulers.mainThread()).subscribe(this); } } + + /** + * Image that automatically scales to fit a line of text in the containing {@link TextView}. + */ + public final static class LineHeightContainerDrawable extends ContainerDrawable { + private final TextView view; + + public LineHeightContainerDrawable(@NonNull final TextView view, final Observable<? extends Drawable> drawableObservable) { + super(view, drawableObservable); + this.view = view; + } + + @Override + public void call(final Drawable newDrawable) { + super.call(newDrawable); + setBounds(ImageUtils.scaleImageToLineHeight(newDrawable, view)); + } + } + + public static boolean canBeOpenedExternally(final String source) { + return !containsPattern(source, NO_EXTERNAL); + } + + public static Rect scaleImageToLineHeight(final Drawable drawable, final TextView view) { + final int lineHeight = (int) (view.getLineHeight() * 0.8); + final int width = drawable.getIntrinsicWidth() * lineHeight / drawable.getIntrinsicHeight(); + return new Rect(0, 0, width, lineHeight); + } + + public static Bitmap convertToBitmap(final Drawable drawable) { + if (drawable instanceof BitmapDrawable) { + return ((BitmapDrawable) drawable).getBitmap(); + } + + // handle solid colors, which have no width + int width = drawable.getIntrinsicWidth(); + width = width > 0 ? width : 1; + int height = drawable.getIntrinsicHeight(); + height = height > 0 ? height : 1; + + final Bitmap bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888); + final Canvas canvas = new Canvas(bitmap); + drawable.setBounds(0, 0, canvas.getWidth(), canvas.getHeight()); + drawable.draw(canvas); + + return bitmap; + } } diff --git a/main/src/cgeo/geocaching/utils/JsonUtils.java b/main/src/cgeo/geocaching/utils/JsonUtils.java new file mode 100644 index 0000000..492e137 --- /dev/null +++ b/main/src/cgeo/geocaching/utils/JsonUtils.java @@ -0,0 +1,20 @@ +package cgeo.geocaching.utils; + +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.ObjectReader; +import com.fasterxml.jackson.databind.ObjectWriter; +import com.fasterxml.jackson.databind.node.JsonNodeFactory; + +public class JsonUtils { + + private static final ObjectMapper mapper = new ObjectMapper(); + public static final ObjectReader reader = mapper.reader(); + public static final ObjectWriter writer = mapper.writer(); + + public static final JsonNodeFactory factory = new JsonNodeFactory(true); + + private JsonUtils() { + // Do not instantiate + } + +} diff --git a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java index a69f427..d4cf16e 100644 --- a/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java +++ b/main/src/cgeo/geocaching/utils/LeastRecentlyUsedSet.java @@ -20,12 +20,9 @@ import java.util.List; * access has to be guarded externally or the synchronized getAsList method can be used * to get a clone for iteration. */ -public class LeastRecentlyUsedSet<E> extends AbstractSet<E> - implements Cloneable, java.io.Serializable { +public class LeastRecentlyUsedSet<E> extends AbstractSet<E> { - private static final long serialVersionUID = -1942301031191419547L; - - private transient LeastRecentlyUsedMap<E, Object> map; + private final LeastRecentlyUsedMap<E, Object> map; private static final Object PRESENT = new Object(); public LeastRecentlyUsedSet(int maxEntries, int initialCapacity, float loadFactor) { @@ -132,26 +129,6 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> } /** - * (synchronized) Clone of the set - * Copy of the HashSet code if clone() - * - * @see HashSet - */ - @Override - @SuppressWarnings("unchecked") - public Object clone() throws CloneNotSupportedException { - try { - synchronized (this) { - final LeastRecentlyUsedSet<E> newSet = (LeastRecentlyUsedSet<E>) super.clone(); - newSet.map = (LeastRecentlyUsedMap<E, Object>) map.clone(); - return newSet; - } - } catch (CloneNotSupportedException e) { - throw new InternalError(); - } - } - - /** * Creates a clone as a list in a synchronized fashion. * * @return List based clone of the set @@ -160,56 +137,4 @@ public class LeastRecentlyUsedSet<E> extends AbstractSet<E> return new ArrayList<>(this); } - /** - * Serialization version of HashSet with the additional parameters for the custom Map - * - * @see HashSet - */ - private void writeObject(java.io.ObjectOutputStream s) - throws java.io.IOException { - // Write out any hidden serialization magic - s.defaultWriteObject(); - - // Write out HashMap capacity and load factor - s.writeInt(map.initialCapacity); - s.writeFloat(map.loadFactor); - s.writeInt(map.getMaxEntries()); - - // Write out size - s.writeInt(map.size()); - - // Write out all elements in the proper order. - for (final E e : map.keySet()) { - s.writeObject(e); - } - } - - /** - * Serialization version of HashSet with the additional parameters for the custom Map - * - * @see HashSet - */ - @SuppressWarnings("unchecked") - private void readObject(java.io.ObjectInputStream s) - throws java.io.IOException, ClassNotFoundException { - // Read in any hidden serialization magic - s.defaultReadObject(); - - // Read in HashMap capacity and load factor and create backing HashMap - final int capacity = s.readInt(); - final float loadFactor = s.readFloat(); - final int maxEntries = s.readInt(); - - map = new LeastRecentlyUsedMap.LruCache<>(maxEntries, capacity, loadFactor); - - // Read in size - final int size = s.readInt(); - - // Read in all elements in the proper order. - for (int i = 0; i < size; i++) { - E e = (E) s.readObject(); - map.put(e, PRESENT); - } - } - } diff --git a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java index 1401542..0c6365c 100644 --- a/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java +++ b/main/src/cgeo/geocaching/utils/OOMDumpingUncaughtExceptionHandler.java @@ -11,14 +11,12 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand private boolean defaultReplaced = false; public static boolean activateHandler() { - final OOMDumpingUncaughtExceptionHandler handler = new OOMDumpingUncaughtExceptionHandler(); return handler.activate(); } private boolean activate() { - defaultHandler = Thread.getDefaultUncaughtExceptionHandler(); // replace default handler if that has not been done already @@ -34,10 +32,8 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand } public static boolean resetToDefault() { - - boolean defaultResetted = false; - final UncaughtExceptionHandler unspecificHandler = Thread.getDefaultUncaughtExceptionHandler(); + boolean defaultResetted = unspecificHandler != null; if (unspecificHandler instanceof OOMDumpingUncaughtExceptionHandler) { final OOMDumpingUncaughtExceptionHandler handler = (OOMDumpingUncaughtExceptionHandler) unspecificHandler; @@ -48,7 +44,6 @@ public class OOMDumpingUncaughtExceptionHandler implements UncaughtExceptionHand } private boolean reset() { - final boolean resetted = defaultReplaced; if (defaultReplaced) { diff --git a/main/src/cgeo/geocaching/utils/RxUtils.java b/main/src/cgeo/geocaching/utils/RxUtils.java index 241ba78..ef79f93 100644 --- a/main/src/cgeo/geocaching/utils/RxUtils.java +++ b/main/src/cgeo/geocaching/utils/RxUtils.java @@ -1,23 +1,49 @@ package cgeo.geocaching.utils; import rx.Observable; +import rx.Observable.OnSubscribe; +import rx.Observable.Operator; import rx.Scheduler; +import rx.Scheduler.Worker; +import rx.Subscriber; +import rx.android.schedulers.AndroidSchedulers; +import rx.functions.Action0; +import rx.functions.Func1; +import rx.internal.operators.OperatorTakeWhile; import rx.observables.BlockingObservable; import rx.schedulers.Schedulers; +import rx.subscriptions.Subscriptions; + +import android.os.Handler; +import android.os.HandlerThread; +import android.os.Looper; import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit; +import java.util.concurrent.atomic.AtomicInteger; public class RxUtils { - // Utility class, not to be instanciated - private RxUtils() {} + private RxUtils() { + // Utility class, not to be instantiated + } public final static Scheduler computationScheduler = Schedulers.computation(); public static final Scheduler networkScheduler = Schedulers.from(new ThreadPoolExecutor(10, 10, 5, TimeUnit.SECONDS, new LinkedBlockingQueue<Runnable>())); + private static final HandlerThread looperCallbacksThread = + new HandlerThread("Looper callbacks thread", android.os.Process.THREAD_PRIORITY_BACKGROUND); + + static { + looperCallbacksThread.start(); + } + + public static final Looper looperCallbacksLooper = looperCallbacksThread.getLooper(); + public static final Scheduler looperCallbacksScheduler = AndroidSchedulers.handlerThread(new Handler(looperCallbacksLooper)); + public static final Worker looperCallbacksWorker = looperCallbacksScheduler.createWorker(); + public static <T> void waitForCompletion(final BlockingObservable<T> observable) { observable.lastOrDefault(null); } @@ -25,4 +51,73 @@ public class RxUtils { public static void waitForCompletion(final Observable<?>... observables) { waitForCompletion(Observable.merge(observables).toBlocking()); } + + /** + * Subscribe function whose subscription and unsubscription take place on a looper thread. + * + * @param <T> + * the type of the observable + */ + public static abstract class LooperCallbacks<T> implements OnSubscribe<T> { + + final AtomicInteger counter = new AtomicInteger(0); + final long stopDelay; + final TimeUnit stopDelayUnit; + protected Subscriber<? super T> subscriber; + + public LooperCallbacks(final long stopDelay, final TimeUnit stopDelayUnit) { + this.stopDelay = stopDelay; + this.stopDelayUnit = stopDelayUnit; + } + + public LooperCallbacks() { + this(0, TimeUnit.SECONDS); + } + + @Override + final public void call(final Subscriber<? super T> subscriber) { + this.subscriber = subscriber; + looperCallbacksWorker.schedule(new Action0() { + @Override + public void call() { + if (counter.getAndIncrement() == 0) { + onStart(); + } + subscriber.add(Subscriptions.create(new Action0() { + @Override + public void call() { + looperCallbacksWorker.schedule(new Action0() { + @Override + public void call() { + if (counter.decrementAndGet() == 0) { + onStop(); + } + } + }, stopDelay, stopDelayUnit); + } + })); + } + }); + } + + abstract protected void onStart(); + + abstract protected void onStop(); + } + + public static <T> Operator<T, T> operatorTakeUntil(final Func1<? super T, Boolean> predicate) { + return new OperatorTakeWhile<>(new Func1<T, Boolean>() { + private boolean quitting = false; + + @Override + public Boolean call(final T item) { + if (quitting) { + return false; + } + quitting |= predicate.call(item); + return true; + } + }); + } + } diff --git a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java b/main/src/cgeo/geocaching/utils/StartableHandlerThread.java deleted file mode 100644 index 91ab1d0..0000000 --- a/main/src/cgeo/geocaching/utils/StartableHandlerThread.java +++ /dev/null @@ -1,80 +0,0 @@ -package cgeo.geocaching.utils; - -import org.eclipse.jdt.annotation.NonNull; -import rx.Subscriber; -import rx.functions.Action0; -import rx.subscriptions.Subscriptions; - -import android.annotation.SuppressLint; -import android.content.Context; -import android.os.Handler; -import android.os.HandlerThread; -import android.os.Message; - -/** - * Derivated class of {@link android.os.HandlerThread} with an exposed handler and a start/stop mechanism - * based on subscriptions. - */ - -public class StartableHandlerThread extends HandlerThread { - - private final static int START = 1; - private final static int STOP = 2; - - static public interface Callback { - public void start(final Context context, final Handler handler); - public void stop(); - } - - // The handler and the thread are intimely linked, there will be no leak. - @SuppressLint("HandlerLeak") - private class StartableHandler extends Handler { - public StartableHandler() { - super(StartableHandlerThread.this.getLooper()); - } - - @Override - public void handleMessage(final Message message) { - if (callback != null) { - switch (message.what) { - case START: - callback.start((Context) message.obj, this); - break; - case STOP: - callback.stop(); - break; - } - } - } - } - - private Handler handler; - private Callback callback; - - public StartableHandlerThread(@NonNull final String name, final int priority, final Callback callback) { - super(name, priority); - this.callback = callback; - } - - public StartableHandlerThread(@NonNull final String name, final int priority) { - this(name, priority, null); - } - - public synchronized Handler getHandler() { - if (handler == null) { - handler = new StartableHandler(); - } - return handler; - } - - public void start(final Subscriber<?> subscriber, final Context context) { - getHandler().obtainMessage(START, context).sendToTarget(); - subscriber.add(Subscriptions.create(new Action0() { - @Override - public void call() { - getHandler().sendEmptyMessage(STOP); - } - })); - } - -} diff --git a/main/src/cgeo/geocaching/utils/TextUtils.java b/main/src/cgeo/geocaching/utils/TextUtils.java index 77aa167..04a9007 100644 --- a/main/src/cgeo/geocaching/utils/TextUtils.java +++ b/main/src/cgeo/geocaching/utils/TextUtils.java @@ -27,11 +27,11 @@ public final class TextUtils { } /** - * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned + * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned * * @param data * Data to search in - * @param p + * @param pattern * Pattern to search for * @param trim * Set to true if the group found should be trim'ed @@ -44,37 +44,38 @@ public final class TextUtils { * @return defaultValue or the n-th group if the pattern matches (trimmed if wanted) */ @SuppressFBWarnings("DM_STRING_CTOR") - public static String getMatch(@Nullable final String data, final Pattern p, final boolean trim, final int group, final String defaultValue, final boolean last) { + public static String getMatch(@Nullable final String data, final Pattern pattern, final boolean trim, final int group, final String defaultValue, final boolean last) { if (data != null) { - - String result = null; - final Matcher matcher = p.matcher(data); - + final Matcher matcher = pattern.matcher(data); if (matcher.find()) { - result = matcher.group(group); - } - if (null != result) { - final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result); - result = remover.replaceAll(" "); + String result = matcher.group(group); + while (last && matcher.find()) { + result = matcher.group(group); + } - return trim ? new String(result).trim() : new String(result); - // Java copies the whole page String, when matching with regular expressions - // later this would block the garbage collector, as we only need tiny parts of the page - // see http://developer.android.com/reference/java/lang/String.html#backing_array - // Thus the creating of a new String via String constructor is necessary here!! + if (result != null) { + final Matcher remover = PATTERN_REMOVE_NONPRINTABLE.matcher(result); + result = remover.replaceAll(" "); - // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler! + // Some versions of Java copy the whole page String, when matching with regular expressions + // later this would block the garbage collector, as we only need tiny parts of the page + // see http://developer.android.com/reference/java/lang/String.html#backing_array + // Thus the creating of a new String via String constructor is voluntary here!! + // And BTW: You cannot even see that effect in the debugger, but must use a separate memory profiler! + return trim ? new String(result).trim() : new String(result); + } } } + return defaultValue; } /** - * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned + * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned * * @param data * Data to search in - * @param p + * @param pattern * Pattern to search for * @param trim * Set to true if the group found should be trim'ed @@ -82,38 +83,35 @@ public final class TextUtils { * Value to return if the pattern is not found * @return defaultValue or the first group if the pattern matches (trimmed if wanted) */ - public static String getMatch(final String data, final Pattern p, final boolean trim, final String defaultValue) { - return TextUtils.getMatch(data, p, trim, 1, defaultValue, false); + public static String getMatch(final String data, final Pattern pattern, final boolean trim, final String defaultValue) { + return TextUtils.getMatch(data, pattern, trim, 1, defaultValue, false); } /** - * Searches for the pattern p in the data. If the pattern is not found defaultValue is returned + * Searches for the pattern pattern in the data. If the pattern is not found defaultValue is returned * * @param data * Data to search in - * @param p + * @param pattern * Pattern to search for * @param defaultValue * Value to return if the pattern is not found * @return defaultValue or the first group if the pattern matches (trimmed) */ - public static String getMatch(@Nullable final String data, final Pattern p, final String defaultValue) { - return TextUtils.getMatch(data, p, true, 1, defaultValue, false); + public static String getMatch(@Nullable final String data, final Pattern pattern, final String defaultValue) { + return TextUtils.getMatch(data, pattern, true, 1, defaultValue, false); } /** - * Searches for the pattern p in the data. + * Searches for the pattern pattern in the data. * * @param data - * @param p - * @return true if data contains the pattern p + * @param pattern + * @return true if data contains the pattern pattern */ - public static boolean matches(final String data, final Pattern p) { - if (data == null) { - return false; - } + public static boolean matches(final String data, final Pattern pattern) { // matcher is faster than String.contains() and more flexible - it takes patterns instead of fixed texts - return p.matcher(data).find(); + return data != null && pattern.matcher(data).find(); } @@ -182,7 +180,7 @@ public final class TextUtils { */ public static long checksum(final String input) { final CRC32 checksum = new CRC32(); - checksum.update(input.getBytes()); + checksum.update(input.getBytes(CHARSET_UTF8)); return checksum.getValue(); } } diff --git a/main/templates/keys.xml b/main/templates/keys.xml index d40c0e3..b624b86 100644 --- a/main/templates/keys.xml +++ b/main/templates/keys.xml @@ -23,7 +23,7 @@ <string name="oc_ro_okapi_consumer_key" translatable="false">@ocro.okapi.consumer.key@</string> <string name="oc_ro_okapi_consumer_secret" translatable="false">@ocro.okapi.consumer.secret@</string> - <!-- Opencaching.og.uk --> + <!-- Opencaching.org.uk --> <string name="oc_uk_okapi_consumer_key" translatable="false">@ocuk.okapi.consumer.key@</string> <string name="oc_uk_okapi_consumer_secret" translatable="false">@ocuk.okapi.consumer.secret@</string> </resources> diff --git a/main/thirdparty/android/support/v4/app/FragmentListActivity.java b/main/thirdparty/android/support/v4/app/FragmentListActivity.java index a7f8880..9641249 100644 --- a/main/thirdparty/android/support/v4/app/FragmentListActivity.java +++ b/main/thirdparty/android/support/v4/app/FragmentListActivity.java @@ -254,12 +254,10 @@ public class FragmentListActivity extends FragmentActivity { /** * Provide the cursor for the list view. */ - public void setListAdapter(final ListAdapter adapter) { - synchronized (this) { - ensureList(); - mAdapter = adapter; - mList.setAdapter(adapter); - } + public synchronized void setListAdapter(final ListAdapter adapter) { + ensureList(); + mAdapter = adapter; + mList.setAdapter(adapter); } /** diff --git a/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java b/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java index 027ff53..01a1872 100644 --- a/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java +++ b/main/thirdparty/cgeo/org/kxml2/io/KXmlSerializer.java @@ -32,6 +32,7 @@ import java.io.OutputStreamWriter; import java.io.Writer; import java.util.Locale; +@SuppressWarnings("ALL") public class KXmlSerializer implements XmlSerializer { // static final String UNDEFINED = ":"; |
