diff --git a/.dockerignore b/.dockerignore deleted file mode 100644 index 63ab206..0000000 --- a/.dockerignore +++ /dev/null @@ -1,5 +0,0 @@ -**/.* -**/build.Containerfile -**/local.properties -**/build -**/*.iml diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 69b5f38..86bab0b 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -1,33 +1,25 @@ +image: ubuntu:focal + variables: - GIT_SUBMODULE_STRATEGY: recursive - ANDROID_HOME: $PWD/android-sdk + GIT_SUBMODULE_STRATEGY: recursive + ANDROID_HOME: $PWD/android-sdk + ANDROID_SDK_TOOLS: 7583922_latest + ANDROID_SDK_LICENSE_HASH: 24333f8a63b6825ea9c5514f83c2829b004d1fee -reference: - image: debian:bookworm-slim - before_script: - - apt-get update - - apt-get -y install ca-certificates buildah - # switch to iptables legacy, as GitLab CI doesn't support nftables - - apt-get -y install --no-install-recommends iptables - - update-alternatives --set iptables /usr/sbin/iptables-legacy - script: - - buildah build --file build.Containerfile --output build . - after_script: - - sha256sum build/* - artifacts: - name: oeffi-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA - paths: - - build/** +before_script: + - apt-get update + - apt-get -y upgrade + - apt-get -y install ${JDK_PACKAGE} + - apt-get -y install wget gradle + - wget --quiet --output-document=commandlinetools-linux.zip https://dl.google.com/android/repository/commandlinetools-linux-${ANDROID_SDK_TOOLS}.zip + - mkdir -p ${ANDROID_HOME} + - unzip -d ${ANDROID_HOME} commandlinetools-linux.zip + - mkdir -p ${ANDROID_HOME}/licenses + - echo -e "\n${ANDROID_SDK_LICENSE_HASH}" >> ${ANDROID_HOME}/licenses/android-sdk-license -bookworm-jdk17: - image: debian:bookworm-slim +build: + parallel: + matrix: + - JDK_PACKAGE: [ openjdk-8-jdk, openjdk-11-jdk ] script: - - apt-get update - - apt-get -y install openjdk-17-jdk-headless gradle sdkmanager - - yes | sdkmanager --licenses >/dev/null || true - gradle build --stacktrace - - gradle --version - artifacts: - name: oeffi-$CI_JOB_NAME-$CI_COMMIT_REF_SLUG-$CI_COMMIT_SHORT_SHA - paths: - - oeffi/build/outputs/apk/**/*.apk diff --git a/build.Containerfile b/build.Containerfile deleted file mode 100644 index a52d6b8..0000000 --- a/build.Containerfile +++ /dev/null @@ -1,57 +0,0 @@ -# -# Usage: -# -# docker build --file build.Containerfile --output . -# or -# podman build --file build.Containerfile --output . -# -# For improved reproducibility, the project directory entries can be ordered -# like this: -# -# buildah build --cap-add=sys_admin --device /dev/fuse --file build.Containerfile --output . -# -# In any case, the unsigned APKs are written to the specified output -# directory. Use `apksigner` to sign before installing via `adb install`. -# - -FROM debian:bookworm-slim AS build-stage - -# install debian packages -ENV DEBIAN_FRONTEND noninteractive -RUN --mount=target=/var/lib/apt/lists,type=cache,sharing=locked \ - --mount=target=/var/cache/apt,type=cache,sharing=locked \ - /bin/rm -f /etc/apt/apt.conf.d/docker-clean && \ - /usr/bin/apt-get update && \ - /usr/bin/apt-get --yes --no-install-recommends install disorderfs openjdk-17-jdk-headless gradle sdkmanager && \ - /bin/ln -fs /usr/share/zoneinfo/CET /etc/localtime && \ - /usr/sbin/dpkg-reconfigure --frontend noninteractive tzdata && \ - /bin/ln -s /proc/self/mounts /etc/mtab && \ - /usr/sbin/adduser --disabled-login --gecos "" builder - -# give up privileges -USER builder - -# copy project source code -WORKDIR /home/builder -COPY --chown=builder / project/ - -# accept SDK licenses -ENV ANDROID_HOME /home/builder/android-sdk -RUN --mount=target=/home/builder/android-sdk,type=cache,uid=1000,gid=1000,sharing=locked \ - yes | /usr/bin/sdkmanager --licenses >/dev/null - -# build project -RUN --mount=target=/home/builder/android-sdk,type=cache,uid=1000,gid=1000,sharing=locked \ - --mount=target=/home/builder/.gradle,type=cache,uid=1000,gid=1000,sharing=locked \ - if [ -e /dev/fuse ] ; \ - then /bin/mv project project.u && /bin/mkdir project && \ - /usr/bin/disorderfs --sort-dirents=yes --reverse-dirents=no project.u project ; \ - fi && \ - /usr/bin/gradle --project-dir project/ --no-build-cache --no-daemon --no-parallel clean :oeffi:assembleRelease && \ - if [ -e /dev/fuse ] ; \ - then /bin/fusermount -u project | true && /bin/rmdir project && /bin/mv project.u project ; \ - fi - -# export build output -FROM scratch AS export-stage -COPY --from=build-stage /home/builder/project/oeffi/build/outputs/apk/release/oeffi-release-unsigned.apk / diff --git a/build.gradle b/build.gradle index 48ae808..2706ca3 100644 --- a/build.gradle +++ b/build.gradle @@ -6,7 +6,7 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:3.1.4' - classpath 'net.sf.proguard:proguard-gradle:6.2.2' + classpath 'net.sf.proguard:proguard-gradle:6.0.3' classpath('fr.avianey.androidsvgdrawable:gradle-plugin:3.0.2') { exclude group: 'xerces' } diff --git a/oeffi/AndroidManifest.xml b/oeffi/AndroidManifest.xml index b68ae87..876e0af 100644 --- a/oeffi/AndroidManifest.xml +++ b/oeffi/AndroidManifest.xml @@ -18,21 +18,22 @@ + android:versionCode="120017" + android:versionName="12.0.17"> + android:minSdkVersion="21" + android:targetSdkVersion="30" /> - + + + @@ -86,7 +86,6 @@ + + + + + + @@ -113,11 +118,107 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:theme="@style/My.Theme.Translucent"> + + + + + + + + + + - - - - - - - - - @@ -166,7 +257,6 @@ - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + android:taskAffinity="de.schildbach.oeffi.directions" /> @@ -217,7 +511,6 @@ + android:theme="@style/My.Theme.Fullscreen"> @@ -263,7 +554,4 @@ android:exported="false" /> - - - diff --git a/oeffi/CHANGES b/oeffi/CHANGES index 7b8aec2..b8d9eab 100644 --- a/oeffi/CHANGES +++ b/oeffi/CHANGES @@ -1,68 +1,4 @@ -v13.0.11 - -* Add region again: Poland (PKP) - -v13.0.4-v13.0.10 - -* Target Android 15. - -v13.0.3 - -* Remove regions: Czech Republic, Italy, Paris, Spain, Nicaragua - -v13.0.2 - -* Remove region: Finland - -v13.0-v13.0.1 - -* Running the app now requires Android 7.0 (Nougat) or higher. -* Building the app now requires OpenJDK 17. -* Remove `google` flavor. -* Git: rename `master` to `main` branch. - -v12.1.18-v12.1.21 - -* Remove region: Belgium (SNCB) - -v12.1.15-v12.1.17 - -* Migrate region Augsburg from EFA to Hafas - -v12.1.7-v12.1.14 - -* Merge regions Oberelbe (VVO) and Mittelsachsen (VMS) into Saxony - -v12.1.3-v12.1.6 - -* Remove region: Poland (PKP) - -v12.1.1-v12.1.2 - -* Use activity result API for permission requests, picking contacts and picking favorite stations. - -v12.1 - -* Enable per-app locale selection on Android 13 (and higher). -* Drop unnecessary permissions. - -v12.0.25 - -* Target Android 13. - -v12.0.23-v12.0.24 - -* Migrate periodic app-widget refresh from JobIntentService to JobScheduler - -v12.0.21-v12.0.22 - -* Target Android 12. - -v12.0.19-v12.0.20 - -* Fix showing a location on an external maps app. - -v12.0.12-v12.0.18 +v12.0.12-v12.0.17 * Remove region: Schweizerische Bundesbahnen (SBB) diff --git a/oeffi/assets/networks.txt b/oeffi/assets/networks.txt index 367e768..5fe780e 100644 --- a/oeffi/assets/networks.txt +++ b/oeffi/assets/networks.txt @@ -8,7 +8,7 @@ DB|de-DE|DE BVG|de-DE|Brandenburg;Berlin VBB|de-DE|Brandenburg BAYERN|de-DE|Bayern;Würzburg;Regensburg -AVV_AUGSBURG|de-DE|Augsburg +AVV|de-DE|Augsburg MVV|de-DE|Bayern;München INVG|de-DE|Ingolstadt VGN|de-DE|Nürnberg;Fürth;Erlangen @@ -19,7 +19,8 @@ SH|de-DE|Schleswig-Holstein;Kiel;Lübeck;Hamburg GVH|de-DE|Niedersachsen;Hannover;Hamburg BSVAG|de-DE|Braunschweig;Wolfsburg VBN|de-DE|Niedersachsen;Hamburg;Bremen;Bremerhaven;Oldenburg (Oldenburg);Osnabrück;Göttingen;Rostock -VVO|de-DE|Sachsen;Dresden;Mittelsachsen;Chemnitz +VVO|de-DE|Sachsen;Dresden +VMS|de-DE|Mittelsachsen;Chemnitz NASA|de-DE|Sachsen;Leipzig;Sachsen-Anhalt;Magdeburg;Halle VMT|de-DE|Thüringen;Mittelthüringen;Erfurt;Jena;Gera;Weimar;Gotha VRR|de-DE|Nordrhein-Westfalen;Essen;Dortmund;Düsseldorf;Münster;Paderborn;Höxter;Bielefeld @@ -45,10 +46,25 @@ VVT|de-AT|Tirol|disabled SVV|de-AT|Salzburg|disabled VMOBIL|de-AT|Vorarlberg;Bregenz|disabled +# CZ +CZECH_REPUBLIC|cs-CZ|Tschechien;Praha;Prag|beta + # CH VBL|de-CH|Luzern ZVV|de-CH|Zürich +# IT +IT|it-IT|IT|alpha + +# FR +PARIS|fr-FR|FR|alpha + +# ES +SPAIN|es-ES|ES|alpha + +# BE +SNCB|be-BE|BE|alpha + # LU LU|lb-LU|LU;Luxemburg @@ -61,10 +77,16 @@ DSB|da-DK|DK;København # SE SE|sv-SE|SE;Stockholm +# FI +FINLAND|fi-FI|FI;Helsinki|beta + # GB TLEM|en-UK|GB;Greater London;Derbyshire;Leicestershire;Rutland;Northamptonshire;Nottinghamshire;Lincolnshire;Berkshire;Buckinghamshire;East Sussex;Hampshire;Isle of Wight;Kent;Oxfordshire;Surrey;West Sussex;Essex;Hertfordshire;Bedfordshire;Cambridgeshire;Norfolk;Suffolk;Somerset;Gloucestershire;Wiltshire;Dorset;Devon;Cornwall;West Devon;Stowford;Eastleigh;Swindon;Gloucester;Plymouth;Torbay;Bournemouth;Poole;Birmingham MERSEY|en-UK|GB;Liverpool|beta +# IE +TFI|ga-IE|IE;Dublin;GB;Belfast + # PL PL|pl-PL|PL;Warschau @@ -79,3 +101,6 @@ CMTA|us-US|US;Texas;Austin|beta # AU SYDNEY|en-AU|AU;New South Wales;Sydney MET|en-AU|AU;Victoria;Melbourne|disabled + +# NI +NICARAGUA|es-NI|NI;Managua|beta diff --git a/oeffi/assets/plans-index.txt b/oeffi/assets/plans-index.txt index c4bb1a5..2c4bf1b 100644 --- a/oeffi/assets/plans-index.txt +++ b/oeffi/assets/plans-index.txt @@ -3,13 +3,13 @@ # de -berlin_bsu_ab|52.520134,13.388018|2022-12-11|Berlin S+U-Bahn-Netz (AB)|Berliner Verkehrsbetriebe||BVG -berlin_bsu_abc|52.520134,13.388018|2022-12-11|Berlin S+U-Bahn-Netz (ABC)|Berliner Verkehrsbetriebe||BVG -berlin_tram|52.521152,13.412832|2022-12-16|Berlin Tram-Netz|Berliner Verkehrsbetriebe||BVG -berlin_bus|52.5071378,13.3318680|2022-12-09|Berlin Bus-Netz|Berliner Verkehrsbetriebe||BVG -berlin_nacht|52.520134,13.388018|2022-12-09|Berlin Nachtverkehr (geografisch)|Berliner Verkehrsbetriebe||BVG +berlin_bsu_ab|52.520134,13.388018|2020-10-31|Berlin S+U-Bahn-Netz (AB)|Berliner Verkehrsbetriebe||BVG +berlin_bsu_abc|52.520134,13.388018|2019-10-31|Berlin S+U-Bahn-Netz (ABC)|Berliner Verkehrsbetriebe||BVG +berlin_tram|52.521152,13.412832|2020-04-14|Berlin Tram-Netz|Berliner Verkehrsbetriebe||BVG +berlin_bus|52.5071378,13.3318680|2019-12-15|Berlin Bus-Netz|Berliner Verkehrsbetriebe||BVG +berlin_nacht|52.520134,13.388018|2020-10-31|Berlin Nachtverkehr (geografisch)|Berliner Verkehrsbetriebe||BVG berlin_transitmap|52.520134,13.388018|2018-12-09|New Berlin rapid transit route map|berlintransitmap.de -berlin_anbindung_ber|52.363127,13.50498|2020-11|BER (Flughafen Berlin Brandenburg) Anbindung|Robert Aehnelt, Creative Commons|https://upload.wikimedia.org/wikipedia/commons/e/e0/BER_Anbindung_2020.jpg +# berlin_anbindung_ber|52.363127,13.50498|2011-10|Berlin Brandenburg Flughafen Anbindung|Robert Aehnelt, Creative Commons|https://upload.wikimedia.org/wikipedia/commons/d/d3/Anbindung_BER.png brandenburg_regionalverkehr|52.525578,13.369523|2019-12-15|Brandenburg Regionalverkehr|Verkehrsverbund Berlin-Brandenburg||VBB potsdam_tag|52.390931,13.067171|2019-12-15|Potsdam Tagesliniennetz|Verkehrsverbund Berlin-Brandenburg||VBB potsdam_nacht|52.390931,13.067171|2019-12-15|Potsdam Nachtliniennetz|Verkehrsverbund Berlin-Brandenburg||VBB @@ -23,7 +23,6 @@ stettin|53.4182413,14.5494069|2019-12-09|Stettin|Zarząd Dróg i Transportu Miej dresden|51.050961,13.733239|2021-01-25|Dresden Liniennetz|Dresdner Verkehrsbetriebe AG dresden_rolli|51.050961,13.733239|2020|Dresden für mobilitätseingeschränkte Personen|Dresdner Verkehrsbetriebe AG dresden_nacht|51.050961,13.733239|2021-01-25|Dresden Nachtverkehr|Verkehrsverbund Oberelbe||VVO -dresden_tarif|51.050961,13.733239|2020-12-13|Dresden Tarifzonen|Verkehrsverbund Oberelbe||VVO chemnitz_tag|50.836227,12.919122|2018-02-26|Chemnitz Netzplan|Chemnitzer Verkehrs-AG chemnitz_nacht|50.836227,12.919122|2017-12-10|Chemnitz Nachtnetz|Chemnitzer Verkehrs-AG sachsen_spnv|50.925093,12.775949|2020-12-13|Sachsen SPNV-Netz @@ -32,7 +31,6 @@ leipzig_tag|51.345476,12.379382|2020-11-18|Leipzig Linien Tag|Leipziger Verkehrs leipzig_nacht|51.345476,12.379382|2020-11-18|Leipzig Linien Nacht|Leipziger Verkehrsbetriebe GmbH halle_tag|51.477347,11.985263|2020-08-27|Halle Linien Tag|SWH/HAVAG halle_nacht|51.477347,11.985263|2020-08-27|Halle Linien Nacht|SWH/HAVAG -jena|50.9248984,11.587666|2024|Jena|Jenaer Nahverkehr GmbH muenchen_schnellbahn|48.140377,11.560643|2019-12|München Schnellbahn|MVV muenchen_tram_metrobus|48.140377,11.560643|2019-12-15|München Tram/MetroBus|MVV/MVG muenchen_nacht|48.140377,11.560643|2019-12-15|München Nachtnetz|MVG @@ -43,10 +41,10 @@ augsburg|48.364936,10.893713|2018-12|Augsburg Innenraum|AVV GmbH augsburg_nachtbus|48.364936,10.893713|2018-12|Augsburg Nachtbus|AVV GmbH ulm_stadt|48.398610,9.983322|2018-12|Ulm / Neu-Ulm Stadtnetz|Donau-Iller-Nahverkehrsverbund GmbH ulm_regional|48.398610,9.983322|2018-11-02|Ulm Regionalnetz|Donau-Iller-Nahverkehrsverbund GmbH -nuernberg_gesamtraum|49.445719,11.082618|2024-01-01|Nürnberg Schienennetz Gesamtraum|Verkehrsverbund Großraum Nürnberg||VGN -nuernberg_verkehrsnetz|49.445719,11.082618|2023-12-10|Nürnberg-Fürth Liniennetz|Verkehrsverbund Großraum Nürnberg||VGN -nuernberg_schiene|49.445719,11.082618|2023-12-10|Nürnberg-Fürth Schienennetz|Verkehrsverbund Großraum Nürnberg||VGN -nuernberg_nightliner|49.445719,11.082618|2023-12|Nürnberg-Fürth Nightliner|Verkehrsverbund Großraum Nürnberg||VGN +nuernberg_gesamtraum|49.445719,11.082618|2018-01-01|Nürnberg Schienennetz Gesamtraum|Verkehrsverbund Großraum Nürnberg||VGN +nuernberg_verkehrsnetz|49.445719,11.082618|2019-12-01|Nürnberg, Fürth, Stein Verkehrsnetz|Verkehrsverbund Großraum Nürnberg||VGN +nuernberg_schiene|49.445719,11.082618|2019-12-01|Nürnberg, Fürth Schienennetz|Verkehrsverbund Großraum Nürnberg||VGN +nuernberg_nightliner|49.445719,11.082618|2019-12-01|Nürnberg, Fürth Nightliner|Verkehrsverbund Großraum Nürnberg||VGN fuerth_verkehrsnetz|49.469832,10.990178|2019-12-01|Fürth Verkehrsnetz|Verkehrsverbund Großraum Nürnberg||VGN fuerth_nightliner|49.469832,10.990178|2016-12-11|Fürth Nightliner|Verkehrsverbund Großraum Nürnberg||VGN erlangen_verkehrsnetz|49.595851,11.001701|2019-12-01|Erlangen Verkehrsnetz|Verkehrsverbund Großraum Nürnberg||VGN @@ -60,26 +58,25 @@ regensburg_nacht|49.011489,12.09971|2018-04-14|Regensburg Nachtbus|Regensburger bremen|53.08319,8.81360|2020-03|Bremen Liniennetz|BSAG bremen_nacht|53.08319,8.81360||Bremen Nachtnetz|BSAG bremen_stadt|53.08319,8.81360|2020-02|Bremen Stadtnetz (geografisch)|BSAG -hamburg_usar|53.552946,10.006782|2023-12-10|Hamburg Schnellbahn/Regional|HVV -hamburg_metrobus_gross|53.552946,10.006782|2023-12-10|Hamburg MetroBus Liniennetz|HVV -hamburg_region|53.552946,10.006782|2023-12-10|Hamburg Regionalverkehr|HVV -hamburg_weiss_bus|53.552946,10.006782|2024-01-08|Hamburg Busliniennetz|Lucas Weiss -kiel_liniennetz|54.313282,10.132341|2023-12|Kiel Liniennetz|KVG -kiel_nacht|54.313282,10.132341|2022-12|Kiel Nachtliniennetz|KVG +hamburg_usar|53.552946,10.006782|2020-12-13|Hamburg Schnellbahn/Regional|HVV +hamburg_metrobus_gross|53.552946,10.006782|2020-12-13|Hamburg MetroBus Großbereich|HVV +hamburg_region|53.552946,10.006782|2019-12-15|Hamburg Regionalverkehr|HVV +kiel_liniennetz|54.313282,10.132341|2019-12|Kiel Liniennetz|KVG +kiel_nacht|54.313282,10.132341|2019-12|Kiel Nachtliniennetz|KVG luebeck_region|53.858795,10.664646|2018-12-09|Lübeck Liniennetz Region|LVG, nah.sh, Stadtverkehr Lübeck||SH -rostock_vvw_liniennetz|54.078139,12.131724|2023-08-28|Rostock Liniennetz|Verkehrsverbund Warnow -rostock_vvw_region|54.078139,12.131724|2023-08-28|Rostock Region|Verkehrsverbund Warnow -rostock_region|54.078139,12.131724|2023-12-10|Rostock und Umgebung|Lucas Weiss, CC-BY-NC-SA 4.0 +rostock_vvw_liniennetz|54.078139,12.131724|2019-01-07|Rostock Liniennetz|Verkehrsverbund Warnow +rostock_vvw_region|54.078139,12.131724|2019-01-07|Rostock Region|Verkehrsverbund Warnow +rostock_region|54.078139,12.131724|2021-01-04|Rostock und Umgebung|Lucas Weiss, CC-BY-NC-SA 4.0 wismar|53.903943,11.391928||Wismar Stadtverkehr|NAHBUS Nordwestmecklenburg GmbH nordwestmecklenburg|53.903943,11.391928|2016-03-14|Nordwestmecklenburg Busnetz|NAHBUS Nordwestmecklenburg GmbH osnabrueck|52.272832,8.061726|2018-08-09|Osnabrück Liniennetz|Stadtwerke Osnabrück osnabrueck_nacht|52.272832,8.061726|2018-08-09|Osnabrück NachtBus-Netz|Stadtwerke Osnabrück -stuttgart_verbund|48.784068,9.181713|2023-12|Stuttgart Verbund-Liniennetz|Verkehrs- und Tarifverbund Stuttgart GmbH -stuttgart_nacht|48.77861,9.179803|2023-12|Stuttgart Nachtverkehr|Verkehrs- und Tarifverbund Stuttgart GmbH +stuttgart_verbund|48.784068,9.181713|2018-12|Stuttgart Verbund-Liniennetz|Verkehrs- und Tarifverbund Stuttgart GmbH +stuttgart_nacht|48.77861,9.179803|2018-12|Stuttgart Nachtverkehr|Verkehrs- und Tarifverbund Stuttgart GmbH ringzug|48.113621,8.660603|2013-08|Ringzug|Zweckverband Ringzug -hannover_stadtbahn|52.376715,9.741168|2023-12-10|Hannover Stadtbahnnetz|GVH -hannover_bus|52.376715,9.741168|2023-12-10|Hannover Busnetz|GVH -hannover_regional|52.376715,9.741168|2023-12-10|Hannover Regional- und S-Bahn-Linien|GVH +hannover_stadtbahn|52.376715,9.741168|2018-12|Hannover Stadtbahnnetz|GVH +hannover_bus|52.376715,9.741168|2018-12|Hannover Busnetz|GVH +hannover_regional|52.376715,9.741168|2018-12|Hannover Regional- und S-Bahn-Linien|GVH celle_stadt|52.620411,10.059814|2019-01|Celle Liniennetz Stadt|CeBus celle_region|52.620411,10.059814|2018-01|Celle Liniennetz Region|CeBus goettingen|51.536290,9.926981|2019-12-15|Göttingen Liniennetz|Göttinger Verkehrsbetriebe GmbH @@ -90,24 +87,16 @@ bielefeld_netz|52.029241,8.532835|2018-06|Bielefeld Netzplan (geografisch)|moBie essen_schiene|51.451355,7.014793|2015-06-14|Essen SchienenNetz|EVAG essen_tag|51.451355,7.014793|2015-06-14|Essen TagNetz|EVAG essen_nacht|51.451355,7.014793|2013-06-09|Essen NachtNetz|EVAG -dortmund_schiene|51.517843,7.459272|2023-01|Dortmund Schienennetz|DSW21 -dortmund_tag|51.517843,7.459272|2023-12|Dortmund Netzplan Tag|DSW21 -dortmund_nacht|51.517843,7.459272|2021-01|Dortmund Netzplan Nacht|DSW21 -krefeld_stadt|51.323714,6.565800|2021-05|Krefeld Liniennetz Stadt|SWK/VRR -krefeld_stadt_nacht|51.323714,6.565800|2021-05|Krefeld Nachtnetz|SWK/VRR +krefeld_stadt|51.323714,6.565800|2013-06|Krefeld Liniennetz Stadt|SWK/VRR +krefeld_stadt_nacht|51.323714,6.565800|2013-06|Krefeld Nachtnetz|SWK/VRR frankfurt_liniennetz|50.106318,8.662139|2020-12-13|Frankfurt am Main Liniennetz|traffiQ frankfurt_flughafen_bus|50.037936,8.559958|2020-12-13|Frankfurt Flughafen Buslinien|traffiQ -gelsenkirchen_linienplan_tag|51.504768,7.102298|2023-01|Gelsenkirchen Linienplan Tag|Vestische -gelsenkirchen_linienplan_nacht|51.504768,7.102298|2023-01|Gelsenkirchen Linienplan Nacht|Vestische -recklinghausen_linienplan|51.616096,7.203273|2023-06|Recklinghausen Linienplan|Vestische -waltrop_linienplan|51.622799,7.390409|2022-08|Waltrop Linienplan|Vestische rmv_schnellbahn|50.106318,8.662139|2020-12-13|Rhein-Main Schnellbahn|RMV rmv_regional|50.106318,8.662139|2020-12-13|Rhein-Main Regional|RMV rmv_nacht|50.106318,8.662139|2020-12-13|Rhein-Main Nachtnetz|RMV mainz|50.001395,8.258818|2019-12-15|Mainz Tag|Mainzer Verkehrsgesellschaft mbH mainz_nacht|50.001395,8.258818|2019-12-15|Mainz Nacht|Mainzer Verkehrsgesellschaft mbH koeln_schnellverkehr|50.943579,6.95796|2019-12-15|Köln Schnellverkehr|Verkehrsverbund Rhein-Sieg GmbH -koeln_stadtbahn|50.943579,6.95796|2024-04-02|Köln Stadtbahn|Kölner Verkehrs-Betriebe AG koeln_bus|50.943579,6.95796|2019-12-15|Köln Busnetz|Verkehrsverbund Rhein-Sieg GmbH bonn_schnellverkehr|50.732784,7.096447|2019-12-15|Bonn Schnellverkehr|Verkehrsverbund Rhein-Sieg GmbH bonn_bus|50.732784,7.096447|2019-12-15|Bonn Busnetz|Verkehrsverbund Rhein-Sieg GmbH @@ -115,33 +104,29 @@ leverkusen_bus|51.036197,6.994385|2019-12-15|Leverkusen Busnetz|Verkehrsverbund nrw_regio|51.429807,6.775253|2016-12|NRW Busse & Bahnen|VRS aachen_schnellverkehr|50.768399,6.090705|2018-12|Aachen Schnellverkehr|Aachener Verkehrsverbund GmbH||AVV_AACHEN aachen_region|50.768399,6.090705|2018-12|Aachen Region, Bus und Bahn|Aachener Verkehrsverbund GmbH||AVV_AACHEN -darmstadt|49.872582,8.630916|2023-12-10|Darmstadt Stadt|Darmstadt-Dieburger Nahverkehrsorganisation -darmstadt_nacht|49.872582,8.630916|2023-12-10|Darmstadt NightLiner|Darmstadt-Dieburger Nahverkehrsorganisation -darmstadt_region|49.872582,8.630916|2023-12-10|Darmstadt-Dieburg Region|Darmstadt-Dieburger Nahverkehrsorganisation -duesseldorf|51.230794,6.769025|2024-01-07|Düsseldorf Liniennetz|Rheinbahn -duesseldorf_nacht|51.230794,6.769025|2024-01-07|Düsseldorf Nachtlinien|Rheinbahn +darmstadt|49.872582,8.630916|2020-12-13|Darmstadt Stadt|Darmstadt-Dieburger Nahverkehrsorganisation +darmstadt_nacht|49.872582,8.630916|2020-12-13|Darmstadt NightLiner|Darmstadt-Dieburger Nahverkehrsorganisation +darmstadt_region|49.872582,8.630916|2020-12-13|Darmstadt-Dieburg Region|Darmstadt-Dieburger Nahverkehrsorganisation +duesseldorf|51.230794,6.769025|2018-08-29|Düsseldorf Liniennetz|Rheinbahn +duesseldorf_nacht|51.230794,6.769025|2018-08-29|Düsseldorf Nachtlinien|Rheinbahn duisburg|51.428813,6.772554|2018-03-01|Duisburg Liniennetz|DVG/VRR schwerin|53.634476,11.407313|2018-07|Schwerin Liniennetz|Nahverkehr Schwerin GmbH -magdeburg_tag|52.130783,11.627347|2021-12-24|Magdeburg Liniennetz|Magdeburger Verkehrsbetriebe GmbH & Co. KG -magdeburg_nacht|52.130783,11.627347|2021-12-24|Magdeburg Nacht-Liniennetz|Magdeburger Verkehrsbetriebe GmbH & Co. KG -magdeburg_verbund|52.130783,11.627347|2020-12|Magdeburg Verbund-Liniennetz|Nahverkehrsservice Sachsen-Anhalt GmbH -hagen|51.362675,7.461087|2024-01-08|Hagen Liniennetz|Hagener Straßenbahn AG -hagen_nacht|51.362675,7.461087|2024-01-08|Hagen Nachtnetz|Hagener Straßenbahn AG -braunschweig_gesamt|52.252187,10.539705|2024-03-18|Braunschweig Liniennetz|Braunschweiger Verkehrs-GmbH -braunschweig_nacht|52.252187,10.539705|2024-03-18|Braunschweig Nachtnetz|Braunschweiger Verkehrs-GmbH +magdeburg_tag|52.130783,11.627347|2018-12-03|Magdeburg Liniennetz|Magdeburger Regionalverkehrsverbund +magdeburg_nacht|52.130783,11.627347|2018-12-03|Magdeburg Nacht-Liniennetz|Magdeburger Regionalverkehrsverbund +magdeburg_verbund|52.130783,11.627347|2018-12|Magdeburg Verbund-Liniennetz|Magdeburger Regionalverkehrsverbund +hagen|51.362675,7.461087|2019-12-15|Hagen Liniennetz|Hagener Straßenbahn AG +braunschweig_gesamt|52.252187,10.539705|2019-10-03|Braunschweig Liniennetz|Braunschweiger Verkehrs-GmbH +braunschweig_nacht|52.252187,10.539705|2019-10-03|Braunschweig Nachtnetz|Braunschweiger Verkehrs-GmbH salzgitter|52.15116,10.332488|2017-12|Salzgitter Liniennetz|KVG Braunschweig -wolfenbuettel|52.15898,10.532012|2021-10|Wolfenbüttel Liniennetz|KVG Braunschweig +wolfenbuettel|52.15898,10.532012|2018-10|Wolfenbüttel Liniennetz|KVG Braunschweig helmstedt|52.222331,11.010690|2015-12|Helmstedt Liniennetz|KVG Braunschweig harz|51.888518,10.554843|2018-06|Harz Liniennetz|KVG Braunschweig wolfsburg|52.429484,10.788249|2018-08-09|Wolfsburg Liniennetz|WVG -karlsruhe_liniennetz|48.993988,8.400328|2022-12-11|Karlsruhe Liniennetz|Karlsruher Verkehrsverbund||KVV -karlsruhe_rolli|48.993988,8.400328|2021-12-12|Karlsruhe für mobilitätseingeschränkte Personen|Karlsruher Verkehrsverbund||KVV +karlsruhe_liniennetz|48.993988,8.400328|2020-12-13|Karlsruhe Liniennetz|Karlsruher Verkehrsverbund||KVV +karlsruhe_rolli|48.993988,8.400328|2020-12-18|Karlsruhe für mobilitätseingeschränkte Personen|Karlsruher Verkehrsverbund||KVV karlsruhe_regio|48.993988,8.400328|2020-12-13|Karlsruhe Regionalverkehr|Karlsruher Verkehrsverbund||KVV -karlsruhe_bus|49.009498,8.404073|2022-12-11|Karlsruhe Busnetz|Karlsruher Verkehrsverbund||KVV +karlsruhe_bus|49.009498,8.404073|2020-12-13|Karlsruhe Busnetz|Karlsruher Verkehrsverbund||KVV karlsruhe_nightliner|49.009498,8.404073|2020-11-28|Karlsruhe Nightliner|Karlsruher Verkehrsverbund||KVV -badenbaden_busnetz|48.79014,8.19126|2021-02-18|Rastatt (Südlicher Landkreis) und Baden-Baden Busnetz|Karlsruher Verkehrsverbund||KVV -rastatt_busnetz|48.86065,8.21525|2021-02-18|Karlsruhe (Südlicher Landkreis) und Rastatt (Nördlicher Landkreis) Busnetz|Karlsruher Verkehrsverbund||KVV -bruchsal_busnetz|49.12428,8.59017|2021-02-18|Bruchsal (Nördlicher Landkreis) Busnetz|Karlsruher Verkehrsverbund||KVV freiburg_liniennetz|47.996556,7.840286|2019-11|Freiburg Liniennetz|Freiburger Verkehrs AG||VAGFR freiburg_nacht|47.996556,7.840286|2019-11|Freiburg Nachtbus|Freiburger Verkehrs AG||VAGFR freiburg_regio|47.996556,7.840286|2019-03|Freiburg Regio|RVF @@ -157,9 +142,9 @@ rhein_neckar_regio|49.481844,8.459115|2019-11|Rhein-Neckar Regionalverkehr|RNV G # at -linz|48.290893,14.291965|2024-01|Linz Verkehrslinienplan|Linz AG -linz_nacht|48.290893,14.291965|2024-01|Linz Nachtverkehr|Linz AG -innsbruck_liniennetz|47.26332,11.400951|2021|Innsbruck Liniennetz|Innsbrucker Verkehrsbetriebe GmbH +linz|48.290893,14.291965|2018-10|Linz Verkehrslinienplan|Linz AG +linz_nacht|48.290893,14.291965|2016-12|Linz Nachtverkehr|Linz AG +innsbruck_liniennetz|47.26332,11.400951|2018|Innsbruck Liniennetz|Innsbrucker Verkehrsbetriebe GmbH innsbruck_tram|47.26332,11.400951|2012-12-15|Innsbruck Straßenbahn|Steve Stipsits|http://www.public-transport.at/netzplan_innsbruck_gross_aktuell.gif graz|47.073371,15.416154|2018-09-08|Graz Liniennetz|Verbund Linie graz_nightline|47.073371,15.416154|2018-09-08|Graz Nightline|Verbund Linie @@ -203,7 +188,7 @@ amsterdam_centre|52.378861,4.900392||Amsterdam City Centre (geographical)|GVB copenhagen_city|55.672717,12.562532||Copenhagen City|DOT copenhagen_region|55.672717,12.562532||Copenhagen Greater Area|DOT copenhagen_night|55.672717,12.562532|2015-02|Copenhagen Night Bus|DOT -aarhus_dag|56.149808,10.204283|2022-06-26|Aarhus Dag|Damian Leonhardt +aarhus_dag|56.149808,10.204283|2020-12-13|Aarhus Dag|Damian Leonhardt # se @@ -211,6 +196,10 @@ gothenburg_tram|57.708792,11.973538|2015-12-13|Göteborg Tram and Trunk Bus|väs gothenburg_commuter|57.708792,11.973538|2016-08-21|Göteborg Express Buses and Commuter Trains|västtrafik gothenburg_ferry|57.708792,11.973538|2015-12-13|Göteborg Tram and Ferry|västtrafik gothenburg_region|57.708792,11.973538|2016-01-03|Göteborg Region|västtågen +jonkoping_bus|57.784691,14.163508||Jonkoping Bus||http://transportmaps.free.fr/maps/Sweden/Jonkoping/map_Bus_Lines.jpg +jonkoping_map|57.784691,14.163508||Jonkoping Map (geographical)||http://transportmaps.free.fr/maps/Sweden/Jonkoping/map_Bus_Map.jpg +linkoping_bus|58.416129,15.626714|2009-06-14|Linkoping Bus||http://transportmaps.free.fr/maps/Sweden/Linkoping/map_Bus.jpg +stockholm_train|59.329875,18.057218||Stockholm Train|Storstockholms Lokaltrafik|http://transportmaps.free.fr/maps/Sweden/Stockholm/map_All_Trains.jpg # no @@ -233,7 +222,7 @@ istanbul_rail|41.011939,28.984308|2015-09|Istanbul Rail Transit|Maximilian Dörr # uk -london_tube|51.513507,-0.110264|2023-05|London Tube|TfL +london_tube|51.513507,-0.110264|2016-06|London Tube|TfL london_overground|51.513507,-0.110264|2016-05|London Overground|TfL london_rail|51.513507,-0.110264|2016-05|London Rail & Tube Services|TfL london_bus|51.513507,-0.110264|2016|London Bus (geographical)|TfL @@ -241,7 +230,13 @@ london_night|51.513507,-0.110264|2016|London Night Bus|TfL london_tram|51.37937,-0.101759|2016-01|London Trams|TfL manchester_tram|53.479754,-2.24272|2019|Manchester Tram network|Transport for Greater Manchester|https://images.ctfassets.net/nv7y93idf4jq/ZQ9ZsaDtyEoUMiwISqm4c/97b5a9cf4c29b171aaa1b50424a2efba/Metrolink-Map-2019.jpg menchester_train|53.479754,-2.24272|2018-11|Manchester Train network|Transport for Greater Manchester|https://images.ctfassets.net/nv7y93idf4jq/6cNIqEFra0ke8YMqAkq6Cq/22e8549c97ff4fb852713be3699145e1/Combined_Rail_Metrolink_network_map_v17__free_bus_.jpg +bath_bus|51.377628,-2.357004||Bath Bus Network|FWT|http://transportmaps.free.fr/maps/United_Kingdom/Bath/map_Bus_Network.jpg +bath_bus_city_center|51.377628,-2.357004||Bath Bus City Zone 1|FWT|http://transportmaps.free.fr/maps/United_Kingdom/Bath/map_City_Center_Bus.jpg +leeds_bus|53.795649,-1.548003||Leeds Bus Network|FWT|http://transportmaps.free.fr/maps/United_Kingdom/Leeds/map_Bus.jpg newcastle_metro|54.972793,-1.604741||Newcastle Metro|Nexus +nottingham_bus_city_center|52.947042,-1.146276||Nottingham City Centre Bus/Tram|FWT|http://transportmaps.free.fr/maps/United_Kingdom/Nottingham/map_Bus_NCTX_Inner_City_Map.jpg +nottingham_bus_greater_area|52.947042,-1.146276||Nottingham Greater Area Bus||http://transportmaps.free.fr/maps/United_Kingdom/Nottingham/map_Greater_Nottingham_Buses.jpg +york|53.957911,-1.093061||York Bus||http://transportmaps.free.fr/maps/United_Kingdom/York/map_Bus.jpg westmidlands_rail|52.477785,-1.898184||West Midlands Rail Network| # it @@ -253,12 +248,17 @@ napoli_region|40.852780,14.271692||Napoli Region|Unico Campania # fr -paris_metro_ratp|48.855414,2.34488|2022-12|Paris Metro|RATP +paris_metro_ratp|48.855414,2.34488|2014-01|Paris Metro|RATP paris_metro|48.855414,2.34488|2012-12|Paris Metro|Nathan Kaufmann, Creative Commons # po +gdansk_tram|54.355338,18.644478|2008|Gdansk Tram|ZKM|http://transportmaps.free.fr/maps/Poland/Gdansk/map_Tram.jpg +krakow_tram|50.065365,19.947124|2010-03-01|Krakow Tram|KST|http://transportmaps.free.fr/maps/Poland/Krakow/map_Tram.jpg +poznan_tram|52.40144,16.91215||Poznan Tram|MPK|http://transportmaps.free.fr/maps/Poland/Poznan/map_Trams.jpg +poznan_night|52.40144,16.91215||Poznan Night Buses|MPK|http://transportmaps.free.fr/maps/Poland/Poznan/map_Night_Buses.jpg warsaw_rail|52.230515,21.010702|2011-12-01|Warsaw Rail Transport|ztm|https://www.ztm.waw.pl/mapa/duze/tramwaje.gif +warsaw_metro_tram|52.230515,21.010702||Warsaw Metro and Tram|zajcev|http://transportmaps.free.fr/maps/Poland/Warsaw/map_Subway_and_Tramway.jpg # cz @@ -289,7 +289,7 @@ losangeles_system|34.05427,-118.246715|2016-06|Los Angeles System|metro.net losangeles_downtown|34.05427,-118.246715|2016-06|Los Angeles Downtown|metro.net losangeles_metro|34.05427,-118.246715|2016-05|Los Angeles Metro|metro.net|https://media.metro.net/riding_metro/maps/images/rail_map.gif losangeles_metro_metrolink|34.05427,-118.246715|2016-11|Los Angeles Metro & Rail|metro.net|https://media.metro.net/riding_metro/maps/images/metro_regionalrail_map.gif -newyork_subway|40.738452,-73.991919|2023-02|New York Subway (geographical)|MTA +newyork_subway|40.738452,-73.991919|2010-12|New York Subway (geographical)|MTA philadelphia_regional|39.957386,-75.180488|2012-06|Philadelphia Regional Train & Rail Transit|SEPTA chicago_downtown|41.878674,-87.640333|2016-02|Chicago Downtown|RTA Chicago chicago_trains|41.878674,-87.640333|2015-03|Chicago Train Connections|RTA Chicago diff --git a/oeffi/build.gradle b/oeffi/build.gradle index 2356b7b..2fcc1d9 100644 --- a/oeffi/build.gradle +++ b/oeffi/build.gradle @@ -7,35 +7,31 @@ configurations { all*.exclude group: 'org.json', module: 'json' all*.exclude group: 'net.sf.kxml', module: 'kxml2' all*.exclude group: 'androidx.legacy', module: 'legacy-support-core-ui' - all*.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk7' - all*.exclude group: 'org.jetbrains.kotlin', module: 'kotlin-stdlib-jdk8' + all*.exclude group: 'androidx.lifecycle', module: 'lifecycle-runtime' } dependencies { - implementation(project(':public-transport-enabler')) { - exclude group: 'org.slf4j', module: 'slf4j-api' - } - implementation 'androidx.annotation:annotation:1.8.2' - implementation 'androidx.recyclerview:recyclerview:1.3.2' + implementation project(':public-transport-enabler') + implementation 'de.schildbach.wallet:integration-android:2.0' + implementation 'androidx.annotation:annotation:1.3.0' + implementation 'androidx.recyclerview:recyclerview:1.2.1' implementation 'androidx.coordinatorlayout:coordinatorlayout:1.2.0' - implementation 'androidx.drawerlayout:drawerlayout:1.2.0' - implementation 'androidx.core:core:1.13.1' - implementation 'androidx.activity:activity:1.9.3' + implementation 'androidx.drawerlayout:drawerlayout:1.1.1' + implementation 'androidx.core:core:1.6.0' //noinspection GradleDependency implementation 'com.squareup.okhttp3:okhttp:3.12.13' //noinspection GradleDependency implementation 'com.squareup.okhttp3:logging-interceptor:3.12.13' - implementation 'com.google.guava:guava:33.4.0-android' - implementation 'org.osmdroid:osmdroid-android:6.1.20' - //noinspection GradleDependency - implementation 'org.slf4j:slf4j-api:2.0.9' - implementation 'com.github.tony19:logback-android:3.0.0' + implementation 'com.google.guava:guava:31.0.1-android' + implementation 'org.osmdroid:osmdroid-android:6.1.11' + implementation 'org.slf4j:slf4j-api:1.7.36' + implementation 'com.github.tony19:logback-android:2.0.0' testImplementation 'junit:junit:4.13.2' } android { - compileSdkVersion 'android-34' - buildToolsVersion '35.0.0' + compileSdkVersion 'android-30' + buildToolsVersion '30.0.3' defaultConfig { generatedDensities = ['hdpi', 'xhdpi'] @@ -53,6 +49,18 @@ android { } } + flavorDimensions 'flavor' + productFlavors { + aosp { + dimension 'flavor' + versionNameSuffix '-aosp' + } + google { + dimension 'flavor' + versionNameSuffix '-google' + } + } + sourceSets { main { manifest.srcFile 'AndroidManifest.xml' @@ -63,6 +71,12 @@ android { test { java.srcDirs = ['test'] } + aosp { + java.srcDirs = ['src-aosp'] + } + google { + java.srcDirs = ['src-google'] + } } compileOptions { @@ -72,16 +86,15 @@ android { lintOptions { abortOnError false + disable 'MissingTranslation' } packagingOptions { exclude 'META-INF/*.version' exclude 'META-INF/proguard/**' - exclude 'META-INF/services/**' exclude 'META-INF/*.kotlin_module' - exclude 'META-INF/**/coroutines.pro' - exclude 'DebugProbesKt.bin' exclude 'kotlin/**' + exclude 'okhttp3/internal/publicsuffix/publicsuffixes.gz' } } diff --git a/oeffi/proguard.cfg b/oeffi/proguard.cfg index 8df31dd..9376e30 100644 --- a/oeffi/proguard.cfg +++ b/oeffi/proguard.cfg @@ -1,4 +1,4 @@ --android +-dontskipnonpubliclibraryclasses -dontoptimize -dontpreverify -dontobfuscate @@ -46,18 +46,9 @@ } # androidx --dontwarn kotlinx.coroutines.** --dontwarn module-info +-dontwarn androidx.core.** -dontnote androidx.core.** -dontnote androidx.versionedparcelable.VersionedParcel --dontnote kotlin.** --dontnote kotlinx.** --dontwarn androidx.lifecycle.SavedStateHandle --dontwarn androidx.activity.Api34Impl --dontwarn androidx.activity.BackEventCompat --dontnote androidx.activity.ImmLeaksCleaner$Companion$** --dontwarn androidx.core.view.accessibility.AccessibilityNodeInfoCompat$AccessibilityActionCompat --dontwarn androidx.**$Api34Impl,androidx.**$Api34Impl$** # OkHttp -dontwarn okio.DeflaterSink @@ -72,20 +63,22 @@ # Guava -dontwarn sun.misc.Unsafe --dontwarn javax.lang.model.element.Modifier --dontwarn com.google.common.reflect.Invokable,com.google.common.reflect.Invokable$** --dontwarn java.lang.reflect.AnnotatedType +-dontwarn java.lang.ClassValue +-dontwarn com.google.errorprone.annotations.** +-dontwarn afu.org.checkerframework.checker.**,org.checkerframework.checker.** -dontnote com.google.common.reflect.** -dontnote com.google.appengine.** -dontnote com.google.apphosting.** -dontnote com.google.common.cache.Striped64,com.google.common.cache.Striped64$Cell -dontnote com.google.common.hash.Striped64,com.google.common.hash.Striped64$Cell -dontnote com.google.common.util.concurrent.AbstractFuture$UnsafeAtomicHelper --dontnote com.google.common.io.TempFileCreator,com.google.common.io.TempFileCreator$** -dontnote dalvik.system.CloseGuard -# SLF4J, logback-android --keep class org.slf4j.impl.LoggerServiceProvider +# slf4j +-dontwarn org.slf4j.MDC +-dontwarn org.slf4j.MarkerFactory + +# logback-android -dontwarn javax.mail.** -dontnote ch.qos.logback.core.android.AndroidContextUtil diff --git a/oeffi/res/drawable-anydpi/ic_stat_notify_sync_24dp.xml b/oeffi/res/drawable-anydpi/ic_stat_notify_sync_24dp.xml new file mode 100644 index 0000000..9e6222a --- /dev/null +++ b/oeffi/res/drawable-anydpi/ic_stat_notify_sync_24dp.xml @@ -0,0 +1,11 @@ + + + + diff --git a/oeffi/res/drawable-anydpi/network_vms_logo.xml b/oeffi/res/drawable-anydpi/network_vms_logo.xml new file mode 100644 index 0000000..6f4e46d --- /dev/null +++ b/oeffi/res/drawable-anydpi/network_vms_logo.xml @@ -0,0 +1,25 @@ + + + + + + + + + + diff --git a/oeffi/res/drawable-anydpi/network_vvo_logo.xml b/oeffi/res/drawable-anydpi/network_vvo_logo.xml new file mode 100644 index 0000000..b850b7d --- /dev/null +++ b/oeffi/res/drawable-anydpi/network_vvo_logo.xml @@ -0,0 +1,65 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/oeffi/res/drawable-xhdpi/network_nri_icon.png b/oeffi/res/drawable-xhdpi/network_nri_icon.png new file mode 100644 index 0000000..0b5115b Binary files /dev/null and b/oeffi/res/drawable-xhdpi/network_nri_icon.png differ diff --git a/oeffi/res/drawable-xhdpi/network_pl_icon.png b/oeffi/res/drawable-xhdpi/network_pl_icon.png new file mode 100644 index 0000000..58f18d4 Binary files /dev/null and b/oeffi/res/drawable-xhdpi/network_pl_icon.png differ diff --git a/oeffi/res/drawable-xhdpi/network_sncb_icon.png b/oeffi/res/drawable-xhdpi/network_sncb_icon.png new file mode 100644 index 0000000..67edda3 Binary files /dev/null and b/oeffi/res/drawable-xhdpi/network_sncb_icon.png differ diff --git a/oeffi/res/drawable-xhdpi/network_tfi_icon.png b/oeffi/res/drawable-xhdpi/network_tfi_icon.png new file mode 100644 index 0000000..fac162c Binary files /dev/null and b/oeffi/res/drawable-xhdpi/network_tfi_icon.png differ diff --git a/oeffi/res/layout/plans_content.xml b/oeffi/res/layout/plans_content.xml index 58d2e48..cf96dae 100644 --- a/oeffi/res/layout/plans_content.xml +++ b/oeffi/res/layout/plans_content.xml @@ -4,7 +4,6 @@ android:id="@+id/plans_layout" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/bg_plan" android:inAnimation="@android:anim/fade_in" android:outAnimation="@android:anim/fade_out"> @@ -25,7 +24,7 @@ android:layout_height="wrap_content" android:paddingTop="8dp" android:text="@string/plan_progress_loading" - android:textColor="@color/fg_significant_on_light" + android:textColor="@color/fg_significant_on_dark" android:textStyle="bold" /> diff --git a/oeffi/res/menu/station_map_context.xml b/oeffi/res/menu/station_map_context.xml index f3458ad..2926ee0 100644 --- a/oeffi/res/menu/station_map_context.xml +++ b/oeffi/res/menu/station_map_context.xml @@ -3,8 +3,24 @@ xmlns:android="http://schemas.android.com/apk/res/android"> + android:title="@string/map_dialog_google_maps" /> + + + + diff --git a/oeffi/res/values-de/directions.xml b/oeffi/res/values-de/directions.xml index 7260277..f5dfe7c 100644 --- a/oeffi/res/values-de/directions.xml +++ b/oeffi/res/values-de/directions.xml @@ -4,6 +4,7 @@ Mein aktueller Standort Adresse aus Kontakt Haltestellen-Favorit + Berechtigungs-Problem:\n%1$s Verkehrsmittel auswählen diff --git a/oeffi/res/values-de/networks.xml b/oeffi/res/values-de/networks.xml index af68f7f..5ec93de 100644 --- a/oeffi/res/values-de/networks.xml +++ b/oeffi/res/values-de/networks.xml @@ -19,8 +19,8 @@ Lokal- und Regionalverkehr Ingolstadt Lokal- und Regionalverkehr - Augsburg - nur Tram und Bus + Augsburg + nur Tram und Bus Verkehrsverbund Großraum Nürnberg Nürnberg, Fürth & Erlangen, Lokal- und Regionalverkehr Mittelschwaben (Krumbach, Günzburg, …) @@ -39,8 +39,10 @@ Lokal- und Regionalverkehr Erfurt, Jena & Mittelthüringen Lokal- und Regionalverkehr - Sachsen - Dresden & Chemnitz, Lokal- und Regionalverkehr + Verkehrsverbund Oberelbe + Dresden, Lokal- und Regionalverkehr + Verkehrsverbund Mittelsachsen + Chemnitz & Mittelsachsen, Lokal- und Regionalverkehr Verkehrsverbund Rhein-Ruhr Nordrhein-Westfalen, Düsseldorf & Dortmund, Lokal- und Regionalverkehr Köln & Bonn @@ -81,10 +83,20 @@ Lokal- und Regionalverkehr Bregenz & Vorarlberg Nicht verfügbar, weil der VVV ausdrücklich darum gebeten hat, nicht in Öffi enthalten zu sein. Du kannst stattdessen die ÖBB verwenden, das funktioniert für Vorarlberg auch gut. + Prag + Lokal- und Regionalverkehr Luzern nur Tram und Bus Zürich Lokal- und Regionalverkehr + Italien + Fern-, Regional und Lokalverkehr (z.B. Mailand, Rom, Neapel) + Paris + Lokal- und Regionalverkehr + Spanien + Lokal- und Regionalverkehr (z.B. Barcelona) + Belgien + Fern-, Regional und Lokalverkehr (z.B. Antwerpen, Gent, Charleroi, Brüssel) Luxemburg Lokal- und Regionalverkehr Niederlande @@ -93,10 +105,14 @@ Fern-, Regional und Lokalverkehr (z.B. Kopenhagen) Schweden Fern-, Regional und Lokalverkehr (z.B. Stockholm) + Finnland + Fern-, Regional und Lokalverkehr (z.B. Helsinki) England, Schottland & Wales. Lokal- und Regionalverkehr (z.B. London, Birmingham) Liverpool Lokal- und Regionalverkehr + Irland & Nordirland + Lokal- und Regionalverkehr Polen Fern-, Regional und Lokalverkehr (z.B. Warschau) Dubai @@ -109,6 +125,8 @@ Lokal- und Regionalverkehr Sydney Lokal- und Regionalverkehr + Nicaragua + nationale und städtische Buslinien Melbourne Nicht mehr verfügbar, da der PTV keine EFA-API mehr anbietet. diff --git a/oeffi/res/values-de/select_map.xml b/oeffi/res/values-de/select_map.xml index 4ede69e..a352113 100644 --- a/oeffi/res/values-de/select_map.xml +++ b/oeffi/res/values-de/select_map.xml @@ -1,6 +1,10 @@ - externe Karte + Google Maps + Amazon Maps + OpenStreetMap + Street View + Navigation für Fußgänger diff --git a/oeffi/res/values-de/strings.xml b/oeffi/res/values-de/strings.xml index 35a5423..f89554e 100644 --- a/oeffi/res/values-de/strings.xml +++ b/oeffi/res/values-de/strings.xml @@ -134,6 +134,7 @@ Keine Haltestellen gefunden + Abfahrtszeiten an dieser Haltestelle Favorit Lade Abfahrtszeiten… Keine Abfahrten @@ -329,6 +330,7 @@ Senden Herunterladen Schließen + App-Widget-Aktivität Fernverkehr @@ -354,7 +356,6 @@ %1$dh %2$02d min - Gleis %s - Steig %s + Gleis %s diff --git a/oeffi/res/values-night-v29/colors.xml b/oeffi/res/values-night-v29/colors.xml new file mode 100644 index 0000000..1734f89 --- /dev/null +++ b/oeffi/res/values-night-v29/colors.xml @@ -0,0 +1,9 @@ + + + + + @android:color/transparent + @android:color/transparent + @android:color/transparent + + diff --git a/oeffi/res/values-night/colors.xml b/oeffi/res/values-night/colors.xml index 179b8b9..a0653fd 100644 --- a/oeffi/res/values-night/colors.xml +++ b/oeffi/res/values-night/colors.xml @@ -23,6 +23,7 @@ #444444 #ffff00 #bbbbbb + #44000000 0.8 diff --git a/oeffi/res/values-night/styles.xml b/oeffi/res/values-night/styles.xml index f80397d..4ef9f8d 100644 --- a/oeffi/res/values-night/styles.xml +++ b/oeffi/res/values-night/styles.xml @@ -5,6 +5,8 @@ diff --git a/oeffi/res/values-notnight-v29/styes.xml b/oeffi/res/values-notnight-v29/styes.xml index 92f8177..6cf7297 100644 --- a/oeffi/res/values-notnight-v29/styes.xml +++ b/oeffi/res/values-notnight-v29/styes.xml @@ -4,6 +4,8 @@ diff --git a/oeffi/res/values-v29/colors.xml b/oeffi/res/values-v29/colors.xml new file mode 100644 index 0000000..1734f89 --- /dev/null +++ b/oeffi/res/values-v29/colors.xml @@ -0,0 +1,9 @@ + + + + + @android:color/transparent + @android:color/transparent + @android:color/transparent + + diff --git a/oeffi/res/values-w486dp/dimens.xml b/oeffi/res/values-w420dp/dimens.xml similarity index 100% rename from oeffi/res/values-w486dp/dimens.xml rename to oeffi/res/values-w420dp/dimens.xml diff --git a/oeffi/res/values/colors.xml b/oeffi/res/values/colors.xml index 5f9c7e5..dd27815 100644 --- a/oeffi/res/values/colors.xml +++ b/oeffi/res/values/colors.xml @@ -37,7 +37,9 @@ #757575 #bbbbbb #ff4444 - #ffd8d8d8 + #44000000 + @android:color/black + #44000000 1 diff --git a/oeffi/res/values/directions.xml b/oeffi/res/values/directions.xml index b2155d2..f61237c 100644 --- a/oeffi/res/values/directions.xml +++ b/oeffi/res/values/directions.xml @@ -5,6 +5,7 @@ My current location Contact address Favorite station + Permission problem:\n%1$s Pick means of transport diff --git a/oeffi/res/values/networks.xml b/oeffi/res/values/networks.xml index 936d8db..8a2eca1 100644 --- a/oeffi/res/values/networks.xml +++ b/oeffi/res/values/networks.xml @@ -18,8 +18,8 @@ local and regional Ingolstadt local and regional - Augsburg - tram and bus only + Augsburg + tram and bus only Verkehrsverbund Großraum Nürnberg Nuremberg, Fürth & Erlangen, local and regional Mittelschwaben (Krumbach, Günzburg, …) @@ -38,8 +38,10 @@ local and regional Erfurt, Jena & Central Thuringia local and regional - Saxony - Dresden & Chemnitz, local and regional + Verkehrsverbund Oberelbe + Dresden, local and regional + Verkehrsverbund Mittelsachsen + Chemnitz & Mittelsachsen, local and regional Verkehrsverbund Rhein-Ruhr North Rhine-Westphalia, Düsseldorf & Dortmund, local and regional Cologne & Bonn @@ -81,10 +83,20 @@ local and regional Bregenz & Vorarlberg Not available, because the VVV explicitly desires not to be included in Offi. You can use the ÖBB instead, it works well for Vorarlberg too. + Prague + local and regional Lucerne tram and bus only Zurich local and regional + Italy + long-distance, regional and local (e.g. Milan, Rome, Naples) + Paris + local and regional + Spain + local and regional (e.g. Barcelona) + Belgium + long-distance, regional and local (e.g. Antwerp, Ghent, Charleroi, Brussels) Luxembourg local and regional Netherlands @@ -93,10 +105,14 @@ long-distance, regional and local (e.g. Copenhagen) Sweden long-distance, regional and local (e.g. Stockholm) + Finland + long-distance, regional and local (e.g. Helsinki) England, Scotland & Wales local and regional (e.g. London, Birmingham) Liverpool local and regional + Ireland & Northern Ireland + local and regional Poland long-distance, regional and local (e.g. Warsaw) Dubai @@ -109,6 +125,8 @@ local and regional Sydney local and regional + Nicaragua + national and urban buses Melbourne Not available, because the PTV does not offer an EFA API any more. diff --git a/oeffi/res/values/select_map.xml b/oeffi/res/values/select_map.xml index 663812e..9068e4e 100644 --- a/oeffi/res/values/select_map.xml +++ b/oeffi/res/values/select_map.xml @@ -1,6 +1,10 @@ - External map + Google Maps + Amazon Maps + OpenStreetMap + Street View + Walking Directions diff --git a/oeffi/res/values/strings.xml b/oeffi/res/values/strings.xml index 6720387..96d47c0 100644 --- a/oeffi/res/values/strings.xml +++ b/oeffi/res/values/strings.xml @@ -46,7 +46,7 @@ About Offi Copyright - © 2010-2025, the Öffi developers + © 2010-2021, the Öffi developers License Privacy Policy https://oeffi.schildbach.de/privacy_policy.txt @@ -60,7 +60,6 @@ Donations Donate Bitcoins Bitcoin is an Internet currency. - bitcoin:bc1q8ruc8hanp7hrzfs48dvtuzz4ukmpe7cgsvvzrt Donate Euros https://oeffi.schildbach.de/donate.html @@ -136,6 +135,7 @@ No stations found + Departure times of this station Favorite Loading departures… No departures @@ -331,6 +331,7 @@ Send Download Dismiss + App-widget activity Highspeed @@ -356,7 +357,6 @@ %1$dh %2$02d min - platform %s platform %s diff --git a/oeffi/res/values/styles.xml b/oeffi/res/values/styles.xml index 8a913e7..727a1f6 100644 --- a/oeffi/res/values/styles.xml +++ b/oeffi/res/values/styles.xml @@ -5,6 +5,8 @@ @@ -23,10 +27,13 @@ diff --git a/oeffi/res/xml/locale_config.xml b/oeffi/res/xml/locale_config.xml deleted file mode 100644 index cb544e7..0000000 --- a/oeffi/res/xml/locale_config.xml +++ /dev/null @@ -1,5 +0,0 @@ - - - - - diff --git a/oeffi/res/xml/nearest_favorite_station_widget.xml b/oeffi/res/xml/nearest_favorite_station_widget.xml index 98b8c3c..157e072 100644 --- a/oeffi/res/xml/nearest_favorite_station_widget.xml +++ b/oeffi/res/xml/nearest_favorite_station_widget.xml @@ -6,4 +6,5 @@ android:minResizeHeight="55dp" android:minWidth="294dp" android:previewImage="@drawable/nearest_favorite_station_widget_preview" - android:resizeMode="horizontal|vertical" /> + android:resizeMode="horizontal|vertical" + android:updatePeriodMillis="1800000" /> diff --git a/oeffi/res/xml/network_security_config.xml b/oeffi/res/xml/network_security_config.xml index 0bd486a..7f0db28 100644 --- a/oeffi/res/xml/network_security_config.xml +++ b/oeffi/res/xml/network_security_config.xml @@ -10,11 +10,13 @@ + www.belgianrail.be android.vrsinfo.de wojhati.rta.ae appefa10.verbundlinie.at railteam.hafas.eu mobil.vbl.ch + efa.vvo-online.de mobil.rozklad-pkp.pl diff --git a/oeffi/res/xml/preference_donate.xml b/oeffi/res/xml/preference_donate.xml index dc906d1..78f34fb 100644 --- a/oeffi/res/xml/preference_donate.xml +++ b/oeffi/res/xml/preference_donate.xml @@ -5,11 +5,7 @@ - - + android:title="@string/about_donate_bitcoin_title" /> . + */ + +package de.schildbach.oeffi; + +public class Variants { + public static final boolean ENABLE_DONATE = true; +} diff --git a/oeffi/src-google/de/schildbach/oeffi/Variants.java b/oeffi/src-google/de/schildbach/oeffi/Variants.java new file mode 100644 index 0000000..3fe9f90 --- /dev/null +++ b/oeffi/src-google/de/schildbach/oeffi/Variants.java @@ -0,0 +1,22 @@ +/* + * Copyright the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.oeffi; + +public class Variants { + public static final boolean ENABLE_DONATE = false; +} diff --git a/oeffi/src/de/schildbach/oeffi/Application.java b/oeffi/src/de/schildbach/oeffi/Application.java index 1b00c67..a69fcab 100644 --- a/oeffi/src/de/schildbach/oeffi/Application.java +++ b/oeffi/src/de/schildbach/oeffi/Application.java @@ -17,9 +17,13 @@ package de.schildbach.oeffi; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.content.Context; import android.content.SharedPreferences; import android.content.pm.PackageInfo; import android.content.pm.PackageManager.NameNotFoundException; +import android.os.Build; import android.preference.PreferenceManager; import ch.qos.logback.classic.Level; import ch.qos.logback.classic.LoggerContext; @@ -31,6 +35,7 @@ import ch.qos.logback.core.rolling.TimeBasedRollingPolicy; import com.google.common.base.Stopwatch; import de.schildbach.oeffi.directions.QueryHistoryProvider; import de.schildbach.oeffi.stations.FavoriteStationsProvider; +import de.schildbach.oeffi.stations.NearestFavoriteStationWidgetService; import de.schildbach.oeffi.util.ErrorReporter; import de.schildbach.pte.NetworkId; import okhttp3.OkHttpClient; @@ -86,76 +91,38 @@ public class Application extends android.app.Application { final Stopwatch watch = Stopwatch.createStarted(); + // 2018-07-06: migrate IVB to use OEBB + final String IVB = "IVB"; + migrateSelectedNetwork(IVB, NetworkId.OEBB); + FavoriteStationsProvider.deleteFavoriteStations(this, IVB); + QueryHistoryProvider.deleteQueryHistory(this, IVB); + + // 2018-11-05: migrate NRI to use RT + final String NRI = "NRI"; + migrateSelectedNetwork(NRI, NetworkId.RT); + FavoriteStationsProvider.deleteFavoriteStations(this, NRI); + QueryHistoryProvider.deleteQueryHistory(this, NRI); + + // 2018-12-06: migrate VAGFR to use NVBW + final String VAGFR = "VAGFR"; + migrateSelectedNetwork(VAGFR, NetworkId.NVBW); + FavoriteStationsProvider.migrateFavoriteStations(this, VAGFR, NetworkId.NVBW); + QueryHistoryProvider.migrateQueryHistory(this, VAGFR, NetworkId.NVBW); + // 2020-11-22: delete unused downloaded station databases final FilenameFilter filter = (dir, name) -> name.endsWith(".db") || name.endsWith(".db.meta"); for (final File file : getFilesDir().listFiles(filter)) file.delete(); - // 2023-01-09: migrate VMS to use VVO - final String VMS = "VMS"; - migrateSelectedNetwork(VMS, NetworkId.VVO); - FavoriteStationsProvider.migrateFavoriteStations(this, VMS, NetworkId.VVO); - QueryHistoryProvider.migrateQueryHistory(this, VMS, NetworkId.VVO); - - // 2023-11-05: migrate TFI to use RT - final String TFI = "TFI"; - migrateSelectedNetwork(TFI, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, TFI); - QueryHistoryProvider.deleteQueryHistory(this, TFI); - - // 2023-11-16: migrate AVV to use AVV_AUGSBURG - final String AVV = "AVV"; - migrateSelectedNetwork(AVV, NetworkId.AVV_AUGSBURG); - FavoriteStationsProvider.deleteFavoriteStations(this, AVV); - QueryHistoryProvider.deleteQueryHistory(this, AVV); - - // 2023-12-17: migrate SNCB to use RT - final String SNCB = "SNCB"; - migrateSelectedNetwork(SNCB, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, SNCB); - QueryHistoryProvider.deleteQueryHistory(this, SNCB); - - // 2024-04-27: EFA-ID migration of MVV - FavoriteStationsProvider.migrateFavoriteStationIds(this, NetworkId.MVV, "0", "10000", 91000000); - QueryHistoryProvider.migrateQueryHistoryIds(this, NetworkId.MVV, "0", "10000", 91000000); - - // 2024-08-09: migrate Finland to use RT - final String FINLAND = "FINLAND"; - migrateSelectedNetwork(FINLAND, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, FINLAND); - QueryHistoryProvider.deleteQueryHistory(this, FINLAND); - - // 2024-08-30: migrate Czech Republic to use RT - final String CZECH_REPUBLIC = "CZECH_REPUBLIC"; - migrateSelectedNetwork(CZECH_REPUBLIC, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, CZECH_REPUBLIC); - QueryHistoryProvider.deleteQueryHistory(this, CZECH_REPUBLIC); - - // 2024-08-30: migrate Italy to use RT - final String IT = "IT"; - migrateSelectedNetwork(IT, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, IT); - QueryHistoryProvider.deleteQueryHistory(this, IT); - - // 2024-08-30: migrate Paris to use RT - final String PARIS = "PARIS"; - migrateSelectedNetwork(PARIS, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, PARIS); - QueryHistoryProvider.deleteQueryHistory(this, PARIS); - - // 2024-08-30: migrate Spain to use RT - final String SPAIN = "SPAIN"; - migrateSelectedNetwork(SPAIN, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, SPAIN); - QueryHistoryProvider.deleteQueryHistory(this, SPAIN); - - // 2024-08-30: migrate Nicaragua to use RT - final String NICARAGUA = "NICARAGUA"; - migrateSelectedNetwork(NICARAGUA, NetworkId.RT); - FavoriteStationsProvider.deleteFavoriteStations(this, NICARAGUA); - QueryHistoryProvider.deleteQueryHistory(this, NICARAGUA); + // 2021-09-18: migrate SBB to use RT + final String SBB = "SBB"; + migrateSelectedNetwork(SBB, NetworkId.RT); + FavoriteStationsProvider.deleteFavoriteStations(this, SBB); + QueryHistoryProvider.deleteQueryHistory(this, SBB); log.info("Migrations took {}", watch); + + initNotificationManager(); } private void initLogging() { @@ -211,6 +178,20 @@ public class Application extends android.app.Application { config.setUserAgentValue(getPackageName()); } + private void initNotificationManager() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final Stopwatch watch = Stopwatch.createStarted(); + final NotificationManager nm = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); + + final NotificationChannel appwidget = new NotificationChannel( + NearestFavoriteStationWidgetService.NOTIFICATION_CHANNEL_ID_APPWIDGET, + getString(R.string.notification_channel_appwidget_name), NotificationManager.IMPORTANCE_LOW); + nm.createNotificationChannel(appwidget); + + log.info("created notification channels, took {}", watch); + } + } + private void migrateSelectedNetwork(final String fromName, final NetworkId to) { final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -233,4 +214,13 @@ public class Application extends android.app.Application { public static final int versionCode(final Application application) { return application.packageInfo().versionCode; } + + public static final String versionFlavor(final Application application) { + final String applicationVersion = versionName(application); + final int applicationVersionSplit = applicationVersion.indexOf('-'); + if (applicationVersionSplit >= 0) + return applicationVersion.substring(applicationVersionSplit + 1); + else + return null; + } } diff --git a/oeffi/src/de/schildbach/oeffi/Constants.java b/oeffi/src/de/schildbach/oeffi/Constants.java index 3394e43..94cf1f7 100644 --- a/oeffi/src/de/schildbach/oeffi/Constants.java +++ b/oeffi/src/de/schildbach/oeffi/Constants.java @@ -17,9 +17,7 @@ package de.schildbach.oeffi; -import android.graphics.Color; import android.text.format.DateUtils; -import androidx.activity.SystemBarStyle; import okhttp3.HttpUrl; import java.util.Locale; @@ -32,6 +30,8 @@ public class Constants { public static final String PLAN_INDEX_FILENAME = "plans-index.txt"; public static final String PLAN_STATIONS_FILENAME = "plans-stations.txt"; + public static final String BITCOIN_ADDRESS = "bc1q8ruc8hanp7hrzfs48dvtuzz4ukmpe7cgsvvzrt"; + public static final String REPORT_EMAIL = "oeffi.app@gmail.com"; public static final long LOCATION_UPDATE_FREQ_MS = 10 * DateUtils.SECOND_IN_MILLIS; @@ -67,6 +67,4 @@ public class Constants { public static final String DESTINATION_ARROW_PREFIX = Character.toString(Constants.CHAR_RIGHTWARDS_ARROW) + Constants.CHAR_THIN_SPACE; public static final String DESTINATION_ARROW_INVISIBLE_PREFIX = " "; - - public static final SystemBarStyle STATUS_BAR_STYLE = SystemBarStyle.dark(Color.TRANSPARENT); } diff --git a/oeffi/src/de/schildbach/oeffi/MyActionBar.java b/oeffi/src/de/schildbach/oeffi/MyActionBar.java index 5d1ef79..bf2600e 100644 --- a/oeffi/src/de/schildbach/oeffi/MyActionBar.java +++ b/oeffi/src/de/schildbach/oeffi/MyActionBar.java @@ -19,7 +19,6 @@ package de.schildbach.oeffi; import android.content.Context; import android.content.res.Resources; -import android.os.Build; import android.os.Handler; import android.util.AttributeSet; import android.view.Gravity; @@ -34,6 +33,7 @@ import android.widget.ImageView.ScaleType; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.TextView; +import de.schildbach.oeffi.util.CheatSheet; import de.schildbach.oeffi.util.ToggleImageButton; public class MyActionBar extends LinearLayout { @@ -147,8 +147,7 @@ public class MyActionBar extends LinearLayout { if (descriptionRes != 0) { final String description = context.getString(descriptionRes); button.setContentDescription(description); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - button.setTooltipText(description); + CheatSheet.setup(button, description); } addView(button, BUTTON_INSERT_INDEX, buttonParams); @@ -167,8 +166,7 @@ public class MyActionBar extends LinearLayout { if (descriptionRes != 0) { final String description = context.getString(descriptionRes); button.setContentDescription(description); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - button.setTooltipText(description); + CheatSheet.setup(button, description); } addView(button, BUTTON_INSERT_INDEX, buttonParams); @@ -178,8 +176,7 @@ public class MyActionBar extends LinearLayout { public View addProgressButton() { progressAlwaysVisible = true; progressView.setVisibility(View.VISIBLE); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - progressButton.setTooltipText(progressButton.getContentDescription()); + CheatSheet.setup(progressButton); return getProgressButton(); } diff --git a/oeffi/src/de/schildbach/oeffi/OeffiActivity.java b/oeffi/src/de/schildbach/oeffi/OeffiActivity.java index 5b66bc6..ede9d8e 100644 --- a/oeffi/src/de/schildbach/oeffi/OeffiActivity.java +++ b/oeffi/src/de/schildbach/oeffi/OeffiActivity.java @@ -17,11 +17,13 @@ package de.schildbach.oeffi; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.ActivityManager.TaskDescription; import android.content.SharedPreferences; import android.content.res.Resources; -import android.graphics.Color; import android.graphics.drawable.Drawable; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceManager; import android.text.format.DateUtils; @@ -29,8 +31,6 @@ import android.view.View; import android.view.ViewGroup; import android.widget.LinearLayout; import android.widget.TextView; -import androidx.activity.ComponentActivity; -import androidx.activity.EdgeToEdge; import de.schildbach.oeffi.network.NetworkResources; import de.schildbach.oeffi.util.ErrorReporter; import de.schildbach.pte.NetworkId; @@ -38,7 +38,7 @@ import de.schildbach.pte.dto.ResultHeader; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -public abstract class OeffiActivity extends ComponentActivity { +public abstract class OeffiActivity extends Activity { protected Application application; protected SharedPreferences prefs; @@ -46,12 +46,12 @@ public abstract class OeffiActivity extends ComponentActivity { @Override protected void onCreate(final Bundle savedInstanceState) { - EdgeToEdge.enable(this, Constants.STATUS_BAR_STYLE); super.onCreate(savedInstanceState); this.application = (Application) getApplication(); this.prefs = PreferenceManager.getDefaultSharedPreferences(this); - ErrorReporter.getInstance().check(this, applicationVersionCode(), application.okHttpClient()); + ErrorReporter.getInstance().check(this, applicationVersionCode(), applicationVersionFlavor(), + application.okHttpClient()); } protected void updateFragments(final int listFrameResId, final int mapFrameResId) { @@ -100,6 +100,10 @@ public abstract class OeffiActivity extends ComponentActivity { return Application.versionCode(application); } + protected final String applicationVersionFlavor() { + return Application.versionFlavor(application); + } + protected final long applicationFirstInstallTime() { return application.packageInfo().firstInstallTime; } @@ -153,4 +157,10 @@ public abstract class OeffiActivity extends ComponentActivity { return str; } + + @TargetApi(24) + @Override + public boolean isInMultiWindowMode() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && super.isInMultiWindowMode(); + } } diff --git a/oeffi/src/de/schildbach/oeffi/OeffiMainActivity.java b/oeffi/src/de/schildbach/oeffi/OeffiMainActivity.java index 1d3bba0..37cd20d 100644 --- a/oeffi/src/de/schildbach/oeffi/OeffiMainActivity.java +++ b/oeffi/src/de/schildbach/oeffi/OeffiMainActivity.java @@ -34,10 +34,9 @@ import android.text.format.DateUtils; import android.view.Gravity; import android.view.KeyEvent; import android.view.Menu; -import android.view.MenuInflater; import android.view.MenuItem; import android.view.View; -import androidx.core.view.MenuProvider; +import androidx.annotation.Nullable; import androidx.drawerlayout.widget.DrawerLayout; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -71,7 +70,6 @@ import okhttp3.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; @@ -92,7 +90,6 @@ public abstract class OeffiMainActivity extends OeffiActivity { private DrawerLayout navigationDrawerLayout; private RecyclerView navigationDrawerListView; - private MenuProvider navigationDrawerMenuProvider; private View navigationDrawerFooterView; private View navigationDrawerFooterHeartView; @@ -116,86 +113,6 @@ public abstract class OeffiMainActivity extends OeffiActivity { versionCode = applicationVersionCode(); lastVersionCode = prefs.getInt(Constants.PREFS_KEY_LAST_VERSION, 0); - navigationDrawerMenuProvider = new MenuProvider() { - @Override - public void onCreateMenu(final Menu menu, final MenuInflater inflater) { - inflater.inflate(R.menu.global_options, menu); - } - - @Override - public void onPrepareMenu(final Menu menu) { - final MenuItem stationsItem = menu.findItem(R.id.global_options_stations); - stationsItem.setChecked(OeffiMainActivity.this instanceof StationsActivity); - final MenuItem directionsItem = menu.findItem(R.id.global_options_directions); - directionsItem.setChecked(OeffiMainActivity.this instanceof DirectionsActivity); - final MenuItem plansItem = menu.findItem(R.id.global_options_plans); - plansItem.setChecked(OeffiMainActivity.this instanceof PlansPickerActivity); - } - - @Override - public boolean onMenuItemSelected(final MenuItem item) { - switch (item.getItemId()) { - case R.id.global_options_stations: { - if (OeffiMainActivity.this instanceof StationsActivity) - return true; - final Intent intent = new Intent(OeffiMainActivity.this, StationsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right); - return true; - } - - case R.id.global_options_directions: { - if (OeffiMainActivity.this instanceof DirectionsActivity) - return true; - final Intent intent = new Intent(OeffiMainActivity.this, DirectionsActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - if (OeffiMainActivity.this instanceof StationsActivity) - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - else - overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right); - return true; - } - - case R.id.global_options_plans: { - if (OeffiMainActivity.this instanceof PlansPickerActivity) - return true; - final Intent intent = new Intent(OeffiMainActivity.this, PlansPickerActivity.class); - intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); - startActivity(intent); - finish(); - overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); - return true; - } - - case R.id.global_options_donate: { - PreferenceActivity.start(OeffiMainActivity.this, DonateFragment.class.getName()); - return true; - } - - case R.id.global_options_report_bug: { - ErrorReporter.sendBugMail(OeffiMainActivity.this, application.packageInfo()); - return true; - } - - case R.id.global_options_preferences: { - PreferenceActivity.start(OeffiMainActivity.this); - return true; - } - - case R.id.global_options_about: { - PreferenceActivity.start(OeffiMainActivity.this, AboutFragment.class.getName()); - return true; - } - } - - return false; - } - }; - if (prefsGetNetwork() == null) { NetworkPickerActivity.start(this); @@ -260,13 +177,13 @@ public abstract class OeffiMainActivity extends OeffiActivity { final NavigationMenuAdapter menuAdapter = new NavigationMenuAdapter(this, item -> { - navigationDrawerMenuProvider.onMenuItemSelected(item); + onOptionsItemSelected(item); navigationDrawerLayout.closeDrawers(); return false; }); final Menu menu = menuAdapter.getMenu(); - navigationDrawerMenuProvider.onCreateMenu(menu, getMenuInflater()); - navigationDrawerMenuProvider.onPrepareMenu(menu); + onCreateOptionsMenu(menu); + onPrepareOptionsMenu(menu); navigationDrawerListView.setLayoutManager(new LinearLayoutManager(this)); navigationDrawerListView @@ -319,6 +236,99 @@ public abstract class OeffiMainActivity extends OeffiActivity { navigationDrawerLayout.closeDrawer(Gravity.LEFT); } + @Override + public boolean onCreateOptionsMenu(final Menu menu) { + getMenuInflater().inflate(R.menu.global_options, menu); + return true; + } + + @Override + public boolean onPrepareOptionsMenu(final Menu menu) { + final MenuItem stationsItem = menu.findItem(R.id.global_options_stations); + stationsItem.setChecked(this instanceof StationsActivity); + + final MenuItem directionsItem = menu.findItem(R.id.global_options_directions); + directionsItem.setChecked(this instanceof DirectionsActivity); + + final MenuItem plansItem = menu.findItem(R.id.global_options_plans); + plansItem.setChecked(this instanceof PlansPickerActivity); + + final MenuItem donateItem = menu.findItem(R.id.global_options_donate); + donateItem.setVisible(Variants.ENABLE_DONATE); + + return super.onPrepareOptionsMenu(menu); + } + + @Override + public boolean onOptionsItemSelected(final MenuItem item) { + switch (item.getItemId()) { + case R.id.global_options_stations: { + if (this instanceof StationsActivity) + return true; + + final Intent intent = new Intent(this, StationsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right); + + return true; + } + + case R.id.global_options_directions: { + if (this instanceof DirectionsActivity) + return true; + + final Intent intent = new Intent(this, DirectionsActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + if (this instanceof StationsActivity) + overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); + else + overridePendingTransition(R.anim.enter_from_left, R.anim.exit_to_right); + + return true; + } + + case R.id.global_options_plans: { + if (this instanceof PlansPickerActivity) + return true; + + final Intent intent = new Intent(this, PlansPickerActivity.class); + intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + finish(); + overridePendingTransition(R.anim.enter_from_right, R.anim.exit_to_left); + + return true; + } + + case R.id.global_options_donate: { + if (Variants.ENABLE_DONATE) + PreferenceActivity.start(this, DonateFragment.class.getName()); + return true; + } + + case R.id.global_options_report_bug: { + ErrorReporter.sendBugMail(this, application.packageInfo()); + return true; + } + + case R.id.global_options_preferences: { + PreferenceActivity.start(this); + return true; + } + + case R.id.global_options_about: { + PreferenceActivity.start(this, AboutFragment.class.getName()); + return true; + } + } + + return false; + } + @Override public boolean onKeyDown(final int keyCode, final KeyEvent event) { if (keyCode == KeyEvent.KEYCODE_MENU) { @@ -341,11 +351,18 @@ public abstract class OeffiMainActivity extends OeffiActivity { private void downloadAndProcessMessages(final String network) { final HttpUrl.Builder remoteUrl = Constants.MESSAGES_BASE_URL.newBuilder(); - remoteUrl.addPathSegment("messages.txt"); + final StringBuilder remoteFileName = new StringBuilder("messages"); + final String flavor = applicationVersionFlavor(); + if (flavor != null) + remoteFileName.append('-').append(flavor); + remoteFileName.append(".txt"); + remoteUrl.addPathSegment(remoteFileName.toString()); final String installerPackageName = Installer.installerPackageName(this); if (installerPackageName != null) remoteUrl.addEncodedQueryParameter("installer", installerPackageName); remoteUrl.addQueryParameter("version", Integer.toString(versionCode)); + if (flavor != null) + remoteUrl.addQueryParameter("flavor", flavor); remoteUrl.addQueryParameter("sdk", Integer.toString(Build.VERSION.SDK_INT)); remoteUrl.addQueryParameter("task", taskName()); final File localFile = new File(getFilesDir(), "messages.txt"); diff --git a/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java b/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java index 1da216c..18bfce1 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java +++ b/oeffi/src/de/schildbach/oeffi/directions/DirectionsActivity.java @@ -58,14 +58,11 @@ import android.widget.Filter; import android.widget.Filterable; import android.widget.PopupMenu; import android.widget.TextView; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContract; -import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.common.base.Throwables; @@ -119,7 +116,6 @@ import org.osmdroid.views.overlay.Overlay; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import javax.net.ssl.SSLException; import java.io.ByteArrayInputStream; import java.io.IOException; @@ -136,8 +132,8 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; -public class DirectionsActivity extends OeffiMainActivity implements QueryHistoryClickListener, - QueryHistoryContextMenuItemListener { +public class DirectionsActivity extends OeffiMainActivity implements ActivityCompat.OnRequestPermissionsResultCallback, + QueryHistoryClickListener, QueryHistoryContextMenuItemListener { private ConnectivityManager connectivityManager; private LocationManager locationManager; @@ -171,72 +167,25 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor private static final int DIALOG_CLEAR_HISTORY = 1; + private static final int REQUEST_CODE_CONTACTS_PERMISSION_FROM = 1; + private static final int REQUEST_CODE_CONTACTS_PERMISSION_VIA = 2; + private static final int REQUEST_CODE_CONTACTS_PERMISSION_TO = 3; + private static final int REQUEST_CODE_LOCATION_PERMISSION_FROM = 4; + private static final int REQUEST_CODE_LOCATION_PERMISSION_VIA = 5; + private static final int REQUEST_CODE_LOCATION_PERMISSION_TO = 6; + private static final int REQUEST_CODE_PICK_CONTACT_FROM = 7; + private static final int REQUEST_CODE_PICK_CONTACT_VIA = 8; + private static final int REQUEST_CODE_PICK_CONTACT_TO = 9; + private static final int REQUEST_CODE_PICK_STATION_FROM = 10; + private static final int REQUEST_CODE_PICK_STATION_VIA = 11; + private static final int REQUEST_CODE_PICK_STATION_TO = 12; + private static final Logger log = LoggerFactory.getLogger(DirectionsActivity.class); private static final String INTENT_EXTRA_FROM_LOCATION = DirectionsActivity.class.getName() + ".from_location"; private static final String INTENT_EXTRA_TO_LOCATION = DirectionsActivity.class.getName() + ".to_location"; private static final String INTENT_EXTRA_TIME_SPEC = DirectionsActivity.class.getName() + ".time_spec"; - - private static class PickContact extends ActivityResultContract { - @Override - public Intent createIntent(final Context context, Void unused) { - return new Intent(Intent.ACTION_PICK, CommonDataKinds.StructuredPostal.CONTENT_URI); - } - - @Override - public Uri parseResult(final int resultCode, @Nullable final Intent intent) { - if (resultCode == Activity.RESULT_OK && intent != null) - return intent.getData(); - else - return null; - } - } - - private final ActivityResultLauncher requestLocationPermissionFromLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) - viewFromLocation.acquireLocation(); - }); - private final ActivityResultLauncher requestLocationPermissionViaLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) - viewViaLocation.acquireLocation(); - }); - private final ActivityResultLauncher requestLocationPermissionToLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) - viewToLocation.acquireLocation(); - }); - private final ActivityResultLauncher pickContactFromLauncher = - registerForActivityResult(new PickContact(), contentUri -> { - if (contentUri != null) - resultPickContact(contentUri, viewFromLocation); - }); - private final ActivityResultLauncher pickContactViaLauncher = - registerForActivityResult(new PickContact(), contentUri -> { - if (contentUri != null) - resultPickContact(contentUri, viewViaLocation); - }); - private final ActivityResultLauncher pickContactToLauncher = - registerForActivityResult(new PickContact(), contentUri -> { - if (contentUri != null) - resultPickContact(contentUri, viewToLocation); - }); - private final ActivityResultLauncher pickStationFromLauncher = - registerForActivityResult(new FavoriteStationsActivity.PickFavoriteStation(), contentUri -> { - if (contentUri != null) - resultPickStation(contentUri, viewFromLocation); - }); - private final ActivityResultLauncher pickStationViaLauncher = - registerForActivityResult(new FavoriteStationsActivity.PickFavoriteStation(), contentUri -> { - if (contentUri != null) - resultPickStation(contentUri, viewViaLocation); - }); - private final ActivityResultLauncher pickStationToLauncher = - registerForActivityResult(new FavoriteStationsActivity.PickFavoriteStation(), contentUri -> { - if (contentUri != null) - resultPickStation(contentUri, viewToLocation); - }); + private static final Intent INTENT_PICK_CONTACTS = new Intent(Intent.ACTION_PICK, CommonDataKinds.StructuredPostal.CONTENT_URI); public static void start(final Context context, @Nullable final Location fromLocation, @Nullable final Location toLocation, @Nullable final TimeSpec timeSpec, final int intentFlags) { @@ -252,17 +201,19 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor private class LocationContextMenuItemClickListener implements PopupMenu.OnMenuItemClickListener { private final LocationView locationView; - private final ActivityResultLauncher requestLocationPermissionLauncher; - private final ActivityResultLauncher pickContactLauncher; - private final ActivityResultLauncher pickStationLauncher; + private final int contactsPermissionRequestCode; + private final int locationPermissionRequestCode; + private final int pickContactRequestCode; + private final int pickStationRequestCode; public LocationContextMenuItemClickListener(final LocationView locationView, - final ActivityResultLauncher requestLocationPermissionLauncher, - final ActivityResultLauncher pickContactLauncher, final ActivityResultLauncher pickStationLauncher) { + final int contactsPermissionRequestCode, final int locationPermissionRequestCode, + final int pickContactRequestCode, final int pickStationRequestCode) { this.locationView = locationView; - this.requestLocationPermissionLauncher = requestLocationPermissionLauncher; - this.pickContactLauncher = pickContactLauncher; - this.pickStationLauncher = pickStationLauncher; + this.contactsPermissionRequestCode = contactsPermissionRequestCode; + this.locationPermissionRequestCode = locationPermissionRequestCode; + this.pickContactRequestCode = pickContactRequestCode; + this.pickStationRequestCode = pickStationRequestCode; } public boolean onMenuItemClick(final MenuItem item) { @@ -271,14 +222,20 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) locationView.acquireLocation(); else - requestLocationPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION); + ActivityCompat.requestPermissions(DirectionsActivity.this, + new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, locationPermissionRequestCode); return true; } else if (item.getItemId() == R.id.directions_location_contact) { - pickContactLauncher.launch(null); + if (ContextCompat.checkSelfPermission(DirectionsActivity.this, + Manifest.permission.READ_CONTACTS) == PackageManager.PERMISSION_GRANTED) + startActivityForResult(INTENT_PICK_CONTACTS, pickContactRequestCode); + else + ActivityCompat.requestPermissions(DirectionsActivity.this, + new String[] { Manifest.permission.READ_CONTACTS }, contactsPermissionRequestCode); return true; } else if (item.getItemId() == R.id.directions_location_favorite_station) { if (network != null) - pickStationLauncher.launch(network); + FavoriteStationsActivity.startForResult(DirectionsActivity.this, pickStationRequestCode, network); return true; } else { return false; @@ -309,11 +266,10 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor backgroundHandler = new Handler(backgroundThread.getLooper()); setContentView(R.layout.directions_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); final MyActionBar actionBar = getMyActionBar(); @@ -362,13 +318,15 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor viewFromLocation.setAdapter(autoCompleteAdapter); viewFromLocation.setListener(locationChangeListener); viewFromLocation.setContextMenuItemClickListener(new LocationContextMenuItemClickListener(viewFromLocation, - requestLocationPermissionFromLauncher, pickContactFromLauncher, pickStationFromLauncher)); + REQUEST_CODE_CONTACTS_PERMISSION_FROM, REQUEST_CODE_LOCATION_PERMISSION_FROM, + REQUEST_CODE_PICK_CONTACT_FROM, REQUEST_CODE_PICK_STATION_FROM)); viewViaLocation = findViewById(R.id.directions_via); viewViaLocation.setAdapter(autoCompleteAdapter); viewViaLocation.setListener(locationChangeListener); viewViaLocation.setContextMenuItemClickListener(new LocationContextMenuItemClickListener(viewViaLocation, - requestLocationPermissionViaLauncher, pickContactViaLauncher, pickStationViaLauncher)); + REQUEST_CODE_CONTACTS_PERMISSION_VIA, REQUEST_CODE_LOCATION_PERMISSION_VIA, + REQUEST_CODE_PICK_CONTACT_VIA, REQUEST_CODE_PICK_STATION_VIA)); viewToLocation = findViewById(R.id.directions_to); viewToLocation.setAdapter(autoCompleteAdapter); @@ -385,7 +343,8 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor } }); viewToLocation.setContextMenuItemClickListener(new LocationContextMenuItemClickListener(viewToLocation, - requestLocationPermissionToLauncher, pickContactToLauncher, pickStationToLauncher)); + REQUEST_CODE_CONTACTS_PERMISSION_TO, REQUEST_CODE_LOCATION_PERMISSION_TO, + REQUEST_CODE_PICK_CONTACT_TO, REQUEST_CODE_PICK_STATION_TO)); viewProducts = findViewById(R.id.directions_products); viewProductToggles.add(findViewById(R.id.directions_products_i)); @@ -459,11 +418,10 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor viewQueryHistoryList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); queryHistoryListAdapter = new QueryHistoryAdapter(this, network, this, this); viewQueryHistoryList.setAdapter(queryHistoryListAdapter); - ViewCompat.setOnApplyWindowInsetsListener(viewQueryHistoryList, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + viewQueryHistoryList.setOnApplyWindowInsetsListener((v, insets) -> { v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), - insets.bottom); - return windowInsets; + insets.getSystemWindowInsetBottom()); + return insets; }); viewQueryHistoryEmpty = findViewById(R.id.directions_query_history_empty); @@ -550,17 +508,15 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor }); final TextView mapDisclaimerView = findViewById(R.id.directions_map_disclaimer); mapDisclaimerView.setText(mapView.getTileProvider().getTileSource().getCopyrightNotice()); - ViewCompat.setOnApplyWindowInsetsListener(mapDisclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + mapDisclaimerView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0,0,0, insets.getSystemWindowInsetBottom()); + return insets; }); final ZoomControls zoom = findViewById(R.id.directions_map_zoom); - ViewCompat.setOnApplyWindowInsetsListener(zoom, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + zoom.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); mapView.setZoomControls(zoom); @@ -1176,20 +1132,75 @@ public class DirectionsActivity extends OeffiMainActivity implements QueryHistor return super.onCreateDialog(id); } - private void resultPickContact(final Uri contentUri, final LocationView targetLocationView) { - final Cursor c = managedQuery(contentUri, null, null, null, null); - if (c.moveToFirst()) { - final String data = c - .getString(c.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS)); - final Location location = new Location(LocationType.ADDRESS, null, null, data.replace("\n", " ")); - targetLocationView.setLocation(location); - log.info("Picked {} from contacts", location); - requestFocusFirst(); + @Override + protected void onActivityResult(final int requestCode, final int resultCode, final Intent result) { + if (result == null) + return; + + if (resultCode == Activity.RESULT_OK) { + switch (requestCode) { + case (REQUEST_CODE_PICK_CONTACT_FROM): + resultPickContact(result, viewFromLocation); + break; + + case (REQUEST_CODE_PICK_CONTACT_VIA): + resultPickContact(result, viewViaLocation); + break; + + case (REQUEST_CODE_PICK_CONTACT_TO): + resultPickContact(result, viewToLocation); + break; + + case (REQUEST_CODE_PICK_STATION_FROM): + resultPickStation(result, viewFromLocation); + break; + + case (REQUEST_CODE_PICK_STATION_VIA): + resultPickStation(result, viewViaLocation); + break; + + case (REQUEST_CODE_PICK_STATION_TO): + resultPickStation(result, viewToLocation); + break; + } } } - private void resultPickStation(final Uri contentUri, final LocationView targetLocationView) { - final Cursor c = managedQuery(contentUri, null, null, null, null); + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + if (requestCode == REQUEST_CODE_CONTACTS_PERMISSION_FROM) + startActivityForResult(INTENT_PICK_CONTACTS, REQUEST_CODE_PICK_CONTACT_FROM); + else if (requestCode == REQUEST_CODE_CONTACTS_PERMISSION_VIA) + startActivityForResult(INTENT_PICK_CONTACTS, REQUEST_CODE_PICK_CONTACT_VIA); + else if (requestCode == REQUEST_CODE_CONTACTS_PERMISSION_TO) + startActivityForResult(INTENT_PICK_CONTACTS, REQUEST_CODE_PICK_CONTACT_TO); + else if (requestCode == REQUEST_CODE_LOCATION_PERMISSION_FROM) + viewFromLocation.acquireLocation(); + else if (requestCode == REQUEST_CODE_LOCATION_PERMISSION_VIA) + viewViaLocation.acquireLocation(); + else if (requestCode == REQUEST_CODE_LOCATION_PERMISSION_TO) + viewToLocation.acquireLocation(); + } + + private void resultPickContact(final Intent result, final LocationView targetLocationView) { + try { + final Cursor c = managedQuery(result.getData(), null, null, null, null); + if (c.moveToFirst()) { + final String data = c + .getString(c.getColumnIndexOrThrow(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS)); + final Location location = new Location(LocationType.ADDRESS, null, null, data.replace("\n", " ")); + targetLocationView.setLocation(location); + log.info("Picked {} from contacts", location); + requestFocusFirst(); + } + } catch (final SecurityException x) { + new Toast(this).longToast(R.string.directions_location_choose_error_permission, x.getMessage()); + } + } + + private void resultPickStation(final Intent result, final LocationView targetLocationView) { + final Cursor c = managedQuery(result.getData(), null, null, null, null); if (c.moveToFirst()) { final Location location = FavoriteStationsProvider.getLocation(c); targetLocationView.setLocation(location); diff --git a/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java b/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java index 7455469..ec659bd 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java +++ b/oeffi/src/de/schildbach/oeffi/directions/DirectionsShortcutActivity.java @@ -30,8 +30,7 @@ import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import com.google.common.base.Throwables; import de.schildbach.oeffi.Constants; @@ -59,7 +58,8 @@ import org.slf4j.LoggerFactory; import javax.net.ssl.SSLException; import java.util.Set; -public class DirectionsShortcutActivity extends OeffiActivity implements LocationHelper.Callback { +public class DirectionsShortcutActivity extends OeffiActivity + implements ActivityCompat.OnRequestPermissionsResultCallback, LocationHelper.Callback { public static final String INTENT_EXTRA_NETWORK = "network"; public static final String INTENT_EXTRA_TYPE = "type"; public static final String INTENT_EXTRA_ID = "stationid"; @@ -76,15 +76,9 @@ public class DirectionsShortcutActivity extends OeffiActivity implements Locatio private final Handler handler = new Handler(); private QueryTripsRunnable queryTripsRunnable; - private static final Logger log = LoggerFactory.getLogger(DirectionsShortcutActivity.class); + private static final int REQUEST_CODE_REQUEST_LOCATION_PERMISSION = 1; - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - if (granted) - maybeStartLocation(); - else - errorDialog(R.string.acquire_location_no_permission); - }); + private static final Logger log = LoggerFactory.getLogger(DirectionsShortcutActivity.class); @Override protected void onCreate(final Bundle savedInstanceState) { @@ -100,7 +94,8 @@ public class DirectionsShortcutActivity extends OeffiActivity implements Locatio Manifest.permission.ACCESS_FINE_LOCATION) == PackageManager.PERMISSION_GRANTED) maybeStartLocation(); else - requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, + REQUEST_CODE_REQUEST_LOCATION_PERMISSION); } @Override @@ -110,6 +105,17 @@ public class DirectionsShortcutActivity extends OeffiActivity implements Locatio super.onDestroy(); } + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + if (requestCode == REQUEST_CODE_REQUEST_LOCATION_PERMISSION) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + maybeStartLocation(); + else + errorDialog(R.string.acquire_location_no_permission); + } + } + public void maybeStartLocation() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) diff --git a/oeffi/src/de/schildbach/oeffi/directions/LocationView.java b/oeffi/src/de/schildbach/oeffi/directions/LocationView.java index 7c14a5b..da74771 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/LocationView.java +++ b/oeffi/src/de/schildbach/oeffi/directions/LocationView.java @@ -41,6 +41,7 @@ import android.widget.FrameLayout; import android.widget.ListAdapter; import android.widget.PopupMenu; import android.widget.TextView.OnEditorActionListener; +import androidx.annotation.Nullable; import com.google.common.base.Strings; import de.schildbach.oeffi.Constants; import de.schildbach.oeffi.R; @@ -52,7 +53,6 @@ import de.schildbach.pte.dto.Location; import de.schildbach.pte.dto.LocationType; import de.schildbach.pte.dto.Point; -import javax.annotation.Nullable; import java.util.Locale; public class LocationView extends FrameLayout implements LocationHelper.Callback { diff --git a/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java b/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java index 8372c16..10b5f51 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java +++ b/oeffi/src/de/schildbach/oeffi/directions/TripDetailsActivity.java @@ -44,15 +44,13 @@ import android.util.DisplayMetrics; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.ImageButton; import android.widget.LinearLayout; import android.widget.PopupMenu; import android.widget.TableLayout; import android.widget.TextView; import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import com.google.common.base.MoreObjects; import de.schildbach.oeffi.Constants; import de.schildbach.oeffi.LocationAware; @@ -189,11 +187,10 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen scheduleTripIntent = scheduleTripIntent(trip); setContentView(R.layout.directions_trip_details_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); final MyActionBar actionBar = getMyActionBar(); @@ -286,11 +283,9 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen ((TextView) findViewById(R.id.directions_trip_details_footer)) .setText(Html.fromHtml(getString(R.string.directions_trip_details_realtime))); - final View disclaimerView = findViewById(R.id.directions_trip_details_disclaimer_group); - ViewCompat.setOnApplyWindowInsetsListener(disclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + findViewById(R.id.directions_trip_details_disclaimer_group).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); final TextView disclaimerSourceView = findViewById(R.id.directions_trip_details_disclaimer_source); updateDisclaimerSource(disclaimerSourceView, network.name(), null); @@ -319,10 +314,9 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen }); final TextView mapDisclaimerView = findViewById(R.id.directions_trip_details_map_disclaimer); mapDisclaimerView.setText(mapView.getTileProvider().getTileSource().getCopyrightNotice()); - ViewCompat.setOnApplyWindowInsetsListener(mapDisclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + mapDisclaimerView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0,0,0, insets.getSystemWindowInsetBottom()); + return insets; }); } @@ -372,6 +366,11 @@ public class TripDetailsActivity extends OeffiActivity implements LocationListen super.onDestroy(); } + @Override + public void onAttachedToWindow() { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + @Override public void onConfigurationChanged(final Configuration config) { super.onConfigurationChanged(config); diff --git a/oeffi/src/de/schildbach/oeffi/directions/TripsGalleryAdapter.java b/oeffi/src/de/schildbach/oeffi/directions/TripsGalleryAdapter.java index 01f314e..7d1d88c 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/TripsGalleryAdapter.java +++ b/oeffi/src/de/schildbach/oeffi/directions/TripsGalleryAdapter.java @@ -41,6 +41,7 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Adapter; import android.widget.BaseAdapter; +import androidx.annotation.Nullable; import com.google.common.base.Preconditions; import de.schildbach.oeffi.R; import de.schildbach.pte.dto.Line; @@ -52,7 +53,6 @@ import de.schildbach.pte.dto.Trip.Individual; import de.schildbach.pte.dto.Trip.Leg; import de.schildbach.pte.dto.Trip.Public; -import javax.annotation.Nullable; import java.util.Collections; import java.util.Date; import java.util.List; diff --git a/oeffi/src/de/schildbach/oeffi/directions/TripsOverviewActivity.java b/oeffi/src/de/schildbach/oeffi/directions/TripsOverviewActivity.java index b0f8247..59d544f 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/TripsOverviewActivity.java +++ b/oeffi/src/de/schildbach/oeffi/directions/TripsOverviewActivity.java @@ -31,9 +31,7 @@ import android.text.format.DateUtils; import android.view.View; import android.widget.AdapterView; import android.widget.TextView; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; +import androidx.annotation.Nullable; import com.google.common.collect.ComparisonChain; import com.google.common.collect.Ordering; import com.google.common.util.concurrent.Uninterruptibles; @@ -56,7 +54,6 @@ import de.schildbach.pte.exception.SessionExpiredException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import javax.net.ssl.SSLException; import java.io.ByteArrayOutputStream; import java.io.IOException; @@ -133,11 +130,10 @@ public class TripsOverviewActivity extends OeffiActivity { final Uri historyUri = historyUriStr != null ? Uri.parse(historyUriStr) : null; setContentView(R.layout.directions_trip_overview_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); final MyActionBar actionBar = getMyActionBar(); @@ -168,11 +164,9 @@ public class TripsOverviewActivity extends OeffiActivity { }); barView.setOnScrollListener(() -> handler.post(checkMoreRunnable)); - final View disclaimerView = findViewById(R.id.directions_trip_overview_disclaimer_group); - ViewCompat.setOnApplyWindowInsetsListener(disclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + findViewById(R.id.directions_trip_overview_disclaimer_group).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); processResult(result, dep); diff --git a/oeffi/src/de/schildbach/oeffi/directions/list/QueryHistoryContextMenuItemListener.java b/oeffi/src/de/schildbach/oeffi/directions/list/QueryHistoryContextMenuItemListener.java index a8a2f54..74d0179 100644 --- a/oeffi/src/de/schildbach/oeffi/directions/list/QueryHistoryContextMenuItemListener.java +++ b/oeffi/src/de/schildbach/oeffi/directions/list/QueryHistoryContextMenuItemListener.java @@ -17,10 +17,9 @@ package de.schildbach.oeffi.directions.list; +import androidx.annotation.Nullable; import de.schildbach.pte.dto.Location; -import javax.annotation.Nullable; - public interface QueryHistoryContextMenuItemListener { boolean onQueryHistoryContextMenuItemClick(int adapterPosition, Location from, Location to, @Nullable byte[] serializedSavedTrip, int menuItemId, @Nullable Location menuItemLocation); diff --git a/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java b/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java index e5e49fc..ecbde91 100644 --- a/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java +++ b/oeffi/src/de/schildbach/oeffi/network/NetworkPickerActivity.java @@ -18,6 +18,8 @@ package de.schildbach.oeffi.network; import android.Manifest; +import android.annotation.TargetApi; +import android.app.Activity; import android.app.ActivityManager.TaskDescription; import android.content.Context; import android.content.Intent; @@ -25,10 +27,10 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.Configuration; import android.content.res.Resources; -import android.graphics.Color; import android.location.Address; import android.location.Criteria; import android.location.LocationManager; +import android.os.Build; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; @@ -38,14 +40,8 @@ import android.view.KeyEvent; import android.view.View; import android.widget.LinearLayout; import android.widget.TextView; -import androidx.activity.ComponentActivity; -import androidx.activity.EdgeToEdge; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.AreaAware; @@ -61,6 +57,7 @@ import de.schildbach.oeffi.network.list.NetworksAdapter; import de.schildbach.oeffi.util.DividerItemDecoration; import de.schildbach.oeffi.util.GeocoderThread; import de.schildbach.oeffi.util.LocationHelper; +import de.schildbach.pte.AbstractNavitiaProvider; import de.schildbach.pte.NetworkId; import de.schildbach.pte.NetworkProvider; import de.schildbach.pte.dto.Location; @@ -78,8 +75,8 @@ import java.util.List; import java.util.Locale; import java.util.Map; -public class NetworkPickerActivity extends ComponentActivity implements LocationHelper.Callback, NetworkClickListener, - NetworkContextMenuItemListener { +public class NetworkPickerActivity extends Activity implements ActivityCompat.OnRequestPermissionsResultCallback, + LocationHelper.Callback, NetworkClickListener, NetworkContextMenuItemListener { public static void start(final Context context) { final Intent intent = new Intent(context, NetworkPickerActivity.class); context.startActivity(intent); @@ -105,16 +102,12 @@ public class NetworkPickerActivity extends ComponentActivity implements Location private static final String INDEX_FILENAME = "networks.txt"; private static final int MAX_LAST_NETWORKS = 3; - private static final Logger log = LoggerFactory.getLogger(NetworkPickerActivity.class); + private static final int REQUEST_CODE_REQUEST_LOCATION_PERMISSION = 1; - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - maybeStartLocation(); - }); + private static final Logger log = LoggerFactory.getLogger(NetworkPickerActivity.class); @Override protected void onCreate(final Bundle savedInstanceState) { - EdgeToEdge.enable(this, Constants.STATUS_BAR_STYLE); super.onCreate(savedInstanceState); prefs = PreferenceManager.getDefaultSharedPreferences(this); @@ -126,11 +119,10 @@ public class NetworkPickerActivity extends ComponentActivity implements Location backgroundHandler = new Handler(backgroundThread.getLooper()); setContentView(R.layout.network_picker_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); actionBar = findViewById(R.id.action_bar); @@ -143,19 +135,18 @@ public class NetworkPickerActivity extends ComponentActivity implements Location final String network = prefsGetNetwork(); listAdapter = new NetworksAdapter(this, network, this, this); listView.setAdapter(listAdapter); - ViewCompat.setOnApplyWindowInsetsListener(listView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), insets.bottom); - return windowInsets; + listView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), + insets.getSystemWindowInsetBottom()); + return insets; }); mapView = findViewById(R.id.network_picker_map); final TextView mapDisclaimerView = findViewById(R.id.network_picker_map_disclaimer); mapDisclaimerView.setText(mapView.getTileProvider().getTileSource().getCopyrightNotice()); - ViewCompat.setOnApplyWindowInsetsListener(mapDisclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + mapDisclaimerView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); if (network != null) { @@ -204,7 +195,8 @@ public class NetworkPickerActivity extends ComponentActivity implements Location if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) - requestPermissionLauncher.launch(Manifest.permission.ACCESS_COARSE_LOCATION); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, + REQUEST_CODE_REQUEST_LOCATION_PERMISSION); } @Override @@ -241,6 +233,13 @@ public class NetworkPickerActivity extends ComponentActivity implements Location super.onDestroy(); } + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + if (requestCode == REQUEST_CODE_REQUEST_LOCATION_PERMISSION) + maybeStartLocation(); + } + public void maybeStartLocation() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) @@ -482,6 +481,10 @@ public class NetworkPickerActivity extends ComponentActivity implements Location final NetworkProvider networkProvider = NetworkProviderFactory.provider(NetworkId.valueOf(network.id)); + // workaround, because of network access for navitia + if (AbstractNavitiaProvider.class.isAssignableFrom(networkProvider.getClass())) + return false; + boolean inArea = false; final Point[] area = getArea(networkProvider); @@ -572,4 +575,10 @@ public class NetworkPickerActivity extends ComponentActivity implements Location actionBar.setBackgroundColor(color); setTaskDescription(new TaskDescription(null, null, color)); } + + @TargetApi(24) + @Override + public boolean isInMultiWindowMode() { + return Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && super.isInMultiWindowMode(); + } } diff --git a/oeffi/src/de/schildbach/oeffi/network/NetworkProviderFactory.java b/oeffi/src/de/schildbach/oeffi/network/NetworkProviderFactory.java index 886733c..c76ca61 100644 --- a/oeffi/src/de/schildbach/oeffi/network/NetworkProviderFactory.java +++ b/oeffi/src/de/schildbach/oeffi/network/NetworkProviderFactory.java @@ -18,21 +18,23 @@ package de.schildbach.oeffi.network; import com.google.common.base.Charsets; -import com.google.common.io.BaseEncoding; import de.schildbach.pte.AbstractNetworkProvider; import de.schildbach.pte.AvvAachenProvider; -import de.schildbach.pte.AvvAugsburgProvider; +import de.schildbach.pte.AvvProvider; import de.schildbach.pte.BartProvider; import de.schildbach.pte.BayernProvider; import de.schildbach.pte.BsvagProvider; import de.schildbach.pte.BvgProvider; import de.schildbach.pte.CmtaProvider; +import de.schildbach.pte.CzechRepublicProvider; import de.schildbach.pte.DbProvider; import de.schildbach.pte.DingProvider; import de.schildbach.pte.DsbProvider; import de.schildbach.pte.DubProvider; +import de.schildbach.pte.FinlandProvider; import de.schildbach.pte.GvhProvider; import de.schildbach.pte.InvgProvider; +import de.schildbach.pte.ItalyProvider; import de.schildbach.pte.KvvProvider; import de.schildbach.pte.LinzProvider; import de.schildbach.pte.LuProvider; @@ -42,22 +44,28 @@ import de.schildbach.pte.MvvProvider; import de.schildbach.pte.NasaProvider; import de.schildbach.pte.NetworkId; import de.schildbach.pte.NetworkProvider; +import de.schildbach.pte.NicaraguaProvider; import de.schildbach.pte.NsProvider; import de.schildbach.pte.NvbwProvider; import de.schildbach.pte.NvvProvider; import de.schildbach.pte.OebbProvider; +import de.schildbach.pte.ParisProvider; import de.schildbach.pte.PlProvider; import de.schildbach.pte.RtProvider; import de.schildbach.pte.RtaChicagoProvider; import de.schildbach.pte.SeProvider; import de.schildbach.pte.ShProvider; +import de.schildbach.pte.SncbProvider; +import de.schildbach.pte.SpainProvider; import de.schildbach.pte.StvProvider; import de.schildbach.pte.SydneyProvider; +import de.schildbach.pte.TfiProvider; import de.schildbach.pte.TlemProvider; import de.schildbach.pte.VbbProvider; import de.schildbach.pte.VblProvider; import de.schildbach.pte.VbnProvider; import de.schildbach.pte.VgnProvider; +import de.schildbach.pte.VmsProvider; import de.schildbach.pte.VmtProvider; import de.schildbach.pte.VmvProvider; import de.schildbach.pte.VrnProvider; @@ -76,10 +84,9 @@ import java.util.Map; public final class NetworkProviderFactory { private static Map providerCache = new HashMap<>(); - private static final BaseEncoding BASE64 = BaseEncoding.base64(); - private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:134.0) Gecko/20100101 Firefox/134.0"; - private static final byte[] VRS_CLIENT_CERTIFICATE = BASE64.decode("MIILOQIBAzCCCv8GCSqGSIb3DQEHAaCCCvAEggrsMIIK6DCCBZ8GCSqGSIb3DQEHBqCCBZAwggWMAgEAMIIFhQYJKoZIhvcNAQcBMBwGCiqGSIb3DQEMAQYwDgQITP1aoTF3ISwCAggAgIIFWBba5Nms7ssWBgCkVFboVo4EQSGNe6GvJLvlAIAPGBieMyQOeJJwDJgl422+dzIAr+wxYNTgXMBMf7ZwPpVLUyCECGcePHfbLKyAK5CqvP+zYdGYc8oHF5JcukK2wm0oCxt4sRvPKAimFjU1NWFVzX8HY8dTYia59nOF1dk7LmfA5wI8Jr2YURB71lycHLvm4KbBl23AZmEgaAGWPcHhzPFfslo8arlixKGJqc02Tq9gA0+ZY/nkvNtl7fEbVJkHXF7QP7D5O7N5T6D2THyad9rqVdS499VwQ16b5lBTgV5vWD5Ctf5riuewc4aUziGLnukBrHgWOHK8TfsAhtTOrUerAFLNVB2jF6nBKbgywBXKYOBDhKX3MdVmt3srkq0/Ta2+bxUHfwRt17EQKFzboiNuraALs2jXrbSHvuO+pV2yj0WP/sX8d6KXf3XMFejynv7Os7sD0mQTcllsN9bf2oGVUnSaHT97RAekYxaF7LX+q94rhXmhpFPH/ILQEt92lF+nk+XlmhlGT9SUhwUJ6AKysFRY7si/ofE+8V4ZFHDnyjoUNDhOUYC/Z4I7YpozuPECPKNReTbPdHXqlBIiEx243gutskl8duiGYEv7TzraAq0Nag6Xk8YcXoyMXGC8wrecU7Uts9Tm2OBErAqxvFWXL9eN/EsYV8SB745tmU+T4EqJDDZQZnRAerg7Ms4iSKSbPNj/OtwpIptv43NWAtyzEEc6NxwwQTIJZL0v9jwB0mUY7TgM4a+VwMTBHcBNZH5+x8dpwh1H8MYh91UaBOidbc2PJeLtT4pIxYlcyYGl9LJa68WgzBkc7uJmETNOfKfdJEazLvH/jIRsLBwzPj/pbJDPER82wC8l5mmbOyNa/vgjsSAvm2uYDsV1fo8xdik3q/SFRHseIf2vQtybDXrytafUb9D6/0puTycMo5IfXegHvuwIJVhYFcqoCDX8VkkebHHWdWelr7yPealzjksddiJ9a4mksc4js3g7if5cQwYkfiVNE2FQukkjJx1xhgRCsnTRv1K0n0t1g4D5CD4oYjTBiYzgF/t2CqH85wNAVKnJmKNyt0Weqcf6GQwu0oVC+9IqSAiy07KvEbLxjjqcBarQjGKPSLmJeQ0x9X+9KIaEKG3gdN5l8ptlfHhML2wZsn0cTCBU1otOdLcu4QmBGf6DSTSCXcH4GGvlWdxjxdQ7Docmdp3hQBh8wY7jRST+YWcp5zQWkOpClFjKIKx2s+0sG7XM+LNPr2zSJZTyLcPlqdc9aam9LL3nf3CUtUNVrDaiyfTYhgpBHkwc+4P8MIsaZy8gowfBhovsYvfE5aFzF3rfLf30r31/ju/jkcfnWW995X+AJb8pcQuC6R7xJ82lZyPRpyfs96eCmizjIcAcL6Wz+SQEsUE3zNuH/ctpqhD5gCKXhJTj6sXjdiGNkYqPyxKX3blw8fdh+nIe3kBdC9deaw4S+5QYNKPSmdmQAAaOxOyzLi+DKgR9bV6SzWUAO/kWCdRaCdCDy9WS+6CQ2AVsQOSYv1vBMWkZ0u5/EHqPsb6y1wtXvE0/s7T4KZi7taP/72dDclPgNHsWCW5HbSaeyx83efu3fpX7i8tsWmr+QeeRuLGJ5z0NOBKasIKhCe3XPWZGNzKNca0WJk7UWepYFfiPv57tFj6Y0zautFHFNRgP+iu0hX7nNNn0AVXjuFFiZ/fwhjFmXExSYG9xSzcR5aJha0GEJ+MQbIZD7/Ay8GRmPFrrN8x40svTfiWu71qpxqsfco+2sKhJtBxJoO/cnjRz5PrtCdnqi4dYHtvOAyjaaF/3hQvDyiEoiDuxTPIVyjCCBUEGCSqGSIb3DQEHAaCCBTIEggUuMIIFKjCCBSYGCyqGSIb3DQEMCgECoIIE7jCCBOowHAYKKoZIhvcNAQwBAzAOBAg71M5exZmMVQICCAAEggTIohxJ2uLoi9RYzxe7t0XOHkTBSI+/Rn3oQNecNuMe/YNpMMsRCQjSOJToWHGayBQJmwSkMd3NP4QnDfqWFIxHbgnfj3FLTIyfkDIObzpfHwLCOrYHQxK9Zr4t/0SfEy/34uH40ZEiPe7Mnn/iTTZy37ecZgLsvlr6wp5Gao3oBjhKZlxJM043Hy9Dk1vtRCRIFCFbdGXtcLnuVKASc+GVw6QJKoXLerImV0U5Pg6khh0huTALEULuvq5cEIlKBNqyZ37cfb3Cvf9mWSTferBcUymGyHtdh+mHtVPb3ZycprtFmKcGMR9bXK0FJ63fERmXRHBN1ZKVC0beWVgcGybDQKdx9Y26UQLtO3xdZK0Eb3Kn8jVJG3sEJi2u3CLS4wD533+jj+b1uuL8Uj/aZy2UvrbIez48JStZgBGg+IhLK5keW7KV1lHiOVwZuWERpxzbNx7jaZRWIUCwN+aMJts1d5aY+wYvlJ9uk2lQc8qpIDIHHXHvyUEnk7jxw88gQjNgo1lvUHewiQk6VBwXX7EII0kLxdNfEpBT9RAdqURqy8dpoQemoc2zwce0e14G+IElJ1ES1j2jMYkYuggjpfUJBc34QrQI2a7UQwloUMwkdoi9nwgnpeL5G3Jyvgfxxf+D9xSXh8auH5IsdO0/enDGo/Xo+ygQ3tgY3dGI02frzRF24i4hFp/FAdbLjytjgCF0KIEXbJylEweZX2g61jL/fJVowJIA3wXDSuIBq9YRdpEA2OhgCdpwcz69W9T5lVfuJBgKOKcFKSQgDm0sEEkcUV9WR4CWfC9lZ+haHvNcrJBsRkHg6KKsV8PwwbUs2WeXl3NvGnJ/kSQbqJOLfURPziY9w4phupuSTAqmQIc0D4MSZLEjDcXKjg3ifFi4NlGLy+iyzGBoC1YZk1OOlO3uhKxxSD8FG6ncRGHEr8OU+2Yj/qubqZMpckPLXPdWbZB24bQxPTKGeQjFGlgt95H3/aRK9FzmBLc1FOe4qnT9chzbewsAnuho+F7Rqe36hPCZHlIrND0RCOdTAw7buJg6yPIbpDA41SpvS1F/BdFuDepf4yd0NWt4N46zUHmpxavv+2zmDiAUG95ZQ7AmkAA39tc+XtQv3IhLK6Wa7joM61jtau34td3vi1RvN2fPY2jQqOvKA2/hTVw5SzWCI0Tl7le6+ol1/QeUJfpjBZl6Ai+ydgVycSXuyq+MXB/UUEWo8RmlX8R9+y2KtCGV0TQjfX/um1D77LzurRO430m2pggcxmdCiFyl4CRp+rXhw7W6nGwLqZfD2msKthh+tn2QxoNII1oGHHsF7fxE/E4wm54IGtqfLM5pV/5hrqgVfTetABMLFEbtIHrxEDms80SyvsP2/JgelFFrs90wZr9QkLVBBQtZpwmLu39u24HlGXhZflXX0fmlHT2vN1e/EH43Nl/iPgZPYTj6fGGJFdaKNm0QlLym2M0btN3MNMXHETUoLDOg17AomH3NRvSIARu92qa48rX+SeCdF0NJ3VmA2I3Fl4A47epkmMcCzF078UVPC2eQ9M2NtxIAsqQnfIFfxirTuSCdeVS06n8KbMi7PG4Luc7IUPr4W3SQ9mY8XjFgRjVl86QpExzE6P5WZ/RDrgaypcDED6BvMSUwIwYJKoZIhvcNAQkVMRYEFKkQDH5bs77hmpmQ899BQPMX5lIDMDEwITAJBgUrDgMCGgUABBSqWv+fwvAy3ohpbmU2hfBpJbEejAQIPczIVgsfvYECAggA"); + private static final String USER_AGENT = "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36"; + private static final String NAVITIA_AUTHORIZATION = "577e5781-23ee-4ff0-a5b3-92e5b04887e5"; // oeffi@schildbach.de public static synchronized NetworkProvider provider(final NetworkId networkId) { final NetworkProvider cachedNetworkProvider = providerCache.get(networkId); @@ -87,8 +94,7 @@ public final class NetworkProviderFactory { return cachedNetworkProvider; final AbstractNetworkProvider networkProvider = forId(networkId); - if (networkId != NetworkId.PL) - networkProvider.setUserAgent(USER_AGENT); + networkProvider.setUserAgent(USER_AGENT); providerCache.put(networkId, networkProvider); return networkProvider; } @@ -97,7 +103,8 @@ public final class NetworkProviderFactory { if (networkId.equals(NetworkId.RT)) return new RtProvider(); else if (networkId.equals(NetworkId.DB)) - return new DbProvider(); + return new DbProvider("{\"type\":\"AID\",\"aid\":\"n91dB8Z77MLdoR0K\"}", + "bdI8UVj40K5fvxwf".getBytes(Charsets.UTF_8)); else if (networkId.equals(NetworkId.BVG)) return new BvgProvider("{\"aid\":\"1Rxs112shyHLatUX4fofnmdxK\",\"type\":\"AID\"}"); else if (networkId.equals(NetworkId.VBB)) @@ -112,8 +119,8 @@ public final class NetworkProviderFactory { else if (networkId.equals(NetworkId.INVG)) return new InvgProvider("{\"type\":\"AID\",\"aid\":\"GITvwi3BGOmTQ2a5\"}", "ERxotxpwFT7uYRsI".getBytes(Charsets.UTF_8)); - else if (networkId.equals(NetworkId.AVV_AUGSBURG)) - return new AvvAugsburgProvider("{\"type\":\"AID\",\"aid\":\"jK91AVVZU77xY5oH\"}"); + else if (networkId.equals(NetworkId.AVV)) + return new AvvProvider(); else if (networkId.equals(NetworkId.VGN)) return new VgnProvider(HttpUrl.parse("https://efa.vgn.de/vgnExt_oeffi/")); else if (networkId.equals(NetworkId.VVM)) @@ -123,7 +130,7 @@ public final class NetworkProviderFactory { else if (networkId.equals(NetworkId.SH)) return new ShProvider("{\"aid\":\"r0Ot9FLFNAFxijLW\",\"type\":\"AID\"}"); else if (networkId.equals(NetworkId.GVH)) - return new GvhProvider(); + return new GvhProvider(HttpUrl.parse("https://www.efa.de/app_oeffi/")); else if (networkId.equals(NetworkId.BSVAG)) return new BsvagProvider(); else if (networkId.equals(NetworkId.VBN)) @@ -134,11 +141,13 @@ public final class NetworkProviderFactory { else if (networkId.equals(NetworkId.VMT)) return new VmtProvider("{\"aid\":\"vj5d7i3g9m5d7e3\",\"type\":\"AID\"}"); else if (networkId.equals(NetworkId.VVO)) - return new VvoProvider(HttpUrl.parse("https://efa.vvo-online.de/Oeffi/")); + return new VvoProvider(); + else if (networkId.equals(NetworkId.VMS)) + return new VmsProvider(HttpUrl.parse("https://efa.vms.de/Oeffi/")); else if (networkId.equals(NetworkId.VRR)) return new VrrProvider(); else if (networkId.equals(NetworkId.VRS)) - return new VrsProvider(VRS_CLIENT_CERTIFICATE); + return new VrsProvider(); else if (networkId.equals(NetworkId.AVV_AACHEN)) return new AvvAachenProvider("{\"id\":\"AVV_AACHEN\",\"l\":\"vs_oeffi\",\"type\":\"WEB\"}", "{\"type\":\"AID\",\"aid\":\"4vV1AcH3N511icH\"}"); @@ -164,10 +173,20 @@ public final class NetworkProviderFactory { return new LinzProvider(); else if (networkId.equals(NetworkId.STV)) return new StvProvider(); + else if (networkId.equals(NetworkId.CZECH_REPUBLIC)) + return new CzechRepublicProvider(NAVITIA_AUTHORIZATION); else if (networkId.equals(NetworkId.VBL)) return new VblProvider(); else if (networkId.equals(NetworkId.ZVV)) return new ZvvProvider("{\"type\":\"AID\",\"aid\":\"hf7mcf9bv3nv8g5f\"}"); + else if (networkId.equals(NetworkId.IT)) + return new ItalyProvider(NAVITIA_AUTHORIZATION); + else if (networkId.equals(NetworkId.PARIS)) + return new ParisProvider(NAVITIA_AUTHORIZATION); + else if (networkId.equals(NetworkId.SPAIN)) + return new SpainProvider(NAVITIA_AUTHORIZATION); + else if (networkId.equals(NetworkId.SNCB)) + return new SncbProvider("{\"type\":\"AID\",\"aid\":\"sncb-mobi\"}"); else if (networkId.equals(NetworkId.LU)) return new LuProvider("{\"type\":\"AID\",\"aid\":\"SkC81GuwuzL4e0\"}"); else if (networkId.equals(NetworkId.NS)) @@ -176,12 +195,16 @@ public final class NetworkProviderFactory { return new DsbProvider("{\"type\":\"AID\",\"aid\":\"irkmpm9mdznstenr-android\"}"); else if (networkId.equals(NetworkId.SE)) return new SeProvider("{\"type\":\"AID\",\"aid\":\"h5o3n7f4t2m8l9x1\"}"); + else if (networkId.equals(NetworkId.FINLAND)) + return new FinlandProvider(NAVITIA_AUTHORIZATION); else if (networkId.equals(NetworkId.TLEM)) return new TlemProvider(); else if (networkId.equals(NetworkId.MERSEY)) return new MerseyProvider(); + else if (networkId.equals(NetworkId.TFI)) + return new TfiProvider(); else if (networkId.equals(NetworkId.PL)) - return new PlProvider("{\"type\":\"AID\",\"aid\":\"DrxJYtYZQpEBCtcb\"}"); + return new PlProvider(); else if (networkId.equals(NetworkId.DUB)) return new DubProvider(); else if (networkId.equals(NetworkId.BART)) @@ -192,6 +215,8 @@ public final class NetworkProviderFactory { return new CmtaProvider("{\"type\":\"AID\",\"aid\":\"web9j2nak29uz41irb\"}"); else if (networkId.equals(NetworkId.SYDNEY)) return new SydneyProvider(); + else if (networkId.equals(NetworkId.NICARAGUA)) + return new NicaraguaProvider(NAVITIA_AUTHORIZATION); else throw new IllegalArgumentException(networkId.name()); } diff --git a/oeffi/src/de/schildbach/oeffi/network/NetworkResources.java b/oeffi/src/de/schildbach/oeffi/network/NetworkResources.java index 69e6e7d..a4b3f61 100644 --- a/oeffi/src/de/schildbach/oeffi/network/NetworkResources.java +++ b/oeffi/src/de/schildbach/oeffi/network/NetworkResources.java @@ -20,8 +20,8 @@ package de.schildbach.oeffi.network; import android.content.Context; import android.content.res.Resources; import android.graphics.drawable.Drawable; +import androidx.annotation.Nullable; -import javax.annotation.Nullable; import java.util.Locale; import static com.google.common.base.Preconditions.checkNotNull; diff --git a/oeffi/src/de/schildbach/oeffi/network/list/NetworkViewHolder.java b/oeffi/src/de/schildbach/oeffi/network/list/NetworkViewHolder.java index 62e3160..e943777 100644 --- a/oeffi/src/de/schildbach/oeffi/network/list/NetworkViewHolder.java +++ b/oeffi/src/de/schildbach/oeffi/network/list/NetworkViewHolder.java @@ -24,12 +24,11 @@ import android.widget.ImageButton; import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.R; import de.schildbach.oeffi.network.NetworkResources; -import javax.annotation.Nullable; - public class NetworkViewHolder extends RecyclerView.ViewHolder { private final Context context; private final Resources res; diff --git a/oeffi/src/de/schildbach/oeffi/plans/PlanActivity.java b/oeffi/src/de/schildbach/oeffi/plans/PlanActivity.java index 1310d4d..900fb72 100644 --- a/oeffi/src/de/schildbach/oeffi/plans/PlanActivity.java +++ b/oeffi/src/de/schildbach/oeffi/plans/PlanActivity.java @@ -17,27 +17,26 @@ package de.schildbach.oeffi.plans; +import android.app.Activity; import android.app.SearchManager; import android.content.Context; import android.content.Intent; import android.database.Cursor; import android.graphics.Bitmap; import android.graphics.BitmapFactory; -import android.graphics.Color; import android.net.Uri; import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.view.View; +import android.view.WindowManager; import android.view.animation.Animation; import android.view.animation.AnimationUtils; import android.widget.PopupMenu; import android.widget.TextView; import android.widget.ViewAnimator; -import androidx.activity.ComponentActivity; -import androidx.activity.EdgeToEdge; -import androidx.activity.SystemBarStyle; +import androidx.annotation.Nullable; import com.google.common.util.concurrent.FutureCallback; import com.google.common.util.concurrent.Futures; import com.google.common.util.concurrent.ListenableFuture; @@ -69,7 +68,6 @@ import okhttp3.HttpUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.net.HttpURLConnection; @@ -81,13 +79,10 @@ import java.util.TreeSet; import static com.google.common.base.Preconditions.checkNotNull; -public class PlanActivity extends ComponentActivity { +public class PlanActivity extends Activity { public static final String INTENT_EXTRA_PLAN_ID = "plan_id"; // Used in launcher shortcuts private static final String INTENT_EXTRA_SELECTED_STATION_ID = PlanActivity.class.getName() + ".selected_station_id"; - // plan backgrounds are always white, so force navigation bar to light mode - private static final SystemBarStyle NAVIGATION_BAR_STYLE = SystemBarStyle.light(Color.TRANSPARENT, - Color.TRANSPARENT); public static Intent intent(final Context context, final String planId, final String selectedStationId) { final Intent intent = new Intent(Intent.ACTION_VIEW, null, context, PlanActivity.class); @@ -122,7 +117,6 @@ public class PlanActivity extends ComponentActivity { @Override protected void onCreate(final Bundle savedInstanceState) { - EdgeToEdge.enable(this, Constants.STATUS_BAR_STYLE, NAVIGATION_BAR_STYLE); super.onCreate(savedInstanceState); this.application = (Application) getApplication(); @@ -132,6 +126,7 @@ public class PlanActivity extends ComponentActivity { backgroundHandler = new Handler(backgroundThread.getLooper()); setContentView(R.layout.plans_content); + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); final Animation zoomControlsAnimation = AnimationUtils.loadAnimation(this, R.anim.zoom_controls); zoomControlsAnimation.setFillAfter(true); // workaround: set through code because XML does not work @@ -260,6 +255,11 @@ public class PlanActivity extends ComponentActivity { super.onDestroy(); } + @Override + public void onAttachedToWindow() { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + @Override public boolean onSearchRequested() { if (!stations.isEmpty()) diff --git a/oeffi/src/de/schildbach/oeffi/plans/PlanContentProvider.java b/oeffi/src/de/schildbach/oeffi/plans/PlanContentProvider.java index 058993d..3517dbc 100644 --- a/oeffi/src/de/schildbach/oeffi/plans/PlanContentProvider.java +++ b/oeffi/src/de/schildbach/oeffi/plans/PlanContentProvider.java @@ -25,6 +25,7 @@ import android.database.CursorWrapper; import android.database.MatrixCursor; import android.net.Uri; import android.provider.BaseColumns; +import androidx.annotation.Nullable; import com.google.common.base.Objects; import com.google.common.base.Splitter; import com.google.common.base.Strings; @@ -41,7 +42,6 @@ import okhttp3.HttpUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.File; import java.io.FileInputStream; diff --git a/oeffi/src/de/schildbach/oeffi/plans/PlansPickerActivity.java b/oeffi/src/de/schildbach/oeffi/plans/PlansPickerActivity.java index 1440601..1c574a9 100644 --- a/oeffi/src/de/schildbach/oeffi/plans/PlansPickerActivity.java +++ b/oeffi/src/de/schildbach/oeffi/plans/PlansPickerActivity.java @@ -33,15 +33,12 @@ import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.TextView; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import androidx.core.content.pm.ShortcutInfoCompat; import androidx.core.content.pm.ShortcutManagerCompat; -import androidx.core.graphics.Insets; import androidx.core.graphics.drawable.IconCompat; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.common.util.concurrent.FutureCallback; @@ -66,12 +63,11 @@ import okhttp3.HttpUrl; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.File; import java.net.HttpURLConnection; -public class PlansPickerActivity extends OeffiMainActivity implements LocationHelper.Callback, PlanClickListener, - PlanContextMenuItemListener { +public class PlansPickerActivity extends OeffiMainActivity implements ActivityCompat.OnRequestPermissionsResultCallback, + LocationHelper.Callback, PlanClickListener, PlanContextMenuItemListener { private ConnectivityManager connectivityManager; private LocationHelper locationHelper; @@ -88,15 +84,11 @@ public class PlansPickerActivity extends OeffiMainActivity implements LocationHe private Cursor cursor; + private static final int REQUEST_CODE_REQUEST_LOCATION_PERMISSION = 1; private static final int THUMB_CACHE_SIZE = 2 * 1024 * 1024; private static final Logger log = LoggerFactory.getLogger(PlansPickerActivity.class); - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - maybeStartLocation(); - }); - @Override protected String taskName() { return "plans"; @@ -113,11 +105,10 @@ public class PlansPickerActivity extends OeffiMainActivity implements LocationHe thumbCache = new Cache(cacheDir, THUMB_CACHE_SIZE); setContentView(R.layout.plans_picker_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); actionBar = getMyActionBar(); @@ -135,10 +126,10 @@ public class PlansPickerActivity extends OeffiMainActivity implements LocationHe listView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); listAdapter = new PlansAdapter(this, cursor, thumbCache, this, this, application.okHttpClient()); listView.setAdapter(listAdapter); - ViewCompat.setOnApplyWindowInsetsListener(listView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), insets.bottom); - return windowInsets; + listView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), + insets.getSystemWindowInsetBottom()); + return insets; }); connectivityWarningView = findViewById(R.id.plans_picker_connectivity_warning_box); @@ -163,7 +154,8 @@ public class PlansPickerActivity extends OeffiMainActivity implements LocationHe if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) - requestPermissionLauncher.launch(Manifest.permission.ACCESS_COARSE_LOCATION); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_COARSE_LOCATION }, + REQUEST_CODE_REQUEST_LOCATION_PERMISSION); } @Override @@ -194,6 +186,13 @@ public class PlansPickerActivity extends OeffiMainActivity implements LocationHe super.onDestroy(); } + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + if (requestCode == REQUEST_CODE_REQUEST_LOCATION_PERMISSION) + maybeStartLocation(); + } + public void maybeStartLocation() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_COARSE_LOCATION) != PackageManager.PERMISSION_GRANTED) diff --git a/oeffi/src/de/schildbach/oeffi/plans/list/PlanViewHolder.java b/oeffi/src/de/schildbach/oeffi/plans/list/PlanViewHolder.java index f539e8e..57693fa 100644 --- a/oeffi/src/de/schildbach/oeffi/plans/list/PlanViewHolder.java +++ b/oeffi/src/de/schildbach/oeffi/plans/list/PlanViewHolder.java @@ -29,12 +29,12 @@ import android.widget.ImageView; import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.R; import de.schildbach.oeffi.network.NetworkResources; import okhttp3.Call; -import javax.annotation.Nullable; import java.util.Date; public class PlanViewHolder extends RecyclerView.ViewHolder { diff --git a/oeffi/src/de/schildbach/oeffi/plans/list/PlansAdapter.java b/oeffi/src/de/schildbach/oeffi/plans/list/PlansAdapter.java index 4e5debd..bbf2b66 100644 --- a/oeffi/src/de/schildbach/oeffi/plans/list/PlansAdapter.java +++ b/oeffi/src/de/schildbach/oeffi/plans/list/PlansAdapter.java @@ -26,6 +26,7 @@ import android.os.Handler; import android.provider.BaseColumns; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.Constants; import de.schildbach.oeffi.R; @@ -38,7 +39,6 @@ import okhttp3.OkHttpClient; import okhttp3.Request; import okhttp3.Response; -import javax.annotation.Nullable; import java.io.File; import java.io.IOException; import java.util.Date; diff --git a/oeffi/src/de/schildbach/oeffi/preference/AboutFragment.java b/oeffi/src/de/schildbach/oeffi/preference/AboutFragment.java index 8fb27e2..9f30553 100644 --- a/oeffi/src/de/schildbach/oeffi/preference/AboutFragment.java +++ b/oeffi/src/de/schildbach/oeffi/preference/AboutFragment.java @@ -24,12 +24,11 @@ import android.os.Build; import android.os.Bundle; import android.preference.Preference; import android.preference.PreferenceFragment; +import androidx.annotation.Nullable; import de.schildbach.oeffi.Application; import de.schildbach.oeffi.R; import de.schildbach.oeffi.util.Installer; -import javax.annotation.Nullable; - public class AboutFragment extends PreferenceFragment { private static final String KEY_ABOUT_VERSION = "about_version"; private static final String KEY_ABOUT_MARKET_APP = "about_market_app"; diff --git a/oeffi/src/de/schildbach/oeffi/preference/CommonFragment.java b/oeffi/src/de/schildbach/oeffi/preference/CommonFragment.java index 9486a15..a1bc3ec 100644 --- a/oeffi/src/de/schildbach/oeffi/preference/CommonFragment.java +++ b/oeffi/src/de/schildbach/oeffi/preference/CommonFragment.java @@ -17,18 +17,20 @@ package de.schildbach.oeffi.preference; +import android.os.Build; import android.os.Bundle; import android.preference.PreferenceFragment; +import androidx.annotation.Nullable; import de.schildbach.oeffi.R; -import javax.annotation.Nullable; - public class CommonFragment extends PreferenceFragment { + private static final String KEY_BATTERY_OPTIMIZATIONS = "battery_optimizations"; @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); addPreferencesFromResource(R.xml.preference_common); + findPreference(KEY_BATTERY_OPTIMIZATIONS).setEnabled(Build.VERSION.SDK_INT >= Build.VERSION_CODES.M); } } diff --git a/oeffi/src/de/schildbach/oeffi/preference/DirectionsFragment.java b/oeffi/src/de/schildbach/oeffi/preference/DirectionsFragment.java index 2706ad3..cdb06ac 100644 --- a/oeffi/src/de/schildbach/oeffi/preference/DirectionsFragment.java +++ b/oeffi/src/de/schildbach/oeffi/preference/DirectionsFragment.java @@ -19,10 +19,9 @@ package de.schildbach.oeffi.preference; import android.os.Bundle; import android.preference.PreferenceFragment; +import androidx.annotation.Nullable; import de.schildbach.oeffi.R; -import javax.annotation.Nullable; - public class DirectionsFragment extends PreferenceFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { diff --git a/oeffi/src/de/schildbach/oeffi/preference/DonateFragment.java b/oeffi/src/de/schildbach/oeffi/preference/DonateFragment.java index 890c797..6c511c5 100644 --- a/oeffi/src/de/schildbach/oeffi/preference/DonateFragment.java +++ b/oeffi/src/de/schildbach/oeffi/preference/DonateFragment.java @@ -17,13 +17,12 @@ package de.schildbach.oeffi.preference; -import android.content.ActivityNotFoundException; import android.os.Bundle; -import android.preference.Preference; import android.preference.PreferenceFragment; +import androidx.annotation.Nullable; +import de.schildbach.oeffi.Constants; import de.schildbach.oeffi.R; - -import javax.annotation.Nullable; +import de.schildbach.wallet.integration.android.BitcoinIntegration; public class DonateFragment extends PreferenceFragment { private static final String KEY_ABOUT_DONATE_BITCOIN = "about_donate_bitcoin"; @@ -31,14 +30,10 @@ public class DonateFragment extends PreferenceFragment { @Override public void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); + addPreferencesFromResource(R.xml.preference_donate); - final Preference donateBitcoinPreference = findPreference(KEY_ABOUT_DONATE_BITCOIN); - donateBitcoinPreference.setOnPreferenceClickListener(preference -> { - try { - startActivity(donateBitcoinPreference.getIntent()); - } catch (final ActivityNotFoundException x) { - donateBitcoinPreference.setEnabled(false); - } + findPreference(KEY_ABOUT_DONATE_BITCOIN).setOnPreferenceClickListener(preference -> { + BitcoinIntegration.request(getActivity(), Constants.BITCOIN_ADDRESS); return true; }); } diff --git a/oeffi/src/de/schildbach/oeffi/preference/PreferenceActivity.java b/oeffi/src/de/schildbach/oeffi/preference/PreferenceActivity.java index 2ee2611..ff08703 100644 --- a/oeffi/src/de/schildbach/oeffi/preference/PreferenceActivity.java +++ b/oeffi/src/de/schildbach/oeffi/preference/PreferenceActivity.java @@ -19,13 +19,9 @@ package de.schildbach.oeffi.preference; import android.app.Activity; import android.content.Intent; -import android.os.Bundle; import android.view.MenuItem; -import android.view.View; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import de.schildbach.oeffi.R; +import de.schildbach.oeffi.Variants; import java.util.List; @@ -40,21 +36,11 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { activity.startActivity(intent); } - @Override - public void onCreate(final Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(v.getPaddingLeft(), insets.top, v.getPaddingRight(), v.getPaddingBottom()); - return windowInsets; - }); - } - @Override public void onBuildHeaders(final List
target) { loadHeadersFromResource(R.xml.preference_headers, target); - loadHeadersFromResource(R.xml.preference_headers_donate, target); + if (Variants.ENABLE_DONATE) + loadHeadersFromResource(R.xml.preference_headers_donate, target); } @Override @@ -73,6 +59,6 @@ public class PreferenceActivity extends android.preference.PreferenceActivity { return CommonFragment.class.getName().equals(fragmentName) || DirectionsFragment.class.getName().equals(fragmentName) || AboutFragment.class.getName().equals(fragmentName) - || DonateFragment.class.getName().equals(fragmentName); + || (Variants.ENABLE_DONATE && DonateFragment.class.getName().equals(fragmentName)); } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/DecodeForeignActivity.java b/oeffi/src/de/schildbach/oeffi/stations/DecodeForeignActivity.java new file mode 100644 index 0000000..58b020b --- /dev/null +++ b/oeffi/src/de/schildbach/oeffi/stations/DecodeForeignActivity.java @@ -0,0 +1,125 @@ +/* + * Copyright the original author or authors. + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package de.schildbach.oeffi.stations; + +import android.app.Activity; +import android.app.ProgressDialog; +import android.content.Intent; +import android.net.Uri; +import android.os.Bundle; +import de.schildbach.oeffi.Application; +import de.schildbach.oeffi.R; +import de.schildbach.oeffi.util.DialogBuilder; +import de.schildbach.pte.NetworkId; +import de.schildbach.pte.dto.Location; +import de.schildbach.pte.dto.LocationType; +import okhttp3.Call; +import okhttp3.Callback; +import okhttp3.HttpUrl; +import okhttp3.Request; +import okhttp3.Response; + +import java.io.IOException; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class DecodeForeignActivity extends Activity { + private static final Pattern PATTERN_META_REFRESH = Pattern + .compile(" finish()); + progressDialog.setCanceledOnTouchOutside(false); + + final Request.Builder request = new Request.Builder(); + request.url(HttpUrl.parse(uri.toString())); + final Call call = application.okHttpClient().newCall(request.build()); + call.enqueue(new Callback() { + public void onResponse(final Call call, final Response r) throws IOException { + try (final Response response = r) { + if (response.isSuccessful()) { + final Matcher mRefresh = PATTERN_META_REFRESH.matcher(response.body().string()); + if (mRefresh.find()) { + final Uri refreshUri = Uri.parse(mRefresh.group(1)); + + runOnUiThread(() -> { + progressDialog.dismiss(); + if ("mobil.rmv.de".equals(refreshUri.getHost()) + && "/mobile".equals(refreshUri.getPath())) { + final String id = refreshUri.getQueryParameter("id"); + StationDetailsActivity.start(DecodeForeignActivity.this, + NetworkId.NVV, new Location(LocationType.STATION, id)); + finish(); + } else { + errorDialog(R.string.stations_decode_foreign_failed); + } + }); + } else { + onFail(); + } + } else { + onFail(); + } + } + } + + public void onFailure(final Call call, final IOException x) { + onFail(); + } + + private void onFail() { + runOnUiThread(() -> { + progressDialog.dismiss(); + errorDialog(R.string.stations_decode_foreign_failed); + }); + } + }); + } else { + throw new IllegalArgumentException("cannot handle path: '" + path + "'"); + } + } else { + throw new IllegalArgumentException("cannot handle host: '" + host + "'"); + } + } + } + + private void errorDialog(final int resId) { + final DialogBuilder builder = DialogBuilder.warn(this, 0); + builder.setMessage(resId); + builder.setPositiveButton("Ok", (dialog, which) -> finish()); + builder.setOnCancelListener(dialog -> finish()); + builder.show(); + } +} diff --git a/oeffi/src/de/schildbach/oeffi/stations/FavoriteStationsActivity.java b/oeffi/src/de/schildbach/oeffi/stations/FavoriteStationsActivity.java index 729d99f..86c3664 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/FavoriteStationsActivity.java +++ b/oeffi/src/de/schildbach/oeffi/stations/FavoriteStationsActivity.java @@ -24,10 +24,7 @@ import android.net.Uri; import android.os.Bundle; import android.view.View; import android.widget.ViewAnimator; -import androidx.activity.result.contract.ActivityResultContract; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.MyActionBar; @@ -41,7 +38,6 @@ import de.schildbach.pte.NetworkId; import de.schildbach.pte.dto.Departure; import de.schildbach.pte.dto.Location; -import javax.annotation.Nullable; import java.util.List; import static com.google.common.base.Preconditions.checkNotNull; @@ -55,21 +51,10 @@ public class FavoriteStationsActivity extends OeffiActivity context.startActivity(intent); } - public static class PickFavoriteStation extends ActivityResultContract { - @Override - public Intent createIntent(final Context context, final NetworkId network) { - final Intent intent = new Intent(context, FavoriteStationsActivity.class); - intent.putExtra(INTENT_EXTRA_NETWORK, checkNotNull(network)); - return intent; - } - - @Override - public Uri parseResult(final int resultCode, @Nullable final Intent intent) { - if (resultCode == Activity.RESULT_OK && intent != null) - return intent.getData(); - else - return null; - } + public static void startForResult(final Activity activity, final int requestCode, final NetworkId network) { + final Intent intent = new Intent(activity, FavoriteStationsActivity.class); + intent.putExtra(INTENT_EXTRA_NETWORK, checkNotNull(network)); + activity.startActivityForResult(intent, requestCode); } @Nullable @@ -87,11 +72,10 @@ public class FavoriteStationsActivity extends OeffiActivity network = (NetworkId) intent.getSerializableExtra(INTENT_EXTRA_NETWORK); setContentView(R.layout.favorites_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); final MyActionBar actionBar = getMyActionBar(); @@ -106,10 +90,10 @@ public class FavoriteStationsActivity extends OeffiActivity listView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); adapter = new FavoriteStationsAdapter(this, network, this, network == null ? this : null); listView.setAdapter(adapter); - ViewCompat.setOnApplyWindowInsetsListener(listView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), insets.bottom); - return windowInsets; + listView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), + insets.getSystemWindowInsetBottom()); + return insets; }); updateGUI(); @@ -137,7 +121,6 @@ public class FavoriteStationsActivity extends OeffiActivity } else if (menuItemId == R.id.station_context_remove_favorite) { adapter.removeEntry(adapterPosition); updateGUI(); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget return true; } else { return false; diff --git a/oeffi/src/de/schildbach/oeffi/stations/FavoriteUtils.java b/oeffi/src/de/schildbach/oeffi/stations/FavoriteUtils.java index 4edb9f2..489c5f3 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/FavoriteUtils.java +++ b/oeffi/src/de/schildbach/oeffi/stations/FavoriteUtils.java @@ -17,23 +17,23 @@ package de.schildbach.oeffi.stations; +import android.appwidget.AppWidgetManager; +import android.appwidget.AppWidgetProviderInfo; import android.content.ContentResolver; import android.content.ContentValues; +import android.content.Context; +import android.content.Intent; import android.database.Cursor; import android.net.Uri; import de.schildbach.pte.NetworkId; import de.schildbach.pte.dto.Location; -import de.schildbach.pte.dto.LocationType; import java.util.HashMap; import java.util.Map; -import static com.google.common.base.Preconditions.checkArgument; - public class FavoriteUtils { public static Uri persist(final ContentResolver contentResolver, final int type, final NetworkId networkId, final Location station) { - checkArgument(station.type == LocationType.STATION, "not a station: %s", station); final ContentValues values = new ContentValues(); values.put(FavoriteStationsProvider.KEY_TYPE, type); values.put(FavoriteStationsProvider.KEY_STATION_NETWORK, networkId.name()); @@ -71,4 +71,17 @@ public class FavoriteUtils { return favorites; } + public static void notifyFavoritesChanged(final Context context) { + // notify widgets + final AppWidgetManager appWidgetManager = AppWidgetManager.getInstance(context); + for (final AppWidgetProviderInfo providerInfo : appWidgetManager.getInstalledProviders()) { + // limit to own widgets + if (providerInfo.provider.getPackageName().equals(context.getPackageName())) { + final Intent intent = new Intent(AppWidgetManager.ACTION_APPWIDGET_UPDATE); + intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, + appWidgetManager.getAppWidgetIds(providerInfo.provider)); + context.sendBroadcast(intent); + } + } + } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/LineView.java b/oeffi/src/de/schildbach/oeffi/stations/LineView.java index 2fc9668..652d372 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/LineView.java +++ b/oeffi/src/de/schildbach/oeffi/stations/LineView.java @@ -28,7 +28,6 @@ import android.graphics.Paint.FontMetrics; import android.graphics.RectF; import android.graphics.Shader; import android.graphics.Typeface; -import android.os.Build; import android.text.Spannable; import android.text.SpannableStringBuilder; import android.text.style.ReplacementSpan; @@ -37,6 +36,7 @@ import android.widget.TextView; import com.google.common.base.Joiner; import com.google.common.base.Strings; import de.schildbach.oeffi.R; +import de.schildbach.oeffi.util.CheatSheet; import de.schildbach.pte.Standard; import de.schildbach.pte.dto.Line; import de.schildbach.pte.dto.Line.Attr; @@ -177,19 +177,16 @@ public class LineView extends TextView { "product_" + Character.toLowerCase(line.productCode()), "string", context.getPackageName()); final String sheet = Joiner.on('\n').skipNulls().join(line.name, productResId != 0 ? context.getString(productResId) : null, line.network); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - if (Strings.emptyToNull(sheet) != null) - setTooltipText(sheet); - else - setTooltipText(null); + if (Strings.emptyToNull(sheet) != null) + CheatSheet.setup(this, sheet); + else + CheatSheet.remove(this); } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - setTooltipText(null); + CheatSheet.remove(this); } } else { setText(null); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) - setTooltipText(null); + CheatSheet.remove(this); } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetProvider.java b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetProvider.java index 9c2ddfa..709fe44 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetProvider.java +++ b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetProvider.java @@ -17,20 +17,14 @@ package de.schildbach.oeffi.stations; +import android.appwidget.AppWidgetManager; import android.appwidget.AppWidgetProvider; import android.content.Context; import android.content.Intent; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; public class NearestFavoriteStationWidgetProvider extends AppWidgetProvider { - - private static final Logger log = LoggerFactory.getLogger(NearestFavoriteStationWidgetProvider.class); - @Override - public void onReceive(final Context context, final Intent intent) { - final String action = intent.getAction(); - log.info("got broadcast: {}", action); - NearestFavoriteStationWidgetService.schedulePeriodic(context); + public void onUpdate(final Context context, final AppWidgetManager appWidgetManager, final int[] appWidgetIds) { + NearestFavoriteStationWidgetService.enqueueWork(context, new Intent()); } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetService.java b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetService.java index 1add583..086cda1 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetService.java +++ b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationWidgetService.java @@ -19,10 +19,6 @@ package de.schildbach.oeffi.stations; import android.Manifest; import android.app.PendingIntent; -import android.app.job.JobInfo; -import android.app.job.JobParameters; -import android.app.job.JobScheduler; -import android.app.job.JobService; import android.appwidget.AppWidgetManager; import android.content.ComponentName; import android.content.ContentResolver; @@ -42,10 +38,10 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.Process; import android.text.format.DateFormat; -import android.text.format.DateUtils; import android.view.View; import android.widget.RemoteViews; -import androidx.annotation.WorkerThread; +import androidx.core.app.JobIntentService; +import androidx.core.app.NotificationCompat; import androidx.core.content.ContextCompat; import com.google.common.base.Throwables; import com.google.common.util.concurrent.SettableFuture; @@ -72,59 +68,24 @@ import java.util.Collections; import java.util.Date; import java.util.List; import java.util.concurrent.ExecutionException; -import java.util.concurrent.Executor; -import java.util.concurrent.Executors; import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeoutException; -public class NearestFavoriteStationWidgetService extends JobService { +public class NearestFavoriteStationWidgetService extends JobIntentService { private AppWidgetManager appWidgetManager; private LocationManager locationManager; private ContentResolver contentResolver; - private Executor executor = Executors.newFixedThreadPool(2); private HandlerThread backgroundThread; private Handler backgroundHandler; - private static final int JOB_ID_PERIODIC = 0; - private static final int JOB_ID_IMMEDIATE = 1; + private static final int JOB_ID = 1; + public static final String NOTIFICATION_CHANNEL_ID_APPWIDGET = "appwidget"; + private static final int NOTIFICATION_ID_APPWIDGET_UPDATE = 1; private static final Logger log = LoggerFactory.getLogger(NearestFavoriteStationWidgetService.class); - public static void schedulePeriodic(final Context context) { - final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - final ComponentName providerName = new ComponentName(context, NearestFavoriteStationWidgetProvider.class); - final boolean haveWidgets = AppWidgetManager.getInstance(context).getAppWidgetIds(providerName).length > 0; - if (haveWidgets) { - final JobInfo.Builder jobInfo = new JobInfo.Builder(JOB_ID_PERIODIC, new ComponentName(context, - NearestFavoriteStationWidgetService.class)); - jobInfo.setPeriodic(DateUtils.MINUTE_IN_MILLIS * 15); - jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - final JobInfo job = jobInfo.build(); - jobScheduler.schedule(job); - log.info("Scheduled periodic job: {}", job); - } else { - jobScheduler.cancelAll(); - } - } - - public static void scheduleImmediate(final Context context) { - final JobScheduler jobScheduler = (JobScheduler) context.getSystemService(Context.JOB_SCHEDULER_SERVICE); - final ComponentName providerName = new ComponentName(context, NearestFavoriteStationWidgetProvider.class); - final boolean haveWidgets = AppWidgetManager.getInstance(context).getAppWidgetIds(providerName).length > 0; - if (haveWidgets) { - final JobInfo.Builder jobInfo = new JobInfo.Builder(JOB_ID_IMMEDIATE, new ComponentName(context, - NearestFavoriteStationWidgetService.class)); - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) - jobInfo.setExpedited(true); - else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) - jobInfo.setImportantWhileForeground(true); - jobInfo.setRequiredNetworkType(JobInfo.NETWORK_TYPE_ANY); - final JobInfo job = jobInfo.build(); - jobScheduler.schedule(job); - log.info("Scheduled immediate job: {}", job); - } else { - jobScheduler.cancelAll(); - } + public static void enqueueWork(final Context context, final Intent work) { + enqueueWork(context, NearestFavoriteStationWidgetService.class, JOB_ID, work); } @Override @@ -149,35 +110,32 @@ public class NearestFavoriteStationWidgetService extends JobService { private RemoteViews views; @Override - public boolean onStartJob(final JobParameters params) { - log.info("Job started: {}", params); - executor.execute(() -> { - runJob(); - jobFinished(params, false); - log.info("Job finished: {}", params); - }); - return true; + protected void onHandleWork(final Intent intent) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + final NotificationCompat.Builder notification = new NotificationCompat.Builder(this, + NOTIFICATION_CHANNEL_ID_APPWIDGET); + notification.setSmallIcon(R.drawable.ic_stat_notify_sync_24dp); + notification.setWhen(System.currentTimeMillis()); + notification.setOngoing(true); + startForeground(NOTIFICATION_ID_APPWIDGET_UPDATE, notification.build()); + } + + handleIntent(); + + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) + stopForeground(true); } - @Override - public boolean onStopJob(final JobParameters params) { - log.info("Job stopped: {}", params); - return false; - } - - @WorkerThread - private void runJob() { + private void handleIntent() { final ComponentName providerName = new ComponentName(this, NearestFavoriteStationWidgetProvider.class); final int[] appWidgetIds = appWidgetManager.getAppWidgetIds(providerName); - if (appWidgetIds.length == 0) - return; views = new RemoteViews(getPackageName(), R.layout.station_widget_content); if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED || (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q && ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED)) { final PendingIntent intent = PendingIntent.getActivity(this, 0, new Intent(this, - NearestFavoriteStationsWidgetPermissionActivity.class), PendingIntent.FLAG_IMMUTABLE); + NearestFavoriteStationsWidgetPermissionActivity.class), 0); widgetsMessage(appWidgetIds, getString(R.string.nearest_favorite_station_widget_no_location_permission), intent); log.info("No location permission"); return; @@ -423,7 +381,7 @@ public class NearestFavoriteStationWidgetService extends JobService { intent.setAction(AppWidgetManager.ACTION_APPWIDGET_UPDATE); intent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_IDS, new int[] { appWidgetId }); intent.setData(Uri.parse(intent.toUri(Intent.URI_INTENT_SCHEME))); - return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE); + return PendingIntent.getBroadcast(this, 0, intent, PendingIntent.FLAG_UPDATE_CURRENT); } private static class Favorite implements Comparable { diff --git a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationsWidgetPermissionActivity.java b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationsWidgetPermissionActivity.java index bd52c48..0f9e752 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationsWidgetPermissionActivity.java +++ b/oeffi/src/de/schildbach/oeffi/stations/NearestFavoriteStationsWidgetPermissionActivity.java @@ -18,41 +18,22 @@ package de.schildbach.oeffi.stations; import android.Manifest; -import android.content.pm.PackageManager; -import android.os.Build; +import android.app.Activity; import android.os.Bundle; -import androidx.activity.ComponentActivity; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; -import androidx.core.content.ContextCompat; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - -import java.util.LinkedList; -import java.util.List; -import java.util.Map; - -public class NearestFavoriteStationsWidgetPermissionActivity extends ComponentActivity { - private static final Logger log = LoggerFactory.getLogger(NearestFavoriteStationsWidgetPermissionActivity.class); - - private final ActivityResultLauncher requestPermissionsLauncher = - registerForActivityResult(new ActivityResultContracts.RequestMultiplePermissions(), results -> { - for (Map.Entry entry : results.entrySet()) - log.info("{} {}", entry.getKey(), entry.getValue() ? "granted" : "denied"); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget - finish(); - }); +import androidx.core.app.ActivityCompat; +public class NearestFavoriteStationsWidgetPermissionActivity extends Activity { @Override protected void onCreate(final Bundle savedInstanceState) { super.onCreate(savedInstanceState); - final List permissions = new LinkedList<>(); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_FINE_LOCATION) != PackageManager.PERMISSION_GRANTED) - permissions.add(Manifest.permission.ACCESS_FINE_LOCATION); - if (ContextCompat.checkSelfPermission(this, Manifest.permission.ACCESS_BACKGROUND_LOCATION) != PackageManager.PERMISSION_GRANTED) - if (Build.VERSION.SDK_INT < Build.VERSION_CODES.R || permissions.isEmpty()) - permissions.add(Manifest.permission.ACCESS_BACKGROUND_LOCATION); - log.info("Requesting permissions: {}", permissions); - requestPermissionsLauncher.launch(permissions.toArray(new String[0])); + ActivityCompat.requestPermissions(this, new String[] { Manifest.permission.ACCESS_FINE_LOCATION, + Manifest.permission.ACCESS_BACKGROUND_LOCATION }, 0); + } + + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + FavoriteUtils.notifyFavoritesChanged(this); + finish(); } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/Station.java b/oeffi/src/de/schildbach/oeffi/stations/Station.java index 9853707..52b5c07 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/Station.java +++ b/oeffi/src/de/schildbach/oeffi/stations/Station.java @@ -17,6 +17,7 @@ package de.schildbach.oeffi.stations; +import androidx.annotation.Nullable; import de.schildbach.pte.NetworkId; import de.schildbach.pte.dto.Departure; import de.schildbach.pte.dto.LineDestination; @@ -24,7 +25,6 @@ import de.schildbach.pte.dto.Location; import de.schildbach.pte.dto.Product; import de.schildbach.pte.dto.QueryDeparturesResult; -import javax.annotation.Nullable; import java.util.Date; import java.util.EnumSet; import java.util.List; diff --git a/oeffi/src/de/schildbach/oeffi/stations/StationContextMenu.java b/oeffi/src/de/schildbach/oeffi/stations/StationContextMenu.java index 0b9ded6..21b780c 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/StationContextMenu.java +++ b/oeffi/src/de/schildbach/oeffi/stations/StationContextMenu.java @@ -22,6 +22,7 @@ import android.content.ComponentName; import android.content.ContentResolver; import android.content.Context; import android.content.Intent; +import android.content.pm.PackageManager; import android.database.Cursor; import android.net.Uri; import android.view.LayoutInflater; @@ -120,23 +121,54 @@ public class StationContextMenu extends PopupMenu { public static void prepareMapMenu(final Context context, final Menu menu, final NetworkId network, final Location location) { new MenuInflater(context).inflate(R.menu.station_map_context, menu); - final MenuItem mapsItem = menu.findItem(R.id.station_map_context_maps); + final MenuItem googleMapsItem = menu.findItem(R.id.station_map_context_google_maps); + final MenuItem amazonMapsItem = menu.findItem(R.id.station_map_context_amazon_maps); + final MenuItem openStreetMapItem = menu.findItem(R.id.station_map_context_openstreetmap); + final MenuItem googleStreetViewItem = menu.findItem(R.id.station_map_context_google_street_view); + final MenuItem googleNavigationItem = menu.findItem(R.id.station_map_context_google_navigation); if (location.hasCoord()) { final double lat = location.getLatAsDouble(); final double lon = location.getLonAsDouble(); final String name = location.name; - final Intent mapsIntent = new Intent(Intent.ACTION_VIEW, + final Intent googleMapsIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(String.format(Locale.ENGLISH, "geo:%.6f,%.6f?q=%.6f,%.6f%s", lat, lon, lat, lon, name != null ? '(' + URLEncoder.encode(name.replaceAll("[()]", "")) + ')' : ""))); - mapsItem.setOnMenuItemClickListener(item -> { - context.startActivity(mapsIntent); - return true; - }); - mapsItem.setVisible(true); + googleMapsIntent.setComponent( + new ComponentName("com.google.android.apps.maps", "com.google.android.maps.MapsActivity")); + prepareMapMenuItem(context, googleMapsItem, googleMapsIntent); + + final Intent amazonMapsIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse(String.format(Locale.ENGLISH, "geo:%.6f,%.6f?q=%.6f,%.6f%s", lat, lon, lat, lon, + name != null ? '(' + URLEncoder.encode(name.replaceAll("[()]", "")) + ')' : ""))); + amazonMapsIntent.setComponent(new ComponentName("com.amazon.geo.client.maps", + "com.amazon.geo.client.renderer.MapsAppActivityDuke")); + prepareMapMenuItem(context, amazonMapsItem, amazonMapsIntent); + + final Intent openStreetMapIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse(String.format(Locale.ENGLISH, "osmand.geo:%.6f,%.6f?q=%.6f,%.6f%s", lat, lon, lat, lon, + name != null ? '(' + URLEncoder.encode(name.replaceAll("[()]", "")) + ')' : ""))); + prepareMapMenuItem(context, openStreetMapItem, openStreetMapIntent); + + final Intent googleStreetViewIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse(String.format(Locale.ENGLISH, "google.streetview:cbll=%.6f,%.6f", lat, lon))); + googleStreetViewIntent + .setComponent(new ComponentName("com.google.android.street", "com.google.android.street.Street")); + prepareMapMenuItem(context, googleStreetViewItem, googleStreetViewIntent); + + final Intent googleNavigationIntent = new Intent(Intent.ACTION_VIEW, + Uri.parse(String.format(Locale.ENGLISH, "google.navigation:ll=%.6f,%.6f&title=%s&mode=w", lat, lon, + name != null ? URLEncoder.encode(name) : ""))); + googleNavigationIntent.setComponent(new ComponentName("com.google.android.apps.maps", + "com.google.android.maps.driveabout.app.NavigationActivity")); + prepareMapMenuItem(context, googleNavigationItem, googleNavigationIntent); } else { - mapsItem.setVisible(false); + googleMapsItem.setVisible(false); + amazonMapsItem.setVisible(false); + openStreetMapItem.setVisible(false); + googleStreetViewItem.setVisible(false); + googleNavigationItem.setVisible(false); } final ContentResolver contentResolver = context.getContentResolver(); @@ -160,4 +192,13 @@ public class StationContextMenu extends PopupMenu { stationsCursor.close(); } } + + private static void prepareMapMenuItem(final Context context, final MenuItem item, final Intent intent) { + final PackageManager pm = context.getPackageManager(); + item.setVisible(pm.resolveActivity(intent, 0) != null); + item.setOnMenuItemClickListener(item1 -> { + context.startActivity(intent); + return true; + }); + } } diff --git a/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java b/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java index 7c57fe6..c1d3c76 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java +++ b/oeffi/src/de/schildbach/oeffi/stations/StationDetailsActivity.java @@ -34,12 +34,11 @@ import android.text.format.DateUtils; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; +import android.view.WindowManager; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.ViewAnimator; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.common.base.Joiner; @@ -60,23 +59,22 @@ import de.schildbach.pte.dto.Line; import de.schildbach.pte.dto.LineDestination; import de.schildbach.pte.dto.Location; import de.schildbach.pte.dto.LocationType; -import de.schildbach.pte.dto.Product; import de.schildbach.pte.dto.QueryDeparturesResult; import de.schildbach.pte.dto.StationDepartures; import org.osmdroid.util.GeoPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.IOException; import java.io.Serializable; import java.util.ArrayList; import java.util.Date; -import java.util.EnumSet; import java.util.LinkedHashMap; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.regex.Matcher; +import java.util.regex.Pattern; import static com.google.common.base.Preconditions.checkArgument; import static com.google.common.base.Preconditions.checkNotNull; @@ -140,11 +138,10 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa backgroundHandler = new Handler(backgroundThread.getLooper()); setContentView(R.layout.stations_station_details_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); actionBar = getMyActionBar(); @@ -160,13 +157,13 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa FavoriteStationsProvider.TYPE_FAVORITE, selectedNetwork, selectedStation); if (rowUri != null) { selectedFavState = FavoriteStationsProvider.TYPE_FAVORITE; - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(StationDetailsActivity.this); } } else { final int numRows = FavoriteUtils.delete(getContentResolver(), selectedNetwork, selectedStation.id); if (numRows > 0) { selectedFavState = null; - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(StationDetailsActivity.this); } } }); @@ -178,41 +175,111 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa listView.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); listAdapter = new DeparturesAdapter(this); listView.setAdapter(listAdapter); - ViewCompat.setOnApplyWindowInsetsListener(listView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + listView.setOnApplyWindowInsetsListener((v, insets) -> { v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), - insets.bottom + (int) (48 * getResources().getDisplayMetrics().density)); - return windowInsets; + insets.getSystemWindowInsetBottom() + (int)(48 * getResources().getDisplayMetrics().density)); + return insets; }); mapView = findViewById(R.id.stations_station_details_map); mapView.setStationsAware(this); final TextView mapDisclaimerView = findViewById(R.id.stations_station_details_map_disclaimer); mapDisclaimerView.setText(mapView.getTileProvider().getTileSource().getCopyrightNotice()); - ViewCompat.setOnApplyWindowInsetsListener(mapDisclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + mapDisclaimerView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0,0,0, insets.getSystemWindowInsetBottom()); + return insets; }); resultStatusView = findViewById(R.id.stations_station_details_result_status); final Intent intent = getIntent(); - final NetworkId network = (NetworkId) checkNotNull(intent.getSerializableExtra(INTENT_EXTRA_NETWORK)); - final Station station = new Station(network, (Location) intent.getSerializableExtra(INTENT_EXTRA_STATION)); - if (intent.hasExtra(INTENT_EXTRA_DEPARTURES)) - station.departures = (List) intent.getSerializableExtra(INTENT_EXTRA_DEPARTURES); - selectStation(station); - statusMessage(getString(R.string.stations_station_details_progress)); + final Uri uri = intent.getData(); + + if (uri != null && uri.getScheme().equals("http")) { + log.info("Got intent: {}", intent); + + final String host = uri.getHost(); + final String path = uri.getPath().trim(); + + if ("oeffi.schildbach.de".equals(host)) { + final NetworkId network = NetworkId.valueOf(uri.getQueryParameter("network").toUpperCase()); + final String stationId = uri.getQueryParameter("id"); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else if ("qr.bvg.de".equals(host)) { + final Matcher m = Pattern.compile("/h(\\d+)").matcher(path); + if (m.matches()) { + final NetworkId network = NetworkId.BVG; + String stationId = bvgStationIdNfcToQr(m.group(1)); + if (stationId.length() <= 6) // mast + stationId = '~' + stationId; + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else { + throw new IllegalArgumentException("could not parse path: '" + path + "'"); + } + } else if ("mobil.s-bahn-berlin.de".equals(host)) { + if ("/".equals(path)) { + final NetworkId network = NetworkId.VBB; + final String qr = uri.getQueryParameter("QR"); + final String stationId = qr != null ? qr : uri.getQueryParameter("qr"); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else { + throw new IllegalArgumentException("could not parse path: '" + path + "'"); + } + } else if ("www.mvg-live.de".equals(host)) { + final Matcher m = Pattern.compile("/qr/(\\d+)-\\d*-\\d*").matcher(path); + if (m.matches()) { + final NetworkId network = NetworkId.MVV; + final String stationId = m.group(1); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else { + throw new IllegalArgumentException("could not parse path: '" + path + "'"); + } + } else if ("wap.rmv.de".equals(host)) { + final NetworkId network = NetworkId.NVV; + final String stationId = uri.getQueryParameter("id"); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else if ("m.vrn.de".equals(host)) { + final Matcher m = Pattern.compile("/(\\d+)").matcher(path); + if (m.matches()) { + final NetworkId network = NetworkId.VRN; + final String stationId = Integer.toString(Integer.parseInt(m.group(1)) + 6000000); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else { + throw new IllegalArgumentException("could not parse path: '" + path + "'"); + } + } else if ("www.rheinbahn.de".equals(host)) { + final Matcher m = Pattern.compile("/QRBarcode/HS/(\\d+)_\\d*.html").matcher(path); + if (m.matches()) { + final NetworkId network = NetworkId.VRR; + final String stationId = Integer.toString(Integer.parseInt(m.group(1)) + 20000000); + selectStation(new Station(network, new Location(LocationType.STATION, stationId, null, null))); + } else { + throw new IllegalArgumentException("could not parse path: '" + path + "'"); + } + } else if ("mobil.vvs.de".equals(host)) { + final NetworkId network = NetworkId.VVS; + final int stationId = Integer.parseInt(uri.getQueryParameter("name_dm")); + // final String lineId = uri.getQueryParameter("line"); + selectStation(new Station(network, new Location(LocationType.STATION, + Integer.toString(stationId < 10000 ? stationId + 5000000 : stationId), null, null))); + } else { + throw new RuntimeException("cannot handle host: '" + host + "'"); + } + } else { + final NetworkId network = (NetworkId) checkNotNull(getIntent().getSerializableExtra(INTENT_EXTRA_NETWORK)); + final Station station = new Station(network, (Location) intent.getSerializableExtra(INTENT_EXTRA_STATION)); + if (intent.hasExtra(INTENT_EXTRA_DEPARTURES)) + station.departures = (List) intent.getSerializableExtra(INTENT_EXTRA_DEPARTURES); + selectStation(station); + statusMessage(getString(R.string.stations_station_details_progress)); + } favoriteButton .setChecked(selectedFavState != null && selectedFavState == FavoriteStationsProvider.TYPE_FAVORITE); - final View disclaimerView = findViewById(R.id.stations_station_details_disclaimer_group); - ViewCompat.setOnApplyWindowInsetsListener(disclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + findViewById(R.id.stations_station_details_disclaimer_group).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); disclaimerSourceView = findViewById(R.id.stations_station_details_disclaimer_source); updateDisclaimerSource(disclaimerSourceView, selectedNetwork.name(), null); @@ -265,6 +332,11 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa super.onDestroy(); } + @Override + public void onAttachedToWindow() { + getWindow().addFlags(WindowManager.LayoutParams.FLAG_SHOW_WHEN_LOCKED); + } + @Override public void onConfigurationChanged(final Configuration config) { super.onConfigurationChanged(config); @@ -433,8 +505,9 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa @Override public int getItemCount() { final List selectedDepartures = StationDetailsActivity.this.selectedDepartures; - final int numDepartures = selectedDepartures != null ? selectedDepartures.size() : 0; - return numDepartures + 1; // account for header + if (selectedDepartures == null || selectedDepartures.isEmpty()) + return 1; + return selectedDepartures.size(); } @Override @@ -620,17 +693,9 @@ public class StationDetailsActivity extends OeffiActivity implements StationsAwa itemView.setOnClickListener(null); } - // position: use german translation "Gleis" only for trains, otherwise use "Steig" - boolean isTrain = departure.line.product != null && - EnumSet.of(Product.HIGH_SPEED_TRAIN, Product.REGIONAL_TRAIN, Product.SUBURBAN_TRAIN, - Product.SUBWAY).contains(departure.line.product); - positionView.setText(departure.position != null ? - Constants.DESTINATION_ARROW_INVISIBLE_PREFIX + - context.getString(isTrain ? - R.string.position_platform_train : - R.string.position_platform, - departure.position) : - null); + // position + positionView.setText(departure.position != null ? Constants.DESTINATION_ARROW_INVISIBLE_PREFIX + + context.getString(R.string.position_platform, departure.position) : null); // capacity final int[] capacity = departure.capacity; diff --git a/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java b/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java index 9cfc18c..45f4908 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java +++ b/oeffi/src/de/schildbach/oeffi/stations/StationsActivity.java @@ -53,12 +53,9 @@ import android.view.ViewGroup; import android.widget.Button; import android.widget.TextView; import android.widget.ViewAnimator; -import androidx.activity.result.ActivityResultLauncher; -import androidx.activity.result.contract.ActivityResultContracts; +import androidx.annotation.Nullable; +import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; -import androidx.core.graphics.Insets; -import androidx.core.view.ViewCompat; -import androidx.core.view.WindowInsetsCompat; import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; @@ -102,7 +99,6 @@ import org.osmdroid.util.GeoPoint; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.IOException; import java.util.ArrayList; import java.util.Arrays; @@ -120,7 +116,7 @@ import java.util.Map; import java.util.Set; public class StationsActivity extends OeffiMainActivity implements StationsAware, LocationAware, - StationContextMenuItemListener { + ActivityCompat.OnRequestPermissionsResultCallback, StationContextMenuItemListener { private ConnectivityManager connectivityManager; private LocationManager locationManager; private SensorManager sensorManager; @@ -164,11 +160,6 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware private static final Logger log = LoggerFactory.getLogger(StationsActivity.class); - private final ActivityResultLauncher requestPermissionLauncher = - registerForActivityResult(new ActivityResultContracts.RequestPermission(), granted -> { - startLocationProvider(); - }); - @Override protected String taskName() { return "stations"; @@ -187,11 +178,10 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware res = getResources(); setContentView(R.layout.stations_content); - final View contentView = findViewById(android.R.id.content); - ViewCompat.setOnApplyWindowInsetsListener(contentView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(insets.left, 0, insets.right, 0); - return windowInsets; + getWindow().getDecorView().setSystemUiVisibility(View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION); + findViewById(android.R.id.content).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(insets.getSystemWindowInsetLeft(), 0, insets.getSystemWindowInsetRight(), 0); + return insets; }); actionBar = getMyActionBar(); @@ -254,7 +244,8 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware final Button locationPermissionRequestButton = findViewById( R.id.stations_location_permission_request_button); - locationPermissionRequestButton.setOnClickListener(v -> requestPermissionLauncher.launch(Manifest.permission.ACCESS_FINE_LOCATION)); + locationPermissionRequestButton.setOnClickListener(v -> ActivityCompat.requestPermissions(StationsActivity.this, + new String[] { Manifest.permission.ACCESS_FINE_LOCATION }, 0)); final Button locationSettingsButton = findViewById(R.id.stations_list_location_settings); locationSettingsButton.setOnClickListener(v -> startActivity(new Intent(Settings.ACTION_LOCATION_SOURCE_SETTINGS))); @@ -270,26 +261,22 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware mapView.setLocationAware(this); final TextView mapDisclaimerView = findViewById(R.id.stations_map_disclaimer); mapDisclaimerView.setText(mapView.getTileProvider().getTileSource().getCopyrightNotice()); - ViewCompat.setOnApplyWindowInsetsListener(mapDisclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + mapDisclaimerView.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); final ZoomControls zoom = findViewById(R.id.stations_map_zoom); - ViewCompat.setOnApplyWindowInsetsListener(zoom, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + zoom.setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); mapView.setZoomControls(zoom); connectivityWarningView = findViewById(R.id.stations_connectivity_warning_box); - final View disclaimerView = findViewById(R.id.stations_disclaimer_group); - ViewCompat.setOnApplyWindowInsetsListener(disclaimerView, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); - v.setPadding(0, 0, 0, insets.bottom); - return windowInsets; + findViewById(R.id.stations_disclaimer_group).setOnApplyWindowInsetsListener((v, insets) -> { + v.setPadding(0, 0, 0, insets.getSystemWindowInsetBottom()); + return insets; }); disclaimerSourceView = findViewById(R.id.stations_disclaimer_source); @@ -423,11 +410,10 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware stationList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL_LIST)); stationListAdapter = new StationsAdapter(this, maxDeparturesPerStation, products, this, this); stationList.setAdapter(stationListAdapter); - ViewCompat.setOnApplyWindowInsetsListener(stationList, (v, windowInsets) -> { - final Insets insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars()); + stationList.setOnApplyWindowInsetsListener((v, insets) -> { v.setPadding(v.getPaddingLeft(), v.getPaddingTop(), v.getPaddingRight(), - insets.bottom + (int) (48 * res.getDisplayMetrics().density)); - return windowInsets; + insets.getSystemWindowInsetBottom() + (int)(48 * res.getDisplayMetrics().density)); + return insets; }); stationList.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override @@ -710,7 +696,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware if (rowUri != null) { favorites.put(location.id, FavoriteStationsProvider.TYPE_FAVORITE); postLoadNextVisible(0); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(this); return true; } else { return false; @@ -721,7 +707,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware final int numRows = FavoriteUtils.delete(getContentResolver(), network, location.id); if (numRows > 0) { favorites.remove(location.id); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(this); return true; } else { return false; @@ -733,7 +719,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware network, location); if (rowUriIgnored != null) { favorites.put(location.id, FavoriteStationsProvider.TYPE_IGNORE); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(this); return true; } else { return false; @@ -745,7 +731,7 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware if (numRowsIgnored > 0) { favorites.remove(location.id); postLoadNextVisible(0); - NearestFavoriteStationWidgetService.scheduleImmediate(this); // refresh app-widget + FavoriteUtils.notifyFavoritesChanged(this); return true; } else { return false; @@ -1356,6 +1342,13 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware return null; } + @Override + public void onRequestPermissionsResult(final int requestCode, final String[] permissions, + final int[] grantResults) { + if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) + startLocationProvider(); + } + public boolean onStationContextMenuItemClick(final int adapterPosition, final NetworkId network, final Location station, final @Nullable List departures, final int menuItemId) { if (menuItemId == R.id.station_context_add_favorite) { @@ -1505,12 +1498,11 @@ public class StationsActivity extends OeffiMainActivity implements StationsAware try { final SuggestLocationsResult result = networkProvider.suggestLocations(query, EnumSet.of(LocationType.STATION), 0); - if (result.status == SuggestLocationsResult.Status.OK) { + if (result.status == SuggestLocationsResult.Status.OK) log.info("Got {}", result.toShortString()); for (final Location l : result.getLocations()) if (l.type == LocationType.STATION) stations.add(new Station(network, l)); - } } catch (final IOException x) { x.printStackTrace(); } diff --git a/oeffi/src/de/schildbach/oeffi/stations/list/FavoriteStationsAdapter.java b/oeffi/src/de/schildbach/oeffi/stations/list/FavoriteStationsAdapter.java index ece643e..45619c1 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/list/FavoriteStationsAdapter.java +++ b/oeffi/src/de/schildbach/oeffi/stations/list/FavoriteStationsAdapter.java @@ -24,14 +24,13 @@ import android.net.Uri; import android.provider.BaseColumns; import android.view.LayoutInflater; import android.view.ViewGroup; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import de.schildbach.oeffi.R; import de.schildbach.oeffi.stations.FavoriteStationsProvider; import de.schildbach.pte.NetworkId; import de.schildbach.pte.dto.Location; -import javax.annotation.Nullable; - public class FavoriteStationsAdapter extends RecyclerView.Adapter { private final Context context; private final ContentResolver contentResolver; diff --git a/oeffi/src/de/schildbach/oeffi/stations/list/StationContextMenuItemListener.java b/oeffi/src/de/schildbach/oeffi/stations/list/StationContextMenuItemListener.java index 051aace..3902e63 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/list/StationContextMenuItemListener.java +++ b/oeffi/src/de/schildbach/oeffi/stations/list/StationContextMenuItemListener.java @@ -17,11 +17,11 @@ package de.schildbach.oeffi.stations.list; +import androidx.annotation.Nullable; import de.schildbach.pte.NetworkId; import de.schildbach.pte.dto.Departure; import de.schildbach.pte.dto.Location; -import javax.annotation.Nullable; import java.util.List; public interface StationContextMenuItemListener { diff --git a/oeffi/src/de/schildbach/oeffi/stations/list/StationViewHolder.java b/oeffi/src/de/schildbach/oeffi/stations/list/StationViewHolder.java index 3bfe3ea..015e467 100644 --- a/oeffi/src/de/schildbach/oeffi/stations/list/StationViewHolder.java +++ b/oeffi/src/de/schildbach/oeffi/stations/list/StationViewHolder.java @@ -30,6 +30,7 @@ import android.view.WindowManager; import android.widget.ImageButton; import android.widget.PopupMenu; import android.widget.TextView; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.RecyclerView; import com.google.common.base.Joiner; import de.schildbach.oeffi.Constants; @@ -49,7 +50,6 @@ import de.schildbach.pte.dto.Location; import de.schildbach.pte.dto.Product; import de.schildbach.pte.dto.QueryDeparturesResult; -import javax.annotation.Nullable; import java.util.Date; import java.util.LinkedHashMap; import java.util.LinkedList; diff --git a/oeffi/src/de/schildbach/oeffi/util/CheatSheet.java b/oeffi/src/de/schildbach/oeffi/util/CheatSheet.java new file mode 100644 index 0000000..6ad8c2d --- /dev/null +++ b/oeffi/src/de/schildbach/oeffi/util/CheatSheet.java @@ -0,0 +1,135 @@ +/* + * Copyright 2012 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package de.schildbach.oeffi.util; + +import android.content.Context; +import android.graphics.Rect; +import android.text.TextUtils; +import android.view.Gravity; +import android.view.View; +import android.widget.Toast; + +/** + * Helper class for showing cheat sheets (tooltips) for icon-only UI elements on long-press. This is already + * default platform behavior for icon-only {@link android.app.ActionBar} items and tabs. This class provides + * this behavior for any other such UI element. + * + *

+ * Based on the original action bar implementation in + * ActionMenuItemView.java. + */ +public class CheatSheet { + /** + * The estimated height of a toast, in dips (density-independent pixels). This is used to determine + * whether or not the toast should appear above or below the UI element. + */ + private static final int ESTIMATED_TOAST_HEIGHT_DIPS = 48; + + /** + * Sets up a cheat sheet (tooltip) for the given view by setting its + * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the + * view's {@link android.view.View#getContentDescription() content description} will be shown either above + * (default) or below the view (if there isn't room above it). + * + * @param view + * The view to add a cheat sheet for. + */ + public static void setup(View view) { + view.setOnLongClickListener(v -> showCheatSheet(v, v.getContentDescription())); + } + + /** + * Sets up a cheat sheet (tooltip) for the given view by setting its + * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the + * given text will be shown either above (default) or below the view (if there isn't room above it). + * + * @param view + * The view to add a cheat sheet for. + * @param textResId + * The string resource containing the text to show on long-press. + */ + public static void setup(View view, final int textResId) { + view.setOnLongClickListener(v -> showCheatSheet(v, v.getContext().getString(textResId))); + } + + /** + * Sets up a cheat sheet (tooltip) for the given view by setting its + * {@link android.view.View.OnLongClickListener}. When the view is long-pressed, a {@link Toast} with the + * given text will be shown either above (default) or below the view (if there isn't room above it). + * + * @param view + * The view to add a cheat sheet for. + * @param text + * The text to show on long-press. + */ + public static void setup(View view, final CharSequence text) { + view.setOnLongClickListener(v -> showCheatSheet(v, text)); + } + + /** + * Removes the cheat sheet for the given view by removing the view's + * {@link android.view.View.OnLongClickListener}. + * + * @param view + * The view whose cheat sheet should be removed. + */ + public static void remove(final View view) { + view.setOnLongClickListener(null); + } + + /** + * Internal helper method to show the cheat sheet toast. + */ + private static boolean showCheatSheet(View view, CharSequence text) { + if (TextUtils.isEmpty(text)) { + return false; + } + + final int[] screenPos = new int[2]; // origin is device display + final Rect displayFrame = new Rect(); // includes decorations (e.g. status bar) + view.getLocationOnScreen(screenPos); + view.getWindowVisibleDisplayFrame(displayFrame); + + final Context context = view.getContext(); + final int viewWidth = view.getWidth(); + final int viewHeight = view.getHeight(); + final int viewCenterX = screenPos[0] + viewWidth / 2; + final int screenWidth = context.getResources().getDisplayMetrics().widthPixels; + final int estimatedToastHeight = (int) (ESTIMATED_TOAST_HEIGHT_DIPS + * context.getResources().getDisplayMetrics().density); + + Toast cheatSheet = Toast.makeText(context, text, Toast.LENGTH_SHORT); + boolean showBelow = screenPos[1] < estimatedToastHeight; + if (showBelow) { + // Show below + // Offsets are after decorations (e.g. status bar) are factored in + cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top + viewHeight); + } else { + // Show above + // Offsets are after decorations (e.g. status bar) are factored in + // NOTE: We can't use Gravity.BOTTOM because when the keyboard is up + // its height isn't factored in. + cheatSheet.setGravity(Gravity.TOP | Gravity.CENTER_HORIZONTAL, viewCenterX - screenWidth / 2, + screenPos[1] - displayFrame.top - estimatedToastHeight); + } + + cheatSheet.show(); + return true; + } +} diff --git a/oeffi/src/de/schildbach/oeffi/util/Downloader.java b/oeffi/src/de/schildbach/oeffi/util/Downloader.java index b35a6a1..d03b094 100644 --- a/oeffi/src/de/schildbach/oeffi/util/Downloader.java +++ b/oeffi/src/de/schildbach/oeffi/util/Downloader.java @@ -17,6 +17,7 @@ package de.schildbach.oeffi.util; +import androidx.annotation.Nullable; import com.google.common.util.concurrent.ListenableFuture; import com.google.common.util.concurrent.SettableFuture; import com.google.common.util.concurrent.Striped; @@ -32,7 +33,6 @@ import okhttp3.ResponseBody; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import javax.annotation.Nullable; import java.io.BufferedReader; import java.io.File; import java.io.FileOutputStream; diff --git a/oeffi/src/de/schildbach/oeffi/util/ErrorReporter.java b/oeffi/src/de/schildbach/oeffi/util/ErrorReporter.java index cbc90ce..4ffd60e 100644 --- a/oeffi/src/de/schildbach/oeffi/util/ErrorReporter.java +++ b/oeffi/src/de/schildbach/oeffi/util/ErrorReporter.java @@ -181,7 +181,8 @@ public class ErrorReporter implements Thread.UncaughtExceptionHandler { report.append("Manufacturer: " + Build.MANUFACTURER + "\n"); report.append("Phone Model: " + Build.MODEL + "\n"); report.append("Android Version: " + Build.VERSION.RELEASE + "\n"); - report.append("Android security patch level: ").append(Build.VERSION.SECURITY_PATCH).append("\n"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) + report.append("Android security patch level: ").append(Build.VERSION.SECURITY_PATCH).append("\n"); report.append("ABIs: ").append(Joiner.on(", ").skipNulls().join(Strings.emptyToNull(Build.CPU_ABI), Strings.emptyToNull(Build.CPU_ABI2))).append("\n"); report.append("Board: " + Build.BOARD + "\n"); @@ -314,12 +315,15 @@ public class ErrorReporter implements Thread.UncaughtExceptionHandler { private final static Pattern PATTERN_VERSION = Pattern.compile("

([^<]*)
"); - public void check(final Context context, final int applicationVersionCode, final OkHttpClient okHttpClient) { + public void check(final Context context, final int applicationVersionCode, final String applicationVersionFlavor, + final OkHttpClient okHttpClient) { if (!stackTraceFile.exists()) return; final HttpUrl.Builder url = HttpUrl.parse(context.getString(R.string.about_changelog_summary)).newBuilder(); url.addQueryParameter("version", Integer.toString(applicationVersionCode)); + if (applicationVersionFlavor != null) + url.addQueryParameter("flavor", applicationVersionFlavor); url.addQueryParameter("sdk", Integer.toString(Build.VERSION.SDK_INT)); url.addQueryParameter("check", null); final Request.Builder request = new Request.Builder().url(url.build()); diff --git a/oeffi/src/de/schildbach/oeffi/util/LocationUriParser.java b/oeffi/src/de/schildbach/oeffi/util/LocationUriParser.java index 1ba5b5a..45168a0 100644 --- a/oeffi/src/de/schildbach/oeffi/util/LocationUriParser.java +++ b/oeffi/src/de/schildbach/oeffi/util/LocationUriParser.java @@ -42,8 +42,38 @@ public class LocationUriParser { } final String scheme = uri.getScheme(); + final String host = uri.getHost(); + final String query = uri.getQuery(); - if ("geo".equals(scheme)) { + if (("http".equals(scheme) || "https".equals(scheme)) && "maps.google.com".equals(host)) { + final String q = getQueryParameter(query, "q"); + + final String saddr = getQueryParameter(query, "saddr"); + final String sll = getQueryParameter(query, "sll"); + final Location fromLocation; + if (saddr != null) + fromLocation = parseAddrParam(saddr, null); + else if (sll != null) + fromLocation = parseAddrParam(sll, q); + else if (q != null) + fromLocation = new Location(LocationType.ANY, null, null, q); + else + fromLocation = null; + + final String daddr = getQueryParameter(query, "daddr"); + if (daddr != null) + return new Location[] { fromLocation, parseAddrParam(daddr, null) }; + else + return new Location[] { fromLocation }; + } else if ("google.navigation".equals(scheme)) { + final Location location; + if (uri.isOpaque()) + location = parseGoogleNavigation(uri.getSchemeSpecificPart()); + else + location = parseGoogleNavigation(uri.getQuery()); + + return new Location[] { location }; + } else if ("geo".equals(scheme)) { final Location location = parseGeo(uri.getSchemeSpecificPart().replaceAll(",?\n+", ",+")); return new Location[] { location }; @@ -52,6 +82,25 @@ public class LocationUriParser { throw new IllegalArgumentException("cannot parse: '" + encodedUriString + "'"); } + private static Location parseGoogleNavigation(final String query) { + final String q = getQueryParameter(query, "q"); + final String ll = getQueryParameter(query, "ll"); + final String title = getQueryParameter(query, "title"); + + if (ll != null) { + return parseAddrParam(ll, title != null ? title : q); + } else if (q != null) { + final Location location = parseAddrParam(q, title); + + if (location != null) + return location; + else + return new Location(LocationType.ANY, null, null, q); + } + + throw new IllegalArgumentException("cannot parse: '" + query + "'"); + } + private static final Pattern P_URI_GEO = Pattern.compile("(-?\\d*\\.?\\d+),(-?\\d*\\.?\\d+)", Pattern.DOTALL); private static Location parseGeo(final String query) throws NumberFormatException { diff --git a/oeffi/src/de/schildbach/oeffi/util/Toast.java b/oeffi/src/de/schildbach/oeffi/util/Toast.java index 35388d8..e823108 100644 --- a/oeffi/src/de/schildbach/oeffi/util/Toast.java +++ b/oeffi/src/de/schildbach/oeffi/util/Toast.java @@ -18,6 +18,9 @@ package de.schildbach.oeffi.util; import android.content.Context; +import android.graphics.PorterDuff; +import android.widget.TextView; +import de.schildbach.oeffi.R; public class Toast { private final Context context; diff --git a/oeffi/test/de/schildbach/oeffi/util/LocationUriParserTest.java b/oeffi/test/de/schildbach/oeffi/util/LocationUriParserTest.java index 32bf714..221918a 100644 --- a/oeffi/test/de/schildbach/oeffi/util/LocationUriParserTest.java +++ b/oeffi/test/de/schildbach/oeffi/util/LocationUriParserTest.java @@ -23,6 +23,150 @@ import org.junit.Assert; import org.junit.Test; public class LocationUriParserTest { + @Test + public void googleNow() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "http://maps.google.com/maps/?saddr=@53.5878849,9.9050791&daddr=home@52.36894989013672,9.719719886779785&layer=t&dirflg=d"); + + Assert.assertEquals(2, results.length); + final Location fromLocation = results[0]; + final Location toLocation = results[1]; + + Assert.assertEquals(LocationType.ADDRESS, fromLocation.type); + Assert.assertEquals(9905079, fromLocation.getLonAs1E6()); + Assert.assertEquals(53587885, fromLocation.getLatAs1E6()); + Assert.assertNull(fromLocation.name); + + Assert.assertEquals(LocationType.ADDRESS, toLocation.type); + Assert.assertEquals(9719720, toLocation.getLonAs1E6()); + Assert.assertEquals(52368950, toLocation.getLatAs1E6()); + Assert.assertEquals("home", toLocation.name); + } + + @Test + public void oldGoogleNow() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "http://maps.google.com/maps?daddr=52.52568054199219,13.374129295349121(Work)&dirflg=r&saddr=52.5360876,13.4263088"); + + Assert.assertEquals(2, results.length); + final Location fromLocation = results[0]; + final Location toLocation = results[1]; + + Assert.assertEquals(LocationType.COORD, fromLocation.type); + Assert.assertEquals(13426309, fromLocation.getLonAs1E6()); + Assert.assertEquals(52536088, fromLocation.getLatAs1E6()); + Assert.assertNull(fromLocation.name); + + Assert.assertEquals(LocationType.ADDRESS, toLocation.type); + Assert.assertEquals(13374129, toLocation.getLonAs1E6()); + Assert.assertEquals(52525681, toLocation.getLatAs1E6()); + Assert.assertEquals("Work", toLocation.name); + } + + @Test + public void artem() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "http://maps.google.com/maps?saddr=52.5474,13.344510000000014&daddr=52.5057193,13.353883799999949"); + + Assert.assertEquals(2, results.length); + final Location fromLocation = results[0]; + final Location toLocation = results[1]; + + Assert.assertEquals(LocationType.COORD, fromLocation.type); + Assert.assertEquals(13344510, fromLocation.getLonAs1E6()); + Assert.assertEquals(52547400, fromLocation.getLatAs1E6()); + Assert.assertNull(fromLocation.name); + + Assert.assertEquals(LocationType.COORD, toLocation.type); + Assert.assertEquals(13353884, toLocation.getLonAs1E6()); + Assert.assertEquals(52505719, toLocation.getLatAs1E6()); + Assert.assertNull(toLocation.name); + } + + @Test + public void googleNavigationThrownByGoogleNow() throws Exception { + // "Take me to Alexanderplatz" + + final Location[] results = LocationUriParser.parseLocations( + "google.navigation:title=Alexanderplatz,+10178+Berlin&ll=52.521918,13.413215&token=Fb5rIQMdX6vMACFuFuRmwwH8MA&entry=r&mode=d"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(13413215, location.getLonAs1E6()); + Assert.assertEquals(52521918, location.getLatAs1E6()); + Assert.assertEquals("Alexanderplatz, 10178 Berlin", location.name); + } + + @Test + public void googleNavigation() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "google.navigation:///?ll=52.512845,13.420671&q=Rungestraße+20,+10179+Berlin&title=c-base&entry=w&opt=+flg=0x3000000"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(13420671, location.getLonAs1E6()); + Assert.assertEquals(52512845, location.getLatAs1E6()); + Assert.assertEquals("c-base", location.name); + } + + @Test + public void googleNavigationOpaque() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "google.navigation:q=Work&ll=47.460044860839844,9.6347074508667&mode=d&entry=r&altvia=47.4140989,9.7192978&altvia=47.460046399999996,9.6350649"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(9634707, location.getLonAs1E6()); + Assert.assertEquals(47460045, location.getLatAs1E6()); + Assert.assertEquals("Work", location.name); + } + + @Test + public void googleNavigationQueryOnly() throws Exception { + final Location[] results = LocationUriParser.parseLocations("google.navigation:///?q=gleimstr.&mode=w&entry=v"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ANY, location.type); + Assert.assertFalse(location.hasCoord()); + Assert.assertEquals("gleimstr.", location.name); + } + + @Test + public void googleNavigationTitle() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "google.navigation:///?ll=52.535836,13.401525&title=Zionskirchstraße+7,+10119+Berlin,+Germany&entry=w"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(13401525, location.getLonAs1E6()); + Assert.assertEquals(52535836, location.getLatAs1E6()); + Assert.assertEquals("Zionskirchstraße 7, 10119 Berlin, Germany", location.name); + } + + @Test + public void googleNavigationQueryOnlyMixed() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "google.navigation:///?q=52.513064,13.420106000000033(C-Base,+Rungestraße,+Berlin,+Germany)&entry=w"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(13420106, location.getLonAs1E6()); + Assert.assertEquals(52513064, location.getLatAs1E6()); + Assert.assertEquals("C-Base, Rungestraße, Berlin, Germany", location.name); + } + @Test public void contacts() throws Exception { final Location[] results = LocationUriParser.parseLocations("geo:0,0?q=Karl-Marx-Allee+84,+Berlin"); @@ -78,6 +222,22 @@ public class LocationUriParserTest { Assert.assertEquals("Prinzenstraße 85, Berlin", location.name); } + @Test + public void calendar() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "http://maps.google.com/maps?q=motion*s Tanz- und Bewegungsstudio - Stella Caric GmbH, Prinzenstraße 85, Aufgang B1 - Zugang von der Oranienstraße, 10969 Berlin, Germany&sll=52.50352,13.409187999999972&radius=5"); + + Assert.assertEquals(1, results.length); + final Location location = results[0]; + + Assert.assertEquals(LocationType.ADDRESS, location.type); + Assert.assertEquals(52503520, location.getLatAs1E6()); + Assert.assertEquals(13409188, location.getLonAs1E6()); + Assert.assertEquals( + "motion*s Tanz- und Bewegungsstudio - Stella Caric GmbH, Prinzenstraße 85, Aufgang B1 - Zugang von der Oranienstraße, 10969 Berlin, Germany", + location.name); + } + @Test public void geoVariant() throws Exception { final Location[] results = LocationUriParser.parseLocations("geo:52.1333313,11.60000038?z=6"); @@ -91,6 +251,17 @@ public class LocationUriParserTest { Assert.assertNull(location.name); } + @Test + public void googleCalendarWithAddress() throws Exception { + final Location[] results = LocationUriParser.parseLocations( + "https://maps.google.com/?q=Husemannstra%C3%9Fe,+10435+Berlin,+Germany&ftid=0x47a84e01b6fb4d0b:0x65bf58162bae72fb"); + Assert.assertEquals(1, results.length); + final Location location = results[0]; + Assert.assertEquals(LocationType.ANY, location.type); + Assert.assertFalse(location.hasCoord()); + Assert.assertEquals("Husemannstraße, 10435 Berlin, Germany", location.name); + } + @Test(expected = RuntimeException.class) public void exceptionBecauseOfScheme() throws Exception { LocationUriParser.parseLocations("foo:bar"); diff --git a/public-transport-enabler b/public-transport-enabler index c7f77ac..dfcc152 160000 --- a/public-transport-enabler +++ b/public-transport-enabler @@ -1 +1 @@ -Subproject commit c7f77ac4c89b9dbb31625998b9de1dd3d617d68a +Subproject commit dfcc1525166e4eab61ba526ec80c2184ceb927e2 diff --git a/settings.gradle b/settings.gradle index 7fe5e56..2ec54c4 100644 --- a/settings.gradle +++ b/settings.gradle @@ -2,14 +2,10 @@ import org.gradle.util.GradleVersion import org.gradle.api.GradleScriptException // required Gradle version -def minGradleVersion = GradleVersion.version("4.4") // including -def maxGradleVersion = GradleVersion.version("7.0") // excluding +def minGradleVersion = GradleVersion.version("4.4") -if (GradleVersion.current() < minGradleVersion || GradleVersion.current() >= maxGradleVersion) - throw new GradleScriptException("build requires Gradle between ${minGradleVersion.version} (including) and ${maxGradleVersion.version} (excluding)", null) - -gradle.startParameter.excludedTaskNames << "lint" -gradle.startParameter.excludedTaskNames << ":oeffi:lintVitalRelease" +if (GradleVersion.current() < minGradleVersion) + throw new GradleScriptException("build requires Gradle ${minGradleVersion} or later", null) include 'oeffi' include 'public-transport-enabler'