diff --git a/.gitignore b/.gitignore index 38dfa7307..98d9e2828 100644 --- a/.gitignore +++ b/.gitignore @@ -6,8 +6,10 @@ /mason_packages /config.gypi /config-ios.gypi +/config-android.gypi /build /include/mbgl/shader/shaders.hpp /src/shader/shaders_gl.cpp /src/shader/shaders_gles2.cpp /bin/style.bin.js +*~ diff --git a/.mason b/.mason index 462fe2847..a3ebabd0c 160000 --- a/.mason +++ b/.mason @@ -1 +1 @@ -Subproject commit 462fe28479ea700943abd2fde15b03038a168da0 +Subproject commit a3ebabd0c6d57bbe2687b8a46e25aeff0ca2ab40 diff --git a/.travis.yml b/.travis.yml index a7c7109f0..14d36d655 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,12 @@ matrix: exclude: - os: linux include: + - os: linux + env: CONFIG=release MASON_PLATFORM=android TESTMUNK=yes + compiler: clang + - os: linux + env: CONFIG=debug MASON_PLATFORM=android TESTMUNK=no + compiler: clang - os: linux env: BUILDTYPE=Release JOBS=16 compiler: clang @@ -19,21 +25,26 @@ matrix: env: global: - - secure: "bG4YYWMfl9API0MSRgmOaJrlGLv06tRg9KJNawBieZvBJbITPpxVGJZT3/l/SEJ+Rl15e2dRex4k+MGQlmT2SqPQxTEYWv1qxNigKPPcla7IWeNmWWqW8uVvFjdglojgBOK2k/xErVQtA4zDfi3mwSXH4DKwquXWsoEKmX2SV7M=" - - secure: "Cbvap9ubVKgjPe3hUhI6JGeDZzBXHpOG9RaYKh+SdoIPhKnlJiNOYm1egomi+e4uqJInlFKuVHTw7Ng9Cun6Zm0jIxpkSchv1GpsR7hmB3UGnGed19Dw8121FwuUaktN+4YnbVlsyd+u8EHD3+h58t4eELrLrZolM4rS7DL6caA=" + - secure: "MZHblLZXG/jWf2w0ZFlxCLDwx2qtGgRDODQyg1BR7JIuMz6AtWv8XR/sUczWLbiABCL0a/NzJF1g4v2pI7X69IntcjOdIABBgTh7++6+1TJ0Kp8viEltb55nQG3lHy/R6fOaI7Pj9tuCX0PCRtGA5C/fGnodLGEjy3RVOJ09ln0=" + - secure: "KaSQbhgjtV7ZCkesHmvrNsbQVjk5SPfGKB1VkWenRGYhLF45HpSRNwSxMQddZ566Pg7qIFgF1iWl/B0QW3B6AWL5WmzQ5AOJgwS876pNIc/UT7ubMPtgAtjpvw1bQvQP3B8MrB+3OE5c6tD+a3LhR9krV//dOsfErR5Yy+3Mbkc=" + - ANDROID_BUILD_INSTANCE_PROFILE: arn:aws:iam::234858372212:instance-profile/android-gl-build/travis/instance-profile/travis-mapbox-gl-native-AndroidInstanceProfile-13EA815DD8IQ9 - secure: "RiBIBfVhhaMjU5ksuwJO3shdvG9FpinBjdSv4co9jg9171SR8edNriedHjVKSIeBhSGNmZmX+twS3dJS/By6tl/LKh9sTynA+ZAYYljkE7jn881B/gMrlYvdAA6og5KvkhV1/0iJWlhuZrMTkhpDR200iLgg3EWBhWjltzmDW/I=" + - secure: "CHBiUM60TolDbQnn+4IRA/tvOKwKs3g9EDvv8YHSJMg3FuHmjKQkprBasvxf3hnTXg4WLZEubmeDcyJ6RRzPP5mMSr/hksYl0pSjj/6TUecE5fHPVVeN7txVqkpOBf9i45Y+iBUQMjBb1NnDK3pHXxpnAs1Q/pe7vReErj4GF1U=" - LD_LIBRARY_PATH: '/usr/local/lib' + - TERM: dumb before_install: +- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service mysql stop; fi +- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then sudo service postgresql stop; fi - source ./scripts/local_mason.sh - source ./scripts/travis_helper.sh - source ./scripts/flags.sh - ./scripts/travis_before_install.sh -- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.3.1`/lib; fi -- if [[ ${TRAVIS_OS_NAME} == "linux" ]]; then glxinfo; fi +- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then export LD_LIBRARY_PATH=`mason prefix mesa 10.3.1`/lib; fi +- if [[ ${TRAVIS_OS_NAME} == "linux" && ${MASON_PLATFORM} != "android" ]]; then glxinfo; fi install: -- make config.gypi +- if [[ ${MASON_PLATFORM} != "android" ]]; then make config.gypi; fi - ulimit -c before_script: diff --git a/Makefile b/Makefile index 3bb8e2d8f..25eb94c43 100644 --- a/Makefile +++ b/Makefile @@ -2,6 +2,7 @@ BUILDTYPE ?= Release PYTHON ?= python V ?= 1 PREFIX ?= /usr/local +JOBS ?= 1 ifeq ($(shell uname -s), Darwin) PLATFORM ?= osx @@ -74,9 +75,34 @@ build/linux/mapboxgl-app.xcodeproj: linux/mapboxgl-app.gyp config.gypi deps/run_gyp linux/mapboxgl-app.gyp -Iconfig.gypi -Dplatform=linux --depth=. --generator-output=./build -f xcode .PHONY: build/bin/render.xcodeproj -build/bin/render.xcodeproj: bin/render.gyp config.gypi + build/bin/render.xcodeproj: bin/render.gyp config.gypi deps/run_gyp bin/render.gyp -Iconfig.gypi -Dplatform=$(PLATFORM) --depth=. --generator-output=./build -f xcode +.PHONY: android +android: + ./scripts/local_mason.sh && \ + MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env PATH && \ + export CXX="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CXX`" && \ + export CC="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CC`" && \ + export LD="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env LD`" && \ + export LINK="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CXX`" && \ + export AR="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env AR`" && \ + export RANLIB="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env RANLIB`" && \ + export LDFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env LDFLAGS` ${LDFLAGS}" && \ + export CFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CFLAGS` ${CFLAGS}" && \ + export CPPFLAGS="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env CPPFLAGS` ${CPPFLAGS}" && \ + export PATH="`MASON_DIR=./.mason MASON_PLATFORM=android ./.mason/mason env PATH`:${PATH}" && \ + MASON_PLATFORM=android ./configure config-android.gypi && \ + deps/run_gyp android/mapboxgl-app.gyp -Iconfig-android.gypi -Dplatform=android --depth=. --generator-output=./build/android -f make-android && \ + $(MAKE) -C build/android -j$(JOBS) BUILDTYPE=$(BUILDTYPE) V=$(V) androidapp && \ + mkdir -p android/java/lib/src/main/jniLibs/armeabi-v7a && \ + cp build/android/out/$(BUILDTYPE)/lib.target/libmapbox-gl.so android/java/lib/src/main/jniLibs/armeabi-v7a/libmapbox-gl.so && \ + mkdir -p android/java/lib/src/main/assets && \ + cp build/android/out/$(BUILDTYPE)/ca-bundle.crt android/java/lib/src/main/assets/ca-bundle.crt && \ + cp -r build/android/out/$(BUILDTYPE)/styles android/java/lib/src/main/assets/styles && \ + cd android/java && \ + ./gradlew --parallel-threads=$(JOBS) build + ##### Test cases ############################################################### test: build/test/Makefile @@ -157,7 +183,10 @@ clean: clear_sqlite_cache clear_xcode_cache -find ./deps/gyp -name "*.pyc" -exec rm {} \; -rm -rf ./build/ -rm -rf ./macosx/build/ - -rm -rf ./config.gypi ./config-ios.gypi + -rm -rf ./config.gypi ./config-ios.gypi ./config-android.gypi + -rm -rf ./android/java/build ./android/java/app/build ./android/java/lib/build + -rm -rf ./android/java/lib/src/main/jniLibs ./android/java/lib/src/main/assets + -rm -f ./android/test/features.zip distclean: clean -rm -rf ./mason_packages diff --git a/README.md b/README.md index 032848fad..8404c937f 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ [![Travis](https://api.travis-ci.org/mapbox/mapbox-gl-native.svg?branch=master)](https://travis-ci.org/mapbox/mapbox-gl-native/builds) An OpenGL renderer for [Mapbox Vector Tiles](https://www.mapbox.com/blog/vector-tiles), -implemented in C++11, currently targeting iOS, OS X, and Ubuntu Linux. +implemented in C++11, currently targeting iOS, OS X, Android, and Ubuntu Linux. # Depends @@ -62,9 +62,9 @@ We are using Ubuntu for development. While the software should work on other dis Install GCC 4.8+ if you are running Ubuntu 13.10 or older. Alternatively, you can also use Clang 3.4+. - sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test - sudo apt-get update - sudo apt-get install gcc-4.8 g++-4.8 + sudo add-apt-repository --yes ppa:ubuntu-toolchain-r/test + sudo apt-get update + sudo apt-get install gcc-4.8 g++-4.8 Ensure you have git and other build essentials: @@ -81,17 +81,17 @@ Install glfw3 dependencies: Finally, install Boost. If you're running Ubuntu 12.04 or older, you need to install a backport PPA since the version provided by APT doesn't contain Boost Geometry: - sudo add-apt-repository --yes ppa:boost-latest/ppa - sudo apt-get update - sudo apt-get install libboost1.55-dev + sudo add-apt-repository --yes ppa:boost-latest/ppa + sudo apt-get update + sudo apt-get install libboost1.55-dev Otherwise, you can just install - sudo apt-get install libboost-dev + sudo apt-get install libboost-dev Once you're done installing the build dependencies, you can get started by running - ./configure + ./configure Then, you can then proceed to build the library: @@ -102,6 +102,48 @@ Set an access token as described below, and then run: make run-linux +## Android (on Linux) + +Install a few build depedencies: + + apt-get install -y make git build-essential automake \ + libtool make cmake pkg-config lib32stdc++6 lib32z1 + +Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html + + export JAVA_HOME="/dir/to/jdk1.7.0_71" + +Install the [Android NDK Revision 10d](https://developer.android.com/tools/sdk/ndk/index.html). + + export ANDROID_NDK_PATH="/dir/to/android-ndk-r10d" + +Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html). + + export ANDROID_HOME="/dir/to/android-sdk-linux" + +Run: + + make android + +You can then open `android/java` in Android Studio via "Import Non-Android Studio Project". + +## Android (on OS X) + +Install Oracle JDK 7 (requires license agreement) from http://www.oracle.com/technetwork/java/javase/downloads/jdk7-downloads-1880260.html + +Install the [Android NDK Revision 10d](https://developer.android.com/tools/sdk/ndk/index.html) for 64-bit OS X. + + export ANDROID_NDK_PATH="/dir/to/android-ndk-r10d" + +Install the Android SDK. We recommend doing this by way of [Android Studio](https://developer.android.com/sdk/installing/studio.html). The latest versions recommend that you place the SDK someplace like `/usr/local/android-sdk-macosx`. + + export ANDROID_HOME="/dir/to/android-sdk-macosx" + +Run: + + make android + +You can then open `android/java` in Android Studio via "Import Non-Android Studio Project". # Troubleshooting @@ -123,6 +165,8 @@ For iOS and OS X use of the demo apps in Xcode, setup the access token by editin For Linux, set the environment variable `MAPBOX_ACCESS_TOKEN` to your token. +For Android, gradle will take the value of `MAPBOX_ACCESS_TOKEN` and save it to `android/java/app/src/main/res/raw/token.txt` where the app will read it from. + # Style Some styles in JSON format are included at `./styles`. See the [style spec](https://github.com/mapbox/mapbox-gl-style-spec) for more details. diff --git a/android/LICENSE_COMMONS.txt b/android/LICENSE_COMMONS.txt new file mode 100644 index 000000000..d64569567 --- /dev/null +++ b/android/LICENSE_COMMONS.txt @@ -0,0 +1,202 @@ + + Apache License + Version 2.0, January 2004 + http://www.apache.org/licenses/ + + TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION + + 1. Definitions. + + "License" shall mean the terms and conditions for use, reproduction, + and distribution as defined by Sections 1 through 9 of this document. + + "Licensor" shall mean the copyright owner or entity authorized by + the copyright owner that is granting the License. + + "Legal Entity" shall mean the union of the acting entity and all + other entities that control, are controlled by, or are under common + control with that entity. For the purposes of this definition, + "control" means (i) the power, direct or indirect, to cause the + direction or management of such entity, whether by contract or + otherwise, or (ii) ownership of fifty percent (50%) or more of the + outstanding shares, or (iii) beneficial ownership of such entity. + + "You" (or "Your") shall mean an individual or Legal Entity + exercising permissions granted by this License. + + "Source" form shall mean the preferred form for making modifications, + including but not limited to software source code, documentation + source, and configuration files. + + "Object" form shall mean any form resulting from mechanical + transformation or translation of a Source form, including but + not limited to compiled object code, generated documentation, + and conversions to other media types. + + "Work" shall mean the work of authorship, whether in Source or + Object form, made available under the License, as indicated by a + copyright notice that is included in or attached to the work + (an example is provided in the Appendix below). + + "Derivative Works" shall mean any work, whether in Source or Object + form, that is based on (or derived from) the Work and for which the + editorial revisions, annotations, elaborations, or other modifications + represent, as a whole, an original work of authorship. For the purposes + of this License, Derivative Works shall not include works that remain + separable from, or merely link (or bind by name) to the interfaces of, + the Work and Derivative Works thereof. + + "Contribution" shall mean any work of authorship, including + the original version of the Work and any modifications or additions + to that Work or Derivative Works thereof, that is intentionally + submitted to Licensor for inclusion in the Work by the copyright owner + or by an individual or Legal Entity authorized to submit on behalf of + the copyright owner. For the purposes of this definition, "submitted" + means any form of electronic, verbal, or written communication sent + to the Licensor or its representatives, including but not limited to + communication on electronic mailing lists, source code control systems, + and issue tracking systems that are managed by, or on behalf of, the + Licensor for the purpose of discussing and improving the Work, but + excluding communication that is conspicuously marked or otherwise + designated in writing by the copyright owner as "Not a Contribution." + + "Contributor" shall mean Licensor and any individual or Legal Entity + on behalf of whom a Contribution has been received by Licensor and + subsequently incorporated within the Work. + + 2. Grant of Copyright License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + copyright license to reproduce, prepare Derivative Works of, + publicly display, publicly perform, sublicense, and distribute the + Work and such Derivative Works in Source or Object form. + + 3. Grant of Patent License. Subject to the terms and conditions of + this License, each Contributor hereby grants to You a perpetual, + worldwide, non-exclusive, no-charge, royalty-free, irrevocable + (except as stated in this section) patent license to make, have made, + use, offer to sell, sell, import, and otherwise transfer the Work, + where such license applies only to those patent claims licensable + by such Contributor that are necessarily infringed by their + Contribution(s) alone or by combination of their Contribution(s) + with the Work to which such Contribution(s) was submitted. If You + institute patent litigation against any entity (including a + cross-claim or counterclaim in a lawsuit) alleging that the Work + or a Contribution incorporated within the Work constitutes direct + or contributory patent infringement, then any patent licenses + granted to You under this License for that Work shall terminate + as of the date such litigation is filed. + + 4. Redistribution. You may reproduce and distribute copies of the + Work or Derivative Works thereof in any medium, with or without + modifications, and in Source or Object form, provided that You + meet the following conditions: + + (a) You must give any other recipients of the Work or + Derivative Works a copy of this License; and + + (b) You must cause any modified files to carry prominent notices + stating that You changed the files; and + + (c) You must retain, in the Source form of any Derivative Works + that You distribute, all copyright, patent, trademark, and + attribution notices from the Source form of the Work, + excluding those notices that do not pertain to any part of + the Derivative Works; and + + (d) If the Work includes a "NOTICE" text file as part of its + distribution, then any Derivative Works that You distribute must + include a readable copy of the attribution notices contained + within such NOTICE file, excluding those notices that do not + pertain to any part of the Derivative Works, in at least one + of the following places: within a NOTICE text file distributed + as part of the Derivative Works; within the Source form or + documentation, if provided along with the Derivative Works; or, + within a display generated by the Derivative Works, if and + wherever such third-party notices normally appear. The contents + of the NOTICE file are for informational purposes only and + do not modify the License. You may add Your own attribution + notices within Derivative Works that You distribute, alongside + or as an addendum to the NOTICE text from the Work, provided + that such additional attribution notices cannot be construed + as modifying the License. + + You may add Your own copyright statement to Your modifications and + may provide additional or different license terms and conditions + for use, reproduction, or distribution of Your modifications, or + for any such Derivative Works as a whole, provided Your use, + reproduction, and distribution of the Work otherwise complies with + the conditions stated in this License. + + 5. Submission of Contributions. Unless You explicitly state otherwise, + any Contribution intentionally submitted for inclusion in the Work + by You to the Licensor shall be under the terms and conditions of + this License, without any additional terms or conditions. + Notwithstanding the above, nothing herein shall supersede or modify + the terms of any separate license agreement you may have executed + with Licensor regarding such Contributions. + + 6. Trademarks. This License does not grant permission to use the trade + names, trademarks, service marks, or product names of the Licensor, + except as required for reasonable and customary use in describing the + origin of the Work and reproducing the content of the NOTICE file. + + 7. Disclaimer of Warranty. Unless required by applicable law or + agreed to in writing, Licensor provides the Work (and each + Contributor provides its Contributions) on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + implied, including, without limitation, any warranties or conditions + of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A + PARTICULAR PURPOSE. You are solely responsible for determining the + appropriateness of using or redistributing the Work and assume any + risks associated with Your exercise of permissions under this License. + + 8. Limitation of Liability. In no event and under no legal theory, + whether in tort (including negligence), contract, or otherwise, + unless required by applicable law (such as deliberate and grossly + negligent acts) or agreed to in writing, shall any Contributor be + liable to You for damages, including any direct, indirect, special, + incidental, or consequential damages of any character arising as a + result of this License or out of the use or inability to use the + Work (including but not limited to damages for loss of goodwill, + work stoppage, computer failure or malfunction, or any and all + other commercial damages or losses), even if such Contributor + has been advised of the possibility of such damages. + + 9. Accepting Warranty or Additional Liability. While redistributing + the Work or Derivative Works thereof, You may choose to offer, + and charge a fee for, acceptance of support, warranty, indemnity, + or other liability obligations and/or rights consistent with this + License. However, in accepting such obligations, You may act only + on Your own behalf and on Your sole responsibility, not on behalf + of any other Contributor, and only if You agree to indemnify, + defend, and hold each Contributor harmless for any liability + incurred by, or claims asserted against, such Contributor by reason + of your accepting any such warranty or additional liability. + + END OF TERMS AND CONDITIONS + + APPENDIX: How to apply the Apache License to your work. + + To apply the Apache License to your work, attach the following + boilerplate notice, with the fields enclosed by brackets "[]" + replaced with your own identifying information. (Don't include + the brackets!) The text should be enclosed in the appropriate + comment syntax for the file format. We also recommend that a + file or class name and description of purpose be included on the + same "printed page" as the copyright notice for easier + identification within third-party archives. + + Copyright [yyyy] [name of copyright owner] + + 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. diff --git a/android/LICENSE_GESTURES.md b/android/LICENSE_GESTURES.md new file mode 100644 index 000000000..d2d488c6e --- /dev/null +++ b/android/LICENSE_GESTURES.md @@ -0,0 +1,13 @@ +Android Gesture Detectors Framework Licence +=================================== + +Copyright (c) 2012, Almer Thie + +All rights reserved. + +Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: + + * Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. + * Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/android/LICENSE_LOST.txt b/android/LICENSE_LOST.txt new file mode 100644 index 000000000..a0fba4cbc --- /dev/null +++ b/android/LICENSE_LOST.txt @@ -0,0 +1,13 @@ +Copyright (c) 2014 Mapzen + +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. diff --git a/android/cpp/jni.cpp b/android/cpp/jni.cpp new file mode 100644 index 000000000..9123d9aba --- /dev/null +++ b/android/cpp/jni.cpp @@ -0,0 +1,1024 @@ +#include +#include + +#include +#include +#include +#include +#include + +#include + +#include +#include + +#include +#include +#include +#include +#include +#include + +#pragma clang diagnostic ignored "-Wunused-parameter" + +namespace mbgl { +namespace android { + +std::string cachePath; +std::string dataPath; +std::string apkPath; +std::string androidRelease; + +jmethodID onMapChangedId = nullptr; +jmethodID onFpsChangedId = nullptr; + +jclass lonLatClass = nullptr; +jmethodID lonLatConstructorId = nullptr; +jfieldID lonLatLonId = nullptr; +jfieldID lonLatLatId = nullptr; + +jclass lonLatZoomClass = nullptr; +jmethodID lonLatZoomConstructorId = nullptr; +jfieldID lonLatZoomLonId = nullptr; +jfieldID lonLatZoomLatId = nullptr; +jfieldID lonLatZoomZoomId = nullptr; + +jclass runtimeExceptionClass = nullptr; +jclass nullPointerExceptionClass = nullptr; + +jmethodID listToArrayId = nullptr; + +jclass arrayListClass = nullptr; +jmethodID arrayListConstructorId = nullptr; +jmethodID arrayListAddId = nullptr; + +bool throw_error(JNIEnv *env, const char *msg) { + if (env->ThrowNew(runtimeExceptionClass, msg) < 0) { + env->ExceptionDescribe(); + return false; + } + + return true; +} + +std::string std_string_from_jstring(JNIEnv *env, jstring jstr) { + std::string str; + + if (jstr == nullptr) { + if (env->ThrowNew(nullPointerExceptionClass, "String cannot be null.") < 0) { + env->ExceptionDescribe(); + return str; + } + + return str; + } + + jsize len = env->GetStringLength(jstr); + if (len < 0) { + env->ExceptionDescribe(); + return str; + } + + const jchar *chars = env->GetStringChars(jstr, nullptr); + if (chars == nullptr) { + env->ExceptionDescribe(); + return str; + } + + std::u16string ustr(reinterpret_cast(chars), len); + env->ReleaseStringChars(jstr, chars); + chars = nullptr; + str = std::wstring_convert, char16_t>().to_bytes(ustr); + return str; +} + +jstring std_string_to_jstring(JNIEnv *env, std::string str) { + std::u16string ustr = + std::wstring_convert, char16_t>().from_bytes(str); + + jstring jstr = env->NewString(reinterpret_cast(ustr.c_str()), ustr.size()); + if (jstr == nullptr) { + env->ExceptionDescribe(); + return nullptr; + } + + return jstr; +} + +std::vector std_vector_string_from_jobject(JNIEnv *env, jobject jlist) { + std::vector vector; + + if (jlist == nullptr) { + if (env->ThrowNew(nullPointerExceptionClass, "List cannot be null.") < 0) { + env->ExceptionDescribe(); + return vector; + } + + return vector; + } + + jobjectArray array = + reinterpret_cast(env->CallObjectMethod(jlist, listToArrayId)); + if (env->ExceptionCheck() || (array == nullptr)) { + env->ExceptionDescribe(); + return vector; + } + + jsize len = env->GetArrayLength(array); + if (len < 0) { + env->ExceptionDescribe(); + return vector; + } + + for (jsize i = 0; i < len; i++) { + jstring jstr = reinterpret_cast(env->GetObjectArrayElement(array, i)); + if (jstr == nullptr) { + env->ExceptionDescribe(); + return vector; + } + + vector.push_back(std_string_from_jstring(env, jstr)); + } + + return vector; +} + +jobject std_vector_string_to_jobject(JNIEnv *env, std::vector vector) { + jobject jlist = env->NewObject(arrayListClass, arrayListConstructorId); + if (jlist == nullptr) { + env->ExceptionDescribe(); + return nullptr; + } + + for (std::string str : vector) { + env->CallBooleanMethod(jlist, arrayListAddId, std_string_to_jstring(env, str)); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return nullptr; + } + } + + return jlist; +} +} +} + +namespace { + +using namespace mbgl::android; + +jlong JNICALL +nativeCreate(JNIEnv *env, jobject obj, jstring cachePath_, jstring dataPath_, jstring apkPath_) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreate"); + cachePath = std_string_from_jstring(env, cachePath_); + dataPath = std_string_from_jstring(env, dataPath_); + apkPath = std_string_from_jstring(env, apkPath_); + NativeMapView *nativeMapView = new NativeMapView(env, obj); + jlong mapViewPtr = reinterpret_cast(nativeMapView); + return mapViewPtr; +} + +void JNICALL nativeDestroy(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeDestroy"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + delete nativeMapView; + nativeMapView = nullptr; +} + +void JNICALL nativeInitializeDisplay(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeInitializeDisplay"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + try + { + nativeMapView->initializeDisplay(); + } catch(const std::exception& e) { + throw_error(env, "Unable to initialize GL display."); + } +} + +void JNICALL nativeTerminateDisplay(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminateDisplay"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->terminateDisplay(); +} + +void JNICALL nativeInitializeContext(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeInitializeContext"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + try { + nativeMapView->initializeContext(); + } catch(const std::exception& e) { + throw_error(env, "Unable to initialize GL context."); + } +} + +void JNICALL nativeTerminateContext(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminateContext"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->terminateContext(); +} + +void JNICALL +nativeCreateSurface(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject surface) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeCreateSurface"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + try { + nativeMapView->createSurface(ANativeWindow_fromSurface(env, surface)); + } catch(const std::exception& e) { + throw_error(env, "Unable to create GL surface."); + } +} + +void JNICALL nativeDestroySurface(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeDestroySurface"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->destroySurface(); +} + +void JNICALL nativeStart(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStart"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->start(); +} + +void JNICALL nativeStop(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStop"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->stop(); +} + +void JNICALL nativePause(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativePause"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->pause(); +} + +void JNICALL nativeResume(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResume"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->resume(); +} + +void JNICALL nativeRun(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRun"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().run(); +} + +void JNICALL nativeRerender(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRerender"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().rerender(); +} + +void JNICALL nativeUpdate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeUpdate"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().update(); +} + +void JNICALL nativeTerminate(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeTerminate"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().terminate(); +} + +jboolean JNICALL nativeNeedsSwap(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeNeedsSwap"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().needsSwap(); +} + +void JNICALL nativeSwapped(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSwapped"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().swapped(); +} + +void JNICALL nativeResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height, + jfloat ratio) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResize"); + assert(nativeMapViewPtr != 0); + assert(width >= 0); + assert(height >= 0); + assert(width <= UINT16_MAX); + assert(height <= UINT16_MAX); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().resize(width, height, ratio); +} + +void JNICALL nativeResize(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jint width, jint height, + jfloat ratio, jint fbWidth, jint fbHeight) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResize"); + assert(nativeMapViewPtr != 0); + assert(width >= 0); + assert(height >= 0); + assert(width <= UINT16_MAX); + assert(height <= UINT16_MAX); + assert(fbWidth >= 0); + assert(fbHeight >= 0); + assert(fbWidth <= UINT16_MAX); + assert(fbHeight <= UINT16_MAX); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().resize(width, height, ratio, fbWidth, fbHeight); +} + +void JNICALL nativeRemoveClass(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring clazz) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRemoveClass"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().removeClass(std_string_from_jstring(env, clazz)); +} + +jboolean JNICALL nativeHasClass(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring clazz) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeHasClass"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().hasClass(std_string_from_jstring(env, clazz)); +} + +void JNICALL nativeAddClass(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring clazz) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeAddClass"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().addClass(std_string_from_jstring(env, clazz)); +} + +void JNICALL +nativeSetClasses(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject classes) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetClasses"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setClasses(std_vector_string_from_jobject(env, classes)); +} + +jobject JNICALL nativeGetClasses(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetClasses"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return std_vector_string_to_jobject(env, nativeMapView->getMap().getClasses()); +} + +void JNICALL nativeSetDefaultTransitionDuration(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, + jlong milliseconds) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetDefaultTransitionDuration"); + assert(nativeMapViewPtr != 0); + assert(milliseconds >= 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setDefaultTransitionDuration(milliseconds); +} + +jlong JNICALL nativeGetDefaultTransitionDuration(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetDefaultTransitionDuration"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getDefaultTransitionDuration(); +} + +void JNICALL nativeSetStyleURL(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring url) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetStyleURL"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setStyleURL(std_string_from_jstring(env, url)); +} + +void JNICALL nativeSetStyleJSON(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, + jstring newStyleJson, jstring base) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetStyleJSON"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setStyleJSON(std_string_from_jstring(env, newStyleJson), + std_string_from_jstring(env, base)); +} + +jstring JNICALL nativeGetStyleJSON(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetStyleJSON"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return std_string_to_jstring(env, nativeMapView->getMap().getStyleJSON()); +} + +void JNICALL +nativeSetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jstring accessToken) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetAccessToken"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getFileSource().setAccessToken(std_string_from_jstring(env, accessToken)); +} + +jstring JNICALL nativeGetAccessToken(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetAccessToken"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return std_string_to_jstring(env, nativeMapView->getFileSource().getAccessToken()); +} + +void JNICALL nativeCancelTransitions(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeCancelTransitions"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().cancelTransitions(); +} + +void JNICALL nativeMoveBy(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble dx, jdouble dy, + jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeMoveBy"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().moveBy(dx, dy, duration); +} + +void JNICALL nativeSetLonLat(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jobject lonLat, + jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetLonLat"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + double lon = env->GetDoubleField(lonLat, lonLatLonId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + double lat = env->GetDoubleField(lonLat, lonLatLatId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + nativeMapView->getMap().setLonLat(lon, lat, duration); +} + +jobject JNICALL nativeGetLonLat(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetLonLat"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + double lon = 0.0, lat = 0.0; + nativeMapView->getMap().getLonLat(lon, lat); + + jobject ret = env->NewObject(lonLatClass, lonLatConstructorId, lon, lat); + if (ret == nullptr) { + env->ExceptionDescribe(); + return nullptr; + } + + return ret; +} + +void JNICALL nativeStartPanning(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartPanning"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().startPanning(); +} + +void JNICALL nativeStopPanning(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopPanning"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().stopPanning(); +} + +void JNICALL nativeResetPosition(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetPosition"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().resetPosition(); +} + +void JNICALL nativeScaleBy(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble ds, jdouble cx, + jdouble cy, jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeScaleBy"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().scaleBy(ds, cx, cy, duration); +} + +void JNICALL nativeSetScale(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble scale, + jdouble cx, jdouble cy, jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetScale"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setScale(scale, cx, cy, duration); +} + +jdouble JNICALL nativeGetScale(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetScale"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getScale(); +} + +void JNICALL +nativeSetZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble zoom, jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setZoom(zoom, duration); +} + +jdouble JNICALL nativeGetZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getZoom(); +} + +void JNICALL nativeSetLonLatZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, + jobject lonLatZoom, jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetLonLatZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + + double lon = env->GetDoubleField(lonLatZoom, lonLatZoomLonId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + double lat = env->GetDoubleField(lonLatZoom, lonLatZoomLatId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + double zoom = env->GetDoubleField(lonLatZoom, lonLatZoomZoomId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + return; + } + + nativeMapView->getMap().setLonLatZoom(lon, lat, zoom, duration); +} + +jobject JNICALL nativeGetLonLatZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetLonLatZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + double lon = 0.0, lat = 0.0, zoom = 0.0; + nativeMapView->getMap().getLonLatZoom(lon, lat, zoom); + + jobject ret = env->NewObject(lonLatZoomClass, lonLatZoomConstructorId, lon, lat, zoom); + if (ret == nullptr) { + env->ExceptionDescribe(); + return nullptr; + } + + return ret; +} + +void JNICALL nativeResetZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().resetZoom(); +} + +void JNICALL nativeStartScaling(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartScaling"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().startScaling(); +} + +void JNICALL nativeStopScaling(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopScaling"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().stopScaling(); +} + +jdouble JNICALL nativeGetMinZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetMinZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getMinZoom(); +} + +jdouble JNICALL nativeGetMaxZoom(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetMaxZoom"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getMaxZoom(); +} + +void JNICALL nativeRotateBy(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble sx, + jdouble sy, jdouble ex, jdouble ey, jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeRotateBy"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().rotateBy(sx, sy, ex, ey, duration); +} + +void JNICALL nativeSetBearing(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble degrees, + jdouble duration) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetBearing"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setBearing(degrees, duration); +} + +void JNICALL nativeSetBearing(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jdouble degrees, + jdouble cx, jdouble cy) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetBearing"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setBearing(degrees, cx, cy); +} + +jdouble JNICALL nativeGetBearing(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetBearing"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getBearing(); +} + +void JNICALL nativeResetNorth(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeResetNorth"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().resetNorth(); +} + +void JNICALL nativeStartRotating(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStartRotating"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().startRotating(); +} + +void JNICALL nativeStopRotating(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeStopRotating"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().stopRotating(); +} + +void JNICALL nativeSetDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean debug) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().setDebug(debug); + nativeMapView->enableFps(debug); +} + +void JNICALL nativeToggleDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeToggleDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getMap().toggleDebug(); + nativeMapView->enableFps(nativeMapView->getMap().getDebug()); +} + +jboolean JNICALL nativeGetDebug(JNIEnv *env, jobject obj, jlong nativeMapViewPtr) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeGetDebug"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + return nativeMapView->getMap().getDebug(); +} + +void JNICALL +nativeSetReachability(JNIEnv *env, jobject obj, jlong nativeMapViewPtr, jboolean status) { + mbgl::Log::Debug(mbgl::Event::JNI, "nativeSetReachability"); + assert(nativeMapViewPtr != 0); + NativeMapView *nativeMapView = reinterpret_cast(nativeMapViewPtr); + nativeMapView->getFileSource().setReachability(status); +} +} + +extern "C" { + +extern "C" JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { + mbgl::Log::Set(); + + mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnLoad"); + + JNIEnv *env = nullptr; + jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + return JNI_ERR; + } + + lonLatClass = env->FindClass("com/mapbox/mapboxgl/lib/LonLat"); + if (lonLatClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatConstructorId = env->GetMethodID(lonLatClass, "", "(DD)V"); + if (lonLatConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatLonId = env->GetFieldID(lonLatClass, "lon", "D"); + if (lonLatLonId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatLatId = env->GetFieldID(lonLatClass, "lat", "D"); + if (lonLatLatId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomClass = env->FindClass("com/mapbox/mapboxgl/lib/LonLatZoom"); + if (lonLatClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomConstructorId = env->GetMethodID(lonLatZoomClass, "", "(DDD)V"); + if (lonLatZoomConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomLonId = env->GetFieldID(lonLatZoomClass, "lon", "D"); + if (lonLatZoomLonId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomLatId = env->GetFieldID(lonLatZoomClass, "lat", "D"); + if (lonLatZoomLatId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomZoomId = env->GetFieldID(lonLatZoomClass, "zoom", "D"); + if (lonLatZoomZoomId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + jclass nativeMapViewClass = env->FindClass("com/mapbox/mapboxgl/lib/NativeMapView"); + if (nativeMapViewClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + onMapChangedId = env->GetMethodID(nativeMapViewClass, "onMapChanged", "()V"); + if (onMapChangedId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + onFpsChangedId = env->GetMethodID(nativeMapViewClass, "onFpsChanged", "(D)V"); + if (onFpsChangedId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + runtimeExceptionClass = env->FindClass("java/lang/RuntimeException"); + if (runtimeExceptionClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + nullPointerExceptionClass = env->FindClass("java/lang/NullPointerException"); + if (nullPointerExceptionClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + jclass listClass = env->FindClass("java/util/List"); + if (listClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + listToArrayId = env->GetMethodID(listClass, "toArray", "()[Ljava/lang/Object;"); + if (listToArrayId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + arrayListClass = env->FindClass("java/util/ArrayList"); + if (arrayListClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + arrayListConstructorId = env->GetMethodID(arrayListClass, "", "()V"); + if (arrayListConstructorId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + arrayListAddId = env->GetMethodID(arrayListClass, "add", "(Ljava/lang/Object;)Z"); + if (arrayListAddId == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + // NOTE: if you get java.lang.UnsatisfiedLinkError you likely forgot to set the size of the + // array correctly (too large) + std::array methods = {{ // Can remove the extra brace in C++14 + {"nativeCreate", "(Ljava/lang/String;Ljava/lang/String;Ljava/lang/String;)J", + reinterpret_cast(&nativeCreate)}, + {"nativeDestroy", "(J)V", reinterpret_cast(&nativeDestroy)}, + {"nativeInitializeDisplay", "(J)V", reinterpret_cast(&nativeInitializeDisplay)}, + {"nativeTerminateDisplay", "(J)V", reinterpret_cast(&nativeTerminateDisplay)}, + {"nativeInitializeContext", "(J)V", reinterpret_cast(&nativeInitializeContext)}, + {"nativeTerminateContext", "(J)V", reinterpret_cast(&nativeTerminateContext)}, + {"nativeCreateSurface", "(JLandroid/view/Surface;)V", + reinterpret_cast(&nativeCreateSurface)}, + {"nativeDestroySurface", "(J)V", reinterpret_cast(&nativeDestroySurface)}, + {"nativeStart", "(J)V", reinterpret_cast(&nativeStart)}, + {"nativeStop", "(J)V", reinterpret_cast(&nativeStop)}, + {"nativePause", "(J)V", reinterpret_cast(&nativePause)}, + {"nativeResume", "(J)V", reinterpret_cast(&nativeResume)}, + {"nativeRun", "(J)V", reinterpret_cast(&nativeRun)}, + {"nativeRerender", "(J)V", reinterpret_cast(&nativeRerender)}, + {"nativeUpdate", "(J)V", reinterpret_cast(&nativeUpdate)}, + {"nativeTerminate", "(J)V", reinterpret_cast(&nativeTerminate)}, + {"nativeNeedsSwap", "(J)Z", reinterpret_cast(&nativeNeedsSwap)}, + {"nativeSwapped", "(J)V", reinterpret_cast(&nativeSwapped)}, + {"nativeResize", "(JIIF)V", + reinterpret_cast( + static_cast( + &nativeResize))}, + {"nativeResize", "(JIIFII)V", + reinterpret_cast(static_cast(&nativeResize))}, + {"nativeAddClass", "(JLjava/lang/String;)V", + reinterpret_cast(&nativeAddClass)}, + {"nativeRemoveClass", "(JLjava/lang/String;)V", + reinterpret_cast(&nativeRemoveClass)}, + {"nativeHasClass", "(JLjava/lang/String;)Z", + reinterpret_cast(&nativeHasClass)}, + {"nativeSetClasses", "(JLjava/util/List;)V", + reinterpret_cast(&nativeSetClasses)}, + {"nativeGetClasses", "(J)Ljava/util/List;", + reinterpret_cast(&nativeGetClasses)}, + {"nativeSetDefaultTransitionDuration", "(JJ)V", + reinterpret_cast(&nativeSetDefaultTransitionDuration)}, + {"nativeGetDefaultTransitionDuration", "(J)J", + reinterpret_cast(&nativeGetDefaultTransitionDuration)}, + {"nativeSetStyleUrl", "(JLjava/lang/String;)V", + reinterpret_cast(&nativeSetStyleURL)}, + {"nativeSetStyleJson", "(JLjava/lang/String;Ljava/lang/String;)V", + reinterpret_cast(&nativeSetStyleJSON)}, + {"nativeGetStyleJson", "(J)Ljava/lang/String;", + reinterpret_cast(&nativeGetStyleJSON)}, + {"nativeSetAccessToken", "(JLjava/lang/String;)V", + reinterpret_cast(&nativeSetAccessToken)}, + {"nativeGetAccessToken", "(J)Ljava/lang/String;", + reinterpret_cast(&nativeGetAccessToken)}, + {"nativeCancelTransitions", "(J)V", reinterpret_cast(&nativeCancelTransitions)}, + {"nativeMoveBy", "(JDDD)V", reinterpret_cast(&nativeMoveBy)}, + {"nativeSetLonLat", "(JLcom/mapbox/mapboxgl/lib/LonLat;D)V", + reinterpret_cast(&nativeSetLonLat)}, + {"nativeGetLonLat", "(J)Lcom/mapbox/mapboxgl/lib/LonLat;", + reinterpret_cast(&nativeGetLonLat)}, + {"nativeStartPanning", "(J)V", reinterpret_cast(&nativeStartPanning)}, + {"nativeStopPanning", "(J)V", reinterpret_cast(&nativeStopPanning)}, + {"nativeResetPosition", "(J)V", reinterpret_cast(&nativeResetPosition)}, + {"nativeScaleBy", "(JDDDD)V", reinterpret_cast(&nativeScaleBy)}, + {"nativeSetScale", "(JDDDD)V", reinterpret_cast(&nativeSetScale)}, + {"nativeGetScale", "(J)D", reinterpret_cast(&nativeGetScale)}, + {"nativeSetZoom", "(JDD)V", reinterpret_cast(&nativeSetZoom)}, + {"nativeGetZoom", "(J)D", reinterpret_cast(&nativeGetZoom)}, + {"nativeSetLonLatZoom", "(JLcom/mapbox/mapboxgl/lib/LonLatZoom;D)V", + reinterpret_cast(&nativeSetLonLatZoom)}, + {"nativeGetLonLatZoom", "(J)Lcom/mapbox/mapboxgl/lib/LonLatZoom;", + reinterpret_cast(&nativeGetLonLatZoom)}, + {"nativeResetZoom", "(J)V", reinterpret_cast(&nativeResetZoom)}, + {"nativeStartPanning", "(J)V", reinterpret_cast(&nativeStartScaling)}, + {"nativeStopPanning", "(J)V", reinterpret_cast(&nativeStopScaling)}, + {"nativeGetMinZoom", "(J)D", reinterpret_cast(&nativeGetMinZoom)}, + {"nativeGetMaxZoom", "(J)D", reinterpret_cast(&nativeGetMaxZoom)}, + {"nativeRotateBy", "(JDDDDD)V", reinterpret_cast(&nativeRotateBy)}, + {"nativeSetBearing", "(JDD)V", + reinterpret_cast( + static_cast( + &nativeSetBearing))}, + {"nativeSetBearing", "(JDDD)V", + reinterpret_cast( + static_cast( + &nativeSetBearing))}, + {"nativeGetBearing", "(J)D", reinterpret_cast(&nativeGetBearing)}, + {"nativeResetNorth", "(J)V", reinterpret_cast(&nativeResetNorth)}, + {"nativeStartRotating", "(J)V", reinterpret_cast(&nativeStartRotating)}, + {"nativeStopRotating", "(J)V", reinterpret_cast(&nativeStopRotating)}, + {"nativeSetDebug", "(JZ)V", reinterpret_cast(&nativeSetDebug)}, + {"nativeToggleDebug", "(J)V", reinterpret_cast(&nativeToggleDebug)}, + {"nativeGetDebug", "(J)Z", reinterpret_cast(&nativeGetDebug)}, + {"nativeSetReachability", "(JZ)V", reinterpret_cast(&nativeSetReachability)}}}; + + if (env->RegisterNatives(nativeMapViewClass, methods.data(), methods.size()) < 0) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatClass = reinterpret_cast(env->NewGlobalRef(lonLatClass)); + if (lonLatClass == nullptr) { + env->ExceptionDescribe(); + return JNI_ERR; + } + + lonLatZoomClass = reinterpret_cast(env->NewGlobalRef(lonLatZoomClass)); + if (lonLatZoomClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(lonLatClass); + return JNI_ERR; + } + + runtimeExceptionClass = reinterpret_cast(env->NewGlobalRef(runtimeExceptionClass)); + if (runtimeExceptionClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(lonLatClass); + env->DeleteGlobalRef(lonLatZoomClass); + return JNI_ERR; + } + + nullPointerExceptionClass = + reinterpret_cast(env->NewGlobalRef(nullPointerExceptionClass)); + if (nullPointerExceptionClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(lonLatClass); + env->DeleteGlobalRef(lonLatZoomClass); + env->DeleteGlobalRef(runtimeExceptionClass); + return JNI_ERR; + } + + arrayListClass = reinterpret_cast(env->NewGlobalRef(arrayListClass)); + if (arrayListClass == nullptr) { + env->ExceptionDescribe(); + env->DeleteGlobalRef(lonLatClass); + env->DeleteGlobalRef(lonLatZoomClass); + env->DeleteGlobalRef(runtimeExceptionClass); + env->DeleteGlobalRef(nullPointerExceptionClass); + return JNI_ERR; + } + + char release[PROP_VALUE_MAX] = ""; + __system_property_get("ro.build.version.release", release); + androidRelease = std::string(release); + + return JNI_VERSION_1_6; +} + +extern "C" JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { + mbgl::Log::Debug(mbgl::Event::JNI, "JNI_OnUnload"); + + JNIEnv *env = nullptr; + jint ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } + + env->DeleteGlobalRef(lonLatClass); + lonLatClass = nullptr; + lonLatConstructorId = nullptr; + lonLatLonId = nullptr; + lonLatLatId = nullptr; + + env->DeleteGlobalRef(lonLatZoomClass); + lonLatZoomClass = nullptr; + lonLatZoomConstructorId = nullptr; + lonLatZoomLonId = nullptr; + lonLatZoomLatId = nullptr; + lonLatZoomZoomId = nullptr; + + onMapChangedId = nullptr; + onFpsChangedId = nullptr; + + env->DeleteGlobalRef(runtimeExceptionClass); + runtimeExceptionClass = nullptr; + + env->DeleteGlobalRef(nullPointerExceptionClass); + nullPointerExceptionClass = nullptr; + + listToArrayId = nullptr; + + env->DeleteGlobalRef(arrayListClass); + arrayListClass = nullptr; + arrayListConstructorId = nullptr; + arrayListAddId = nullptr; +} +} diff --git a/android/cpp/native_map_view.cpp b/android/cpp/native_map_view.cpp new file mode 100644 index 000000000..e177fd497 --- /dev/null +++ b/android/cpp/native_map_view.cpp @@ -0,0 +1,826 @@ +#include +#include +#include +#include +#include + +#include + +#include + +#include +#include +#include +#include +#include +#include +#include + +namespace mbgl { +namespace android { + +void log_egl_string(EGLDisplay display, EGLint name, const char *label) { + const char *str = eglQueryString(display, name); + if (str == nullptr) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglQueryString(%d) returned error %d", name, + eglGetError()); + throw new std::runtime_error("eglQueryString() failed"); + } else { + char buf[513]; + for (int len = std::strlen(str), pos = 0; len > 0; len -= 512, pos += 512) { + strncpy(buf, str + pos, 512); + buf[512] = 0; + mbgl::Log::Info(mbgl::Event::OpenGL, "EGL %s: %s", label, buf); + } + } +} + +void log_gl_string(GLenum name, const char *label) { + const GLubyte *str = glGetString(name); + if (str == nullptr) { + mbgl::Log::Error(mbgl::Event::OpenGL, "glGetString(%d) returned error %d", name, + glGetError()); + throw new std::runtime_error("glGetString() failed"); + } else { + char buf[513]; + for (int len = std::strlen(reinterpret_cast(str)), pos = 0; len > 0; + len -= 512, pos += 512) { + strncpy(buf, reinterpret_cast(str) + pos, 512); + buf[512] = 0; + mbgl::Log::Info(mbgl::Event::OpenGL, "GL %s: %s", label, buf); + } + } +} + +NativeMapView::NativeMapView(JNIEnv *env, jobject obj_) + : mbgl::View(*this), + fileSource(mbgl::platform::defaultCacheDatabase()), + map(*this, fileSource) { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::NativeMapView"); + + assert(env != nullptr); + assert(obj_ != nullptr); + + if (env->GetJavaVM(&vm) < 0) { + env->ExceptionDescribe(); + return; + } + + obj = env->NewGlobalRef(obj_); + if (obj == nullptr) { + env->ExceptionDescribe(); + return; + } +} + +NativeMapView::~NativeMapView() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::~NativeMapView"); + terminateContext(); + destroySurface(); + terminateDisplay(); + + assert(vm != nullptr); + assert(obj != nullptr); + + jint ret; + JNIEnv *env = nullptr; + ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (ret == JNI_OK) { + env->DeleteGlobalRef(obj); + } else { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } + obj = nullptr; + vm = nullptr; +} + +void NativeMapView::activate() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::activate"); + if ((display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE) && (context != EGL_NO_CONTEXT)) { + if (!eglMakeCurrent(display, surface, surface, context)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", + eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } + } else { + mbgl::Log::Info(mbgl::Event::Android, "Not activating as we are not ready"); + } +} + +void NativeMapView::deactivate() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::deactivate"); + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", + eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } +} + +void NativeMapView::swap() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::swap"); + + if (map.needsSwap() && (display != EGL_NO_DISPLAY) && (surface != EGL_NO_SURFACE)) { + if (!eglSwapBuffers(display, surface)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglSwapBuffers() returned error %d", + eglGetError()); + throw new std::runtime_error("eglSwapBuffers() failed"); + } + map.swapped(); + updateFps(); + } else { + mbgl::Log::Info(mbgl::Event::Android, "Not swapping as we are not ready"); + } +} + +void NativeMapView::notify() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notify()"); + // noop +} + +mbgl::Map &NativeMapView::getMap() { return map; } + +mbgl::CachingHTTPFileSource &NativeMapView::getFileSource() { return fileSource; } + +bool NativeMapView::inEmulator() { + // Detect if we are in emulator + char prop[PROP_VALUE_MAX]; + __system_property_get("ro.kernel.qemu", prop); + return strtol(prop, nullptr, 0) == 1; +} + +void NativeMapView::initializeDisplay() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::initializeDisplay"); + + assert(display == EGL_NO_DISPLAY); + assert(config == nullptr); + assert(format < 0); + + display = eglGetDisplay(EGL_DEFAULT_DISPLAY); + if (display == EGL_NO_DISPLAY) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetDisplay() returned error %d", eglGetError()); + throw new std::runtime_error("eglGetDisplay() failed"); + } + + EGLint major, minor; + if (!eglInitialize(display, &major, &minor)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglInitialize() returned error %d", eglGetError()); + throw new std::runtime_error("eglInitialize() failed"); + } + if ((major <= 1) && (minor < 3)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "EGL version is too low, need 1.3, got %d.%d", major, + minor); + throw new std::runtime_error("EGL version is too low"); + } + + log_egl_string(display, EGL_VENDOR, "Vendor"); + log_egl_string(display, EGL_VERSION, "Version"); + log_egl_string(display, EGL_CLIENT_APIS, "Client APIs"); + log_egl_string(display, EGL_EXTENSIONS, "Client Extensions"); + + // Detect if we are in emulator + if (inEmulator()) { + mbgl::Log::Warning(mbgl::Event::Android, "In emulator! Enabling hacks :-("); + } + + // Get all configs at least RGB 565 with 16 depth and 8 stencil + EGLint configAttribs[] = { + EGL_CONFIG_CAVEAT, EGL_NONE, EGL_RENDERABLE_TYPE, + EGL_OPENGL_ES2_BIT, EGL_SURFACE_TYPE, EGL_WINDOW_BIT, + EGL_BUFFER_SIZE, 16, EGL_RED_SIZE, + 5, EGL_GREEN_SIZE, 6, + EGL_BLUE_SIZE, 5, EGL_DEPTH_SIZE, + 16, EGL_STENCIL_SIZE, 8, + (inEmulator() ? EGL_NONE : EGL_CONFORMANT), EGL_OPENGL_ES2_BIT, // Ugly hack + (inEmulator() ? EGL_NONE : EGL_COLOR_BUFFER_TYPE), EGL_RGB_BUFFER, // Ugly hack + EGL_NONE}; + EGLint numConfigs; + if (!eglChooseConfig(display, configAttribs, nullptr, 0, &numConfigs)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig(NULL) returned error %d", + eglGetError()); + throw new std::runtime_error("eglChooseConfig() failed"); + } + if (numConfigs < 1) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned no configs."); + throw new std::runtime_error("eglChooseConfig() failed"); + } + + const std::unique_ptr configs = mbgl::util::make_unique(numConfigs); + if (!eglChooseConfig(display, configAttribs, configs.get(), numConfigs, &numConfigs)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglChooseConfig() returned error %d", eglGetError()); + throw new std::runtime_error("eglChooseConfig() failed"); + } + + config = chooseConfig(configs.get(), numConfigs); + if (config == nullptr) { + mbgl::Log::Error(mbgl::Event::OpenGL, "No config chosen"); + throw new std::runtime_error("No config chosen"); + } + + if (!eglGetConfigAttrib(display, config, EGL_NATIVE_VISUAL_ID, &format)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglGetConfigAttrib() returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + mbgl::Log::Info(mbgl::Event::OpenGL, "Chosen window format is %d", format); +} + +void NativeMapView::terminateDisplay() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::terminateDisplay"); + + if (display != EGL_NO_DISPLAY) { + // Destroy the surface first, if it still exists. This call needs a valid surface. + if (surface != EGL_NO_SURFACE) { + if (!eglDestroySurface(display, surface)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", + eglGetError()); + throw new std::runtime_error("eglDestroySurface() failed"); + } + surface = EGL_NO_SURFACE; + } + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } + + if (!eglTerminate(display)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglTerminate() returned error %d", + eglGetError()); + throw new std::runtime_error("eglTerminate() failed"); + } + } + + display = EGL_NO_DISPLAY; + config = nullptr; + format = -1; +} + +void NativeMapView::initializeContext() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::initializeContext"); + + assert(display != EGL_NO_DISPLAY); + assert(context == EGL_NO_CONTEXT); + assert(config != nullptr); + + const EGLint contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE}; + context = eglCreateContext(display, config, EGL_NO_CONTEXT, contextAttribs); + if (context == EGL_NO_CONTEXT) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateContext() returned error %d", + eglGetError()); + throw new std::runtime_error("eglCreateContext() failed"); + } +} + +void NativeMapView::terminateContext() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::terminateContext"); + if (display != EGL_NO_DISPLAY) { + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } + + if (context != EGL_NO_CONTEXT) { + if (!eglDestroyContext(display, context)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroyContext() returned error %d", + eglGetError()); + throw new std::runtime_error("eglDestroyContext() failed"); + } + } + } + + context = EGL_NO_CONTEXT; +} + +void NativeMapView::createSurface(ANativeWindow *window_) { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::createSurface"); + + assert(window == nullptr); + assert(window_ != nullptr); + window = window_; + + assert(display != EGL_NO_DISPLAY); + assert(surface == EGL_NO_SURFACE); + assert(config != nullptr); + assert(format >= 0); + + ANativeWindow_setBuffersGeometry(window, 0, 0, format); + + const EGLint surfaceAttribs[] = {EGL_NONE}; + surface = eglCreateWindowSurface(display, config, window, surfaceAttribs); + if (surface == EGL_NO_SURFACE) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglCreateWindowSurface() returned error %d", + eglGetError()); + throw new std::runtime_error("eglCreateWindowSurface() failed"); + } + + if (!firstTime) { + firstTime = true; + + if (!eglMakeCurrent(display, surface, surface, context)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglMakeCurrent() returned error %d", + eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } + + log_gl_string(GL_VENDOR, "Vendor"); + log_gl_string(GL_RENDERER, "Renderer"); + log_gl_string(GL_VERSION, "Version"); + if (!inEmulator()) { + log_gl_string(GL_SHADING_LANGUAGE_VERSION, + "SL Version"); // In the emulator this returns NULL with error code 0? + // https://code.google.com/p/android/issues/detail?id=78977 + } + log_gl_string(GL_EXTENSIONS, "Extensions"); + + loadExtensions(); + + if (!eglMakeCurrent(display, EGL_NO_SURFACE, EGL_NO_SURFACE, EGL_NO_CONTEXT)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglMakeCurrent(EGL_NO_CONTEXT) returned error %d", eglGetError()); + throw new std::runtime_error("eglMakeCurrent() failed"); + } + } + + resume(); +} + +void NativeMapView::destroySurface() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::destroySurface"); + + pause(true); + + if (surface != EGL_NO_SURFACE) { + if (!eglDestroySurface(display, surface)) { + mbgl::Log::Error(mbgl::Event::OpenGL, "eglDestroySurface() returned error %d", + eglGetError()); + throw new std::runtime_error("eglDestroySurface() failed"); + } + } + + surface = EGL_NO_SURFACE; + + if (window != nullptr) { + ANativeWindow_release(window); + window = nullptr; + } +} + +// Speed +/* +typedef enum { + Format16Bit = 0, + Format32BitNoAlpha = 1, + Format32BitAlpha = 2, + Format24Bit = 3, + Unknown = 4 +} BufferFormat; + +typedef enum { + Format16Depth8Stencil = 0, + Format24Depth8Stencil = 1, +} DepthStencilFormat; +*/ + +// Quality +typedef enum { + Format16Bit = 3, + Format32BitNoAlpha = 1, + Format32BitAlpha = 2, + Format24Bit = 0, + Unknown = 4 +} BufferFormat; + +typedef enum { + Format16Depth8Stencil = 1, + Format24Depth8Stencil = 0, +} DepthStencilFormat; + +// Tuple is +typedef std::tuple ConfigProperties; + +EGLConfig NativeMapView::chooseConfig(const EGLConfig configs[], EGLint numConfigs) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Found %d configs", numConfigs); + + // Create a list of configs that pass our filters + std::list configList; + for (int i = 0; i < numConfigs; i++) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Config %d:", i); + + EGLint caveat, conformant, bits, red, green, blue, alpha, alphaMask, depth, stencil, + sampleBuffers, samples; + + if (!eglGetConfigAttrib(display, configs[i], EGL_CONFIG_CAVEAT, &caveat)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_CONFIG_CAVEAT) returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_CONFORMANT, &conformant)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_CONFORMANT) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_BUFFER_SIZE, &bits)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_BUFFER_SIZE) returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_RED_SIZE, &red)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_RED_SIZE) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_GREEN_SIZE, &green)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_GREEN_SIZE) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_BLUE_SIZE, &blue)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_BLUE_SIZE) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_SIZE, &alpha)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_ALPHA_SIZE) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_ALPHA_MASK_SIZE, &alphaMask)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_ALPHA_MASK_SIZE) returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_DEPTH_SIZE, &depth)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_DEPTH_SIZE) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_STENCIL_SIZE, &stencil)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_STENCIL_SIZE) returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLE_BUFFERS, &sampleBuffers)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_SAMPLE_BUFFERS) returned error %d", + eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + if (!eglGetConfigAttrib(display, configs[i], EGL_SAMPLES, &samples)) { + mbgl::Log::Error(mbgl::Event::OpenGL, + "eglGetConfigAttrib(EGL_SAMPLES) returned error %d", eglGetError()); + throw new std::runtime_error("eglGetConfigAttrib() failed"); + } + + mbgl::Log::Info(mbgl::Event::OpenGL, "...Caveat: %d", caveat); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Conformant: %d", conformant); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Color: %d", bits); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Red: %d", red); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Green: %d", green); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Blue: %d", blue); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Alpha: %d", alpha); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Alpha mask: %d", alphaMask); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Depth: %d", depth); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Stencil: %d", stencil); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Sample buffers: %d", sampleBuffers); + mbgl::Log::Info(mbgl::Event::OpenGL, "...Samples: %d", samples); + + bool configOk = true; + configOk &= (depth == 24) || (depth == 16); + configOk &= stencil == 8; + configOk &= sampleBuffers == 0; + configOk &= samples == 0; + + // Filter our configs first for depth, stencil and anti-aliasing + if (configOk) { + // Work out the config's buffer format + BufferFormat bufferFormat; + if ((bits == 16) && (red == 5) && (green == 6) && (blue == 5) && (alpha == 0)) { + bufferFormat = Format16Bit; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = Format32BitNoAlpha; + } else if ((bits == 32) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 8)) { + bufferFormat = Format32BitAlpha; + } else if ((bits == 24) && (red == 8) && (green == 8) && (blue == 8) && (alpha == 0)) { + bufferFormat = Format24Bit; + } else { + bufferFormat = Unknown; + } + + // Work out the config's depth stencil format + DepthStencilFormat depthStencilFormat; + if ((depth == 16) && (stencil == 8)) { + depthStencilFormat = Format16Depth8Stencil; + } else { + depthStencilFormat = Format24Depth8Stencil; + } + + bool isNotConformant = (conformant & EGL_OPENGL_ES2_BIT) != EGL_OPENGL_ES2_BIT; + bool isCaveat = caveat != EGL_NONE; + EGLConfig configId = configs[i]; + + // Ignore formats we don't recognise + if (bufferFormat != Unknown) { + configList.push_back(std::make_tuple(bufferFormat, depthStencilFormat, + isNotConformant, isCaveat, i, configId)); + } + } + } + + if (configList.empty()) { + mbgl::Log::Error(mbgl::Event::OpenGL, "Config list was empty."); + } + + // Sort the configs to find the best one + configList.sort(); + usingDepth24 = std::get<1>(configList.front()) == Format24Depth8Stencil; + bool isConformant = !std::get<2>(configList.front()); + bool isCaveat = std::get<3>(configList.front()); + int configNum = std::get<4>(configList.front()); + EGLConfig configId = std::get<5>(configList.front()); + + mbgl::Log::Info(mbgl::Event::OpenGL, "Chosen config is %d", configNum); + + if (isCaveat) { + mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config has a caveat."); + } + if (!isConformant) { + mbgl::Log::Warning(mbgl::Event::OpenGL, "Chosen config is not conformant."); + } + + return configId; +} + +void NativeMapView::start() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::start"); + + if (display == EGL_NO_DISPLAY) { + initializeDisplay(); + } + + if (context == EGL_NO_CONTEXT) { + initializeContext(); + } + + assert(display != EGL_NO_DISPLAY); + assert(context != EGL_NO_CONTEXT); + + map.start(true); +} + +void NativeMapView::loadExtensions() { + const GLubyte *str = glGetString(GL_EXTENSIONS); + if (str == nullptr) { + mbgl::Log::Error(mbgl::Event::OpenGL, "glGetString(GL_EXTENSIONS) returned error %d", + glGetError()); + return; + } + + std::string extensions(reinterpret_cast(str)); + + if (extensions.find("GL_OES_vertex_array_object") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_OES_vertex_array_object."); + gl::BindVertexArray = reinterpret_cast( + eglGetProcAddress("glBindVertexArrayOES")); + gl::DeleteVertexArrays = reinterpret_cast( + eglGetProcAddress("glDeleteVertexArraysOES")); + gl::GenVertexArrays = reinterpret_cast( + eglGetProcAddress("glGenVertexArraysOES")); + gl::IsVertexArray = + reinterpret_cast(eglGetProcAddress("glIsVertexArrayOES")); + assert(gl::BindVertexArray != nullptr); + assert(gl::DeleteVertexArrays != nullptr); + assert(gl::GenVertexArrays != nullptr); + assert(gl::IsVertexArray != nullptr); + } + + if (extensions.find("GL_OES_packed_depth_stencil") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_OES_packed_depth_stencil."); + gl::isPackedDepthStencilSupported = true; + } + + if (extensions.find("GL_OES_depth24") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_OES_depth24."); + if (usingDepth24) { + gl::isDepth24Supported = true; + } else { + mbgl::Log::Info(mbgl::Event::OpenGL, "Preferring 16 bit depth."); + } + } + + if (extensions.find("GL_KHR_debug") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_KHR_debug."); + gl::DebugMessageControl = reinterpret_cast( + eglGetProcAddress("glDebugMessageControl")); + gl::DebugMessageInsert = reinterpret_cast( + eglGetProcAddress("glDebugMessageInsert")); + gl::DebugMessageCallback = reinterpret_cast( + eglGetProcAddress("glDebugMessageCallback")); + gl::GetDebugMessageLog = reinterpret_cast( + eglGetProcAddress("glGetDebugMessageLog")); + gl::GetPointerv = + reinterpret_cast(eglGetProcAddress("glGetPointerv")); + gl::PushDebugGroup = + reinterpret_cast(eglGetProcAddress("glPushDebugGroup")); + gl::PopDebugGroup = + reinterpret_cast(eglGetProcAddress("glPopDebugGroup")); + gl::ObjectLabel = + reinterpret_cast(eglGetProcAddress("glObjectLabel")); + gl::GetObjectLabel = + reinterpret_cast(eglGetProcAddress("glGetObjectLabel")); + gl::ObjectPtrLabel = + reinterpret_cast(eglGetProcAddress("glObjectPtrLabel")); + gl::GetObjectPtrLabel = reinterpret_cast( + eglGetProcAddress("glGetObjectPtrLabel")); + assert(gl::DebugMessageControl != nullptr); + assert(gl::DebugMessageInsert != nullptr); + assert(gl::DebugMessageCallback != nullptr); + assert(gl::GetDebugMessageLog != nullptr); + assert(gl::GetPointerv != nullptr); + assert(gl::PushDebugGroup != nullptr); + assert(gl::PopDebugGroup != nullptr); + assert(gl::ObjectLabel != nullptr); + assert(gl::GetObjectLabel != nullptr); + assert(gl::ObjectPtrLabel != nullptr); + assert(gl::GetObjectPtrLabel != nullptr); + } else { + if (extensions.find("GL_EXT_debug_marker") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_EXT_debug_marker."); + gl::InsertEventMarkerEXT = reinterpret_cast( + eglGetProcAddress("glInsertEventMarkerEXT")); + gl::PushGroupMarkerEXT = reinterpret_cast( + eglGetProcAddress("glPushGroupMarkerEXT")); + gl::PopGroupMarkerEXT = reinterpret_cast( + eglGetProcAddress("glPopGroupMarkerEXT")); + assert(gl::InsertEventMarkerEXT != nullptr); + assert(gl::PushGroupMarkerEXT != nullptr); + assert(gl::PopGroupMarkerEXT != nullptr); + } + + if (extensions.find("GL_EXT_debug_label") != std::string::npos) { + mbgl::Log::Info(mbgl::Event::OpenGL, "Using GL_EXT_debug_label."); + gl::LabelObjectEXT = reinterpret_cast( + eglGetProcAddress("glLabelObjectEXT")); + gl::GetObjectLabelEXT = reinterpret_cast( + eglGetProcAddress("glGetObjectLabelEXT")); + assert(gl::LabelObjectEXT != nullptr); + assert(gl::GetObjectLabelEXT != nullptr); + } + } +} + +void NativeMapView::stop() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::stop"); + + if ((display != EGL_NO_DISPLAY) && (context != EGL_NO_CONTEXT)) { + map.stop(); + } +} + +void NativeMapView::pause(bool waitForPause) { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::pause %s", + (waitForPause) ? "true" : "false"); + + if ((display != EGL_NO_DISPLAY) && (context != EGL_NO_CONTEXT)) { + map.pause(waitForPause); + } +} + +void NativeMapView::resume() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::resume"); + + assert(display != EGL_NO_DISPLAY); + assert(context != EGL_NO_CONTEXT); + + if (surface != EGL_NO_SURFACE) { + map.resume(); + } else { + mbgl::Log::Debug(mbgl::Event::Android, "Not resuming because we are not ready"); + } +} + +void NativeMapView::notifyMapChange(mbgl::MapChange, mbgl::timestamp) { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::notifyMapChange()"); + + assert(vm != nullptr); + assert(obj != nullptr); + + JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::notifyMapChange()", NULL}; + + jint ret; + JNIEnv *env = nullptr; + bool detach = false; + ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + if (ret != JNI_EDETACHED) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } else { + ret = vm->AttachCurrentThread(&env, &args); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); + throw new std::runtime_error("AttachCurrentThread() failed"); + } + detach = true; + } + } + + env->CallVoidMethod(obj, onMapChangedId); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + if (detach) { + if ((ret = vm->DetachCurrentThread()) != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); + throw new std::runtime_error("DetachCurrentThread() failed"); + } + } + env = nullptr; +} + +void NativeMapView::enableFps(bool enable) { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::enableFps()"); + + fpsEnabled = enable; +} + +void NativeMapView::updateFps() { + mbgl::Log::Debug(mbgl::Event::Android, "NativeMapView::updateFps()"); + + if (!fpsEnabled) { + return; + } + + static int frames = 0; + static int64_t timeElapsed = 0LL; + + frames++; + struct timespec now; + clock_gettime(CLOCK_MONOTONIC, &now); + int64_t currentTime = now.tv_sec * 1000000000LL + now.tv_nsec; + + if (currentTime - timeElapsed >= 1) { + fps = frames / ((currentTime - timeElapsed) / 1E9); + mbgl::Log::Debug(mbgl::Event::Render, "FPS: %4.2f", fps); + timeElapsed = currentTime; + frames = 0; + } + + assert(vm != nullptr); + assert(obj != nullptr); + + JavaVMAttachArgs args = {JNI_VERSION_1_2, "NativeMapView::updateFps()", NULL}; + + jint ret; + JNIEnv *env = nullptr; + bool detach = false; + ret = vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6); + if (ret != JNI_OK) { + if (ret != JNI_EDETACHED) { + mbgl::Log::Error(mbgl::Event::JNI, "GetEnv() failed with %i", ret); + throw new std::runtime_error("GetEnv() failed"); + } else { + ret = vm->AttachCurrentThread(&env, &args); + if (ret != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "AttachCurrentThread() failed with %i", ret); + throw new std::runtime_error("AttachCurrentThread() failed"); + } + detach = true; + } + } + + env->CallVoidMethod(obj, onFpsChangedId, fps); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + } + + if (detach) { + if ((ret = vm->DetachCurrentThread()) != JNI_OK) { + mbgl::Log::Error(mbgl::Event::JNI, "DetachCurrentThread() failed with %i", ret); + throw new std::runtime_error("DetachCurrentThread() failed"); + } + } + env = nullptr; +} +} +} diff --git a/android/java/.gitignore b/android/java/.gitignore new file mode 100644 index 000000000..4c79b6763 --- /dev/null +++ b/android/java/.gitignore @@ -0,0 +1,21 @@ +# Gradle files +.gradle/ + +# IntelliJ files +.idea +*.iml + +# Build files +build/ +*/build/ +*.so +*.apk + +# Lib assets +lib/src/main/assets/ + +# Local settings +local.properties + +# Token file +app/src/main/res/raw/token.txt diff --git a/android/java/app/build.gradle b/android/java/app/build.gradle new file mode 100644 index 000000000..e44bbdbd5 --- /dev/null +++ b/android/java/app/build.gradle @@ -0,0 +1,63 @@ +apply plugin: 'com.android.application' + +task accessToken { + def rawDir = new File("app/src/main/res/raw") + rawDir.mkdirs() + def tokenFile = new File("app/src/main/res/raw/token.txt") + String token = System.getenv('MAPBOX_ACCESS_TOKEN') + if (token == null) { + throw new InvalidUserDataException("You must set the MAPBOX_ACCESS_TOKEN environment variable.") + } + tokenFile.write(token) +} + +gradle.projectsEvaluated { + preBuild.dependsOn('accessToken') +} + +android { + compileSdkVersion 21 + buildToolsVersion "21.1.2" + + repositories { + jcenter() + } + + defaultConfig { + applicationId "com.mapbox.mapboxgl.app" + minSdkVersion 14 + targetSdkVersion 21 + versionCode 3 + versionName "0.1.2" + } + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_7 + targetCompatibility JavaVersion.VERSION_1_7 + } + + packagingOptions { + exclude 'META-INF/LICENSE.txt' + exclude 'META-INF/NOTICE.txt' + } + + lintOptions { + checkAllWarnings true + disable 'UnusedAttribute' + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt' + } + } +} + +dependencies { + compile project(':lib') + compile 'com.android.support:support-annotations:21.0.0' + compile 'com.android.support:support-v4:21.0.3' + compile 'com.android.support:appcompat-v7:21.0.3' + compile 'com.mapzen.android:lost:0.2' +} diff --git a/android/java/app/lint.xml b/android/java/app/lint.xml new file mode 100644 index 000000000..94058fd4d --- /dev/null +++ b/android/java/app/lint.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/android/java/app/src/main/AndroidManifest.xml b/android/java/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..deee959d7 --- /dev/null +++ b/android/java/app/src/main/AndroidManifest.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + diff --git a/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java new file mode 100644 index 000000000..c26cdfd7a --- /dev/null +++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MainActivity.java @@ -0,0 +1,270 @@ +package com.mapbox.mapboxgl.app; + +import android.location.Location; +import android.os.Bundle; +import android.support.v7.app.ActionBarActivity; +import android.support.v7.widget.Toolbar; +import android.util.Log; +import android.view.Menu; +import android.view.MenuInflater; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +import android.widget.ImageView; +import android.widget.Spinner; +import android.widget.TextView; +import android.widget.Toast; + +import com.mapbox.mapboxgl.lib.LonLatZoom; +import com.mapbox.mapboxgl.lib.MapView; +import com.mapzen.android.lost.LocationClient; +import com.mapzen.android.lost.LocationListener; +import com.mapzen.android.lost.LocationRequest; + +public class MainActivity extends ActionBarActivity { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "MainActivity"; + + // + // Instance members + // + + // Holds the MapFragment + private MapFragment mMapFragment; + + // The FPS label + private TextView mFpsTextView; + + // The compass + private ImageView mCompassView; + + // Used for GPS + private boolean mIsGpsOn = false; + private LocationClient mLocationClient; + private LocationListener mLocationListener; + private LocationRequest mLocationRequest; + + // + // Lifecycle events + // + + // Called when activity is created + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + Log.v(TAG, "onCreate"); + + // Load the layout + setContentView(R.layout.activity_main); + mMapFragment = (MapFragment)getSupportFragmentManager().findFragmentById(R.id.fragment_map); + mMapFragment.getMap().setOnFpsChangedListener(new MyOnFpsChangedListener()); + mMapFragment.getMap().setOnMapChangedListener(new MyOnMapChangedListener()); + + mFpsTextView = (TextView)findViewById(R.id.view_fps); + mFpsTextView.setText(""); + + mCompassView = (ImageView)findViewById(R.id.view_compass); + mCompassView.setOnClickListener(new CompassOnClickListener()); + + // Add a toolbar as the action bar + Toolbar mainToolbar = (Toolbar)findViewById(R.id.toolbar_main); + setSupportActionBar(mainToolbar); + getSupportActionBar().setDisplayShowTitleEnabled(false); + + // Add the spinner to select map styles + Spinner styleSpinner = (Spinner)findViewById(R.id.spinner_style); + ArrayAdapter styleAdapter = ArrayAdapter.createFromResource(getSupportActionBar().getThemedContext(), + R.array.style_list, android.R.layout.simple_spinner_dropdown_item); + styleSpinner.setAdapter(styleAdapter); + styleSpinner.setOnItemSelectedListener(new StyleSpinnerListener()); + + // Prepare for GPS + mLocationRequest = LocationRequest.create(); + mLocationRequest.setInterval(0); + mLocationRequest.setSmallestDisplacement(0); + + mLocationClient = new LocationClient(getApplicationContext(), new LocationClient.ConnectionCallbacks() { + @Override + public void onConnected(Bundle connectionHint) { + updateLocation(mLocationClient.getLastLocation()); + + mLocationListener = new LocationListener() { + @Override + public void onLocationChanged(Location location) { + //Toast.makeText(getApplicationContext(), "Location: " + location.toString(), Toast.LENGTH_SHORT).show(); + updateLocation(location); + } + }; + + mLocationClient.requestLocationUpdates(mLocationRequest, mLocationListener); + } + + @Override + public void onDisconnected() { + mLocationListener = null; + } + }); + } + + // Called when our app goes into the background + @Override + public void onPause() { + super.onPause(); + Log.v(TAG, "onPause"); + + // Cancel GPS + if (mIsGpsOn) { + mLocationClient.disconnect(); + } + } + + // Called when our app comes into the foreground + @Override + public void onResume() { + super.onResume(); + Log.v(TAG, "onResume"); + + // Restart GPS + // Cancel any outstanding GPS + if (mIsGpsOn) { + mLocationClient.connect(); + } + } + + // + // Other events + // + + // Adds items to the action bar menu + @Override + public boolean onCreateOptionsMenu(Menu menu) { + MenuInflater inflater = getMenuInflater(); + inflater.inflate(R.menu.menu_main, menu); + return super.onCreateOptionsMenu(menu); + } + + // Called when pressing action bar items + @Override + public boolean onOptionsItemSelected(MenuItem item) { + switch (item.getItemId()) { + case R.id.action_gps: + // Toggle GPS position updates + if (mIsGpsOn) { + mIsGpsOn = false; + item.setIcon(R.drawable.ic_action_location_searching); + mLocationClient.disconnect(); + + } else { + mIsGpsOn = true; + item.setIcon(R.drawable.ic_action_location_found); + mLocationClient.connect(); + } + return true; + + case R.id.action_debug: + // Toggle debug mode + mMapFragment.getMap().toggleDebug(); + + // Show the FPS counter + if (mMapFragment.getMap().isDebugActive()) { + mFpsTextView.setVisibility(View.VISIBLE); + mFpsTextView.setText(getResources().getString(R.string.label_fps)); + } else { + mFpsTextView.setVisibility(View.INVISIBLE); + } + return true; + + default: + return super.onOptionsItemSelected(item); + } + } + + // Handles location updates from GPS + private void updateLocation(Location location) { + if (location != null) { + LonLatZoom coordinate = new LonLatZoom(location.getLongitude(), location.getLatitude(), 16); + mMapFragment.getMap().setCenterCoordinate(coordinate, true); + } + } + + // This class handles style change events + private class StyleSpinnerListener implements AdapterView.OnItemSelectedListener { + + @Override + public void onItemSelected(AdapterView parent, View view, int position, long id) { + switch(position) { + // Bright + case 0: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/bright-v6.json"); + break; + + // Basic + case 1: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/basic-v6.json"); + break; + + // Outdoors + case 2: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/outdoors-v6.json"); + break; + + // Satellite + case 3: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/satellite-v6.json"); + break; + + // Pencil + case 4: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/pencil-v6.json"); + break; + + // Empty + case 5: + mMapFragment.getMap().setStyleUrl("asset://styles/styles/empty-v6.json"); + break; + + default: + onNothingSelected(parent); + break; + } + } + + @Override + public void onNothingSelected(AdapterView parent) { + mMapFragment.getMap().setStyleUrl(""); + } + } + + // Called when FPS changes + public class MyOnFpsChangedListener implements MapView.OnFpsChangedListener { + + @Override + public void onFpsChanged(double fps) { + mFpsTextView.setText(getResources().getString(R.string.label_fps) + String.format(" %4.2f", fps)); + } + } + + // Called when map state changes + public class MyOnMapChangedListener implements MapView.OnMapChangedListener { + + @Override + public void onMapChanged() { + mCompassView.setRotation((float)mMapFragment.getMap().getDirection()); + } + } + + // Called when someone presses the compass + public class CompassOnClickListener implements View.OnClickListener { + + @Override + public void onClick(View view) { + mMapFragment.getMap().resetNorth(); + } + } +} diff --git a/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java new file mode 100644 index 000000000..a9e6f0c89 --- /dev/null +++ b/android/java/app/src/main/java/com/mapbox/mapboxgl/app/MapFragment.java @@ -0,0 +1,129 @@ +package com.mapbox.mapboxgl.app; + +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import com.mapbox.mapboxgl.lib.MapView; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +public class MapFragment extends Fragment { + + // + // Static members + // + + // Tag used for logging + private static final String TAG = "MapFragment"; + + // + // Instance members + // + + // The map + private MapView mMap; + + // + // Lifecycle events + // + + // Called when the fragment is created + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + super.onCreateView(inflater, container, savedInstanceState); + Log.v(TAG, "onCreateView"); + + // Create the map + mMap = (MapView) inflater.inflate(R.layout.fragment_main, container, true); + + // Load the access token + try { + BufferedReader reader = new BufferedReader(new InputStreamReader(getActivity().getApplicationContext().getResources().openRawResource(R.raw.token))); + String line = reader.readLine(); + mMap.setAccessToken(line); + } catch (IOException e) { + // Do nothing + } + + // Need to pass on any saved state to the map + mMap.onCreate(savedInstanceState); + + // Return the map as the root view + return mMap; + } + + // Called when the fragment is destroyed + @Override + public void onDestroyView() { + super.onDestroyView(); + Log.v(TAG, "onDestroyView"); + + // Need to pass on to view + mMap.onDestroy(); + mMap = null; + } + + // Called when the fragment is visible + @Override + public void onStart() { + super.onStart(); + Log.v(TAG, "onStart"); + + // Need to pass on to view + mMap.onStart(); + } + + // Called when the fragment is invisible + @Override + public void onStop() { + super.onStop(); + Log.v(TAG, "onStop"); + + // Need to pass on to view + mMap.onStop(); + } + + // Called when the fragment is in the background + @Override + public void onPause() { + super.onPause(); + Log.v(TAG, "onPause"); + + // Need to pass on to view + mMap.onPause(); + } + + // Called when the fragment is no longer in the background + @Override + public void onResume() { + super.onResume(); + Log.v(TAG, "onResume"); + + // Need to pass on to view + mMap.onResume(); + } + + // Called before fragment is destroyed + @Override + public void onSaveInstanceState(Bundle outState) { + Log.v(TAG, "onSaveInstanceState"); + + // Need to retrieve any saved state from the map + mMap.onSaveInstanceState(outState); + super.onSaveInstanceState(outState); + } + + // + // Property methods + // + + public MapView getMap() { + return mMap; + } +} diff --git a/android/java/app/src/main/res/drawable-hdpi/ic_action_about.png b/android/java/app/src/main/res/drawable-hdpi/ic_action_about.png new file mode 100644 index 000000000..077dcec80 Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/ic_action_about.png differ diff --git a/android/java/app/src/main/res/drawable-hdpi/ic_action_location_found.png b/android/java/app/src/main/res/drawable-hdpi/ic_action_location_found.png new file mode 100644 index 000000000..e1bf5807b Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/ic_action_location_found.png differ diff --git a/android/java/app/src/main/res/drawable-hdpi/ic_action_location_searching.png b/android/java/app/src/main/res/drawable-hdpi/ic_action_location_searching.png new file mode 100644 index 000000000..48f4aec5b Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/ic_action_location_searching.png differ diff --git a/android/java/app/src/main/res/drawable-hdpi/icon.png b/android/java/app/src/main/res/drawable-hdpi/icon.png new file mode 100644 index 000000000..f9875161d Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/icon.png differ diff --git a/android/java/app/src/main/res/drawable-hdpi/icon_burned.png b/android/java/app/src/main/res/drawable-hdpi/icon_burned.png new file mode 100644 index 000000000..f9875161d Binary files /dev/null and b/android/java/app/src/main/res/drawable-hdpi/icon_burned.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/compass.png b/android/java/app/src/main/res/drawable-mdpi/compass.png new file mode 100644 index 000000000..58e7e08d2 Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/compass.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/ic_action_about.png b/android/java/app/src/main/res/drawable-mdpi/ic_action_about.png new file mode 100644 index 000000000..624e745b6 Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/ic_action_about.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/ic_action_location_found.png b/android/java/app/src/main/res/drawable-mdpi/ic_action_location_found.png new file mode 100644 index 000000000..33f660f2d Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/ic_action_location_found.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/ic_action_location_searching.png b/android/java/app/src/main/res/drawable-mdpi/ic_action_location_searching.png new file mode 100644 index 000000000..fcb72b882 Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/ic_action_location_searching.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/icon.png b/android/java/app/src/main/res/drawable-mdpi/icon.png new file mode 100644 index 000000000..3c0cc701a Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/icon.png differ diff --git a/android/java/app/src/main/res/drawable-mdpi/icon_burned.png b/android/java/app/src/main/res/drawable-mdpi/icon_burned.png new file mode 100644 index 000000000..3c0cc701a Binary files /dev/null and b/android/java/app/src/main/res/drawable-mdpi/icon_burned.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/ic_action_about.png b/android/java/app/src/main/res/drawable-xhdpi/ic_action_about.png new file mode 100644 index 000000000..3be315270 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/ic_action_about.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_found.png b/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_found.png new file mode 100644 index 000000000..4c06441f9 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_found.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_searching.png b/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_searching.png new file mode 100644 index 000000000..7a3d4984f Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/ic_action_location_searching.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/icon.png b/android/java/app/src/main/res/drawable-xhdpi/icon.png new file mode 100644 index 000000000..0cbf59936 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/icon.png differ diff --git a/android/java/app/src/main/res/drawable-xhdpi/icon_burned.png b/android/java/app/src/main/res/drawable-xhdpi/icon_burned.png new file mode 100644 index 000000000..0cbf59936 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xhdpi/icon_burned.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/ic_action_about.png b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_about.png new file mode 100644 index 000000000..0fe809b90 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_about.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_found.png b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_found.png new file mode 100644 index 000000000..43f18ea01 Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_found.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_searching.png b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_searching.png new file mode 100644 index 000000000..aff020a6b Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/ic_action_location_searching.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/icon.png b/android/java/app/src/main/res/drawable-xxhdpi/icon.png new file mode 100644 index 000000000..6ac3191eb Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/icon.png differ diff --git a/android/java/app/src/main/res/drawable-xxhdpi/icon_burned.png b/android/java/app/src/main/res/drawable-xxhdpi/icon_burned.png new file mode 100644 index 000000000..6ac3191eb Binary files /dev/null and b/android/java/app/src/main/res/drawable-xxhdpi/icon_burned.png differ diff --git a/android/java/app/src/main/res/layout/activity_main.xml b/android/java/app/src/main/res/layout/activity_main.xml new file mode 100644 index 000000000..d03aae6af --- /dev/null +++ b/android/java/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + diff --git a/android/java/app/src/main/res/layout/fragment_main.xml b/android/java/app/src/main/res/layout/fragment_main.xml new file mode 100644 index 000000000..311242472 --- /dev/null +++ b/android/java/app/src/main/res/layout/fragment_main.xml @@ -0,0 +1,4 @@ + + diff --git a/android/java/app/src/main/res/menu/menu_main.xml b/android/java/app/src/main/res/menu/menu_main.xml new file mode 100644 index 000000000..bf6dbbf36 --- /dev/null +++ b/android/java/app/src/main/res/menu/menu_main.xml @@ -0,0 +1,11 @@ + + + + + diff --git a/android/java/app/src/main/res/values/strings.xml b/android/java/app/src/main/res/values/strings.xml new file mode 100644 index 000000000..7f7e63cfb --- /dev/null +++ b/android/java/app/src/main/res/values/strings.xml @@ -0,0 +1,23 @@ + + + + Mapbox GL + + Toggle GPS location + + Toggle debug mode + + FPS: + + Map compass. Click to reset the map rotation to North. + + + Bright + Basic + Outdoors + Satellite + + + + + diff --git a/android/java/app/src/main/res/values/styles.xml b/android/java/app/src/main/res/values/styles.xml new file mode 100644 index 000000000..2399c78be --- /dev/null +++ b/android/java/app/src/main/res/values/styles.xml @@ -0,0 +1,5 @@ + + +